понедельник, 28 апреля 2025 г.

Сверточная нейросеть на си




////////

 Вот пример реализации простой сверточной нейронной сети на языке C для распознавания рукописных цифр (аналогично LeNet-5):


```c

#include <stdio.h>

#include <stdlib.h>

#include <math.h>

#include <time.h>


// Размеры входного изображения

#define INPUT_SIZE 28

#define INPUT_CHANNELS 1


// Параметры сверточного слоя

#define CONV1_FILTERS 6

#define CONV1_KERNEL_SIZE 5

#define CONV1_OUTPUT_SIZE (INPUT_SIZE - CONV1_KERNEL_SIZE + 1)


// Параметры пулинга

#define POOL1_SIZE 2

#define POOL1_OUTPUT_SIZE (CONV1_OUTPUT_SIZE / POOL1_SIZE)


// Параметры полносвязных слоев

#define DENSE1_SIZE 120

#define DENSE2_SIZE 10


// Прототипы функций

void convolve2d(float* input, float* output, float filters[CONV1_FILTERS][CONV1_KERNEL_SIZE][CONV1_KERNEL_SIZE], float biases[CONV1_FILTERS]);

void maxpool2d(float* input, float* output);

void dense_layer(float* input, float* output, int input_size, int output_size, float weights[], float biases[]);

void relu(float* arr, int size);

void softmax(float* arr, int size);


int main() {

    srand(time(0));

    

    // Инициализация входных данных (пример)

    float input[INPUT_SIZE][INPUT_SIZE];

    for(int i = 0; i < INPUT_SIZE; i++)

        for(int j = 0; j < INPUT_SIZE; j++)

            input[i][j] = (float)rand() / RAND_MAX;  // Случайные значения 0-1


    // Сверточный слой

    float conv1_filters[CONV1_FILTERS][CONV1_KERNEL_SIZE][CONV1_KERNEL_SIZE];

    float conv1_biases[CONV1_FILTERS] = {0};

    float conv1_output[CONV1_FILTERS][CONV1_OUTPUT_SIZE][CONV1_OUTPUT_SIZE] = {0};

    

    // Инициализация весов

    for(int f = 0; f < CONV1_FILTERS; f++)

        for(int i = 0; i < CONV1_KERNEL_SIZE; i++)

            for(int j = 0; j < CONV1_KERNEL_SIZE; j++)

                conv1_filters[f][i][j] = (float)rand() / RAND_MAX * 2 - 1;  // Веса -1..1


    convolve2d((float*)input, (float*)conv1_output, conv1_filters, conv1_biases);

    relu((float*)conv1_output, CONV1_FILTERS * CONV1_OUTPUT_SIZE * CONV1_OUTPUT_SIZE);


    // Слой пулинга

    float pool1_output[CONV1_FILTERS][POOL1_OUTPUT_SIZE][POOL1_OUTPUT_SIZE];

    maxpool2d((float*)conv1_output, (float*)pool1_output);


    // Полносвязные слои

    float dense1_weights[DENSE1_SIZE][CONV1_FILTERS * POOL1_OUTPUT_SIZE * POOL1_OUTPUT_SIZE];

    float dense1_biases[DENSE1_SIZE] = {0};

    float dense1_output[DENSE1_SIZE] = {0};

    

    float dense2_weights[DENSE2_SIZE][DENSE1_SIZE];

    float dense2_biases[DENSE2_SIZE] = {0};

    float output[DENSE2_SIZE] = {0};


    // Прямое распространение

    dense_layer((float*)pool1_output, dense1_output, 

                CONV1_FILTERS * POOL1_OUTPUT_SIZE * POOL1_OUTPUT_SIZE, DENSE1_SIZE,

                (float*)dense1_weights, dense1_biases);

    relu(dense1_output, DENSE1_SIZE);


    dense_layer(dense1_output, output, DENSE1_SIZE, DENSE2_SIZE,

                (float*)dense2_weights, dense2_biases);

    softmax(output, DENSE2_SIZE);


    // Вывод результатов

    printf("Output probabilities:\n");

    for(int i = 0; i < DENSE2_SIZE; i++)

        printf("%d: %.3f\n", i, output[i]);


    return 0;

}


// Сверточный слой

void convolve2d(float* input, float* output, 

               float filters[CONV1_FILTERS][CONV1_KERNEL_SIZE][CONV1_KERNEL_SIZE],

               float biases[CONV1_FILTERS]) {

    for(int f = 0; f < CONV1_FILTERS; f++) {

        for(int i = 0; i < CONV1_OUTPUT_SIZE; i++) {

            for(int j = 0; j < CONV1_OUTPUT_SIZE; j++) {

                float sum = biases[f];

                for(int ki = 0; ki < CONV1_KERNEL_SIZE; ki++) {

                    for(int kj = 0; kj < CONV1_KERNEL_SIZE; kj++) {

                        int input_idx = (i + ki) * INPUT_SIZE + (j + kj);

                        sum += input[input_idx] * filters[f][ki][kj];

                    }

                }

                output[f * CONV1_OUTPUT_SIZE * CONV1_OUTPUT_SIZE + i * CONV1_OUTPUT_SIZE + j] = sum;

            }

        }

    }

}


// Макс-пулинг

void maxpool2d(float* input, float* output) {

    for(int c = 0; c < CONV1_FILTERS; c++) {

        for(int i = 0; i < POOL1_OUTPUT_SIZE; i++) {

            for(int j = 0; j < POOL1_OUTPUT_SIZE; j++) {

                float max_val = -INFINITY;

                for(int pi = 0; pi < POOL1_SIZE; pi++) {

                    for(int pj = 0; pj < POOL1_SIZE; pj++) {

                        int idx = c * CONV1_OUTPUT_SIZE * CONV1_OUTPUT_SIZE + 

                                 (i * POOL1_SIZE + pi) * CONV1_OUTPUT_SIZE + 

                                 (j * POOL1_SIZE + pj);

                        if(input[idx] > max_val) max_val = input[idx];

                    }

                }

                output[c * POOL1_OUTPUT_SIZE * POOL1_OUTPUT_SIZE + i * POOL1_OUTPUT_SIZE + j] = max_val;

            }

        }

    }

}


// Полносвязный слой

void dense_layer(float* input, float* output, int input_size, int output_size, 

                float weights[], float biases[]) {

    for(int i = 0; i < output_size; i++) {

        output[i] = biases[i];

        for(int j = 0; j < input_size; j++) {

            output[i] += input[j] * weights[i * input_size + j];

        }

    }

}


// Функция активации ReLU

void relu(float* arr, int size) {

    for(int i = 0; i < size; i++)

        if(arr[i] < 0) arr[i] = 0;

}


// Функция активации Softmax

void softmax(float* arr, int size) {

    float max = arr[0];

    float sum = 0;

    

    for(int i = 0; i < size; i++)

        if(arr[i] > max) max = arr[i];

    

    for(int i = 0; i < size; i++) {

        arr[i] = exp(arr[i] - max);

        sum += arr[i];

    }

    

    for(int i = 0; i < size; i++)

        arr[i] /= sum;

}

```


Этот код демонстрирует:

1. Сверточный слой с 6 фильтрами 5x5

2. Слой макс-пулинга 2x2

3. Два полносвязных слоя

4. Функции активации ReLU и Softmax

5. Инициализацию случайными весами


Особенности реализации:

- Используются фиксированные размеры слоев

- Веса инициализируются случайными значениями

- Нет механизма обучения (только прямое распространение)

- Оптимизировано для читаемости, а не производительности


Для использования в реальных задачах потребуется:

1. Добавить загрузку данных (например, MNIST)

2. Реализовать обратное распространение и обучение

3. Добавить проверки ошибок

4. Оптимизировать вычисления

5. Реализовать динамическое выделение памяти


Этот пример служит отправной точкой для понимания принципов работы CNN на низкоуровневом языке.

Комментариев нет:

Отправить комментарий