46.6kデモを予約するログイン無料で始める

自然言語処理の基礎:トークン、Nグラム、Bag-of-Wordsモデル

この記事では、今日のあらゆる言語モデルを理解するために不可欠な、自然言語処理の基礎について説明します。

#はじめに

ChatGPT(GPT-3.5)や、Pi, Claude, BardなどのLLMが一世を風靡しました。これらの言語モデルは一体どのように機能し、なぜ学習させたタスクに対してうまく機能するのだろうか?完全な答えは誰にもわからないが、自然言語処理の基礎を正しく理解することは、その内部構造を解明するのに役立つ。特に、トークンとn-gramsに関する知識は、ほぼすべての最新の自己回帰モデルと自己符号化モデル(そして今日のLLMに広く適用可能)を理解する上で非常に貴重です。さっそく、その世界に飛び込んでみよう。

トークンとN-gram

CやC++の入門コースでは、文字列の概念を早い段階で学ぶことが多い。例えばC言語の文字列は、ヌル文字で終わる文字の配列として表現できる:

char my_str[128] = "Milvus";

この例では、各文字は離散的な単位と考えることができ、これらの各文字の組み合わせが意味を持つテキストになります。この場合、my_strは世界で最も広く採用されているベクトルデータベースに対応しています。簡単に言うと、これがn-gramの定義である。一連の文字(または次の段落で説明する他の離散的な単位)であり、それらをつなぎ合わせると首尾一貫した意味を持つ。この例ではNは文字列の総文字数(この場合は7)に相当する。

n-gramの概念は1文字である必要はなく、単語にも拡張できる。例えば、以下の文字列は単語のトリグラム(3-gram)である:

char my_str[128] = "Milvus ベクトルデータベース"

上の例では、my_strが3つの単語から構成されていることは明らかだが、句読点を考慮すると少し厄介なことになる:

char my_str[128] = "ミルヴァスのアーキテクチャは比類ない"

上記の文字列は厳密に言えば4つの単語だが、最初の単語 Milvus's は所有格の名詞で、別の単語 Milvus をベースとしている。言語モデルの場合、このような単語を個別の単位に分割することで、余分な文脈を保持することができる:Milvusと's` です。これらは「トークン」と呼ばれ、文章を単語に分割する基本的な方法は「トークン化」と呼ばれる。この戦略により、上の文字列はトークンの5グラムになります。

最近の言語モデルはすべて、データを変換する前に何らかの入力トークン化を行います。例えば、WordPiece は、BERT のほとんどのフレーバーで使用されている一般的なものです。このシリーズでは、トークナイザについてあまり深く掘り下げません。もう少し詳しく知りたい方は、Huggingface's tokenizer summary をご覧ください。

N-gram モデル

n-gramの理解が深まったところで、次は_n-gramモデルについて説明します。簡単に言うと、n-gramモデルとは、あるトークンが既存のトークン列の後に現れる確率を出力する、単純な確率論的言語モデルです。例えば、文やフレーズの中で特定のトークンが他のトークンの後に続く確率(pp)をモデル化することができます:

p(database∣vector)=0.1p(database | vector) = 0.1

上の文は、この特定の言語モデルでは、単語 "vector "は10%の確率で単語 "database "に続くということを表している。n-gramモデルの場合、これらのモデルは常に入力文書のコーパスに含まれるビグラムのカウントを見て計算されますが、他の言語モデルでは、手動で設定したり、機械学習モデルの出力から取得したりすることができます。

上の例はビグラムモデルですが、これを任意の長さのシーケンスに拡張することができます。以下はトリグラムの例である:

p(database∣Milvus,vector)=0.9p(database | Milvus, vector) = 0.9

これは、単語 "database "が90%の確率でトークン "Milvus vector "に続くという事実を表している。同様に

p(chocolate∣Milvus,vector)=0.001p(chocolate | Milvus, vector) = 0.001

これは、"Milvus vector "の後に続く単語が "chocolate "である可能性は低い(正確には0.1%の確率)という事実をコード化している。これをもっと長いシーケンスに適用すると、次のようになる:

p(Milvus∣the,most,widely,adopted,vector,database,is)=0.999p(Milvus | the, most, widely, adopted, vector, database, is) = 0.999

さて、確率的n-gramモデルについてしっかり理解したところで、より重要な問題に目を向けましょう。この確率はどうやって計算するのだろうか?簡単で単純な答えは、文書や文書コーパスに出現する回数を数えるということだ。次の3つのフレーズを例に説明しよう(それぞれの文頭の<S>は特別な文頭トークンを表す)。わかりやすくするために、それぞれの文の最後のピリオドとその前のトークンの間に余分なスペースを追加しました。

  • <Milvusは最も広く採用されているベクトルデータベースである。
  • <Milvusによるベクトル検索 .`
  • <S> Milvusはロックである .

、Milvus、vector`のいずれかで始まるビグラムを列挙してみよう:

python some_bigrams = { # これらのビグラムはで始まる ("", "Milvus"):2, ("", "vector"):1, # これらのビッグラムはMilvusで始まる ("Milvus", "is"):1, ("Milvus", "."):1, ("Milvus", "rocks"):1, # これらのビッグラムはvectorで始まる ("vector", "database"):1, ("vector", "search"):1 }


これらの出現回数があれば、各トークンの出現回数の合計で正規化することで、確率を計算することができる。例えば

$$
    p(Milvus | <S>) = \frac{c(<S>, Milvus)}{c(<S>)} = \frac{2}{3}.
$$

同様に

$$
    p(rocks | Milvus) = \frac{c(Milvus, rocks)}{c(Milvus)} = \frac{1}{3}.
    p(database | vector) = \frac{c(vector database)}{c(vector)} = \frac{1}{2}.
$$

この知識を持って、これを一般化し、ビグラムモデルを構築するコードを書いてみよう。簡単のため、すべての入力文書の各トークンが空白で区切られていると仮定します(最近のトークン解析器はもっと複雑なルールを持つことが多いことを、前のセクションで思い出してください)。まずはモデルそのもの、つまりビグラム数とトークン数の定義から始めよう。

python
from typing import Dict, Tuple
from collections import defaultdict


# キーはトークンに対応する
# 値は出現回数
token_counts = defaultdict(int)

# キーは2タプルのビグラムペア
# 値は出現数
bigram_counts = defaultdict(int)


def build_bigram_model(corpus: Dict[str]):
    """ビグラムモデル。
    """

    # コーパスの全文書をループする
    for doc in corpus:

        prev = "<S>"
        for word in doc.split():

            # トークン数の更新
            token_counts[word] += 1

            # bigramのカウントを更新
            bigram = (prev, word)
            bigram_counts[bigram] += 1

            prev = word

        # 確率が1になるように、ダミーの終端トークンを追加する。
        bigram_counts[(word, "</S>")] += 1

    return (token_counts, bigram_counts)


def bigram_probability(bigram: Tuple[str]):
    """コーパスからbigramの尤度を計算する。
    """

    return bigram_counts[bigram] / token_counts[bigram[0]]。

次に build_bigram_model はコーパスの文書全体をループし、それぞれを空白で分割してから bigram とトークンのカウントを格納する。次に bigram_probability 関数を呼び出すことができ、対応する bigram の数とトークンの数を調べて比率を返す。

では、バイデン大統領の2022年の一般教書演説でテストしてみよう。よろしければ、transcriptをダウンロードして、上のコードで試してみてください。

パイソン

with open("state_of_the_union.txt", "r") as f...: ... build_bigram_model([f.read()]) ... print(bigram_probability(("keep", "moving")))) 0.07692307692307693


## 単語の袋モデル

n-gramの議論に続いて、_bag-of-wordsモデル_ (BoW)についても簡単に触れておこう。BoWモデルは文書やコーパスを順序のないトークンの集合として表現する。この意味で、各トークンの出現頻度は維持するが、各文書内での出現順序は無視する。そのため、BoWモデルの文書全体はスパースベクトルに変換することができ、ベクトルの各エントリーは、特定の単語が文書内に出現する頻度に対応する。ここでは「Milvus is the most widely adopted [vector database](https://zilliz.com/).Milvusを使った[ベクトル検索](https://zilliz.com/learn/vector-similarity-search)は簡単です。"をBoWスパースベクトルとして表現します:

パイソン
# 限定された語彙
bow_vector = [
    0, # a
    1, # 採用
    0, # バッグ
    0, # 本
    0, # 座標
    1, # データベース
    1, # イージー
    0, # 素晴らしい
    0, # 良い
    0, # 素晴らしい
    2, # is
    0, #ジャグル
    2, #ミルバス
    1, # 最も
    0, #決して
    0, #証明
    0, #商
    0, # 比
    0, # 直す
    1, # 検索
    1, # その
    0, # 畝る
    2, # ベクトル
    1, # 広く
    1, # with
    0, # yes
    0, # ゼブラ
]

これらのスパース・ベクトルは、テキストやセンチメントの分類など、様々なNLPタスクで使用することができる。このブログポストではBoWモデルの学習と推論については触れません。もう少し詳しく知りたい方は、Jason Brownleeのブログが良いリソースです。

BoWモデルは理解しやすく使いやすいが、明確な限界がある。文脈や個々のトークンの意味的な意味を捉えることができないため、最も単純なタスクを超えるものには適していない。このような理由から、現在ではもっぱらニューラルネットワークが使用されている。

自然言語処理の基礎 まとめ

この投稿では、自然言語処理の基礎(https://zilliz.com/learn/A-Beginner-Guide-to-Natural-Language-Processing)として、トークン化、N-gram、Bag-of-wordsモデルの3つを取り上げました。特に、n-gramに関する概念は、自己回帰モデルや自己符号化モデルの学習方法に関する後のトピックで広く役立ちます。

次回のチュートリアルでは、「最新の」NLP、つまりリカレントネットワークとテキスト埋め込みについて説明します。ご期待ください!