ブログ

同義語APIを使用して同義語を自動化し、アップロードする方法

LLM を使用して同義語を自動的に識別および生成し、用語をプログラムで Elasticsearch 同義語 API に読み込むことができる方法について説明します。

効率的なユーザー エクスペリエンスを提供するには、検索結果の品質を向上させることが不可欠です。検索を最適化する 1 つの方法は、同義語を通じて検索対象の用語を自動的に拡張することです。これにより、クエリをより広範囲に解釈できるようになり、言語のバリエーションをカバーして結果の一致が向上します。

このブログでは、大規模言語モデル (LLM) を使用して同義語を自動的に識別および生成し、これらの用語をプログラムで Elasticsearch の同義語 API に読み込むことができる方法について説明します。

同義語はいつ使用すればよいですか?

同義語を使用すると、ベクトル検索に比べて高速かつコスト効率の高いソリューションになります。埋め込みに関する深い知識や複雑なベクトル取り込みプロセスを必要としないため、実装はより簡単です。

さらに、ベクトル検索ではインデックス作成と検索を埋め込むために大きなストレージ容量とメモリが必要になるため、リソースの消費量が少なくなります。

もう一つの重要な側面は、検索の地域化です。同義語を使用すると、現地の言語や習慣に応じて用語を適応させることができます。これは、埋め込みが地域的な表現や国固有の用語と一致しない可能性がある場合に役立ちます。たとえば、一部の単語や頭字語は地域によって意味が異なる場合がありますが、現地のユーザーにとっては当然同義語として扱われます。ブラジルでは、これはかなり一般的です。「アバカシ」と「アナナス」は同じ果物(パイナップル)ですが、北東部の一部の地域では後者の用語の方が一般的に使用されています。同様に、南東部でよく知られている「pão francês」は、北東部では「pão careca」として知られている場合があります。

LLM を使用して同義語を生成するにはどうすればよいでしょうか?

同義語を自動的に取得するには、用語のコンテキストを分析して適切なバリエーションを提案する LLM を使用できます。このアプローチにより、同義語を動的に拡張できるため、固定辞書に依存せずに、より広範で正確な検索が可能になります。

このデモでは、LLM を使用して電子商取引製品の同義語を生成します。多くの検索では、検索語句のバリエーションにより、結果がほとんど返されないか、まったく返されません。同義語を使用すると、この問題は解決できます。たとえば、「スマートフォン」を検索すると、さまざまな種類の携帯電話が検索されるため、ユーザーは探している製品を見つけることができます。

要件

始める前に、環境を設定し、必要な依存関係を定義する必要があります。Elastic が提供するソリューションを使用して、 Elasticsearch と Kibana を Docker でローカルに実行します。コードは Python v3.9.6 で記述され、次の依存関係があります。

pip install openai==1.59.8 elasticsearch==8.15.1

製品インデックスの作成

最初は、同義語をサポートしない製品のインデックスを作成します。これにより、クエリを検証し、同義語を含むインデックスと比較できるようになります。

インデックスを作成するには、Kibana DevTools で次のコマンドを使用して製品データセットを一括ロードします。

POST _bulk
{"index": {"_index": "products", "_id": 10001}}
{"category": "Electronics", "name": "iPhone 14 Pro"}
{"index": {"_index": "products", "_id": 10007}}
{"category": "Electronics", "name": "MacBook Pro 16-inch"}
{"index": {"_index": "products", "_id": 10013}}
{"category": "Electronics", "name": "Samsung Galaxy Tab S8"}
{"index": {"_index": "products", "_id": 10037}}
{"category": "Electronics", "name": "Apple Watch Series 8"}
{"index": {"_index": "products", "_id": 10049}}
{"category": "Electronics", "name": "Kindle Paperwhite"}
{"index": {"_index": "products", "_id": 10067}}
{"category": "Electronics", "name": "Samsung QLED 4K TV"}
{"index": {"_index": "products", "_id": 10073}}
{"category": "Electronics", "name": "HP Spectre x360 Laptop"}
{"index": {"_index": "products", "_id": 10079}}
{"category": "Electronics", "name": "Apple AirPods Pro"}
{"index": {"_index": "products", "_id": 10115}}
{"category": "Electronics", "name": "Amazon Echo Show 10"}
{"index": {"_index": "products", "_id": 10121}}
{"category": "Electronics", "name": "Apple iPad Air"}
{"index": {"_index": "products", "_id": 10127}}
{"category": "Electronics", "name": "Apple AirPods Max"}
{"index": {"_index": "products", "_id": 10151}}
{"category": "Electronics", "name": "Sony WH-1000XM4 Headphones"}
{"index": {"_index": "products", "_id": 10157}}
{"category": "Electronics", "name": "Google Pixel 6 Pro"}
{"index": {"_index": "products", "_id": 10163}}
{"category": "Electronics", "name": "Apple MacBook Air"}
{"index": {"_index": "products", "_id": 10181}}
{"category": "Electronics", "name": "Google Pixelbook Go"}
{"index": {"_index": "products", "_id": 10187}}
{"category": "Electronics", "name": "Sonos Beam Soundbar"}
{"index": {"_index": "products", "_id": 10199}}
{"category": "Electronics", "name": "Apple TV 4K"}
{"index": {"_index": "products", "_id": 10205}}
{"category": "Electronics", "name": "Samsung Galaxy Watch 4"}
{"index": {"_index": "products", "_id": 10211}}
{"category": "Electronics", "name": "Apple MacBook Pro 16-inch"}
{"index": {"_index": "products", "_id": 10223}}
{"category": "Electronics", "name": "Amazon Echo Dot (4th Gen)"}

LLMによる同義語の生成

このステップでは、LLM を使用して同義語を動的に生成します。これを実現するために、OpenAI API を統合し、適切なモデルとプロンプトを定義します。LLM は製品のカテゴリと名前を受け取り、同義語が文脈的に適切であることを確認します。

import json
import logging

from openai import OpenAI

def call_gpt(prompt, model):
    try:
        logging.info("generate synonyms by llm...")
        response = client.chat.completions.create(
            model=model,
            messages=[{"role": "user", "content": prompt}],
            temperature=0.7,
            max_tokens=1000
        )
        content = response.choices[0].message.content.strip()
        return content
    except Exception as e:
        logging.error(f"Failed to use model: {e}")
        return None

def generate_synonyms(category, products):
   synonyms = {}

   for product in products:
       prompt = f"You are an expert in generating synonyms for products. Based on the category and product name provided, generate synonyms or related terms. Follow these rules:\n"
       prompt += "1. **Format**: The first word should be the main item (part of the product name, excluding the brand), followed by up to 3 synonyms separated by commas.\n"
       prompt += "2. **Exclude the brand**: Do not include the brand name in the synonyms.\n"
       prompt += "3. **Maximum synonyms**: Generate a maximum of 3 synonyms per product.\n\n"
       prompt += f"The category is: **{category}**, and the product is: **{product}**. Return only the synonyms in the requested format, without additional explanations."

       response = call_gpt(prompt, "gpt-4o")
       synonyms[product] = response

   return synonyms

作成された製品インデックスから、「エレクトロニクス」カテゴリ内のすべてのアイテムを取得し、その名前を LLM に送信します。予想される出力は次のようになります。

{
  "iPhone 14 Pro": ["iPhone", "smartphone", "mobile", "handset"],
  "MacBook Pro 16-inch": ["MacBook", "Laptop", "Notebook", "Ultrabook"],
  "Samsung Galaxy Tab S8": ["Tab", "Tablet", "Slate", "Pad"],
  "Bose QuietComfort 35 Headphones": ["Headphones", "earphones", "earbuds", "headset"]
}

生成された同義語は、Synonyms API を使用して Elasticsearch に登録できます。

Synonyms API による同義語の管理

シノニム API は、システム内でシノニム セットを直接管理する効率的な方法を提供します。各同義語セットは同義語ルールで構成され、検索では単語のグループが同等として扱われます。

同義語セットの作成例

PUT _synonyms/my-synonyms-set
{
  "synonyms_set": [
    {
      "id": "rule-1",
      "synonyms": "hello, hi"
    },
    {
      "synonyms": "bye, goodbye"
    }
  ]
}

これにより、「my-synonyms-set」というセットが作成され、そこでは「hello」と「hi」が「bye」と「goodbye」と同様に同等として扱われます。

製品カタログの同義語作成の実装

以下は、同義語セットを構築して Elasticsearch に挿入するメソッドです。同義語ルールは、LLM によって提案された同義語のマッピングに基づいて生成されます。各ルールには、スラッグ形式の製品名に対応する ID と、LLM によって計算された同義語のリストがあります。

import json
import logging

from elasticsearch import Elasticsearch
from slugify import slugify

es = Elasticsearch(
    "http://localhost:9200",
    api_key="your_api_key"
)

def mount_synonyms(results):
   synonyms_set = [{"id": slugify(product), "synonyms": synonyms} for product, synonyms in
                   results.items()]

   try:
       response = es.synonyms.put_synonym(id="products-synonyms-set",
                                                 synonyms_set=synonyms_set)

       logging.info(json.dumps(response.body, indent=4))
       return response.body
   except Exception as e:
       logging.error(f"Error create synonyms: {str(e)}")
       return None

以下は、同義語セットを作成するためのリクエスト ペイロードです。

{
   "synonyms_set":[
      {
         "id": "iphone-14-pro",
         "synonyms": "iPhone, smartphone, mobile, handset"
      },
      {
         "id": "macbook-pro-16-inch",
         "synonyms": "MacBook, Laptop, Notebook, Computer"
      },
      {
         "id": "samsung-galaxy-tab-s8",
         "synonyms": "Tablet, Slate, Pad, Device"
      },
      {
         "id": "garmin-forerunner-945",
         "synonyms": "Forerunner, smartwatch, fitness watch, GPS watch"
      },
      {
         "id": "bose-quietcomfort-35-headphones",
         "synonyms": "Headphones, Earphones, Headset, Cans"
      }
   ]
}

クラスターにシノニム セットが作成されたら、次のステップに進み、定義したセットを使用してシノニムをサポートする新しいインデックスを作成します。

LLM によって生成された同義語と、Synonyms API によって定義された同義語セットの作成を含む完全な Python コードは次のとおりです。

import json
import logging

from elasticsearch import Elasticsearch
from openai import OpenAI
from slugify import slugify

logging.basicConfig(level=logging.INFO)

client = OpenAI(
   api_key="your-key",
)

es = Elasticsearch(
    "http://localhost:9200",
    api_key="your_api_key"
)


def call_gpt(prompt, model):
   try:
       logging.info("generate synonyms by llm...")
       response = client.chat.completions.create(
           model=model,
           messages=[{"role": "user", "content": prompt}],
           temperature=0.7,
           max_tokens=1000
       )
       content = response.choices[0].message.content.strip()
       return content
   except Exception as e:
       logging.error(f"Failed to use model: {e}")
       return None


def generate_synonyms(category, products):
   synonyms = {}

   for product in products:
       prompt = f"You are an expert in generating synonyms for products. Based on the category and product name provided, generate synonyms or related terms. Follow these rules:\n"
       prompt += "1. **Format**: The first word should be the main item (part of the product name, excluding the brand), followed by up to 3 synonyms separated by commas.\n"
       prompt += "2. **Exclude the brand**: Do not include the brand name in the synonyms.\n"
       prompt += "3. **Maximum synonyms**: Generate a maximum of 3 synonyms per product.\n\n"
       prompt += f"The category is: **{category}**, and the product is: **{product}**. Return only the synonyms in the requested format, without additional explanations."

       response = call_gpt(prompt, "gpt-4o")
       synonyms[product] = response

   return synonyms


def get_products(category):
   query = {
       "size": 50,
       "_source": ["name"],
       "query": {
           "bool": {
               "filter": [
                   {
                       "term": {
                           "category.keyword": category
                       }
                   }
               ]
           }
       }
   }
   response = es.search(index="products", body=query)

   if response["hits"]["total"]["value"] > 0:
       product_names = [hit["_source"]["name"] for hit in response["hits"]["hits"]]
       return product_names
   else:
       return []


def mount_synonyms(results):
   synonyms_set = [{"id": slugify(product), "synonyms": synonyms} for product, synonyms in
                   results.items()]

   try:
       es_client = get_client_es()
       response = es_client.synonyms.put_synonym(id="products-synonyms-set",
                                                 synonyms_set=synonyms_set)

       logging.info(json.dumps(response.body, indent=4))
       return response.body
   except Exception as e:
       logging.error(f"Erro update synonyms: {str(e)}")
       return None


if __name__ == '__main__':
   category = "Electronics"
   products = get_products("Electronics")
   llm_synonyms = generate_synonyms(category, products)
   mount_synonyms(llm_synonyms)

同義語サポート付きのインデックスの作成

新しいインデックスが作成され、 productsインデックスのすべてのデータが再インデックスされます。このインデックスは、以前に作成されたproducts-synonyms-setを適用するsynonyms_filterを使用します。

以下はシノニムを使用するように構成されたインデックス マッピングです。

PUT products_02
{
  "settings": {
    "analysis": {
      "filter": {
        "synonyms_filter": {
          "type": "synonym",
          "synonyms_set": "products-synonyms-set",
          "updateable": true
        }
      },
      "analyzer": {
        "synonyms_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "synonyms_filter"
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "ID": {
        "type": "long"
      },
      "category": {
        "type": "keyword"
      },
      "name": {
        "type": "text",
        "analyzer": "standard",
        "search_analyzer": "synonyms_analyzer"
      }
    }
  }
}

productsインデックスの再インデックス

ここで、 Reindex API を使用して、 productsインデックスからシノニムのサポートを含む新しいproducts_02インデックスにデータを移行します。Kibana DevTools で次のコードが実行されました。

POST _reindex
{
  "source": {
    "index": "products"
  },
  "dest": {
    "index": "products_02"
  }
}

移行後、 products_02インデックスが作成され、構成された同義語セットを使用して検索を検証できるようになります。

同義語による検索の検証

2つのインデックス間の検索結果を比較してみましょう。両方のインデックスに対して同じクエリを実行し、結果を取得するために同義語が使用されているかどうかを検証します。

productsインデックスで検索(同義語なし)

Kibana を使用して検索を実行し、結果を分析します。「分析 > 検出」メニューで、作成したインデックスのデータを視覚化するためのデータ ビューを作成します。

Discovery 内で、データ ビューをクリックし、名前とインデックス パターンを定義します。「 products 」インデックスでは、「 products 」パターンを使用します。次に、「 products_02」 パターンを使用して、「 products_02 」インデックスの新しいデータ ビューを作成するプロセスを繰り返します。

データ ビューを構成したら、分析 > 検出に戻り、検証を開始できます。

ここで、DataView 製品を選択し、「タブレット」という用語で検索を実行すると、「Kindle Paperwhite」や「Apple iPad Air」などの製品があることがわかっているにもかかわらず、結果は表示されません。

products_02インデックスで検索 (同義語をサポート)

シノニムをサポートする「 products_synonyms 」データ ビューで同じクエリを実行すると、製品が正常に取得されました。これは、構成された同義語セットが正しく機能していることを示しており、検索された用語のさまざまなバリエーションが期待どおりの結果を返すことが保証されています。

同じクエリを Kibana DevTools で直接実行することで、同じ結果を得ることができます。Elasticsearch Search API を使用して、products_02 インデックスを検索するだけです。

まとめ

Elasticsearch に同義語を実装することで、製品カタログ検索の精度と範囲が向上しました。主な差別化要因はLLMの使用であり、これにより同義語が自動的かつ文脈に応じて生成され、事前定義されたリストの必要性がなくなりました。モデルは製品名とカテゴリを分析し、電子商取引に関連する同義語を確保しました。

さらに、同義語 API により辞書管理が簡素化され、同義語セットを動的に変更できるようになりました。このアプローチにより、検索はより柔軟になり、さまざまなユーザーのクエリ パターンに適応できるようになりました。

このプロセスは、新しいデータとモデルの調整によって継続的に改善され、ますます効率的な研究体験を保証します。

参照資料

Elasticsearchをローカルで実行する

https://www.elastic.co/guide/en/elasticsearch/reference/current/run-elasticsearch-locally.html

同義語API

https://www.elastic.co/guide/en/elasticsearch/reference/current/synonyms-apis.html

関連記事

判断リストによる検索クエリの関連性の評価

Jhon Guzmán

MLを使用したフィルターとファセットの生成

Andre Luiz

コンテキストのためのYou Know - パート1:ハイブリッド検索とコンテキストエンジニアリングの進化

Woody Walton

Elasticsearchでの後期相互作用モデルのスケーリング - パート 2

Peter Straßer

面倒な手間を省いたハイブリッド検索:リトリーバーによるハイブリッド検索の簡素化

Mike Pellegrini

最先端の検索体験を構築する準備はできましたか?

十分に高度な検索は 1 人の努力だけでは実現できません。Elasticsearch は、データ サイエンティスト、ML オペレーター、エンジニアなど、あなたと同じように検索に情熱を傾ける多くの人々によって支えられています。ぜひつながり、協力して、希望する結果が得られる魔法の検索エクスペリエンスを構築しましょう。

はじめましょう