VKВлад КуликовОткрытые лекции
Теория информации для машинного обучения3 / 16

Модуль 3. Кросс-энтропия и KL-дивергенция

Сначала попробуйте решить упражнение. Ответы, подробные решения и проектные ориентиры раскрываются отдельно и сохраняют своё состояние при переключении RU/EN.

Основной маршрут

Скачать файлы курса (.zip) 33,9 КБ

Архив содержит девять Python-файлов решений, общих для RU/EN, с сохранением структуры каталогов.

Упражнение 1. Нулевая вероятность и сглаживание

Пусть

P=(0.9,0.1),Q=(1,0).P=(0.9,0.1),\qquad Q=(1,0).

  1. Найдите H(P,Q)H(P,Q) и DKL(P∥Q)D_{\mathrm{KL}}(P\|Q).

  2. Замените QQ на Qε=(1−ε,ε)Q_\varepsilon=(1-\varepsilon,\varepsilon), где 0<ε<10<\varepsilon<1, и выпишите обе величины.

  3. Найдите ведущий рост при ε→0+\varepsilon\to0^+.

  4. Объясните, почему численное ограничение вероятностей снизу (clipping) устраняет inf, но одновременно меняет оцениваемое распределение.

Показать ответ и решение

Пусть

P=(0.9,0.1),Q=(1,0).P=(0.9,0.1),\qquad Q=(1,0).

1. Несовпадение поддержек

Кросс-энтропия в битах равна

H2(P,Q)=−0.9log⁡21−0.1log⁡20=+∞.H_2(P,Q) =-0.9\log_2 1-0.1\log_2 0 =+\infty.

Соответственно,

DKL,2(P∥Q)=+∞,D_{\mathrm{KL},2}(P\|Q)=+\infty,

потому что PP допускает второй исход, а QQ объявляет его невозможным.

2. ε\varepsilon-сглаживание

Для

Qε=(1−ε,ε)Q_\varepsilon=(1-\varepsilon,\varepsilon)

получаем

H2(P,Qε)=−0.9log⁡2(1−ε)−0.1log⁡2ε.H_2(P,Q_\varepsilon) =-0.9\log_2(1-\varepsilon) -0.1\log_2\varepsilon.

Энтропия PP равна

H2(P)=−0.9log⁡20.9−0.1log⁡20.1.H_2(P) =-0.9\log_2 0.9-0.1\log_2 0.1.

Поэтому

DKL,2(P∥Qε)=0.9log⁡20.91−ε+0.1log⁡20.1ε=H2(P,Qε)−H2(P).\begin{aligned} D_{\mathrm{KL},2}(P\|Q_\varepsilon) &=0.9\log_2\frac{0.9}{1-\varepsilon} +0.1\log_2\frac{0.1}{\varepsilon}\\ &=H_2(P,Q_\varepsilon)-H_2(P). \end{aligned}

3. Асимптотика при ε→0+\varepsilon\to0^+

Поскольку

−log⁡2(1−ε)=O(ε),-\log_2(1-\varepsilon)=O(\varepsilon),

главный расходящийся член равен

H2(P,Qε)=0.1log⁡21ε+O(1).H_2(P,Q_\varepsilon) =0.1\log_2\frac1\varepsilon+O(1).

Для KL коэффициент тот же:

DKL,2(P∥Qε)=0.1log⁡21ε+O(1).D_{\mathrm{KL},2}(P\|Q_\varepsilon) =0.1\log_2\frac1\varepsilon+O(1).

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

4. Что делает ограничение вероятностей (clipping)

Замена

Python
q = np.clip(q, eps, 1.0)
q = q / q.sum()

создаёт некоторое новое сглаженное распределение Q~ε\widetilde Q_\varepsilon (для показанного кода это не буквально (1−ε,ε)(1-\varepsilon,\varepsilon)). Это может быть разумной защитой численной реализации, но функция потерь после такого ограничения — уже логарифмическая потеря сглаженной модели, а не исходного QQ. Если ограничение применяется только внутри логарифма без повторной нормировки, объект вообще может перестать быть корректным вероятностным распределением.

На практике лучше вычислять логарифмы вероятностей из логитов через устойчивый log_softmax: конечные логиты автоматически дают строго положительные вероятности softmax и не требуют ручного ограничения.

Упражнение 2. Градиент кросс-энтропии после softmax

Для

qj=ezj∑kezk,ℓ(z;r)=−∑jrjlog⁡qjq_j=\frac{e^{z_j}}{\sum_k e^{z_k}}, \qquad \ell(z;r)=-\sum_j r_j\log q_j

выведите

∂ℓ∂zj=qj−rj\frac{\partial\ell}{\partial z_j}=q_j-r_j

и

∇z2ℓ=diag⁡(q)−qq⊤.\nabla_z^2\ell =\operatorname{diag}(q)-qq^\top.

Покажите, что матрица Гессе положительно полуопределена, и объясните, почему это не делает глубокую нейросеть выпуклой по её весам.

Показать ответ и решение

Пусть

qj=ezj∑kezk,ℓ(z;r)=−∑jrjlog⁡qj,∑jrj=1.q_j=\frac{e^{z_j}}{\sum_ke^{z_k}}, \qquad \ell(z;r)=-\sum_jr_j\log q_j, \qquad \sum_jr_j=1.

Используем

log⁡qj=zj−log⁡∑kezk.\log q_j=z_j-\log\sum_ke^{z_k}.

Тогда

ℓ(z;r)=−∑jrjzj+(∑jrj)log⁡∑kezk=−r⊤z+log⁡∑kezk.\begin{aligned} \ell(z;r) &=-\sum_jr_jz_j +\left(\sum_jr_j\right)\log\sum_ke^{z_k}\\ &=-r^\top z+\log\sum_ke^{z_k}. \end{aligned}

Дифференцируем по zmz_m:

∂ℓ∂zm=−rm+ezm∑kezk=qm−rm.\frac{\partial\ell}{\partial z_m} =-r_m+\frac{e^{z_m}}{\sum_ke^{z_k}} =q_m-r_m.

Следовательно,

∇zℓ=q−r.\boxed{\nabla_z\ell=q-r}.

Для целевого one-hot-вектора r=eyr=e_y:

∇zℓ=q−ey.\nabla_z\ell=q-e_y.

Матрица Гессе (Hessian)

Из стандартной производной softmax

∂qi∂zj=qi(δij−qj)\frac{\partial q_i}{\partial z_j} =q_i(\delta_{ij}-q_j)

получаем

∇z2ℓ=diag⁡(q)−qq⊤.\nabla_z^2\ell =\operatorname{diag}(q)-qq^\top.

Для любого v∈RKv\in\mathbb R^K

v⊤∇z2ℓ v=∑jqjvj2−(∑jqjvj)2=Var⁡J∼q(vJ)≥0.\begin{aligned} v^\top\nabla_z^2\ell\,v &=\sum_jq_jv_j^2-\left(\sum_jq_jv_j\right)^2\\ &=\operatorname{Var}_{J\sim q}(v_J)\\ &\ge0. \end{aligned}

Значит, матрица Гессе положительно полуопределена, и кросс-энтропия выпукла по логитам.

Нулевое направление — добавление одной и той же константы ко всем логитам: softmax не меняется при z↦z+c1z\mapsto z+c\mathbf1. Но логиты нелинейно зависят от весов глубокой сети, поэтому композиция функции потерь с сетью в общем случае невыпукла по параметрам.

Упражнение 3. Условная KL для LLM

Выведите

EP[−log⁡qθ(Y∣C)]=HP(Y∣C)+EC∼PDKL(P(⋅∣C)∥Qθ(⋅∣C)).\mathbb E_P[-\log q_\theta(Y\mid C)] =H_P(Y\mid C) +\mathbb E_{C\sim P} D_{\mathrm{KL}} \bigl(P(\cdot\mid C)\|Q_\theta(\cdot\mid C)\bigr).

Затем докажите цепное правило

DKL(P1:T∥Q1:T)=∑t=1TEP(X<t)DKL(P(⋅∣X<t)∥Q(⋅∣X<t)).D_{\mathrm{KL}}(P_{1:T}\|Q_{1:T}) = \sum_{t=1}^T \mathbb E_{P(X_{<t})} D_{\mathrm{KL}} \bigl(P(\cdot\mid X_{<t})\|Q(\cdot\mid X_{<t})\bigr).

В двух-трёх предложениях объясните, почему валидационная потеря при teacher forcing не полностью характеризует поведение модели на собственных сгенерированных префиксах.

Факультативное математическое углубление

Показать ответ и решение

1. Разложение условной кросс-энтропии

Начнём с

L(Q)=EC,Y∼P[−log⁡q(Y∣C)].\mathcal L(Q) =\mathbb E_{C,Y\sim P}[-\log q(Y\mid C)].

Добавим и вычтем log⁡p(Y∣C)\log p(Y\mid C):

L(Q)=EP[−log⁡p(Y∣C)]+EP[log⁡p(Y∣C)q(Y∣C)].\begin{aligned} \mathcal L(Q) &=\mathbb E_P[-\log p(Y\mid C)]\\ &\quad+ \mathbb E_P\left[ \log\frac{p(Y\mid C)}{q(Y\mid C)} \right]. \end{aligned}

Первое слагаемое — условная энтропия:

EP[−log⁡p(Y∣C)]=HP(Y∣C).\mathbb E_P[-\log p(Y\mid C)]=H_P(Y\mid C).

Во втором сначала зафиксируем C=cC=c:

EY∼P(⋅∣c)[log⁡p(Y∣c)q(Y∣c)]=DKL(P(⋅∣c)∥Q(⋅∣c)).\mathbb E_{Y\sim P(\cdot\mid c)} \left[ \log\frac{p(Y\mid c)}{q(Y\mid c)} \right] =D_{\mathrm{KL}}\bigl(P(\cdot\mid c)\|Q(\cdot\mid c)\bigr).

После усреднения по CC:

L(Q)=HP(Y∣C)+EC∼PDKL(P(⋅∣C)∥Q(⋅∣C)).\boxed{ \mathcal L(Q) =H_P(Y\mid C) +\mathbb E_{C\sim P} D_{\mathrm{KL}} \bigl(P(\cdot\mid C)\|Q(\cdot\mid C)\bigr) }.

2. Цепное правило для авторегрессионной KL-дивергенции

Факторизуем оба распределения:

p(x1:T)=∏t=1Tp(xt∣x<t),q(x1:T)=∏t=1Tq(xt∣x<t).p(x_{1:T})=\prod_{t=1}^Tp(x_t\mid x_{<t}), \qquad q(x_{1:T})=\prod_{t=1}^Tq(x_t\mid x_{<t}).

Тогда

log⁡p(x1:T)q(x1:T)=∑t=1Tlog⁡p(xt∣x<t)q(xt∣x<t).\log\frac{p(x_{1:T})}{q(x_{1:T})} =\sum_{t=1}^T \log\frac{p(x_t\mid x_{<t})}{q(x_t\mid x_{<t})}.

Усредняем по PP и меняем местами конечную сумму и ожидание:

DKL(P1:T∥Q1:T)=∑t=1TEX1:T∼P[log⁡p(Xt∣X<t)q(Xt∣X<t)]=∑t=1TEX<t∼PDKL(P(⋅∣X<t)∥Q(⋅∣X<t)).\begin{aligned} D_{\mathrm{KL}}(P_{1:T}\|Q_{1:T}) &=\sum_{t=1}^T \mathbb E_{X_{1:T}\sim P} \left[ \log\frac{p(X_t\mid X_{<t})}{q(X_t\mid X_{<t})} \right]\\ &=\sum_{t=1}^T \mathbb E_{X_{<t}\sim P} D_{\mathrm{KL}} \bigl(P(\cdot\mid X_{<t})\|Q(\cdot\mid X_{<t})\bigr). \end{aligned}

Последнее равенство — закон полного ожидания: при фиксированном префиксе внутреннее ожидание берётся по истинному распределению следующего токена.

3. Почему loss с teacher forcing не описывает всё поведение при генерации

При teacher forcing префиксы распределены как P(X<t)P(X_{<t}): это реальные префиксы из набора данных. При свободной генерации распределение префиксов определяется уже QQ. Раннее отклонение меняет последующие контексты, и модель может попасть в область, редко представленную при валидации с teacher forcing. Поэтому хороший NLL на отложенных данных необходим для качественной вероятностной модели на распределении данных, но не является полной оценкой свободной авторегрессионной генерации.

Упражнение 4. Прямая и обратная KL для ограниченной модели

Для

p(x)=12N(x;−3,1)+12N(x;3,1)p(x)=\tfrac12\mathcal N(x;-3,1) +\tfrac12\mathcal N(x;3,1)

и qμ,σ(x)=N(x;μ,σ2)q_{\mu,\sigma}(x)=\mathcal N(x;\mu,\sigma^2):

  1. аналитически найдите оптимум прямой KL;

  2. численно минимизируйте обратную KL при помощи квадратуры Гаусса—Эрмита;

  3. запустите оптимизацию из нескольких начальных точек и найдите оба минимума около отдельных мод и широкий центральный локальный минимум;

  4. нарисуйте pp и три характерных qq.

Не дискретизируйте плотности как ненормированные массивы и не подменяйте нули произвольным ограничением снизу (clipping) внутри целевой функции: это может создать другую задачу оптимизации.

Показать ответ и решение

Пусть

p(x)=12N(x;−3,1)+12N(x;3,1),p(x)=\tfrac12\mathcal N(x;-3,1)+\tfrac12\mathcal N(x;3,1),

а

qμ,σ(x)=N(x;μ,σ2).q_{\mu,\sigma}(x)=\mathcal N(x;\mu,\sigma^2).

1. Аналитический оптимум прямой KL

При фиксированном PP минимизация DKL(P∥Q)D_{\mathrm{KL}}(P\|Q) эквивалентна минимизации кросс-энтропии

−EP[log⁡qμ,σ(X)].-\mathbb E_P[\log q_{\mu,\sigma}(X)].

Для гауссиана

−log⁡qμ,σ(x)=12log⁡(2πσ2)+(x−μ)22σ2.-\log q_{\mu,\sigma}(x) =\frac12\log(2\pi\sigma^2) +\frac{(x-\mu)^2}{2\sigma^2}.

Следовательно, с точностью до константы нужно минимизировать

J(μ,σ2)=12log⁡σ2+EP[(X−μ)2]2σ2.J(\mu,\sigma^2) =\frac12\log\sigma^2 +\frac{\mathbb E_P[(X-\mu)^2]}{2\sigma^2}.

По μ\mu минимум достигается при

μ∗=EP[X]=0.\mu^*=\mathbb E_P[X]=0.

После этого

(σ∗)2=Var⁡P(X).(\sigma^*)^2=\operatorname{Var}_P(X).

Для симметричной смеси

Var⁡P(X)=E[Var⁡(X∣Z)]+Var⁡(E[X∣Z])=1+9=10.\operatorname{Var}_P(X) =\mathbb E[\operatorname{Var}(X\mid Z)] +\operatorname{Var}(\mathbb E[X\mid Z]) =1+9=10.

Итак,

μfwd=0,σfwd=10≈3.1623.\boxed{ \mu_{\mathrm{fwd}}=0, \qquad \sigma_{\mathrm{fwd}}=\sqrt{10}\approx3.1623 }.

2. Обратная KL через квадратуру Гаусса—Эрмита

Для обратной KL удобно выполнить репараметризацию

X=μ+σZ,Z∼N(0,1),X=\mu+\sigma Z,\qquad Z\sim\mathcal N(0,1),

и считать

DKL(Q∥P)=EZ[log⁡q(μ+σZ)−log⁡p(μ+σZ)].D_{\mathrm{KL}}(Q\|P) =\mathbb E_Z[\log q(\mu+\sigma Z)-\log p(\mu+\sigma Z)].

Ниже используется roots_hermitenorm, чьи узлы и веса непосредственно подходят для интегралов с весом e−z2/2e^{-z^2/2}.

Python
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import minimize
from scipy.special import logsumexp, roots_hermitenorm
from scipy.stats import norm

LOG_2PI = np.log(2.0 * np.pi)

# E[f(Z)] for Z ~ N(0, 1)
z_nodes, z_weights = roots_hermitenorm(800)
z_weights = z_weights / np.sqrt(2.0 * np.pi)


def log_p(x: np.ndarray) -> np.ndarray:
    """Log-density of 0.5 N(-3,1) + 0.5 N(3,1)."""
    components = np.stack([
        np.log(0.5) - 0.5 * (x + 3.0) ** 2 - 0.5 * LOG_2PI,
        np.log(0.5) - 0.5 * (x - 3.0) ** 2 - 0.5 * LOG_2PI,
    ])
    return logsumexp(components, axis=0)


def reverse_kl(params: np.ndarray) -> float:
    mu, log_sigma = params
    sigma = np.exp(log_sigma)

    # Reparameterization X = mu + sigma Z, Z ~ N(0,1)
    x = mu + sigma * z_nodes
    log_q = -0.5 * z_nodes**2 - log_sigma - 0.5 * LOG_2PI

    return float(np.dot(z_weights, log_q - log_p(x)))


starts = [
    (-4.0, 0.0),
    (-2.0, 0.0),
    (0.0, 0.0),
    (2.0, 0.0),
    (4.0, 0.0),
    (0.0, 1.0),
]

solutions = []
for start in starts:
    result = minimize(
        reverse_kl,
        x0=np.array(start),
        method="L-BFGS-B",
        bounds=[(-8.0, 8.0), (-4.0, 3.0)],
        options={"ftol": 1e-13, "gtol": 1e-9, "maxiter": 2000},
    )
    if not result.success:
        raise RuntimeError(result.message)

    mu, log_sigma = result.x
    solutions.append((start, mu, np.exp(log_sigma), result.fun))

for start, mu, sigma, value in solutions:
    print(
        f"start={start!s:>12} -> "
        f"mu={mu:+.6f}, sigma={sigma:.6f}, reverse_KL={value:.6f}"
    )

Типичный вывод:

text
start= (-4.0, 0.0) -> mu=-2.984306, sigma=1.023369, reverse_KL=0.688769
start= (-2.0, 0.0) -> mu=-2.984306, sigma=1.023369, reverse_KL=0.688769
start=  (0.0, 0.0) -> mu=+0.000000, sigma=2.7438,   reverse_KL=0.8406
start=  (2.0, 0.0) -> mu=+2.984306, sigma=1.023369, reverse_KL=0.688769
start=  (4.0, 0.0) -> mu=+2.984306, sigma=1.023369, reverse_KL=0.688769
start=  (0.0, 1.0) -> mu=+0.000000, sigma=2.7438,   reverse_KL=0.8406

Последние цифры центрального решения слегка меняются с порядком квадратуры, потому что это численный интеграл. Симметричные минимумы около ±2.9843\pm2.9843 устойчивы и имеют меньшее значение целевой функции.

3. Визуализация

Python
x = np.linspace(-7.0, 7.0, 2000)
p = 0.5 * norm.pdf(x, -3.0, 1.0) + 0.5 * norm.pdf(x, 3.0, 1.0)

q_forward = norm.pdf(x, 0.0, np.sqrt(10.0))
q_reverse_right = norm.pdf(x, 2.984306, 1.023369)
q_reverse_center = norm.pdf(x, 0.0, 2.7438)

plt.figure(figsize=(10, 5))
plt.plot(x, p, linewidth=2.5, label="target mixture p")
plt.plot(x, q_forward, linewidth=2, label="forward KL optimum")
plt.plot(x, q_reverse_right, linewidth=2, label="reverse KL global optimum")
plt.plot(
    x,
    q_reverse_center,
    linewidth=2,
    linestyle="--",
    label="reverse KL central local optimum",
)
plt.xlabel("x")
plt.ylabel("density")
plt.legend()
plt.grid(alpha=0.25)
plt.show()

4. Что именно показывает пример

Прямая KL в выбранном гауссовском семействе совпадает с целевым распределением по среднему и дисперсии и потому «накрывает» обе моды. Обратная KL имеет два более выгодных решения возле отдельных мод и менее выгодное широкое центральное решение. Это сильная иллюстрация асимметрии, но не универсальный закон о каждой модели и каждом оптимизаторе.

Инженерный бонус

Аудит функции потерь в PyTorch. На небольшом батче проверьте, что:

  1. F.cross_entropy(logits, target) совпадает с F.nll_loss(F.log_softmax(logits, dim=-1), target);

  2. autograd даёт градиент q−eyq-e_y с учётом выбранной редукции;

  3. целевые распределения вероятностей реализуют кросс-энтропию с мягкими метками;

  4. label_smoothing, weight, ignore_index и reduction меняют смысл усреднённого числа.

Сформулируйте, какую именно функцию потерь допустимо экспоненцировать для отчёта о токенной перплексии.

Показать ответ и решение
Python
import torch
import torch.nn.functional as F


torch.manual_seed(7)
logits = torch.randn(4, 5, dtype=torch.float64, requires_grad=True)
target = torch.tensor([0, 3, 1, 4])

# 1. Fused CE == log_softmax + NLL for class-index targets
loss_fused = F.cross_entropy(logits, target, reduction="mean")
loss_manual = F.nll_loss(
    F.log_softmax(logits, dim=-1),
    target,
    reduction="mean",
)
torch.testing.assert_close(loss_fused, loss_manual)

# 2. Per-example gradient is (q - one_hot) / batch_size for mean reduction
loss_fused.backward()
q = logits.detach().softmax(dim=-1)
one_hot = F.one_hot(target, num_classes=5).to(torch.float64)
expected_grad = (q - one_hot) / logits.shape[0]
torch.testing.assert_close(logits.grad, expected_grad)

# 3. Soft-label cross-entropy
soft_target = 0.9 * one_hot + 0.1 / logits.shape[-1]
loss_soft_fused = F.cross_entropy(
    logits.detach(),
    soft_target,
    reduction="mean",
)
loss_soft_manual = -(
    soft_target * F.log_softmax(logits.detach(), dim=-1)
).sum(dim=-1).mean()
torch.testing.assert_close(loss_soft_fused, loss_soft_manual)

# 4. Label smoothing changes the target distribution and the loss
loss_smooth = F.cross_entropy(
    logits.detach(),
    target,
    label_smoothing=0.1,
)
print("ordinary CE:", float(loss_fused.detach()))
print("smoothed CE:", float(loss_smooth))

Для токенной перплексии нужно экспоненцировать средний обычный NLL по целевым токенам, измеренный в натах:

PPL⁡=emean token NLL.\operatorname{PPL}=e^{\text{mean token NLL}}.

В среднее должны входить ровно нужные немаскированные целевые токены, каждый с обычным весом. Функция потерь со сглаживанием меток (label smoothing), весами классов, произвольным перевзвешиванием примеров или другой нормировкой может быть полезна для обучения, но её экспонента не является стандартной перплексией.