Мы привыкли использовать ReduceLROnPlateau если val_loss не улучшается N эпох подряд — режем learning_rate. Это работает. Мы ждем, пока обучение врежется в стену, и только потом реагируем.
А что, если мы могли бы увидеть эту стену заранее? Что, если бы мы могли сбросить скорость плавно, еще на подходе к плато, и снова нажать на газ, если впереди откроется новый спуск?
Я хочу поделиться концепцией умного LR шедулера, который управляет скоростью обучения, анализируя не сам loss, а скорость его изменения.
Проблема ReduceLROnPlateau: Мы реагируем на симптом, а не на причину
ReduceLROnPlateau срабатывает, когда val_loss перестает падать. Это уже финальная стадия замедления. В этот момент оптимизатор уже блуждает по плоскому дну долины, и резкое снижение LR — это скорее мера отчаяния.
Мы теряем драгоценные эпохи, пока ждем срабатывания patience, и упускаем момент, когда можно было бы вмешаться.
«Шедулер, который смотрит на вторую производную»
Давайте рассуждать как физики, а не как программисты.
val_loss это наша позиция на вертикальной оси ландшафта потерь.
Изменение val_loss за эпоху (loss_t — loss_{t-1}) это наша скорость спуска.
Изменение этой скорости это наше ускорение (или замедление). Это и есть вторая производная loss по времени (эпохам).
Идея проста: Мы будем менять learning_rate не тогда, когда скорость упала до нуля, а тогда, когда ускорение стало отрицательным (мы начали замедляться).
Наблюдаем за скоростью: На каждой эпохе мы вычисляем, насколько val_loss улучшился по сравнению с предыдущей эпохой. Назовем это improvement.
Анализируем тренд улучшений: Мы собираем историю improvement за последние, скажем, 10-15 эпох.
Ищем замедление: Если мы видим, что среднее improvement за последние эпохи стабильно уменьшается (т.е. мы все еще спускаемся, но все медленнее и медленнее), это сигнал к упреждающему снижению learning_rate. Мы притормаживаем перед входом в крутой поворот.
Ищем ускорение: А что, если после снижения LR мы внезапно нашли новый крутой спуск? Скорость улучшений (improvement) снова начнет расти. Наш умный шедулер это заметит и может вернуть learning_rate обратно на более высокое значение, чтобы быстрее пройти этот новый участок
Реализация на Python и PyTorch
Давайте набросаем класс, реализующий эту логику.
import numpy as np class ProactiveLRScheduler: def __init__(self, optimizer, factor=0.1, patience=10, window_size=20, min_lr=1e-8, cooldown=0, verbose=True): self.optimizer = optimizer self.factor = factor self.patience = patience self.window_size = window_size self.min_lr = min_lr self.cooldown = cooldown self.verbose = verbose self.history = [] self.bad_trend_counter = 0 self.cooldown_counter = 0 self.last_lr_change_epoch = 0 def step(self, current_loss, epoch): self.history.append(current_loss) if len(self.history) < self.window_size: return # Накапливаем историю if self.cooldown_counter > 0: self.cooldown_counter -= 1 return # Находимся в периоде охлаждения после изменения LR # Вычисляем скорости улучшений за последние N-1 эпох improvements = -np.diff(self.history[-self.window_size:]) # Если среднее улучшение стало очень маленьким или отрицательным — это плохой знак # Мы можем анализировать тренд этих улучшений x = np.arange(len(improvements)) slope, _ = np.polyfit(x, improvements, 1) # Наклон тренда улучшений if self.verbose: print(f»[Scheduler] Наклон тренда улучшений: {slope:.6f}») # Если наклон < 0, значит, улучшения замедляются if slope < 0: self.bad_trend_counter += 1 else: # Если улучшения снова начали ускоряться, сбрасываем счетчик self.bad_trend_counter = 0 if self.bad_trend_counter >= self.patience: self._reduce_lr(epoch) self.bad_trend_counter = 0 # Сбрасываем после срабатывания def _reduce_lr(self, epoch): for i, param_group in enumerate(self.optimizer.param_groups): old_lr = float(param_group[‘lr’]) new_lr = max(old_lr * self.factor, self.min_lr) if old_lr — new_lr > 1e-8: # Если изменение значимо param_group[‘lr’] = new_lr if self.verbose: print(f»Эпоха {epoch}: снижаю learning rate группы {i} с {old_lr:.2e} до {new_lr:.2e}.») self.cooldown_counter = self.cooldown self.last_lr_change_epoch = epoch
(Примечание: это концептуальная реализация. Логику возврата LR можно добавить как отдельное условие, если slope становится сильно положительным)
Заключение
Возможно, этот подход не всегда будет лучше стандартного, но он точно является шагом к более умным и адаптивным инструментам, которые чувствуют процесс обучения, а не просто следуют жестким правилам.
Источник: habr.com



























