Learning rate: слишком большой и слишком малый шаг

Learning rate решает, выживет ли обучение: слишком большой шаг разносит спуск вразнос, слишком маленький — заставляет ползти вечность.

Learning rate (шаг обучения, η) — множитель, на который масштабируется шаг в градиентном спуске. Самый важный гиперпараметр оптимизации.

Шаг должен быть «в самый раз»

Вернёмся к спуску с горы. Если делать гигантские прыжки, легко перескочить долину и оказаться на противоположном склоне выше, чем был, — и так скакать всё выше. Если семенить миллиметровыми шажками, спустишься, но к закату. Хороший шаг — компромисс: достаточно большой, чтобы двигаться быстро, но достаточно малый, чтобы не промахиваться мимо дна. Сравним три значения lr на одной задаче.

def grad(x): return 2 * (x - 3)      # минимум функции (x-3)^2 в x=3

def descend(lr, steps=8):
    x = 0.0
    path = [round(x, 3)]
    for _ in range(steps):
        x = x - lr * grad(x)
        path.append(round(x, 3))
    return path

print("lr=0.01 (мало) :", descend(0.01))
print("lr=0.3  (норм) :", descend(0.3))
print("lr=0.9  (велик):", descend(0.9))

Вывод:

lr=0.01 (мало) : [0.0, 0.06, 0.119, 0.176, 0.233, 0.288, 0.342, 0.396, 0.448]
lr=0.3  (норм) : [0.0, 1.8, 2.52, 2.808, 2.923, 2.969, 2.988, 2.995, 2.998]
lr=0.9  (велик): [0.0, 5.4, 1.08, 4.536, 1.771, 3.983, 2.214, 3.629, 2.497]

Читаем результаты

  • lr = 0.01 (слишком мало): за 8 шагов добрались только до 0.45 при цели 3 — спуск ползёт, потребуются сотни итераций.
  • lr = 0.3 (в самый раз): к восьмому шагу уже 2.998 — почти точное попадание в минимум. Быстро и устойчиво.
  • lr = 0.9 (великоват): точка перескакивает минимум и колеблется вокруг 3 (5.4 → 1.08 → 4.54 → ...), но колебания затухают и спуск всё же сходится. Чуть больше — и затухание сменится взрывом.

Расходимость: когда шаг убивает обучение

Если шаг слишком велик, спуск не просто медленный — он расходится: значения улетают в бесконечность, и потеря растёт вместо падения. Это частая беда новичков: «модель не учится, loss стал NaN». Почти всегда виноват завышенный learning rate. Посмотрим на lr = 1.5.

def grad(x): return 2 * (x - 3)

x = 0.0
lr = 1.5            # слишком большой шаг
for step in range(1, 7):
    x = x - lr * grad(x)
    print(f"шаг {step}: x = {round(x, 2)}")

Вывод:

шаг 1: x = 9.0
шаг 2: x = -9.0
шаг 3: x = 27.0
шаг 4: x = -45.0
шаг 5: x = 99.0
шаг 6: x = -189.0

Вместо схождения к 3 точка всё дальше улетает от минимума, осциллируя со взрывным ростом. Это и есть расходимость.

Как выбирают learning rate

СимптомПричинаЧто делать
Потеря растёт / NaNlr слишком большойуменьшить в 3–10 раз
Потеря прыгает, не падаетlr великоватнемного уменьшить
Потеря падает очень медленноlr слишком маленькийувеличить

На практике пробуют значения по логарифмической шкале (0.001, 0.01, 0.1) и смотрят на кривую потерь. Часто применяют расписание: начинают с большего шага, затем уменьшают по ходу обучения — крупные шаги в начале, аккуратная доводка в конце.

Итог

  • Learning rate η — главный гиперпараметр: размер шага градиентного спуска.
  • Слишком малый → спуск ползёт; слишком большой → прыгает или расходится в бесконечность.
  • Растущая потеря (NaN) почти всегда означает завышенный learning rate.
  • Подбирают по логарифмической шкале; часто уменьшают по ходу обучения.
Проверьте себя
1. Что произойдёт при слишком большом learning rate?
AСпуск станет идеально точным
BСпуск может перепрыгивать минимум и расходиться — потеря растёт в бесконечность
CОбучение просто остановится без ошибок
DLearning rate не влияет на сходимость
2. Loss во время обучения стал NaN. Что наиболее вероятно?
AСлишком маленький learning rate
BСлишком большой learning rate вызвал расходимость
CМало данных
DНеправильно посчитано среднее
3. Чем плох слишком маленький learning rate?
AОн приводит к расходимости
BОбучение сходится очень медленно — нужны тысячи лишних итераций
CОн делает потерю отрицательной
DНичем, маленький шаг всегда лучше
Поддержать проект