Обучающий лосс падает, а валидационный растёт с третьей эпохи. Что вы проверите и в каком порядке?
Валидационный лосс достигает минимума на 3-й эпохе и затем устойчиво растёт, а обучающий продолжает падать. Первый порыв команды — поднять dropout до 0.7 и переобучить.
Ограничения: архитектуру пока не меняйте, а прогон должен сохранять лучшую модель, а не последнюю.
X_tr, X_val, y_tr, y_val = train_test_split(X, y, test_size=0.05, shuffle=True)
scaler = StandardScaler().fit(X) # обучен на всех данных
X_tr, X_val = scaler.transform(X_tr), scaler.transform(X_val)
best = None
for epoch in range(50):
train_one_epoch(model, X_tr, y_tr)
val = evaluate(model, X_val, y_val)
print(epoch, val)
torch.save(model.state_dict(), "final.pt")
Назовите, что проверяете до добавления регуляризации, и почините код.
Сначала убедитесь, что разрыв настоящий: валидация достаточно велика, из того же распределения и без утечки, льстившей ранним эпохам. Затем взвесьте ёмкость модели против размера выборки и перечитайте расписание скорости. И лишь потом регуляризация: сперва early stopping, затем dropout и weight decay.
- ✗Хватаются за более сильную регуляризацию до проверки сплита и поиска утечки
- ✗Обучают scaler на всех данных, занося статистики валидации в обучение
- ✗Сохраняют веса последней эпохи, когда лучшая модель была на несколько эпох раньше
- →Почему 5-процентный валидационный сплит делает раннее расхождение ненадёжным?
- →Какой регуляризатор вы возьмёте первым и почему именно его?
Поднимать dropout здесь рано — сигнал ещё не проверен, и в коде две настоящие ошибки.
1. Утечка через StandardScaler. Он обучен на X целиком, то есть среднее и дисперсия валидации попали в обучение. Ранние эпохи выглядят лучше, чем есть, и «расхождение» частично рукотворно. Обучать scaler надо только на X_tr.
2. Сплит в 5% и shuffle=True. На маленькой валидации кривая шумит настолько, что минимум на 3-й эпохе может быть случайностью. Берите 15–20%, а при временной структуре данных — разбиение по времени вместо перемешивания.
3. Сохраняется последняя эпоха, а не лучшая. Даже с настоящим переобучением прогон обязан отдать веса лучшей эпохи.
X_tr, X_val, y_tr, y_val = train_test_split(X, y, test_size=0.2, shuffle=True, stratify=y)
scaler = StandardScaler().fit(X_tr) # ТОЛЬКО обучающая часть
X_tr, X_val = scaler.transform(X_tr), scaler.transform(X_val)
best_val, best_state, patience, bad = float("inf"), None, 5, 0
for epoch in range(50):
train_one_epoch(model, X_tr, y_tr)
val = evaluate(model, X_val, y_val)
if val < best_val:
best_val, best_state, bad = val, copy.deepcopy(model.state_dict()), 0
else:
bad += 1
if bad >= patience:
break
model.load_state_dict(best_state)
torch.save(best_state, "best.pt")
Порядок: размер и происхождение сплита → утечка → ёмкость против объёма данных → расписание learning rate → и лишь затем dropout, weight decay, аугментация.