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

Модуль 14. Компьютерное зрение через теорию информации

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

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

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

Упражнение 1. Цена служебной информации

Пусть каждый блок содержит n=8n=8 бинарных символов. Скрытый режим ZZ равновероятен:

P(Z=0)=P(Z=1)=1/2.P(Z=0)=P(Z=1)=1/2.

Условно на ZZ символы независимы:

P(Yi=1∣Z=0)=0.05,P(Yi=1∣Z=1)=0.5.P(Y_i=1\mid Z=0)=0.05, \qquad P(Y_i=1\mid Z=1)=0.5.
  1. Найдите маргинальную вероятность P(Yi=1)P(Y_i=1).

  2. Посчитайте rate факторизованной модели 8H(Yi)8H(Y_i).

  3. Посчитайте иерархический rate H(Z)+8H(Yi∣Z)H(Z)+8H(Y_i\mid Z).

  4. Найдите точную H(Y8)H(Y^8) перебором 256 строк.

  5. Объясните, почему иерархический кодер лучше факторизованной модели, но хуже идеального совместного кодера.

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

Обозначим бинарную энтропию через

h2(p)=−plog⁡2p−(1−p)log⁡2(1−p).h_2(p)=-p\log_2p-(1-p)\log_2(1-p).

Маргинальная вероятность единицы:

P(Yi=1)=12⋅0.05+12⋅0.5=0.275.P(Y_i=1) = \frac12\cdot0.05+\frac12\cdot0.5 =0.275.

Поэтому факторизованная модель платит

Rfact=8h2(0.275)≈8⋅0.848548178=6.788385426R_{\mathrm{fact}} =8h_2(0.275) \approx 8\cdot0.848548178 = 6.788385426

бит на блок.

Условная энтропия одного символа:

H(Yi∣Z)=12h2(0.05)+12h2(0.5)≈0.643198479.H(Y_i\mid Z) = \frac12h_2(0.05)+\frac12h_2(0.5) \approx0.643198479.

Иерархический rate:

Rhier=H(Z)+8H(Yi∣Z)=1+8⋅0.643198479≈6.145587828.R_{\mathrm{hier}} =H(Z)+8H(Y_i\mid Z) =1+8\cdot0.643198479 \approx6.145587828.

Чтобы найти точную энтропию блока, для строки с kk единицами используем

pk=12(0.05)k(0.95)8−k+12(0.5)8.p_k = \frac12(0.05)^k(0.95)^{8-k} + \frac12(0.5)^8.

Таких строк (8k){8\choose k}, поэтому

H(Y8)=−∑k=08(8k)pklog⁡2pk≈5.961948275.H(Y^8) = - \sum_{k=0}^{8} {8\choose k}p_k\log_2p_k \approx5.961948275.

Итак,

Rfact>Rhier>H(Y8).R_{\mathrm{fact}} > R_{\mathrm{hier}} > H(Y^8).

Выигрыш иерархической модели над факторизованной:

6.788385426−6.145587828≈0.642797598 бита.6.788385426-6.145587828 \approx0.642797598\text{ бита}.

Её проигрыш идеальному совместному кодеру:

6.145587828−5.961948275≈0.183639554 бита.6.145587828-5.961948275 \approx0.183639554\text{ бита}.

Последняя величина равна H(Z∣Y8)H(Z\mid Y^8): после наблюдения блока режим почти, но не совершенно, восстанавливается. Hyperprior помогает ограниченной факторизации, но не может превзойти идеальное моделирование полного совместного распределения.

Упражнение 2. Гауссовское зашумление и I–MMSE

Пусть

X∼N(0,2),Yγ=γX+N.X\sim\mathcal N(0,2), \qquad Y_\gamma=\sqrt\gamma X+N.
  1. Найдите I(X;Yγ)I(X;Y_\gamma).

  2. Найдите mmse⁡(γ)\operatorname{mmse}(\gamma).

  3. Проверьте тождество I–MMSE.

  4. Для Xt=0.8X+0.6εX_t=0.8X+0.6\varepsilon выведите апостериорное среднее и score.

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

Пусть σX2=2\sigma_X^2=2. Для гауссовского канала

Yγ=γX+NY_\gamma=\sqrt\gamma X+N

взаимная информация в натах:

I(X;Yγ)=12ln⁡(1+2γ).\boxed{ I(X;Y_\gamma) = \frac12\ln(1+2\gamma) }.

Апостериорная дисперсия, а значит и MMSE:

mmse⁡(γ)=21+2γ.\boxed{ \operatorname{mmse}(\gamma) = \frac{2}{1+2\gamma} }.

Дифференцируем:

dIdγ=11+2γ=12mmse⁡(γ).\frac{dI}{d\gamma} = \frac{1}{1+2\gamma} = \frac12\operatorname{mmse}(\gamma).

Для

Xt=0.8X+0.6εX_t=0.8X+0.6\varepsilon

дисперсия наблюдения:

Var⁡(Xt)=0.82⋅2+0.62=1.64.\operatorname{Var}(X_t) =0.8^2\cdot2+0.6^2 =1.64.

Апостериорное среднее:

E[X∣Xt=xt]=0.8⋅21.64xt=4041xt≈0.97561xt.\mathbb E[X\mid X_t=x_t] = \frac{0.8\cdot2}{1.64}x_t = \frac{40}{41}x_t \approx0.97561x_t.

Score маргинального XtX_t:

∇xtlog⁡pt(xt)=−xt1.64=−2541xt.\boxed{ \nabla_{x_t}\log p_t(x_t) =-\frac{x_t}{1.64} =-\frac{25}{41}x_t }.

Проверка тождества denoiser–score:

0.8 E[X∣xt]−xt0.62=−xt1.64.\frac{0.8\,\mathbb E[X\mid x_t]-x_t}{0.6^2} = -\frac{x_t}{1.64}.

Упражнение 3. Перцептивное качество при нулевом rate

Пусть X∼Bernoulli⁡(0.1)X\sim\operatorname{Bernoulli}(0.1), мера искажения — расстояние Хэмминга.

Сравните два декодера с нулевым rate:

  1. всегда выдавать X^=0\widehat X=0;

  2. независимо сэмплировать X^∼Bernoulli⁡(0.1)\widehat X\sim\operatorname{Bernoulli}(0.1).

Для каждого найдите:

  • ожидаемое искажение;
  • маргинальное распределение реконструкций;
  • выполняется ли perfect perception PX^=PXP_{\widehat X}=P_X.

Объясните цену ограничения на перцептивное качество.

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

Декодер 1: всегда X^=0\widehat X=0

Расстояние Хэмминга равно единице только при X=1X=1, поэтому

D1=P(X=1)=0.1.D_1=P(X=1)=0.1.

Но

PX^=δ0≠Bernoulli⁡(0.1),P_{\widehat X}=\delta_0 \ne \operatorname{Bernoulli}(0.1),

поэтому perfect perception не выполняется.

Декодер 2: независимая выборка из правильного маргинального распределения

Теперь

X^∼Bernoulli⁡(0.1)\widehat X\sim\operatorname{Bernoulli}(0.1)

и

PX^=PX.P_{\widehat X}=P_X.

Точное совпадение распределений (perfect perception) выполняется, но независимость даёт

D2=P(X≠X^)=P(X=1,X^=0)+P(X=0,X^=1)=0.1⋅0.9+0.9⋅0.1=0.18.\begin{aligned} D_2 &=P(X\ne\widehat X)\\ &=P(X=1,\widehat X=0) +P(X=0,\widehat X=1)\\ &=0.1\cdot0.9+0.9\cdot0.1\\ &=0.18. \end{aligned}

Цена ограничения на перцептивное качество в этом игрушечном примере:

0.18−0.1=0.080.18-0.1=0.08

дополнительного ожидаемого искажения.

Упражнение 4. Что означает граница InfoNCE

Для размера батча K=1024K=1024 ожидаемая функция потерь InfoNCE равна 2.12.1 ната.

  1. Посчитайте границу log⁡K−L\log K-\mathcal L.

  2. Почему это не точная MI?

  3. Почему увеличение KK не гарантирует улучшения качества в целевой задаче?

  4. Почему эта формула не делает CLIP строгим Information Bottleneck?

Показать ответ и решение
log⁡K=log⁡1024=10log⁡2≈6.931471806.\log K = \log1024 =10\log2 \approx6.931471806.

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

I(ZI;ZT)≥6.931471806−2.1≈4.831471806 ната.\boxed{ I(Z_I;Z_T) \ge 6.931471806-2.1 \approx4.831471806\text{ ната} }.

Это около

4.831471806ln⁡2≈6.970 бита.\frac{4.831471806}{\ln2} \approx6.970\text{ бита}.

Граница не равна истинной MI, потому что:

  • класс критиков может быть ограниченным;
  • отрицательные примеры должны соответствовать заданной схеме выборки;
  • эмпирическая функция потерь на батче является случайной оценкой ожидаемой целевой функции;
  • нижняя граница насыщается сверху числом log⁡K\log K.

Рост KK увеличивает потолок границы и число отрицательных примеров, но не гарантирует лучшее представление. Возможны ложноотрицательные пары и дополнительные затраты на оптимизацию и изменение геометрии задачи.

CLIP не является строгим IB, потому что в целевой функции отсутствует явный штраф на I(XI;ZI)I(X_I;Z_I): модель не минимизирует количество информации, сохранённой визуальным энкодером.

Упражнение 5. Аугментация уничтожает информацию о целевой переменной

Пусть изображение кодируется парой

X=(Y,N),X=(Y,N),

где Y∈{0,1}Y\in\{0,1\} — цветовая метка, а NN — независимая форма. Аугментация переводит изображение в оттенки серого и полностью удаляет YY.

  1. Покажите, что I(Y;V)=0I(Y;V)=0.

  2. Докажите, что для любого энкодера Z=f(V)Z=f(V):

    I(Y;Z)=0.I(Y;Z)=0.
  3. Объясните, почему сильная инвариантность может ухудшить перенос на целевую задачу.

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

По условию версия в оттенках серого (grayscale) VV полностью не зависит от цветовой метки YY. Поэтому

PY,V=PYPVP_{Y,V}=P_YP_V

и

I(Y;V)=0.I(Y;V)=0.

Для любого энкодера Z=f(V)Z=f(V) имеем марковскую цепь

Y⟶V⟶Z.Y\longrightarrow V\longrightarrow Z.

По DPI:

I(Y;Z)≤I(Y;V)=0.I(Y;Z) \le I(Y;V) =0.

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

I(Y;Z)=0.\boxed{I(Y;Z)=0}.

Сильная инвариантность полезна лишь тогда, когда удаляемое свойство действительно является нерелевантным фактором (nuisance) для будущей задачи. Если целевая задача требует цвет, перевод в оттенки серого уничтожает необходимый сигнал ещё до энкодера.

Упражнение 6. Реальный rate квантизованной модели

Используется group-wise INT4 с полными группами по 128 весов и одним 16-битным scale на каждую группу.

  1. Найдите эффективную разрядность на исходный вес без учёта выравнивания.

  2. Сравните с плотным INT8.

  3. Рассмотрите 50% неструктурированную разреженность: каждый ненулевой вес хранится как INT8 плюс 16-битный индекс. Каков rate на один исходный вес?

  4. Почему номинальная разреженность не гарантирует уменьшения файла и задержки?

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

На группу из 128 весов приходится:

  • 128⋅4=512128\cdot4=512 бит для квантованных значений;
  • 16 бит для scale.

Эффективная разрядность:

4+16128=4.125 бита на исходный вес.\boxed{ 4+\frac{16}{128} =4.125 \text{ бита на исходный вес} }.

Плотный INT8 использует 8 бит на вес, поэтому групповой INT4 примерно в

84.125≈1.94\frac{8}{4.125} \approx1.94

раза компактнее до учёта выравнивания и заголовков.

При 50% неструктурированной разреженности каждый ненулевой вес стоит

8+16=24 бита.8+16=24\text{ бита}.

На один исходный вес:

0.5⋅24=12 бит.0.5\cdot24=12\text{ бит}.

Такой разреженный формат больше плотного INT8. Кроме того, нерегулярный доступ к памяти и отсутствие подходящих ядер могут не дать ускорения даже при меньшем размере файла.

Упражнение 7. Python: учёт rate, удаление шума и квантизация

Запустите:

text
M14_cv_information_experiments.py

Скрипт:

  1. сравнивает битовые расходы factorized-, hierarchical- и ideal-joint-моделей в игрушечной hyperprior-модели;

  2. численно проверяет Gaussian I–MMSE;

  3. строит кривую rate–distortion для групповой симметричной квантизации весов: группы по 128, clipping по p99.9 внутри каждой группы и один FP16-scale на группу.

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

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

Ожидаемые ключевые числа скрипта:

text
factorized_rate_bits: 6.788385
hierarchical_rate_bits: 6.145588
ideal_joint_entropy_bits: 5.961948
hierarchical_overhead_vs_joint: 0.183640

Ошибка конечных разностей для I–MMSE должна быть малой, а аналитическое тождество для score — совпадать с машинной точностью.

В эксперименте обе координаты кривой относятся к одной схеме: группы по 128 весов, clipping по p99.9 внутри каждой группы и один хранимый FP16-scale на группу. При фиксированном random seed ожидаются следующие значения:

payloadэффективных бит/веснормированная MSE
INT22.1250.554104
INT33.1250.173767
INT44.1250.046102
INT66.1250.003975
INT88.1250.001815

В эксперименте по квантизации нормированная MSE весов обычно возрастает при уменьшении разрядности. Но это только локальная суррогатная мера искажения. Два слоя с одинаковой MSE весов могут по-разному влиять на логиты, калибровку и качество в целевой задаче; для вывода, учитывающего задачу, требуется прогон реальной модели.

Программная часть решения · один исходный файл для RU/EN

PythonM14_cv_information_experiments.pyСкачать .py
from __future__ import annotations

from itertools import product
from pathlib import Path
import math

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np

ROOT = Path(__file__).resolve().parent
OUT = ROOT / "generated_experiment"
OUT.mkdir(exist_ok=True)


def binary_entropy(p: float) -> float:
    if p <= 0.0 or p >= 1.0:
        return 0.0
    return -p * math.log2(p) - (1.0 - p) * math.log2(1.0 - p)


def hyperprior_experiment() -> dict[str, float]:
    n = 8
    p0 = 0.05
    p1 = 0.50
    p_marg = 0.5 * (p0 + p1)

    factorized = n * binary_entropy(p_marg)
    conditional_one = 0.5 * binary_entropy(p0) + 0.5 * binary_entropy(p1)
    hierarchical = 1.0 + n * conditional_one

    probs: list[float] = []
    for bits in product((0, 1), repeat=n):
        k = sum(bits)
        prob_z0 = p0**k * (1.0 - p0) ** (n - k)
        prob_z1 = p1**k * (1.0 - p1) ** (n - k)
        probs.append(0.5 * prob_z0 + 0.5 * prob_z1)

    ideal_joint = -sum(p * math.log2(p) for p in probs if p > 0.0)

    labels = ["Factorized", "Hierarchical", "Ideal joint"]
    values = [factorized, hierarchical, ideal_joint]
    fig, ax = plt.subplots(figsize=(7.6, 4.8))
    ax.bar(labels, values)
    ax.set_ylabel("Bits per 8-symbol block")
    ax.set_title("Toy hyperprior: model factorization versus ideal joint coding")
    for i, value in enumerate(values):
        ax.text(i, value + 0.04, f"{value:.3f}", ha="center", va="bottom")
    ax.set_ylim(0.0, max(values) + 0.7)
    fig.tight_layout()
    fig.savefig(OUT / "M14_hyperprior_rates.png", dpi=180, bbox_inches="tight", pad_inches=0.08)
    plt.close(fig)

    return {
        "factorized_rate_bits": factorized,
        "hierarchical_rate_bits": hierarchical,
        "ideal_joint_entropy_bits": ideal_joint,
        "hierarchical_overhead_vs_joint": hierarchical - ideal_joint,
        "saving_vs_factorized": factorized - hierarchical,
    }


def gaussian_immse_experiment() -> dict[str, float]:
    variance = 2.0
    gamma = np.linspace(0.0, 12.0, 2001)
    mutual_information = 0.5 * np.log1p(variance * gamma)
    mmse = variance / (1.0 + variance * gamma)

    derivative_numeric = np.diff(mutual_information) / np.diff(gamma)
    gamma_mid = 0.5 * (gamma[:-1] + gamma[1:])
    derivative_theory = 0.5 * variance / (1.0 + variance * gamma_mid)
    max_error = float(np.max(np.abs(derivative_numeric - derivative_theory)))

    fig, ax = plt.subplots(figsize=(7.8, 4.8))
    ax.plot(gamma, mutual_information, label=r"$I(X;Y_\gamma)$, nats")
    ax.plot(gamma, 0.5 * mmse, label=r"$\frac{1}{2}\,\mathrm{mmse}(\gamma)$")
    ax.set_xlabel("SNR $\\gamma$")
    ax.set_ylabel("Value")
    ax.set_title("Gaussian channel: mutual information and the I–MMSE derivative")
    ax.legend()
    fig.tight_layout()
    fig.savefig(OUT / "M14_gaussian_immse.png", dpi=180, bbox_inches="tight", pad_inches=0.08)
    plt.close(fig)

    alpha = 0.8
    sigma = 0.6
    var_xt = alpha**2 * variance + sigma**2
    posterior_coefficient = alpha * variance / var_xt
    x_grid = np.linspace(-4.0, 4.0, 1001)
    posterior_mean = posterior_coefficient * x_grid
    score_from_denoiser = (alpha * posterior_mean - x_grid) / sigma**2
    score_direct = -x_grid / var_xt
    score_max_error = float(np.max(np.abs(score_from_denoiser - score_direct)))

    return {
        "immse_finite_difference_max_error": max_error,
        "score_identity_max_error": score_max_error,
        "posterior_mean_coefficient": posterior_coefficient,
        "xt_variance": var_xt,
    }


def groupwise_symmetric_quantize(
    weights: np.ndarray,
    bits: int,
    group_size: int = 128,
    clip_percentile: float = 99.9,
) -> tuple[np.ndarray, np.ndarray]:
    """Quantize each group with its own stored FP16 scale.

    The payload uses signed symmetric integer levels ``[-qmax, qmax]``.  Each
    group is clipped independently and the resulting scale is rounded to FP16
    before both quantization and reconstruction, matching the 16-bit overhead
    charged by the rate calculation.
    """
    if bits < 2:
        raise ValueError("bits must be at least 2 for signed symmetric quantization")
    if group_size <= 0:
        raise ValueError("group_size must be positive")
    if not 0.0 < clip_percentile <= 100.0:
        raise ValueError("clip_percentile must lie in (0, 100]")

    array = np.asarray(weights, dtype=float)
    flat = array.reshape(-1)
    if flat.size == 0:
        return array.copy(), np.empty(0, dtype=float)

    qmax = 2 ** (bits - 1) - 1
    reconstructed = np.empty_like(flat)
    n_groups = math.ceil(flat.size / group_size)
    scales = np.empty(n_groups, dtype=float)
    smallest_fp16 = float(np.finfo(np.float16).smallest_subnormal)

    for group_index, start in enumerate(range(0, flat.size, group_size)):
        group = flat[start:start + group_size]
        clip = float(np.percentile(np.abs(group), clip_percentile))
        if clip == 0.0:
            scale = 1.0
        else:
            scale = float(np.float16(clip / qmax))
            if scale == 0.0:
                scale = smallest_fp16

        q = np.clip(np.rint(group / scale), -qmax, qmax)
        reconstructed[start:start + group.size] = q * scale
        scales[group_index] = scale

    return reconstructed.reshape(array.shape), scales


def quantization_experiment() -> list[dict[str, float]]:
    rng = np.random.default_rng(14)
    group_size = 128
    n_groups = 1_563
    n = n_groups * group_size
    weights = rng.normal(0.0, 1.0, size=n)
    outlier_mask = rng.random(n) < 0.005
    weights[outlier_mask] += rng.normal(0.0, 8.0, size=int(outlier_mask.sum()))

    variance = float(np.var(weights))
    scale_bits = 16
    clip_percentile = 99.9
    rows: list[dict[str, float]] = []

    for bits in (2, 3, 4, 6, 8):
        reconstructed, scales = groupwise_symmetric_quantize(
            weights,
            bits,
            group_size=group_size,
            clip_percentile=clip_percentile,
        )
        if scales.size != n_groups:
            raise RuntimeError("unexpected number of quantization groups")
        mse = float(np.mean((weights - reconstructed) ** 2))
        effective_bits = bits + scale_bits / group_size
        rows.append(
            {
                "bits": float(bits),
                "effective_bits_per_weight": effective_bits,
                "mse": mse,
                "nmse": mse / variance,
                "mean_fp16_scale": float(np.mean(scales)),
            }
        )

    fig, ax = plt.subplots(figsize=(7.6, 4.8))
    ax.plot(
        [row["effective_bits_per_weight"] for row in rows],
        [row["nmse"] for row in rows],
        marker="o",
    )
    for row in rows:
        ax.annotate(
            f"INT{int(row['bits'])}",
            (row["effective_bits_per_weight"], row["nmse"]),
            xytext=(4, 5),
            textcoords="offset points",
        )
    ax.set_xlabel("Effective bits per weight (including scale overhead)")
    ax.set_ylabel("Normalized weight MSE")
    ax.set_yscale("log")
    ax.set_title("Group-wise weight quantization: a local rate–distortion curve")
    fig.tight_layout()
    fig.savefig(OUT / "M14_weight_quantization_rd.png", dpi=180, bbox_inches="tight", pad_inches=0.08)
    plt.close(fig)

    return rows


def main() -> None:
    print("=== Hyperprior rate accounting ===")
    hyper = hyperprior_experiment()
    for key, value in hyper.items():
        print(f"{key}: {value:.6f}")

    print("\n=== Gaussian I-MMSE ===")
    immse = gaussian_immse_experiment()
    for key, value in immse.items():
        print(f"{key}: {value:.10f}")

    print("\n=== Group-wise symmetric weight quantization ===")
    rows = quantization_experiment()
    print("scheme: group_size=128, per-group p99.9 clipping, one FP16 scale per group")
    print("bits  effective_bpw   nmse         mse")
    for row in rows:
        print(
            f"{int(row['bits']):>4d}  "
            f"{row['effective_bits_per_weight']:>13.6f}  "
            f"{row['nmse']:>11.8f}  "
            f"{row['mse']:>11.8f}"
        )


if __name__ == "__main__":
    main()