Производная как скорость изменения
Что такое производная, как её посчитать численно и по правилам и чему равны производные экспоненты, логарифма, сигмоиды, tanh и SiLU — первый шаг к пониманию того, как обучается нейросеть.
Зачем это нужно
Заголовок раздела «Зачем это нужно»Обучение нейросети — это подкручивание миллиардов «ручек»-параметровпараметрEN parameter так, чтобы функция потерьфункция потерьEN loss function уменьшался. Для каждой ручки нужно знать ответ на один вопрос: если чуть-чуть повернуть её вправо, loss вырастет или упадёт — и насколько сильно? Ответ на этот вопрос и есть производная.
В уроке 1.0.1 вы находили наклон прямой: на сколько растёт , когда растёт на 1. Производная обобщает наклон на любые кривые: это наклон в одной точке. В этом уроке мы определим её через предел, научимся считать численно (и разберём ловушку, в которую попадают все), выучим правила и найдём производные функций активации, на которых работают нейросети. В конце сделаем первые шаги градиентного спуска — вручную.
Скорость изменения
Заголовок раздела «Скорость изменения»Камень падает с башни. За секунд он пролетает метров. Какова его скорость ровно в момент ?
Среднюю скорость на отрезке посчитать легко: пройденный путь делим на время. Будем сужать отрезок:
| Отрезок времени | Путь, м | Средняя скорость, м/с |
|---|---|---|
| от 2 до 3 с | 24.5 | |
| от 2 до 2.1 с | 20.09 | |
| от 2 до 2.01 с | 19.649 | |
| «от 2 до 2 + почти 0» | — | 19.6 |
Средние скорости приближаются к 19.6 м/с — это мгновенная скорость, то, что показывает спидометр. ПроизводнаяпроизводнаяEN derivativeМгновенная скорость изменения функции — f′(x) = lim (f(x+h) − f(x))/h при h → 0. Геометрически — наклон касательной. — это и есть мгновенная скорость изменения функции.
Геометрически средняя скорость — наклон секущей, прямой через две точки графика. Когда вторая точка приближается к первой, секущая поворачивается и в пределе становится касательной — прямой, которая в этой точке «прижимается» к графику. Производная — наклон касательной.
Определение через предел
Заголовок раздела «Определение через предел»Запишем то же самое формулой. Сдвинемся от точки на маленький шаг и посмотрим, как изменилась функция:
Значок читается «пределпределEN limitЗначение, к которому сколь угодно близко подходит выражение, когда аргумент стремится к заданному числу. при , стремящемся к нулю». Подставить нельзя — получится . Но можно смотреть, к какому числу приближается дробь, когда становится всё меньше. Для в точке :
| 1 | 0.1 | 0.01 | 0.001 | |
|---|---|---|---|---|
| 7 | 6.1 | 6.01 | 6.001 |
Дробь стремится к 6. Алгебра подтверждает: , и при остаётся 6. Если повторить выкладку для произвольного , получится .
Обозначения. Пишут («эф штрих») или (запись Лейбница: «маленькое изменение на маленькое изменение »). В ML вы постоянно будете видеть — производную loss по весу.
Как читать производную.
- Знак: — функция в этой точке растёт, — убывает, — график горизонтален. В минимуме и максимуме гладкой функции производная равна нулю.
- Величина — чувствительность. означает: если увеличить на 0.001, loss вырастет примерно на 0.005. Производная — это «курс обмена» маленьких изменений входа на изменения выхода.
Где производной нет. В уроке 1.0.1 мы заметили «угол» у графика в нуле. Слева наклон −1, справа +1, одного наклона нет — производная в этой точке не существует. У ReLU в нуле то же самое: слева наклон 0, справа 1. На практике это не мешает: PyTorch просто считает производную ReLU в нуле равной 0, а ровно в 0 вход попадает редко.
Численная производная
Заголовок раздела «Численная производная»Определение сразу даёт способ посчитать производную на компьютере: взять маленькое и не переходить к пределу.
- Прямая разность: .
- Центральная разность: — секущая через две точки по обе стороны от .
Центральная разностьчисленная производная (конечные разности)EN numerical derivative (finite differences)Приближение производной по значениям функции. Центральная разность (f(x+h) − f(x−h))/2h точнее прямой (f(x+h) − f(x))/h. намного точнее. У функции, которая изгибается вверх (как или ), секущая «справа» завышает наклон, а «слева» — занижает, и в симметричной формуле эти главные ошибки взаимно уничтожаются. Ошибка прямой разности пропорциональна , центральной — . Проверим на в точке , где точный ответ :
| Ошибка прямой разности | Ошибка центральной разности | |
|---|---|---|
| 0.1 | 0.14 | 0.0045 |
| 0.01 | 0.014 | 0.000045 |
| 0.001 | 0.0014 | 0.00000045 |
Уменьшили в 10 раз — ошибка прямой разности упала в 10 раз, центральной — в 100.
Ловушка: слишком маленький шаг
Заголовок раздела «Ловушка: слишком маленький шаг»Кажется, что чем меньше , тем лучше. Проверим на тех же и :
import math
for k in range(1, 13): h = 10 ** -k central = (math.exp(1 + h) - math.exp(1 - h)) / (2 * h) print(f"h = 1e-{k:<2d} ошибка = {abs(central - math.e):.1e}")h = 1e-1 ошибка = 4.5e-03h = 1e-2 ошибка = 4.5e-05h = 1e-3 ошибка = 4.5e-07h = 1e-4 ошибка = 4.5e-09h = 1e-5 ошибка = 5.9e-11h = 1e-6 ошибка = 1.6e-10h = 1e-7 ошибка = 5.9e-11h = 1e-8 ошибка = 6.6e-09h = 1e-9 ошибка = 6.6e-09h = 1e-10 ошибка = 6.7e-07h = 1e-11 ошибка = 1.0e-05h = 1e-12 ошибка = 2.1e-04Сначала ошибка падает, около достигает дна и дальше уже не уменьшается, а начиная с быстро растёт. Причина в числах с плавающей точкой (урок 0.0.2): float64 хранит около 16 значащих цифр. Когда крошечное, и совпадают почти во всех цифрах, и при вычитании остаются только последние, «шумные». Деление на крошечное этот шум раздувает.
Почему нейросети не учат численными производными? Для каждого параметра нужно два вычисления функции. У модели на 7 млрд параметров это 14 млрд прогонов модели на один шаг обучения. Обратное распространение ошибки (следующий урок) получает все производные сразу, примерно за время трёх прогонов: обратный проход стоит около двух прямых. Численная производная остаётся незаменимой для проверки: если ваш код backward ошибается, сравнение с численной производной это покажет (урок 1.2.3).
Правила дифференцирования
Заголовок раздела «Правила дифференцирования»Считать пределы каждый раз не нужно — есть таблица и правила. Их выводят из определения один раз.
| Функция | Производная | Пример |
|---|---|---|
| константа | ||
| , в точке 2 это 12 | ||
| производная равна самой функции | ||
| в точке 10 производная 0.1 | ||
| см. сигмоиду ниже |
Как читать таблицу: и — любые две функции, а и — их производные. Например, в строке произведения для берём (производная 1) и (производная ): . В точке это , и центральная разность даёт то же число.
Пример: . По правилам : (множитель и степень), (степень даёт ), а производная разности — разность производных. В точке это . Численно: . Совпало.
Экспонента — единственная (с точностью до множителя) функция, равная своей производной. Именно поэтому число из урока 1.0.2 так удобно: растёт со скоростью, равной своему значению.
Логарифм и cross-entropy. В уроке 1.3.2 loss языковой модели — это , где — вероятность правильного токена. Производная по равна :
| 0.9 | 0.5 | 0.1 | 0.01 | |
|---|---|---|---|---|
| −1.11 | −2 | −10 | −100 |
Чем увереннее модель ошибается (маленькое ), тем сильнее толчок к исправлению. Почти угаданный ответ двигается слабо.
Производные функций активации
Заголовок раздела «Производные функций активации»Сигмоида
Заголовок раздела «Сигмоида»сигмоидасигмоида (логистическая функция)EN sigmoid (logistic function)σ(z) = 1 / (1 + e^(−z)) — превращает любое число в вероятность от 0 до 1. удобно переписать как (умножили числитель и знаменатель на , как в уроке 1.0.1). Это дробь, поэтому берём правило частного из таблицы: числитель , его производная ; знаменатель , его производная (единица — константа, её производная 0). Подставляем в :
Производная выражается через само значение функции — при обучении её не нужно пересчитывать, достаточно запомнить после прямого прохода. Числа:
| 0 | 2 | 5 | 10 | |
|---|---|---|---|---|
| 0.25 | 0.105 | 0.0066 | 0.000045 |
Максимум — 0.25, а на «хвостах» производная почти исчезает. Сигмоида насыщается: если вход нейрона большой, сигнал об ошибке через неё почти не проходит. Это одна из причин, почему в глубоких сетях от сигмоиды отказались (урок 3.4.1).
Гиперболический тангенс
Заголовок раздела «Гиперболический тангенс»гиперболический тангенсгиперболический тангенсEN hyperbolic tangent (tanh)tanh x = (eˣ − e⁻ˣ)/(eˣ + e⁻ˣ) — «сигмоида» со значениями от −1 до 1. Производная 1 − tanh² x. — родственник сигмоиды со значениями от −1 до 1:
В нуле — вчетверо больше, чем у сигмоиды. Вывод формулы удобно сделать с помощью цепного правила — это задание в следующем уроке, а пока проверим численно: , и центральная разность даёт то же число. tanh используется в рекуррентных сетях (рекуррентная нейросетьрекуррентная нейросетьEN recurrent neural network (RNN) и LSTMLSTM (долгая краткосрочная память)EN long short-term memory (LSTM)RNN с ячейкой памяти c_t и тремя гейтами (Hochreiter, Schmidhuber, 1997). Ячейка обновляется сложением c_t = f·c_{t−1} + i·g — «остаточная связь во времени». — архитектуры, которые были до трансформеров, ступень 3) и внутри приближённой формулы GELU в GPT-2.
ReLU и SiLU
Заголовок раздела «ReLU и SiLU»У ReLU всё просто: производная равна 0 при и 1 при . Сигнал об ошибке либо проходит без изменений, либо полностью блокируется.
Современные LLM — Llama, Qwen, DeepSeek — используют в FFN-блоке (два линейных слоя с активацией между ними,
урок 1.1.2) активацию SiLU (её ещё называют Swish):
. В конфигах моделей это строчка "hidden_act": "silu". Производная — по правилу
произведения:
В точке 0 это , в точке 1 — . В отличие от ReLU, при отрицательных производная SiLU по модулю небольшая (около она даже слегка отрицательна, примерно ), но не нулевая: «выключенные» нейроны продолжают получать сигнал и могут «ожить».
| Активация | Производная | В нуле | Особенность |
|---|---|---|---|
| сигмоида | 0.25 | насыщается на хвостах | |
| tanh | 1 | тоже насыщается | |
| ReLU | 0 или 1 | 0 (по соглашению) | при градиент ровно 0 |
| SiLU | 0.5 | гладкая, используется в SwiGLU |
Первые шаги к обучению
Заголовок раздела «Первые шаги к обучению»Вернёмся к «чаше» ошибки из урока 1.0.1: , минимум в . Раскроем скобки: , и по правилам .
В точке производная равна : loss убывает, если увеличивать . Значит, шагнём против знака производной: , где — маленький множитель, скорость обученияскорость обученияEN learning rate. Возьмём :
| Шаг | Новый | ||
|---|---|---|---|
| 0 | 0 | −24 | 2.4 |
| 1 | 2.4 | −4.8 | 2.88 |
| 2 | 2.88 | −0.96 | 2.976 |
| 3 | 2.976 | −0.192 | 2.9952 |
За четыре шага мы почти на дне: расстояние до каждый раз сокращается в 5 раз. Это и есть градиентный спускградиентный спускEN gradient descent в миниатюре; во всех подробностях — в уроке 1.4.1.
PyTorch считает производные сам
Заголовок раздела «PyTorch считает производные сам»Вручную производные считают, чтобы понимать. На практике их считает autograd — часть PyTorch, которая автоматически находит производные (о PyTorch — заметка в уроке 1.1.2):
import torch
x = torch.tensor(2.0, requires_grad=True) # число 2.0 в виде тензора; «следи за производными по x»y = torch.sigmoid(x) # y = σ(x)y.backward() # посчитать dy/dx; ответ появится в x.gradprint(y.item(), x.grad.item()) # 0.8807970285415649 0.10499362647533417# .item() достаёт из тензора обычное число Pythons = torch.sigmoid(torch.tensor(2.0)).item()print(s * (1 - s)) # 0.10499362305391458 — наша формула σ(1 − σ)Числа совпадают до седьмого знака: тензоры по умолчанию хранят числа во float32, где около 7 значащих цифр, а Python — во float64, где около 16 (урок 0.0.2). Как autograd устроен внутри, вы поймёте в следующих двух уроках, а в модуле 3.2 напишете свой.
Практика
Заголовок раздела «Практика»Реализуйте прямую и центральную разности, аналитические производные сигмоиды, tanh и SiLU и функцию, которая сверяет аналитику с численным ответом. Устойчивая сигмоида из урока 1.0.1 уже есть в заготовке.
import math
def sigmoid(x):
"""Устойчивая сигмоида из урока 1.0.1 (уже готова)."""
if x >= 0:
return 1 / (1 + math.exp(-x))
e = math.exp(x)
return e / (1 + e)
def forward_diff(f, x, h=1e-5):
"""Прямая разность: (f(x + h) − f(x)) / h.
f — это функция, её можно вызывать: f(x + h).
Пример: для f(x) = x² в точке 3 при h = 0.1 получится (3.1² − 3²) / 0.1 = 6.1.
"""
# TODO: одна строка с return
...
def central_diff(f, x, h=1e-5):
"""Центральная разность: (f(x + h) − f(x − h)) / (2h). Она точнее прямой.
Пример: для f(x) = x² в точке 3 при h = 0.1 получится (3.1² − 2.9²) / 0.2 = 6.0.
Осторожно со скобками: «/ 2 * h» поделит на 2 и УМНОЖИТ на h — нужно «/ (2 * h)».
"""
# TODO: одна строка с return
...
def d_sigmoid(x):
"""Аналитическая производная сигмоиды: σ(x) · (1 − σ(x))."""
# TODO: ваш код
...
def d_tanh(x):
"""Аналитическая производная tanh: 1 − tanh²(x). Используйте math.tanh."""
# TODO: ваш код
...
def d_silu(x):
"""Производная SiLU(x) = x · σ(x) — активации из SwiGLU в Qwen и DeepSeek.
Правило произведения: (u·v)′ = u′·v + u·v′, где u = x, v = σ(x).
"""
# TODO: ваш код
...
def max_error(f, df, xs, h=1e-5):
"""Наибольшее расхождение |df(x) − central_diff(f, x, h)| по всем точкам xs.
df — аналитическая производная (например, d_sigmoid), f — сама функция (sigmoid).
Для каждой точки x из xs посчитайте |df(x) − central_diff(f, x, h)| и верните самое большое число.
"""
# TODO: ваш код
...
# Проверьте себя кнопкой «Запустить»:
print(forward_diff(lambda x: x ** 2, 3, h=0.1)) # ≈ 6.1
print(central_diff(lambda x: x ** 2, 3, h=0.1)) # ≈ 6.0
print(d_sigmoid(0), d_tanh(0), d_silu(0)) # 0.25 1.0 0.5
xs = [-3, -1, 0, 0.5, 2, 4]
print(max_error(sigmoid, d_sigmoid, xs)) # порядка 1e-11
Задание для Jupyter (по желанию). Постройте в matplotlib на одном графике ошибку прямой и центральной разности
для в точке 1 как функцию от до в логарифмическом масштабе по обеим осям (plt.loglog).
Найдите на графике «дно» для каждой формулы. Потом повторите во float32 (np.float32) и объясните, почему дно сдвинулось.
Проверь себя
Заголовок раздела «Проверь себя»- Производная — мгновенная скорость изменения и наклон касательной: . Знак говорит, растёт ли функция, величина — насколько она чувствительна.
- Численно считаем центральной разностью с (float64); слишком маленький шаг портит ответ из-за округления.
- Правила: , , , правила суммы, произведения и частного.
- , , ReLU’ — 0 или 1, SiLU’ .
- Шаг против знака производной уменьшает loss — это градиентный спуск.
Дальше: цепное правило и вычислительный граф — как найти производную функции, собранной из сотен звеньев, и посчитать её руками так же, как это делает loss.backward().