Как читать формулы
Греческие буквы, индексы, знаки Σ и Π, векторная запись — и разбор по символам настоящих формул из статей (MSE, softmax, cross-entropy, градиентный шаг, attention) с переводом каждой в код.
Зачем это нужно
Заголовок раздела «Зачем это нужно»Формула — это сжатый код. Строчка заменяет полстраницы текста. На этом языке написана вся литература по LLM — от статьи «Attention Is All You Need» до техотчётов DeepSeek и Qwen. Пока формулы выглядят как шифр, статьи закрыты, и приходится верить пересказам.
Хорошая новость: словарь небольшой. Полтора десятка греческих букв, несколько значков (, , ,
) и правила индексов — этого хватает, чтобы читать почти все формулы этого курса. Главное правило урока:
каждую формулу переводим в код. Если вы смогли написать формулу циклом for, значит, вы её поняли.
Греческие буквы ML
Заголовок раздела «Греческие буквы ML»Греческие буквы в формулах — просто имена переменных, как lr или theta в коде. Но у многих есть устойчивые
значения, и их полезно знать заранее:
| Буква | Читается | Что обычно значит в ML | Где встретится |
|---|---|---|---|
| альфа | коэффициент; показатель степени в scaling laws | 1.0.2, 5.1 | |
| бета | в Adam; сила KL-штрафа в DPO и GRPO | 1.4.2, ступень 6 | |
| гамма | масштаб в нормализации; дисконт в RL | 4.5, ступень 6 | |
| дельта | малое изменение, разность: | модуль 1.2 | |
| эпсилон | крошечное число против деления на ноль (1e-8 в Adam); порог клиппинга в PPO и GRPO | 1.4.2, ступень 6 | |
| эта | learning rate | модуль 1.4 | |
| тета | все параметры модели разом | везде | |
| лямбда | коэффициент регуляризации (weight decay) | 2.1, 1.4.2 | |
| мю | среднее | этот урок, 1.3 | |
| сигма | стандартное отклонение; сигмоида | 1.0.1, этот урок | |
| пи | число 3.14…, а в RL — политика, то есть сама модель | ступень 6 | |
| тау | температура (часто пишут и ) | 4.1.3 | |
| сигма заглавная | сумма | этот урок | |
| пи заглавная | произведение | этот урок | |
| набла | градиент | 1.2.3 | |
| «дэ круглое» | частная производная | 1.2.3 |
Латинские буквы тоже имеют привычные роли: — вход, — правильный ответ, — предсказание, и — веса, — смещение, или — loss, или — число примеров, — размерность, — позиция токена или шаг обучения, — длина последовательности.
Индексы: нижние и верхние
Заголовок раздела «Индексы: нижние и верхние»Нижний индекснижний индексEN subscript (index)x_i — i-й элемент. В формулах счёт обычно с 1, в Python — с 0. Индекс бывает и просто названием, как k в d_k. означает «-й элемент ». Пусть . В математике
счёт обычно идёт с единицы: , . В Python — с нуля: x[0] == 5, x[2] == 2. Это главный
источник ошибок при переводе формул в код:
| В формуле | В Python |
|---|---|
x[0], x[1], …, x[n-1] | |
for i in range(n) | |
| — строка , столбец | W[i][j] или в NumPy W[i, j] (с тем же сдвигом: — это W[0, 0]) |
Индекс-название. Не каждый нижний индекс — номер. В буква значит «keys»: это просто имя «размерность ключей». Точно так же — «первая бета», — «политика с параметрами », — «градиент по ». Подсказка: если по букве в индексе нет суммы и она не меняется в цикле, скорее всего, это название.
Верхние индексы бывают четырёх видов:
| Запись | Читается | Что значит |
|---|---|---|
| икс в квадрате | степень (урок 1.0.2) | |
| икс с индексом i в скобках | -й пример датасета, а не степень | |
| или | W транспонированная | строки и столбцы меняются местами (урок 1.1.2) |
| , | W с индексом Q, тета со звёздочкой | метки: «матрица для запросов», «лучшие параметры» |
Значки над буквой тоже несут смысл: («с крышкой») — предсказание или оценка, («с чертой») — среднее, («с тильдой») — изменённая или приближённая версия, («штрих») — другое значение или производная.
Знак суммы Σ — это цикл for
Заголовок раздела «Знак суммы Σ — это цикл for»Знак суммызнак суммы (Σ)EN summation notation (sigma notation)Σ_{i=1}^{n} x_i — «сложить x_i для i от 1 до n»; в коде — цикл for с накоплением суммы от 0. — самый частый значок в ML:
Читается: «сумма по от 1 до икс-итых». Под знаком записаны переменная цикла и начало (), над ним — конец (), справа — что складываем (тело цикла). Перевод в Python один к одному:
x = [5, 8, 2, 7]n = len(x)
total = 0 # сумма начинается с нуляfor i in range(n): # i = 0, 1, …, n−1 (в формуле 1, …, n) total += x[i] # тело суммы: x_iprint(total) # 22print(sum(x)) # 22 — то же самое встроенной функциейСправа от может стоять любое выражение. Например, , в коде
sum(xi ** 2 for xi in x).
Среднее и дисперсия. Две формулы, которые встретятся сотни раз:
Среднее — сумма, делённая на количество: . Дисперсия — средний квадрат отклонения
от среднего. Отклонения равны , их квадраты — , сумма — 21,
а . Стандартное отклонение — корень из дисперсии: .
Обратите внимание: формула дисперсии использует результат формулы среднего. В коде сначала считаем mu,
потом дисперсию.
Сокращённые записи. Часто пределы суммы не пишут: значит «по всем ». Запись значит «по всем из множества ». Двойная сумма — это два вложенных цикла: сумма всех элементов матрицы.
Взвешенная сумма — это нейрон. Нейрон из урока 1.0.1 с входами записывают так:
w = [0.5, -1.0, 2.0]x = [4.0, 1.0, 0.5]b = 0.1z = sum(wi * xi for wi, xi in zip(w, x)) + b # 0.5·4 − 1·1 + 2·0.5 + 0.1print(z) # 2.1Знак произведения Π
Заголовок раздела «Знак произведения Π»Знак произведениязнак произведения (Π)EN product notation (capital pi)Π_{i=1}^{n} x_i — «перемножить все x_i»; накопление начинается с 1. устроен так же, только числа перемножаются, и накопление начинается с единицы (с нуля всё обнулится):
Главная формула языкового моделирования (урок 4.1.1) — как раз произведение:
Читаем по символам: «вероятность всей последовательности от до равна произведению по от 1 до вероятностей токена при условии всех предыдущих токенов». Вертикальная черта читается «при условии» (урок 1.3.1). Запись означает «все с номерами меньше », то есть префикс текста.
Из прошлого урока мы знаем, что логарифм превращает произведение в сумму: . Поэтому в коде вы почти никогда не увидите — только сумму логарифмов:
import mathprobs = [0.5, 0.2, 0.9] # p(x_t | x_<t) для трёх токенов
p = 1.0 # произведение начинается с 1for q in probs: p *= qprint(p) # 0.09000000000000001print(math.log(p)) # -2.407945608651872print(sum(math.log(q) for q in probs)) # -2.407945608651872 — то же через суммуВекторы и матрицы в формулах
Заголовок раздела «Векторы и матрицы в формулах»Во многих учебниках (например, в d2l) шрифт подсказывает тип объекта:
- — курсив, строчная буква: скаляр (одно число);
- — жирная строчная: вектор (список чисел);
- или — заглавная: матрица (таблица чисел).
Многие статьи жирный шрифт не используют, и тип приходится угадывать по контексту. Здесь выручает запись с — самая полезная строчка в любой статье, потому что она сообщает формы (shape) массивов:
| Формула | Читается | В NumPy |
|---|---|---|
| x принадлежит эр в степени d | x.shape == (d,) | |
| W — матрица m на n из вещественных чисел | W.shape == (m, n) | |
| w транспонированное на x | w @ x — то же, что | |
| W на x | W @ x | |
| поэлементное произведение | a * b |
Буквой обозначают множество вещественных (обычных дробных) чисел. Запись — это сумма из раздела про нейрон, только короче. Так что нейрон пишут ещё и так: . Как устроены векторы и матрицы, разберём в модуле 1.1, а пока достаточно уметь их узнавать.
Другие значки
Заголовок раздела «Другие значки»| Значок | Читается | Пример | В коде |
|---|---|---|---|
| «по определению равно» | def relu(x): … | ||
| «присвоить» | theta = theta - lr * g | ||
| приблизительно равно | math.isclose(a, b) | ||
| пропорционально | равно с точностью до общего множителя | ||
| принадлежит | i in range(n), форма массива | ||
| много меньше | — | ||
| максимум | max(z) | ||
| аргмакс: номер максимума | np.argmax(z) вернёт 1 | ||
| модуль или размер множества | — число токенов в словаре | abs(x), len(V) | |
| норма (длина) вектора | np.linalg.norm(x) (урок 1.1.1) | ||
| математическое ожидание | среднее по батчу (урок 1.3.1) | ||
| «выбрано из распределения» | сэмплирование |
Обратите внимание на . В математике счёт идёт с единицы, и аргмакс равен 2. np.argmax
считает с нуля и вернёт 1. Это тот же сдвиг на единицу, что и с индексами. Аргмакс понадобится в «жадной»
генерации: модель берёт токен с наибольшей вероятностью (урок 4.1.3).
Разбираем настоящие формулы
Заголовок раздела «Разбираем настоящие формулы»Теперь словаря хватает, чтобы прочитать пять формул, на которых держится весь курс. Для каждой — чтение по символам, пример с числами и код.
1. Среднеквадратичная ошибка (MSE)
Заголовок раздела «1. Среднеквадратичная ошибка (MSE)»Читаем: «одна -я суммы по от 1 до квадратов разностей предсказания и ответа ». По частям: — усреднение, — цикл по примерам, — ошибка на -м примере, квадрат — штраф, который растёт быстрее ошибки (урок 1.0.1). Это MSEсреднеквадратичная ошибка (MSE)EN mean squared error (MSE)Корень из MSE — RMSE (root mean squared error), измеряется в тех же единицах, что и целевая переменная., среднеквадратичная ошибка.
Числа: , . Ошибки равны , квадраты — . Сумма 4.25, делим на 3 и получаем MSE .
y_true = [3.0, 5.0, 2.0]y_pred = [2.5, 5.0, 4.0]n = len(y_true)
total = 0.0for i in range(n): total += (y_pred[i] - y_true[i]) ** 2 # (ŷ_i − y_i)²print(total / n) # 1.41666666666666672. Softmax
Заголовок раздела «2. Softmax»Читаем: «-я компонента softmax от вектора равна в степени , делённому на сумму по от 1 до экспонент ». Здесь — логиты: «сырые» оценки, которые модель выдаёт для каждого токена словаря. Это любые числа, в том числе отрицательные. — размер словаря (не матрица значений!). Softmax превращает логиты в вероятности: все положительные, в сумме 1.
Главное наблюдение — в формуле два разных индекса. Индекс свободный: формула верна для каждого , поэтому результат — целый вектор. Индекс связанный: он живёт только внутри и «исчезает» после суммирования. В коде сумма по считается один раз, а потом используется для каждого . Эту же формулу часто пишут короче: — «вероятность пропорциональна экспоненте логита».
Числа: . Экспоненты равны , их сумма — 11.213. Делим каждую на сумму и получаем .
import mathz = [2.0, 1.0, 0.1]exps = [math.exp(zj) for zj in z] # e^{z_j} для всех js = sum(exps) # Σ_j e^{z_j} — считаем один разp = [e / s for e in exps] # для каждого i: e^{z_i} / sprint([round(pi, 3) for pi in p]) # [0.659, 0.242, 0.099]Это «школьная» версия. На логитах вроде 1000 она переполнится. Устойчивую версию вы напишете в уроке 1.3.2.
3. Cross-entropy
Заголовок раздела «3. Cross-entropy»Читаем: «аш от и равно минус сумме по от 1 до произведений на логарифм ». Здесь и — два распределения, то есть наборы вероятностей по всем токенам словаря с суммой 1. — правильный ответ, — предсказание модели (выход softmax), — натуральный логарифм (урок 1.0.2). В языковой модели правильный ответ один — токен . Поэтому , а остальные (такой вектор называют one-hot). В сумме выживает одно слагаемое:
Числа: берём из softmax выше и правильный токен (первый). Тогда .
import mathq = [0.659, 0.242, 0.099] # из softmaxp = [1, 0, 0] # one-hot: правильный — первый токенfull = -sum(pi * math.log(qi) for pi, qi in zip(p, q))short = -math.log(q[0]) # индекс 0 в Python = t = 1 в формулеprint(round(full, 3), round(short, 3)) # 0.417 0.417Это cross-entropyперекрёстная энтропияEN cross-entropy — функция потерь каждой LLM. Почему она именно такая, подробно разобрано в уроке 1.3.2.
4. Шаг градиентного спуска
Заголовок раздела «4. Шаг градиентного спуска»Читаем: «тета присвоить тета минус эта, умноженное на градиент эл по тета». Здесь — все параметры,
— learning rateскорость обученияEN learning rate, а — градиент функции потерь,
то есть направление её самого быстрого роста (урок 1.2.3). Минус означает «идём в обратную сторону», вниз по
склону. Стрелка — это присваивание, как = в коде.
Числа: вернёмся к «чаше» из урока 1.0.1. Её производная равна (как её найти, узнаете в модуле 1.2). Начнём с и :
w, lr = 2.0, 0.1for step in range(4): grad = 8 * w - 24 # ∇L(w) w = w - lr * grad # θ ← θ − η·∇L print(step, round(w, 4))0 2.81 2.962 2.9923 2.9984С каждым шагом подходит к дну чаши . Именно этот цикл в разных обличьях крутится при обучении любой нейросети (градиентный спускградиентный спускEN gradient descent, модуль 1.4).
5. Attention
Заголовок раздела «5. Attention»Читаем: «attention от , , равно softmax от , умноженного на транспонированное и делённого на корень из , и всё это умножить на ». Это формула (1) из статьи «Attention Is All You Need», сердце трансформера (механизм вниманиямеханизм вниманияEN attention). Разберём каждый символ. Пусть в тексте токенов:
| Символ | Форма | Смысл |
|---|---|---|
| (queries, запросы) | что ищет каждый токен | |
| (keys, ключи) | что предлагает каждый токен | |
| (values, значения) | какую информацию токен передаёт | |
| все попарные сходства: элемент равен | ||
| число | масштаб, чтобы числа не росли с размерностью (урок 4.4.2) | |
| каждую строку превращает в веса с суммой 1 | ||
| каждая строка ответа — взвешенная сумма строк |
Здесь — матрица значений, а не размер словаря, как в softmax выше. Снова решает контекст.
flowchart LR Q["Q (T×d_k)"] --> S["Q·Kᵀ / √d_k<br/>(T×T)"] K["K (T×d_k)"] --> S S --> W["softmax по строкам<br/>веса (T×T)"] W --> O["веса · V<br/>ответ (T×d_v)"] V["V (T×d_v)"] --> O
Числа: возьмём один запрос и три токена с ключами , , и значениями , , ; . Сначала распишем формулу циклами — каждое умножение матриц превращается в :
import math
q = [1.0, 0.0] # запрос одного токенаK = [[1.0, 0.0], [0.0, 1.0], [-1.0, 0.0]] # ключи трёх токеновV = [[10.0, 0.0], [0.0, 10.0], [0.0, 0.0]] # значения трёх токеновd_k = len(q)
scores = []for j in range(len(K)): # для каждого токена j s = sum(q[c] * K[j][c] for c in range(d_k)) # Σ_c q_c · K_jc — сходство запроса с ключом j scores.append(s / math.sqrt(d_k)) # делим на √d_kprint([round(s, 3) for s in scores]) # [0.707, 0.0, -0.707]
exps = [math.exp(s) for s in scores]weights = [e / sum(exps) for e in exps] # softmaxprint([round(w, 3) for w in weights]) # [0.576, 0.284, 0.14]
out = [sum(weights[j] * V[j][c] for j in range(len(V))) for c in range(2)] # Σ_j w_j · v_jprint([round(o, 3) for o in out]) # [5.76, 2.84]Запрос больше всего похож на первый ключ, поэтому первый токен получил наибольший вес 0.576 и в ответе преобладает его значение. А теперь та же формула для трёх запросов сразу, на NumPy — одна строка на символ:
import numpy as np
Q = np.array([[1.0, 0.0], [0.0, 1.0], [-1.0, 0.0]]) # (T=3, d_k=2)K = np.array([[1.0, 0.0], [0.0, 1.0], [-1.0, 0.0]]) # (3, 2)V = np.array([[10.0, 0.0], [0.0, 10.0], [0.0, 0.0]]) # (3, 2)d_k = Q.shape[1]
scores = Q @ K.T / np.sqrt(d_k) # QKᵀ/√d_k → (3, 3)weights = np.exp(scores) / np.exp(scores).sum(axis=1, keepdims=True) # softmax по строкамout = weights @ V # · V → (3, 2)print(out.round(3))[[5.76 2.84 ] [2.483 5.035] [1.4 2.84 ]]Первая строка совпала с ответом циклов. Подробно attention разберём в модуле 4.4, а сейчас важно, что вы можете прочитать эту формулу символ за символом.
Шпаргалка: как прочитать незнакомую формулу
Заголовок раздела «Шпаргалка: как прочитать незнакомую формулу»Пять разборов выше шли по одному и тому же плану. Он работает и для формулы из любой новой статьи:
- Что вычисляем? Посмотрите на левую часть: число, вектор или матрица получится в итоге.
- Прочитайте абзац «where…» под формулой и выпишите, что значит каждая буква.
- Найдите формы. Записи вида подскажут
shapeкаждого массива. - Разберите индексы. Индекс под или — переменная цикла, свободный индекс — «для каждого», индекс-название — просто имя.
- Подставьте маленькие числа — два-три элемента, как в наших примерах, — и посчитайте руками.
- Напишите циклами, сверьте с ручным счётом и только потом переходите на NumPy.
Если на каком-то шаге застряли, значит, не хватает одного обозначения. Найдите его в таблицах этого урока или в разделе обозначений книги d2l (ссылка в источниках).
Практика
Заголовок раздела «Практика»Переведите шесть формул в код: среднее, дисперсию, MSE, произведение , скалярное произведение и аргмакс. Формула каждой функции записана в её docstring.
# Переведите каждую формулу в код. Можно циклом for (так нагляднее),
# можно встроенными sum(...) и генераторами. NumPy здесь не нужен.
# Помните: в формулах индексы идут с 1 до n, а в Python — с 0 до n−1.
def mean(xs):
"""Среднее: μ = (1/n) · Σ_{i=1}^{n} x_i
mean([2, 4, 6]) → 4.0
"""
# TODO
...
def variance(xs):
"""Дисперсия: σ² = (1/n) · Σ_{i=1}^{n} (x_i − μ)², где μ — среднее.
variance([2, 4, 4, 4, 5, 5, 7, 9]) → 4.0
"""
# TODO: сначала посчитайте μ через mean(xs)
...
def mse(y_pred, y_true):
"""Среднеквадратичная ошибка: MSE = (1/n) · Σ_{i=1}^{n} (ŷ_i − y_i)²
mse([2.5, 5.0, 4.0], [3.0, 5.0, 2.0]) → 1.4166…
"""
# TODO
...
def product(xs):
"""Произведение: Π_{i=1}^{n} x_i = x_1 · x_2 · … · x_n
product([1, 2, 3, 4]) → 24. Произведение пустого списка равно 1.
"""
# TODO: с какого числа начинать накопление — с 0 или с 1?
...
def dot(w, x):
"""Скалярное произведение: wᵀx = Σ_{i=1}^{n} w_i · x_i
dot([0.5, -1.0, 2.0], [4.0, 1.0, 0.5]) → 2.0
"""
# TODO
...
def argmax(xs):
"""argmax_i x_i — НОМЕР (индекс с нуля) самого большого элемента, а не сам элемент.
argmax([0.1, 0.7, 0.2]) → 1. При равенстве — первый из максимальных.
"""
# TODO
...
print(mean([2, 4, 6]), variance([2, 4, 4, 4, 5, 5, 7, 9]))
print(mse([2.5, 5.0, 4.0], [3.0, 5.0, 2.0]))
print(product([1, 2, 3, 4]), dot([0.5, -1.0, 2.0], [4.0, 1.0, 0.5]))
print(argmax([0.1, 0.7, 0.2]))
Проверь себя
Заголовок раздела «Проверь себя»- Греческие буквы — просто имена переменных с устойчивыми ролями: — параметры, — learning rate, и — среднее и разброс, — крошечное число. Смысл уточняйте в абзаце «where…» под формулой.
- — цикл со сложением, начатым с 0, — цикл с умножением, начатым с 1. В формулах счёт с 1, в Python — с 0.
- Верхний индекс бывает степенью (), номером примера (), транспонированием () или меткой (). Запись сообщает форму массива.
- Вы прочитали и перевели в код MSE, softmax, cross-entropy, шаг градиентного спуска, attention и даже формулу MLA из статьи DeepSeek.
Дальше: тригонометрия для ML — радианы, синус и косинус как координаты точки на окружности и повороты векторов. На этом стоят позиционные кодировки трансформера и RoPE.