Фаза 03 · урок 09
Расписания скорости обучения и разогрев
Цель урока: Установите скорость обучения 0,1. Обучение расходится — функция потерь улетает в бесконечность за 3 шага. Установите 0,0001. Обучение ползёт — после 100 эпох модель едва сдвинулась со случайной инициализации. Установите 0,01. Обучение…
Текущий релиз AlexBred.com: первые 100 уроков русскоязычной программы.
Содержание урока
- Цели обучения
- Проблема
- Концепция
- Постоянная скорость обучения
- Ступенчатое затухание
- Косинусная анниляция
- Разогрев: почему начинаем с малого
- Линейный разогрев + косинусное затухание
- Политика 1cycle
- Формы расписаний
- Схема выбора
- Реальные числа из опубликованных моделей
- Соберите это
- Шаг 1: функции расписаний
- Шаг 2: визуализируйте все расписания
- Шаг 3: обучающая сеть
- Шаг 4: сравните все расписания
- Шаг 5: слишком высокая и слишком низкая LR
- Используйте это
- Выпустите это
- Упражнения
- Ключевые термины
- Дополнительное чтение
Скорость обучения — самый важный гиперпараметр. Не архитектура. Не размер набора данных. Не функция активации. Скорость обучения. Если настраивать только что-то одно — настраивайте её.
Тип: Сборка Языки: Python Предварительные требования: Урок 03.06 (Оптимизаторы), урок 03.08 (Инициализация весов) Время: ~90 минут
Цели обучения
- Реализовать с нуля постоянное, ступенчатое затухание, косинусную анниляцию, разогрев + косинус и расписания скорости обучения 1cycle
- Продемонстрировать три режима отказа при выборе скорости обучения: расходимость (слишком высокая), застой (слишком низкая) и колебания (без затухания)
- Объяснить, почему разогрев необходим оптимизаторам на основе Adam и как он стабилизирует раннее обучение
- Сравнить скорость сходимости всех пяти расписаний на одной задаче и выбрать подходящее для заданного бюджета обучения
Проблема
Установите скорость обучения 0,1. Обучение расходится — функция потерь улетает в бесконечность за 3 шага. Установите 0,0001. Обучение ползёт — после 100 эпох модель едва сдвинулась со случайной инициализации. Установите 0,01. Обучение работает 50 эпох, а затем функция потерь колеблется вокруг минимума, которого никогда не достигнет, потому что шаги слишком велики.
Оптимальная скорость обучения не постоянна. Она меняется во время обучения. В начале нужны большие шаги, чтобы быстро продвигаться. В конце нужны крошечные шаги, чтобы осесть в остром минимуме. Разница между моделью с точностью 90% и моделью с точностью 95% часто состоит лишь в расписании.
Каждая крупная модель, опубликованная за последние три года, использует расписание скорости обучения. Llama 3 использовала пиковое lr=3e-4 с 2000 шагами разогрева и косинусным затуханием до 3e-5. GPT-3 использовала lr=6e-4 с разогревом на протяжении 375 миллионов токенов. Это не произвольный выбор. Это результат масштабных переборов гиперпараметров, стоивших миллионы долларов.
Вам нужно понимать расписания, потому что значения по умолчанию не будут работать для вашей задачи. При дообучении предобученной модели подходящее расписание отличается от обучения с нуля. При увеличении размера пакета нужно менять период разогрева. Когда обучение ломается на шаге 10 000, нужно знать, проблема ли это расписания или чего-то другого.
Концепция
Постоянная скорость обучения
Самый простой подход. Выберите число и используйте его на каждом шаге.
lr(t) = lr_0
Редко бывает оптимальным. Оно либо слишком высоко для конца обучения (колебания вокруг минимума), либо слишком низко для начала (вычисления тратятся на крошечные шаги). Неплохо работает для маленьких моделей и отладки. Ужасный выбор для чего-либо, что обучается дольше часа.
Ступенчатое затухание
Старый подход эпохи ResNet. Уменьшайте скорость обучения в некоторое число раз (обычно в 10) на фиксированных эпохах.
lr(t) = lr_0 * gamma^(floor(epoch / step_size))
Здесь gamma = 0.1 и step_size = 30 означают: lr уменьшается в 10 раз каждые 30 эпох. ResNet-50 использовала это — lr=0.1, уменьшение в 10 раз на эпохах 30, 60 и 90.
Проблема: оптимальные точки затухания зависят от набора данных и архитектуры. Перейдите к другой задаче — и придётся снова настраивать моменты снижения. Переходы резкие: функция потерь может всплеснуть при внезапной смене скорости.
Косинусная анниляция
Плавное затухание от максимальной скорости обучения к минимальной по косинусной кривой:
lr(t) = lr_min + 0.5 * (lr_max - lr_min) * (1 + cos(pi * t / T))
Здесь t — текущий шаг, а T — общее число шагов.
При t=0 косинусный член равен 1, поэтому lr = lr_max. При t=T косинусный член равен -1, поэтому lr = lr_min. Затухание сначала мягкое, в середине ускоряется и к концу снова становится мягким.
Это значение по умолчанию для большинства современных запусков обучения. Не нужно настраивать гиперпараметры помимо lr_max и lr_min. Форма косинуса согласуется с эмпирическим наблюдением: большая часть обучения происходит в середине процесса, и в этот критический период нужны разумные размеры шагов.
Разогрев: почему начинаем с малого
Adam и другие адаптивные оптимизаторы поддерживают скользящие оценки среднего значения и дисперсии градиентов. На шаге 0 эти оценки инициализируются нулями. Первые несколько обновлений градиента основаны на ненадёжной статистике. Если в этот период скорость обучения велика, модель делает огромные, плохо направленные шаги.
Разогрев исправляет это. Начните с очень малой скорости обучения (часто lr_max / warmup_steps или даже с нуля) и линейно увеличивайте её до lr_max в течение первых N шагов. К моменту достижения полной скорости обучения статистика Adam стабилизируется.
lr(t) = lr_max * (t / warmup_steps) for t < warmup_steps
Обычный разогрев составляет 1–5% общего числа шагов обучения. Llama 3 обучалась примерно на 1,8 триллиона токенов и разогревалась 2000 шагов. GPT-3 разогревалась на протяжении 375 миллионов токенов.
Линейный разогрев + косинусное затухание
Современное значение по умолчанию. Линейно увеличьте скорость, затем уменьшайте её по косинусу:
if t < warmup_steps:
lr(t) = lr_max * (t / warmup_steps)
else:
progress = (t - warmup_steps) / (total_steps - warmup_steps)
lr(t) = lr_min + 0.5 * (lr_max - lr_min) * (1 + cos(pi * progress))
Именно это используют Llama, GPT, PaLM и большинство современных трансформеров. Разогрев предотвращает раннюю нестабильность. Косинусное затухание позволяет модели осесть в хорошем минимуме.
Политика 1cycle
Открытие Leslie Smith (2018): в первой половине обучения увеличивайте скорость обучения от малого значения до высокого, затем во второй половине уменьшайте её обратно. Контринтуитивно — зачем увеличивать скорость обучения в середине?
Теория такова: высокая скорость обучения действует как регуляризация, добавляя шум в траекторию оптимизации. В фазе наращивания модель исследует большую часть ландшафта функции потерь и находит лучшие бассейны. В фазе снижения она затем уточняет решение в лучшем из найденных бассейнов.
Phase 1 (0 to T/2): lr ramps from lr_max/25 to lr_max
Phase 2 (T/2 to T): lr ramps from lr_max to lr_max/10000
1cycle часто обучает быстрее косинусной анниляции при фиксированном вычислительном бюджете. Компромисс: необходимо заранее знать общее число шагов.
Формы расписаний
Схема выбора
Реальные числа из опубликованных моделей
lr-schedule
Соберите это
Шаг 1: функции расписаний
Каждая функция принимает текущий шаг и возвращает скорость обучения на этом шаге.
import math
def constant_schedule(step, lr=0.01, **kwargs):
return lr
def step_decay_schedule(step, lr=0.1, step_size=100, gamma=0.1, **kwargs):
return lr * (gamma ** (step // step_size))
def cosine_schedule(step, lr=0.01, total_steps=1000, lr_min=1e-5, **kwargs):
if step >= total_steps:
return lr_min
return lr_min + 0.5 * (lr - lr_min) * (1 + math.cos(math.pi * step / total_steps))
def warmup_cosine_schedule(step, lr=0.01, total_steps=1000, warmup_steps=100, lr_min=1e-5, **kwargs):
if total_steps <= warmup_steps:
return lr * (step / max(warmup_steps, 1))
if step < warmup_steps:
return lr * step / warmup_steps
progress = (step - warmup_steps) / (total_steps - warmup_steps)
return lr_min + 0.5 * (lr - lr_min) * (1 + math.cos(math.pi * progress))
def one_cycle_schedule(step, lr=0.01, total_steps=1000, **kwargs):
mid = max(total_steps // 2, 1)
if step < mid:
return (lr / 25) + (lr - lr / 25) * step / mid
else:
progress = (step - mid) / max(total_steps - mid, 1)
return lr * (1 - progress) + (lr / 10000) * progress
Шаг 2: визуализируйте все расписания
Выведите текстовый график, показывающий, как каждое расписание развивается в ходе обучения.
def visualize_schedule(name, schedule_fn, total_steps=500, **kwargs):
steps = list(range(0, total_steps, total_steps // 20))
if total_steps - 1 not in steps:
steps.append(total_steps - 1)
lrs = [schedule_fn(s, total_steps=total_steps, **kwargs) for s in steps]
max_lr = max(lrs) if max(lrs) > 0 else 1.0
print(f"\n{name}:")
for s, lr_val in zip(steps, lrs):
bar_len = int(lr_val / max_lr * 40)
bar = "#" * bar_len
print(f" Step {s:4d}: lr={lr_val:.6f} {bar}")
Шаг 3: обучающая сеть
Простая двухслойная сеть на наборе данных с кругом — как в предыдущих уроках, но теперь мы варьируем расписание.
import random
def sigmoid(x):
x = max(-500, min(500, x))
return 1.0 / (1.0 + math.exp(-x))
def relu(x):
return max(0.0, x)
def relu_deriv(x):
return 1.0 if x > 0 else 0.0
def make_circle_data(n=200, seed=42):
random.seed(seed)
data = []
for _ in range(n):
x = random.uniform(-2, 2)
y = random.uniform(-2, 2)
label = 1.0 if x * x + y * y < 1.5 else 0.0
data.append(([x, y], label))
return data
def train_with_schedule(schedule_fn, schedule_name, data, epochs=300, base_lr=0.05, **kwargs):
random.seed(0)
hidden_size = 8
total_steps = epochs * len(data)
std = math.sqrt(2.0 / 2)
w1 = [[random.gauss(0, std) for _ in range(2)] for _ in range(hidden_size)]
b1 = [0.0] * hidden_size
w2 = [random.gauss(0, std) for _ in range(hidden_size)]
b2 = 0.0
step = 0
epoch_losses = []
for epoch in range(epochs):
total_loss = 0
correct = 0
for x, target in data:
lr = schedule_fn(step, lr=base_lr, total_steps=total_steps, **kwargs)
z1 = []
h = []
for i in range(hidden_size):
z = w1[i][0] * x[0] + w1[i][1] * x[1] + b1[i]
z1.append(z)
h.append(relu(z))
z2 = sum(w2[i] * h[i] for i in range(hidden_size)) + b2
out = sigmoid(z2)
error = out - target
d_out = error * out * (1 - out)
for i in range(hidden_size):
d_h = d_out * w2[i] * relu_deriv(z1[i])
w2[i] -= lr * d_out * h[i]
for j in range(2):
w1[i][j] -= lr * d_h * x[j]
b1[i] -= lr * d_h
b2 -= lr * d_out
total_loss += (out - target) ** 2
if (out >= 0.5) == (target >= 0.5):
correct += 1
step += 1
avg_loss = total_loss / len(data)
accuracy = correct / len(data) * 100
epoch_losses.append(avg_loss)
return epoch_losses
Шаг 4: сравните все расписания
Обучите одну и ту же сеть с каждым расписанием и сравните итоговую функцию потерь и поведение сходимости.
def compare_schedules(data):
configs = [
("Constant", constant_schedule, {}),
("Step Decay", step_decay_schedule, {"step_size": 15000, "gamma": 0.1}),
("Cosine", cosine_schedule, {"lr_min": 1e-5}),
("Warmup+Cosine", warmup_cosine_schedule, {"warmup_steps": 3000, "lr_min": 1e-5}),
("1cycle", one_cycle_schedule, {}),
]
print(f"\n{'Schedule':<20} {'Start Loss':>12} {'Mid Loss':>12} {'End Loss':>12} {'Best Loss':>12}")
print("-" * 70)
for name, schedule_fn, extra_kwargs in configs:
losses = train_with_schedule(schedule_fn, name, data, epochs=300, base_lr=0.05, **extra_kwargs)
mid_idx = len(losses) // 2
best = min(losses)
print(f"{name:<20} {losses[0]:>12.6f} {losses[mid_idx]:>12.6f} {losses[-1]:>12.6f} {best:>12.6f}")
Шаг 5: слишком высокая и слишком низкая LR
Продемонстрируйте три режима отказа: слишком высокая (расходимость), слишком низкая (ползучее обучение) и подходящая скорость.
def lr_sensitivity(data):
learning_rates = [1.0, 0.1, 0.01, 0.001, 0.0001]
print("\nLR Sensitivity (constant schedule, 100 epochs):")
print(f" {'LR':>10} {'Start Loss':>12} {'End Loss':>12} {'Status':>15}")
print(" " + "-" * 52)
for lr in learning_rates:
losses = train_with_schedule(constant_schedule, f"lr={lr}", data, epochs=100, base_lr=lr)
start = losses[0]
end = losses[-1]
if end > start or math.isnan(end) or end > 1.0:
status = "DIVERGED"
elif end > start * 0.9:
status = "BARELY MOVED"
elif end < 0.15:
status = "CONVERGED"
else:
status = "LEARNING"
end_str = f"{end:.6f}" if not math.isnan(end) else "NaN"
print(f" {lr:>10.4f} {start:>12.6f} {end_str:>12} {status:>15}")
Используйте это
PyTorch предоставляет планировщики в torch.optim.lr_scheduler:
import torch
import torch.optim as optim
from torch.optim.lr_scheduler import CosineAnnealingLR, OneCycleLR, StepLR
model = nn.Sequential(nn.Linear(10, 64), nn.ReLU(), nn.Linear(64, 1))
optimizer = optim.Adam(model.parameters(), lr=3e-4)
scheduler = CosineAnnealingLR(optimizer, T_max=1000, eta_min=1e-5)
for step in range(1000):
loss = train_step(model, optimizer)
scheduler.step()
Для разогрева + косинуса используйте lambda-планировщик или get_cosine_schedule_with_warmup из HuggingFace:
from transformers import get_cosine_schedule_with_warmup
scheduler = get_cosine_schedule_with_warmup(
optimizer,
num_warmup_steps=2000,
num_training_steps=100000,
)
Функцию HuggingFace используют большинство скриптов дообучения Llama и GPT. Если сомневаетесь, используйте разогрев + косинус, где разогрев составляет 3–5% общего числа шагов. Это работает почти для всего.
Выпустите это
Этот урок создаёт:
outputs/prompt-lr-schedule-advisor.md— промпт, который рекомендует правильное расписание скорости обучения и гиперпараметры для вашей конфигурации обучения
Упражнения
-
Реализуйте экспоненциальное затухание:
lr(t) = lr_0 * gamma^t, гдеgamma = 0.999. Сравните его с косинусной анниляцией на наборе данных с кругом. -
Реализуйте тест диапазона скорости обучения (Leslie Smith): обучайте несколько сотен шагов, экспоненциально увеличивая LR от
1e-7до 1. Постройте график функции потерь относительно LR. Оптимальная максимальная LR находится непосредственно перед тем, как функция потерь начинает расти. -
Обучайте с разогревом + косинусом, но варьируйте длину разогрева: 0%, 1%, 5%, 10%, 20% общего числа шагов. Найдите золотую середину, при которой обучение наиболее стабильно.
-
Реализуйте косинусную анниляцию с тёплыми перезапусками (SGDR): сбрасывайте скорость обучения до
lr_maxкаждые T шагов и снова уменьшайте её. Сравните со стандартным косинусом на более длинном запуске обучения. -
Создайте «хирурга расписаний», который отслеживает функцию потерь обучения, автоматически переключается с разогрева на косинус, когда функция потерь стабилизируется, и уменьшает
lr, если функция потерь слишком долго находится на плато.
Ключевые термины
| Термин | Как обычно говорят | Что это действительно означает |
|---|---|---|
| Скорость обучения | «Как быстро обучается модель» | Скаляр, умножающий градиент для определения размера обновления параметров |
| Расписание | «Меняйте LR со временем» | Функция, отображающая шаг обучения в скорость обучения и предназначенная для оптимизации сходимости |
| Разогрев | «Начните с малой LR» | Линейное увеличение LR от почти нуля до целевого значения в первые N шагов для стабилизации статистики оптимизатора |
| Косинусная анниляция | «Плавное затухание LR» | Уменьшение LR по косинусной кривой от lr_max до lr_min в течение обучения |
| Ступенчатое затухание | «Снижайте LR на контрольных точках» | Умножение LR на коэффициент (обычно 0,1) через фиксированные интервалы эпох |
| Политика 1cycle | «Вверх, затем вниз» | Метод Leslie Smith: увеличивать, а затем уменьшать LR в одном цикле для более быстрой сходимости |
| Тест диапазона LR | «Найдите лучшую скорость обучения» | Короткое обучение с увеличением LR для нахождения значения, при котором функция потерь начинает расходиться |
| Косинус с тёплыми перезапусками | «Сбросить и повторить» | Периодический сброс LR до lr_max с последующим новым затуханием (SGDR) |
| Eta min | «Нижняя граница LR» | Минимальная скорость обучения, до которой затухает расписание |
| Пиковая скорость обучения | «Максимальная LR» | Наибольшая LR, достигаемая во время обучения, обычно после разогрева |
Дополнительное чтение
- Loshchilov & Hutter, «SGDR: Stochastic Gradient Descent with Warm Restarts» (2017) — представили косинусную анниляцию и тёплые перезапуски
- Smith, «Super-Convergence: Very Fast Training of Neural Networks Using Large Learning Rates» (2018) — статья о политике 1cycle
- Touvron et al., «Llama 2: Open Foundation and Fine-Tuned Chat Models» (2023) — документирует масштабное использование расписания разогрев + косинус
- Goyal et al., «Accurate, Large Minibatch SGD: Training ImageNet in 1 Hour» (2017) — правило линейного масштабирования и разогрев для больших пакетов
Источник: Learning Rate Schedules and Warmup 03.08 — Инициализация весов · Фаза 3 — Основы глубокого обучения · Полный каталог · 03.10 — Создайте собственный мини-фреймворк