← Все главы

06 · Версия материала 5

От подсчёта переходов к биграммной модели

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

Подсчитайте семь переходов и постройте прогноз для A

В главе 5 мы разбивали документы на перекрывающиеся пары «вход — цель», чтобы обучать последовательную модель. Для биграммной таблицы нужны данные до такого разбиения: исходные закодированные документы обучающей выборки. Если считать переходы по перекрывающимся окнам, один и тот же переход может попасть в расчёт несколько раз. Здесь каждая соседняя пара должна увеличить ровно один счётчик.

Во всей главе используем один и тот же порядок токенов:

ТокенBOSEOSABC
ID01234

Токен C входит в словарь, но в этих двух документах обучающей выборки не встречается. Документы обрабатываем по отдельности:

d1: BOS(0) → A(2) → A(2) → B(3) → EOS(1)
d2: BOS(0) → A(2) → B(3) → EOS(1)

Прежде чем продолжить, ответьте на четыре вопроса:

  1. Сколько соседних переходов содержат оба документа вместе?
  2. Какие токены встречаются сразу после A и сколько раз?
  3. Какой токен должен оказаться самым вероятным продолжением A?
  4. Чем случай «после A ни разу не встретился C» отличается от случая «после C не встретилось ничего»?

В первом документе четыре перехода, во втором — три:

d1: BOS→A, A→A, A→B, B→EOS
d2: BOS→A, A→B, B→EOS

После объединения результатов ненулевы только четыре счётчика: BOSA=2\operatorname{BOS}\to A=2, AA=1A\to A=1, AB=2A\to B=2 и BEOS=2B\to\operatorname{EOS}=2. Для текущего токена A расположим возможные продолжения в порядке [BOS, EOS, A, B, C]:

счётчики для A = [0, 0, 1, 2, 0]
сумма строки    = 3

Так устроена биграммная модель: текущий токен выбирает строку таблицы, а каждому возможному следующему токену соответствует столбец. Всё, что было до текущего токена, модель отбрасывает. Разделив каждый счётчик в строке A на её сумму, получаем оценку максимального правдоподобия (MLE):

[0/3,0/3,1/3,2/3,0/3]=[0,0,1/3,2/3,0].[0/3,0/3,1/3,2/3,0/3]=[0,0,1/3,2/3,0].

Наибольшая вероятность только у B. Оценка MLE для перехода A→C определена, но равна нулю: из A наблюдалось три перехода, поэтому строку можно нормировать.

Для контекста C ситуация иная. Его строка равна [0,0,0,0,0], а сумма строки — нулю. Деление каждого счётчика на эту сумму привело бы к делению на ноль, поэтому распределение MLE для C не определено. Нулевая ячейка в существующем распределении и отсутствие всего распределения — разные случаи.

Теперь применим аддитивное сглаживание с α=1\alpha=1. При вычислении вероятностей прибавим единицу к каждому счётчику. Эта единица — не новое наблюдение, а псевдочастота, которую вводит правило сглаживания. Исходная таблица счётчиков при этом не меняется. Для A получаем:

исходные счётчики=[0,0,1,2,0],с псевдочастотой=[1,1,2,3,1],знаменатель=3+1×5=8,вероятности=[1/8,1/8,2/8,3/8,1/8].\begin{aligned} \text{исходные счётчики} &= [0,0,1,2,0], \\ \text{с псевдочастотой} &= [1,1,2,3,1], \\ \text{знаменатель} &= 3+1\times5=8, \\ \text{вероятности} &= [1/8,1/8,2/8,3/8,1/8]. \end{aligned}

После C наблюдений нет. Пять одинаковых псевдочастот дают знаменатель 0+1×5=50+1\times5=5 и равномерное распределение [1/5,1/5,1/5,1/5,1/5][1/5,1/5,1/5,1/5,1/5]. Это распределение задано правилом сглаживания, а не выведено из данных о C. Поэтому нельзя заключать, что в реальном языке все продолжения после C одинаково правдоподобны.

Остаётся проверить границу документов. Если склеить их в последовательность [0,2,2,3,1,0,2,3,1], получится восемь соседних пар вместо семи. Лишняя пара — EOS(1)→BOS(0): ни в одном исходном документе такого перехода нет.

Запишите подсчёт внутри документов и нормировку строк

Весь расчёт можно записать одной формулой:

Cij=d𝒟trt=0d2𝟏[zt(d)=izt+1(d)=j],Ni=kVCik,P^MLE(ji)=CijNi  (Ni>0),P^α(ji)=Cij+αNi+αV  (α>0)C_{ij}=\sum_{d\in\mathcal{D}_{tr}}\sum_{t=0}^{|d|-2}\mathbf{1}[z_t^{(d)}=i\land z_{t+1}^{(d)}=j],\quad N_i=\sum_{k\in V}C_{ik},\quad \widehat P_{\mathrm{MLE}}(j\mid i)=\frac{C_{ij}}{N_i}\;(N_i>0),\quad \widehat P_{\alpha}(j\mid i)=\frac{C_{ij}+\alpha}{N_i+\alpha|V|}\;(\alpha>0)

Начнём с двух сумм в левой части. Внешняя сумма перебирает исходные документы обучающей выборки по одному. Во внутренней сумме позиции нумеруются с нуля, как индексы среза в Rust. Если d2|d|\ge2, индекс tt принимает значения от 00 до d2|d|-2, а t+1t+1 остаётся внутри того же документа. Для более короткого документа внутренняя сумма пуста. Верхний индекс (d)(d) подчёркивает, что оба токена пары принадлежат одному документу.

ID текущего токена ii задаёт строку, а ID возможного продолжения jj — столбец. Индикатор добавляет единицу, только когда соседние токены равны ii и jj. Следовательно, CijC_{ij} показывает, сколько раз встретился переход iji\to j. Суммирование по всем столбцам kk даёт NiN_i — общее число переходов, начинающихся с ii.

Оценка MLE делит CijC_{ij} на NiN_i, поэтому она определена только при Ni>0N_i>0. При аддитивном сглаживании числитель каждой ячейки становится Cij+αC_{ij}+\alpha. Поскольку в строке V|V| ячеек, знаменатель увеличивается на αV\alpha|V|, а не только на α\alpha. В каждой строке сумма новых числителей совпадает со знаменателем, поэтому сумма вероятностей остаётся равной единице. При α>0\alpha>0 знаменатель положителен даже у строки, в которой нет ни одного наблюдения.

Реализация на Rust хранит таблицу в одном построчном массиве: ячейка (i,j)(i,j) находится по смещению iV+ji|V|+j. Это лишь способ размещения данных в памяти; вероятностный смысл ячейки остаётся прежним: «следующим будет токен jj, если текущий токен — ii».

Разберите каждое обозначение

ОбозначениеСмысл в этой главе
𝒟tr\mathcal{D}_{tr}Множество исходных документов обучающей выборки с BOS в начале и EOS в конце. Валидационная и тестовая выборки сюда не входят.
ddОдин документ обучающей выборки.
$d
ttПозиция с нумерацией от нуля, после которой в dd ещё есть следующий токен.
zt(d)z_t^{(d)}ID токена в позиции tt документа dd.
iiID текущего токена; задаёт строку таблицы счётчиков.
jjID возможного следующего токена; задаёт столбец таблицы счётчиков.
kkИндекс для перебора всех ID из VV при вычислении суммы строки.
VVМножество допустимых ID токенов.
$V
CijC_{ij}Сколько раз в обучающей выборке встретился переход iji\to j.
NiN_iСколько наблюдавшихся переходов начинается с ii; сумма строки ii.
𝟏[]\mathbf{1}[\cdot]Индикатор: единица при выполненном условии и ноль в противном случае.
P^MLE(ji)\widehat P_{\mathrm{MLE}}(j\mid i)Оценка вероятности следующего токена методом максимального правдоподобия; определена только при Ni>0N_i>0.
α\alphaПоложительная псевдочастота, добавляемая к каждому возможному продолжению при сглаживании. В примере α=1\alpha=1.
P^α(ji)\widehat P_{\alpha}(j\mid i)Сглаженная с параметром α\alpha оценка вероятности того, что после ii следует jj.

Важно понимать, к какой части таблицы относится утверждение. Фраза «C не встретился после A» описывает одну ячейку в строке A. Фраза «после C не встретился ни один токен» описывает всю строку C. Если перепутать строку и столбец, можно ошибочно решить, что распределение MLE существует или, наоборот, не существует.

Оцените возможности и ограничения классического подхода

До появления нейросетевых языковых моделей для прогноза следующего слова часто использовали n-граммные модели. Они учитывают только контекст фиксированной длины; у биграммной модели контекст состоит из одного текущего токена. Такую таблицу легко проверить, а прогноз по ней быстро вычисляется. Однако для любых двух последовательностей, которые оканчиваются одним и тем же токеном, модель выдаёт одинаковое распределение.

Чен и Гудман (1996) определяют биграммную оценку максимального правдоподобия как отношение числа наблюдений пары к числу наблюдений её контекста и объясняют, почему нулевая вероятность n-граммы, отсутствовавшей в обучающих данных, создаёт серьёзную проблему для языковой модели. В работе сравниваются несколько семейств методов сглаживания. Аддитивное сглаживание устроено просто, но далеко не является лучшим методом для практического применения: одинаковая псевдочастота не позволяет отличить друг от друга более и менее правдоподобные продолжения, которые не встречались в обучающей выборке.

Бенжио, Дюшарм и Венсан (2000) описывают традиционную n-граммную модель как таблицу условных вероятностей для коротких контекстов. В предложенной ими нейросетевой модели распределённые представления слов позволяют обобщать наблюдения на похожие последовательности. У независимых ячеек нашей таблицы такой возможности нет.

В этой главе используется сглаживание с α=1\alpha=1, потому что все числители и знаменатели легко проверить вручную. Ограничение метода видно в том же примере: после сглаживания вероятность 1/81/8 получает не только переход A→C, но и переход A→BOS, хотя BOS допустим только в начале документа. Одинаковая псевдочастота в каждой ячейке ничего не знает ни о роли токенов, ни о синтаксисе.

В статьях не задаются принятые в этой главе ID токенов, границы документов, значение α\alpha, выбор только обучающей части корпуса и порядок вывода кандидатов с одинаковой вероятностью. Это учебные и инженерные решения курса. Программа на Rust вычисляет MLE и сглаженные оценки по одной и той же таблице, поэтому разницу между ними можно проверить непосредственно.

Сверьте ручной расчёт с реализацией на Rust

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

Два документа обучающей выборки с маркерами BOS и EOS rust/demos/ch06-bigram-baseline/src/lib.rs#wrapped-training-fixture
pub const DOCUMENT_1: &[u32] = &[BOS, A, A, B, EOS];
pub const DOCUMENT_2: &[u32] = &[BOS, A, B, EOS];
pub const TRAINING_DOCUMENTS: [&[u32]; 2] = [DOCUMENT_1, DOCUMENT_2];

pub fn fitted_model() -> Result<BigramModel, BigramError> {
    BigramModel::fit_training_documents(VOCABULARY_SIZE, ALPHA, TRAINING_DOCUMENTS)
}

fit_training_documents проверяет размер словаря, параметр сглаживания, возможность выделить память под таблицу и каждый переданный ID. Внешний цикл перебирает документы, а windows(2) создаёт соседние пары только внутри текущего документа. Пустой срез и срез из одного токена не добавляют переходов; при этом все имеющиеся в них ID всё равно проверяются. Метод fit_encoded_training_partition явно запрашивает Partition::Train, поэтому валидационные и тестовые документы не попадают в подсчёт.

Подсчёт соседних пар отдельно внутри каждого документа обучающей выборки rust/crates/llm-from-scratch/src/bigram.rs#fit-training-documents
    /// Fits one count per adjacent pair in each caller-supplied training document.
    ///
    /// Documents remain separate: the final token of one document is never paired
    /// with the first token of the next document.
    pub fn fit_training_documents<'a, I>(
        vocabulary_size: usize,
        alpha: f64,
        training_documents: I,
    ) -> Result<Self, BigramError>
    where
        I: IntoIterator<Item = &'a [u32]>,
    {
        if vocabulary_size == 0 {
            return Err(BigramError::EmptyVocabulary);
        }
        let smoothing_mass = alpha * vocabulary_size as f64;
        if !alpha.is_finite() || alpha <= 0.0 || !smoothing_mass.is_finite() {
            return Err(BigramError::InvalidAlpha);
        }

        let cell_count = vocabulary_size
            .checked_mul(vocabulary_size)
            .ok_or(BigramError::TableTooLarge)?;
        let mut counts = Vec::new();
        counts
            .try_reserve_exact(cell_count)
            .map_err(|_| BigramError::TableTooLarge)?;
        counts.resize(cell_count, 0_u64);

        let mut model = Self {
            vocabulary_size,
            alpha,
            counts,
            fitted_documents: 0,
            fitted_transitions: 0,
        };

        for document in training_documents {
            model.fitted_documents = model
                .fitted_documents
                .checked_add(1)
                .ok_or(BigramError::TooManyDocuments)?;
            for token in document {
                model.token_index(*token)?;
            }
            for pair in document.windows(2) {
                let from = model.token_index(pair[0])?;
                let to = model.token_index(pair[1])?;
                let cell = from * vocabulary_size + to;
                model.counts[cell] = model.counts[cell]
                    .checked_add(1)
                    .ok_or(BigramError::TooManyTransitions)?;
                model.fitted_transitions = model
                    .fitted_transitions
                    .checked_add(1)
                    .ok_or(BigramError::TooManyTransitions)?;
            }
        }

        Ok(model)
    }

    /// Selects the original encoded training documents and never requests held-out data.
    pub fn fit_encoded_training_partition(
        vocabulary_size: usize,
        alpha: f64,
        partitions: &EncodedCorpusPartitions,
    ) -> Result<Self, BigramError> {
        Self::fit_training_documents(
            vocabulary_size,
            alpha,
            partitions
                .documents(Partition::Train)
                .iter()
                .map(|document| document.token_ids()),
        )
    }

Тип возвращаемого значения сохраняет важное различие. Some(0.0) означает, что строка MLE существует, но выбранный переход имеет нулевую вероятность. None означает, что сумма строки равна нулю и нормировать её нельзя. После сглаживания распределение существует для любого корректного контекста. Если максимальное значение счётчика достигается для нескольких токенов, most_likely_tokens возвращает все их ID по возрастанию. Этот порядок нужен только для воспроизводимого вывода: первый ID не становится более вероятным. Аддитивное сглаживание не меняет ранжирование токенов и сохраняет все равенства, потому что к каждой ячейке прибавляется одна и та же α\alpha, а затем все числители делятся на общий знаменатель.

Разные представления для нулевой вероятности и неопределённой строки MLE rust/crates/llm-from-scratch/src/bigram.rs#probability-rows
    /// Returns `None` when no outgoing transition was observed for `from`.
    pub fn maximum_likelihood_distribution(
        &self,
        from: u32,
    ) -> Result<Option<Vec<f64>>, BigramError> {
        let total = self.row_total(from)?;
        if total == 0 {
            return Ok(None);
        }
        Ok(Some(
            self.counts_row(from)?
                .iter()
                .map(|count| *count as f64 / total as f64)
                .collect(),
        ))
    }

    pub fn maximum_likelihood_probability(
        &self,
        from: u32,
        to: u32,
    ) -> Result<Option<f64>, BigramError> {
        self.token_index(to)?;
        let total = self.row_total(from)?;
        if total == 0 {
            return Ok(None);
        }
        Ok(Some(self.count(from, to)? as f64 / total as f64))
    }

    pub fn smoothing_denominator(&self, from: u32) -> Result<f64, BigramError> {
        Ok(self.row_total(from)? as f64 + self.alpha * self.vocabulary_size as f64)
    }

    pub fn smoothed_probability(&self, from: u32, to: u32) -> Result<f64, BigramError> {
        let numerator = self.count(from, to)? as f64 + self.alpha;
        Ok(numerator / self.smoothing_denominator(from)?)
    }

    pub fn smoothed_distribution(&self, from: u32) -> Result<Vec<f64>, BigramError> {
        self.token_index(from)?;
        (0..self.vocabulary_size)
            .map(|to| {
                let to = u32::try_from(to).map_err(|_| BigramError::TokenOutOfRange)?;
                self.smoothed_probability(from, to)
            })
            .collect()
    }

    /// Returns every count-maximizing successor in ascending token-ID order.
    pub fn most_likely_tokens(&self, from: u32) -> Result<Vec<u32>, BigramError> {
        let row = self.counts_row(from)?;
        let maximum = row
            .iter()
            .copied()
            .max()
            .expect("a vocabulary is non-empty");
        row.iter()
            .enumerate()
            .filter(|(_, count)| **count == maximum)
            .map(|(token, _)| u32::try_from(token).map_err(|_| BigramError::TokenOutOfRange))
            .collect()
    }

Демонстрационная программа сначала печатает счётчики, а затем обе оценки вероятности. Отдельно показаны переход A→C, строка C и искусственный переход, который появился бы при склеивании документов.

Счётчики, оценки MLE, сглаженные вероятности и проверка границы документов rust/demos/ch06-bigram-baseline/src/main.rs#learner-output
    println!("tokens: BOS=0 EOS=1 A=2 B=3 C=4");
    println!("alpha: {ALPHA:.1}");
    println!("training document d1: {DOCUMENT_1:?}");
    println!("training document d2: {DOCUMENT_2:?}");
    println!("counted transitions: {}", model.fitted_transitions());
    println!(
        "A counts: {} total={}",
        format_counts(model.counts_row(A).expect("A is in the vocabulary")),
        model.row_total(A).expect("A is in the vocabulary")
    );
    println!("A MLE: {}", format_probabilities(&a_mle));
    println!(
        "A add-alpha: {} denominator={:.0}",
        format_probabilities(&a_smoothed),
        model
            .smoothing_denominator(A)
            .expect("A is in the vocabulary")
    );
    println!(
        "unseen successor A->C: MLE={:.3} add-alpha={:.3}",
        model
            .maximum_likelihood_probability(A, C)
            .expect("A and C are in the vocabulary")
            .expect("A has outgoing transitions"),
        model
            .smoothed_probability(A, C)
            .expect("A and C are in the vocabulary")
    );
    println!(
        "C counts: {} total={}",
        format_counts(model.counts_row(C).expect("C is in the vocabulary")),
        model.row_total(C).expect("C is in the vocabulary")
    );
    println!("C MLE: {c_mle}");
    println!(
        "C add-alpha: {} denominator={:.0}",
        format_probabilities(&c_smoothed),
        model
            .smoothing_denominator(C)
            .expect("C is in the vocabulary")
    );
    println!("flattening would invent: EOS({EOS})->BOS(0)");

Запустите пример:

./course run cargo run --quiet --locked -p ch06-bigram-baseline

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

Сопоставьте исходные документы с двумя строками таблицы

Перед просмотром таблиц попробуйте предсказать ответы:

  1. К каким ячейкам будет добавлена псевдочастота α\alpha?
  2. Изменится ли самое вероятное продолжение A после сглаживания?
  3. Появится ли единственный максимум в сглаженном распределении после C?
  4. Какой маркер, допустимый только в начале документа, всё же получит ненулевую вероятность после A?

Проследите, как две строки счётчиков превращаются в вероятности

Один и тот же пример на Rust задаёт два отдельных документа обучающей выборки и строки таблицы для токенов A и C. Сопоставьте строку A, где токен C ни разу не был продолжением, и строку C, для которой вообще не наблюдались продолжения.

Размер словаря
5
Параметр сглаживания
1.000
Документов в обучающей выборке
2
Подсчитано переходов
7

Переходы, учтённые внутри каждого документа

  1. Документ обучающей выборки d1
  2. Документ обучающей выборки d2
  • BOS0 служебный маркер начала или конца документа
  • EOS1 служебный маркер начала или конца документа
  • A2 токен содержимого, встречающийся в обучающей выборке
  • B3 токен содержимого, встречающийся в обучающей выборке
  • C4 есть в словаре, но не встречается в этих документах

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

Контекст A: продолжение C не встретилось

Текущий токен: A2

Общее число наблюдений в строке
3
Знаменатель сглаженного распределения
8
Следующий токен Число наблюдений Добавленная псевдочастота Счётчик плюс псевдочастота Оценка MLE Сглаженная вероятность
BOS0 0 +1.000 1.000 0.000 0.125
EOS1 0 +1.000 1.000 0.000 0.125
A2 1 +1.000 2.000 0.333 0.250
B3 2 +1.000 3.000 0.667 0.375
C4 0 +1.000 1.000 0.000 0.125

Продолжение C после A не встретилось, но сумма строки A равна трём. Поэтому его оценка MLE определена и равна нулю; после сглаживания с единичной псевдочастотой это продолжение получает вероятность, равную одной восьмой.

Контекст C: после C нет ни одного наблюдения

Текущий токен: C4

Общее число наблюдений в строке
0
Знаменатель сглаженного распределения
5
Следующий токен Число наблюдений Добавленная псевдочастота Счётчик плюс псевдочастота Оценка MLE Сглаженная вероятность
BOS0 0 +1.000 1.000 не определена: сумма строки равна нулю 0.200
EOS1 0 +1.000 1.000 не определена: сумма строки равна нулю 0.200
A2 0 +1.000 1.000 не определена: сумма строки равна нулю 0.200
B3 0 +1.000 1.000 не определена: сумма строки равна нулю 0.200
C4 0 +1.000 1.000 не определена: сумма строки равна нулю 0.200

В обучающих документах ни один переход не начинается с C, поэтому сумма строки равна нулю и получить распределение MLE нельзя. Равномерное распределение задаёт правило сглаживания; из данных не следует, что продолжения после C действительно равновероятны.

Проверка границы между документами

Если склеить документы, между ними появится искусственный переход EOS→BOS. При обработке документов по отдельности он не попадает в таблицу.

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

Теперь проверьте ответы по полным таблицам. Псевдочастота α\alpha добавляется к каждой ячейке. После A единственным максимумом остаётся B. После C все токены имеют одинаковую сглаженную вероятность. Даже структурно недопустимый здесь переход A→BOS получает ненулевую вероятность.

Сначала решите задачи, затем проверьте расчёты

Во всех заданиях документы остаются раздельными, а столбцы идут в порядке [BOS, EOS, A, B, C]. Сначала запишите ответы и только потом запускайте пример.

  1. Перечислите семь переходов и укажите исходный документ для каждого из них.
  2. Для контекста A вычислите строку счётчиков, строку MLE и сглаженную строку при α=1\alpha=1.
  3. Объясните, почему MLE(AC)=0\operatorname{MLE}(A\to C)=0, а распределение MLE для контекста C не определено.
  4. Мысленно склейте документы. Какой искусственный переход появится и сколько соседних пар окажется в общей последовательности?
  5. Почему подсчёт по перекрывающимся окнам главы 5 с T=3T=3 и S=1S=1 привёл бы к повторному учёту некоторых переходов в документе d1?
  6. Пересчитайте сглаженную строку A для α=1/2\alpha=1/2.
  7. Проверьте, что обе сглаженные строки из примера дают сумму вероятностей, равную единице.
  8. Назовите все токены с максимальной вероятностью в сглаженном распределении после A и после C.
  9. Допустим, в валидационной выборке часто встречается A→C. Нужно ли менять таблицу перед оцениванием в главе 7?
Проверьте ответы и ход вычислений
  1. Документ d1 даёт BOS→A, A→A, A→B, B→EOS; документ d2BOS→A, A→B, B→EOS.
  2. Строка счётчиков равна [0,0,1,2,0][0,0,1,2,0], строка MLE — [0,0,1/3,2/3,0][0,0,1/3,2/3,0]. После добавления псевдочастоты получаем числители [1,1,2,3,1][1,1,2,3,1] и знаменатель 88, поэтому сглаженная строка равна [1/8,1/8,2/8,3/8,1/8][1/8,1/8,2/8,3/8,1/8].
  3. Для A значение NA=3N_A=3, поэтому строку можно нормировать, а ячейка в столбце C даёт 0/3=00/3=0. Для C значение NC=0N_C=0; нормирование потребовало бы деления на ноль, поэтому распределения MLE нет.
  4. Между документами появится EOS(1)→BOS(0). В склеенной последовательности девять токенов, а значит, восемь соседних пар — на одну больше, чем в исходных документах.
  5. Два полных окна перекрываются. Переходы A→A и A→B входят в оба окна, поэтому каждый из них был бы учтён дважды.
  6. Знаменатель равен 3+(1/2)×5=11/23+(1/2)\times5=11/2. Числители [1/2,1/2,3/2,5/2,1/2][1/2,1/2,3/2,5/2,1/2] дают строку [1/11,1/11,3/11,5/11,1/11][1/11,1/11,3/11,5/11,1/11].
  7. Для A сумма числителей равна 1+1+2+3+1=81+1+2+3+1=8, для C55. В обоих случаях она совпадает со знаменателем, поэтому после деления сумма вероятностей равна единице.
  8. В сглаженном распределении после A единственный максимум у B. После C все ID [0,1,2,3,4] имеют одинаковую максимальную вероятность. Порядок ID по возрастанию нужен только для воспроизводимого вывода и не выражает предпочтение модели.
  9. Нет. Валидационная выборка нужна для оценки уже зафиксированной модели. Если пересчитать таблицу с учётом этих переходов, сведения из валидационной выборки попадут в параметры модели.

Проверка понимания: фраза «C не встретился после A, поэтому строка C не определена» смешивает столбец и строку. Отсутствие перехода A→C относится к одной ячейке существующей строки A. Распределения MLE для C нет по другой причине: в обучающих данных ни один переход не начинается с C.

Зафиксируйте первую модель, которая возвращает полное распределение вероятностей

Теперь в курсе есть модель, которая принимает ID текущего токена ii и возвращает вероятность для каждого возможного следующего токена jj. Она учитывает только один токен контекста и не умеет переносить наблюдения из одного контекста на похожие контексты. Тем не менее результат уже имеет ту же форму, что и выход декодера: распределение вероятностей по всему словарю.

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