ユニグラム言語モデルによるサブワード分割の理論と実装

BPE(Byte Pair Encoding)でサブワード分割を学ぶと、たいてい次の疑問にぶつかります。「頻度の高いペアを順にくっつけていく——それはわかった。でも、その分割が『良い』とどうやって言えるの?」と。BPE はマージの手順を決めるだけで、「lower を low+er に切るのと lo+wer に切るの、どちらが良いか」を測るものさしを持っていません。ただ手続きが先に決まっているだけです。

この問いに正面から答えるのが、本記事で扱う Unigram 言語モデル(Unigram LM)によるサブワード分割です。発想はこうです——各サブワードに「確率」を割り当て、分割全体の良さをその確率の積で測る。そうすれば「どの分割が最も起こりやすいか」を数値で比較でき、いちばん確率の高い分割を選べます。BPE が「手続き」だったのに対し、Unigram は「確率モデル」なのです。

この違いは実用面でも効いてきます。Unigram 方式は Google の SentencePiece の既定アルゴリズムで、mT5・XLM-R・ALBERT・T5 など数多くの主要モデルが採用しています。確率モデルであることの恩恵は大きく、たとえば——

  • 原理的な分割の選択: 「確率最大の分割」という明確な基準(Viterbi)で切れる
  • subword regularization: 1つの単語をわざと複数通りに切ってサンプリングし、データ拡張として使える(頑健性が上がる)
  • トップダウンな語彙構築: 巨大な種語彙から「貢献の小さいピース」を確率的に削って目標サイズに絞れる

本記事では、この Unigram LM を確率モデルの定式化から始め、周辺尤度・EM アルゴリズム・Viterbi 分割・語彙削減・subword regularization までを、スクラッチ実装と10枚の図で一つずつ解き明かします。

BPEは下から貪欲にマージ、Unigramは大きな語彙から確率で削る

上の図が、2つの方式の対比です。BPE(左)は文字から出発して頻度の高いペアを積み上げていく「ボトムアップ」。Unigram(右)は巨大な種語彙から出発して、確率的に貢献の小さいものを削り落としていく「トップダウン」。同じサブワード分割でも、作り方の思想が正反対なのが見て取れます。

本記事の内容

  • Unigram LM の確率モデルとしての定式化(なぜ「言語モデル」なのか)
  • 分割ラティスと周辺尤度:あらゆる分割を足し合わせる
  • EM アルゴリズム(forward-backward)による確率の学習
  • Viterbi による最良分割と、語彙削減のプロセス
  • subword regularization:確率的に分割を揺らして頑健にする

前提知識

以下の記事を先に読むと理解が深まります。

EM アルゴリズムの考え方(隠れ変数を期待値で埋めて反復する)を知っていると、学習の節がぐっと楽になります。

分割には「候補」が何通りもある

Unigram の話に入る前に、まず素朴な事実を確認しましょう。1つの単語をサブワードに切る方法は、1通りではありません。たとえば “lower” を切るなら、”low” + “er” でもいいし、”lo” + “w” + “er” でも、極端には “l” + “o” + “w” + “e” + “r” と1文字ずつでもいい。語彙にあるpiece の組み合わせだけ、分割の候補が存在します。

lowerの分割ラティス。lowerは複数の分割候補を持つ

この図は、単語 “lower” の分割候補をラティス(格子)として描いたものです。文字と文字の境目を「ノード」とし、語彙にあるサブワードを「そのノード間をつなぐエッジ」として表しています。ノード0からノード5まで、どのエッジをたどるかで分割が決まります。低い弧(”low” や “er”)を通れば少ないトークンに、上の直線(1文字ずつ)を通れば多くのトークンになります。

問題は、「この何通りもある経路のうち、どれを選ぶべきか」です。BPE はマージ規則の順序で機械的に1つに決めていました。Unigram はここに確率を持ち込みます。各サブワードに確率を割り当て、経路(分割)全体の確率が最大のものを選ぶ——次はその確率モデルを定式化します。

Unigram 言語モデルの定式化

Unigram LM の核心は、たった1つの仮定です。「サブワードは互いに独立に選ばれる」と考えます。つまり、ある分割 $s = (x_1, x_2, \dots, x_k)$($k$ 個のサブワードの列)が起こる確率を、各サブワードの確率ので表します:

$$ \begin{equation} P(s) = \prod_{i=1}^{k} p(x_i) \end{equation} $$

ここで $p(x_i)$ は、サブワード $x_i$ が単独で現れる確率です。前後にどんなサブワードが来るかは一切考慮しません——だからこそ「ユニグラム(1-gram)」なのです。バイグラム(2-gram)なら $p(x_i \mid x_{i-1})$ のように直前を見ますが、ユニグラムは各ピースをてんでバラバラに、独立と見なします。

分割の確率は各サブワード確率の積。ユニグラムは独立と仮定

なぜこれを「言語モデル」と呼ぶのでしょうか。言語モデルとは本来「単語列の起こりやすさに確率を与えるもの」です。Unigram トークナイザーは、その最も単純な形——サブワードの列に、独立性を仮定して確率を与えるモデル——になっています。単語列ではなくサブワード列を対象にした、いちばん素朴な言語モデルなのです。

この定義から、”lower” を “low”+”er” に切る確率は $p(\text{low}) \times p(\text{er})$、”lo”+”w”+”er” に切る確率は $p(\text{lo}) \times p(\text{w}) \times p(\text{er})$ と計算できます。あとは確率が最大の分割を選べばよい——のですが、その前に「そもそも確率 $p(x)$ をどう決めるのか」という学習の問題を片付けねばなりません。そのために必要なのが、次の周辺尤度という考え方です。

周辺尤度:あらゆる分割を足し合わせる

確率 $p(x)$ を学習したいのですが、ここに厄介な事情があります。コーパスには “lower” という単語は書いてありますが、「それがどう分割されるべきか」は書いてありません。分割は隠れた変数なのです。この状況は、GMM でどのデータ点がどのクラスタに属するかが隠れているのと、まったく同じ構造です。

隠れ変数を扱う定石は、「あり得る値すべてについて足し合わせる(周辺化する)」ことです。単語 $w$ が生成される確率を、$w$ を作れるすべての分割について足し合わせて定義します:

$$ \begin{equation} P(w) = \sum_{s \in S(w)} \prod_{x \in s} p(x) \end{equation} $$

ここで $S(w)$ は $w$ を作れる分割の集合です。この $P(w)$ を周辺尤度と呼びます。「low+er で作る道」も「lo+w+er で作る道」も、$w$ にたどり着くならすべて足す——という考え方です。

周辺尤度はあらゆる分割の確率の和

学習の目標は、コーパス全体の周辺尤度(の対数の和)を最大にする $p(x)$ を見つけることです:

$$ \mathcal{L} = \sum_{w} \text{freq}(w) \cdot \log P(w) = \sum_{w} \text{freq}(w) \cdot \log \sum_{s \in S(w)} \prod_{x \in s} p(x) $$

「よく出る単語ほど、その周辺尤度が高くなるように確率を配分する」わけです。この対数の中に和がある形($\log \sum$)は直接最大化するのが難しく、まさに EM アルゴリズムの出番です。次の節で、この最大化を EM でどう解くかを見ます。

EM アルゴリズムによる学習

EM は「隠れ変数(ここでは分割)を期待値で埋めて、パラメータ(確率 $p(x)$)を更新する」ことを繰り返す手法です。Unigram の場合、2つのステップはこうなります。

E ステップ:各サブワードの「期待出現回数」を求める。 現在の確率 $p(x)$ のもとで、各単語がどう分割されそうかの「重み」を計算し、各サブワードが平均して何回使われるかを見積もります。ここで、分割の候補は指数的にたくさんありますが、forward-backward アルゴリズムを使えば、全候補を明示的に列挙せずに効率よく期待回数を計算できます。

forward 確率 $\alpha_j$ は「単語の先頭から位置 $j$ までを作るすべての分割の確率の和」、backward 確率 $\beta_i$ は「位置 $i$ から末尾までを作る確率の和」です。すると、サブワード $w_{i:j}$(位置 $i$ から $j$)が使われる事後確率は

$$ \gamma_{i:j} = \frac{\alpha_i \cdot p(w_{i:j}) \cdot \beta_j}{P(w)} $$

で求まります(分母 $P(w) = \alpha_n$ は周辺尤度)。この式は「前半をどう作ってもよく($\alpha_i$)、このピースを通り($p(w_{i:j})$)、後半をどう作ってもよい($\beta_j$)」経路の割合を表しています。これを全単語・全位置で足し、単語頻度で重み付ければ、各サブワードの期待出現回数が得られます。

M ステップ:確率を更新する。 期待出現回数を総数で割って正規化するだけです:

$$ p(x) \leftarrow \frac{\text{期待出現回数}(x)}{\sum_{x’} \text{期待出現回数}(x’)} $$

EMのE-stepとM-stepの反復ループ

この E → M を繰り返すと、周辺尤度は単調に増加することが理論的に保証されています(EM の一般的性質)。実装して確かめましょう。まず forward-backward と EM の中核です。

from math import log, exp, inf
from collections import Counter

def logsumexp(xs):
    xs = [x for x in xs if x > -inf]
    if not xs: return -inf
    m = max(xs)
    return m + log(sum(exp(x - m) for x in xs))

def lattice_fb(word, logp, max_len=6):
    """forward-backward。alpha, beta, logZ(=周辺対数尤度) を返す"""
    n = len(word)
    alpha = [-inf]*(n+1); alpha[0] = 0.0
    for j in range(1, n+1):
        alpha[j] = logsumexp([alpha[i] + logp[word[i:j]]
                              for i in range(max(0, j-max_len), j)
                              if word[i:j] in logp and alpha[i] > -inf])
    beta = [-inf]*(n+1); beta[n] = 0.0
    for i in range(n-1, -1, -1):
        beta[i] = logsumexp([logp[word[i:j]] + beta[j]
                             for j in range(i+1, min(i+max_len, n)+1)
                             if word[i:j] in logp and beta[j] > -inf])
    return alpha, beta, alpha[n]

対数空間で計算しているのは、確率の積がすぐアンダーフローするからです。次に EM の1ステップです。期待カウントを集めて確率を更新し、更新前の周辺対数尤度を返します。

def em_step(corpus, logp, max_len=6):
    exp_counts = Counter(); ll = 0.0
    for w, f in corpus.items():
        alpha, beta, logZ = lattice_fb(w, logp, max_len)
        ll += f * logZ                              # コーパスの周辺対数尤度
        n = len(w)
        for i in range(n):
            for j in range(i+1, min(i+max_len, n)+1):
                piece = w[i:j]
                if piece in logp:
                    # このピースの事後確率 = alpha_i * p * beta_j / Z
                    exp_counts[piece] += f * exp(alpha[i] + logp[piece] + beta[j] - logZ)
    total = sum(exp_counts.values())
    new_logp = {p: log(c/total) for p, c in exp_counts.items() if c > 0}
    return new_logp, ll

種語彙(seed vocab)として「コーパスに現れる全部分文字列」を初期確率つきで用意し、EM を回します。

CORPUS = Counter({"low":6,"lower":4,"lowest":3,"slow":3,"slower":2,
                  "new":6,"newer":4,"newest":3,"wide":4,"wider":3,"widest":2,
                  "fast":4,"faster":3,"fastest":2})

def seed_vocab(corpus, max_len=6):
    counts, chars = Counter(), set()
    for w, f in corpus.items():
        chars.update(w)
        for i in range(len(w)):
            for j in range(i+1, min(i+max_len, len(w))+1):
                counts[w[i:j]] += f
    total = sum(counts.values()) + len(chars)
    return {p: log((c+1)/total) for p, c in counts.items()}

logp = seed_vocab(CORPUS)
lls = []
for _ in range(12):
    logp, ll = em_step(CORPUS, logp)
    lls.append(ll)
print([round(x, 2) for x in lls])

このコードを実行すると、周辺対数尤度は [-221.36, -144.34, -132.17, -130.67, -130.5, ...] と、反復のたびに増えていき、やがて収束します。図にすると一目瞭然です。

EMを回すほど周辺対数尤度が単調に増加する

曲線はきれいな単調増加を描き、数回で頭打ちになっています。最初の1回で大きく跳ね上がるのは、初期のいい加減な確率から、コーパスをうまく説明する確率へと一気に補正が入るためです。

なぜ EM を回すと尤度が必ず上がるのでしょうか。直感的には、EM は「対数の中に和がある」扱いにくい目的関数 $\log \sum_s \prod p(x)$ を直接いじる代わりに、その下界を最大化しています。E ステップで「現在の確率のもとでの分割の事後分布」を求め、それを重みにして下界(各分割の対数確率の期待値)を作ります。M ステップはこの下界を最大にする確率を閉じた形で与えます。下界は真の対数尤度を決して超えず、かつ現在の点で接しているので、下界を上げれば真の尤度も上がる——これが単調性の理屈です。GMM で「responsibility を求めてから平均・分散を更新する」のとまったく同じ構造だと気づけば、見通しがよくなります。

学習した確率が手に入ったので、いよいよ「最良の分割」を求めてみましょう。

Viterbi による最良分割

学習した $p(x)$ のもとで、単語を確率最大の1通りに分割するには、Viterbi アルゴリズムを使います。forward-backward が「全経路の和」を計算したのに対し、Viterbi は「最良の1経路」を動的計画法で探します。和(logsumexp)が最大(max)に変わるだけで、構造はそっくりです。

def viterbi(word, logp, max_len=6):
    n = len(word)
    best = [-inf]*(n+1); best[0] = 0.0; back = [0]*(n+1)
    for j in range(1, n+1):
        for i in range(max(0, j-max_len), j):
            piece = word[i:j]
            if piece in logp and best[i] > -inf:
                s = best[i] + logp[piece]
                if s > best[j]:
                    best[j] = s; back[j] = i
    seg, j = [], n
    while j > 0:
        i = back[j]; seg.append(word[i:j]); j = i
    return seg[::-1], best[n]

ただし、種語彙のまま Viterbi をかけると、”lower” も “newest” も丸ごと1トークンに切れてしまいます。単語そのものが語彙に入っていて高い確率を持つからです。実務では、この巨大な種語彙を目標サイズまで削る必要があります。それが次の語彙削減です。ここでは先に、削減後のモデルでの分割結果を見ておきます。

学習後のViterbi分割。頻出語は1トークン、未知語は語幹と接尾辞に分解

削減後のモデルで分割すると、興味深い振る舞いが見られます。コーパスに頻出する “lower”・”newest”・”faster”・”widest” は1トークンのまま残ります(効率的)。一方、コーパスに無い活用形 “slowest”・”fastest” は、既知のピースを使って “slow”+”est”・”fast”+”est” に分解されます。頻出語は丸ごと覚え、未知語は語幹と接尾辞に一般化する——これこそ Unigram が「意味のある単位」を学べている証拠です。この賢い振る舞いを支えるのが、次の語彙削減です。

語彙削減:大きな語彙から確率で削る

Unigram の語彙構築は、BPE と逆向きの「トップダウン」です。まず巨大な種語彙(全部分文字列)から出発し、EM で確率を推定したうえで、「あっても無くても尤度にほとんど影響しないピース」を削っていきます。各ピースについて「それを消したときに周辺尤度がどれだけ下がるか(=そのピースの貢献度)」を評価し、貢献の小さいものから一定割合を削除。削除後にまた EM で確率を推定し直す——これを目標の語彙サイズになるまで繰り返します。

ここで大事な安全弁が1つあります。1文字のピースは決して削らないことです。1文字さえ残っていれば、どんな未知語でも最低限「1文字ずつ」には分割できるので、「分割不能で壊れる」ことがありません。これは前回の記事で見た byte-level のフォールバックと同じ発想です。実装では期待出現回数を貢献度の代理として使い、下位を削ります。

def prune(corpus, logp, keep_ratio=0.7, max_len=6):
    exp_counts = Counter()
    for w, f in corpus.items():
        alpha, beta, logZ = lattice_fb(w, logp, max_len)
        n = len(w)
        for i in range(n):
            for j in range(i+1, min(i+max_len, n)+1):
                piece = w[i:j]
                if piece in logp:
                    exp_counts[piece] += f * exp(alpha[i]+logp[piece]+beta[j]-logZ)
    multi = sorted([(p, c) for p, c in exp_counts.items() if len(p) >= 2],
                   key=lambda x: x[1], reverse=True)
    singles = [p for p in logp if len(p) == 1]              # 1文字は必ず残す
    kept = set(p for p, _ in multi[:int(len(multi)*keep_ratio)]) | set(singles)
    total = sum(exp_counts[p] for p in kept)
    return {p: log(exp_counts[p]/total) for p in kept if exp_counts[p] > 0}

def train_em(corpus, logp, n_iter):
    for _ in range(n_iter):
        logp, _ = em_step(corpus, logp)
    return logp

# 種語彙 → EM → 削減 → EM … を繰り返す
logp = train_em(CORPUS, seed_vocab(CORPUS), 8)
sizes = [len(logp)]
for _ in range(5):
    logp = prune(CORPUS, logp, 0.7)     # 貢献の小さいピースを削る
    logp = train_em(CORPUS, logp, 4)    # 削減後にまた確率を推定
    sizes.append(len(logp))
print(sizes)   # 例: [72, 29, 21, 15, 11, 8]

語彙削減で種語彙72から目標サイズへ絞り込む

図のとおり、語彙サイズは 72 → 29 → 21 → 15 → 11 → 8 と段階的に減っていきます。各段階で「本当に役に立つピース」だけが生き残り、冗長な部分文字列(”lowe” や “astest” のような中途半端な断片)が削られていきます。実際の SentencePiece では、これを数万〜十数万の語彙サイズになるまで大規模コーパスで行います。BPE が「足りないものを足していく」のに対し、Unigram は「多すぎるものを削っていく」——同じ語彙に別方向からたどり着くわけです。

削りすぎると語幹まで消えてしまうので、途中の適度な語彙サイズで止めるのが実用的です。先ほど(Viterbi の節)の図で見た分割は、2ラウンドだけ削って約21ピースにしたモデルのものです。これを明示的に再現してみます。

model = train_em(CORPUS, seed_vocab(CORPUS), 8)
for _ in range(2):                                  # 2ラウンドだけ削減(約21ピース)
    model = prune(CORPUS, model, 0.7)
    model = train_em(CORPUS, model, 4)
print({w: viterbi(w, model)[0] for w in ["lower", "newest", "faster", "widest"]})
print({w: viterbi(w, model)[0] for w in ["slowest", "fastest"]})
# 頻出: {'lower': ['lower'], 'newest': ['newest'], 'faster': ['faster'], 'widest': ['widest']}
# 未知: {'slowest': ['slow', 'est'], 'fastest': ['fast', 'est']}

コメントの出力どおり、頻出語は1トークンのまま、未知の活用形は語幹+接尾辞に分かれます。これが先ほどの図の中身です。

確率モデルであることの恩恵は、良い分割を選べることだけではありません。もう1つ、BPE には真似のできない強力な使い方があります。分割をわざと揺らすことです。

subword regularization:分割を揺らして頑健にする

BPE では、ある単語の分割はほぼ1通りに固定されます。しかし Unigram は確率モデルなので、「確率に応じて複数の分割をサンプリングする」ことができます。これを subword regularization と呼びます。学習のたびに同じ単語を少しずつ違う分割で見せることで、モデルが特定の分割に過度に依存しなくなり、頑健性が上がる——一種のデータ拡張です。

サンプリングは、forward 確率を使った後方サンプリングで行えます。末尾から先頭に向かって、各ノードで「そこに至る経路の確率」に比例して直前のノードを選んでいきます。さらに、確率を温度 $T$ で調整すると、多様性を制御できます。$T$ を大きくするほど確率の差が平坦になり、普段は選ばれない分割も出やすくなります。

import numpy as np

def sample_seg(word, logp, T=1.0, rng=None, max_len=6):
    rng = rng or np.random.default_rng(0)
    n = len(word)
    lp = {p: v/T for p, v in logp.items()}       # 温度で平坦化
    alpha = [-inf]*(n+1); alpha[0] = 0.0
    for j in range(1, n+1):
        alpha[j] = logsumexp([alpha[i] + lp[word[i:j]]
                              for i in range(max(0, j-max_len), j)
                              if word[i:j] in lp and alpha[i] > -inf])
    seg, j = [], n
    while j > 0:                                  # 後方サンプリング
        cands, ws = [], []
        for i in range(max(0, j-max_len), j):
            piece = word[i:j]
            if piece in lp and alpha[i] > -inf:
                cands.append(i); ws.append(alpha[i] + lp[piece] - alpha[j])
        ps = np.array([exp(w) for w in ws]); ps /= ps.sum()
        i = cands[rng.choice(len(cands), p=ps)]
        seg.append(word[i:j]); j = i
    return seg[::-1]

削減後のモデルで、”lowest” と “newest” を何度もサンプリングし、「1トークンのまま(lowest)」ではなく「語幹+接尾辞に分割された(low+est)」割合が、温度でどう変わるかを測ってみます。

温度Tを上げると分割の多様性が増す

結果は明快です。温度 $T=1$ では分割される割合は約13%(ほとんどは1トークンのまま)ですが、$T=2$ で約29%、$T=3$ で約39% と、温度を上げるほど「別の切り方」が顔を出します。学習時にこの揺らぎを注入すれば、モデルは “lowest” を丸ごと見ることも “low”+”est” と見ることも経験し、未知の活用形にも強くなります。Viterbi で1通りに決め打つ(推論時)のと、サンプリングで揺らす(学習時)のを使い分けられるのが、確率モデルならではの柔軟さです。

ここまでの Unigram の特徴を、冒頭で対比した BPE と並べて整理しておきましょう。

BPE との比較まとめ

BPEとUnigram LMの比較表

表のとおり、両者は多くの点で対照的です。BPE は下から貪欲にマージする決定的な手続きで、確率モデルを持たず、分割は基本的に1通り。GPT 系の byte-level トークナイザーで使われます。一方 Unigram LM は、上から確率で削るトップダウンな構築で、各ピースに確率を持ち、Viterbi で最良分割を選びつつサンプリングもできる。SentencePiece を通じて mT5・XLM-R・ALBERT などで広く使われています。

どちらが優れているかは一概には言えません。BPE はシンプルで高速、Unigram は確率的な柔軟さと subword regularization が武器です。大切なのは、両者が「同じサブワード分割という目的地に、正反対のアプローチで到達している」と理解することです。

最後に、この Unigram を実務で動かすうえでの勘どころを、本記事のスクラッチ実装を踏まえて補足しておきます。

実装上の勘どころと SentencePiece

本記事の実装はミニマルですが、実際の SentencePiece も骨格は同じです。動かすうえで押さえておきたい点を挙げます。

計算量とサブワードの最大長。 forward-backward も Viterbi も、単語長 $n$ とサブワード最大長 $L$ に対して $O(nL)$ で回ります。$L$(コード中の max_len)を無制限にすると長い部分文字列がすべて候補になり、種語彙が爆発して計算も重くなります。実際には $L$ を十数文字程度に制限します。日本語のように空白がない言語では「一文まるごと」が1つの処理単位になり得るので、この上限がとくに効いてきます。

初期確率とスムージング。 種語彙の初期確率を頻度から作るとき、本記事では $+1$ のスムージングを入れました。これは、たまたま出現回数が偶然0や極端に小さいピースの確率が壊れないための安全策です。EM は初期値に依存する局所最適解しか保証しないので、この初期化は地味ながら結果に効きます。

空白の扱いと言語非依存性。 SentencePiece の重要な設計は、空白すら1つの記号()として語彙に含め、生のテキストから直接学習する点です。これにより「空白で単語に区切ってから処理する」という英語前提の手続きが不要になり、空白のない日本語・中国語でも同じ仕組みがそのまま動きます。トークナイザーが言語ごとにどう効くかは、前回の記事で詳しく扱いました。

規模。 本記事の種語彙は72ピースでしたが、実際には数百万文の大規模コーパスから出発し、目標語彙(3.2万〜25万など)まで削ります。原理は本記事とまったく同じで、規模が違うだけです。だからこそ、小さなコーパスでも仕組みを一度自分の手で動かしておくと、実際のトークナイザーの挙動が推測できるようになります。

まとめ

本記事では、Unigram 言語モデルによるサブワード分割を、確率モデルの定式化から実装まで解説しました。要点を整理します。

  • 確率モデル:分割の良さを各サブワード確率の積 $P(s)=\prod p(x_i)$ で測る。前後を見ない「ユニグラム」の言語モデル
  • 周辺尤度:分割は隠れ変数なので、あらゆる分割を足し合わせた $P(w)=\sum_s \prod p(x)$ を最大化する
  • EM(forward-backward):E ステップで各ピースの期待出現回数を求め、M ステップで正規化。周辺対数尤度が単調増加することを実測で確認
  • Viterbi:確率最大の分割を動的計画法で求める。頻出語は丸ごと、未知語は語幹+接尾辞に一般化
  • 語彙削減:巨大な種語彙から貢献の小さいピースを削るトップダウン構築(1文字は必ず残す)
  • subword regularization:確率的に分割をサンプリングし、温度で多様性を制御して頑健化

Unigram を理解すると、「トークナイザーは単なる文字列処理ではなく、サブワード上の確率モデルである」という視点が手に入ります。この視点は、多言語対応(前回の記事)や、言語モデル全体の確率的な理解へとまっすぐつながっています。

次のステップとして、以下の記事も参考にしてください。