OpenSearch2.13にSudachiを入れるのに苦労したはなし

https://qiita.com/mh-northlander/items/83c3888bb5fefe34be20

この記事の通りにやったらできるだろと思ってたら案外苦戦したのでこの部分だけ切り出し。

結果としては

testの指定が書いてないバージョンでこける。ので-x test記載。
`./gradlew -PengineVersion=os:2.13.0 build -x test`

ってことだった。

dockerでマルチステージビルドするのがスムーズなので辞書のダウンロード含めて書いてしまうのがよき。

```dockerfile
# ビルドステージ
FROM gradle:jdk17 as build-stage


RUN git clone https://github.com/WorksApplications/elasticsearch-sudachi.git
WORKDIR elasticsearch-sudachi
RUN git checkout v3.1.0

# OpenSearch (os), v2.13.0 でビルド。testの指定が書いてないバージョンでこける。ので-x test記載。
RUN ./gradlew -PengineVersion=os:2.13.0 build -x test

# ここからダウンロード
# 20240428時点最新がこれ。
# coreは必須。
RUN wget http://sudachi.s3-website-ap-northeast-1.amazonaws.com/sudachidict/sudachi-dictionary-20240409-core.zip 
RUN unzip sudachi-dictionary-20240409-core.zip sudachi-dictionary-20240409/system_core.dic

# fullもあとでつかいたくなるので書いてあるがなくていい。
RUN wget http://sudachi.s3-website-ap-northeast-1.amazonaws.com/sudachidict/sudachi-dictionary-20240409-full.zip 
RUN unzip sudachi-dictionary-20240409-full.zip sudachi-dictionary-20240409/system_full.dic

# smallもあとでつかいたくなるので書いてあるがなくていい。
RUN wget http://sudachi.s3-website-ap-northeast-1.amazonaws.com/sudachidict/sudachi-dictionary-20240409-small.zip 
RUN unzip sudachi-dictionary-20240409-small.zip sudachi-dictionary-20240409/system_small.dic

# ここでユーザー辞書のビルドもかいておく

# ランタイムステージ
FROM opensearchproject/opensearch:2.13.0

# ビルドステージからプラグインをコピー
COPY --from=build-stage /home/gradle/elasticsearch-sudachi/build/distributions/opensearch-2.13.0-analysis-sudachi-3.1.0.zip .

# 辞書をコピーする
COPY --from=build-stage /home/gradle/elasticsearch-sudachi/sudachi-dictionary-20240409/system_core.dic config/sudachi/system_core.dic
COPY --from=build-stage /home/gradle/elasticsearch-sudachi/sudachi-dictionary-20240409/system_full.dic config/sudachi/system_full.dic
COPY --from=build-stage /home/gradle/elasticsearch-sudachi/sudachi-dictionary-20240409/system_small.dic config/sudachi/system_small.dic

# プラグインをファイルからインストールする
RUN /usr/share/opensearch/bin/opensearch-plugin install --batch file:opensearch-2.13.0-analysis-sudachi-3.1.0.zip

RUN opensearch-plugin install analysis-kuromoji
```


いえい。

# 形態素解析
フィルターとかの詳細は
https://github.com/WorksApplications/elasticsearch-sudachi/tree/develop
このへんに詳しい。

正規化とsearchモード組み合わせが基本的には強い。


```JSON

PUT sudachi-index-example
{
  "settings" : {
      "analysis" : {
          "filter" : {
              "search" : {
                  "type" : "sudachi_split",
                  "mode" : "search"
              },
              "synonym" : {
                  "type" : "synonym",
                  "synonyms" : [ "関西国際空港,関空", "関西 => 近畿" ]
              },
              "romaji_readingform" : {
                  "type" : "sudachi_readingform",
                  "use_romaji" : true
              },
              "katakana_readingform" : {
                  "type" : "sudachi_readingform",
                  "use_romaji" : false
              }
          },
          "analyzer" : {
              "sudachi_baseform_analyzer" : {
                  "filter" : [ "sudachi_baseform" ],
                  "type" : "custom",
                  "tokenizer" : "sudachi_tokenizer"
              },
              "sudachi_normalizedform_analyzer" : {
                  "filter" : [ "sudachi_normalizedform" ],
                  "type" : "custom",
                  "tokenizer" : "sudachi_tokenizer"
              },
              "sudachi_readingform_analyzer" : {
                  "filter" : [ "katakana_readingform" ],
                  "type" : "custom",
                  "tokenizer" : "sudachi_tokenizer"
              },
              "sudachi_romaji_analyzer" : {
                  "filter" : [ "romaji_readingform" ],
                  "type" : "custom",
                  "tokenizer" : "sudachi_tokenizer"
              },
              "sudachi_search_analyzer" : {
                  "filter" : [ "search" ],
                  "type" : "custom",
                  "tokenizer" : "sudachi_tokenizer"
              },
              "sudachi_search_analyzer2" : {
                  "filter" : [ "search" ,"sudachi_normalizedform"],
                  "type" : "custom",
                  "tokenizer" : "sudachi_tokenizer"
              },
              "sudachi_synonym_analyzer" : {
                  "filter" : [ "synonym", "search" ],
                  "type" : "custom",
                  "tokenizer" : "sudachi_tokenizer"
              },
              "sudachi_analyzer" : {
                  "filter" : [],
                  "type": "custom",
                  "tokenizer": "sudachi_tokenizer"
              },
              "sudachi_a_analyzer" : {
                  "filter" : [],
                  "type" : "custom",
                  "tokenizer" : "sudachi_a_tokenizer"
              }
          },
          "tokenizer" : {
              "sudachi_tokenizer": {
                  "type": "sudachi_tokenizer",
                  "split_mode": "C"
              },
              "sudachi_a_tokenizer": {
                  "type": "sudachi_tokenizer",
                  "split_mode": "A"
              }
          }
      }
  },
  "mappings": {
    "properties": {
      "本文": {
        "type": "text",
        "analyzer": "sudachi_search_analyzer2"
      }
    }
  }
}
```

とりあえず…例がたくさんついてて見にくいけど、sudachiで形態素解析するindex。本文にsudachi_search_analyzer2で解析してる。
```JSON 
  "sudachi_search_analyzer2" : {
      "filter" : [ "search" ,"sudachi_normalizedform"],
      "type" : "custom",
      "tokenizer" : "sudachi_tokenizer"
  },
```
これはsearchモードとsudachi_normalizedformで正規化してるので検索。

```JSON
POST _bulk
{"index": {"_index": "sudachi-index-example", "_id": "1"}}
{"本文": "Qiitaで記事書くの久しぶりかも。個人的には雑な記事で許される雰囲気が好き。"}
{"index": {"_index": "sudachi-index-example", "_id": "2"}}
{"本文": "テストデータです。"}
{"index": {"_index": "sudachi-index-example", "_id": "3"}}
{"本文": "ねむいよ。"}
{"index": {"_index": "sudachi-index-example", "_id": "4"}}
{"本文": "サンプルデータで検索引っかかりやすさの差が出るデータを作るのがめんどくさい。"}
{"index": {"_index": "sudachi-index-example", "_id": "5"}}
{"本文": "本日は晴天なり"}
{"index": {"_index": "sudachi-index-example", "_id": "6"}}
{"本文": "すっごくどうでもいいけど、「アイマリンプロジェクト 「Dive to Blue」 MMD MUSIC VIDEO」がマイブームです。https:\/\/www.youtube.com/watch?v=XCzGs6rLf4s"}
```
とりあえずいつもの？これ。



`GET sudachi-index-example/_search?q=プロジェクト`

```JSON
{
  "took": 16,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 0,
      "relation": "eq"
    },
    "max_score": null,
    "hits": []
  }
}
```
結果は…ゼロ件orz。これはkuromojiでもゼロ件なんですが、形態素解析の都合で「アイマリンプロジェクト」が固有名詞っぽく（実際固有名詞）、「アイマリンプロジェクト」とされてしまっているせいです。「アイ」「マリン」「プロジェクト」と「アイマリンプロジェクト」それぞれが転置マッピングされててほしいわけなんだが…。この辺はユーザー辞書が欲しい。

今どきだったらもうハイブリッド検索でembeddingをちょっと混ぜるとかのほうが安定はするかもしれないね。


ちなみに正規化が効くのはこういう話。

```JSON
POST _bulk
{"index": {"_index": "sudachi-index-example"}}
{"本文": "デンプンってでんぷんとか澱粉とかでん粉とか表記ゆれが多いよね"}
{"index": {"_index": "sudachi-index-example"}}
{"本文": "デンプンって表記ゆれが多いよね"}
{"index": {"_index": "sudachi-index-example"}}
{"本文": "でんぷんって表記ゆれが多いよね"}
{"index": {"_index": "sudachi-index-example"}}
{"本文": "でん粉って表記ゆれが多いよね"}
{"index": {"_index": "sudachi-index-example"}}
{"本文": "澱粉って表記ゆれが多いよね"}
```

`GET sudachi-index-example/_search?q=でんぷん`
ひらがなで「でんぷん」ってキーワード入れてても「でんぷん」「澱粉」「デンプン」「でん粉」全部ひっかかります。なぜならば辞書側で正規化してみんな「澱粉」で登録されてるから。
```JSON
{
  "took": 3,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 5,
      "relation": "eq"
    },
    "max_score": 1.2181944,
    "hits": [
      {
        "_index": "sudachi-index-example",
        "_id": "Oy2NII8BjEjOixjkjdRN",
        "_score": 1.2181944,
        "_source": {
          "本文": "デンプンってでんぷんとか澱粉とかでん粉とか表記ゆれが多いよね"
        }
      },
      {
        "_index": "sudachi-index-example",
        "_id": "PC2NII8BjEjOixjkjdRN",
        "_score": 0.8899586,
        "_source": {
          "本文": "デンプンって表記ゆれが多いよね"
        }
      },
      {
        "_index": "sudachi-index-example",
        "_id": "PS2NII8BjEjOixjkjdRN",
        "_score": 0.8899586,
        "_source": {
          "本文": "でんぷんって表記ゆれが多いよね"
        }
      },
      {
        "_index": "sudachi-index-example",
        "_id": "Pi2NII8BjEjOixjkjdRN",
        "_score": 0.8899586,
        "_source": {
          "本文": "でん粉って表記ゆれが多いよね"
        }
      },
      {
        "_index": "sudachi-index-example",
        "_id": "Py2NII8BjEjOixjkjdRN",
        "_score": 0.8899586,
        "_source": {
          "本文": "澱粉って表記ゆれが多いよね"
        }
      }
    ]
  }
}
```

前回のindexに入れてやって試すとこんな感じ


```JSON
POST my-nlp-e5/_search?search_pipeline=nlp-search-pipeline
{ 
  "_source": {
    "exclude": [
      "embedding"
    ]
  },
  "query": {
          "match": {
            "text": {
              "query": "でんぷん"
            }
          }
  }

}

```
kuromojiだけの結果。
```
{
  "took": 13,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 2,
      "relation": "eq"
    },
    "max_score": 1.8653811,
    "hits": [
      {
        "_index": "my-nlp-e5",
        "_id": "SC2OII8BjEjOixjkptTa",
        "_score": 1.8653811,
        "_source": {
          "text": "でんぷんって表記ゆれが多いよね"
        }
      },
      {
        "_index": "my-nlp-e5",
        "_id": "Ri2OII8BjEjOixjkptTa",
        "_score": 1.525284,
        "_source": {
          "text": "デンプンってでんぷんとか澱粉とかでん粉とか表記ゆれが多いよね"
        }
      }
    ]
  }
}
```

「でんぷん」表記があるのだけでてきました。



ちなみにハイブリッドだと
```JSON
POST my-nlp-e5/_search?search_pipeline=nlp-search-pipeline
{ 
  "_source": {
    "exclude": [
      "embedding"
    ]
  },
  "query": {
    "hybrid": {
      "queries": [
        {
          "match": {
            "text": {
              "query": "でんぷん"
            }
          }
        },
        {
          "neural": {
            "embedding": {
              "query_text": "でんぷん",
              "model_id": "SAS3FY8Bu7RVyOi7jDQ2",
              "k": 5
            }
          }
        }
      ]
    }
  }

}
```

ぜんぶでてきます

```JSON
{
  "took": 275,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 5,
      "relation": "eq"
    },
    "max_score": 1,
    "hits": [
      {
        "_index": "my-nlp-e5",
        "_id": "SC2OII8BjEjOixjkptTa",
        "_score": 1,
        "_source": {
          "text": "でんぷんって表記ゆれが多いよね"
        }
      },
      {
        "_index": "my-nlp-e5",
        "_id": "Ri2OII8BjEjOixjkptTa",
        "_score": 0.49498686,
        "_source": {
          "text": "デンプンってでんぷんとか澱粉とかでん粉とか表記ゆれが多いよね"
        }
      },
      {
        "_index": "my-nlp-e5",
        "_id": "SS2OII8BjEjOixjkptTa",
        "_score": 0.3487706,
        "_source": {
          "text": "でん粉って表記ゆれが多いよね"
        }
      },
      {
        "_index": "my-nlp-e5",
        "_id": "Ry2OII8BjEjOixjkptTa",
        "_score": 0.02692861,
        "_source": {
          "text": "デンプンって表記ゆれが多いよね"
        }
      },
      {
        "_index": "my-nlp-e5",
        "_id": "Si2OII8BjEjOixjkptTa",
        "_score": 0.00070000003,
        "_source": {
          "text": "澱粉って表記ゆれが多いよね"
        }
      }
    ]
  }
}
```


sudachiでアイマリンプロジェクトをプロジェクトとかマリンでひっかかるようにしたいなー