N-gram
テキストを N 文字または N 単語ずつの連続部分列に分割する手法。検索やテキスト類似度の計算に使われる。
N-gram は、テキストを N 個の連続する文字または単語の部分列に分割する手法です。N=1 をユニグラム (unigram)、N=2 をバイグラム (bigram)、N=3 をトライグラム (trigram) と呼びます。たとえば「東京都」を文字バイグラムに分割すると「東京」「京都」の 2 つが得られます。文字単位だけでなく単語単位の N-gram もあり、「I love Tokyo」の単語バイグラムは「I love」「love Tokyo」となります。自然言語処理や情報検索の分野で古くから使われている基礎的な手法であり、現在でも多くのシステムの中核を担っています。
N-gram の最大の利点は、形態素解析や辞書を必要としない点です。日本語や中国語のように単語の区切りが明確でない言語でも、文字列を機械的にスライドするだけでトークンを生成できます。全文検索エンジンの Elasticsearch や Apache Solr では N-gram トークナイザが標準で提供されており、言語を問わない検索インデックスの構築に活用されています。
N をいくつにするかは検索の当たり方を直接左右します。Elasticsearch の ngram トークナイザは既定で下限が 1 文字、上限が 2 文字です。公式ドキュメントは、通常は下限と上限を同じ値に揃えるのが妥当で、出発点としては 3 文字が良いとしています。理由として、長さを短くするほど多くの文書がマッチする一方でマッチの質は下がる、と説明されています。幅を広げたい場合は上限と下限の差の許容量を決めている index.max_ngram_diff の調整が必要で、安易に広い範囲を登録しようとするとインデックス作成の段階で弾かれる作りになっています。「とりあえず 1 文字から 5 文字まで全部入れる」という設計に歯止めがかかっているわけです。
テキスト類似度の計算にも N-gram は広く使われています。2 つのテキストから生成した N-gram 集合の重なり具合を Jaccard 係数やコサイン類似度で比較することで、文書間の類似性を数値化できます。この手法はスペルチェック、あいまい検索、盗用検出、レコメンデーションエンジンなど多様な場面で応用されています。単語の意味を解釈しなくても文字の並びの重なりだけで数値が出るため、対応言語を増やしても仕組みをほとんど変えずに使い回せる点が実務上の強みです。
N-gram は言語モデルの基盤技術としても重要な役割を果たしてきました。N-gram 言語モデルでは、直前の N-1 個の単語から次の単語の出現確率を推定します。深層学習が主流になる以前は、機械翻訳や音声認識の中核技術として広く採用されていました。深層学習が主流になった後も、計算コストが低く挙動を説明しやすいという利点から、軽量なテキスト分類やフィルタリングの用途では現役です。
一方で N-gram にはいくつかの注意点があります。まず N を大きくしたときにコストがどこで増えるのかを取り違えやすい点です。1 つのテキストから取り出される N-gram の個数そのものは、N が大きいほどむしろ減ります。増えるのは「あり得る N-gram の種類」の方で、こちらは文字種の数の N 乗というオーダーで組み合わせ的に膨らみます。その結果、実在のテキストには大半の種類が一度も現れず、言語モデルでは出現数がゼロの N-gram をどう扱うか (スムージング) が課題になります。検索インデックスの容量が実際に膨らむのは、1 文字から N 文字までを同時に登録する構成を選んだ場合です。また「京都」を検索した際に「東京都」がヒットするように、意味的に無関係な部分列がマッチする偽陽性の問題があります。この問題を緩和するために、N-gram と形態素解析を組み合わせたハイブリッド方式を採用するシステムも少なくありません。
文字数カウントの観点では、N-gram の分割結果はテキストの長さに直接依存します。文字数 L のテキストから生成される文字 N-gram の数は L-N+1 個であり、テキストが短いほど生成される N-gram の数も少なくなります。注意したいのは L が N より小さい場合で、この式は計算上マイナスになりますが実際の個数は 0 個です。3 文字のテキストに 5-gram を当てても 1 つも取り出せません。短い見出しや検索クエリを扱う設計では、N を大きくしすぎるとインデックス側にも検索語側にも何も登録されず、「エラーは出ないのにヒットが常にゼロ」という気づきにくい形で失敗します。扱うテキストの最短長を先に把握しておけば、この落とし穴は設計段階で避けられます。