Нейронные сети: перцептрон, обучение, свёртки
Устройство нейрона и слоя, функции активации, обратное распространение ошибки, градиентный спуск, переобучение и регуляризация, свёрточные сети для изображений.
Нейросеть — это функция с миллионами настраиваемых коэффициентов и алгоритм их подбора по примерам. Никакой магии внутри нет: линейная алгебра плюс градиентный спуск. Разберём, как это работает.
Нейрон
y = f( Σ wᵢxᵢ + b ) xᵢ — входы, wᵢ — веса, b — смещение f — функция активации В матричной форме для слоя: Z = W·X + b A = f(Z) Без нелинейной функции активации сеть любой глубины эквивалентна одному линейному слою — именно активация даёт способность приближать сложные зависимости.
| Функция | Формула | Диапазон | Где применяется |
|---|---|---|---|
| Sigmoid | 1/(1+e⁻ˣ) | (0, 1) | Выход при бинарной классификации |
| Tanh | (eˣ−e⁻ˣ)/(eˣ+e⁻ˣ) | (−1, 1) | Скрытые слои, устаревает |
| ReLU | max(0, x) | [0, ∞) | Стандарт для скрытых слоёв |
| Leaky ReLU | max(0.01x, x) | (−∞, ∞) | Когда ReLU «умирает» |
| Softmax | eˣⁱ/Σeˣʲ | (0,1), сумма = 1 | Выход при многоклассовой классификации |
Обучение
Функция потерь измеряет ошибку: регрессия: MSE = (1/n)·Σ(y − ŷ)² бинарная классификация: BCE = −(1/n)·Σ[y·ln ŷ + (1−y)·ln(1−ŷ)] многоклассовая: CrossEntropy = −Σ yᵢ·ln ŷᵢ Градиентный спуск обновляет веса: w := w − η · ∂L/∂w η — скорость обучения (learning rate)
- Прямой проход: данные идут через слои, получается предсказание.
- Вычисление потерь: сравнение предсказания с истинным ответом.
- Обратный проход: по цепному правилу считаются производные потерь по каждому весу.
- Обновление весов в сторону убывания ошибки.
- Повторение на следующей порции данных — и так много эпох.
Обратное распространение ошибки — это применение цепного правила дифференцирования к композиции функций, которой и является сеть. Ничего сверх математики второго курса здесь не требуется, и на защите такое объяснение ценится выше пересказа про «искусственные нейроны как в мозге».
Простая сеть на numpy
import numpy as np
np.random.seed(42)
def sigmoid(x): return 1 / (1 + np.exp(-x))
def sigmoid_deriv(a): return a * (1 - a) # через выход, так короче
# Задача XOR — линейно неразделимая, одним слоем не решается
X = np.array([[0,0], [0,1], [1,0], [1,1]])
y = np.array([[0], [1], [1], [0]])
# Инициализация: масштаб важен, нули или большие значения ломают обучение
W1 = np.random.randn(2, 4) * 0.5
b1 = np.zeros((1, 4))
W2 = np.random.randn(4, 1) * 0.5
b2 = np.zeros((1, 1))
lr = 0.5
for epoch in range(10001):
# Прямой проход
A1 = sigmoid(X @ W1 + b1)
A2 = sigmoid(A1 @ W2 + b2)
loss = np.mean((y - A2) ** 2)
# Обратный проход
dA2 = (A2 - y) * sigmoid_deriv(A2)
dW2 = A1.T @ dA2
db2 = dA2.sum(axis=0, keepdims=True)
dA1 = (dA2 @ W2.T) * sigmoid_deriv(A1)
dW1 = X.T @ dA1
db1 = dA1.sum(axis=0, keepdims=True)
# Обновление
W2 -= lr * dW2; b2 -= lr * db2
W1 -= lr * dW1; b1 -= lr * db1
if epoch % 2000 == 0:
print(f'эпоха {epoch:5d} потери {loss:.5f}')
print('предсказания:', A2.round(3).ravel()) # ~[0, 1, 1, 0]
Переобучение
| Признак | Недообучение | Переобучение |
|---|---|---|
| Ошибка на обучении | Высокая | Очень низкая |
| Ошибка на тесте | Высокая | Высокая |
| Причина | Модель слишком проста | Модель запомнила выборку |
| Что делать | Усложнить сеть, обучать дольше | Больше данных, регуляризация, упростить модель |
- Dropout — при обучении случайно отключаются нейроны, сеть не может опираться на отдельные связи.
- L2-регуляризация — штраф за большие веса добавляется к функции потерь.
- Ранняя остановка — обучение прекращают, когда ошибка на валидации начинает расти.
- Аугментация данных — повороты, сдвиги, изменение яркости изображений искусственно расширяют выборку.
- Batch normalization — нормализация активаций внутри сети, ускоряет обучение и немного регуляризует.
Свёрточные сети
Полносвязный слой для картинки 224×224×3 потребовал бы 150 528 входов на нейрон — миллиарды параметров. Свёртка применяет небольшое ядро (3×3, 5×5) ко всему изображению, обучая всего 9 или 25 весов на фильтр. Размер выхода: out = (in − k + 2p)/s + 1 k — размер ядра, p — паддинг, s — шаг Типичная архитектура: Conv → ReLU → Pooling → Conv → ReLU → Pooling → Flatten → Dense → Softmax
Свёрточные слои учатся выделять признаки иерархически: первые — края и градиенты, средние — текстуры и части объектов, глубокие — целые объекты. Пулинг уменьшает размерность и даёт устойчивость к небольшим сдвигам.
import torch
import torch.nn as nn
class SimpleCNN(nn.Module):
def __init__(self, num_classes=10):
super().__init__()
self.features = nn.Sequential(
nn.Conv2d(1, 32, kernel_size=3, padding=1),
nn.BatchNorm2d(32), nn.ReLU(),
nn.MaxPool2d(2), # 28x28 → 14x14
nn.Conv2d(32, 64, kernel_size=3, padding=1),
nn.BatchNorm2d(64), nn.ReLU(),
nn.MaxPool2d(2), # 14x14 → 7x7
)
self.classifier = nn.Sequential(
nn.Flatten(),
nn.Dropout(0.5),
nn.Linear(64 * 7 * 7, 128), nn.ReLU(),
nn.Linear(128, num_classes),
)
def forward(self, x):
return self.classifier(self.features(x))
model = SimpleCNN()
print('параметров:', sum(p.numel() for p in model.parameters()))
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
Метрики
Accuracy = (TP + TN)/всего — доля верных ответов Precision = TP/(TP + FP) — точность: сколько из найденных верны Recall = TP/(TP + FN) — полнота: сколько из нужных найдено F1 = 2·P·R/(P + R) — их гармоническое среднее На несбалансированных данных accuracy обманчива: если брака 1 %, модель, всегда отвечающая «не брак», даёт 99 % точности и нулевую пользу.
Что включить в курсовую
- Постановку задачи и описание набора данных с распределением классов.
- Обоснование архитектуры: почему столько слоёв и нейронов.
- Разделение данных на обучающую, валидационную и тестовую части.
- Графики потерь и метрик по эпохам для обучения и валидации — по ним видно переобучение.
- Матрицу ошибок и разбор случаев, где модель ошибается.
- Сравнение с более простым методом: логистической регрессией или деревом решений.
- Вывод о применимости с конкретными числами метрик.
Частые вопросы
Сколько нужно данных?
Порядок — тысячи примеров на класс для обучения с нуля. При меньших объёмах применяют перенос обучения: берут сеть, обученную на большом наборе, и дообучают последние слои на своих данных — так хватает сотен примеров.
Почему сеть не обучается?
Чаще всего: слишком большая скорость обучения (потери скачут или уходят в бесконечность), слишком малая (потери не меняются), неотнормированные входные данные, ошибка в разметке. Проверять стоит в этом порядке.
Обязательно ли использовать GPU?
Для небольших сетей и табличных данных нет. Для свёрточных сетей на изображениях разница в скорости — десятки раз, и обучение на процессоре займёт сутки вместо часа. Для курсовой хватает бесплатных ресурсов вроде Google Colab.