понедельник, 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 на низкоуровневом языке.

суббота, 26 апреля 2025 г.

 

Программирование нейронной сети на языке C


DeepSeek

 требует понимания основ линейной алгебры, механики нейросетей и работы с памятью. Ниже приведен пример реализации простой однослойной нейросети (перцептрона) для решения задачи бинарной классификации.


---


### 1. Базовые концепции

- **Нейрон**: Вычисляет взвешенную сумму входов + смещение (bias), применяет функцию активации.

- **Обучение**: Корректировка весов через градиентный спуск (или метод обратного распространения ошибки).


---


### 2. Пример реализации (однослойный перцептрон)

#### Шаг 1: Определение структуры нейрона

```c

typedef struct {

    double *weights; // Веса (включая смещение)

    int num_inputs;   // Количество входов (без учета смещения)

} Perceptron;

```


#### Шаг 2: Инициализация перцептрона

```c

#include <stdlib.h>

#include <stdio.h>

#include <math.h>


// Функция инициализации

void init_perceptron(Perceptron *p, int num_inputs) {

    p->num_inputs = num_inputs;

    p->weights = (double*)malloc((num_inputs + 1) * sizeof(double)); // +1 для смещения


    // Инициализация случайными значениями (-1 до 1)

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

        p->weights[i] = (double)rand() / RAND_MAX * 2.0 - 1.0;

    }

}

```


#### Шаг 3: Функция активации (сигмоида)

```c

double sigmoid(double x) {

    return 1.0 / (1.0 + exp(-x));

}

```


#### Шаг 4: Вычисление выхода

```c

double predict(Perceptron *p, double *inputs) {

    double sum = p->weights[0]; // Смещение (bias)

    for (int i = 0; i < p->num_inputs; i++) {

        sum += inputs[i] * p->weights[i + 1];

    }

    return sigmoid(sum);

}

```


#### Шаг 5: Обучение (метод обратного распространения)

```c

void train(Perceptron *p, double *inputs, double target, double learning_rate) {

    double prediction = predict(p, inputs);

    double error = target - prediction;


    // Обновление весов (включая смещение)

    p->weights[0] += error * learning_rate; // Обновление смещения

    for (int i = 0; i < p->num_inputs; i++) {

        p->weights[i + 1] += error * inputs[i] * learning_rate;

    }

}

```


---


### 3. Пример использования

```c

int main() {

    srand(time(NULL));


    // Создание перцептрона с 2 входами

    Perceptron p;

    init_perceptron(&p, 2);


    // Данные для обучения (логическое И)

    double inputs[4][2] = {{0, 0}, {0, 1}, {1, 0}, {1, 1}};

    double targets[4] = {0, 0, 0, 1};


    // Обучение (1000 эпох)

    for (int epoch = 0; epoch < 1000; epoch++) {

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

            train(&p, inputs[i], targets[i], 0.1);

        }

    }


    // Тестирование

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

        printf("Input: [%f, %f] -> Output: %f\n", 

               inputs[i][0], inputs[i][1], predict(&p, inputs[i]));

    }


    free(p.weights);

    return 0;

}

```


---


### 4. Компиляция и запуск

```bash

gcc perceptron.c -lm -o perceptron && ./perceptron

```


---


### 5. Что можно улучшить?

1. **Многослойные сети**: Добавить скрытые слои и реализовать полноценный backpropagation.

2. **Матричные операции**: Использовать библиотеки вроде BLAS для ускорения вычислений.

3. **Регуляризация**: Добавить L1/L2-регуляризацию для предотвращения переобучения.

4. **Пакетная обработка**: Реализовать обновление весов на мини-батчах.


---


### 6. Ограничения C для нейросетей

- Нет встроенной поддержки GPU.

- Требуется ручное управление памятью.

- Нет библиотек уровня PyTorch/TensorFlow (но можно интегрировать через API).


Для сложных проектов лучше использовать Python, но C подходит для изучения основ и embedded-решений.

///////////////


Реализация многослойной нейронной сети (Multilayer Perceptron, MLP) на C сложнее, но возможна. Приведу пример архитектуры с одним скрытым слоем и backpropagation.


---


### 1. Структуры данных

```c

#include <stdlib.h>

#include <stdio.h>

#include <math.h>

#include <time.h>


// Структура слоя

typedef struct {

    double **weights;    // Матрица весов [output_size x (input_size + 1)]

    int input_size;      // Размер входа (без bias)

    int output_size;     // Размер выхода

} Layer;


// Структура сети

typedef struct {

    Layer *layers;       // Массив слоев

    int num_layers;      // Количество слоев

} MLP;

```


---


### 2. Базовые функции

#### Инициализация слоя:

```c

Layer create_layer(int input_size, int output_size) {

    Layer l;

    l.input_size = input_size;

    l.output_size = output_size;

    

    // Выделение памяти: +1 для bias

    l.weights = (double**)malloc(output_size * sizeof(double*));

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

        l.weights[i] = (double*)malloc((input_size + 1) * sizeof(double));

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

            l.weights[i][j] = (double)rand() / RAND_MAX * 2.0 - 1.0;

        }

    }

    return l;

}

```


#### Функции активации:

```c

double sigmoid(double x) { return 1.0 / (1.0 + exp(-x)); }

double dsigmoid(double x) { return x * (1.0 - x); }  // Производная

```


---


### 3. Прямое распространение (Forward Pass)

```c

double* forward(Layer *layer, double *input) {

    double *output = (double*)malloc(layer->output_size * sizeof(double));

    

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

        double sum = layer->weights[i][0];  // Bias term

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

            sum += input[j] * layer->weights[i][j+1];

        }

        output[i] = sigmoid(sum);

    }

    return output;

}

```


---


### 4. Обратное распространение (Backpropagation)

#### Вычисление градиентов:

```c

void backward(

    Layer *layer,

    double *input,

    double *output_error,

    double learning_rate,

    double *gradient_prev

) {

    // Вычисляем градиенты для предыдущего слоя

    for (int i = 0; i < layer->input_size; i++) {

        gradient_prev[i] = 0.0;

        for (int j = 0; j < layer->output_size; j++) {

            gradient_prev[i] += output_error[j] * layer->weights[j][i+1];

        }

        gradient_prev[i] *= dsigmoid(input[i]);

    }


    // Обновляем веса

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

        layer->weights[i][0] += output_error[i] * learning_rate;  // Bias

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

            layer->weights[i][j+1] += 

                learning_rate * output_error[i] * input[j];

        }

    }

}

```


---


### 5. Пример сети (2-2-1) для XOR

#### Создание сети:

```c

MLP create_mlp() {

    MLP net;

    net.num_layers = 2;

    net.layers = (Layer*)malloc(2 * sizeof(Layer));

    

    // Скрытый слой: 2 входных нейрона, 2 выходных

    net.layers[0] = create_layer(2, 2);

    

    // Выходной слой: 2 входных, 1 выходной

    net.layers[1] = create_layer(2, 1);

    

    return net;

}

```


#### Обучение:

```c

void train(MLP *net, double *input, double target, double lr) {

    // Forward pass

    double *h1 = forward(&net->layers[0], input);

    double *output = forward(&net->layers[1], h1);

    

    // Ошибка выходного слоя

    double output_error = (target - output[0]) * dsigmoid(output[0]);

    

    // Backprop для выходного слоя

    double grad1[2];

    backward(&net->layers[1], h1, &output_error, lr, grad1);

    

    // Backprop для скрытого слоя

    double grad0[2];

    backward(&net->layers[0], input, grad1, lr, grad0);

    

    free(h1);

    free(output);

}

```


---


### 6. Тестирование

```c

int main() {

    srand(time(NULL));

    

    MLP net = create_mlp();

    double inputs[4][2] = {{0,0}, {0,1}, {1,0}, {1,1}};

    double targets[4] = {0, 1, 1, 0};  // XOR

    

    // Обучение

    for (int epoch = 0; epoch < 10000; epoch++) {

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

            train(&net, inputs[i], targets[i], 0.1);

        }

    }

    

    // Тестирование

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

        double *h1 = forward(&net.layers[0], inputs[i]);

        double *output = forward(&net.layers[1], h1);

        printf("Input: [%d, %d] -> Output: %.4f\n", 

            (int)inputs[i][0], (int)inputs[i][1], output[0]);

        free(h1);

        free(output);

    }

    

    // Освобождение памяти

    for (int l = 0; l < net.num_layers; l++) {

        for (int i = 0; i < net.layers[l].output_size; i++) {

            free(net.layers[l].weights[i]);

        }

        free(net.layers[l].weights);

    }

    free(net.layers);

    

    return 0;

}

```


---


### 7. Особенности реализации

1. **Архитектура**: Сеть 2-2-1 (2 входа, 2 нейрона в скрытом слое, 1 выход)

2. **Функция активации**: Сигмоида для всех слоев

3. **Обучение**: SGD (стохастический градиентный спуск)

4. **Loss**: MSE (Mean Squared Error)


---


### 8. Что можно улучшить

1. **Мини-батчи**: Реализовать пакетное обновление весов

2. **Регуляризация**: Добавить L2-регуляризацию

3. **Оптимизаторы**: Реализовать Adam или RMSprop

4. **GPU**: Использовать OpenCL/CUDA для ускорения

5. **Сохраниение/загрузка**: Добавить функции для сохранения весов


---


### 9. Ограничения

1. **Производительность**: Чистый C медленнее, чем оптимизированные библиотеки

2. **Отладка**: Нет встроенных инструментов визуализации

3. **Гибкость**: Архитектура жестко задана в коде


Для серьезных проектов лучше использовать готовые фреймворки (TensorFlow, PyTorch), но эта реализация помогает понять основы работы нейросетей "под капотом".