////////
Вот пример реализации простой сверточной нейронной сети на языке 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 на низкоуровневом языке.