Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

Lucene/Elasticsearch の Character Filter でユニコード正...

Lucene/Elasticsearch の Character Filter でユニコード正規化するとトークンのオフセットがズレるバグへの Workaround - Search Engineering Tech Talk 2024 Spring

Search Engineering Tech Talk 2024 Spring での発表資料です。
https://search-tech.connpass.com/event/318126/

この内容の完全版のブログ記事は以下にあります。
https://tech.legalforce.co.jp/entry/2024/05/31/140717

Shunsuke Kanda

May 31, 2024
Tweet

More Decks by Shunsuke Kanda

Other Decks in Technology

Transcript

  1. About me 氏名 • 本名 – 神田 峻介 • ハンドル

    – Kampersanda / かんぱさんだ 経歴 • 理研AIP • LegalOn Technologies(旧LegalForce) ◦ 研究員@LegalForce Research ◦ MLエンジニア@機械学習チーム ◦ 検索エンジニア@検索推薦チーム よく使うアイコン (自分で描いた)
  2. Lucene/Elasticsearch の Analyzer Doc Character Filter Tokenizer Token Filter Index

    トークン化の前処理 トークンに分割 トークンに後処理 (e.g., ユニコード正規化) (e.g., 形態素解析) (e.g., ストップワード除去)
  3. Lucene/Elasticsearch の Analyzer 1㍑の涙 1リットルの涙 1/リットル/の/涙 1/リットル/涙 Index トークン化の前処理 トークンに分割

    トークンに後処理 (e.g., ユニコード正規化) (e.g., 形態素解析) (e.g., ストップワード除去)
  4. Lucene/Elasticsearch の Analyzer 1㍑の涙 1リットルの涙 1/リットル/の/涙 1/リットル/涙 Index トークン化の前処理 トークンに分割

    トークンに後処理 (e.g., ユニコード正規化) (e.g., 形態素解析) (e.g., ストップワード除去) { "tokens" : [ { "token" : "1", "start_offset" : 0, "end_offset" : 1, "type" : "<NUM>", "position" : 0 }, { "token" : "リットル", "start_offset" : 1, "end_offset" : 2, "type" : "<KATAKANA>", "position" : 1 }, { "token" : "涙", "start_offset" : 3, "end_offset" : 4, "type" : "<IDEOGRAPHIC>", "position" : 3 } ] }
  5. Lucene/Elasticsearch の Analyzer 1㍑の涙 1リットルの涙 1/リットル/の/涙 1/リットル/涙 Index トークン化の前処理 トークンに分割

    トークンに後処理 (e.g., ユニコード正規化) (e.g., 形態素解析) (e.g., ストップワード除去) { "tokens" : [ { "token" : "1", "start_offset" : 0, "end_offset" : 1, "type" : "<NUM>", "position" : 0 }, { "token" : "リットル", "start_offset" : 1, "end_offset" : 2, "type" : "<KATAKANA>", "position" : 1 }, { "token" : "涙", "start_offset" : 3, "end_offset" : 4, "type" : "<IDEOGRAPHIC>", "position" : 3 } ] } 0 1 2 3 offset:
  6. バグの再現: 合字 “㍻” を解析 ㍻ 平成 平/成 Index { "tokens"

    : [ { "token" : "平", "start_offset" : 0, "end_offset" : 0, "type" : "<IDEOGRAPHIC>", "position" : 0 }, { "token" : "成", "start_offset" : 0, "end_offset" : 1, "type" : "<IDEOGRAPHIC>", "position" : 1 } ] }
  7. バグの再現: 合字 “㍻” を解析 ㍻ 平成 平/成 Index { "tokens"

    : [ { "token" : "平", "start_offset" : 0, "end_offset" : 0, "type" : "<IDEOGRAPHIC>", "position" : 0 }, { "token" : "成", "start_offset" : 0, "end_offset" : 1, "type" : "<IDEOGRAPHIC>", "position" : 1 } ] } 空文字列として扱われている!
  8. Lucene の Character Filter の設計に起因 解析後のオフセット i から原文のオフセット j へのマッピングは

    correctOffset(i) = j で管理 トークン “平” に期待されるマッピング • 開始地点:correctOffset(0) = 0 • 終了地点:correctOffset(1) = 1 トークン “成” に期待されるマッピング • 開始地点:correctOffset(1) = 0 • 終了地点:correctOffset(2) = 1 なぜ起こるか i j
  9. Lucene の Character Filter の設計に起因 解析後のオフセット i から原文のオフセット j へのマッピングは

    correctOffset(i) = j で管理 トークン “平” に期待されるマッピング • 開始地点:correctOffset(0) = 0 • 終了地点:correctOffset(1) = 1 トークン “成” に期待されるマッピング • 開始地点:correctOffset(1) = 0 • 終了地点:correctOffset(2) = 1 なぜ起こるか i j
  10. Workaround: どこで、どのように対処するか? Doc Character Filter Tokenizer Token Filter Index ①

    原文を事前にユニコード正規化 ② バグ原因の文字以外を正規化 ③ バグ原因のトークンを分割しないように制御 ④ 後処理でユニコード正規化
  11. Workaround: どこで、どのように対処するか? Doc Character Filter Tokenizer Token Filter Index ①

    原文を事前にユニコード正規化 ② バグ原因の文字以外を正規化 ③ バグ原因のトークンを分割しないように制御 ④ 後処理でユニコード正規化
  12. 良さそうな Workaround どこで • Character Filter の時点 どのように • バグの原因となる文字を正規化対象外にする

    • 具体的には ICU normalization character filter には、unicode_set_filter という正規 化する/しない文字を指定できるオプションが存在する Pros • 公式に提供されるオプションを使って素直に実現可能 Cons • 正規化対象外の文字がヒットしない(例えば “㍻” は “平成” でヒットしない)
  13. 対象外となる文字の影響範囲を調査 全てのユニコード文字(コードポイント 0x00 から 0x10FFFF まで)について、 NFKC + Case Folding

    で複数文字に展開される文字を調査 その結果 1,114,112 文字中 1,326 文字(0.12%)が該当 • ㌖ ㍿ ㈱ ㋉ ⑩ ㍢ ㍻ etc. これらを正規化の対象外としても、検索結果への影響は少なそうに思える ※もちろん実際に検索したい文書データでも調査した方がいいです
  14. 具体的な設定例 以下のように unicode_set_filter を設定するだけ "char_filter": { "custom_icu_normalizer": { "type": "icu_normalizer",

    "name": "nfkc_cf", "unicode_set_filter": "[^\u00a8\u00af\u00b4\u00b8\u00bc\u00bd\u00be\u00df… } } ㌖ ㍿ ㈱ ㋉ ⑩ ㍢ ㍻ etc.