Градиентный спуск: идея и реализация
Градиентный спуск находит минимум, делая маленькие шаги против наклона функции — как спуск с горы в тумане.
Градиентный спуск — итеративный метод: из текущей точки шагаем в сторону, противоположную градиенту, и повторяем, пока не достигнем минимума. Правило:
x ← x − η·f'(x), где η — шаг обучения.
Спуск с горы в тумане
Представьте, что вы на склоне в густом тумане и хотите спуститься в долину. Вы не видите дна, но можете ощупать землю под ногами и понять, куда уклон. Логичная стратегия: сделать шаг в сторону самого крутого спуска, потом снова ощупать, снова шагнуть. Градиентный спуск делает ровно это: «наклон под ногами» — это производная (градиент), а «шаг» — обновление параметра. Идём против наклона, потому что нам нужно вниз.
Правило обновления
Формула одна и простая: x_новое = x − η · f'(x). Здесь f'(x) — наклон в текущей точке, а η (эта, learning rate) — шаг обучения: насколько большой шаг делаем. Знак минус разворачивает нас от роста к убыванию. Реализуем спуск для f(x) = (x − 3)² с минимумом в x = 3.
def f(x):
return (x - 3) ** 2
def grad(x):
return 2 * (x - 3) # производная (x-3)^2
x = 0.0 # стартуем далеко от минимума
lr = 0.1 # шаг обучения
for step in range(1, 11):
g = grad(x)
x = x - lr * g # шаг против градиента
print(f"шаг {step:2}: x = {round(x, 4):6} f(x) = {round(f(x), 4)}")
Вывод:
шаг 1: x = 0.6 f(x) = 5.76 шаг 2: x = 1.08 f(x) = 3.6864 шаг 3: x = 1.464 f(x) = 2.3593 шаг 4: x = 1.7712 f(x) = 1.5099 шаг 5: x = 2.017 f(x) = 0.9664 шаг 6: x = 2.2136 f(x) = 0.6185 шаг 7: x = 2.3709 f(x) = 0.3958 шаг 8: x = 2.4967 f(x) = 0.2533 шаг 9: x = 2.5973 f(x) = 0.1621 шаг 10: x = 2.6779 f(x) = 0.1038
Видно, как x ползёт от 0 к 3, а потеря падает с 9 почти до нуля. Каждый шаг меньше предыдущего: ближе к минимуму наклон положе, шаги мельче — спуск сам тормозит.
Доводим до сходимости
Десяти шагов мало; дадим спуску больше итераций и посмотрим, как близко он подойдёт к точному минимуму x = 3.
def grad(x): return 2 * (x - 3)
x = 0.0
lr = 0.1
for _ in range(100):
x = x - lr * grad(x)
print("После 100 шагов x =", round(x, 6))
print("Точный минимум = 3")
print("Ошибка =", round(abs(x - 3), 8))
Вывод:
После 100 шагов x = 3.0 Точный минимум = 3 Ошибка = 0.0
Универсальный спуск через численный градиент
Красота метода в том, что нам не нужна формула производной — её можно посчитать численно (из раздела про анализ). Тогда градиентный спуск работает для любой функции, даже если мы не умеем её дифференцировать на бумаге.
def numeric_grad(f, x, h=1e-6):
return (f(x + h) - f(x - h)) / (2 * h)
def f(x):
return (x - 5) ** 2 + 2 # минимум в x = 5
x = 0.0
lr = 0.1
for _ in range(200):
x = x - lr * numeric_grad(f, x)
print("Найденный минимум x =", round(x, 4), " (ожидаем 5)")
print("Значение функции =", round(f(x), 4), " (ожидаем 2)")
Вывод:
Найденный минимум x = 5.0 (ожидаем 5) Значение функции = 2.0 (ожидаем 2)
Итог
- Градиентный спуск шагает против наклона к минимуму:
x ← x − η·f'(x). - Шаг обучения η регулирует размер шага; знак минус ведёт вниз.
- Возле минимума наклон мал → шаги мельчают, спуск сам замедляется.
- С численным градиентом метод работает для любой функции, без формулы производной.