Нейронные сети сегодня уже не какая-то магия из научных статей. Они стоят за рекомендациями в сервисах, распознаванием изображений и, конечно, за LLM-моделями, которыми мы пользуемся каждый день. Но знакомство с ними у многих происходит через готовые библиотеки такие, как PyTorch или TensorFlow: написал пару строк кода — модель обучилась — всё работает. А что именно произошло внутри обычно остаётся загадкой.
Feed-forward нейронная сеть (FNN) — одна из самых базовых архитектур, на основе которой исторически выросли более сложные модели: DNN, CNN и многие другие современные подходы. Хотя сама по себе она редко используется в практических задачах в чистом виде, именно через неё проще всего понять фундаментальные принципы обучения нейросетей.
В этой статье мы реализуем нейронную сеть прямого распространения с нуля, используя только Python и NumPy — без готовых ML-фреймворков. Такой подход позволяет на практике увидеть, как работают основные концепции и принципы нейронных сетей. Погружаясь одновременно в математику и программирование, вы сможете получить более глубокое понимание того, что происходит внутри модели во время обучения и предсказаний.
Эта реализация станет основой для дальнейшего изучения: по мере освоения материала можно экспериментировать с более сложными архитектурами, различными функциями активации и методами обучения, улучшая свои собственные модели. Статья рассчитана на читателей с базовым пониманием линейной алгебры и Python, и ее цель — показать, как ключевые математические идеи превращаются в работающий код.
Feed-forward neural networks (FNN) стали одной из первых практически применимых архитектур, позволяющих обучать многослойные модели с использованием алгоритма обратного распространения ошибки (backpropagation).
FNN являются базовой архитектурой, на основе которой были разработаны более сложные модели:
Deep Neural Networks (DNN) — по сути те же FNN сети, но с большим количеством скрытых слоев.
Convolutional Neural Networks (CNN) — специализированные сети для обработки данных с пространственной структурой.
Recurrent Neural Networks (RNN) — работают с последовательностями данных.
Generative Adversarial Networks (GAN) — используются для генерации новых данных.
Transformer-based models — современные архитектуры для обработки последовательностей и языка.
Рассмотрим архитектуру FNN.
Архитектура нейронной сети прямого распространения (FNN)
Нейронная сеть состоит из нескольких слоёв:
Входной слой (input) — принимает исходные данные.
Скрытые слои (hidden) — обрабатывают информацию внутри сети.
Выходной слой (output) — выдаёт результат.
Каждый слой нейронной сети содержит несколько нейронов. В классической FNN архитектуре каждый нейрон одного слоя соединен со всеми нейронами следующего слоя. Нейроны представлены узлами графа, а связи между ними — направленными ребрами с числовыми весами.

Во время обучения сеть корректирует веса связей, минимизируя функцию ошибки. Этот процесс продолжается до тех пор, пока качество предсказаний не станет удовлетворительным или не будет достигнут критерий остановки обучения.
Как работают веса в нейросети
Первый слой сети — входной слой. Он может содержать произвольное количество нейронов в зависимости от размерности входных данных. Каждый нейрон входного слоя соединён с нейронами следующего слоя посредством связей (ребер), и каждому соединению соответствует свой вес w.
Мы передаем данные xi на входной слой. Разумеется, сначала нам нужно преобразовать данные в числовые значения, например, если у нас есть изображение, то мы можем принимать значения интенсивности пикселей в качестве входных данных.
Вес соединения определяет силу влияния одного нейрона на другой. Веса могут усиливать или ослаблять входные сигналы:
Если
w>1, сигнал усиливается.Если
w<1, сигнал ослабляется.
На начальном этапе обучения веса обычно инициализируются случайными значениями, чаще всего в диапазоне [-1, 1]. В процессе обучения веса корректируются с помощью алгоритма обратного распространения ошибки (backpropagation) для минимизации ошибки модели.

Функция суммирования
Каждый нейрон получает входные сигналы от предыдущего слоя. Функция суммирования вычисляет взвешенную сумму входов:
Этот процесс позволяет модели учитывать вклад каждого входного признака и выявлять зависимости между входными и выходными данными. Однако на этом этапе сеть способна моделировать только линейные зависимости.
Функция активации
Для моделирования более сложных зависимостей используется функция активации. Она применяется к результату суммирования:
Функция активации выполняет несколько ключевых задач:
Вносит нелинейность в модель. Без неё сеть сводилась бы к линейной модели независимо от количества слоев.
Позволяет убрать симметрию между нейронами, если все веса были инициализированы одинаковыми значениями.
Обеспечивает возможность аппроксимации сложных зависимостей. Согласно теореме об универсальной аппроксимации, нейронная сеть, содержащая хотя бы один скрытый слой и нелинейные функции активации, способна аппроксимировать любую непрерывную функцию с заданной точностью.
Рассмотрим набор данных с двумя признаками x1 и x2 и двумя классами — например, зелёные и жёлтые точки.
Представим набор данных с двумя признаками x1 и x2, где объекты принадлежат к двум классам, например, зеленым и желтым точкам на плоскости.

В простейшем случае классы можно разделить одной прямой линией, и тогда линейная модель успешно решает задачу классификации.

Однако на практике данные часто расположены сложнее: ни одна прямая не способна корректно разделить классы. Такие задачи называются нелинейно разделимыми, и линейная модель в этом случае неизбежно допускает ошибки независимо от выбора параметров.
Чтобы научиться строить более сложные границы разделения, нейронной сети необходим источник нелинейности. Эту роль выполняют функции активации, именно они добавляют модели нелинейность и позволяют скрытым слоям «искривлять» пространство признаков, формируя сложные границы между классами.

Сигмоидальная функция
Одной из наиболее распространенных функций активации является сигмоидальная функция (sigmoid). Она вычисляется по формуле:

Сигмоида отображает любое вещественное значение x в диапазон [0, 1]. Это свойство делает её особенно удобной для задач бинарной классификации. Независимо от значения по оси x, на выходе мы всегда получаем число от 0 до 1. Благодаря этому выходное значение можно интерпретировать как вероятность принадлежности объекта к положительному классу: например, 0 соответствует одному классу, а 1 — другому.
Сигмоидальная функция обладает гладким градиентом и чётко определённой производной, что важно для корректной работы алгоритма обратного распространения ошибки. Производная сигмоиды выражается через саму функцию:
Несмотря на широкое применение, у сигмоиды есть ряд недостатков:
Проблема исчезающего градиента. Для очень больших положительных или отрицательных входных значений функция насыщается: её производная стремится к 1 или 0. В результате градиенты становятся крайне малыми, что замедляет обновление весов во время обучения или даже практически останавливает его.
Не центрированность относительно нуля. Выход сигмоиды всегда находится в диапазоне [0, 1], то есть принимает только положительные значения. Это приводит к тому, что градиенты для весов могут иметь одинаковый знак, что замедляет процесс сходимости и делает обучение менее эффективным.
Функция гиперболического тангенса
Альтернатива сигмоиде. Данная функция преобразует любые вещественные входные значения в диапазон [-1, 1]. Вычисляется по формуле:

Главное преимущество этой функции по сравнению с сигмоидной заключается в том, что она центрирована относительно нуля. Это означает, что её выходные значения распределены симметрично вокруг нуля, что способствует более стабильному и быстрому обучению модели. Производная функции гиперболического тангенса выражается следующим образом:
Как и в случае сигмоиды, производная удобно выражается через значение самой функции, что упрощает вычисления при использовании алгоритма обратного распространения ошибки.
Тем не менее функция гиперболического тангенса не лишена недостатков: аналогично сигмоиде, она подвержена проблеме исчезающего градиента.
Обучение XOR
Рассмотрим реализацию логического оператора XOR, как пример применения FNN. Задача XOR — это классический пример нелинейно разделимой проблемы, которую невозможно решить простой линейной моделью.
Количество нейронов во входном слое соответствует числу признаков. В случае оператора XOR вход состоит из двух бинарных переменных (0 или 1), следовательно, входной слой содержит 2 нейрона. Все нейроны входного слоя полностью соединены с нейронами скрытого слоя.
Поскольку результат XOR — это одно значение (0 или 1), выходной слой содержит 1 нейрон.

На начальном этапе веса всех соединений инициализируются случайными значениями, как правило, в диапазоне [-1, 1]. Это необходимо для того, чтобы нейроны обучались различным признакам, а не выполняли одинаковые вычисления.

Рассмотрим первый обучающий пример: входные значения x1=0 и x2=0
На первом этапе вычисляется взвешенная сумма входных сигналов.
Далее к полученному значению применяется функция активации, в результате чего определяется выход (активация) нейрона следующего слоя.
Выход нейрона можно записать в общем виде:
где:
xi — входные значения,
wi — соответствующие веса,
f — функция активации,
y — выход нейрона.

После первой итерации для входа (0, 0) правильный ответ равен 0. Однако модель выдала 0.48. Поскольку предсказание отличается от истинного значения, вычисляется значение функции потерь.
Эта ошибка используется в алгоритме обратного распространения ошибки (backpropagation) для корректировки весов сети. Процесс повторяется для всех обучающих примеров до тех пор, пока модель не научится выдавать корректные предсказания.
Для вычисления значения функции потерь мы должны учесть все обучающие данные.
Под спойлером изображены результаты вычислений для оставшихся наборов данных.



Нейрон смещения (bias)
В каждый слой, кроме output, добавим дополнительный bias нейрон со значением 1. Используется для лучшего соответствия данных. Обновленная формула для вычисления выхода нейрона:

В первую очередь, проблема при отсутствии нейронов смещения заключается в том, что функции активации, такие как сигмоидная функция, имеют «естественный» центр или пороговое значение (0.5). Если входной сигнал очень мал, значение сигмоидной функции близко к 0. Если входной сигнал очень велик, значение функции близко к 1.
Bias-нейрон позволяет функции активации смещаться горизонтально, обеспечивая лучшее соответствие данным. Благодаря смещению сеть может моделировать функции, которые не проходят через начало координат, что повышает её способность адаптироваться к различным наборам данных.
Функция потерь
Функция потерь измеряет ошибку прогнозирования нейронной сети — то есть различие между истинным значением (training label) и предсказанием модели.
Существует несколько видов функций потерь. Выбор зависит от типа задачи (регрессия, классификация и т.д.). Одной из базовых является функция среднеквадратичной ошибки (MSE). Формула MSE:
где:
— истинное значение,
— предсказание модели,
а N — количество примеров в выборке.
Сначала вычисляется разность между истинным значением и предсказанием. Затем эта разность возводится в квадрат — это необходимо для того, чтобы:
Исключить влияние знака (ошибка не должна компенсироваться за счет отрицательных значений).
Усилить влияние больших ошибок.
После этого вычисляется среднее значение по всем примерам.
Обучение модели заключается в минимизации функции потерь. На каждой итерации алгоритм корректирует веса так, чтобы значение функции потерь уменьшалось. Процесс продолжается до тех пор, пока ошибка остаётся высокой или пока не будет достигнут критерий остановки (например, заданное число эпох или достижение минимального значения ошибки).
Недостаток данного подхода заключается в том, что MSE чувствительна к выбросам. Это связано с возведением ошибки в квадрат: большие отклонения увеличиваются непропорционально сильно, что может привести к тому, что модель будет чрезмерно подстраиваться под аномальные значения, ухудшая обобщающую способность. Поэтому для некоторых задач (особенно классификации) часто используются альтернативные функции потерь, например, кросс-энтропия.
Градиентный спуск
Посмотрим на график функции потерь. На нем изображена упрощенная модель с двумя весами w1 и w2. Однако в реальных задачах количество весов значительно больше, поэтому поверхность функции потерь становится высокомерной
Таким образом, нам необходимо найти минимум функции потерь L(w), что является задачей оптимизации. Чем меньше значение функции потерь, тем точнее предсказания модели.

Для уменьшения значения функции потерь используются различные алгоритмы оптимизации. Их задача — подобрать такие значения весов wi, при которых ошибка модели будет минимальной. Наиболее распространённый и эффективный метод — градиентный спуск. Он основан на вычислении производной функции потерь по параметрам модели (весам).
Идея метода заключается в следующем:
вычисляется градиент (вектор частных производных) функции потерь по всем весам;
затем веса обновляются в направлении, противоположном градиенту (так как градиент указывает на направление наибольшего роста функции);
этот процесс повторяется до достижения минимума.
Представим, что в модели есть только один вес w. Тогда функция потерь L(w) становится функцией одной переменной. Поскольку веса изначально инициализируются случайным образом, мы начинаем с произвольной точки на графике функции L(w). Наша задача — найти её минимум.

Для этого:
1. Вычисляем наклон кривой L(w), то есть частную производную:
2. Производная указывает направление наибольшего роста функции (в сторону максимума).
3. Поскольку нам нужен минимум, мы движемся в противоположном направлении, то есть вдоль отрицательного градиента.

Таким образом, на каждом шаге мы немного корректируем значение веса, постепенно спускаясь к минимуму. Этот процесс повторяется до тех пор, пока изменение ошибки не станет минимальным или не будет достигнут критерий остановки.
Приходим к следующей формуле:
где:
— скорость обучения,
— градиент функции потерь,
— коэффициент импульса,
— изменение веса на предыдущей итерации.
В базовом градиентном спуске используются только скорость обучения и текущий градиент, однако добавление импульса может значительно улучшить процесс обучения.
Скорость обучения:
Определяет, насколько быстро модель обучается. Скорость обучения контролирует размер шага при каждом обновлении весов. Чем больше значение параметра скорости обучения, тем большие шаги мы делаем по направлению к минимуму функции потерь.
Больший шаг скорости обучения увеличивает риск перескакивания минимума функции.
Меньший шаг скорости обучения снижает риск перескакивания минимума функции. Однако шанс застрять на локальном минимуме увеличивается.
Коэффициент импульса:
Помогает сделать процесс оптимизации более стабильным и эффективным. Используется для ускорения градиентного спуска и уменьшения колебаний при обновлении весов. Коэффициент импульса накапливает информацию о предыдущих градиентах, благодаря чему обновления параметров становятся более сглаженными и направленными.
Это позволяет ускорять движение вдоль направлений с устойчивым градиентом и уменьшать колебания в направлениях, где градиент часто меняет знак. В результате оптимизация обычно сходится быстрее.
Обратное распространение ошибки (backpropagation)
Наиболее сложная часть алгоритма обучения — вычисление частных производных функции потерь по каждому весу сети. Поскольку весов может быть очень много, рассчитывать производную для каждого параметра по отдельности напрямую было бы вычислительно дорого и неудобно.
Эту задачу решает алгоритм обратного распространения ошибки. Он является эффективной реализацией градиентного спуска и позволяет вычислять градиенты последовательно, начиная от выходного слоя.
В алгоритме backpropagation вводится новый параметр дельта, который показывает, насколько выход нейрона повлиял на общую ошибку сети. Формула вычисления производной принимает вид:
Выведем данное уравнение и формулы дельты нод для скрытого и выходного слоев. Для этого введем следующие обозначения:
wji— вес от нейрона входного слоя i к нейрону скрытого слоя j,
wkj — вес от нейрона скрытого слоя j к нейрону выходного слоя k,
z — суммарный взвешенный вход нейрона,
a — значение активации нейрона,
tk — целевое значение для выходного нейрона.
Вычисление дельты выходного слоя
Возьмем классическую квадратичную ошибку:
Добавляется деление на 2 для удобства вычисления производной, чтобы убрать коэффициент 2 при дифференцировании.
Как мы видим, данная функция не является прямой функцией от веса w. Для веса wkj зависимость такая:
Поэтому нам нужно применить правило цепочки (chain rule) для определения производной составной функции:
Вычисляем по очереди каждую производную:
№1. Производная функции потерь по активации выхода:
№2. Производная функции активации по сумме входов zk. Если используем сигмоиду, то функция активации равна:
Получаем производную:
№3. Производная суммы взвешенных входов по весу:
Объединяем и получаем формулу градиента:
Вводим параметр дельта выхода:
Вычисление дельты скрытого слоя
Вычисление дельты для скрытого слоя сложнее. Нам нужно найти
где wji — вес от входного нейрона i к скрытому нейрону j.
Теперь зависимость длиннее, чем для выходного слоя:
Применяем правило цепочки:
Вычисляем производные:
№1. На предыдущем шаге мы вычислили, что
№2.
№3.
№4.
Объединяем и получаем формулу для градиента скрытого слоя:
Вводим дельта скрытого слоя:
Обновление веса
И наконец, мы можем обновить веса на основе следующей формулы:
Также необходимо обновить значения смещения. В отличие от весов, при обновлении смещения не используется активация предыдущего слоя, так как смещение не связано с входным сигналом.
Шаги алгоритма:
На вход сети подаются данные из обучающего набора. Сигнал последовательно проходит через все слои нейронной сети — от входного к выходному — и вычисляется предсказанное значение.
Рассчитывается ошибка как разность между истинным значением и предсказанием модели.
Вычисление дельт (backward pass):
- Сначала вычисляется delta для выходного слоя.
- Затем ошибка распространяется назад: вычисляются delta для скрытых слоёв, начиная с последнего скрытого слоя и двигаясь к входному.Веса корректируются по правилу градиентного спуска. Обновление начинается с выходного слоя и продолжается слой за слоем в направлении к входному.
Те же шаги повторяются для следующего примера из обучающего набора.
Реализация в коде
В качестве примера рассмотрим задачу классификации точек на плоскости. Необходимо определить, принадлежит ли точка кругу радиуса r с центром в начале координат.
Определим классы следующим образом:
Класс 1 — точки, расположенные внутри круга радиуса r;
Класс 0 — точки, расположенные вне круга радиуса r.
Для решения задачи используется нейронная сеть с сигмоидальной функцией активации. Сигмоида возвращает значения в диапазоне
[0,1], что позволяет интерпретировать выход сети как вероятность принадлежности объекта к положительному классу. Это упрощает применение порогового правила для бинарной классификации, а также делает процесс обучения более стабильным и результаты — интерпретируемыми.
Реализация будет выполнена на языке Python с использованием библиотеки NumPy для работы с векторами и математическими операциями.
Для начала определим функции вычисления сигмоиды и её производной. Вспомним формулу сигмоидальной функции:
def sigmoid(x): return 1 / (1 + np.exp(-x)) def sigmoid_derivative(x): return x * (1 - x)
Добавляем метод для реализации прямого распространения сигнала от входного слоя к выходному. Для каждого скрытого и выходного слоя вычисляем взвешенную сумму входов и применяем функцию активации.
Вспомним формулу:
def forward(self, input_data): # Вычисляем взвешенную сумму входов + bias для скрытого слоя и применяем функцию активации hidden_sum = np.dot(input_data, self.weights_input_hidden) + self.bias_hidden hidden_activation = sigmoid(hidden_sum) # Вычисляем взвешенную сумму выходов скрытого слоя + bias и применяем функцию активации для получения предсказания output_sum = np.dot(hidden_activation, self.weights_hidden_output) + self.bias_output output_activation = sigmoid(output_sum) return hidden_activation, output_activation
Реализуем функцию обратного распространения ошибки.
Сначала вычисляем дельты для каждого слоя (ошибки нейронов). Вспомним соответствующие формулы:
output_error = output_activation - target_output output_delta = output_error * sigmoid_derivative(output_activation) hidden_delta = output_delta.dot(self.weights_hidden_output.T) * sigmoid_derivative(hidden_activation)
Далее обновляем веса сети на основе рассчитанных дельт. Вспомним формулы для обновления весов:
self.weights_hidden_output -= hidden_activation.T.dot(output_delta) * learning_rate self.bias_output -= np.sum(output_delta, axis=0, keepdims=True) * learning_rate self.weights_input_hidden -= input_data.T.dot(hidden_delta) * learning_rate self.bias_hidden -= np.sum(hidden_delta, axis=0, keepdims=True) * learning_rate
Реализуем тренировочную функцию нейронной сети. Для каждой эпохи выполняется:
Прямое распространение входных данных
Обратное распространение ошибки и обновление весов с заданной скоростью обучения
Каждые 100 итераций вычисляется MSE между целевым выходом и фактическим выходом сети и выводится в консоль.
def train(self, input_data, target_output, epochs, learning_rate): for epoch in range(epochs): hidden_activation, output_activation = self.forward(input_data) self.backward(input_data, target_output, learning_rate, hidden_activation, output_activation) if epoch % 100 == 0: loss = np.mean(np.square(target_output - output_activation)) print(f'Epoch {epoch}, Loss: {loss:.4f}')
Реализуем функцию предсказания. Применяем пороговое значение (0.5) для преобразования вероятностей в бинарный результат классификации (0 или 1).
def predict(self, input_data): _, probabilities = self.forward(input_data) return (probabilities >= 0.5).astype(int)
Задаём параметры сети:
INPUT_SIZE = 2 # две координаты x1, x2 HIDDEN_SIZE = 4 # количество нейронов в скрытом слое (можно экспериментировать) OUTPUT_SIZE = 1 # бинарная классификация
Обучаем сеть на тренировочных данных с заданным числом эпох и скоростью обучения:
EPOCHS = 5000 LEARNING_RATE = 0.1 network.train(input_data, training_labels, EPOCHS, LEARNING_RATE)
На каждой эпохе сеть обновляет веса с помощью обратного распространения ошибки. Каждые 100 итераций выводится значение функции потерь, что позволяет отслеживать процесс обучения.
После обучения можно делать предсказания:
predictions = network.predict(input_data)
Для оценки точности используем сравнение предсказанных значений с истинными метками:
accuracy = np.mean(predictions.flatten() == training_labels.flatten()) print("Accuracy:", accuracy)
Полная реализация кода под спойлером:
import numpy as np # Параметры сети INPUT_SIZE = 2 # две координаты x1, x2 HIDDEN_SIZE = 4 # количество нейронов в скрытом слое (можно экспериментировать) OUTPUT_SIZE = 1 # бинарная классификация EPOCHS = 5000 LEARNING_RATE = 0.1 N_SAMPLES = 200 RADIUS = 0.5 def sigmoid(x): return 1 / (1 + np.exp(-x)) def sigmoid_derivative(x): return x * (1 - x) class FeedForwardNeuralNetwork: def __init__(self, input_size, hidden_size, output_size): self.input_size = input_size self.hidden_size = hidden_size self.output_size = output_size # Начальное значение генератора случайных чисел. Оно гарантирует, что при каждом запуске кода результат будет # одинаковым, а значит, результаты будут воспроизводимыми. random_generator = np.random.default_rng(seed=42) # Инициализация весов случайными числами с нормальным распределением (среднее 0, стандартное отклонение 1) self.weights_input_hidden = random_generator.standard_normal((self.input_size, self.hidden_size)) self.weights_hidden_output = random_generator.standard_normal((self.hidden_size, self.output_size)) # Инициализация bias nodes self.bias_hidden = random_generator.standard_normal((1, self.hidden_size)) self.bias_output = random_generator.standard_normal((1, self.output_size)) def forward(self, input_data): # Вычисляем взвешенную сумму входов + bias для скрытого слоя и применяем функцию активации hidden_sum = np.dot(input_data, self.weights_input_hidden) + self.bias_hidden hidden_activation = sigmoid(hidden_sum) # Вычисляем взвешенную сумму выходов скрытого слоя + bias и применяем функцию активации для получения предсказания output_sum = np.dot(hidden_activation, self.weights_hidden_output) + self.bias_output output_activation = sigmoid(output_sum) return hidden_activation, output_activation def backward(self, input_data, target_output, learning_rate, hidden_activation, output_activation): output_error = output_activation - target_output output_delta = output_error * sigmoid_derivative(output_activation) hidden_delta = output_delta.dot(self.weights_hidden_output.T) * sigmoid_derivative(hidden_activation) # Обновляем веса и нейроны смещения self.weights_hidden_output -= hidden_activation.T.dot(output_delta) * learning_rate self.bias_output -= np.sum(output_delta, axis=0, keepdims=True) * learning_rate self.weights_input_hidden -= input_data.T.dot(hidden_delta) * learning_rate self.bias_hidden -= np.sum(hidden_delta, axis=0, keepdims=True) * learning_rate def train(self, input_data, target_output, epochs, learning_rate): for epoch in range(epochs): hidden_activation, output_activation = self.forward(input_data) self.backward(input_data, target_output, learning_rate, hidden_activation, output_activation) if epoch % 100 == 0: loss = np.mean(np.square(target_output - output_activation)) print(f'Epoch {epoch}, Loss: {loss:.4f}') def predict(self, input_data): _, probabilities = self.forward(input_data) # Применяем пороговое значение для получения бинарной классификации return (probabilities >= 0.5).astype(int) def generate_circle_data(n_samples: int, radius: float, seed: int | None = None): rng = np.random.default_rng(seed) input_data = rng.uniform(-1, 1, (n_samples, 2)) training_labels_circle = (input_data[:, 0] ** 2 + input_data[:, 1] ** 2 <= radius ** 2).astype(int) return input_data, training_labels_circle.reshape(-1, 1) def main(): network = FeedForwardNeuralNetwork(INPUT_SIZE, HIDDEN_SIZE, OUTPUT_SIZE) input_data, training_labels = generate_circle_data(N_SAMPLES, RADIUS) network.train(input_data, training_labels, EPOCHS, LEARNING_RATE) predictions = network.predict(input_data) accuracy = np.mean(predictions.flatten() == training_labels.flatten()) print("Accuracy:", accuracy) if __name__ == "__main__": main()
После запуска у меня получился следующий результат:

Поздравляю!
В ходе работы мы реализовали нейронную сеть прямого распространения (FNN) с нуля, используя библиотеку NumPy.
Этот процесс позволит на практике разобраться в ключевых принципах работы нейронных сетей, понять их математическую основу и увидеть, как отдельные компоненты модели взаимодействуют между собой. Такой подход формирует прочное понимание фундаментальных механизмов машинного обучения и создает основу для изучения более сложных архитектур и методов обучения, а также для применения этих знаний в собственных проектах.
Читайте также:

