Обучающий лосс с первой эпохи стоит на месте и высоко. Как вы будете это диагностировать?
Прогон обучения выдаёт одно и то же значение лосса каждую эпоху, а точность не уходит от уровня случайного угадывания. Загрузчик данных и сама модель проверены по отдельности и заведомо корректны.
Ограничения: меняйте только этот код обучения, архитектуру сохраните и назовите проверку, которая срабатывает первой.
model = MLP(in_dim=64, hidden=128, out_dim=10)
opt = torch.optim.SGD(model.parameters(), lr=1e-7)
loss_fn = torch.nn.CrossEntropyLoss()
for epoch in range(20):
for x, y in loader:
opt.zero_grad()
logits = model(x)
loss = loss_fn(torch.softmax(logits, dim=-1), y)
loss.backward()
opt.step()
Найдите причины, по которым лосс не может сдвинуться, и исправьте их.
Идите от дешёвого: попробуйте переобучиться на одном батче — не вышло, значит баг в коде, а не в данных. Затем проверьте соответствие меток входам, подходит ли лосс задаче, отнормированы ли входы и не абсурдна ли скорость. В конце ищите мёртвые ReLU и параметры без оптимизатора.
- ✗Читают плоский лосс как сходимость, а не как сломанный шаг обучения
- ✗Наращивают ёмкость, не проверив, что модель может переобучиться на одном батче
- ✗Подают вероятности после softmax в лосс, который сам ожидает сырые логиты
- →Почему переобучение на одном батче — самая дешёвая первая проверка?
- →Как убедиться, что градиенты действительно доходят до самого раннего слоя?
Здесь ломаются сразу две вещи, и обе видны без единого запуска.
1. Двойной softmax. CrossEntropyLoss в PyTorch сам применяет log_softmax к сырым логитам. Подавая в него уже нормированные torch.softmax(logits), вы прогоняете softmax дважды: распределение становится почти равномерным, градиент — крошечным, и лосс застывает у ln(10) ≈ 2.30.
2. Learning rate 1e-7. Даже с починенным лоссом шаг настолько мал, что за 20 эпох веса не сдвинутся заметно.
model = MLP(in_dim=64, hidden=128, out_dim=10)
opt = torch.optim.SGD(model.parameters(), lr=1e-2, momentum=0.9)
loss_fn = torch.nn.CrossEntropyLoss()
# Проверка №1 — самая дешёвая: сможет ли модель переобучиться на ОДНОМ батче?
x, y = next(iter(loader))
for _ in range(200):
opt.zero_grad()
loss = loss_fn(model(x), y) # сырые логиты, без softmax
loss.backward()
opt.step()
assert loss.item() < 0.01, "код сломан: одного батча выучить не удалось"
for epoch in range(20):
for x, y in loader:
opt.zero_grad()
loss = loss_fn(model(x), y)
loss.backward()
opt.step()
Порядок проверок: переобучение на одном батче → соответствие меток и лосса задаче → нормировка входов → learning rate → мёртвые ReLU и параметры, не попавшие в оптимизатор.