Перейти к содержанию
С нуля
Программа курса
EN Открыть

Программа курса

Неделя 2. Backpropagation

Фаза 1. Фундамент · неделя 2 из 24

Учиться в приложении: тьютор, задачи с кодом →

Ядро: вычислительный граф, градиент softmax+CE, свой autograd, activation checkpointing · Глубина: прямой режим автодифференцирования, непрерывные распределения (трек D) · ≈ 11 ч ядро / 21 ч всё

Backprop это единственный способ, которым учится всё в этом курсе, от MLP до RLHF. Его ошибки не падают с исключением: градиент, перезаписанный вместо сложенного, просто учит модель хуже. Здесь же появляются p − one_hot(t), который вернётся в policy gradient (неделя 16), и checkpointing, без которого активации больших моделей не помещаются в память (неделя 9).

Теория

  • Вычислительный граф (вычисление, записанное узлами-операциями, по рёбрам которых текут числа). Разбиение на «гейты» (узлы) это вопрос удобства: гейт ставят там, где локальный градиент прост
  • downstream = upstream × local. Здесь upstream: градиент лосса по выходу узла; local: производная выхода узла по его входу; downstream: градиент лосса по этому входу

На пальцах. Граф: q = x + y, затем f = q · z. Входы x = 1, y = 2, z = 4; forward даёт q = 3, f = 12. Backward идёт с конца, upstream на выходе равен 1. Узел ×: ∂f/∂z = q = 3, ∂f/∂q = z = 4: множители поменялись местами. Узел +: local по каждому входу равен 1, поэтому ∂f/∂x = ∂f/∂y = 4 · 1 = 4. Проверка: x = 1.01 даёт q = 3.01, f = 12.04. Прирост 0.04 равен 4 · 0.01.

На пальцах. Пусть f = a · a при a = 3, то есть f = 9. Узел × видит два входа, и оба равны a. По первому входу local равен второму множителю, 3; по второму равен первому, тоже 3. Правильный градиент это сумма: 3 + 3 = 6, как и (a²)' = 2a = 6. Если второй вклад перезаписал первый, выйдет 3. Ошибка вдвое, и никакого исключения.

  • Градиенты складываются на разветвлениях
  • Интуиция узлов: + раздаёт градиент, max маршрутизирует в один вход, × меняет коэффициенты местами
  • Обратный топологический порядок. Топологический порядок такой, где каждый узел стоит после всех своих входов; обратный идёт от лосса к входам, чтобы к узлу успели прийти все вклады
Backprop по графу: upstream × local = downstreamBackprop по графу: upstream × local = downstream
Схема 7. У каждого узла downstream = upstream × local. W входит в два узла (в matmul и в регуляризатор), поэтому его градиенты складываются; номера задают обратный топологический порядок, W обрабатывается последним.
  • Сложность forward и backward одного порядка
  • Автодифференцирование: каждый тип узла знает свой локальный градиент
  • Gradient checking (проверка градиента численно): f'(x) ≈ (f(x+h) − f(x−h)) / 2h

На пальцах. У сети миллион весов. Численный градиент сдвигает каждый вес по очереди в обе стороны: это 2·10⁶ прогонов forward на один шаг. Backward выдаёт все миллион производных за один проход, по цене примерно двух forward: каждый узел один раз умножает upstream на свой local. Поэтому численный градиент нужен только для проверки на крошечной сети, а учат backprop'ом.

  • Прямой и обратный режим автодифференцирования. Прямой (forward mode) несёт производные вместе с forward, от входа к выходу: один проход даёт производные всех выходов по одному входу (Якобиан на вектор, JVP). Обратный (reverse mode; именно его называют backprop) идёт от выхода к входам: один проход даёт производные одного выхода по всем входам (вектор на Якобиан, VJP). У обучения выход один (скалярный лосс), а входов миллиарды, поэтому выбран обратный режим. Его цена: активации forward надо хранить до backward

На пальцах. Сеть из 100 слоёв. Обычный backward держит в памяти все 100 активаций. Checkpointing сохраняет каждую десятую, всего 10 штук. В backward отрезок из 10 слоёв пересчитывается заново от ближайшей сохранённой точки, и в памяти временно ещё 10 его активаций. Пик 10 + 10 = 20 вместо 100, а цена примерно ещё один forward. При 5 точках было бы 5 + 20 = 25, при 20 точках 20 + 5 = 25: минимум ровно при √100 = 10.

  • Activation checkpointing (хранить часть активаций, остальные пересчитывать в backward): память O(N) → O(K + N/K), оптимум при K=√N → память O(√N), вычисления в backward около O(2N)
  • Почему .backward() требует скаляр: ∂L/∂θ это одно число на параметр
  • Градиент от mean-loss = mean градиентов (линейность производной)

На пальцах. Модель предсказала ŷ = 2, правда y = 3. Допустим, правда равна прогнозу плюс гауссов шум (случайная добавка с колоколообразным распределением) с дисперсией 1. Плотность такого исхода e^{−(y−ŷ)²/2} / √(2π), её минус логарифм (y − ŷ)²/2 + ½·ln 2π ≈ 0.5 + 0.919. Второе слагаемое от модели не зависит, поэтому минимизировать отрицательное лог-правдоподобие значит минимизировать квадрат ошибки. Производная по ŷ равна ŷ − y = −1: прогноз минус правда, ровно как p − one_hot(t) ниже.

  • Функция потерь как NLL (negative log-likelihood, отрицательное лог-правдоподобие: минус логарифм вероятности, которую модель дала правильному ответу). Регрессия с гауссовым шумом даёт MSE, категориальный выход даёт кросс-энтропию, Бернулли даёт бинарную кросс-энтропию (Модуль 0, F6). Предположение о шуме выбирает лосс: шум Лапласа (с тяжёлыми хвостами) дал бы |y − ŷ|, то есть MAE, устойчивую к выбросам
  • Почему не учить прямо по accuracy (доле верных ответов): она ступенчатая. Малый сдвиг весов её не меняет, и градиент почти везде равен нулю. Лосс берут гладким и дифференцируемым, метрику считают отдельно

На пальцах. Три класса, логиты z = (0.69, 0, 0). Так как e^0.69 ≈ 2, softmax даёт p = (2, 1, 1) / 4 = (0.5, 0.25, 0.25). Правильный класс второй, лосс −ln 0.25 ≈ 1.386. Формула ниже обещает градиент p − one_hot(t) = (0.5, −0.75, 0.25). Проверим второй логит: поднимем его на 0.01, тогда p₂ станет 0.2519, лосс 1.3788. Лосс упал на 0.0075 = 0.75 · 0.01. Знак и множитель совпали. Спуск тянет правильный логит вверх с силой 1 − p_t, остальные вниз с силой своей вероятности.

Ключевой вывод недели. Сделать руками: это спрашивают постоянно:

Градиент softmax + cross-entropy:
  ∂L/∂z = p − one_hot(t)

Путь: ∂L/∂p (ненулевой только в t) → Якобиан softmax (p_j(1−p_j) на диагонали, −p_j p_i вне) → всё схлопывается.

Градиент softmax + CE на примере из 4 классовГрадиент softmax + CE на примере из 4 классов
Схема 8. Пример на 4 класса: p = softmax(z), из него вычитается one_hot(t), и это весь градиент. Правильный класс получает −(1 − p_t), остальные +p_j, сумма градиента равна нулю.

Код → nanolm/autograd.py: свой autograd-движок (micrograd-стиль): Value со скаляром, градиентом и ссылками на родителей; операции + * tanh exp pow, каждая знает свой локальный градиент; .backward() строит топологическую сортировку графа и проходит её в обратном порядке. Затем Neuron, Layer, MLP поверх Value и обучение на XOR. Задание: exercises/autograd.py, проверка: NANOLM_IMPL=exercises pytest tests/test_autograd.py -v.

На что смотреть в тестах: узел, использованный дважды (a * a, a + a), обязан получить сумму градиентов: это правило разветвления из теории выше. Если градиент перезаписывается вместо +=, упадёт test_reused_variable_accumulates_gradient. Ромбовидный граф (test_diamond_graph_needs_topological_order) ловит обход без топологической сортировки, а случайные выражения сверяются с PyTorch.

Математика (трек D): Uniform, Exponential, Gaussian. Свойство отсутствия памяти (geometric и exponential, единственные memoryless-распределения).

Интервью-вопрос недели: «Выведи градиент softmax + cross-entropy по логитам». Спрашивают дословно. Структура на 3 минуты: (1) ответ первым: ∂L/∂z = p − one_hot(t); (2) путь: ∂L/∂p ненулевой только в t и равен −1/p_t → строка t Якобиана softmax (p_t(1−p_t) на диагонали, −p_t p_j вне) → после умножения остаётся p_j − [j = t]; (3) смысл: правильный логит тянется вверх с силой 1 − p_t, остальные вниз с силой p_j, сумма градиента равна нулю; (4) цифра: z = (0.69, 0, 0), верный класс второй → (0.5, −0.75, 0.25); поднять второй логит на 0.01, и лосс падает на 0.0075; (5) жди «почему в коде это одна функция от логитов, а не softmax, потом log». Ответ: log_softmax через logsumexp (неделя 4), без деления на крошечное p_t.

Глубже: 05-ГЛУБИНА, раздел «Недели 1–4, трек D. Математика: большой недобор».

Результаты недели

  • Могу вывести ∂L/∂z = p − one_hot(t) для softmax + CE через Якобиан softmax за 5 минут.
  • Могу реализовать Value-autograd с топологической сортировкой и обучить на нём MLP.
  • Могу показать на графе, почему градиенты на разветвлении складываются, и найти этот баг в чужом коде.
  • Могу посчитать память и вычисления при activation checkpointing и вывести оптимум K = √N.

Самопроверка

  1. Почему backward идёт в обратном топологическом порядке и что сломается при другом порядке?
  2. Как узлы +, × и max распределяют градиент в обратном проходе?
  3. Почему проверка градиента использует центральную разность и почему в float64?
  4. Почему MSE это отрицательное лог-правдоподобие гауссианы и почему нельзя учить модель прямо по accuracy?
  5. Сколько прогонов forward стоит численный градиент для N весов и почему для обучения нужен обратный режим, а не прямой?

В приложении у недели есть навыки для самооценки, вопросы с проверкой ответа, задачи с кодом на Python и тьютор по материалам курса.

Учиться в приложении: тьютор, задачи с кодом
← НазадНеделя 1. Нейросети и градиенты Дальше →Неделя 3. Оптимизаторы и режим обучения

С нуля
С нуля: курс по LLM

  • Главная
  • Программа курса
  • Приложение
  • Конфиденциальность
  • Условия

Текст курса распространяется по лицензии CC BY-NC-SA 4.0, код nanolm по лицензии Apache-2.0.