ククログ

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

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

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

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

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

結論

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

解説

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

例えば、 TokenUnigram、TokenBigram、TokenTrigram は TokenNgram で以下のように指定できます。

  • 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
    }
  ]
]