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

Модуль 10. Алгоритмическая теория информации: кратчайшие описания, MDL и универсальное предсказание

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

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

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

Упражнение 1. Сколько строк можно сжать

Для фиксированной универсальной префиксной машины покажите, что среди nn-битных строк доля строк, удовлетворяющих

K(x)<n−c,K(x)<n-c,

меньше 2−c2^{-c}.

  1. Сколько бинарных программ имеют длину меньше n−cn-c?

  2. Почему одна завершившаяся программа не может описывать две разные строки?

  3. Как изменится рассуждение для простой сложности C(x)C(x)?

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

Рассмотрим бинарные программы длины строго меньше n−cn-c. Всего бинарных строк таких длин

1+2+⋯+2n−c−1=2n−c−1.1+2+\cdots+2^{n-c-1} = 2^{n-c}-1.

Допустимых завершившихся программ префиксной машины не больше этого числа. Одна детерминированная программа при фиксированной машине имеет не более одного конечного выхода. Поэтому

#{x∈{0,1}n:K(x)<n−c}<2n−c.\#\{x\in\{0,1\}^n:K(x)<n-c\} < 2^{n-c}.

Делим на общее число 2n2^n строк:

#{x∈{0,1}n:K(x)<n−c}2n<2−c.\frac{ \#\{x\in\{0,1\}^n:K(x)<n-c\} }{2^n} < 2^{-c}.

Итого

Pr⁡X∼Un[K(X)<n−c]<2−c.\boxed{ \Pr_{X\sim U_n}[K(X)<n-c]<2^{-c} }.

Сам подсчёт использует только число двоичных программ и детерминированность их выхода. Префиксность важна для выбранного варианта KK, для связи с неравенством Крафта и для последующих вероятностных весов 2−∣p∣2^{-|p|}.

Для простой сложности C(x)C(x) такой же элементарный подсчёт коротких программ даёт аналогичную долю. Различия между CC и KK проявляются в самоделимитации, цепных правилах и алгоритмической вероятности.

Упражнение 2. Короткий генератор и плохое практическое сжатие

Рассмотрите NN байт, сгенерированных детерминированным PRNG из фиксированного начального значения (seed).

  1. Дайте верхнюю границу на K(x)K(x) через длину программы генератора, начального значения и описание NN.

  2. Объясните, почему gzip всё же может вернуть почти 8N8N бит.

  3. Почему из этого не следует, что gzip «ошибся» в собственной задаче?

  4. Какие дополнительные сведения должен получить декодер, чтобы воспользоваться коротким генеративным описанием?

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

Пусть GG — фиксированная программа псевдослучайного генератора (PRNG), ss — его начальное значение (seed), а NN — число выходных байтов. Программа для xx содержит:

  1. описание GG;

  2. начальное значение ss;

  3. самоделимитирующееся описание NN;

  4. команду «сгенерировать первые NN байт».

Поэтому

K(x)≤K(G)+K(s)+K(N)+O(1).\boxed{ K(x) \le K(G)+K(s)+K(N)+O(1) }.

При фиксированных GG и ss правая часть растёт лишь логарифмически с NN, тогда как сырой объект занимает 8N8N бит.

Gzip ищет словари, повторения и локальные статистические закономерности определённых типов. Хороший PRNG специально выдаёт поток, который выглядит случайным для широкого класса ограниченных тестов. Короткая закономерность «это GG с начальным значением ss» существует, но не входит в язык gzip.

Это не ошибка gzip. Его задача — найти хороший код в фиксированном формате за ограниченное время, а не минимизировать по всем программам.

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

Наконец, короткое описание не гарантирует быстрого декодирования. Определение K(x)K(x) не штрафует время исполнения; программа минимальной длины может работать чрезвычайно долго.

Упражнение 3. Энтропия и ожидаемая колмогоровская сложность

Пусть

P=(12,14,18,18).P= \left( \frac12, \frac14, \frac18, \frac18 \right).
  1. Найдите H2(P)H_2(P).

  2. Объясните, почему H2(P)≤EPK(X)H_2(P)\le\mathbb E_P K(X) для префиксной сложности.

  3. Если описание распределения стоит K(P)=40K(P)=40 бит, какую верхнюю границу даёт

    EK(X)≤H2(P)+K(P)+O(1)?\mathbb E K(X) \le H_2(P)+K(P)+O(1)?
  4. Почему эта граница становится содержательнее для блока из nn i.i.d. наблюдений? Какую дополнительную стоимость даёт описание nn?

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

Для

P=(12,14,18,18)P=\left(\frac12,\frac14,\frac18,\frac18\right)

получаем

H2(P)=−12log⁡212−14log⁡214−2⋅18log⁡218=12+12+2⋅38=1.75 бита.\begin{aligned} H_2(P) &= -\frac12\log_2\frac12 -\frac14\log_2\frac14 -2\cdot\frac18\log_2\frac18\\ &= \frac12+\frac12+2\cdot\frac38\\ &= \boxed{1.75\ \text{бита}}. \end{aligned}

Выбранные кратчайшие префиксные программы образуют префиксный код для исходов. Средняя длина любого такого кода не меньше энтропии:

H2(P)≤EPK(X).H_2(P) \le \mathbb E_PK(X).

При K(P)=40K(P)=40 бит верхняя граница равна

EK(X)≤1.75+40+O(1)=41.75+O(1) бита.\mathbb E K(X) \le 1.75+40+O(1) = \boxed{41.75+O(1)\ \text{бита}}.

Для одного исхода эта граница груба: стоимость модели доминирует. Для nn i.i.d. наблюдений декодеру нужно также передать длину блока. Более явная граница имеет вид

EK(X1:n)≤nH2(P)+K(P)+K(n)+O(1),\mathbb E K(X_{1:n}) \le nH_2(P)+K(P)+K(n)+O(1),

где K(n)=O(log⁡n)K(n)=O(\log n). Поэтому

1nEK(X1:n)≤1.75+40+O(log⁡n)n.\frac1n\mathbb E K(X_{1:n}) \le 1.75+ \frac{40+O(\log n)}{n}.

Фиксированная стоимость модели исчезает на символ при n→∞n\to\infty.

По одному наблюдению нельзя восстановить единственное распределение PP. Бесконечно много распределений назначают положительную вероятность тому же исходу. Поэтому короткое описание конкретного объекта и хорошая модель новых данных — разные требования.

Упражнение 4. Точка переключения MDL

Две модели имеют длины

L(hA)=40 бит,L(hB)=220 бит.L(h_A)=40\ \text{бит}, \qquad L(h_B)=220\ \text{бит}.

Средняя длина кода данных на одном объекте равна

ℓA=0.76 бита,ℓB=0.61 бита.\ell_A=0.76\ \text{бита}, \qquad \ell_B=0.61\ \text{бита}.
  1. Запишите полные длины для выборки размера nn.

  2. При каком минимальном nn модель BB становится короче?

  3. Объясните, почему ответ иллюстрирует зависимость допустимой сложности модели от количества данных.

  4. Что изменится, если стоимость L(h)L(h) зависит от точности представления параметров?

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

Полные длины:

LA(n)=40+0.76n,L_A(n)=40+0.76n, LB(n)=220+0.61n.L_B(n)=220+0.61n.

Модель BB выигрывает, когда

220+0.61n<40+0.76n,220+0.61n<40+0.76n,

то есть

180<0.15n,180<0.15n, n>1200.n>1200.

При n=1200n=1200 обе длины равны 952952 битам. Минимальный целый размер для строгого выигрыша BB:

n=1201.\boxed{n=1201}.

Сложная модель платит дополнительные 180180 бит один раз, но экономит 0.150.15 бита на каждом примере. На малой выборке доминирует цена модели; на большой накапливается выигрыш в кодировании данных.

Если L(h)L(h) зависит от точности параметров, стоимость модели сама может расти с размером выборки: большая выборка оправдывает более точную передачу весов. Тогда оптимизация идёт совместно по модели и по точности квантования.

MAP выбирает одну модель, минимизируя −log⁡ph(D)−log⁡π(h)-\log p_h(D)-\log\pi(h). Байесовский смешанный код использует −log⁡∑hπ(h)ph(D)-\log\sum_h\pi(h)p_h(D) и не обязан сначала объявлять одну гипотезу. Поэтому два протокола могут давать разные длины и разный выбор.

Упражнение 5. NCD вручную

Пусть компрессор дал

C(x)=1000,C(y)=900,C(xy)=1300C(x)=1000, \qquad C(y)=900, \qquad C(xy)=1300

байт.

  1. Найдите NCD⁡C(x,y)\operatorname{NCD}_C(x,y).

  2. Как изменится ответ, если C(yx)=1260C(yx)=1260 и используется симметризация через минимум?

  3. Почему результат нельзя интерпретировать как точную NID?

  4. Назовите два возможных артефакта для коротких файлов.

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

Без симметризации:

NCD⁡C(x,y)=1300−min⁡(1000,900)max⁡(1000,900)=4001000=0.40.\operatorname{NCD}_C(x,y) = \frac{1300-\min(1000,900)}{\max(1000,900)} = \frac{400}{1000} = \boxed{0.40}.

Если

Csym(x,y)=min⁡{C(xy),C(yx)}=1260,C_{\mathrm{sym}}(x,y) = \min\{C(xy),C(yx)\} = 1260,

то

NCD⁡C,sym(x,y)=1260−9001000=0.36.\operatorname{NCD}_{C,\mathrm{sym}}(x,y) = \frac{1260-900}{1000} = \boxed{0.36}.

Это не точная NID, потому что C(x)C(x) не равна K(x)K(x), компрессор видит ограниченный класс закономерностей, длины включают заголовки и формат, а порядок конкатенации может менять словарь и окно.

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

Пригодность компрессора проверяют эмпирически: сравнивают несколько кодеков, оба порядка конкатенации, устойчивость к формату и предобработке, а затем оценивают метод ближайшего соседа или кластеризацию на отложенной задаче.

Упражнение 6. Python: компрессоры, предиктивно-последовательный MDL и NCD

Запустите файл M10_algorithmic_mdl_experiments.py.

Он выполняет три эксперимента:

  1. сравнивает gzip, bz2 и LZMA на регулярных и псевдослучайных объектах;

  2. сравнивает предиктивно-последовательную длину двух представлений с похожей итоговой точностью;

  3. строит матрицу NCD для похожих текстов, фрагментов кода и ДНК-подобных последовательностей.

Ответьте:

  • почему PRNG- и SHA-потоки плохо сжимаются обычными компрессорами, хотя имеют короткое генеративное описание;
  • почему представление с меньшим числом нерелевантных измерений (nuisance dimensions) получает меньшую последовательную длину;
  • почему NCD группирует пары внутри домена, но абсолютные значения зависят от bz2;
  • какие проверки нужны перед использованием NCD как рабочей метрики.
Показать ответ и решение

Скрипт использует фиксированные начальные значения и параметры, поэтому результат воспроизводим.

1. Практические компрессоры

Типичный запуск даёт следующие значения в битах на входной байт:

Объектgzipbz2LZMA
Нули0.01170.00520.0171
Периодическая строка0.01680.00780.0181
Повторяющийся англоязычный текст0.03430.02160.0244
PRNG, начальное значение 78.00468.07898.0078
Поток SHA-256 со счётчиком8.00468.07568.0078

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

2. Предиктивно-последовательный MDL

Типичный результат:

Представлениебит/меткуитоговая точность
4 информативных измерения0.74230.7440
4 сигнальных + 96 нерелевантных0.90860.7270

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

Даже при точно одинаковой итоговой точности предиктивно-последовательные длины могли бы различаться: они зависят от всей траектории обучения, а не только от последней точки.

3. NCD

Внутридоменные пары получают наименьшие внедиагональные расстояния:

  • text-A / text-B: около 0.6420.642;
  • code-A / code-B: около 0.5760.576;
  • dna-A / dna-B: около 0.6010.601.

Большинство междоменных расстояний лежит примерно между 0.750.75 и 0.990.99.

Диагональ не обязана быть строго нулевой: реальные заголовки и неидеальность свойства C(xx)≈C(x)C(xx)\approx C(x) создают остаток. Это наглядное отличие практической NCD от теоретической NID.

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

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

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

import bz2
import gzip
import hashlib
import json
import lzma
import math
import random
import zlib
from dataclasses import dataclass
from pathlib import Path
from typing import Callable

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, log_loss
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline

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

SEED = 20260713
rng = np.random.default_rng(SEED)


def repeat_to_length(pattern: bytes, n: int) -> bytes:
    return (pattern * (n // len(pattern) + 1))[:n]


def python_prng_bytes(n: int, seed: int = 7) -> bytes:
    # A short generator plus a short seed describes this object, even though
    # generic compressors will usually see random-looking bytes.
    r = random.Random(seed)
    return bytes(r.randrange(256) for _ in range(n))


def hash_counter_stream(n: int, seed: bytes = b"course-m10") -> bytes:
    out = bytearray()
    counter = 0
    while len(out) < n:
        out.extend(hashlib.sha256(seed + counter.to_bytes(8, "big")).digest())
        counter += 1
    return bytes(out[:n])


COMPRESSORS: dict[str, Callable[[bytes], bytes]] = {
    "gzip": lambda x: gzip.compress(x, compresslevel=9),
    "bz2": lambda x: bz2.compress(x, compresslevel=9),
    "lzma": lambda x: lzma.compress(x, preset=9),
}


def compressor_experiment(n: int = 65536) -> list[dict[str, float | str]]:
    corpus = {
        "zeros": b"\x00" * n,
        "periodic": repeat_to_length(b"abc123|", n),
        "English-like": repeat_to_length(
            b"prediction and compression are two views of the same log-loss; ", n
        ),
        "PRNG seed=7": python_prng_bytes(n, 7),
        "SHA256 stream": hash_counter_stream(n),
    }

    rows: list[dict[str, float | str]] = []
    for name, data in corpus.items():
        for comp_name, comp in COMPRESSORS.items():
            packed = comp(data)
            rows.append(
                {
                    "object": name,
                    "compressor": comp_name,
                    "raw_bytes": len(data),
                    "compressed_bytes": len(packed),
                    "bits_per_byte": 8.0 * len(packed) / len(data),
                }
            )

    names = list(corpus)
    x = np.arange(len(names))
    width = 0.24
    fig, ax = plt.subplots(figsize=(10.5, 5.6))
    for j, comp_name in enumerate(COMPRESSORS):
        vals = [
            next(
                float(row["bits_per_byte"])
                for row in rows
                if row["object"] == name and row["compressor"] == comp_name
            )
            for name in names
        ]
        ax.bar(x + (j - 1) * width, vals, width=width, label=comp_name)
    ax.axhline(8.0, linewidth=1.2, linestyle="--", label="raw: 8 bits/byte")
    ax.set_xticks(x, names, rotation=18, ha="right")
    ax.set_ylabel("bits per input byte")
    ax.set_title("Practical compressors are computable upper-bound mechanisms, not K-oracles")
    ax.legend(ncol=2)
    fig.tight_layout()
    fig.savefig(OUT / "M10_experiment_compressor_bounds.png", dpi=190)
    plt.close(fig)

    return rows


@dataclass
class PrequentialResult:
    name: str
    total_bits: float
    bits_per_label: float
    final_accuracy: float
    cumulative_points: list[tuple[int, float]]


def make_classification_representations(n: int = 5000) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
    z = rng.normal(size=(n, 4))
    logits = 1.3 * z[:, 0] - 0.9 * z[:, 1] + 0.5 * z[:, 2]
    probs = 1.0 / (1.0 + np.exp(-logits))
    y = rng.binomial(1, probs)

    easy = z + rng.normal(scale=0.08, size=z.shape)
    nuisance = rng.normal(size=(n, 96))
    hard = np.concatenate([z + rng.normal(scale=0.18, size=z.shape), nuisance], axis=1)
    return easy, hard, y.astype(int)


def prequential_code(
    X: np.ndarray,
    y: np.ndarray,
    name: str,
    boundaries: list[int],
) -> PrequentialResult:
    n = len(y)
    if boundaries[-1] != n:
        raise ValueError("last boundary must equal dataset size")

    first = boundaries[0]
    total_bits = float(first)  # uniform binary code for first labels
    points: list[tuple[int, float]] = [(first, total_bits)]

    for left, right in zip(boundaries[:-1], boundaries[1:]):
        model = make_pipeline(
            StandardScaler(),
            LogisticRegression(max_iter=2500, C=1.0, solver="lbfgs", random_state=SEED),
        )
        model.fit(X[:left], y[:left])
        probs = model.predict_proba(X[left:right])
        block_nats = log_loss(y[left:right], probs, labels=[0, 1], normalize=False)
        total_bits += float(block_nats / math.log(2.0))
        points.append((right, total_bits))

    final_model = make_pipeline(
        StandardScaler(),
        LogisticRegression(max_iter=2500, C=1.0, solver="lbfgs", random_state=SEED),
    )
    split = int(0.8 * n)
    final_model.fit(X[:split], y[:split])
    acc = accuracy_score(y[split:], final_model.predict(X[split:]))

    return PrequentialResult(
        name=name,
        total_bits=total_bits,
        bits_per_label=total_bits / n,
        final_accuracy=float(acc),
        cumulative_points=points,
    )


def prequential_experiment() -> list[PrequentialResult]:
    X_easy, X_hard, y = make_classification_representations()
    boundaries = [64, 128, 256, 512, 1024, 2048, 3500, len(y)]
    results = [
        prequential_code(X_easy, y, "4 informative dimensions", boundaries),
        prequential_code(X_hard, y, "4 signal + 96 nuisance dimensions", boundaries),
    ]

    fig, ax = plt.subplots(figsize=(9.6, 5.4))
    for result in results:
        xs = [p[0] for p in result.cumulative_points]
        ys = [p[1] / p[0] for p in result.cumulative_points]
        ax.plot(xs, ys, marker="o", label=result.name)
    ax.axhline(1.0, linestyle="--", linewidth=1.2, label="uniform binary code")
    ax.set_xscale("log", base=2)
    ax.set_xlabel("number of labels encoded")
    ax.set_ylabel("cumulative bits per label")
    ax.set_title("Prequential MDL rewards representations that are learned quickly")
    ax.legend()
    fig.tight_layout()
    fig.savefig(OUT / "M10_experiment_prequential.png", dpi=190)
    plt.close(fig)
    return results


def ncd(x: bytes, y: bytes, compressor: Callable[[bytes], bytes]) -> float:
    cx = len(compressor(x))
    cy = len(compressor(y))
    cxy = min(len(compressor(x + y)), len(compressor(y + x)))
    return (cxy - min(cx, cy)) / max(cx, cy)


def mutate(data: bytes, every: int, replacement: bytes) -> bytes:
    out = bytearray(data)
    for i in range(0, len(out), every):
        out[i : i + len(replacement)] = replacement[: min(len(replacement), len(out) - i)]
    return bytes(out)


def ncd_experiment() -> tuple[list[str], np.ndarray]:
    n = 24000
    objects = {
        "text-A": repeat_to_length(b"the model predicts the next token and pays in bits. ", n),
        "text-B": mutate(repeat_to_length(b"the model predicts the next token and pays in bits. ", n), 337, b"loss"),
        "code-A": repeat_to_length(b"for(i=0;i<n;i++){sum+=a[i];}\n", n),
        "code-B": mutate(repeat_to_length(b"for(i=0;i<n;i++){sum+=a[i];}\n", n), 401, b"while"),
        "dna-A": repeat_to_length(b"ACGTACGTGATTACA", n),
        "dna-B": mutate(repeat_to_length(b"ACGTACGTGATTACA", n), 389, b"TTAA"),
    }
    labels = list(objects)
    comp = lambda b: bz2.compress(b, compresslevel=9)
    matrix = np.zeros((len(labels), len(labels)))
    for i, a in enumerate(labels):
        for j, b in enumerate(labels):
            matrix[i, j] = ncd(objects[a], objects[b], comp)

    fig, ax = plt.subplots(figsize=(7.5, 6.4))
    im = ax.imshow(matrix, vmin=0.0, vmax=max(1.0, float(matrix.max())))
    ax.set_xticks(np.arange(len(labels)), labels, rotation=35, ha="right")
    ax.set_yticks(np.arange(len(labels)), labels)
    ax.set_title("Normalized compression distance: shared regularity becomes proximity")
    for i in range(len(labels)):
        for j in range(len(labels)):
            ax.text(j, i, f"{matrix[i, j]:.2f}", ha="center", va="center", fontsize=8)
    fig.colorbar(im, ax=ax, label="NCD")
    fig.tight_layout()
    fig.savefig(OUT / "M10_experiment_ncd.png", dpi=190)
    plt.close(fig)
    return labels, matrix


def main() -> None:
    compressor_rows = compressor_experiment()
    prequential_results = prequential_experiment()
    labels, matrix = ncd_experiment()

    summary = {
        "compressor_rows": compressor_rows,
        "prequential": [
            {
                "name": r.name,
                "total_bits": r.total_bits,
                "bits_per_label": r.bits_per_label,
                "final_accuracy": r.final_accuracy,
            }
            for r in prequential_results
        ],
        "ncd_labels": labels,
        "ncd_matrix": matrix.tolist(),
    }
    (OUT / "M10_experiment_results.json").write_text(
        json.dumps(summary, indent=2, ensure_ascii=False), encoding="utf-8"
    )

    print("Compression experiment (bits per byte):")
    for row in compressor_rows:
        print(
            f"  {row['object']:14s} {row['compressor']:4s}: "
            f"{float(row['bits_per_byte']):.3f}"
        )
    print("\nPrequential results:")
    for r in prequential_results:
        print(
            f"  {r.name}: {r.bits_per_label:.4f} bits/label, "
            f"final accuracy={r.final_accuracy:.4f}"
        )
    print("\nNCD matrix:")
    print(np.array2string(matrix, precision=3, suppress_small=True))


if __name__ == "__main__":
    main()

Бонус. Универсальное доминирование и избыточная логарифмическая потеря

Пусть универсальная полумера удовлетворяет

M(x)≥2−K(μ)−cμ(x)M(x) \ge 2^{-K(\mu)-c}\mu(x)

для всех конечных префиксов xx вычислимого источника μ\mu.

  1. Покажите, что

    log⁡2μ(x)M(x)≤K(μ)+c.\log_2\frac{\mu(x)}{M(x)} \le K(\mu)+c.
  2. Разложите отношение правдоподобий по времени с помощью цепного правила.

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

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

Из

M(x)≥2−K(μ)−cμ(x)M(x) \ge 2^{-K(\mu)-c}\mu(x)

следует

μ(x)M(x)≤2K(μ)+c.\frac{\mu(x)}{M(x)} \le 2^{K(\mu)+c}.

Берём двоичный логарифм:

log⁡2μ(x)M(x)≤K(μ)+c.\boxed{ \log_2\frac{\mu(x)}{M(x)} \le K(\mu)+c }.

Для последовательности при корректно определённых условных вероятностях

log⁡μ(x1:n)M(x1:n)=log⁡∏t=1nμ(xt∣x<t)M(xt∣x<t)=∑t=1nlog⁡μ(xt∣x<t)M(xt∣x<t).\begin{aligned} \log\frac{\mu(x_{1:n})}{M(x_{1:n})} &= \log \prod_{t=1}^{n} \frac{\mu(x_t\mid x_{<t})}{M(x_t\mid x_{<t})}\\ &= \sum_{t=1}^{n} \log \frac{\mu(x_t\mid x_{<t})}{M(x_t\mid x_{<t})}. \end{aligned}

После усреднения по μ\mu каждый ожидаемый член становится условной KL-дивергенцией. Суммарная ожидаемая избыточная логарифмическая потеря ограничена K(μ)+O(1)K(\mu)+O(1) битами.

Сложность истинной модели оплачивается именно множителем 2−K(μ)2^{-K(\mu)}, а после логарифмирования — аддитивным членом K(μ)K(\mu).

Граница не делает MM вычислимой. Можно находить всё новые программы и увеличивать частичную сумму, но невозможно узнать, что никакая ещё программа не внесёт вклад.