Полный пример обучения построчно

Финальная сборка раздела: рабочий пример обучения от начала до конца, строка за строкой.

Цель урока — увидеть все детали вместе на одной маленькой, но реальной задаче: научить линию проходить через данные.

Задача

Сгенерируем точки вокруг прямой y = 2x + 1 с шумом и попросим модель восстановить эту зависимость. Это регрессия: модель nn.Linear(1, 1) должна сама подобрать наклон около 2 и сдвиг около 1.

Шаг 1. Данные

import torch
import torch.nn as nn

torch.manual_seed(0)              # воспроизводимость

# 100 точек: x случайные, y = 2x + 1 + шум
X = torch.rand(100, 1) * 10       # форма (100, 1), значения 0..10
y = 2 * X + 1 + torch.randn(100, 1)  # истинная зависимость + шум

Формы (100, 1) — это 100 примеров по одному признаку. torch.manual_seed(0) фиксирует случайность, чтобы результат повторялся.

Шаг 2. Модель, loss, оптимизатор

model = nn.Linear(1, 1)                         # один вход, один выход
criterion = nn.MSELoss()                        # регрессия -> MSE
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

Три кирпича из прошлых уроков: линейный слой (он и есть наша прямая y = wx + b), среднеквадратичный loss и оптимизатор SGD, которому отдали параметры модели.

Шаг 3. Цикл обучения

for epoch in range(200):
    pred = model(X)                 # 1. forward
    loss = criterion(pred, y)       # 2. loss

    optimizer.zero_grad()           # 3. чистим градиенты
    loss.backward()                 # 4. backprop
    optimizer.step()                # 5. шаг по весам

    if (epoch + 1) % 50 == 0:
        print(f"epoch {epoch+1:3d}  loss {loss.item():.3f}")

Это ровно тот ритуал из пяти шагов. Каждые 50 эпох печатаем loss — он должен снижаться. Обратите внимание на loss.item(): достаём число из тензора для печати (без удержания графа).

Вывод (примерно):

epoch  50  loss 1.421
epoch 100  loss 1.045
epoch 150  loss 0.987
epoch 200  loss 0.971

Loss упал и вышел на плато около 1.0 — это уровень шума, который мы сами добавили. Идеального нуля и не должно быть: модель не может предсказать случайный шум, и это правильно.

Шаг 4. Смотрим, что выучила модель

w = model.weight.item()     # выученный наклон
b = model.bias.item()       # выученный сдвиг
print(f"выучено: y = {w:.2f} * x + {b:.2f}")
# выучено: y = 2.01 * x + 0.95   (близко к истинным 2 и 1)

# предсказание на новой точке — режим оценки, без графа
model.eval()
with torch.no_grad():
    x_new = torch.tensor([[5.0]])
    print("предсказание для x=5:", model(x_new).item())
    # около 11.0  (2*5 + 1)

Модель восстановила почти точные коэффициенты: наклон ≈ 2.01, сдвиг ≈ 0.95 — мы их не задавали, она нашла их сама, минимизируя loss. На предсказании мы аккуратно использовали eval() и no_grad(), как учились в прошлом уроке.

Чек-лист рабочего цикла

Есть в коде?Что именно
дапять шагов в правильном порядке
даzero_grad() перед backward
даloss подходит задаче (MSE для регрессии)
даформы предсказания и цели совпадают (100, 1)
даeval() + no_grad() на предсказании

Итог

  • Полное обучение = данные → (модель, loss, оптимизатор) → цикл из 5 шагов → проверка.
  • Loss выходит на плато уровня шума в данных — нулём он быть не обязан.
  • Модель сама подобрала коэффициенты, минимизируя loss; мы их не задавали.
  • Один и тот же шаблон масштабируется от линии до огромных сетей — меняются лишь модель и данные.
Проверьте себя
1. Почему loss в примере вышел на плато около 1.0, а не упал до нуля?
ALearning rate был слишком мал
BМодель не может предсказать добавленный случайный шум — это нормально
CЗабыли zero_grad
DСлой nn.Linear не способен к регрессии
2. Почему предсказание делают внутри model.eval() и torch.no_grad()?
AЧтобы ускорить backward
BЧтобы слои работали в режиме оценки и не строился лишний граф
CИначе модель не выдаст результат
DЧтобы обнулить веса
3. Что показывает model.weight.item() после обучения на y = 2x + 1?
AЗначение loss
BВыученный наклон прямой, близкий к 2
CЧисло эпох
DГрадиент веса
Поддержать проект