Как работает dropout на обучении и на инференсе?
Что проверяют
Понимаете ли вы, почему dropout ведёт себя по-разному на обучении и инференсе и как при этом сохраняется масштаб активаций. Следом обычно спрашивают, что будет, если забыть model.eval().
Как отвечать
На обучении каждый элемент входа слоя независимо зануляется с вероятностью p, на каждом шаге маска новая. Сеть не может опереться на отдельный нейрон и учится избыточным признакам, а сам метод можно понимать как обучение ансамбля подсетей с общими весами.
Дальше главное, про масштаб. Если выключить половину нейронов, сумма на входе следующего слоя упадёт вдвое, а на инференсе без выключений окажется больше, чем модель видела при обучении. Решается это одним из двух способов:
- inverted dropout: оставшиеся значения на обучении делят на
1 - p, а на инференсе слой ничего не делает. Так работаютnn.Dropoutв PyTorch иDropoutв Keras; - классический вариант из исходной статьи: на обучении без масштабирования, а на инференсе активации умножают на вероятность сохранения
1 - p. Сейчас встречается редко.
Режим переключают явно. В PyTorch это model.train() и model.eval(), в Keras аргумент training при вызове слоя, а fit и predict выставляют его сами. У nn.Dropout значение p по умолчанию 0.5, в трансформерах типично 0.1, а при предобучении больших языковых моделей dropout часто не используют вовсе.
Можно добавить Monte Carlo dropout: оставить dropout включённым на инференсе, прогнать вход несколько раз и по разбросу предсказаний оценить неопределённость модели.
Где спотыкаются
Говорят, что на инференсе веса умножают на 1 - p, не уточняя схему. Для inverted dropout, который стоит во всех современных фреймворках, это двойное масштабирование и неверный ответ.
Считают, что torch.no_grad() отключает dropout. Он отключает только расчёт градиентов, а dropout выключает model.eval(). Забыли его, и предсказания меняются от запуска к запуску.
Вызывают F.dropout внутри forward без training=self.training. У функциональной версии по умолчанию training=True, и dropout работает даже после eval().