Как создать нейросеть с нуля: рабочий пример на Python

Как создать нейросеть с нуля: рабочий пример на Python Полезное

Нейросеть (искусственная нейронная сеть) — это математическая модель, которая преобразует входные числа в выходные через слои простых элементов-нейронов, а нужное преобразование не программируют вручную, а подбирают в процессе обучения на примерах. Ниже я покажу, как создать такую сеть с нуля на Python и numpy: сначала целиком дам минимальный рабочий пример, который обучается решать задачу XOR, а потом разберу его по шагам — forward-проход, ошибку и обратное распространение.

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

Из чего состоит нейросеть: нейрон, слой, веса, активация

Прежде чем писать код, разведу пять терминов, которые новички часто путают. Дальше в примере каждый из них — это конкретная строка кода.

  • Нейрон — один вычислительный элемент: он берет входные числа, умножает каждое на свой вес, складывает и пропускает сумму через функцию активации.
  • Веса — числа-коэффициенты у входов нейрона. Именно их сеть меняет при обучении; в коде это матрицы W1 и W2.
  • Слой — группа нейронов, работающих над одним входом параллельно. Один слой — это одна матрица весов и одно умножение матрицы на вектор.
  • Функция активации — нелинейное преобразование суммы (у нас сигмоида). Без нее сеть из любого числа слоев осталась бы линейной и XOR бы не решила.
  • Эпоха — один полный проход по всем обучающим примерам с обновлением весов. Обучение — это много эпох подряд.
Термин Что это В коде примера
Нейрон взвешенная сумма входов + активация элемент строки X @ W1
Веса подбираемые коэффициенты W1, W2
Слой набор нейронов, одно умножение матриц sigmoid(X @ W1 + b1)
Активация нелинейность (сигмоида) функция sigmoid
Эпоха проход по всем примерам одна итерация цикла for

Сеть прямого распространения (feed-forward) — это когда сигнал идет строго в одну сторону: вход -> скрытый слой -> выход, без циклов и обратных связей. Наш пример — именно такая сеть с одним скрытым слоем.

Минимальный рабочий пример на Python

Ниже — полный скрипт. Он копируется целиком и запускается на Python 3 с установленным numpy (pip install numpy). Задача — классический XOR: сеть должна выдавать 1, когда входы разные, и 0, когда одинаковые. Одним нейроном XOR не решается, поэтому берем скрытый слой.

Архитектура — 2-2-1: два входа, два нейрона в скрытом слое, один выход. np.random.seed(1) фиксирует случайные начальные веса, чтобы результат воспроизводился один в один.

import numpy as np

np.random.seed(1)

# Игрушечная задача XOR: 4 примера, 2 входа -> 1 выход
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
Y = np.array([[0], [1], [1], [0]])

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

def sigmoid_deriv(a):        # a = sigmoid(z)
    return a * (1 - a)

# Веса двух слоев: 2->2 (скрытый) и 2->1 (выходной)
W1 = np.random.uniform(-1, 1, (2, 2))
b1 = np.zeros((1, 2))
W2 = np.random.uniform(-1, 1, (2, 1))
b2 = np.zeros((1, 1))

lr = 0.5
for epoch in range(1, 20001):
    # forward: прямой проход
    h = sigmoid(X @ W1 + b1)      # выход скрытого слоя
    out = sigmoid(h @ W2 + b2)    # выход сети

    # loss: среднеквадратичная ошибка
    loss = np.mean((Y - out) ** 2)

    # backprop: обратное распространение ошибки
    d_out = (out - Y) * sigmoid_deriv(out)
    d_h = (d_out @ W2.T) * sigmoid_deriv(h)

    # обновление весов (градиентный спуск)
    W2 -= lr * (h.T @ d_out)
    b2 -= lr * d_out.sum(axis=0, keepdims=True)
    W1 -= lr * (X.T @ d_h)
    b1 -= lr * d_h.sum(axis=0, keepdims=True)

    if epoch == 1 or epoch % 2000 == 0:
        print(f"epoch {epoch:5d}  loss {loss:.4f}")

print("\nПредсказания сети:")
for x, p in zip(X, out):
    print(f"{x} -> {p[0]:.3f}")

Ожидаемый результат: ошибка падает от эпохи к эпохе почти до нуля, а предсказания сходятся к правильным (около 0 для одинаковых входов и около 1 для разных). Фактический вывод при seed(1):

epoch     1  loss 0.2761
epoch  2000  loss 0.0029
epoch  4000  loss 0.0009
epoch  6000  loss 0.0006
epoch  8000  loss 0.0004
epoch 10000  loss 0.0003
epoch 12000  loss 0.0002
epoch 14000  loss 0.0002
epoch 16000  loss 0.0002
epoch 18000  loss 0.0002
epoch 20000  loss 0.0001

Предсказания сети:
[0 0] -> 0.011
[0 1] -> 0.988
[1 0] -> 0.988
[1 1] -> 0.012

Ошибка упала с 0.2761 до 0.0001 — сеть обучилась. Дальше разберу, что происходит внутри цикла.

Разбор по шагам

1. Forward: прямой проход

На прямом проходе сеть считает свой ответ. Данные идут через два слоя:

h = sigmoid(X @ W1 + b1)      # выход скрытого слоя
out = sigmoid(h @ W2 + b2)    # выход сети

X @ W1 — это умножение матрицы входов на матрицу весов, то самое «нейрон = взвешенная сумма», записанное сразу для всех четырех примеров. Прибавляем смещение b1 и пропускаем через сигмоиду — получаем выход скрытого слоя h. Тот же прием повторяем для второго слоя и получаем ответ сети out (числа от 0 до 1).

2. Loss: насколько сеть ошиблась

loss = np.mean((Y - out) ** 2)

Это среднеквадратичная ошибка (MSE): разница между ожидаемым Y и полученным out, возведенная в квадрат и усредненная. Чем меньше loss, тем ближе ответы сети к правильным. Именно эту величину мы хотим уменьшить.

3. Backprop: обратное распространение ошибки

d_out = (out - Y) * sigmoid_deriv(out)
d_h = (d_out @ W2.T) * sigmoid_deriv(h)

Backpropagation отвечает на вопрос: как каждый вес повлиял на ошибку. Сначала считаем «дельту» выходного слоя d_out — насколько и в какую сторону ошибся выход, с поправкой на производную активации. Затем пробрасываем эту дельту назад через веса W2 на скрытый слой и получаем его дельту d_h. Так ошибка «распределяется» по слоям от выхода ко входу.

4. Обновление весов: градиентный спуск

W2 -= lr * (h.T @ d_out)
W1 -= lr * (X.T @ d_h)

Дельты и входы каждого слоя дают градиент — направление, в котором ошибка растет быстрее всего. Чтобы ошибку уменьшить, делаем шаг в противоположную сторону: вычитаем градиент, умноженный на скорость обучения lr. Смещения b1 и b2 обновляются так же. Повторяем это 20000 эпох — и веса подстраиваются так, что сеть решает XOR.

Скорость обучения lr — это размер шага. Слишком большой — ошибка скачет, слишком маленький — сходится медленно. Значение 0.5 для этой задачи дает устойчивую сходимость.

Частая ошибка: забыть нелинейность

Показательный промах — убрать активацию из скрытого слоя, думая, что «сумма и так посчитается»:

h = X @ W1 + b1          # без sigmoid
out = sigmoid(h @ W2 + b2)

Фактический результат при seed(1): ошибка застревает около loss 0.5 и не падает, а предсказания вырождаются — все стремятся к 1.0 независимо от входа, и XOR не решается. Причина: без нелинейности скрытый слой сводится к одному линейному преобразованию перед сигмоидой, а XOR линейно неразделим. Исправление — вернуть нелинейность: h = sigmoid(X @ W1 + b1).

Что дальше: фреймворки вместо ручного backprop

Ручной пример выше нужен, чтобы понять механику. В реальных задачах градиенты не выводят руками: их считает автоматическое дифференцирование внутри фреймворка. На 2026 год стандартные инструменты — PyTorch и TensorFlow (часто через удобную надстройку Keras).

Та же сеть на фреймворке — это несколько строк: слои описываются декларативно, а forward, loss и backprop берет на себя библиотека. Плюс работа на GPU, готовые слои и оптимизаторы — все то, что в учебном примере пишешь сам.

Важная граница: наш пример — это принцип, а не заготовка для продакшена. Он обучен на четырех точках XOR, без разбиения на обучающую и тестовую выборки, без регуляризации и контроля переобучения. Он показывает, как сеть учится, но не годится для реальных данных и тем более для «предсказания рынка».

Выводы

  • Нейросеть — это модель, которая подбирает преобразование входа в выход не программированием правил, а обучением на примерах через слои нейронов.
  • Минимальную рабочую сеть можно написать на Python и numpy в несколько десятков строк: пример выше решает XOR и сходится с ошибки 0.2761 до 0.0001.
  • Обучение — это цикл из четырех шагов: forward (посчитать ответ), loss (измерить ошибку), backprop (распределить ошибку по слоям), обновление весов (шаг градиентного спуска).
  • Нелинейная функция активации обязательна: без нее слои схлопываются в линейную модель, и XOR не решается.
  • Учебный пример без фреймворка показывает механику, но не заменяет реальную модель: в 2026 году на практике берут PyTorch или TensorFlow.

Где применяется / связь с практикой

Тот же цикл forward — loss — backprop лежит в основе прикладных нейросетей: распознавания изображений, обработки текста, рекомендаций, прогнозов по табличным данным. Разница с учебным примером — в масштабе, объеме данных и инструментах (фреймворк и GPU вместо ручного numpy), но принцип обучения тот же.

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

Если хочется дойти от этого примера до реальных задач системно, посмотрите курс Machine Learning. Basic в Otus — там разбирают, как готовить данные, обучать и оценивать модели на практике. Понять формат занятий и уровень погружения помогают бесплатные вебинары: можно послушать преподавателя и задать вопросы до старта.

Смежные темы: Нейронные сети: описание, особенности, виды, Keras: описание и особенности, Искусственный интеллект: описание, характеристика, создание.

FAQ

Нужна ли видеокарта, чтобы создать нейросеть с нуля?
Для учебного примера на XOR — нет: он считается на процессоре мгновенно. GPU нужен, когда данных и весов много (большие изображения, тексты), и обучение на процессоре становится слишком долгим.

Сколько данных нужно для обучения?
Для XOR хватает четырех примеров, потому что задача исчерпывается ими полностью. Для реальных задач нужны сотни и тысячи размеченных примеров, а еще отдельная тестовая выборка, чтобы проверить, что сеть не просто заучила обучающие данные.

Почему у меня выходят другие числа, чем в примере?
Результат зависит от начальных случайных весов. Если убрать или изменить np.random.seed(1), стартовые веса будут другими, и конкретные значения ошибки и предсказаний немного разойдутся, хотя сеть все равно должна сойтись к решению XOR.

OTUS Журнал