← Назад к статьям

Программируем свою нейронную сеть

M
miholeus
12 марта 2026 г. · 6 мин чтения

Нейронные сети: теория и реализация на NumPy

Нейронные сети являются своего рода трендом в области развития computer science. Появляется всё больше софта, которое использует machine learning алгоритмы внутри. На мой взгляд, бизнес в большей степени сейчас решает задачи на основе классических алгоритмов машинного обучения (т.е. без использования нейронных сетей). Однако есть ряд задач, где нейронные сети хорошо себя зарекомендовали:

  • обработка изображений
  • обработка видео
  • голосовые ассистенты и помощники
  • обработка и распознавание текста
  • self-driving машины
  • разведка ландшафта дронами

Эта статья преследует цель глубже понять строение нейронной сети и даёт возможность написать свою нейронную сеть без использования deep learning библиотек.

Проблема

Нейросети, как правило, решают какую-то проблему. Поэтому придумаем для себя следующую задачу.

Представим, что есть некоторые люди, которые страдают проблемой ожирения и диабетом.

Person Smoking Obesity Exercise Diabetic
Person 10101
Person 20010
Person 31000
Person 41101
Person 51111

Человек может курить (smoking), страдать ожирением (obesity), заниматься спортом (exercise), быть диабетиком (diabetic). Если в ячейке стоит 1 — это истина, 0 — ложь. Например, Person 1 страдает ожирением и является диабетиком.

Мы хотим на новых данных научиться предсказывать эти характеристики. Создадим простую нейронную сеть с одним слоем на вход и одним слоем на выход.

Краткое введение в нейронные сети

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

Сначала нейронная сеть действует практически наугад. Затем получившиеся данные она сравнивает с ответом (который у нас есть на обучающей выборке) и вычисляет разницу между получившимся результатом и правильными данными. Эта функция называется функцией стоимости (cost function), или по-другому функцией потерь (loss function). Под стоимостью/потерей подразумевается ошибка. Наша цель заключается в минимизации этой ошибки.

xi представляют собой независимые переменные (или характеристики), wi представляют собой веса или коэффициенты каждой характеристики. Выходной слой является взвешенной суммой.

Основные составляющие нейронной сети

Слои

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

  • Входной слой — слой, куда приходят наши данные на начальном этапе. Это первый слой нейронной сети.
  • Выходной слой — слой, где мы получаем результат работы нейронной сети.
  • Скрытый слой — все остальные слои в сети. Мы не знаем, что происходит в этих слоях, поэтому они и называются скрытыми.

Нейроны

Каждый слой состоит из нейронов. Нейроны представляют наши данные, каждый нейрон содержит одно числовое значение. Например, если вы передаёте картинку для анализа размерами 640×480 пикселей, вам потребуется 640×480 = 307 200 нейронов на входном слое.

Связанные слои

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

Веса

Веса представляют собой связи между нейронами различных слоёв. По сути, они обозначают, насколько сильна связь между нейронами. Эти веса обновляются во время тренировки нейронной сети.

Bias (отклонение)

Bias является некой константой. Служит для более точной настройки нейронной сети.

Функция активации

Функция, которая применяется к взвешенной сумме нейрона. Наиболее распространённые функции:

  • RELU
  • Гиперболический тангенс
  • Сигмоида

Обратное распространение

Является ключевым алгоритмом в тренировке нейронной сети. Именно он отвечает за подбор весов нейронов и bias. Работа нейронной сети происходит в два этапа.

Прямая связь (Feedforward)

На этом этапе предсказания осуществляются на основе входных данных и весов нейронов. В нашем примере 3 нейрона, которые обозначают: курение, ожирение, занятия спортом.

Также у нас есть bias (отклонение) — очень важный параметр в тренировке сети. Представьте ситуацию, где человек не курит, не страдает ожирением и не занимается спортом. Тогда итоговое значение будет всегда нулём, независимо от подобранных весов. Поэтому мы добавляем отклонение, чтобы получать более надёжный результат:

X·W = x1·w1 + x2·w2 + x3·w3 + b

Веса могут быть любыми значениями, однако на выходе мы хотим получить значение 0 или 1. Поэтому в конце применяется функция активации, которая отображает результат в отрезок [0, 1]. В качестве такой функции возьмём сигмоиду.

Если у вас установлены numpy и matplotlib, можете нарисовать эту функцию:

input = np.linspace(-10, 10, 100)

def sigmoid(x):
    return 1/(1+np.exp(-x))

from matplotlib import pyplot as plt
plt.plot(input, sigmoid(input), c="r")

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

Сначала мы совершенно случайно берём параметры, смотрим на итоговый результат. Затем сравниваем с правильным ответом, считаем ошибку и подбираем веса и отклонение так, чтобы ошибка стремилась к нулю.

В качестве функции потерь возьмём среднеквадратичную ошибку (MSE):

MSE = (1/n) · Σ (predicted − observed)²

где n — количество наблюдений.

Наша главная цель — минимизировать ошибку. Решение этой задачи относится к поиску минимума в классе оптимизационных задач. Решение может быть получено при помощи алгоритма градиентного спуска.

Реализация при помощи NumPy

Запишем наши характеристики и метки:


import numpy as np

features = np.array([[0, 1, 0], [0, 0, 1], [1, 0, 0], [1, 1, 0], [1, 1, 1]])
labels = np.array([[1, 0, 0, 1, 1]])
labels = labels.reshape(5, 1)

Определим веса и bias:

np.random.seed(123)
weights = np.random.rand(3, 1)
bias = np.random.rand(1)
lr = 0.05

Тренируем нашу сеть:

for epoch in range(20000):
    # feedforward
    XW = np.dot(features, weights) + bias
    output = sigmoid(XW)

    # backpropagation step 1
    error = output - labels
    print(error.sum())

    # backpropagation step 2
    dcost_dpred = error
    dpred_dz = sigmoid_der(output)

    output_delta = dcost_dpred * dpred_dz

    inputs = features.T
    weights -= lr * np.dot(inputs, output_delta)

    for num in output_delta:
        bias -= lr * num

Нам нужно посчитать производную функции потерь:

d_cost/dw = d_cost/d_pred · d_pred/dz · dz/dw
  • dcost/dpred вычисляется как 2(predicted − observed). Константу 2 можно убрать — получаем просто error.
  • dpred является нашей сигмоидной функцией, производную которой мы посчитали выше.
  • dz/dw является по сути входными характеристиками.

Вместо того чтобы проходиться по каждой записи и умножать на output_delta, можно транспонировать матрицу характеристик и матрично перемножить с output_delta. В конце умножаем на lr (learning rate) — скорость обучения нейронной сети.

В начале ошибка довольно большая — 0.83. Однако в конце она уменьшается:

0.001682051587058541
0.0016819688044010135
0.0016818860299710938
0.001681803263767337
0.0016817205057886135
0.0016816377560336122
0.0016815550145013198
0.001681472281190189
0.0016813895560991061
0.0016813068392270755
0.0016812241305727664
0.001681141430134642
0.0016810587379115956
0.0016809760539027597
0.001680893378106809

Теперь предскажем новые данные:

new_value = np.array([0, 1, 0])
result = sigmoid(np.dot(new_value, weights) + bias)

print("result is {:.10f}".format(result[0]))

Результат будет 0.9983763058 — т.е. почти 100% вероятность того, что человек будет диабетиком, если он не курит, страдает ожирением и не занимается спортом.

Полный код доступен на GitHub. Там же можно посмотреть пример реализации на TensorFlow.

Комментарии 0

Комментарии проходят модерацию
Загрузка комментариев...