Перейти к содержимому
Ступень 11.0 Школьная математика — освежаем

Степени, экспонента и логарифмы

Степени и корни, научная запись чисел, число e и логарифм — и почему в ML повсюду логарифмы, от cross-entropy и perplexity до графиков scaling laws.

УРОКstage-1/1-0-school-math/02-powers-logs.mdx1 ч 40 минУрок 2 из 5
После урока вы сможете
  • Уверенно считать со степенями, корнями и научной записью чисел (3e-4, 6.71e11) — в голове и в Python
  • Объяснить, что такое число ee и логарифм, и применять свойства log⁡(ab)=log⁡a+log⁡b\log(ab) = \log a + \log b и log⁡an=nlog⁡a\log a^n = n \log a
  • Объяснить, почему вероятности в ML складывают в логарифмах, и посчитать perplexity по списку вероятностей
  • Читать графики в логарифмической шкале и узнавать на них степенные законы

Числа в ML живут в огромном диапазоне. У DeepSeek-V3 671 млрд параметров, типичный learning rate — 0.0003, вероятность редкого токена — 0.00001. А вероятность целого абзаца текста — число с сотнями нулей после запятой. Такие числа неудобно писать и сравнивать, а компьютер может их попросту «потерять».

Степени, научная запись и логарифмы делают огромные и крошечные числа удобными. Логарифм вдобавок сидит в функциях потерь и метриках языковых моделей: cross-entropy, log-softmax, KL-дивергенция, perplexity. После этого урока вы будете понимать, зачем он там нужен.

СтепеньстепеньEN power (exponentiation)aⁿ — a, умноженное само на себя n раз. В Python — a ** n (не a ^ n, это XOR!). ana^n — это aa, умноженное само на себя nn раз: 210=2⋅2⋅…⋅2=10242^{10} = 2 \cdot 2 \cdot \ldots \cdot 2 = 1024. Число aa называют основанием, nn — показателем. В Python степень пишется двумя звёздочками: 2 ** 10.

Все свойства степеней следуют из определения «умножить nn раз»:

ПравилоФормулаПример
Умножениеam⋅an=am+na^m \cdot a^n = a^{m+n}23⋅24=27=1282^3 \cdot 2^4 = 2^7 = 128
Степень степени(am)n=am⋅n(a^m)^n = a^{m \cdot n}(23)2=26=64(2^3)^2 = 2^6 = 64
Нулевая степеньa0=1a^0 = 1100=110^0 = 1
Отрицательная степеньa−n=1/ana^{-n} = 1 / a^n2−3=1/8=0.1252^{-3} = 1/8 = 0.125
Дробная степеньa1/n=ana^{1/n} = \sqrt[n]{a}91/2=39^{1/2} = 3, 81/3=28^{1/3} = 2

Первое правило легко увидеть: 23⋅24=(2⋅2⋅2)⋅(2⋅2⋅2⋅2)2^3 \cdot 2^4 = (2 \cdot 2 \cdot 2) \cdot (2 \cdot 2 \cdot 2 \cdot 2) — всего семь двоек. Проверим остальные в Python:

print(2 ** 3 * 2 ** 4, 2 ** 7) # 128 128
print((2 ** 3) ** 2, 2 ** 6) # 64 64
print(2 ** -3) # 0.125
print(9 ** 0.5, 8 ** (1 / 3)) # 3.0 2.0

Компьютер хранит всё в битах, а nn битов дают 2n2^n разных значений: 8 битов (один байт) — 256 значений, 16 битов — 65 536. Отсюда практический вывод. В nanoGPT номера токенов GPT-2 хранят в 16-битном типе uint16, потому что словарь GPT-2 (50 257 токенов) туда помещается. У Qwen3 в словаре 151 936 позиций — больше, чем 217=131 0722^{17} = 131\,072. Значит, на номер токена нужно 18 битов, uint16 уже не подходит, и приходится брать 32-битный uint32 — вдвое больше памяти на каждый токен датасета.

Длины контекста тоже обычно степени двойки: 4096=2124096 = 2^{12}, 32 768=21532\,768 = 2^{15}. Контекст «128K» — это 128⋅1024=217=131 072128 \cdot 1024 = 2^{17} = 131\,072 токена.

Квадратный кореньквадратный кореньEN square root√a = a^(1/2). В ML — √d_k в attention, стандартное отклонение, RMSNorm. a\sqrt{a} — число, которое в квадрате даёт aa: 9=3\sqrt{9} = 3, 2≈1.414\sqrt{2} \approx 1.414. На языке степеней a=a1/2\sqrt{a} = a^{1/2}. В Python пишут math.sqrt(9) или 9 ** 0.5.

Корни постоянно встречаются в формулах LLM:

  • в attention скалярные произведения делят на dk\sqrt{d_k}: при dk=128d_k = 128 это 128≈11.31\sqrt{128} \approx 11.31 (урок 4.4.2);
  • стандартное отклонение — это корень из дисперсии (урок 1.0.3);
  • RMSNorm в Llama, Qwen и DeepSeek делит вектор на корень из среднего квадрата его элементов (урок 4.5.3);
  • Adam делит шаг на корень из скользящего среднего квадратов градиента (урок 1.4.2).

Попробуйте вычислить (-8) ** (1 / 3). Вместо ожидаемого −2-2 Python вернёт комплексное число (1.0000000000000002+1.7320508075688772j). Дробные степени отрицательных чисел лучше обходить стороной.

Число 671 000 000 000671\,000\,000\,000 удобнее записать как 6.71⋅10116.71 \cdot 10^{11}. Это научная записьнаучная (экспоненциальная) записьEN scientific notation3e-4 = 3·10⁻⁴ = 0.0003; 6.71e11 = 671 млрд.: мантисса от 1 до 10, умноженная на 10 в нужной степени. Отрицательная степень означает маленькое число: 3⋅10−4=0.00033 \cdot 10^{-4} = 0.0003. В Python и в статьях то же самое пишут через букву e, и эта буква значит «умножить на 10 в степени», а не число ee:

ЗаписьВ PythonОбычное числоГде встречается
3⋅10−43 \cdot 10^{-4}3e-40.0003типичный learning rate для Adam
10−810^{-8}1e-80.00000001ε\varepsilon в Adam — защита от деления на ноль
6.022⋅10236.022 \cdot 10^{23}6.022e23602 200 000 000 000 000 000 000число Авогадро
6.71⋅10116.71 \cdot 10^{11}6.71e11671 000 000 000параметры DeepSeek-V3
1.48⋅10131.48 \cdot 10^{13}1.48e1314 800 000 000 000токены обучения DeepSeek-V3

Python сам переходит на научную запись для маленьких чисел: print(0.00001) выведет 1e-05. Чтобы задать формат явно, используйте f-строку: f"{123456789:.2e}" даёт '1.23e+08'.

Умножение в научной записи — мантиссы перемножаем, показатели складываем (правило am⋅an=am+na^m \cdot a^n = a^{m+n}). Оценим, сколько операций ушло на обучение DeepSeek-V3. Есть грубое правило: на каждый параметр и каждый токен приходится около 6 операций — 2 на прямой проход (модель считает ответ) и 4 на обратный (считает поправки к весам, модуль 3.1). У DeepSeek-V3 на каждый токен работают N=3.7⋅1010N = 3.7 \cdot 10^{10} активных параметров, а токенов в предобучении было D=1.48⋅1013D = 1.48 \cdot 10^{13}:

C≈6⋅N⋅D=6⋅3.7⋅1.48⋅1010+13≈32.9⋅1023=3.29⋅1024C \approx 6 \cdot N \cdot D = 6 \cdot 3.7 \cdot 1.48 \cdot 10^{10 + 13} \approx 32.9 \cdot 10^{23} = 3.29 \cdot 10^{24}
N = 37e9 # активных параметров на токен
D = 14.8e12 # токенов в обучении
C = 6 * N * D # правило «6ND»: грубая оценка числа операций
print(f"{C:.2e}") # 3.29e+24
gpu_seconds = 2.664e6 * 3600 # 2.664 млн GPU-часов предобучения из техотчёта → секунды
print(f"{C / gpu_seconds:.2e}") # 3.43e+14 операций в секунду на один GPU

Выходит около 3.4⋅10143.4 \cdot 10^{14} операций в секунду на каждый ускоритель NVIDIA H800, на которых обучали модель. Пик такой карты на обычных для обучения 16-битных числах — около 101510^{15} операций в секунду, так что карты работали примерно на треть от пика (а большая часть умножений у DeepSeek-V3 шла в 8-битном формате FP8, где пик вдвое выше, — так что реальная загрузка ещё ниже). Для большого обучения это нормально, и цифры отчёта сходятся с прикидкой (подробно — в уроке 5.1.1). Такие расчёты «на салфетке» — обычное дело для LLM-инженера, и научная запись делает их быстрыми.

Откуда берётся странное число e≈2.718e \approx 2.718? Представьте банк, который даёт 100% годовых. Если проценты начисляют раз в год, рубль превращается в 2 рубля. Если дважды по 50%, получится (1+1/2)2=2.25(1 + 1/2)^2 = 2.25. Если ежемесячно по 1/121/12, то (1+1/12)12≈2.613(1 + 1/12)^{12} \approx 2.613. Чем чаще начисление, тем больше итог, но рост упирается в предел:

for n in [1, 2, 12, 365, 1_000_000]:
print(n, (1 + 1 / n) ** n)
1 2.0
2 2.25
12 2.613035290224676
365 2.7145674820219727
1000000 2.7182804690957534

Этот предел и есть число eeчисло e (число Эйлера)EN Euler's number ee ≈ 2.71828 — предел (1 + 1/n)ⁿ. Основание экспоненты и натурального логарифма. =2.718281828…= 2.718281828\ldots В Python оно хранится в math.e. Функция exe^x — экспонентаэкспонента (показательная функция)EN exponential functioneˣ — всегда положительна, растёт взрывообразно. Внутри softmax и сигмоиды. из прошлого урока, в коде math.exp(x) или np.exp(x). Для неё работают все правила степеней: ea+b=ea⋅ebe^{a+b} = e^a \cdot e^b, e−x=1/exe^{-x} = 1/e^x, e0=1e^0 = 1.

Почему в ML основание именно ee, а не 2 или 10? Скорость роста exe^x в каждой точке равна её значению. Иначе говоря, производная exe^x равна самой exe^x (модуль 1.2), и с таким основанием формулы градиентов получаются самыми простыми.

Как быстро растёт экспонента: e10≈22 026e^{10} \approx 22\,026, e20≈4.85⋅108e^{20} \approx 4.85 \cdot 10^{8}, e100≈2.69⋅1043e^{100} \approx 2.69 \cdot 10^{43}. Самое большое число в float64 — около 1.8⋅103081.8 \cdot 10^{308}, поэтому math.exp(709) ещё работает, а math.exp(710) бросает OverflowError. В 16-битном формате FP16 максимум всего 65 504, и экспонента переполняется уже при x≈11.1x \approx 11.1. Это одна из причин, почему softmax в обучении с пониженной точностью считают так осторожно (урок 1.3.2 и модуль 5.4).

Степень отвечает на вопрос «чему равно 232^3?» (ответ: 8). ЛогарифмлогарифмEN logarithmlog_b x — в какую степень возвести b, чтобы получить x. Превращает произведение в сумму. отвечает на обратный вопрос: в какую степень нужно возвести 2, чтобы получить 8? Ответ — 3, и это записывают как log⁡28=3\log_2 8 = 3.

log⁡bx=y⟺by=x\log_b x = y \quad \Longleftrightarrow \quad b^y = x

Несколько примеров: log⁡21024=10\log_2 1024 = 10, log⁡101000=3\log_{10} 1000 = 3 (столько нулей), log⁡100.001=−3\log_{10} 0.001 = -3, а log⁡b1=0\log_b 1 = 0 при любом основании, потому что b0=1b^0 = 1.

Логарифм по основанию 2 — это «сколько раз удвоить». Лист бумаги толщиной 0.1 мм после 10 сгибаний станет толщиной 0.1⋅210≈1020.1 \cdot 2^{10} \approx 102 мм. Сколько раз нужно мысленно сложить лист, чтобы его толщина превысила расстояние до Луны? До Луны 384 400 км, или 3.844⋅10113.844 \cdot 10^{11} мм, то есть 3.844⋅10123.844 \cdot 10^{12} толщин листа. Ответ: log⁡2(3.844⋅1012)≈41.8\log_2(3.844 \cdot 10^{12}) \approx 41.8, то есть 42 сгибания. Логарифм превращает «астрономическое» число в обозримое.

Графики степени и логарифма зеркальны относительно прямой y=xy = x: если точка (2,4)(2, 4) лежит на y=2xy = 2^x, то точка (4,2)(4, 2) лежит на y=log⁡2xy = \log_2 x.

-3-3-2-2-1-11122334455(2, 4)(4, 2)(1, 2)(2, 1)y = 2ˣy = log₂ xy = x
Степень и логарифм отменяют друг друга, поэтому их графики — отражения относительно прямой y = x.

Из графика видно главное про логарифм. Он определён только для положительных xx. Он растёт, но очень медленно. А для чисел меньше 1 логарифм отрицателен. Последнее важно: вероятности лежат между 0 и 1, поэтому их логарифмы всегда отрицательны или равны нулю.

Натуральный логарифмнатуральный логарифмEN natural logarithmln x = log_e x. В статьях по ML «log» без основания почти всегда означает ln; в Python — math.log. ln⁡x\ln x — это логарифм по основанию ee. Полезно помнить несколько значений:

xx0.010.10.512ee10
ln⁡x\ln x−4.605−2.303−0.69300.69312.303

Важно: в статьях по ML и в библиотеках «log» без основания почти всегда означает ln⁡\ln. Это относится к math.log, np.log и torch.log.

import math
print(math.log(8, 2), math.log2(8), math.log10(1000)) # 3.0 3.0 3.0
print(math.log(math.e), math.log(1)) # 1.0 0.0
print(math.log(0.5)) # -0.6931471805599453
math.log(0) # ValueError: math domain error

NumPy на np.log(0.0) не падает, а возвращает -inf и печатает предупреждение RuntimeWarning: divide by zero encountered in log. Такой -inf потом тихо превращает loss в inf, а следующие вычисления — в nan («не число»).

СвойствоФормулаПроверка числом
Произведение → суммаlog⁡(ab)=log⁡a+log⁡b\log(ab) = \log a + \log bln⁡(0.5⋅0.1)=−0.693−2.303=−2.996\ln(0.5 \cdot 0.1) = -0.693 - 2.303 = -2.996
Частное → разностьlog⁡(a/b)=log⁡a−log⁡b\log(a/b) = \log a - \log blog⁡10(1000/10)=3−1=2\log_{10}(1000/10) = 3 - 1 = 2
Степень → множительlog⁡(an)=nlog⁡a\log(a^n) = n \log aln⁡210=10ln⁡2≈6.93\ln 2^{10} = 10 \ln 2 \approx 6.93
Смена основанияlog⁡bx=ln⁡x/ln⁡b\log_b x = \ln x / \ln blog⁡381=ln⁡81/ln⁡3=4\log_3 81 = \ln 81 / \ln 3 = 4

Первое свойство — сердце этого урока. Оно следует из правила am⋅an=am+na^m \cdot a^n = a^{m+n}: при умножении чисел их показатели складываются. Проверим в Python:

import math
print(math.log(0.5 * 0.1), math.log(0.5) + math.log(0.1)) # -2.995732273553991 дважды
print(math.log(1000) / math.log(10)) # 2.9999999999999996 (!)
print(math.isclose(math.log(1000) / math.log(10), 3)) # True

Вторая строка — напоминание из урока 0.2.1: дробные числа сравнивают через math.isclose, а не через ==.

1. Произведение вероятностей превращается в сумму

Заголовок раздела «1. Произведение вероятностей превращается в сумму»

Языковая модель оценивает вероятность текста как произведение вероятностей его токенов (урок 4.1.1). Возьмём 1000 токенов с вероятностью 0.01 у каждого. Их произведение равно 0.011000=10−20000.01^{1000} = 10^{-2000}, а самое маленькое положительное число в float64 — около 5⋅10−3245 \cdot 10^{-324}. Произведение превращается в 0.0. Это называют машинным нулём (underflow). Логарифм спасает:

ln⁡(0.011000)=1000⋅ln⁡0.01≈−4605.17\ln\left(0.01^{1000}\right) = 1000 \cdot \ln 0.01 \approx -4605.17
import math
probs = [0.01] * 1000
p = 1.0
for q in probs:
p *= q
print(p) # 0.0 — информация потеряна
print(sum(math.log(q) for q in probs)) # -4605.17018598815 — обычное число

Cross-entropyперекрёстная энтропияEN cross-entropy для одного токена равна −ln⁡p-\ln p, где pp — вероятность, которую модель дала правильному токену (урок 1.3.2). Логарифм здесь полезен дважды. Во-первых, loss всего текста становится суммой (или средним) loss отдельных токенов. Во-вторых, логарифм больно наказывает за уверенные ошибки:

Вероятность правильного токена pp0.90.50.10.01
Loss −ln⁡p-\ln p0.1050.6932.3034.605

Средний −ln⁡p-\ln p по всем токенам — это и есть cross-entropy текста. Если взять от неё экспоненту, получится perplexityперплексияEN perplexity (перплексия). Экспонента и логарифм отменяют друг друга: eln⁡4=4e^{\ln 4} = 4. Поэтому если модель каждому правильному токену давала вероятность 1/41/4, то loss равен ln⁡4≈1.386\ln 4 \approx 1.386, а perplexity — ровно 4. Модель «колеблется между четырьмя равновероятными вариантами».

import math
probs = [0.5, 0.1, 0.9, 0.3] # вероятности правильных токенов
avg_nll = -sum(math.log(p) for p in probs) / len(probs)
print(round(avg_nll, 3), round(math.exp(avg_nll), 3)) # 1.076 2.934

Логарифм по основанию 2 измеряет информацию в битах. Чтобы угадать один вариант из 8 равновероятных, нужно log⁡28=3\log_2 8 = 3 вопроса «да/нет». На этом построена энтропия (урок 1.3.3).

На обычной шкале равные отрезки означают «прибавить одно и то же». На логарифмической шкалелогарифмическая шкалаEN logarithmic scaleРавные отрезки означают умножение (×10), а не прибавление. Степенные законы на ней — прямые линии. равные отрезки означают «умножить на одно и то же», например на 10. Вот размеры моделей семейства Qwen2.5 на обеих шкалах:

Обычная шкала: шаг = +10 млрд01020304050607014B32B72B0.5B, 1.5B, 3B, 7B — слиплисьЛогарифмическая шкала: шаг = ×101101000.5B1.5B3B7B14B32B72B
Модели Qwen2.5 от 0.5B до 72B. На обычной шкале четыре младшие модели слипаются, на логарифмической — равномерно расходятся.

Логарифмическая шкала нужна, когда значения отличаются в разы, а не на единицы:

  • Размеры моделей и бюджеты вычислений. Учебная модель на 100 млн параметров, обученная на 2 млрд токенов, обходится примерно в 101810^{18} операций, DeepSeek-V3 — в 3⋅10243 \cdot 10^{24}, в несколько миллионов раз больше.
  • Сетки learning rate. Перебирают 1e-5, 3e-5, 1e-4, 3e-4, 1e-3: каждый шаг — умножение примерно на 3, два шага — на 10. На лог-шкале точки стоят равномерно.
  • Кривые обучения. Ось шагов часто логарифмическая, чтобы были видны и первые сотни шагов, и последние сотни тысяч.

Степенной закон на лог-шкале — прямая. Возьмём y=10⋅x−0.5y = 10 \cdot x^{-0.5} и посчитаем десятичные логарифмы:

xx110100100010 000
yy103.16210.3160.1
log⁡10y\log_{10} y10.50−0.5−1

Каждое умножение xx на 10 уменьшает log⁡10y\log_{10} y ровно на 0.5. По свойству «степень → множитель» log⁡y=log⁡10−0.5⋅log⁡x\log y = \log 10 - 0.5 \cdot \log x, а это уравнение прямой с наклоном −0.5-0.5. Именно так выглядят scaling laws — законы масштабирования LLM. В статье Kaplan et al. (2020) зависимость loss от вычислений, данных и числа параметров в лог-лог координатах — почти прямые линии. В matplotlib такой график строится одной командой plt.loglog(x, y), а для одной оси — plt.yscale("log").

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

Удвоения, логарифмы и perplexity7 тестов
import math


def doublings(start, target):
    """Сколько раз нужно удвоить start, чтобы получилось не меньше target.

    doublings(1, 1000) → 10   (2**10 = 1024 ≥ 1000, а 2**9 = 512 ещё мало)
    doublings(5, 3)    → 0    (уже достаточно)
    Можно циклом while, а можно через math.log2 и math.ceil.
    """
    # TODO
    ...


def log_base(x, base):
    """Логарифм x по основанию base: в какую степень возвести base, чтобы получить x.

    log_base(8, 2) → 3.0,  log_base(1000, 10) → 3.0
    Подсказка: формула перехода log_b(x) = ln(x) / ln(b).
    """
    # TODO
    ...


def log_likelihood(probs):
    """Логарифм вероятности последовательности: ln(p1 · p2 · … · pn).

    Перемножать 1000 маленьких вероятностей нельзя — произведение станет 0.0!
    Используйте свойство: ln(a · b) = ln(a) + ln(b).
    log_likelihood([0.5, 0.5]) → −1.386…
    """
    # TODO
    ...


def perplexity(probs):
    """Perplexity = e^(средний −ln p) — «из скольких равных вариантов выбирала модель».

    perplexity([0.25, 0.25, 0.25]) → 4.0
    """
    # TODO: средний отрицательный логарифм, потом math.exp
    ...


print(doublings(1, 1000))
print(log_base(8, 2))
print(log_likelihood([0.5, 0.5]))
print(perplexity([0.25, 0.25, 0.25]))
Проверь себя6 вопросов
1 Чему равен log⁡21024\log_2 1024?
2 Какие записи в Python правильно вычисляют 23=82^3 = 8?Можно выбрать несколько ответов
3 Известно, что ln⁡a=−0.7\ln a = -0.7 и ln⁡b=−2.3\ln b = -2.3. Чему равен ln⁡(a⋅b)\ln(a \cdot b)?
4 Модель дала каждому из 100 правильных токенов вероятность 0.1. Чему равна perplexity?
5 Почему вероятность текста в ML считают через сумму логарифмов, а не перемножением вероятностей?
6 Какие записи равны 0.0003?Можно выбрать несколько ответов
  • Степень — повторное умножение; при умножении степеней показатели складываются. В Python степень — **, а не ^.
  • Научная запись (3e-4, 6.71e11) делает огромные и крошечные числа удобными, а прикидки «на салфетке» — быстрыми.
  • Логарифм — обратная к степени операция; в ML «log» означает ln⁡\ln. Главное свойство: log⁡(ab)=log⁡a+log⁡b\log(ab) = \log a + \log b.
  • Поэтому вероятности складывают в логарифмах (нет машинного нуля), loss — это −ln⁡p-\ln p, а perplexity =eloss= e^{\text{loss}}.
  • На логарифмической шкале шаг — умножение, а степенные законы (scaling laws) выглядят прямыми.

Дальше: как читать формулы — греческие буквы, индексы, знаки Σ\Sigma и Π\Pi и разбор настоящих формул из статей, от softmax до attention.