ククログ

株式会社クリアコード > ククログ > Groongaのトークナイザー: TokenNgramとTokenBigramの違いは?

Groongaのトークナイザー: TokenNgramとTokenBigramの違いは?

Groongaのトークナイザーがいっぱいあるけど、どれを使えば良いのか迷っていた阿部です。

特にNgram関連がいっぱいあって迷います。 さらにいうとBigramをしたいときに TokenNgramTokenBigram のどちらを使えば良いのか迷います。 名前からは両方とも同じ処理ができそうです。 今回はこの疑問点について解説します。

この記事はグルカイ!第58回 の内容をククログとしてまとめました。

結論

さっそく結論ですが、Ngramをしたい場合は TokenNgram を使えば良いです。 他のNgram関連のトークナイザーは考えなくてよいです。

解説

TokenNgram はオプションによって動作を指定できます。 それにより他のNgram系のトークナイザーの処理はすべて TokenNgram で実現できます。

例えば、 TokenUnigramTokenBigramTokenTrigramTokenNgram で以下のように指定できます。

  • TokenUnigram: TokenNgram("n", 1)
  • TokenBigram: TokenNgram("n", 2)
  • TokenTrigram: TokenNgram("n", 3)

解説の補足

このような状況になっている理由は以下の2点です。

  1. 以前はオプションの書式が使えなかった
  2. Groongaは後方互換性を大事にしている

1. 以前はオプションの書式が使えなかった

オプションの書式とは () で指定しているものです。先ほどの例だと TokenNgram("n", 1)("n", 1) の部分です。

以前はオプションで挙動を変えられませんでした。挙動を変えたい場合は その変えたい挙動ごとに TokenBigram などを追加する必要がありました。 今は TokenNgram のオプションで他のすべてのNgramのトークナイザーと同じ 処理ができるようになっています。

2. Groongaは後方互換性を大事にしている

TokenNgram があれば十分なので他のトークナイザーは消しても良いのですが、 Groongaは後方互換性を大事にしているため、TokenNgram 以外も引き続き 利用できるようになっています。

まとめ

歴史的経緯と後方互換性のためにたくさんのNgramのトークナイザーがありますが、 TokenNgram を使えばOKです。

補足: GroongaのNgram

GroongaのNgramは純粋なNgramではありません。

TokenBigram の説明にもありますが、 GroongaのNgramはノーマライザーを使うとASCII文字以外にNgramを適用します。 (ASCII文字はNgramで分割されません。)

ノーマライザーの使用の有無で挙動が変わるのはノーマライザーが「ASCII文字」 といったフラグを付けるためです。 このフラグがないとASCII文字か否か判別できないため、ノーマライザーの使用なしの場合は すべてにNgramを適用、ノーマライザー使用ありの場合はASCII文字以外にNgramを適用という 動きをします。

デフォルトでASCII文字以外にNgramを適用する理由はそのほうが便利なことが多いからです。

ASCII文字のみを使う言語(例えば英語)の場合は空白で単語を区切れるので、 その単語で検索することが多いです(= Ngramを使う必要性が低い)。 (apple を検索するときに app で検索することはあまりない。)

逆に日本語や中国語などの場合は空白で文字が区切られないため、Ngramを使ったほうが便利なことが多いです。

という状況からGroongaでは TokenNgram を純粋なNgramではなく、ASCII文字か否かで分割方法を変えるようにしています。

補足: TokenNgram のオプション例

例として2つのみ掲載しますが、他のNgram系のトークナイザーも同様に TokenNgram を使うことで実現できます。

指定できるオプションについては ドキュメント をご確認ください。

TokenBigramIgnoreBlank と同じ挙動

> tokenize TokenBigramIgnoreBlank "日本語 の 勉強" NormalizerAuto

上述の TokenBigramIgnoreBlank と同じ処理をする TokenNgram は以下の通りです。 ポイントはオプションで、 "n", 2 でBigramを、"ignore_blank", true でIgnoreBlankを指定しています。

> tokenize --tokenizer 'TokenNgram("n", 2, "ignore_blank", true)' --string "日本語 の 勉強" --normalizer NormalizerAuto --output_pretty yes
[
  [
    0,
    1713827246.760704,
    0.0002770423889160156
  ],
  [
    {
      "value": "日本",
      "position": 0,
      "force_prefix": false,
      "force_prefix_search": false
    },
    {
      "value": "本語",
      "position": 1,
      "force_prefix": false,
      "force_prefix_search": false
    },
    {
      "value": "語の",
      "position": 2,
      "force_prefix": false,
      "force_prefix_search": false
    },
    {
      "value": "の勉",
      "position": 3,
      "force_prefix": false,
      "force_prefix_search": false
    },
    {
      "value": "勉強",
      "position": 4,
      "force_prefix": false,
      "force_prefix_search": false
    },
    {
      "value": "強",
      "position": 5,
      "force_prefix": false,
      "force_prefix_search": false
    }
  ]
]

TokenBigramSplitSymbolAlpha と同じ挙動

> tokenize TokenBigramSplitSymbolAlpha "Hello world こんにちは" NormalizerAuto

上述の TokenBigramSplitSymbolAlpha と同じ処理をする TokenNgram は以下の通りです。 "unify_alphabet", false でASCII文字についてもBigramで分割するようになります。

> tokenize --tokenizer 'TokenNgram("n", 2, "unify_alphabet", false)' --string "Hello world こんにちは" --normalizer NormalizerAuto --output_pretty yes
[
  [
    0,
    1713848466.56066,
    0.0004296302795410156
  ],
  [
    {
      "value": "he",
      "position": 0,
      "force_prefix": false,
      "force_prefix_search": false
    },
    {
      "value": "el",
      "position": 1,
      "force_prefix": false,
      "force_prefix_search": false
    },
    {
      "value": "ll",
      "position": 2,
      "force_prefix": false,
      "force_prefix_search": false
    },
    {
      "value": "lo",
      "position": 3,
      "force_prefix": false,
      "force_prefix_search": false
    },
    {
      "value": "o",
      "position": 4,
      "force_prefix": false,
      "force_prefix_search": false
    },
    {
      "value": "wo",
      "position": 5,
      "force_prefix": false,
      "force_prefix_search": false
    },
    {
      "value": "or",
      "position": 6,
      "force_prefix": false,
      "force_prefix_search": false
    },
    {
      "value": "rl",
      "position": 7,
      "force_prefix": false,
      "force_prefix_search": false
    },
    {
      "value": "ld",
      "position": 8,
      "force_prefix": false,
      "force_prefix_search": false
    },
    {
      "value": "d",
      "position": 9,
      "force_prefix": false,
      "force_prefix_search": false
    },
    {
      "value": "こん",
      "position": 10,
      "force_prefix": false,
      "force_prefix_search": false
    },
    {
      "value": "んに",
      "position": 11,
      "force_prefix": false,
      "force_prefix_search": false
    },
    {
      "value": "にち",
      "position": 12,
      "force_prefix": false,
      "force_prefix_search": false
    },
    {
      "value": "ちは",
      "position": 13,
      "force_prefix": false,
      "force_prefix_search": false
    },
    {
      "value": "は",
      "position": 14,
      "force_prefix": false,
      "force_prefix_search": false
    }
  ]
]