Режимы train/eval и валидация

Понимаем разницу между режимами обучения и оценки и учимся правильно проверять модель.

model.train() / model.eval() — переключатели режима, которые меняют поведение слоёв вроде dropout и batchnorm.

Почему у модели два режима

Некоторые слои ведут себя по-разному при обучении и при использовании. Главные примеры:

  • Dropout при обучении случайно «гасит» часть нейронов (регуляризация), а на инференсе должен пропускать все — иначе предсказания будут случайными.
  • BatchNorm при обучении нормализует по текущему батчу и копит статистику, а на инференсе использует накопленные средние.

Чтобы слои знали, в каком они режиме, модель переключают: model.train() перед обучением и model.eval() перед оценкой/инференсом.

model.train()    # включить режим обучения (dropout активен)
# ... цикл обучения ...

model.eval()     # включить режим оценки (dropout выключен)
# ... предсказания на тесте ...

Важно: эти методы не запускают обучение и не отключают градиенты сами по себе — они только переключают поведение слоёв. Отключение градиентов — это отдельный torch.no_grad().

Правильная валидация

Чтобы честно оценить модель на отложенных данных, нужно совместить два инструмента: model.eval() (правильное поведение слоёв) и torch.no_grad() (не строить граф, экономия памяти и скорости). Вот канонический блок валидации:

model.eval()                      # 1. режим оценки
with torch.no_grad():            # 2. без построения графа
    correct = 0
    total = 0
    for X_batch, y_batch in val_loader:
        logits = model(X_batch)
        pred = logits.argmax(dim=1)        # класс с макс. логитом
        correct += (pred == y_batch).sum().item()
        total += y_batch.size(0)
    accuracy = correct / total
print("accuracy:", accuracy)

Разберём ключевую строку: logits.argmax(dim=1) для каждого примера берёт индекс наибольшего логита — это и есть предсказанный класс. Сравнение (pred == y_batch) даёт тензор из True/False, .sum() считает число совпадений, .item() достаёт число. Делим на общее количество — получаем точность.

Не забыть вернуться в train

Очень частая ошибка: вызвать model.eval() для валидации и забыть вернуть model.train() перед следующей эпохой. Тогда dropout останется выключенным, и регуляризация перестанет работать. Поэтому в цикле по эпохам режим переключают каждый раз:

for epoch in range(num_epochs):
    model.train()           # обучение
    for X, y in train_loader:
        ...                 # forward, loss, zero_grad, backward, step

    model.eval()            # оценка
    with torch.no_grad():
        ...                 # считаем accuracy на валидации

Зачем валидация вообще

Loss на обучающих данных всегда падает — модель просто запоминает их. Реальное качество показывает только проверка на данных, которых модель не видела. Если на обучении loss маленький, а на валидации большой — это переобучение (про него отдельный урок). Валидация — ваш честный измеритель, без неё легко обмануть себя.

Итог

  • model.train() и model.eval() переключают поведение dropout и batchnorm, но не трогают градиенты.
  • Валидация = model.eval() + torch.no_grad() вместе.
  • logits.argmax(dim=1) даёт предсказанный класс; сравнение с целью — точность.
  • В цикле эпох режим переключают каждый раз; забытый train() выключает регуляризацию.
Проверьте себя
1. Что меняет вызов model.eval()?
AЗапускает обучение модели
BОтключает вычисление градиентов
CПереключает поведение слоёв dropout и batchnorm в режим оценки
DУдаляет параметры модели
2. Какая комбинация правильна для блока валидации?
AТолько model.eval()
BТолько torch.no_grad()
Cmodel.eval() вместе с torch.no_grad()
Dmodel.train() вместе с torch.no_grad()
3. Что вернёт logits.argmax(dim=1) для батча логитов формы (N, C)?
AМаксимальные значения логитов
BИндексы классов с наибольшим логитом для каждого примера
CВероятности всех классов
DСумму логитов по классам
Поддержать проект