Перейти к содержимому
Ступень 11.2 Производные и градиенты

Частные производные и градиент

Производные функций многих переменных, градиент как направление наискорейшего подъёма, якобиан на пальцах и главная формула обучения нейросетей — градиент матричного умножения; в конце — как проверить любой градиент численно.

УРОКstage-1/1-2-calculus/03-gradients.mdx1 ч 50 минУрок 3 из 3
После урока вы сможете
  • Находить частные производные и градиент функции многих переменных и объяснить, почему градиент указывает направление наискорейшего роста
  • Объяснить, что такое якобиан, и почему обратный проход никогда не строит его целиком
  • Вывести и применять формулы ∂L∂W=X⊤G\frac{\partial L}{\partial W} = X^\top G, ∂L∂X=GW⊤\frac{\partial L}{\partial X} = G W^\top, ∂L∂b=∑G\frac{\partial L}{\partial b} = \sum G для линейного слоя
  • Проверять аналитический градиент численным (gradient check) и читать относительную ошибку

Loss языковой модели зависит не от одного числа, а от миллиардов. Значит, нужна производная по каждому из них. Все эти производные вместе — градиентградиентEN gradientВектор всех частных производных ∇f. Указывает направление наискорейшего роста функции; его длина — крутизна склона.. Это вектор, а для матрицы весов — матрица той же формы, что и сами веса. Каждый шаг оптимизатора — от простого градиентного спуска до AdamW и Muon — начинается с градиента.

В прошлом уроке мы прогнали обратный проход для скаляров. Теперь поднимемся на уровень векторов и матриц — именно в таком виде backprop работает в настоящих сетях. Главный результат урока — формула градиента линейного слоя, через который проходит почти вся «тяжёлая» работа модели. А в конце научимся проверять любой градиент численно: это главный инструмент отладки всего, что вы будете писать руками на ступенях 3–5.

Функция f(x,y)=x2+3y2f(x, y) = x^2 + 3y^2 принимает две переменные и возвращает одно число. Её удобно представлять как рельеф: точка (x,y)(x, y) — место на карте, f(x,y)f(x, y) — высота. Как на топографической карте, рельеф рисуют линиями уровня — кривыми, вдоль которых высота одинакова. У нашей функции это вытянутые эллипсы вокруг минимума в точке (0,0)(0, 0).

Loss нейросети — такой же рельеф, только «карта» имеет не два измерения, а миллиарды. Обучение — это спуск в низину по этому рельефу. Нарисовать его нельзя, но вся математика работает так же, как на плоскости.

Как изменится высота, если шагнуть только вдоль оси xx, не трогая yy? Это частная производнаячастная производнаяEN partial derivative∂f/∂x — производная функции многих переменных по одной из них, когда остальные заморожены. по xx: все остальные переменные замораживаем и дифференцируем как обычно.

∂f∂x=lim⁡h→0f(x+h,y)−f(x,y)h\frac{\partial f}{\partial x} = \lim_{h \to 0} \frac{f(x + h, y) - f(x, y)}{h}

Значок ∂\partial («дэ круглое») напоминает, что переменных несколько. Пример: f(x,y)=x2+3xyf(x, y) = x^2 + 3xy в точке (1,2)(1, 2).

  • По xx (считаем yy числом): ∂f∂x=2x+3y=2+6=8\frac{\partial f}{\partial x} = 2x + 3y = 2 + 6 = 8. Почему 3y3y: при замороженном y=2y = 2 слагаемое 3xy3xy — это просто 6x6x, прямая с наклоном 6, то есть 3y3y.
  • По yy (считаем xx числом): ∂f∂y=3x=3\frac{\partial f}{\partial y} = 3x = 3. Слагаемое x2x^2 от yy не зависит — для yy это константа, её производная 0 (урок 1.2.1).

Численная проверка — та же центральная разность, только шевелим одну переменную:

f = lambda x, y: x ** 2 + 3 * x * y
h = 1e-5
print((f(1 + h, 2) - f(1 - h, 2)) / (2 * h)) # ≈ 8.000000000008 — двигаем только x
print((f(1, 2 + h) - f(1, 2 - h)) / (2 * h)) # ≈ 3.000000000064 — двигаем только y

Соберём все частные производные в вектор — это и есть градиент:

∇f=(∂f∂x1,  ∂f∂x2,  …,  ∂f∂xn)\nabla f = \left( \frac{\partial f}{\partial x_1}, \; \frac{\partial f}{\partial x_2}, \; \dots, \; \frac{\partial f}{\partial x_n} \right)

Значок ∇\nabla называют «набла». Для f(x,y)=x2+3y2f(x, y) = x^2 + 3y^2 градиент ∇f=(2x,6y)\nabla f = (2x, 6y), в точке (1,1)(1, 1) это (2,6)(2, 6). У градиента та же форма, что у входа: если вход — матрица 4096×40964096 \times 4096, градиент — тоже матрица 4096×40964096 \times 4096. В PyTorch w.grad всегда имеет форму w.

Шагнём из точки на маленькое расстояние ε\varepsilon в направлении единичного вектора u=(u1,u2)\mathbf{u} = (u_1, u_2). Такой шаг — это сдвиг на εu1\varepsilon u_1 по оси xx и на εu2\varepsilon u_2 по оси yy. Сдвиг по xx меняет высоту примерно на ∂f∂x⋅εu1\frac{\partial f}{\partial x} \cdot \varepsilon u_1 (производная — «курс обмена» из урока 1.2.1), сдвиг по yy — на ∂f∂y⋅εu2\frac{\partial f}{\partial y} \cdot \varepsilon u_2, а при маленьком шаге эти вклады просто складываются. Итого высота изменится примерно на ε⋅(∇f⋅u)\varepsilon \cdot (\nabla f \cdot \mathbf{u}) — скалярное произведение градиента на направление. Величину ∇f⋅u\nabla f \cdot \mathbf{u} называют производной по направлениюпроизводная по направлениюEN directional derivativeСкорость изменения функции при движении в направлении единичного вектора u — равна скалярному произведению ∇f · u.. В точке (1,1)(1, 1) с ∇f=(2,6)\nabla f = (2, 6) сравним несколько направлений. Единичный вектор вдоль градиента получаем нормировкой из урока 1.1.1: длина (2,6)(2, 6) равна 40≈6.32\sqrt{40} \approx 6.32, и (2,6)/6.32≈(0.316,0.949)(2, 6) / 6.32 \approx (0.316, 0.949).

Направление u\mathbf{u}∇f⋅u\nabla f \cdot \mathbf{u}Что происходит
вдоль оси xx: (1,0)(1, 0)2подъём, но пологий
вдоль градиента: (0.316,0.949)(0.316, 0.949)6.32самый крутой подъём
перпендикулярно: (−0.949,0.316)(-0.949, 0.316)0идём по линии уровня — высота не меняется
против градиента: (−0.316,−0.949)(-0.316, -0.949)−6.32самый крутой спуск

Объяснение — из урока 1.1.1: ∇f⋅u=∥∇f∥cos⁡θ\nabla f \cdot \mathbf{u} = \lVert \nabla f \rVert \cos\theta. Косинус максимален, когда угол θ=0\theta = 0, то есть когда мы идём вдоль градиента. Отсюда три свойства:

  1. Градиент указывает направление наискорейшего роста функции.
  2. Его длина ∥∇f∥\lVert \nabla f \rVert — крутизна склона в этом направлении (здесь 4+36≈6.32\sqrt{4 + 36} \approx 6.32).
  3. Градиент перпендикулярен линиям уровня: вдоль линии уровня высота не меняется, производная по направлению равна 0.
0.524.58старт (2.6, 0.9)минимумжёлтые стрелки — ∇f, перпендикулярны линиям уровнякрасный путь — градиентный спуск, η = 0.1
Карта высот f(x, y) = x² + 3y²: эллипсы — линии уровня (подписаны значения f). Градиент указывает «в гору» поперёк линий уровня; спуск идёт против него и быстрее всего гасит крутую координату y.

Чтобы спуститься, шагаем против градиента: θ←θ−η∇L(θ)\boldsymbol{\theta} \leftarrow \boldsymbol{\theta} - \eta \nabla L(\boldsymbol{\theta}). Для нашей функции с η=0.1\eta = 0.1 шаг даёт x←x−0.1⋅2x=0.8xx \leftarrow x - 0.1 \cdot 2x = 0.8x и y←y−0.1⋅6y=0.4yy \leftarrow y - 0.1 \cdot 6y = 0.4y. Крутая координата yy за шаг сжимается в 2.5 раза, а пологая xx — всего в 1.25 раза, и путь на рисунке сначала «падает» на дно оврага, а потом медленно ползёт вдоль него. На вытянутых «оврагах» простой спуск неэффективен — это мотивация для Momentum и Adam (урок 1.4.2).

До сих пор функция выдавала одно число. А слой нейросети превращает вектор в вектор: y=F(x)\mathbf{y} = F(\mathbf{x}), x∈Rn\mathbf{x} \in \mathbb{R}^n, y∈Rm\mathbf{y} \in \mathbb{R}^m. Теперь у каждого выхода есть производная по каждому входу — получается таблица m×nm \times n. Это матрица Якобиматрица Якоби (якобиан)EN Jacobian matrixТаблица всех частных производных векторной функции — ∂yᵢ/∂xⱼ. Для y = Wx якобиан равен самой матрице W., или якобиан:

Jij=∂yi∂xjJ_{ij} = \frac{\partial y_i}{\partial x_j}

Пример с числами: F(x1,x2)=(x1x2,  x1+3x2)F(x_1, x_2) = (x_1 x_2, \; x_1 + 3x_2) в точке (2,1)(2, 1). Первая строка якобиана — частные производные первого выхода: ∂(x1x2)∂x1=x2=1\frac{\partial (x_1 x_2)}{\partial x_1} = x_2 = 1 и ∂(x1x2)∂x2=x1=2\frac{\partial (x_1 x_2)}{\partial x_2} = x_1 = 2. Вторая строка — производные второго выхода: 1 и 3. Итого J=(1213)J = \begin{pmatrix} 1 & 2 \\ 1 & 3 \end{pmatrix}: строка — выход, столбец — вход.

Два важных примера:

  • Линейный слой y=Wx\mathbf{y} = W\mathbf{x}. Поскольку yi=∑jWijxjy_i = \sum_j W_{ij} x_j, производная ∂yi∂xj=Wij\frac{\partial y_i}{\partial x_j} = W_{ij}. Якобиан линейного преобразования — сама матрица WW. Логично: линейная функция везде имеет один и тот же «наклон».
  • Поэлементная функция, например ReLU. Выход yiy_i зависит только от xix_i, поэтому якобиан диагональный: на диагонали 11 там, где xi>0x_i > 0, и 00 в остальных местах.

Цепное правило для векторов звучит знакомо: якобиан композиции — произведение якобианов. Композиция функций ↔ умножение матриц — та же связь, что в уроке 1.1.2.

Возьмём батч из 1000 токенов с векторами размерности 4096 — всего 4.1 млн чисел на входе и столько же на выходе слоя. Якобиан такого слоя — таблица 4.1 млн×4.1 млн≈1.7⋅10134.1\,\text{млн} \times 4.1\,\text{млн} \approx 1.7 \cdot 10^{13} чисел. Это десятки терабайт на один слой.

Спасает то, что loss — одно число. На обратном проходе к слою приходит не якобиан, а вектор g=∂L∂y\mathbf{g} = \frac{\partial L}{\partial \mathbf{y}} той же формы, что y\mathbf{y}. Слою нужно лишь вычислить g⊤J\mathbf{g}^\top J — произведение вектора на якобиан. Для ReLU это поэлементное умножение на маску из нулей и единиц, для линейного слоя — умножение на W⊤W^\top. Ни одна огромная матрица не появляется. Именно эту операцию — «вектор на якобиан» — реализует каждый узел autograd.

Теперь главная формула урока. Линейный слой на батче (урок 1.1.2):

Y=XW+b,X∈RB×n,  W∈Rn×m,  Y∈RB×mY = XW + \mathbf{b}, \qquad X \in \mathbb{R}^{B \times n}, \; W \in \mathbb{R}^{n \times m}, \; Y \in \mathbb{R}^{B \times m}

Читаем: батч XX — BB примеров по nn признаков, матрица весов WW превращает nn входов в mm выходов, получается YY — BB строк по mm чисел. Обратите внимание: здесь для простоты веса хранятся как (n×m)(n \times m), то есть (in, out), и транспонирования в формуле нет. В nn.Linear из урока 1.1.2 веса лежат наоборот, (out, in), — как это меняет формулы, покажем в конце раздела.

Сверху, из следующих слоёв, пришёл градиент G=∂L∂YG = \frac{\partial L}{\partial Y} — матрица формы B×mB \times m. Нужно найти градиенты по весам, по входу (чтобы передать дальше вниз) и по сдвигу:

  ∂L∂W=X⊤G,∂L∂X=G W⊤,∂L∂b=∑строкиG  \boxed{\;\frac{\partial L}{\partial W} = X^\top G, \qquad \frac{\partial L}{\partial X} = G\,W^\top, \qquad \frac{\partial L}{\partial \mathbf{b}} = \sum_{\text{строки}} G\;}
ЧтоФормаГрадиентКак считаетсяФорма градиента
вход XXB×nB \times n∂L/∂X\partial L / \partial XGW⊤G W^\top: (B×m)(m×n)(B \times m)(m \times n)B×nB \times n — уходит в предыдущий слой
веса WWn×mn \times m∂L/∂W\partial L / \partial WX⊤GX^\top G: (n×B)(B×m)(n \times B)(B \times m)n×mn \times m — идёт в оптимизатор
сдвиг b\mathbf{b}mm∂L/∂b\partial L / \partial \mathbf{b}сумма строк GGmm — идёт в оптимизатор

Обратите внимание на последний столбец: форма каждого градиента совпадает с формой того, по чему его берут.

Здесь много индексов. Если при первом чтении они пугают, перейдите к «Мнемонике» и «Примеру с числами» ниже, а сюда вернитесь потом: вывод нужен, чтобы понять, откуда формула, а пользоваться ей можно и без него.

Запишем один элемент выхода: Ybj=∑iXbiWij+bjY_{bj} = \sum_i X_{bi} W_{ij} + b_j. Индексы: bb — номер примера в батче, ii — номер входа, jj — номер выхода. (Буква bb здесь работает дважды: в индексе это номер примера, а bjb_j — сдвиг jj-го выхода. Так пишут во многих статьях — смысл подсказывает место буквы.) Вес WijW_{ij} участвует в элементах YbjY_{bj} всех примеров батча bb (с тем же jj), и каждый раз с множителем XbiX_{bi}. Это ветвление из прошлого урока: вклады всех путей складываются:

∂L∂Wij=∑b∂L∂Ybj⋅∂Ybj∂Wij=∑bGbjXbi=(X⊤G)ij\frac{\partial L}{\partial W_{ij}} = \sum_{b} \frac{\partial L}{\partial Y_{bj}} \cdot \frac{\partial Y_{bj}}{\partial W_{ij}} = \sum_b G_{bj} X_{bi} = (X^\top G)_{ij}

Так же для входа: XbiX_{bi} участвует во всех YbjY_{bj} своей строки с множителями WijW_{ij}, поэтому ∂L∂Xbi=∑jGbjWij=(GW⊤)bi\frac{\partial L}{\partial X_{bi}} = \sum_j G_{bj} W_{ij} = (G W^\top)_{bi}. А сдвиг bjb_j прибавляется к каждой строке — его градиент равен сумме столбца jj матрицы GG по всему батчу.

Формулу легко восстановить, даже если вы её забыли. Градиент по WW обязан иметь форму WW, то есть n×mn \times m. Под рукой есть XX (B×nB \times n) и GG (B×mB \times m). Перемножить их так, чтобы получилось n×mn \times m, можно единственным способом: X⊤GX^\top G. Для ∂L∂X\frac{\partial L}{\partial X} (B×nB \times n) из GG (B×mB \times m) и WW (n×mn \times m) получается только GW⊤G W^\top.

Пусть X=(1234)X = \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} (2 примера по 2 признака), W=(10−1210)W = \begin{pmatrix} 1 & 0 & -1 \\ 2 & 1 & 0 \end{pmatrix}, а LL — просто сумма всех элементов YY. Тогда каждый элемент YY входит в LL с коэффициентом 1, и GG — матрица из единиц формы 2×32 \times 3:

∂L∂W=X⊤G=(444666),∂L∂X=GW⊤=(0303),∂L∂b=(2,2,2)\frac{\partial L}{\partial W} = X^\top G = \begin{pmatrix} 4 & 4 & 4 \\ 6 & 6 & 6 \end{pmatrix}, \quad \frac{\partial L}{\partial X} = G W^\top = \begin{pmatrix} 0 & 3 \\ 0 & 3 \end{pmatrix}, \quad \frac{\partial L}{\partial \mathbf{b}} = (2, 2, 2)

Смысл каждого числа можно проверить «на пальцах». Вес W1jW_{1j} умножается на первый признак обоих примеров, 1+3=41 + 3 = 4. Признак Xb1X_{b1} умножается на первую строку WW, сумма которой 1+0−1=01 + 0 - 1 = 0. Сдвиг прибавлен к двум строкам — отсюда 2.

Градиент одного примера — матрица ранга 1

Заголовок раздела «Градиент одного примера — матрица ранга 1»

Если в батче один пример (B=1B = 1), то XX — это одна строка x\mathbf{x} (1×n1 \times n), а GG — строка g\mathbf{g} (1×m1 \times m). Тогда X⊤G=x⊤gX^\top G = \mathbf{x}^\top \mathbf{g} — столбец на строку, внешнее произведение. По уроку 1.1.3 это матрица ранга 1. Градиент по батчу из BB примеров — сумма BB таких слоёв, его ранг не больше BB.

В настоящем обучении в батче миллионы токенов, и граница «ранг не больше BB» уже ничего не ограничивает: формально градиент может иметь полный ранг. Но на практике в градиенте матрицы весов обычно доминируют немногие направления: несколько первых сингулярных чисел (урок 1.1.3) намного больше остальных. Поэтому к градиенту полезно относиться именно как к матрице — так делает, например, оптимизатор Muon (о нём — в «Китайском угле» ниже).

nn.Linear хранит веса в форме (out, in) и считает Y=XW⊤+bY = XW^\top + \mathbf{b} (урок 1.1.2). Тогда та же логика даёт weight.grad =G⊤X= G^\top X формы (out, in). Проверим:

import torch
torch.manual_seed(0)
layer = torch.nn.Linear(3, 2) # веса (out, in) = (2, 3)
X = torch.randn(4, 3) # батч из 4 примеров
Y = layer(X) # Y = X @ W.T + b, форма (4, 2)
G = torch.randn(4, 2) # пусть dL/dY = G (как будто пришло сверху)
L = (Y * G).sum() # такая L даёт ровно dL/dY = G
L.backward()
print(torch.allclose(layer.weight.grad, G.T @ X)) # True: для весов (out, in) это Gᵀ X
print(torch.allclose(layer.bias.grad, G.sum(0))) # True: сумма по батчу

Трюк L = (Y * G).sum() полезно запомнить: он позволяет «подать сверху» любой градиент GG и проверить обратный проход одного слоя отдельно от всей сети.

Ручной backward ошибается тихо: код работает, loss даже немного падает, а градиент неверен. Самый надёжный способ поймать такую ошибку — проверка градиентапроверка градиентаEN gradient checkСравнение аналитического градиента с численным (центральные разности) по относительной ошибке. Главный способ найти баг в backward. (gradient check): посчитать градиент численно и сравнить с аналитическим.

  1. Для каждого элемента параметра сдвиньте его на +h+h и на −h-h, посчитайте loss и возьмите центральную разность (урок 1.2.1). Верните элемент на место.
  2. Сравните с аналитическим градиентом по относительной ошибке (формула ниже) — абсолютная ничего не скажет, если градиенты порядка 10−610^{-6} или 10310^{3}.
  3. Прочитайте результат по таблице.

Относительная ошибка для аналитического градиента aa и численного nn считается по каждому элементу, и берётся наибольшая:

rel=max⁡∣a−n∣max⁡(10−8,∣a∣+∣n∣)\text{rel} = \max \frac{\lvert a - n \rvert}{\max(10^{-8}, \lvert a \rvert + \lvert n \rvert)}

Числитель — обычная разница, знаменатель приводит её к масштабу самих чисел, а 10−810^{-8} спасает от деления на ноль, когда оба градиента нулевые. Например, a=1a = 1 и n=1.01n = 1.01 дают 0.01/2.01≈0.0050.01 / 2.01 \approx 0.005.

Относительная ошибка (float64)Вердикт
меньше 10−710^{-7}отлично, градиент верен
от 10−710^{-7} до 10−410^{-4}подозрительно; допустимо, если в функции есть изломы (ReLU, max)
от 10−410^{-4} до 10−210^{-2}скорее всего, ошибка
больше 10−210^{-2}ошибка почти наверняка

Проверим на L=∑tanh⁡(XW)L = \sum \tanh(XW). Аналитически ∂L∂Y=1−tanh⁡2(Y)\frac{\partial L}{\partial Y} = 1 - \tanh^2(Y), и по формуле урока ∂L∂W=X⊤(1−tanh⁡2(Y))\frac{\partial L}{\partial W} = X^\top (1 - \tanh^2(Y)). Добавим и вариант с типичным багом — «забыли» производную tanh:

import numpy as np
rng = np.random.default_rng(0)
X = rng.standard_normal((4, 3))
W = rng.standard_normal((3, 2))
def loss(W):
return np.tanh(X @ W).sum() # L = сумма tanh(XW)
# аналитический градиент: dL/dY = 1 − tanh²(Y), затем dL/dW = Xᵀ · dL/dY
Y = X @ W
dW = X.T @ (1 - np.tanh(Y) ** 2)
dW_bug = X.T @ np.ones_like(Y) # «забыли» производную tanh
# численный градиент: шевелим каждый элемент W по очереди
num = np.zeros_like(W) # массив нулей той же формы, что W
h = 1e-5
for idx in np.ndindex(W.shape): # перебирает все индексы: (0, 0), (0, 1), (1, 0), …
old = W[idx]
W[idx] = old + h # сдвинули элемент вверх
up = loss(W)
W[idx] = old - h # и вниз
down = loss(W)
W[idx] = old # обязательно вернуть на место!
num[idx] = (up - down) / (2 * h) # центральная разность из урока 1.2.1
def rel_error(a, b):
return np.max(np.abs(a - b) / np.maximum(1e-8, np.abs(a) + np.abs(b)))
print(f"верный градиент: {rel_error(dW, num):.1e}")
print(f"градиент с багом: {rel_error(dW_bug, num):.1e}")
верный градиент: 1.0e-11
градиент с багом: 4.3e-01

Практические правила.

  • Проверяйте во float64: во float32 численная производная сама ошибается на 10−310^{-3} и маскирует баги.
  • Численный градиент стоит два вычисления loss на каждый элемент. Проверяйте на крошечных размерах (батч 3–5, слой 4×3) или на нескольких случайных элементах большого тензора.
  • В точке излома (ReLU ровно в нуле) численная и аналитическая производные законно расходятся — не пугайтесь одиночных выбросов, если вход оказался рядом с нулём.

В PyTorch для этого есть готовая функция torch.autograd.gradcheck — она делает то же самое и требует float64:

import torch
from torch.autograd import gradcheck
def f(X, W):
return torch.tanh(X @ W).sum()
X = torch.randn(4, 3, dtype=torch.float64, requires_grad=True) # gradcheck требует float64
W = torch.randn(3, 2, dtype=torch.float64, requires_grad=True)
print(gradcheck(f, (X, W), eps=1e-6, atol=1e-4)) # True

Реализуйте численный градиент для массива любой формы, обратный проход линейного слоя и относительную ошибку. Последний тест специально использует квадратные матрицы — там, где мнемоника «по формам» бессильна.

Gradient check для линейного слояnumpy · нужен интернет6 тестов
import numpy as np


def numerical_gradient(f, X, h=1e-5):
    """Численный градиент скалярной функции f по массиву X (любой формы).

    Для каждого элемента X[idx]: сдвиньте его на +h и на −h, посчитайте f,
    возьмите центральную разность и ОБЯЗАТЕЛЬНО верните элемент на место.
    Перебрать все индексы удобно так: for idx in np.ndindex(X.shape).
    """
    # TODO: ваш код
    ...


def linear_backward(X, W, G):
    """Обратный проход линейного слоя Y = X @ W + b.

    X — (B, n), W — (n, m), G = dL/dY — (B, m).
    Верните кортеж (dX, dW, db) формы (B, n), (n, m), (m,).
    """
    # TODO: ваш код
    ...


def rel_error(a, b):
    """Относительная ошибка: max |a − b| / max(1e-8, |a| + |b|) по всем элементам."""
    # TODO: ваш код
    ...


# Проверьте себя кнопкой «Запустить»:
rng = np.random.default_rng(0)
X = rng.standard_normal((4, 3))
W = rng.standard_normal((3, 5))
b = rng.standard_normal(5)
R = rng.standard_normal((4, 5))            # L = sum(Y * R), поэтому dL/dY = R


def loss_of_W(W_):
    return np.sum((X @ W_ + b) * R)


grads = linear_backward(X, W, R)
if grads is not None:                      # пока функция не написана, она возвращает None
    dX, dW, db = grads
    print(rel_error(dW, numerical_gradient(loss_of_W, W)))   # меньше 1e-7 — градиент верен

Задание для Jupyter (по желанию). Возьмите двухслойную сеть L=∑(ReLU(XW1)W2−Y)2L = \sum (\mathrm{ReLU}(XW_1) W_2 - Y)^2 на батче 5×4 со скрытым слоем 6 и выходом 3. Напишите обратный проход руками по формулам урока (не забудьте маску ReLU) и проверьте все градиенты через numerical_gradient. Это ровно та задача, которую вы будете решать в уроке 3.1.2.

Проверь себя6 вопросов
1 Найдите ∂f∂x\frac{\partial f}{\partial x} для f(x,y)=x2y+yf(x, y) = x^2 y + y в точке (2,3)(2, 3).
2 Чему равна длина градиента f(x,y)=x2+3y2f(x, y) = x^2 + 3y^2 в точке (1,1)(1, 1)? Округлите до двух знаков.
3 Куда указывает градиент функции в точке?
4 Y=XWY = XW, где XX — (32×768)(32 \times 768), WW — (768×3072)(768 \times 3072), G=∂L/∂YG = \partial L / \partial Y. Как вычислить ∂L/∂W\partial L / \partial W?
5 Какие утверждения верны?Можно выбрать несколько ответов
6 Y=XWY = XW, LL — сумма всех элементов YY, X=(1234)X = \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix}. Чему равен ∂L/∂W11\partial L / \partial W_{11}?
  • Частная производная — производная по одной переменной при замороженных остальных. Градиент ∇f\nabla f — вектор всех частных производных той же формы, что и вход.
  • Градиент указывает направление наискорейшего роста, его длина — крутизна, он перпендикулярен линиям уровня. Спуск: θ←θ−η∇L\theta \leftarrow \theta - \eta \nabla L.
  • Якобиан — таблица производных вектор-функции; для WxW\mathbf{x} это WW. Backprop не строит якобианы, а умножает на них вектор градиента.
  • Линейный слой: ∂L∂W=X⊤G\frac{\partial L}{\partial W} = X^\top G, ∂L∂X=GW⊤\frac{\partial L}{\partial X} = G W^\top, ∂L∂b=∑G\frac{\partial L}{\partial \mathbf{b}} = \sum G. Градиент одного примера — ранг 1.
  • Любой ручной backward проверяйте численно во float64: относительная ошибка меньше 10−710^{-7} — отлично, больше 10−210^{-2} — баг.

Дальше: модуль 1.3 — вероятность и теория информации: случайные величины, распределения и то, как языковая модель выдаёт вероятности следующего токена.