Фаза 01 · урок 09
Теория информации: энтропия, KL-дивергенция
Цель урока: Вы вызываете CrossEntropyLoss() в каждой обучаемой модели классификации. Видите «перплексию» в каждой статье о языковых моделях. Читаете о KL-дивергенции в VAE, дистилляции и RLHF. Это не разрозненные понятия. Это одна и та же идея в…
Текущий релиз AlexBred.com: первые 100 уроков русскоязычной программы.
Содержание урока
- Цели обучения
- Проблема
- Концепция
- Информационное содержание (неожиданность)
- Энтропия (средняя неожиданность)
- Кросс-энтропия (функция потерь, которую вы используете каждый день)
- KL-дивергенция (расстояние между распределениями)
- Взаимная информация
- Условная энтропия
- Совместная энтропия
- Взаимная информация (подробно)
- Сглаживание меток и кросс-энтропия
- Почему кросс-энтропия — ГЛАВНАЯ функция потерь для классификации
- Биты и наты
- Перплексия
- Соберите сами
- Шаг 1: информационное содержание и энтропия
- Шаг 2: кросс-энтропия и KL-дивергенция
- Шаг 3: кросс-энтропия как функция потерь классификации
- Шаг 4: кросс-энтропия равна отрицательному логарифму правдоподобия
- Шаг 5: взаимная информация
- Примените
- Упражнения
- Ключевые термины
- Дополнительные материалы
Теория информации измеряет неожиданность. На ней строятся функции потерь.
Тип: Изучение Язык: Python Предварительные требования: Фаза 1, урок 06 («Вероятность») Время: ~60 минут
Цели обучения
- Вычислять с нуля энтропию, кросс-энтропию и KL-дивергенцию и объяснять их связь
- Вывести, почему минимизация потерь кросс-энтропии эквивалентна максимизации логарифма правдоподобия
- Вычислять взаимную информацию между признаками и целевой переменной, чтобы ранжировать важность признаков
- Объяснять перплексию как эффективный размер словаря, из которого выбирает языковая модель
Проблема
Вы вызываете CrossEntropyLoss() в каждой обучаемой модели классификации. Видите «перплексию» в каждой статье о языковых моделях. Читаете о KL-дивергенции в VAE, дистилляции и RLHF. Это не разрозненные понятия. Это одна и та же идея в разных обличьях.
Теория информации даёт вам язык для рассуждений о неопределённости, сжатии и предсказании. Клод Шеннон изобрёл её в 1948 году для решения задач связи. Оказывается, обучение нейронной сети — это задача связи: модель пытается передать правильную метку через шумный канал обученных весов.
Этот урок выводит каждую формулу с нуля, чтобы вы увидели, откуда они берутся и почему работают.
Концепция
Информационное содержание (неожиданность)
Когда происходит что-то маловероятное, это несёт больше информации. Монета выпала орлом? Неудивительно. Выигрыш в лотерею? Очень неожиданно.
Информационное содержание события с вероятностью p равно:
I(x) = -log(p(x))
Логарифм по основанию 2 даёт биты. Натуральный логарифм даёт наты. Идея та же, единицы разные.
Event Probability Surprise (bits)
Fair coin heads 0.5 1.0
Rolling a 6 0.167 2.58
1-in-1000 event 0.001 9.97
Certain event 1.0 0.0
Достоверные события несут нулевую информацию. Вы уже знали, что они произойдут.
Энтропия (средняя неожиданность)
Энтропия — это ожидаемая неожиданность по всем возможным исходам распределения.
H(P) = -sum( p(x) * log(p(x)) ) for all x
У честной монеты максимальная энтропия для бинарной переменной: 1 бит. У смещённой монеты (99% орлов) энтропия низкая: 0,08 бита. Вы уже знаете, что произойдёт, поэтому каждый бросок почти ничего не сообщает.
Fair coin: H = -(0.5 * log2(0.5) + 0.5 * log2(0.5)) = 1.0 bit
Biased coin: H = -(0.99 * log2(0.99) + 0.01 * log2(0.01)) = 0.08 bits
Энтропия измеряет неустранимую неопределённость распределения. Сжать данные сильнее неё нельзя.
Кросс-энтропия (функция потерь, которую вы используете каждый день)
Кросс-энтропия измеряет среднюю неожиданность, когда вы используете распределение Q для кодирования событий, которые в действительности происходят из распределения P.
H(P, Q) = -sum( p(x) * log(q(x)) ) for all x
P — истинное распределение (метки). Q — предсказания вашей модели. Если Q идеально совпадает с P, кросс-энтропия равна энтропии. Любое расхождение делает её больше.
В классификации P — one-hot-вектор (истинный класс имеет вероятность 1, все остальные — 0). Тогда кросс-энтропия упрощается до:
H(P, Q) = -log(q(true_class))
Это вся формула потерь кросс-энтропии для классификации. Максимизируйте предсказанную вероятность правильного класса.
KL-дивергенция (расстояние между распределениями)
KL-дивергенция измеряет, насколько больше неожиданности вы получаете, используя Q вместо P.
D_KL(P || Q) = sum( p(x) * log(p(x) / q(x)) ) for all x
= H(P, Q) - H(P)
Кросс-энтропия — это энтропия плюс KL-дивергенция. Поскольку энтропия истинного распределения постоянна во время обучения, минимизация кросс-энтропии равнозначна минимизации KL-дивергенции. Вы приближаете распределение модели к истинному распределению.
KL-дивергенция несимметрична: D_KL(P || Q) != D_KL(Q || P). Это не настоящая метрика расстояния.
Взаимная информация
Взаимная информация измеряет, сколько знание одной переменной сообщает о другой.
I(X; Y) = H(X) - H(X|Y)
= H(X) + H(Y) - H(X, Y)
Если X и Y независимы, взаимная информация равна нулю. Знание одной переменной ничего не говорит о другой. Если они идеально коррелированы, взаимная информация равна энтропии любой из них.
При отборе признаков высокая взаимная информация между признаком и целью означает, что признак полезен. Низкая взаимная информация означает шум.
Условная энтропия
H(Y|X) измеряет, сколько неопределённости о Y остаётся после наблюдения X.
H(Y|X) = H(X,Y) - H(X)
Два крайних случая:
- Если X полностью определяет Y, то H(Y|X) = 0. Знание X устраняет всю неопределённость о Y. Пример: X = температура в градусах Цельсия, Y = температура в градусах Фаренгейта.
- Если X ничего не сообщает о Y, то H(Y|X) = H(Y). Знание X совсем не уменьшает вашу неопределённость. Пример: X = бросок монеты, Y = погода завтра.
Условная энтропия всегда неотрицательна и никогда не превышает H(Y):
0 <= H(Y|X) <= H(Y)
В машинном обучении условная энтропия встречается в деревьях решений. При каждом разбиении алгоритм выбирает признак X, минимизирующий H(Y|X) — признак, который устраняет наибольшую неопределённость о метке Y.
Совместная энтропия
H(X,Y) — это энтропия совместного распределения X и Y.
H(X,Y) = -sum sum p(x,y) * log(p(x,y)) for all x, y
Ключевое свойство:
H(X,Y) <= H(X) + H(Y)
Равенство выполняется, когда X и Y независимы. Если они разделяют информацию, совместная энтропия меньше суммы отдельных энтропий. «Недостающая» энтропия — это в точности взаимная информация.
Связи:
- H(X,Y) = H(X) + H(Y|X) = H(Y) + H(X|Y)
- I(X;Y) = H(X) - H(X|Y) = H(Y) - H(Y|X)
- H(X,Y) = H(X) + H(Y) - I(X;Y)
Взаимная информация (подробно)
Взаимная информация I(X;Y) количественно определяет, насколько знание одной переменной уменьшает неопределённость о другой.
I(X;Y) = H(X) - H(X|Y)
= H(Y) - H(Y|X)
= H(X) + H(Y) - H(X,Y)
= sum sum p(x,y) * log(p(x,y) / (p(x) * p(y)))
Свойства:
- I(X;Y) >= 0 всегда. Наблюдая что-либо, вы никогда не теряете информацию.
- I(X;Y) = 0 тогда и только тогда, когда X и Y независимы.
- I(X;Y) = I(Y;X). Она симметрична, в отличие от KL-дивергенции.
- I(X;X) = H(X). Переменная разделяет с собой всю свою информацию.
Взаимная информация для отбора признаков. В ML нужны признаки, информативные о целевой переменной. Взаимная информация даёт принципиальный способ ранжировать признаки:
- Для каждого признака X_i вычислите I(X_i; Y), где Y — целевая переменная.
- Отранжируйте признаки по оценке MI.
- Оставьте top k признаков.
Это работает для любой связи между признаком и целью — линейной, нелинейной, монотонной или нет. Корреляция выявляет только линейные связи. MI выявляет всё.
| Метод | Выявляет | Вычислительная стоимость | Обрабатывает категориальные данные? |
|---|---|---|---|
| Корреляция Пирсона | Линейные связи | O(n) | Нет |
| Корреляция Спирмена | Монотонные связи | O(n log n) | Нет |
| Взаимная информация | Любая статистическая зависимость | O(n log n) с биннингом | Да |
Сглаживание меток и кросс-энтропия
Стандартная классификация использует жёсткие цели: [0, 0, 1, 0]. Истинному классу присваивается вероятность 1, всем остальным — 0. Сглаживание меток заменяет их мягкими целями:
soft_target = (1 - epsilon) * hard_target + epsilon / num_classes
При epsilon = 0.1 и 4 классах:
- Жёсткая цель: [0, 0, 1, 0]
- Мягкая цель: [0.025, 0.025, 0.925, 0.025]
С точки зрения теории информации сглаживание меток увеличивает энтропию целевого распределения. Жёсткие one-hot-цели имеют энтропию 0 — неопределённости нет. Мягкие цели имеют положительную энтропию.
Почему это помогает:
- Не даёт модели уводить логиты к экстремальным значениям (для идеального совпадения с one-hot-целью при кросс-энтропии потребовались бы бесконечные логиты)
- Работает как регуляризация: модель не может быть уверена на 100%
- Улучшает калибровку: предсказанные вероятности лучше отражают истинную неопределённость
- Сокращает разрыв между поведением при обучении и инференсе
Потери кросс-энтропии при сглаживании меток становятся:
L = (1 - epsilon) * CE(hard_target, prediction) + epsilon * H_uniform(prediction)
Второй член штрафует предсказания, далёкие от равномерного распределения, — это прямая регуляризация уверенности.
Почему кросс-энтропия — ГЛАВНАЯ функция потерь для классификации
Три взгляда, один вывод.
Взгляд теории информации. Кросс-энтропия измеряет, сколько битов вы тратите впустую, используя распределение модели вместо истинного распределения. Минимизация делает модель наиболее эффективным кодировщиком реальности.
Взгляд максимального правдоподобия. Для N обучающих примеров с истинными классами y_i:
Likelihood = product( q(y_i) )
Log-likelihood = sum( log(q(y_i)) )
Negative log-likelihood = -sum( log(q(y_i)) )
Последняя строка — это потери кросс-энтропии. Минимизация кросс-энтропии = максимизация правдоподобия обучающих данных при вашей модели.
Взгляд градиента. Градиент кросс-энтропии по логитам — это просто (predicted - true). Его легко, стабильно и быстро вычислять. Поэтому она идеально сочетается с softmax.
Биты и наты
Единственное различие — основание логарифма.
log base 2 -> bits (information theory tradition)
log base e -> nats (machine learning convention)
log base 10 -> hartleys (rarely used)
1 нат = 1/ln(2) бита = 1,4427 бита. PyTorch и TensorFlow по умолчанию используют натуральный логарифм (наты).
Перплексия
Перплексия — это экспонента кросс-энтропии. Она показывает эффективное число равновероятных вариантов, между которыми не уверена модель.
Perplexity = 2^H(P,Q) (if using bits)
Perplexity = e^H(P,Q) (if using nats)
Языковая модель с перплексией 50 в среднем настолько же не уверена, как если бы ей приходилось равновероятно выбирать из 50 возможных следующих токенов. Чем ниже, тем лучше.
GPT-2 достигала перплексии ~30 на распространённых бенчмарках. У современных моделей на хорошо представленных доменах значения выражаются однозначными числами.
entropy-kl
Соберите сами
Шаг 1: информационное содержание и энтропия
import math
def information_content(p, base=2):
if p <= 0 or p > 1:
return float('inf') if p <= 0 else 0.0
return -math.log(p) / math.log(base)
def entropy(probs, base=2):
return sum(
p * information_content(p, base)
for p in probs if p > 0
)
fair_coin = [0.5, 0.5]
biased_coin = [0.99, 0.01]
fair_die = [1/6] * 6
print(f"Fair coin entropy: {entropy(fair_coin):.4f} bits")
print(f"Biased coin entropy: {entropy(biased_coin):.4f} bits")
print(f"Fair die entropy: {entropy(fair_die):.4f} bits")
Шаг 2: кросс-энтропия и KL-дивергенция
def cross_entropy(p, q, base=2):
total = 0.0
for pi, qi in zip(p, q):
if pi > 0:
if qi <= 0:
return float('inf')
total += pi * (-math.log(qi) / math.log(base))
return total
def kl_divergence(p, q, base=2):
return cross_entropy(p, q, base) - entropy(p, base)
true_dist = [0.7, 0.2, 0.1]
good_model = [0.6, 0.25, 0.15]
bad_model = [0.1, 0.1, 0.8]
print(f"Entropy of true dist: {entropy(true_dist):.4f} bits")
print(f"CE (good model): {cross_entropy(true_dist, good_model):.4f} bits")
print(f"CE (bad model): {cross_entropy(true_dist, bad_model):.4f} bits")
print(f"KL divergence (good): {kl_divergence(true_dist, good_model):.4f} bits")
print(f"KL divergence (bad): {kl_divergence(true_dist, bad_model):.4f} bits")
Шаг 3: кросс-энтропия как функция потерь классификации
def softmax(logits):
max_logit = max(logits)
exps = [math.exp(z - max_logit) for z in logits]
total = sum(exps)
return [e / total for e in exps]
def cross_entropy_loss(true_class, logits):
probs = softmax(logits)
return -math.log(probs[true_class])
logits = [2.0, 1.0, 0.1]
true_class = 0
probs = softmax(logits)
loss = cross_entropy_loss(true_class, logits)
print(f"Logits: {logits}")
print(f"Softmax: {[f'{p:.4f}' for p in probs]}")
print(f"True class: {true_class}")
print(f"Loss: {loss:.4f} nats")
print(f"Perplexity: {math.exp(loss):.2f}")
Шаг 4: кросс-энтропия равна отрицательному логарифму правдоподобия
import random
random.seed(42)
n_samples = 1000
n_classes = 3
true_labels = [random.randint(0, n_classes - 1) for _ in range(n_samples)]
model_logits = [[random.gauss(0, 1) for _ in range(n_classes)] for _ in range(n_samples)]
ce_loss = sum(
cross_entropy_loss(label, logits)
for label, logits in zip(true_labels, model_logits)
) / n_samples
nll = -sum(
math.log(softmax(logits)[label])
for label, logits in zip(true_labels, model_logits)
) / n_samples
print(f"Cross-entropy loss: {ce_loss:.6f}")
print(f"Negative log-likelihood: {nll:.6f}")
print(f"Difference: {abs(ce_loss - nll):.2e}")
Шаг 5: взаимная информация
def mutual_information(joint_probs, base=2):
rows = len(joint_probs)
cols = len(joint_probs[0])
margin_x = [sum(joint_probs[i][j] for j in range(cols)) for i in range(rows)]
margin_y = [sum(joint_probs[i][j] for i in range(rows)) for j in range(cols)]
mi = 0.0
for i in range(rows):
for j in range(cols):
pxy = joint_probs[i][j]
if pxy > 0:
mi += pxy * math.log(pxy / (margin_x[i] * margin_y[j])) / math.log(base)
return mi
independent = [[0.25, 0.25], [0.25, 0.25]]
dependent = [[0.45, 0.05], [0.05, 0.45]]
print(f"MI (independent): {mutual_information(independent):.4f} bits")
print(f"MI (dependent): {mutual_information(dependent):.4f} bits")
Примените
Те же понятия с использованием NumPy — так, как вы будете применять их на практике:
import numpy as np
def np_entropy(p):
p = np.asarray(p, dtype=float)
mask = p > 0
result = np.zeros_like(p)
result[mask] = p[mask] * np.log(p[mask])
return -result.sum()
def np_cross_entropy(p, q):
p, q = np.asarray(p, dtype=float), np.asarray(q, dtype=float)
mask = p > 0
return -(p[mask] * np.log(q[mask])).sum()
def np_kl_divergence(p, q):
return np_cross_entropy(p, q) - np_entropy(p)
true = np.array([0.7, 0.2, 0.1])
pred = np.array([0.6, 0.25, 0.15])
print(f"Entropy: {np_entropy(true):.4f} nats")
print(f"Cross-ent: {np_cross_entropy(true, pred):.4f} nats")
print(f"KL div: {np_kl_divergence(true, pred):.4f} nats")
Вы собрали с нуля то, что torch.nn.CrossEntropyLoss() делает внутри. Теперь вы знаете, почему потери снижаются во время обучения: предсказанное распределение вашей модели приближается к истинному распределению, измеренному в натах потраченной впустую информации.
Упражнения
-
Вычислите энтропию английского алфавита, предполагая равномерное распределение (26 букв). Затем оцените её, используя фактические частоты букв. Какая величина выше и почему?
-
Модель выдаёт логиты [5.0, 2.0, 0.5] для примера с истинным классом 1. Вычислите потери кросс-энтропии вручную, затем проверьте их своей функцией
cross_entropy_loss. Какие логиты дали бы нулевые потери? -
Покажите, что KL-дивергенция несимметрична. Выберите два распределения P и Q и вычислите D_KL(P || Q) и D_KL(Q || P). Объясните, почему они различаются.
-
Создайте функцию, вычисляющую перплексию для последовательности предсказаний токенов. Получив список пар (true_token_index, predicted_logits), верните перплексию последовательности.
Ключевые термины
| Термин | Как обычно говорят | Что это на самом деле означает |
|---|---|---|
| Информационное содержание | «Неожиданность» | Число битов (или натов), необходимое для кодирования события: -log(p) |
| Энтропия | «Случайность» | Средняя неожиданность по всем исходам распределения. Измеряет неустранимую неопределённость. |
| Кросс-энтропия | «Функция потерь» | Средняя неожиданность при использовании распределения модели Q для кодирования событий из истинного распределения P. |
| KL-дивергенция | «Расстояние между распределениями» | Дополнительные биты, потраченные впустую при использовании Q вместо P. Равна кросс-энтропии минус энтропия. Несимметрична. |
| Взаимная информация | «Насколько связаны X и Y» | Уменьшение неопределённости о X благодаря знанию Y. Ноль означает независимость. |
| Softmax | «Превращает логиты в вероятности» | Возводит в экспоненту и нормализует. Отображает любой вещественный вектор в корректное распределение вероятностей. |
| Перплексия | «Насколько модель запутана» | Экспонента кросс-энтропии. Эффективный размер словаря, из которого модель выбирает на каждом шаге. |
| Биты | «Единица Шеннона» | Информация, измеренная логарифмом по основанию 2. Один бит разрешает один честный бросок монеты. |
| Наты | «Единица ML» | Информация, измеренная натуральным логарифмом. По умолчанию используется PyTorch и TensorFlow. |
| Отрицательный логарифм правдоподобия | «Потери NLL» | Идентичен потерям кросс-энтропии для one-hot-меток. Его минимизация максимизирует вероятность правильных предсказаний. |
Дополнительные материалы
- Shannon 1948: A Mathematical Theory of Communication — исходная статья, которую до сих пор можно читать
- Visual Information Theory (Chris Olah) — лучшее визуальное объяснение энтропии и KL-дивергенции
- PyTorch CrossEntropyLoss docs — как фреймворк реализует то, что вы только что собрали
Источник: Information Theory — оригинал Навигация: назад: 01.08 — Оптимизация: семейство градиентного спуска · Фаза 1 — Математические основы · Полный каталог · следующая статья: 01.10 — Понижение размерности: PCA, t-SNE, UMAP.