Elasticsearchでの同義語を利用した検索

BLOG

1. 前書き

こんにちは。

前回に引き続き、ホワイトペーパー「Elasticsearchを使った簡易RAGアプリケーションの作成」に
記載した技術的要素を紹介いたします。(*脚注11

今回は、同義語の利用です。

対象者

  • Elastic Cloud のアカウントをお持ちの方(トライアルライセンスを含む)
  • Elasticsearch の初心者~中級者

できるようになること

  • Elasticsearch に同義語の登録ができる。
  • Elasticsearch で同義語を考慮した検索を実行できる。

前提条件

  • Elastic Cloud (version: 8.17.3)
  • Elasticsearch で日本語の形態素解析の設定を行っていること

Elasticsearchでの日本語に適したインデックスの作成 の回で、日本語の形態素解析の設定方法を紹介しています。

(2025年04月08日時点の情報を元に記載しています。)

2. 同義語とは?

Elasticsearch でキーワード検索を行う場合、検索文字列と同じ文字列を持つドキュメントが検索されます。

たとえば、”おにぎり” で検索した場合、”おにぎり” を含むドキュメントはヒットしますが、
同じような意味を持つ “おむすび” を含むドキュメントはヒットしません。

似たような意味を含むドキュメントを検索したい場合の対策として、同義語を登録する方法を取り上げます。(*脚注22

例)

“おむすび” の同義語として “おにぎり” を登録することで、
“おにぎり” を検索した場合でも “おむすび” を含むドキュメントが検索されるようになります。

3. 同義語登録前の動作確認

前回の Elasticsearchでのユーザー辞書登録を利用した検索で、
「桃太郎」を次のように改変した「柿之助」を kakinosuke_202504 インデックスに登録済です。

  • 桃太郎 → 柿之助
  • きびだんご → おむすび
  • 猿 → ゴリラ
  • 犬 → 猫
  • 雉 → 鷹

kakinosuke_202504 インデックスに対し、”おにぎり” で検索してみます。

リクエスト

GET /kakinosuke/_search
{
  "_source": false,
  "query": {
    "match": {
      "content": "おにぎり"
    }
  }
}

(kakinosuke は、kakinosuke_202504 インデックスに対するエイリアスです。)

レスポンス

{
  ...,
  "hits": {
    "total": {
      "value": 0,
      "relation": "eq"
    },
    ...
  }
}

“おにぎり” という単語を含んだドキュメントがないため、ヒットしません。

4. インデックスへの同義語の追加

同義語をインデックスに追加します。

同義語を参照するタイミングとして、2つが考えられます。

  • ドキュメントの登録時に同義語を参照する。
  • ドキュメントの検索時に同義語を参照する。

今回は、ドキュメントの検索時に同義語を参照するよう、設定します。(*脚注33)

なお、同義語の追加・変更・削除は、Synonyms API を使って行います。(*脚注44

4.1 synonyms_set への同義語の追加

Bulk API (Pythonからのドキュメントの一括登録) で、同義語セット kakinosuke_synonyms_set の枠を作成していました(作成時点の中身は空っぽ)。

kakinosuke_202504 インデックスと kakinosuke_synonyms_set の関係を表す概略図

synonyms_set

kakinosuke_synonyms_set に同義語を追加していきます。

下記のリクエストを Elastic Cloud の Console から発行します。

PUT _synonyms/kakinosuke_synonyms_set
{
  "synonyms_set": [
    {
      "synonyms": "おむすび,おにぎり"
    }
  ]
}

5. 検索

同義語を追加したので、もう一度、”おにぎり” で検索してみます。(*脚注55)

リクエスト

GET /kakinosuke/_search
{
  "_source": false,
  "fields": ["chunk_no", "content"],
  "query": {
    "match": {
      "content": "おにぎり"
    }
  }
}

レスポンス

{
  ...,
  "hits": {
    "total": {
      "value": 9,
      "relation": "eq"
    },
    "max_score": 2.0755403,
    "hits": [
      {
        ...,
        "fields": {
          "chunk_no": [
            20
          ],
          "content": [
            "とたずねました。「悪霊島へ悪霊せいばつに行くのだ。」「お腰に下げたものは、何でございます。」「日本一のおむすびさ。」「一つ下さい、お供しましょう。」「よし、よし、やるから、ついて来い。」鷹もおむすびを一つもらって、柿之助のあとからついて行きました。"
          ]
        }
      },
      ...
    ]
  }
}

“おにぎり” で検索を行いましたが、”おにぎり” の同義語である “おむすび” にマッチしたドキュメントがヒットしていることがわかります。

なお、ハイライト表示を行う指示をしていた場合、”おにぎり” の同義語である “おむすび” が強調表示されます。

リクエスト

GET /kakinosuke/_search
{
  "_source": false,
  "query": {
    "match": {
      "content": "おにぎり"
    }
  },
  "highlight": {
    "fields": {
      "content": {}
    }
  }
}

レスポンス

{
  ...,
  "hits": {
    ...,
    "hits": [
      {
        ...,
        "highlight": {
          "content": [
            "「日本一の<em>おむすび</em>さ。」「一つ下さい、お供しましょう。」「よし、よし、やるから、ついて来い。」鷹も<em>おむすび</em>を一つもらって、柿之助のあとからついて行きました。"
          ]
        }
      },
      ...
    ]
  }
}

Elasticsearchでの検索結果のハイライト表示 の回で Streamlit を使ってハイライト表示していました。この検索アプリで “おにぎり” を検索して、検索結果を画面に表示してみます。

“おにぎり” を検索しましたが、 “おむすび” にマッチした部分がハイライト表示されていることがわかります。

ただし、4番目にヒットしている内容には、”おにぎり” も “おむすび” もヒットしていません。
推測になりますが、”おにぎり” の密ベクトルと、4番目のドキュメントの密ベクトルが近い値になっていたものと思われます。
“おにぎり” と関連がありそうな、 “お湯” や人物(”おじいいさん”, “おばあさん”, “子”)、”手” を含むドキュメントがヒットした可能性があります。

(上記の検索アプリは、キーワード検索と密ベクトルのハイブリッド検索を実行しています。)

※参考までに、下記のようなリクエストを発行すれば、密ベクトル検索のみを行うことができます。

GET /kakinosuke/_search
{
  "knn": {
    "field": "text_embedding.predicted_value",
    "k": 10,
    "num_candidates": 100,
    "query_vector_builder": {
      "text_embedding": {
        "model_id": ".multilingual-e5-small_linux-x86_64",
        "model_text": "おにぎり"
      }
    }
  }
}

6. 発展形

このブログでは人間が同義語を考えて追加しましたが、LLM に同義語を列挙させる、といったテクニックもあります。

詳細については、(*脚注66) のブログを参照してください。

7. まとめ

同義語登録を行うことで、似たような意味を持つ単語もキーワード検索できるようになりました。
このようなチューニングを行うことで、RAG での検索結果を改善し、最終的な回答の精度向上につながり場合もあります。


  1. ホワイトペーパー「Elasticsearchを使った簡易RAGアプリケーションの作成」は、下記からダウンロードできます
    (E-mailアドレスなどの入力が必要です)。
    https://elastic.sios.jp/whitepaper/
    ↩︎
  2. 似たような意味の言葉を検索する方法として、同義語を登録する方法以外に、
    – 疎ベクトル検索を行う。
    – 密ベクトル検索を行う。
    といった方法もあります。
    ただし、5. 検索のところで説明したように、ヒットしてほしくないドキュメントにヒットしてしまう可能性もあります。
    ↩︎
  3. 下記の Elasticsearch 社のブログでも書かれていますが、ドキュメントの検索時に同義語を検索することが推奨されているようです。
    ドキュメントの登録時に同義語を参照した方が検索は速くなるのですが、
    同義語を追加・変更・削除するたびにドキュメントの再登録が必要となり、管理が煩雑になってしまいます。
    検索時に同義語を参照するのであれば、同義語を追加・変更・削除してもドキュメントの再登録は必要ありません。
    同義語について言及している Elasticsearch 社のブログ
    https://www.elastic.co/jp/blog/boosting-the-power-of-elasticsearch-with-synonyms
    ↩︎
  4. 同義語は、下記の Synonyms API を利用して登録します。
    https://www.elastic.co/guide/en/elasticsearch/reference/current/put-synonyms-set.html
    (Synonyms API は、Elasticsearch 8.10 から利用できるようになりました。)
    なお、同義語を登録後、個別の同義語を削除・変更したい場合には、”id” を明示する必要があります。
    同義語を登録後に、削除・変更する可能性がある場合には、あらかじめ “id” を明示して登録しておくことをおすすめします。
    ↩︎
  5. Bulk API (Pythonからのドキュメントの一括登録) で、index の mappings に同義語参照時の設定をあらかじめ
    "updateable": true
    と指定していたので、同義語を追加しただけで検索結果が変わります。
    ↩︎
  6. LLM を使って同義語を自動登録するテクニックについて紹介している Elasticsearch 社のブログ
    https://www.elastic.co/search-labs/blog/elasticsearch-synonyms-automate
    ↩︎