суббота, 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), но эта реализация помогает понять основы работы нейросетей "под капотом".


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

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