- Записывать математическую модель нейрона и полносвязного слоя
- Вычислять ReLU, сигмоиду, tanh и softmax и знать, где они применяются
- Строить сеть с помощью
nn.Moduleиnn.Sequentialи считать её параметры
Мы хотим распознать рукописную цифру: изображение — это 28 × 28 = 784 пикселя, а ответ — одна из десяти цифр от 0 до 9. Какая функция может превратить 784 числа в десять вероятностей? Нейронная сеть строит такую функцию из множества простых частей: линейных преобразований и нелинейных «изгибов» между ними. Название навеяно мозгом, но на деле это математическая функция, параметры которой подбираются градиентным спуском — ровно как в предыдущих уроках.
Искусственный нейрон
- xвходной вектор (n признаков)
- wвеса — важность каждого входа
- bсмещение (bias) — сдвигает порог активации
- φфункция активации (ReLU, сигмоида…)
- aвыход нейрона (активация)
Нейрон вычисляет взвешенную сумму входов, прибавляет смещение и пропускает результат через функцию активации. В примере ниже z = 0,4 · 0,5 + 0,3 · (−1) + (−0,2) · 2 + 0,1 = −0,4: сигмоида превращает это в 0,4013, а ReLU — в 0.
import torch
x = torch.tensor([0.5, -1.0, 2.0])
w = torch.tensor([0.4, 0.3, -0.2])
b = torch.tensor(0.1)
z = w @ x + b
print(round(z.item(), 4))
print(round(torch.sigmoid(z).item(), 4), torch.relu(z).item())-0.4 0.4013 0.0
Функции активации
Без активаций сеть оставалась бы линейной, сколько бы слоёв мы ни добавили. Доказательство короткое: два слоя подряд дают W₂(W₁x + b₁) + b₂ = (W₂W₁)x + (W₂b₁ + b₂) — это один линейный слой с матрицей W = W₂W₁ и смещением b = W₂b₁ + b₂. Нелинейная φ «изгибает» слои и позволяет сети описывать сложные, искривлённые границы.
- ReLUобнуляет отрицательные значения; производная 1 при z > 0 и 0 при z < 0
- σсигмоида: значения в (0; 1), трактуются как вероятность
- tanhгиперболический тангенс: значения в (−1; 1), симметричен относительно нуля
- zсырые оценки (логиты) для K классов
- softmax(z)ᵢвероятность класса i; все положительны и в сумме дают 1
В многоклассовой классификации softmax превращает логиты последнего слоя в распределение вероятностей.
| Функция | Область значений | Где применяется |
|---|---|---|
| ReLU | [0; ∞) | стандартный выбор в скрытых слоях: просто, быстро, градиент не затухает |
| Сигмоида | (0; 1) | выход бинарной классификации, «вентили» (LSTM) |
| tanh | (−1; 1) | рекуррентные сети, когда нужен центрированный выход |
| softmax | (0; 1), сумма 1 | последний слой многоклассовой классификации |
У ReLU есть слабое место: если вход нейрона отрицателен для всех примеров, его градиент всегда нулевой, и нейрон больше не учится («мёртвый ReLU»). LeakyReLU борется с этим, оставляя небольшой наклон на отрицательной стороне (например, 0,01z). В трансформерах широко используют GELU — гладкий вариант ReLU. В PyTorch всё это готово: nn.LeakyReLU(), nn.GELU(), nn.Tanh(), nn.Sigmoid().
import torch
z = torch.tensor([-2.0, 0.0, 1.0, 3.0])
print(torch.relu(z))
print(torch.sigmoid(z).round(decimals=4))
print(torch.tanh(z).round(decimals=4))
p = torch.softmax(z, dim=0)
print(p.round(decimals=4), round(p.sum().item(), 4))tensor([0., 0., 1., 3.]) tensor([0.1192, 0.5000, 0.7311, 0.9526]) tensor([-0.9640, 0.0000, 0.7616, 0.9951]) tensor([0.0057, 0.0418, 0.1135, 0.8390]) 1.0
Сеть выдала логиты z = (2; 1; 0) для трёх классов. Найди вероятности.
Показать решениеСкрыть решение
p ≈ (7,389 / 11,107; 2,718 / 11,107; 1 / 11,107) ≈ (0,665; 0,245; 0,090).
Логиты отличаются всего на 1, а вероятности — примерно в e ≈ 2,72 раза: softmax «усиливает» наибольшую оценку.
import torch
print(torch.softmax(torch.tensor([2.0, 1.0, 0.0]), dim=0).round(decimals=3))tensor([0.6650, 0.2450, 0.0900])
Слои: nn.Linear
Когда несколько нейронов смотрят на один и тот же вход, их векторы весов становятся строками одной матрицы W, и все выходы слоя вычисляются одним матричным произведением. Входы, приходящие пакетом, имеют форму (N, nin), а выходы — (N, nout).
- Xпакет входов, форма (N, nin)
- Wвеса, форма (nout, nin) —
layer.weight - bсмещения, форма (nout,) —
layer.bias - Yвыходы, форма (N, nout)
У каждого выходного нейрона nin весов и одно смещение, поэтому в полносвязном слое (nin + 1) · nout параметров.
import torch
from torch import nn
torch.manual_seed(42)
layer = nn.Linear(in_features=3, out_features=2)
print(layer.weight.shape, layer.bias.shape)
x = torch.randn(4, 3)
print(layer(x).shape)
print(sum(p.numel() for p in layer.parameters()))torch.Size([2, 3]) torch.Size([2]) torch.Size([4, 2]) 8
Веса инициализируются небольшими случайными значениями: по умолчанию nn.Linear выбирает их равномерно из интервала (−1/√nin; 1/√nin). Случайность важна — если бы все веса были одинаковыми, все нейроны слоя получали бы одинаковый градиент и учили бы одно и то же (проблема симметрии).
Сеть на nn.Module и nn.Sequential
В PyTorch каждая модель — наследник nn.Module. В методе __init__ создаются слои (их параметры регистрируются автоматически), а метод forward описывает вычисление от входа к выходу. Модель вызывают как model(x) — PyTorch сам запускает forward.
import torch
from torch import nn
class MLP(nn.Module):
def __init__(self, n_in, n_hidden, n_out):
super().__init__()
self.fc1 = nn.Linear(n_in, n_hidden)
self.fc2 = nn.Linear(n_hidden, n_out)
def forward(self, x):
h = torch.relu(self.fc1(x))
return self.fc2(h)
torch.manual_seed(42)
model = MLP(784, 128, 10)
print(model)
x = torch.randn(32, 784)
print(model(x).shape)
print(sum(p.numel() for p in model.parameters()))MLP( (fc1): Linear(in_features=784, out_features=128, bias=True) (fc2): Linear(in_features=128, out_features=10, bias=True) ) torch.Size([32, 10]) 101770
model.parameters()— все обучаемые параметры; их передают оптимизатору.model.to(device)— одним вызовом переносит все параметры с CPU на GPU (или обратно).model.train()иmodel.eval()— режимы обучения и оценки (важны для слоёв Dropout и BatchNorm).model.state_dict()— словарь параметров; в следующем уроке с его помощью сохраним модель в файл.
Сеть из полносвязных слоёв с активациями между ними называется многослойным перцептроном. Ненормированные выходы её последнего слоя — логиты — могут быть любыми действительными числами. Для предсказания берут индекс наибольшего логита (logits.argmax(dim=1)), для вероятностей — torch.softmax(logits, dim=1).
Как выбрать архитектуру? Для табличных данных обычно хватает 1–3 скрытых слоёв по 32–256 нейронов; начинай с маленькой модели и увеличивай её, пока это позволяют потери на валидации. Для изображений полносвязные слои слишком дороги — переход от цветного изображения 224 × 224 к слою из 1000 нейронов требует уже более 150 миллионов параметров. Эту проблему решают свёрточные сети.
Если слои просто идут друг за другом, класс писать не нужно: nn.Sequential применяет их по порядку. Активация здесь записывается как слой nn.ReLU(). named_parameters() показывает имя и форму каждого параметра — очень полезно при отладке:
import torch
from torch import nn
model = nn.Sequential(
nn.Linear(784, 128), nn.ReLU(),
nn.Linear(128, 64), nn.ReLU(),
nn.Linear(64, 10),
)
for name, p in model.named_parameters():
print(name, tuple(p.shape))
print(sum(p.numel() for p in model.parameters()))0.weight (128, 784) 0.bias (128,) 2.weight (64, 128) 2.bias (64,) 4.weight (10, 64) 4.bias (10,) 109386
Sequential; у слоёв ReLU с номерами 1 и 3 параметров нет.Сколько параметров в сети 784 → 128 → 64 → 10? Сколько памяти они занимают во float32?
Показать решениеСкрыть решение
2-й слой: (128 + 1) · 64 = 8256.
3-й слой: (64 + 1) · 10 = 650.
Итого: 109 386 — как и в выводе кода.
Память: 109 386 · 4 байта ≈ 0,44 МБ. 92 % параметров приходится на первый слой: большие входы обходятся дорого.
Сеть 2 → 2 → 1: W₁ = [[1; −1]; [0,5; 0,5]], b₁ = (0; −1), ReLU в скрытом слое, W₂ = [[2; −3]], b₂ = 1. Найди выход для x = (1; 2).
Показать решениеСкрыть решение
ReLU: h = (0; 0,5) — первый нейрон «выключился».
y = 2 · 0 + (−3) · 0,5 + 1 = −0,5.
Обрати внимание: для этого x первый нейрон никак не влияет на выход, поэтому при обратном проходе градиент его весов тоже будет нулевым — ReLU пропускает градиент только через активные нейроны.
import torch
x = torch.tensor([1.0, 2.0])
W1 = torch.tensor([[1.0, -1.0], [0.5, 0.5]])
b1 = torch.tensor([0.0, -1.0])
W2 = torch.tensor([[2.0, -3.0]])
b2 = torch.tensor([1.0])
h = torch.relu(W1 @ x + b1)
y = W2 @ h + b2
print(h, y)tensor([0.0000, 0.5000]) tensor([-0.5000])
Главное
- Нейрон вычисляет z = w · x + b, затем активацию a = φ(z); слой делает это в матричной форме: Y = X · Wᵀ + b.
- Без активаций многослойная сеть равна одному линейному слою; в скрытых слоях обычно используют ReLU.
- Softmax превращает логиты в вероятности с суммой 1; в
nn.CrossEntropyLossподают сырые логиты. - Модель наследует
nn.Module: слои — в__init__, вычисления — вforward; для простой цепочки хватитnn.Sequential. - В полносвязном слое (nin + 1) · nout параметров;
nn.Linear(n_in, n_out).weightимеет форму (nout, nin).
Проверь себя
Вопросов: 10. Каждый правильный ответ приносит XP.
nn.Linear(10, 5)?