Предисловие
Данная статья написана в качестве рефлексии по поводу выполнения лабораторной работы. Поскольку упор делался на написание рабочей нейронной сети, все приведенные формулы не будут доказываться. Если же вам интересен математический аппарат, то я изучал его по этой статье.
Решаемая задача
Решаемая задача звучит примерно следующим образом: На вход подается картинка размером 7х7, необходимо определить, что на ней нарисовано — круг, квадрат или треугольник.
Вопрос смещения фигуры относительно картинки оставим за скобками, в наших входных данных они всегда будут находиться в центре. Тем не менее на картинках может присутствовать шум, затрудняющий решение задачи.
Теоретическая составляющая
— выход нейрона i слоя k — количество нейронов в слое с номером k — 1 — ожидаемое значение выхода i сети — номер последнего слоя сети — вес нейрона в соответствующей связи, k — номер слоя, i — номер нейрона, j — номер входа из предыдущего слоя
Для лучшего понимания обозначений выходов нейронов и весов приведу простую картинку.
Каждое значение нейрона на выходном слое соответствует вероятности того, что на картинке нарисована соответствующая фигура. Поскольку возможных фигур у нас 3, то выходных нейронов будет тоже 3. На входе же у нас картинки размером 7 на 7, поэтому входов будет 49, соответствующих каждому пикселю изображения соответственно.
Логика работы сети предельно простая — нам надо рассчитать значения всех нейронов.
Объясняю эту формулу — мы берем все нейроны на предыдущем уровне и умножаем их на соответствующие веса, которые выходят из нейронов на прошлом уровне и доходят до нашего нейрона. Затем мы полученную сумму загоняем в функцию активации, это нужно для того, чтобы значения нейронов не превышали 1. В нашем случае, в качестве функции активации будем использовать сигмоиду:
Итак, мы подсчитали, значения выходных нейронов, пройдя по всем слоям, но выходы, естественно, не совпали с нашими ожиданиями касаемо значений вероятности. Что же делать? Подстраивать веса таким образом, чтобы когда в следующий раз мы прогоним то же изображение, сеть дала немного более точный результат.
Альфа — коэффициент обучения, чем ближе фактические значения к ожидаемым, тем меньше нужно делать альфа.
Как вы могли заметить тут также появилось новое, ещё не известное нам обозначение дельта, его расчёт осуществляется по следующему правилу:
Для всех остальных слоев формула основывается на значениях предыдущего слоя:
В общем, тут алгоритм подсчета дельт примерно такой же, как для подсчёта нейронов, только с другими формулами и в обратную сторону.
Практическая составляющая
Весь функционал нейронной сети будем хранить в классе NeuralNet. Для начала напишем его интерфейс, а затем углубляться в реализацию.
Начнем с простого и будем двигаться к более сложному, напишем функции для обучения и валидации:
Часто задаваемые вопросы о создании нейросети на C
Вопрос: Можно ли создать полноценную нейросеть на чистом C?
Ответ: Да, это возможно. Язык C предоставляет полный контроль над памятью и вычислениями, что позволяет эффективно реализовать все компоненты нейросети, от структуры данных до алгоритмов обучения.
Вопрос: С чего начать написание кода нейросети на C?
Ответ: Начните с проектирования структур данных для хранения весов, смещений и активаций нейронов. Затем реализуйте базовые операции: прямое распространение, функцию потерь и обратное распространение ошибки.
Вопрос: Какие математические знания необходимы?
Ответ: Потребуется понимание линейной алгебры (операции с матрицами и векторами), математического анализа (производные для градиентного спуска) и основ теории вероятностей.
Вопрос: Как реализовать обратное распространение (backpropagation) на C?
Ответ: Нужно запрограммировать вычисление градиентов функции потерь по всем параметрам сети, используя цепное правило. Это включает в себя последовательный расчет производных от выходного слоя к входному.
Вопрос: Где взять данные для обучения?
Ответ: Данные можно сгенерировать искусственно для тестирования или использовать открытые датасеты (например, MNIST для распознавания цифр), предварительно загрузив и преобразовав их в бинарный или текстовый формат, понятный вашей программе.
Вопрос: Как ускорить вычисления в нейросети на C?
Ответ: Используйте оптимизации: циклы без ветвлений, SIMD-инструкции (SSE, AVX), параллельные вычисления (OpenMP) и хранение данных в непрерывных массивах для эффективного кэширования.
Вопрос: Как выбрать функцию активации?
Ответ: Для скрытых слоев часто используют ReLU из-за простоты вычисления и борьбы с затухающим градиентом. Для выходного слоя выбор зависит от задачи: softmax для классификации, линейная функция для регрессии.
Вопрос: Как отлаживать нейросеть, написанную на C?
Ответ: Проверяйте корректность на маленьких синтетических датасетах, где известен ожидаемый результат. Визуализируйте промежуточные значения и градиенты, пишите модульные тесты для каждой функции (матричного умножения, вычисления активации и т.д.).
Вопрос: Чем реализация на C лучше использования готовых библиотек (TensorFlow, PyTorch)?
Ответ: Реализация на C дает глубокое понимание внутреннего устройства нейросетей, полный контроль над производительностью и памятью, а также возможность встраивания в системы с ограниченными ресурсами.
Вопрос: Какие типовые ошибки допускают новички при написании нейросети на C?
Ответ: Ошибки выделения/освобождения памяти, некорректная инициализация весов (например, нулями), путаница с размерностями матриц при операциях, неправильная реализация производных функций активации и отсутствие нормализации входных данных.
Краткий чек-лист: путь от идеи до работающей нейросети на C
- Четко определите задачу, которую должна решать нейросеть (классификация, регрессия).
- Изучите математические основы: градиентный спуск, обратное распространение, функции активации.
- Спроектируйте архитектуру сети: количество слоев и нейронов в каждом.
- Создайте структуры данных в C для хранения весов, смещений и активаций.
- Напишите функцию инициализации параметров сети (например, методом Xavier).
- Реализуйте алгоритм прямого распространения сигнала (forward pass).
- Реализуйте функцию вычисления ошибки (loss function).
- Реализуйте алгоритм обратного распространения ошибки (backward pass) для вычисления градиентов.
- Напишите функцию обновления весов по градиенту (оптимизатор, например, SGD).
- Подготовьте или сгенерируйте набор данных для обучения и тестирования.
- Настройте гиперпараметры: скорость обучения, количество эпох, размер батча.
- Реализуйте цикл обучения, выводящий метрики на каждой эпохе.
- Протестируйте обученную модель на отдельной тестовой выборке.
- Добавьте возможность сохранения обученных весов в файл и загрузки из него.
- Проведите оптимизацию кода для повышения скорости вычислений.




























