Режимы 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()выключает регуляризацию.