Модуль 5: Введение в нейросети
Задача модуля: понять, как устроен «кирпичик» глубокого обучения — искусственный нейрон, как из нейронов строятся сети, и почему они способны решать сложные задачи.
Что вы узнаете
- Как работает перцептрон (искусственный нейрон)
- Зачем нужны функции активации и чем отличаются Sigmoid, ReLU, Tanh
- Как из нейронов собирается многослойная сеть (MLP)
- Что такое Forward Pass и как данные проходят через сеть
- Как реализовать простую нейросеть на Python с нуля
5.1 Перцептрон (искусственный нейрон)
Перцептрон — это простейшая модель нейрона. Он берёт набор входных значений, умножает каждое на свой вес, складывает результат и пропускает через функцию активации.
Формула перцептрона
output = activation(w₁·x₁ + w₂·x₂ + ... + wₙ·xₙ + b)
- x₁, x₂, ..., xₙ — входные сигналы (признаки)
- w₁, w₂, ..., wₙ — веса (обучаемые параметры)
- b — смещение (bias), позволяет сдвигать порог срабатывания
- activation — функция активации (нелинейность)
Аналогия с биологическим нейроном
Биологический нейрон получает сигналы через дендриты (входы), обрабатывает их в теле клетки (суммирование + порог) и передаёт результат по аксону (выход). Синапсы — это «веса», определяющие силу связи между нейронами.
| Биологический нейрон | Искусственный нейрон |
|---|---|
| Дендриты (входы) | Входные данные x₁, x₂, ... |
| Сила синапса | Веса w₁, w₂, ... |
| Тело нейрона (суммирование) | Взвешенная сумма + bias |
| Порог срабатывания | Функция активации |
| Аксон (выход) | Output нейрона |
Один нейрон = линейный классификатор
Один перцептрон с пороговой функцией активации способен решить только линейно разделимые задачи — те, где классы можно разделить прямой (в 2D) или гиперплоскостью. Классический пример невозможной задачи — XOR (исключающее ИЛИ). Именно для решения таких задач нейроны объединяют в многослойные сети.
Историческая справка: Перцептрон был предложен Фрэнком Розенблаттом в 1957 году. В 1969 году Минский и Паперт показали ограничения одного перцептрона (невозможность решить XOR), что привело к «зиме AI». Решение пришло с многослойными сетями и алгоритмом обратного распространения ошибки.
5.2 Функции активации
Функция активации вносит нелинейность в работу нейрона. Без неё любая многослойная сеть была бы эквивалентна одному линейному преобразованию (композиция линейных функций — всё ещё линейная функция).
Sigmoid (Логистическая функция)
σ(x) = 1 / (1 + e⁻ˣ)
- Выход: от 0 до 1 — удобно интерпретировать как вероятность
- Гладкая, дифференцируемая
- Проблема: при больших |x| производная ≈ 0 (насыщение) → затухание градиентов
- Выход не центрирован (всегда > 0) → медленная сходимость
- Применение: выходной слой для бинарной классификации
Tanh (Гиперболический тангенс)
tanh(x) = (eˣ - e⁻ˣ) / (eˣ + e⁻ˣ)
- Выход: от −1 до 1 — центрированный (среднее ≈ 0)
- Быстрее сходится, чем sigmoid, т.к. выход центрирован
- Проблема: всё ещё насыщается при больших |x|
- Применение: скрытые слои RNN (до эпохи трансформеров)
ReLU (Rectified Linear Unit)
ReLU(x) = max(0, x)
- Проще всего: если x > 0 → пропускаем, иначе → 0
- Не насыщается при x > 0 → нет затухания градиента
- Очень быстрое вычисление (просто сравнение с нулём)
- Проблема: «мёртвые нейроны» (dying ReLU) — если нейрон всегда выдаёт 0, его градиент = 0 и он не обучается
- Применение: стандарт для скрытых слоёв в большинстве современных сетей
Leaky ReLU и другие варианты
LeakyReLU(x) = x если x > 0, иначе α·x (обычно α = 0.01)
Решает проблему «мёртвых нейронов» — при отрицательных входах всё равно пропускает маленький градиент. Существуют и другие варианты: ELU, GELU (используется в трансформерах), Swish (SiLU).
Сравнение функций активации
| Функция | Диапазон | Плюсы | Минусы | Где используется |
|---|---|---|---|---|
| Sigmoid | [0, 1] | Вероятностная интерпретация | Затухание градиентов | Выходной слой (бинарная кл.) |
| Tanh | [−1, 1] | Центрированный выход | Затухание градиентов | RNN, старые архитектуры |
| ReLU | [0, +∞) | Быстро, без насыщения | Мёртвые нейроны | CNN, MLP (стандарт) |
| GELU | ≈[−0.17, +∞) | Гладкая, хорошо работает | Дороже ReLU | Трансформеры (BERT, GPT) |
Практический совет
Начинайте с ReLU для скрытых слоёв. Если видите «мёртвые нейроны» (многие выходы = 0) — попробуйте Leaky ReLU. Для выхода: sigmoid (бинарная классификация), softmax (многоклассовая), без активации (регрессия).
5.3 Архитектура полносвязной сети (MLP)
MLP (Multi-Layer Perceptron) — это сеть из нескольких слоёв нейронов, где каждый нейрон одного слоя связан со всеми нейронами следующего.
Структура MLP
Три типа слоёв
- Входной слой — количество нейронов = количество признаков (фичей). Не выполняет вычислений, просто передаёт данные
- Скрытые слои — один или несколько. Каждый нейрон: взвешенная сумма + активация. Эти слои учатся находить промежуточные представления (абстракции) данных
- Выходной слой — количество нейронов зависит от задачи:
- Регрессия: 1 нейрон, без активации (или линейная)
- Бинарная классификация: 1 нейрон + sigmoid
- Многоклассовая классификация: N нейронов + softmax
Входной слой Скрытый слой 1 Скрытый слой 2 Выходной слой
(3 признака) (4 нейрона) (4 нейрона) (2 класса)
[x₁] ─────→ [h₁] ─────→ [h₅] ─────→ [y₁] (класс A)
╲ ╱ ╲ ╱ ╲ ╱
[x₂] ──╳──→ [h₂] ──╳──→ [h₆] ──╳──→ [y₂] (класс B)
╱ ╲ ╱ ╲ ╱ ╲
[x₃] ─────→ [h₃] ─────→ [h₇] ─────→
│ │
[h₄] ─────→ [h₈] ─────→
Каждый нейрон связан с каждым нейроном следующего слоя (полносвязность)
Почему «глубина» работает?
Каждый скрытый слой учится представлять данные на всё более высоком уровне абстракции:
- Слой 1: простые паттерны (линии, пороги, базовые комбинации признаков)
- Слой 2: комбинации паттернов (углы, формы, сложные условия)
- Слой N: высокоуровневые абстракции (объекты, концепции)
Именно поэтому глубокие сети (deep learning) эффективнее мелких — они строят иерархию абстракций.
Гиперпараметры MLP
- Количество скрытых слоёв — обычно 1–5 для табличных данных, десятки-сотни для изображений и текста
- Количество нейронов в слое — типичные значения: 32, 64, 128, 256, 512
- Функция активации — ReLU для скрытых, sigmoid/softmax для выходного
- Dropout — случайное «выключение» части нейронов при обучении для предотвращения переобучения
Теорема об универсальной аппроксимации: MLP всего с одним скрытым слоём (достаточно широким) может аппроксимировать любую непрерывную функцию. Но на практике глубокие и узкие сети работают лучше — им нужно меньше параметров для той же точности.
5.4 Прямое распространение (Forward Pass)
Forward Pass — это процесс прохождения данных от входа к выходу сети. На каждом слое выполняются два шага:
- Линейное преобразование:
z = W · x + b(матрица весов × вектор входа + вектор смещений) - Функция активации:
a = activation(z)
Выход одного слоя a становится входом x для следующего. В конце получаем предсказание модели.
Пошагово: Forward Pass для сети с 2 скрытыми слоями
Вход: x = [0.5, 0.3, 0.8]
Слой 1: z₁ = W₁ · x + b₁ (линейная часть)
a₁ = ReLU(z₁) (нелинейная часть)
Слой 2: z₂ = W₂ · a₁ + b₂
a₂ = ReLU(z₂)
Выход: z₃ = W₃ · a₂ + b₃
ŷ = softmax(z₃) (вероятности классов)
Зачем сохранять промежуточные значения?
Все промежуточные значения (z₁, a₁, z₂, a₂, ...) сохраняются в памяти. Они понадобятся на этапе обратного распространения ошибки (backpropagation) для вычисления градиентов и обновления весов. Это один из компромиссов: больше памяти → быстрее обучение.
Ключевая идея
Forward Pass — это просто цепочка матричных умножений + нелинейностей. GPU отлично справляется с матричными умножениями, поэтому нейросети так хорошо ускоряются на видеокартах.
5.5 Код: нейросеть на Python с нуля
Реализуем простую полносвязную сеть только на NumPy, чтобы понять механику. Никаких фреймворков — чистая математика.
Простой нейрон (перцептрон)
import numpy as np
# Функции активации
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def relu(x):
return np.maximum(0, x)
# Простой нейрон
def neuron(inputs, weights, bias):
"""Один нейрон: взвешенная сумма + активация"""
z = np.dot(inputs, weights) + bias # линейная часть
output = sigmoid(z) # нелинейная часть
return output
# Пример: нейрон с 3 входами
inputs = np.array([0.5, 0.3, 0.8])
weights = np.array([0.4, -0.2, 0.6])
bias = 0.1
result = neuron(inputs, weights, bias)
print(f"Вход: {inputs}")
print(f"Веса: {weights}, bias: {bias}")
print(f"Выход нейрона: {result:.4f}")
# Выход нейрона: 0.6457
Полносвязная сеть (MLP) с нуля
import numpy as np
class SimpleNN:
"""Простая нейросеть: вход → скрытый слой → выход"""
def __init__(self, input_size, hidden_size, output_size):
# Инициализация весов (случайные малые числа)
self.W1 = np.random.randn(input_size, hidden_size) * 0.01
self.b1 = np.zeros(hidden_size)
self.W2 = np.random.randn(hidden_size, output_size) * 0.01
self.b2 = np.zeros(output_size)
def relu(self, x):
return np.maximum(0, x)
def sigmoid(self, x):
return 1 / (1 + np.exp(-np.clip(x, -500, 500)))
def forward(self, X):
"""Forward Pass: данные проходят через сеть"""
# Скрытый слой
self.z1 = X @ self.W1 + self.b1 # линейное преобразование
self.a1 = self.relu(self.z1) # активация ReLU
# Выходной слой
self.z2 = self.a1 @ self.W2 + self.b2
self.a2 = self.sigmoid(self.z2) # sigmoid для вероятности
return self.a2
def predict(self, X):
"""Предсказание: 0 или 1"""
probabilities = self.forward(X)
return (probabilities >= 0.5).astype(int)
# Пример: классификация XOR (задача, неразрешимая одним перцептроном!)
X = np.array([[0, 0],
[0, 1],
[1, 0],
[1, 1]])
y = np.array([[0], [1], [1], [0]]) # XOR
# Создаём сеть: 2 входа → 4 скрытых → 1 выход
nn = SimpleNN(input_size=2, hidden_size=4, output_size=1)
# Forward pass (без обучения — веса случайные)
predictions = nn.forward(X)
print("Предсказания (случайные веса):")
for i in range(len(X)):
print(f" {X[i]} → {predictions[i][0]:.4f}")
Важно: Этот код показывает только forward pass. Для обучения нужен ещё backward pass (backpropagation) — вычисление градиентов и обновление весов. Это тема следующего модуля.
То же самое на PyTorch (2 строки)
import torch
import torch.nn as nn
# Та же сеть, но средствами PyTorch
model = nn.Sequential(
nn.Linear(2, 4), # входной → скрытый (2 → 4)
nn.ReLU(), # активация
nn.Linear(4, 1), # скрытый → выходной (4 → 1)
nn.Sigmoid() # вероятность на выходе
)
# Forward pass
X = torch.tensor([[0., 0.], [0., 1.], [1., 0.], [1., 1.]])
output = model(X)
print(output) # 4 вероятности (случайные веса)
# Посмотрим количество параметров
total_params = sum(p.numel() for p in model.parameters())
print(f"Всего параметров: {total_params}")
# 2×4 + 4 + 4×1 + 1 = 17 параметров
Сравнение: с нуля vs фреймворк
Писать с нуля полезно для понимания. В реальных проектах используйте PyTorch или TensorFlow — они автоматически вычисляют градиенты (autograd), поддерживают GPU и имеют готовые слои, оптимизаторы, даталоадеры.
5.6 Практические упражнения
Упражнение 1: Поиграйте с нейроном
Скопируйте код «простого нейрона» выше и попробуйте:
- Измените веса — как меняется выход?
- Замените sigmoid на relu — что происходит?
- Поставьте bias = −10 — какой будет выход sigmoid?
Упражнение 2: Посчитайте параметры
Сеть: 784 входов (изображение 28×28) → 128 нейронов → 64 нейрона → 10 выходов (цифры 0–9).
- Сколько весов в каждом слое?
- Сколько bias'ов?
- Общее число параметров?
Ответ: 784×128 + 128 + 128×64 + 64 + 64×10 + 10 = 109 386 параметров.
Упражнение 3: TensorFlow Playground
Откройте playground.tensorflow.org и попробуйте:
- Решить задачу «спираль» одним скрытым слоём — получится?
- Добавьте 2–3 скрытых слоя — как меняется граница решения?
- Попробуйте разные функции активации
Что дальше: В следующем модуле разберём Backpropagation — как сеть учится, вычисляя градиенты и обновляя веса. Это замыкает цикл: Forward Pass (предсказание) → Loss (ошибка) → Backward Pass (градиенты) → Update (обновление весов).