Пишем простую нейросеть на Python: пошаговое руководство с нуля

Практическое руководство по созданию простой нейросети на Python. Разбираем архитектуру, функции активации, обучение и обратное распространение ошибки. Примеры кода и советы для начинающих.

Что такое нейросеть и зачем её писать самому

Нейронная сеть — это математическая модель, вдохновлённая устройством биологических нейронов. Она состоит из множества взаимосвязанных элементов (нейронов), которые преобразуют входные данные в выходные. Простыми словами, это «чёрный ящик», который учится отображать входные признаки в нужный результат.

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

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

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

Любая нейросеть строится из трёх ключевых элементов: нейронов, слоёв и связей между ними.

Нейрон — это вычислительная единица, которая принимает несколько входных сигналов, умножает каждый на свой вес, суммирует их, добавляет смещение (bias) и пропускает результат через функцию активации. Математически: output = activation(sum(weight_i * input_i) + bias).

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

Веса и смещения — обучаемые параметры сети. В начале они инициализируются случайными значениями, а в процессе обучения корректируются так, чтобы минимизировать ошибку предсказания. Именно настройка весов и есть суть обучения нейросети.

Архитектуры нейросетей: от перцептрона до глубоких сетей

Самая простая архитектура — перцептрон (однослойная нейросеть). Он состоит только из входного и выходного слоёв и способен решать только линейно разделимые задачи (например, логическое И или ИЛИ). Для нелинейных задач, таких как XOR, требуется как минимум один скрытый слой — такой вариант называют многослойным перцептроном (MLP).

Свёрточные нейронные сети (CNN) специально разработаны для работы с изображениями. Они используют свёрточные слои для выделения признаков (края, текстуры) и пулинг-слои для уменьшения размерности. Рекуррентные нейронные сети (RNN) предназначены для последовательных данных (текст, временные ряды) — они запоминают информацию от предыдущих шагов.

Для нашей первой нейросети мы выберем многослойный перцептрон с одним скрытым слоем. Это оптимальный баланс между простотой реализации и способностью решать нелинейные задачи.

Функции активации: зачем они нужны и какие бывают

Функция активации вносит нелинейность в работу нейрона. Без неё вся сеть сводилась бы к линейной комбинации входов, что не позволяет моделировать сложные зависимости.

Сигмоида — классическая функция: f(x) = 1 / (1 + exp(-x)). Она преобразует любое число в диапазон от 0 до 1. Удобна для бинарной классификации, но страдает от проблемы затухающего градиента при большом количестве слоёв.

ReLU (Rectified Linear Unit)f(x) = max(0, x). Проста в вычислении и эффективна для глубоких сетей, так как не насыщается при положительных значениях. Недостаток — «умирающие нейроны» при отрицательных входах.

Гиперболический тангенс (tanh) — похож на сигмоиду, но выдаёт значения от -1 до 1. Часто используется в скрытых слоях.

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

Функции потерь и методы оптимизации

Функция потерь (loss function) измеряет, насколько сильно предсказания сети отличаются от истинных значений. Чем меньше значение функции потерь, тем лучше работает модель.

Для задач регрессии (предсказание числа) часто используют среднеквадратичную ошибку (MSE): MSE = (1/n) * sum((y_true - y_pred)^2). Для бинарной классификации — бинарную кросс-энтропию: BCE = - (y_true * log(y_pred) + (1 - y_true) * log(1 - y_pred)).

Оптимизатор — алгоритм, который обновляет веса сети, чтобы минимизировать функцию потерь. Самый простой — стохастический градиентный спуск (SGD): weights = weights - learning_rate * gradient. Более продвинутые варианты (Adam, Adagrad) адаптивно подбирают скорость обучения для каждого параметра.

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

Пошаговое создание нейросети на Python с NumPy

Напишем нейросеть с одним скрытым слоем для задачи XOR. XOR — классический нелинейный пример: выход равен 1, если входы различны, и 0, если одинаковы.

Шаг 1. Импорт библиотек и определение сигмоиды

import numpy as np

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

def sigmoid_derivative(x):
    return x * (1 - x)

Шаг 2. Подготовка данных

inputs = np.array([[0,0], [0,1], [1,0], [1,1]])
expected_output = np.array([[0], [1], [1], [0]])

Шаг 3. Инициализация параметров

input_size = 2
hidden_size = 2
output_size = 1
learning_rate = 0.1

hidden_weights = np.random.uniform(size=(input_size, hidden_size))
hidden_bias = np.random.uniform(size=(1, hidden_size))
output_weights = np.random.uniform(size=(hidden_size, output_size))
output_bias = np.random.uniform(size=(1, output_size))

Шаг 4. Обучение (прямое и обратное распространение)

for epoch in range(10000):
    # Прямой проход
    hidden_layer_input = np.dot(inputs, hidden_weights) + hidden_bias
    hidden_layer_output = sigmoid(hidden_layer_input)
    output_layer_input = np.dot(hidden_layer_output, output_weights) + output_bias
    predicted_output = sigmoid(output_layer_input)

    # Ошибка
    error = expected_output - predicted_output

    # Обратное распространение
    d_predicted = error * sigmoid_derivative(predicted_output)
    error_hidden = d_predicted.dot(output_weights.T)
    d_hidden = error_hidden * sigmoid_derivative(hidden_layer_output)

    # Обновление весов
    output_weights += hidden_layer_output.T.dot(d_predicted) * learning_rate
    output_bias += np.sum(d_predicted, axis=0, keepdims=True) * learning_rate
    hidden_weights += inputs.T.dot(d_hidden) * learning_rate
    hidden_bias += np.sum(d_hidden, axis=0, keepdims=True) * learning_rate

После 10 000 итераций сеть научится решать XOR с высокой точностью.

Как проверить и протестировать обученную модель

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

print("Результаты после обучения:")
print(predicted_output)

Вы должны увидеть значения, близкие к 0 или 1. Например: [[0.01], [0.99], [0.99], [0.01]].

Затем протестируйте сеть на новых данных, которых не было в обучении:

new_input = np.array([[0, 0]])
result = sigmoid(np.dot(sigmoid(np.dot(new_input, hidden_weights) + hidden_bias), output_weights) + output_bias)
print("Предсказание для [0,0]:", result)

Если сеть обучена правильно, предсказание будет близко к 0. Для других комбинаций — к 1. Обратите внимание: из-за случайной инициализации весов результаты могут немного отличаться при каждом запуске, но после достаточного обучения они должны сходиться к правильным значениям.

Типичные ошибки новичков и как их избежать

1. Неправильная инициализация весов. Если все веса одинаковы (например, нули), нейроны будут обновляться одинаково, и сеть не сможет обучаться. Используйте случайные маленькие значения.

2. Слишком высокая или низкая скорость обучения. При learning_rate > 1 веса могут расходиться, а при < 0.0001 обучение займёт тысячи эпох. Начните с 0.1 и корректируйте.

3. Забыли про смещение (bias). Без смещения нейрон не сможет сдвигать функцию активации, что снижает гибкость сети.

4. Недостаточное количество эпох. Для простых задач (XOR) достаточно 10 000 итераций, но для более сложных может потребоваться 100 000 и больше. Следите за значением ошибки — когда оно перестаёт уменьшаться, обучение можно остановить.

5. Использование неподходящей функции активации. Для бинарной классификации на выходе хорошо подходит сигмоида, а для скрытых слоёв — ReLU или tanh.

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

Как улучшить нейросеть: советы для дальнейшего развития

Наша простая сеть — лишь отправная точка. Вот несколько идей для её улучшения:

  • Добавьте больше скрытых слоёв. Глубокая сеть может моделировать более сложные зависимости. Однако помните о проблеме затухающего градиента — используйте ReLU и нормализацию.
  • Используйте регуляризацию. L2-регуляризация (добавление штрафа за большие веса) или dropout (случайное отключение нейронов) помогают бороться с переобучением.
  • Попробуйте другие оптимизаторы. Adam часто сходится быстрее и стабильнее, чем SGD.
  • Нормализуйте входные данные. Если признаки имеют разный масштаб, сеть будет обучаться медленно. Приводите их к диапазону [0,1] или [-1,1].
  • Используйте готовые фреймворки. Когда вы поймёте основы, переходите к Keras или PyTorch — они автоматизируют многие шаги и позволяют быстро экспериментировать.
  • Экспериментируйте с гиперпараметрами. Количество нейронов, слоёв, скорость обучения, размер батча — всё это влияет на результат. Ведите журнал экспериментов.

Практические примеры применения простых нейросетей

Даже простая нейросеть с одним скрытым слоем может решать полезные задачи:

  • Классификация спама. На вход подаются признаки письма (частота слов, наличие ссылок), на выходе — вероятность, что это спам.
  • Прогнозирование оттока клиентов. Используя историю взаимодействий (частота покупок, обращения в поддержку), можно предсказать, уйдёт ли клиент.
  • Рекомендация товаров. На основе предыдущих покупок и просмотров сеть может оценить вероятность того, что пользователь купит конкретный товар.
  • Распознавание рукописных цифр. Хотя для этого лучше подходят свёрточные сети, многослойный перцептрон тоже справляется с простыми наборами данных вроде MNIST.

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