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

Модуль 7. Канал и пропускная способность

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

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

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

Упражнение 1. Пропускная способность BEC

Пусть X∼Bernoulli⁡(p)X\sim\operatorname{Bernoulli}(p) проходит через BEC(ε)(\varepsilon).

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

    H(X∣Y)=εH(X).H(X\mid Y)=\varepsilon H(X).
  2. Выведите

    I(X;Y)=(1−ε)h2(p).I(X;Y)=(1-\varepsilon)h_2(p).
  3. Максимизируйте по pp и получите C=1−εC=1-\varepsilon.

  4. Объясните, почему прямое сравнение BEC(ε)(\varepsilon) и BSC(ε)(\varepsilon) полезно, но параметр ε\varepsilon не означает идентичную физическую тяжесть шума.

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

Пусть X∼Bernoulli⁡(p)X\sim\operatorname{Bernoulli}(p), а Y∈{0,1,?}Y\in\{0,1,?\} — выход BEC(ε)(\varepsilon).

1. Условная энтропия

Если Y∈{0,1}Y\in\{0,1\}, стирания не произошло и вход известен точно:

H(X∣Y=0)=H(X∣Y=1)=0.H(X\mid Y=0)=H(X\mid Y=1)=0.

Если Y=?Y=?, событие стирания независимо от значения входа, поэтому апостериорное распределение XX совпадает с исходным:

H(X∣Y=?)=H(X)=h2(p).H(X\mid Y=? )=H(X)=h_2(p).

Поскольку Pr⁡(Y=?)=ε\Pr(Y=? )=\varepsilon,

H(X∣Y)=εh2(p).\boxed{ H(X\mid Y)=\varepsilon h_2(p) }.

2. Взаимная информация

I(X;Y)=H(X)−H(X∣Y)=h2(p)−εh2(p)=(1−ε)h2(p).\begin{aligned} I(X;Y) &=H(X)-H(X\mid Y)\\ &=h_2(p)-\varepsilon h_2(p)\\ &=(1-\varepsilon)h_2(p). \end{aligned}

3. Максимизация

Бинарная энтропия максимальна при p=1/2p=1/2 и равна одному биту. Следовательно,

CBEC=1−ε.\boxed{C_{\mathrm{BEC}}=1-\varepsilon}.

4. Почему BEC и BSC с одинаковым ε\varepsilon нельзя понимать как одинаково тяжёлый шум

При ε=0.1\varepsilon=0.1:

CBEC=0.9,CBSC=1−h2(0.1)≈0.5310.C_{\mathrm{BEC}}=0.9, \qquad C_{\mathrm{BSC}}=1-h_2(0.1)\approx0.5310.

Разница возникает не потому, что BEC «физически лучше» при любом сравнении, а потому, что параметры описывают разные наблюдения. В BEC приёмник знает позицию повреждения; в BSC перевёрнутый бит выглядит как обычный допустимый символ. Поэтому одно и то же численное ε\varepsilon не является универсальной шкалой тяжести канала.


Упражнение 2. Асимметричный Z-канал

Для

W=(10α1−α),p=Pr⁡(X=1),W= \begin{pmatrix} 1&0\\ \alpha&1-\alpha \end{pmatrix}, \qquad p=\Pr(X=1),
  1. выведите

    I(p)=h2((1−α)p)−ph2(α);I(p)=h_2((1-\alpha)p)-p h_2(\alpha);
  2. найдите стационарную точку;

  3. для α=0.25\alpha=0.25 вычислите p∗p^* и CC;

  4. сравните с равномерным входом.

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

Канал имеет матрицу

W=(10α1−α),p=Pr⁡(X=1).W= \begin{pmatrix} 1&0\\ \alpha&1-\alpha \end{pmatrix}, \qquad p=\Pr(X=1).

1. Формула для I(p)I(p)

Выход равен единице только когда X=1X=1 и канал не совершил ошибку:

Pr⁡(Y=1)=(1−α)p.\Pr(Y=1)=(1-\alpha)p.

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

H(Y)=h2((1−α)p).H(Y)=h_2((1-\alpha)p).

При X=0X=0 выход детерминирован, а при X=1X=1 имеет бинарную энтропию h2(α)h_2(\alpha). Поэтому

H(Y∣X)=p h2(α).H(Y\mid X)=p\,h_2(\alpha).

Итак,

I(p)=h2((1−α)p)−ph2(α).\boxed{ I(p)=h_2((1-\alpha)p)-p h_2(\alpha) }.

2. Стационарная точка

Для 0<u<10<u<1

h2′(u)=log⁡21−uu.h_2'(u)=\log_2\frac{1-u}{u}.

Поэтому

I′(p)=(1−α)log⁡21−(1−α)p(1−α)p−h2(α).I'(p) =(1-\alpha) \log_2 \frac{1-(1-\alpha)p}{(1-\alpha)p} -h_2(\alpha).

Положим

t=(1−α)p.t=(1-\alpha)p.

Условие I′(p)=0I'(p)=0 эквивалентно

log⁡21−tt=h2(α)1−α,\log_2\frac{1-t}{t} = \frac{h_2(\alpha)}{1-\alpha},

откуда

t∗=11+2h2(α)/(1−α)t^* = \frac{1}{1+2^{h_2(\alpha)/(1-\alpha)}}

и

p∗=1(1−α)(1+2h2(α)/(1−α)).\boxed{ p^* = \frac{1}{(1-\alpha) \left(1+2^{h_2(\alpha)/(1-\alpha)}\right)} }.

Функция I(p)I(p) вогнута по входному распределению, поэтому внутренняя стационарная точка является глобальным максимумом.

3. Числа для α=0.25\alpha=0.25

h2(0.25)≈0.811278.h_2(0.25)\approx0.811278.

Подстановка даёт

p∗≈0.4278256,p^*\approx0.4278256, C≈0.5582386 бита/использование.\boxed{C\approx0.5582386\text{ бита/использование}}.

4. Равномерный вход

При p=1/2p=1/2:

I(1/2)=h2(0.375)−12h2(0.25)≈0.5487949.I(1/2) =h_2(0.375)-\frac12h_2(0.25) \approx0.5487949.

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


Упражнение 3. Обратная теорема через Фано

Пусть существует (n,M)(n,M)-код для DMC с пропускной способностью CC и средней ошибкой Pe(n)P_e^{(n)}.

  1. Используйте Фано, чтобы показать

    log⁡2M≤nC+1+Pe(n)log⁡2M.\log_2M \le nC+1+P_e^{(n)}\log_2M.
  2. Получите нижнюю границу

    Pe(n)≥1−CR−1nR.P_e^{(n)} \ge 1-\frac{C}{R}-\frac{1}{nR}.
  3. Объясните разницу между слабой и сильной обратными теоремами (weak converse и strong converse).

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

Пусть JJ равномерно распределено по MM сообщениям, а J^\widehat J восстанавливается из YnY^n.

1. Верхняя оценка на размер кодовой книги

По неравенству Фано

H(J∣Yn)≤h2(Pe(n))+Pe(n)log⁡2(M−1)≤1+Pe(n)log⁡2M.H(J\mid Y^n) \le h_2(P_e^{(n)}) +P_e^{(n)}\log_2(M-1) \le 1+P_e^{(n)}\log_2M.

Далее

log⁡2M=H(J)=I(J;Yn)+H(J∣Yn)≤I(Xn;Yn)+1+Pe(n)log⁡2M≤nC+1+Pe(n)log⁡2M.\begin{aligned} \log_2M &=H(J)\\ &=I(J;Y^n)+H(J\mid Y^n)\\ &\le I(X^n;Y^n)+1+P_e^{(n)}\log_2M\\ &\le nC+1+P_e^{(n)}\log_2M. \end{aligned}

Здесь использованы марковская цепь J→Xn→YnJ\to X^n\to Y^n, неравенство обработки данных и оценка

I(Xn;Yn)≤∑i=1nI(Xi;Yi)≤nCI(X^n;Y^n)\le\sum_{i=1}^n I(X_i;Y_i)\le nC

для DMC.

2. Нижняя граница на ошибку

Переносим последний член в левую часть:

(1−Pe(n))log⁡2M≤nC+1.(1-P_e^{(n)})\log_2M\le nC+1.

Так как R=(1/n)log⁡2MR=(1/n)\log_2M,

1−Pe(n)≤CR+1nR.1-P_e^{(n)} \le \frac{C}{R}+\frac{1}{nR}.

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

Pe(n)≥1−CR−1nR.\boxed{ P_e^{(n)} \ge 1-\frac{C}{R}-\frac{1}{nR} }.

3. Слабая и сильная обратные теоремы

При фиксированном R>CR>C полученная граница отделяет ошибку от нуля в пределе:

lim inf⁡n→∞Pe(n)≥1−CR>0.\liminf_{n\to\infty}P_e^{(n)} \ge 1-\frac{C}{R}>0.

Это слабая обратная теорема (weak converse). Она доказывает невозможность исчезающей ошибки, но не её стремление к единице.

Сильная обратная теорема (strong converse) для DMC утверждает более сильный результат:

R>C⟹Pe(n)→1.R>C \quad\Longrightarrow\quad P_e^{(n)}\to1.

Поэтому число 1−C/R1-C/R нельзя интерпретировать как предельную ошибку оптимального кода выше пропускной способности: это только слабая нижняя оценка, полученная через Фано.


Упражнение 4. Симметричный шум меток

Пусть YY равномерна на KK классах, а наблюдаемая метка остаётся верной с вероятностью 1−η1-\eta и иначе равномерно заменяется одним из K−1K-1 неверных классов.

  1. Выведите

    I(Y;Y~)=log⁡2K−h2(η)−ηlog⁡2(K−1).I(Y;\widetilde Y) = \log_2K-h_2(\eta)-\eta\log_2(K-1).
  2. Посчитайте значение при K=10K=10, η=0.2\eta=0.2.

  3. Найдите η\eta, при котором канал имеет нулевую пропускную способность.

  4. Вычислите оставшуюся информацию при η=1\eta=1 и объясните, почему нулевая точность не означает нулевую MI.

  5. Объясните, почему шум меток, зависящий от конкретного объекта (instance-dependent label noise), не описывается одной такой матрицей.

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

Пусть YY равномерно распределена по KK классам. При условии Y=iY=i наблюдаемая метка равна ii с вероятностью 1−η1-\eta, а каждый неверный класс получает вероятность η/(K−1)\eta/(K-1).

1. Взаимная информация

Канал слабосимметричен, поэтому при равномерном входе выход тоже равномерен:

H(Y~)=log⁡2K.H(\widetilde Y)=\log_2K.

Каждая строка матрицы переходов содержит вероятности

1−η,ηK−1,…,ηK−1⏟K−1 раз.1-\eta, \quad \underbrace{\frac{\eta}{K-1},\ldots, \frac{\eta}{K-1}}_{K-1\text{ раз}}.

Её энтропия равна

H(Y~∣Y=i)=−(1−η)log⁡2(1−η)−(K−1)ηK−1log⁡2ηK−1=h2(η)+ηlog⁡2(K−1).\begin{aligned} H(\widetilde Y\mid Y=i) &=-(1-\eta)\log_2(1-\eta) -(K-1)\frac{\eta}{K-1} \log_2\frac{\eta}{K-1}\\ &=h_2(\eta)+\eta\log_2(K-1). \end{aligned}

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

I(Y;Y~)=log⁡2K−h2(η)−ηlog⁡2(K−1).\boxed{ I(Y;\widetilde Y) = \log_2K-h_2(\eta)-\eta\log_2(K-1) }.

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

2. K=10K=10, η=0.2\eta=0.2

h2(0.2)≈0.721928,0.2log⁡29≈0.633985.h_2(0.2)\approx0.721928, \qquad 0.2\log_2 9\approx0.633985.

Поэтому

I(Y;Y~)≈3.321928−0.721928−0.633985=1.966015 бита.\boxed{ I(Y;\widetilde Y) \approx 3.321928-0.721928-0.633985 = 1.966015\text{ бита} }.

3. Когда пропускная способность равна нулю

Строки канала совпадают тогда и только тогда, когда каждый выходной класс имеет вероятность 1/K1/K. Для диагонального элемента это требует

1−η=1K,1-\eta=\frac1K,

то есть

η=K−1K.\boxed{ \eta=\frac{K-1}{K} }.

Для K=10K=10 это η=0.9\eta=0.9.

4. Что остаётся при η=1\eta=1

При η=1\eta=1 наблюдаемая метка всегда неверна, но она гарантированно сообщает, каким классом объект не является. Подстановка в формулу даёт

I(Y;Y~)=log⁡2K−log⁡2(K−1)=log⁡2KK−1.I(Y;\widetilde Y) = \log_2K-\log_2(K-1) = \log_2\frac{K}{K-1}.

Для K=10K=10

I(Y;Y~)=log⁡2109≈0.1520 бита.I(Y;\widetilde Y) = \log_2\frac{10}{9} \approx0.1520\text{ бита}.

Таким образом, нулевая точность не означает нулевую взаимную информацию: систематически неправильный сигнал всё ещё может быть частично обратимым. Зависимость пропускной способности от η\eta после точки полного рандомизирования не обязана быть монотонной.

5. Почему одной матрицы недостаточно для шума, зависящего от объекта

Матрица Tij=Pr⁡(Y~=j∣Y=i)T_{ij}=\Pr(\widetilde Y=j\mid Y=i) предполагает

Y~⊥X∣Y.\widetilde Y\perp X\mid Y.

При шуме, зависящем от объекта, правильнее писать

Pr⁡(Y~=j∣Y=i,X=x),\Pr(\widetilde Y=j\mid Y=i,X=x),

и для разных xx переходный закон различается. Усреднённая матрица может скрыть, что ошибки концентрируются на трудных, неоднозначных или систематически смещённых подгруппах.


Упражнение 5. ECOC

Четырём классам назначены кодовые слова:

text
c1 = 0000000
c2 = 1111000
c3 = 1100110
c4 = 1010101
  1. Найдите все попарные расстояния Хэмминга и dmin⁡d_{\min}.

  2. Сколько битовых ошибок гарантированно исправляет декодер ближайшего кодового слова?

  3. Декодируйте вектор 1111010.

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

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

Кодовые слова:

text
c1 = 0000000
c2 = 1111000
c3 = 1100110
c4 = 1010101

1. Попарные расстояния

ПараРасстояние Хэмминга
c1,c2c_1,c_24
c1,c3c_1,c_34
c1,c4c_1,c_44
c2,c3c_2,c_34
c2,c4c_2,c_44
c3,c4c_3,c_44

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

dmin⁡=4.\boxed{d_{\min}=4}.

2. Гарантированное исправление

Декодер ближайшего кодового слова гарантированно исправляет

t=⌊dmin⁡−12⌋=⌊32⌋=1t=\left\lfloor\frac{d_{\min}-1}{2}\right\rfloor =\left\lfloor\frac32\right\rfloor =1

битовую ошибку.

3. Декодирование 1111010

Расстояния до кодовых слов:

КлассРасстояние
c1c_15
c2c_21
c3c_33
c4c_45

Ближайшее слово — c2c_2, поэтому

y^=2.\boxed{\widehat y=2}.

4. Почему кодовая гарантия не равна гарантии классификатора

Классическая оценка по dmin⁡d_{\min} рассматривает произвольное число ошибочных битов в уже сформированном кодовом слове. В ECOC эти «биты» производятся обученными бинарными классификаторами. Их ошибки могут:

  • коррелировать;
  • иметь разные вероятности;
  • зависеть от объекта;
  • систематически поражать одни и те же столбцы;
  • выдавать непрерывные оценки, а не идеальные биты.

Поэтому dmin⁡d_{\min} задаёт полезную геометрическую устойчивость, но реальное качество зависит также от разбиений классов, обучаемости бинарных задач и правила декодирования.


Упражнение 6. Python: Блахут—Аримото и конечная длина блока

Реализуйте две части.

  1. Алгоритмом Блахута—Аримото вычислите пропускную способность и оптимальное входное распределение для:

    • BSC(0.1)(0.1);
    • BEC(0.1)(0.1);
    • Z-канала с α=0.25\alpha=0.25.
  2. Для BSC(0.1)(0.1) сгенерируйте случайные различные кодовые слова, декодируйте по ближайшему расстоянию Хэмминга и сравните:

    • R=0.25<CR=0.25<C;
    • R=0.80>CR=0.80>C;
    • n∈{8,12,16,20}n\in\{8,12,16,20\}.

Усредняйте по нескольким кодовым книгам и при ничьих (ties) выбирайте декодированный индекс равновероятно. Объясните, почему эксперимент при конечном nn не является доказательством теоремы и почему полный перебор при декодировании не масштабируется.

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

Ниже приведена воспроизводимая реализация. Кодовые слова хранятся как упакованные целые числа: при n≤20n\le20 это позволяет быстро считать расстояния Хэмминга через np.bitwise_count и не менять математическую постановку.

Python
from __future__ import annotations

import math
import numpy as np


def binary_entropy(p: float) -> float:
    """Binary entropy in bits."""
    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 mutual_information(p_x: np.ndarray, channel: np.ndarray) -> float:
    """I(X;Y) in bits for an input distribution and a DMC matrix."""
    p_y = p_x @ channel
    joint = p_x[:, None] * channel
    x, y = np.nonzero(joint > 0)
    # Only positive joint mass contributes; unreachable outputs have p_y=0.
    return float(np.sum(joint[x, y] * np.log2(channel[x, y] / p_y[y])))


def blahut_arimoto(
    channel: np.ndarray,
    tol: float = 1e-12,
    max_iter: int = 100_000,
) -> tuple[float, np.ndarray, int]:
    """Return DMC capacity, a maximizing input distribution, and iterations."""
    channel = np.asarray(channel, dtype=float)
    if channel.ndim != 2 or np.any(channel < 0):
        raise ValueError("channel must be a nonnegative 2D array")
    if not np.allclose(channel.sum(axis=1), 1.0):
        raise ValueError("each row of channel must sum to one")

    p_x = np.full(channel.shape[0], 1.0 / channel.shape[0])
    for iteration in range(1, max_iter + 1):
        p_y = p_x @ channel
        ratio = np.divide(
            channel,
            p_y[None, :],
            out=np.ones_like(channel),
            where=channel > 0,
        )
        d_x = np.sum(
            np.where(channel > 0, channel * np.log(ratio), 0.0),
            axis=1,
        )
        p_new = p_x * np.exp(d_x)
        p_new /= p_new.sum()
        if np.max(np.abs(p_new - p_x)) < tol:
            p_x = p_new
            break
        p_x = p_new
    else:
        raise RuntimeError("Blahut-Arimoto did not converge")

    return mutual_information(p_x, channel), p_x, iteration


def random_noise_mask(
    rng: np.random.Generator,
    n: int,
    epsilon: float,
) -> np.uint32:
    """Generate an n-bit BSC noise mask packed into uint32."""
    bits = rng.random(n) < epsilon
    powers = np.left_shift(np.uint32(1), np.arange(n, dtype=np.uint32))
    return np.sum(bits.astype(np.uint32) * powers, dtype=np.uint32)


def simulate_random_codebook(
    rng: np.random.Generator,
    n: int,
    rate: float,
    epsilon: float,
    trials: int = 300,
) -> float:
    """Estimate block error for one distinct random binary codebook."""
    universe = 1 << n
    m = max(2, int(round(2 ** (n * rate))))
    if m > universe:
        raise ValueError("rate requires more distinct codewords than exist")

    codebook = rng.choice(universe, size=m, replace=False).astype(np.uint32)
    errors = 0

    for _ in range(trials):
        message = int(rng.integers(m))
        received = codebook[message] ^ random_noise_mask(rng, n, epsilon)
        distances = np.bitwise_count(codebook ^ received)
        min_distance = distances.min()
        candidates = np.flatnonzero(distances == min_distance)
        decoded = int(rng.choice(candidates))
        errors += decoded != message

    return errors / trials


def main() -> None:
    bsc = np.array([[0.9, 0.1], [0.1, 0.9]])
    bec = np.array([[0.9, 0.0, 0.1], [0.0, 0.9, 0.1]])
    z_channel = np.array([[1.0, 0.0], [0.25, 0.75]])

    print("Blahut-Arimoto")
    for name, channel in [
        ("BSC(0.1)", bsc),
        ("BEC(0.1)", bec),
        ("Z(0.25)", z_channel),
    ]:
        capacity, p_x, iterations = blahut_arimoto(channel)
        print(
            f"{name:9s}: C={capacity:.9f}, "
            f"p_X={p_x}, iterations={iterations}"
        )

    epsilon = 0.1
    capacity = 1.0 - binary_entropy(epsilon)
    print(f"\nBSC(0.1) capacity: {capacity:.9f} bits/use")

    rng = np.random.default_rng(7)
    blocklengths = [8, 12, 16, 20]
    rates = [0.25, 0.80]
    n_codebooks = 10
    trials = 300

    for rate in rates:
        side = "below" if rate < capacity else "above"
        print(f"\nR={rate:.2f} ({side} capacity)")
        for n in blocklengths:
            estimates = np.array(
                [
                    simulate_random_codebook(rng, n, rate, epsilon, trials)
                    for _ in range(n_codebooks)
                ]
            )
            m = max(2, int(round(2 ** (n * rate))))
            print(
                f"n={n:2d}, M={m:6d}: "
                f"mean P_e={estimates.mean():.6f}, "
                f"std={estimates.std(ddof=1):.6f}"
            )


if __name__ == "__main__":
    main()

Один запуск с указанным seed даёт:

text
Blahut-Arimoto
BSC(0.1) : C=0.531004406, p_X=[0.5 0.5], iterations=1
BEC(0.1) : C=0.900000000, p_X=[0.5 0.5], iterations=1
Z(0.25)  : C=0.558238627, p_X=[0.5721744 0.4278256], iterations=26

BSC(0.1) capacity: 0.531004406 bits/use

R=0.25 (below capacity)
n= 8, M=     4: mean P_e=0.082333, std=0.044055
n=12, M=     8: mean P_e=0.061000, std=0.020248
n=16, M=    16: mean P_e=0.046667, std=0.008165
n=20, M=    32: mean P_e=0.036000, std=0.011089

R=0.80 (above capacity)
n= 8, M=    84: mean P_e=0.462333, std=0.033740
n=12, M=   776: mean P_e=0.594667, std=0.022233
n=16, M=  7132: mean P_e=0.674333, std=0.031113
n=20, M= 65536: mean P_e=0.714000, std=0.030217

Как читать результат

Алгоритм Блахута—Аримото воспроизводит аналитические значения:

  • BSC(0.1)(0.1): C≈0.5310044C\approx0.5310044, оптимальный вход равномерен;
  • BEC(0.1)(0.1): C=0.9C=0.9, оптимальный вход равномерен;
  • Z(0.25)(0.25): C≈0.5582386C\approx0.5582386, а PX∗≈(0.5721744,0.4278256).P_X^*\approx(0.5721744,0.4278256).

В моделировании BSC:

  • при R=0.25<CR=0.25<C средняя ошибка по выбранному ансамблю кодовых книг уменьшается с ростом nn;
  • при R=0.80>CR=0.80>C она растёт в направлении единицы, что согласуется с сильной обратной теоремой;
  • отдельные значения не обязаны быть монотонными для каждого seed и каждой кодовой книги;
  • эксперимент не доказывает асимптотическую теорему: он показывает только поведение конкретного конечного ансамбля;
  • декодер перебирает все M≈2nRM\approx2^{nR} кодовых слов, поэтому его стоимость экспоненциальна по nn.

Именно последнее различает доказательство существования хороших случайных кодов и инженерно пригодную систему кодирования.


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

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

import math
import numpy as np


def binary_entropy(p: float) -> float:
    """Binary entropy in bits."""
    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 mutual_information(p_x: np.ndarray, channel: np.ndarray) -> float:
    """I(X;Y) in bits for an input distribution and a DMC matrix."""
    p_y = p_x @ channel
    joint = p_x[:, None] * channel
    x, y = np.nonzero(joint > 0)
    # Only positive joint mass contributes; unreachable outputs have p_y=0.
    return float(np.sum(joint[x, y] * np.log2(channel[x, y] / p_y[y])))


def blahut_arimoto(
    channel: np.ndarray,
    tol: float = 1e-12,
    max_iter: int = 100_000,
) -> tuple[float, np.ndarray, int]:
    """Return DMC capacity, a maximizing input distribution, and iterations."""
    channel = np.asarray(channel, dtype=float)
    if channel.ndim != 2 or np.any(channel < 0):
        raise ValueError("channel must be a nonnegative 2D array")
    if not np.allclose(channel.sum(axis=1), 1.0):
        raise ValueError("each row of channel must sum to one")

    p_x = np.full(channel.shape[0], 1.0 / channel.shape[0])
    for iteration in range(1, max_iter + 1):
        p_y = p_x @ channel
        ratio = np.divide(
            channel,
            p_y[None, :],
            out=np.ones_like(channel),
            where=channel > 0,
        )
        d_x = np.sum(
            np.where(channel > 0, channel * np.log(ratio), 0.0),
            axis=1,
        )
        p_new = p_x * np.exp(d_x)
        p_new /= p_new.sum()
        if np.max(np.abs(p_new - p_x)) < tol:
            p_x = p_new
            break
        p_x = p_new
    else:
        raise RuntimeError("Blahut-Arimoto did not converge")

    return mutual_information(p_x, channel), p_x, iteration


def random_noise_mask(
    rng: np.random.Generator,
    n: int,
    epsilon: float,
) -> np.uint32:
    """Generate an n-bit BSC noise mask packed into uint32."""
    bits = rng.random(n) < epsilon
    powers = np.left_shift(np.uint32(1), np.arange(n, dtype=np.uint32))
    return np.sum(bits.astype(np.uint32) * powers, dtype=np.uint32)


def simulate_random_codebook(
    rng: np.random.Generator,
    n: int,
    rate: float,
    epsilon: float,
    trials: int = 300,
) -> float:
    """Estimate block error for one distinct random binary codebook."""
    universe = 1 << n
    m = max(2, int(round(2 ** (n * rate))))
    if m > universe:
        raise ValueError("rate requires more distinct codewords than exist")

    codebook = rng.choice(universe, size=m, replace=False).astype(np.uint32)
    errors = 0

    for _ in range(trials):
        message = int(rng.integers(m))
        received = codebook[message] ^ random_noise_mask(rng, n, epsilon)
        distances = np.bitwise_count(codebook ^ received)
        min_distance = distances.min()
        candidates = np.flatnonzero(distances == min_distance)
        decoded = int(rng.choice(candidates))
        errors += decoded != message

    return errors / trials


def main() -> None:
    bsc = np.array([[0.9, 0.1], [0.1, 0.9]])
    bec = np.array([[0.9, 0.0, 0.1], [0.0, 0.9, 0.1]])
    z_channel = np.array([[1.0, 0.0], [0.25, 0.75]])

    print("Blahut-Arimoto")
    for name, channel in [
        ("BSC(0.1)", bsc),
        ("BEC(0.1)", bec),
        ("Z(0.25)", z_channel),
    ]:
        capacity, p_x, iterations = blahut_arimoto(channel)
        print(
            f"{name:9s}: C={capacity:.9f}, "
            f"p_X={p_x}, iterations={iterations}"
        )

    epsilon = 0.1
    capacity = 1.0 - binary_entropy(epsilon)
    print(f"\nBSC(0.1) capacity: {capacity:.9f} bits/use")

    rng = np.random.default_rng(7)
    blocklengths = [8, 12, 16, 20]
    rates = [0.25, 0.80]
    n_codebooks = 10
    trials = 300

    for rate in rates:
        side = "below" if rate < capacity else "above"
        print(f"\nR={rate:.2f} ({side} capacity)")
        for n in blocklengths:
            estimates = np.array(
                [
                    simulate_random_codebook(rng, n, rate, epsilon, trials)
                    for _ in range(n_codebooks)
                ]
            )
            m = max(2, int(round(2 ** (n * rate))))
            print(
                f"n={n:2d}, M={m:6d}: "
                f"mean P_e={estimates.mean():.6f}, "
                f"std={estimates.std(ddof=1):.6f}"
            )


if __name__ == "__main__":
    main()

Бонус. Пропускная способность AWGN

Докажите

C=12log⁡2(1+P/N)C=\frac12\log_2(1+P/N)

для Y=X+ZY=X+Z, используя

I(X;Y)=h(Y)−h(Z)I(X;Y)=h(Y)-h(Z)

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

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

Пусть

Y=X+Z,Z∼N(0,N),E[X2]≤P,Y=X+Z, \qquad Z\sim\mathcal N(0,N), \qquad \mathbb E[X^2]\le P,

и ZZ независимо от XX.

Для любого допустимого входа

I(X;Y)=h(Y)−h(Y∣X)=h(Y)−h(Z),I(X;Y)=h(Y)-h(Y\mid X)=h(Y)-h(Z),

поскольку при фиксированном XX величина YY является сдвигом ZZ.

Шум имеет энтропию

h(Z)=12log⁡2(2πeN).h(Z)=\frac12\log_2(2\pi eN).

Кроме того,

Var⁡(Y)=Var⁡(X)+N≤P+N.\operatorname{Var}(Y) = \operatorname{Var}(X)+N \le P+N.

При фиксированной дисперсии гауссовское распределение максимизирует дифференциальную энтропию. Поэтому

h(Y)≤12log⁡2(2πe(P+N)).h(Y) \le \frac12\log_2(2\pi e(P+N)).

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

I(X;Y)≤12log⁡2(2πe(P+N))−12log⁡2(2πeN)=12log⁡2(1+PN).\begin{aligned} I(X;Y) &\le \frac12\log_2(2\pi e(P+N)) - \frac12\log_2(2\pi eN)\\ &= \frac12\log_2\left(1+\frac PN\right). \end{aligned}

Чтобы достичь границы, достаточно выбрать

X∼N(0,P).X\sim\mathcal N(0,P).

Тогда

Y∼N(0,P+N),Y\sim\mathcal N(0,P+N),

верхняя оценка на h(Y)h(Y) достигается с равенством, и

CAWGN=12log⁡2(1+PN).\boxed{ C_{\mathrm{AWGN}} = \frac12\log_2\left(1+\frac PN\right) }.

Это пропускная способность в битах на одно вещественное дискретное использование канала. Формула Blog⁡2(1+S/N)B\log_2(1+S/N) бит/с относится к непрерывному каналу с полосой BB Гц и не должна смешиваться с этой нормировкой.