ブログ

Elasticsearch による語彙検索と意味検索

このブログでは、語彙検索と意味検索に焦点を当て、Elasticsearch を使用して情報を取得するためのさまざまなアプローチについて説明します。

検索は、検索クエリまたは複合クエリに基づいて最も関連性の高い情報を検索するプロセスであり、関連する検索結果はこれらのクエリに最も一致するドキュメントです。検索にはさまざまな課題と方法がありますが、最終的な目標は、質問に対する最適な回答を見つけることです

この目標を考慮して、このブログ投稿では、Elasticsearch を使用して情報を取得するためのさまざまなアプローチを検討し、特にテキスト検索(語彙検索とセマンティック検索)に焦点を当てます。

要件

これを実現するために、eコマース製品情報をシミュレートするために生成されたデータセットでのさまざまな検索シナリオを示す Python の例を提供します。

このデータセットには 2,500 を超える製品が含まれており、それぞれに説明が付いています。これらの製品は 76 の異なる製品カテゴリに分類されており、各カテゴリには以下に示すようにさまざまな数の製品が含まれています。

ツリーマップの視覚化 - カテゴリ.キーワードの上位 22 個の値 (製品カテゴリ)

セットアップには次のものが必要です:

Elastic Cloud を使用します。無料トライアルをご利用いただけます。

このブログ記事で提供されている検索クエリに加えて、 Python ノートブックでは次のプロセスをガイドします。

  • Pythonクライアントを使用してElasticデプロイメントへの接続を確立する

  • Elasticsearchクラスターにテキスト埋め込みモデルをロードする

  • 特徴ベクトルと密ベクトルのインデックスを作成するためのマッピングを使用してインデックスを作成します。

  • テキスト埋め込みとテキスト拡張のための推論プロセッサを備えた取り込みパイプラインを作成する

語彙検索 - スパース検索

テキスト クエリに基づいて Elasticsearch がドキュメントの関連性をランク付けする従来の方法では、 語彙検索用のスパース モデルである BM25 モデルの Lucene 実装が使用されます。この方法は、正確な用語の一致を探すという、テキスト検索の従来のアプローチに従います。

この検索を可能にするために、Elasticsearch はテキスト分析を実行してテキスト フィールドデータを検索可能な形式に変換します。

テキスト分析は、検索に関連するトークンを抽出するプロセスを管理する一連のルールであるアナライザーによって実行されます。アナライザーには、トークナイザーが 1 つだけ必要です。トークナイザーは文字のストリームを受け取り、それを個々のトークン (通常は個々の単語) に分割します。以下に例を示します。

語彙検索のための文字列トークン化

#Performs text analysis on a string and returns the resulting tokens.

# Define the text to be analyzed
text = "Comfortable furniture for a large balcony"

# Define the analyze request
request_body = {
  "analyzer": "standard",
  "text": text
}

# Perform the analyze request
response = client.indices.analyze(analyzer=request_body["analyzer"], text=request_body["text"])

# Extract and display the analyzed tokens
tokens = [token["token"] for token in response["tokens"]]
print("Analyzed Tokens:", tokens)

出力

Analyzed Tokens: ['comfortable', 'furniture', 'for', 'a', 'large', 'balcony']

この例では、デフォルトのアナライザーである標準アナライザーを使用しています。これは、英語の文法に基づいたトークン化を提供するため、ほとんどのユースケースに適しています。トークン化により、個々の用語での一致が可能になりますが、各トークンは文字どおりに一致します。

検索エクスペリエンスをカスタマイズしたい場合は、別の組み込みアナライザーを選択できます。たとえば、ストップ アナライザーを使用するようにコードを更新すると、ストップワードの削除がサポートされ、文字以外の文字でテキストがトークンに分割されます。

...
# Define the analyze request
request_body = {
  "analyzer": "stop",
  "text": text
}
...

出力

Analyzed Tokens: ['comfortable', 'furniture', 'large', 'balcony']

組み込みアナライザーがニーズを満たさない場合は、ゼロ個以上の 文字フィルター トークナイザー 、およびゼロ個以上の トークン フィルター の適切な組み合わせを使用する カスタム アナライザー を作成できます。

"analyzer":  {

  "my_analyzer": {

    "type": "custom", #For custom analyzers, use a type of custom or omit the type parameter.

    "tokenizer": "standard", #Built-in or customized tokenizer

    "filter": ["lowercase", "synonym"] #Built-in or customized token filters
  }
}

トークナイザーとトークン フィルターを組み合わせた上記の例では、テキストは、 同義語トークン フィルター によって処理される前に、 小文字フィルター によって小文字化されます。

語彙マッチング

BM25 は、用語の頻度と重要度に基づいて、特定の検索クエリに対するドキュメントの関連性を測定します。

以下のコードは、 一致 クエリを実行し、 「ecommerce-search」 インデックスの 「description」 フィールド値と検索クエリ 「 Comfortable furniture for a large balcony 」 を考慮して最大 2 つのドキュメントを検索します。

このクエリに一致すると見なされるドキュメントの基準を絞り込むと、精度が向上します。ただし、より具体的な結果を得るには、変動に対する許容度が低くなるというデメリットがあります。

# BM25

response = client.search(size=2,
index="ecommerce-search",
query= {
  "match": {
    "description" : {  
      "query": "Comfortable furniture for a large balcony",
      "analyzer": "stop"
    }
  }
}
)

hits = response['hits']['hits']

if not hits:
  print("No matches found")

else:
  for hit in hits:
    score = hit['_score']
    product = hit['_source']['product']
    category = hit['_source']['category']
    description = hit['_source']['description']
    print(f"\nScore: {score}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")

出力

Score: 15.607948
Product: Barbie Dreamhouse
Category: Toys
Description: is a classic Barbie playset with multiple rooms, furniture, a large balcony, a pool, and accessories. It allows kids to create their dream Barbie world.

Score: 9.137739
Product: Comfortable Rocking Chair
Category: Indoor Furniture
Description: enjoy relaxing moments with this comfortable rocking chair. Its smooth motion and cushioned seat make it an ideal piece of furniture for unwinding.

出力を分析すると、最も関連性の高い結果は「 おもちゃ 」カテゴリの「 バービードリームハウス 」製品であり、その説明には「 家具 」、「 大型」 「バルコニー 」という用語が含まれているため関連性が高く、説明に検索クエリに一致する用語が 3 つ含まれている唯一の製品であり、説明に 「バルコニー」 という用語が含まれているのもこの製品のみです。

2 番目に関連性の高い製品は、「 屋内用家具 」に分類される「 快適なロッキングチェア 」で、その説明には「 快適な 」および「 家具 」という用語が含まれています。データセット内のこの検索クエリの少なくとも 2 つの用語に一致する製品は 3 つだけであり、この製品はそのうちの 1 つです。

「快適」という言葉は 105 製品の説明に登場し、 「家具」という言葉は、おもちゃ屋内用家具、屋外用家具、および「犬と猫の用品とおもちゃ」という 4 つのカテゴリーの 4 製品の説明に登場しています。

ご覧のとおり、クエリを考慮すると最も関連性の高い製品はおもちゃであり、2 番目に関連性の高い製品は屋内用家具です。これらのドキュメントが一致する理由を知るためにスコア計算に関する詳細な情報が必要な場合は、 explain __query パラメータを true に設定できます。

両方の結果が最も関連性の高いものであるにもかかわらず、このデータセット内のドキュメントの数と用語の出現の両方を考慮すると、クエリ「大きなバルコニー用の快適な家具」の背後にある意図は、おもちゃや室内用家具などを除いた、実際の大きなバルコニー用の家具を検索することです。

語彙検索は比較的単純かつ高速ですが、ユーザーの意図やクエリを必ずしも知らずにすべての用語と同義語を知ることが常に可能であるとは限らないため、限界があります。自然言語の使用においてよく見られる現象は語彙の不一致です。調査によると、平均して80% の確率で、異なる人々 (同じ分野の専門家) が同じものを異なる名前で呼ぶことがわかっています。

これらの制限により、意味的知識を組み込んだ他のスコアリング モデルを探すことになります。自然言語のような連続的な入力トークンの処理に優れたトランスフォーマーベースのモデルは、ドキュメントとクエリの両方の数学的表現を考慮して、検索の根本的な意味を捉えます。これにより、テキストの高密度でコンテキストを認識したベクトル表現が可能になり、関連するコンテンツを見つけるための洗練された方法であるセマンティック検索が強化されます。

セマンティック検索 - 高密度検索

このコンテキストでは、データを意味のあるベクトル値に変換した後、 k 最近傍 (kNN)検索アルゴリズムを使用して、データセット内でクエリ ベクトルに最も類似したベクトル表現を検索します。Elasticsearch は、kNN 検索に、正確なブルート フォース kNN近似 kNN (ANN とも呼ばれる) の 2 つの方法をサポートしています。

ブルートフォース kNN は正確な結果を保証しますが、大規模なデータセットでは適切に拡張できません。近似 kNN は、パフォーマンスを向上させるために精度をある程度犠牲にして、近似最近傍を効率的に見つけます。

Lucene の kNN 検索と高密度ベクトル インデックスのサポートにより、Elasticsearch は階層的ナビゲート可能スモール ワールド (HNSW) アルゴリズムを活用し、さまざまなann ベンチマーク データセットにわたって強力な検索パフォーマンスを発揮します。以下のサンプルコードを使用して、Python で近似 kNN 検索を実行できます。

近似kNNによるセマンティック検索

# KNN - approximate kNN

response = client.search(index='ecommerce-search', size=2,
knn={
  "field": "description_vector.predicted_value",
  "k": 50, # Number of nearest neighbors to return as top hits.
#The optimal value of k is dependent on the data. It can vary in different scenarios.

  "num_candidates": 500, # Number of nearest neighbor candidates to consider per shard.

#Increasing num_candidates tends to improve the accuracy of the final k results.

  "query_vector_builder": { # Object indicating how to build a query_vector. kNN search enables you to perform semantic search by using a previously deployed text embedding model, the steps for this process are demonstrated in the Python notebook.
    "text_embedding": { 
      "model_id": "sentence-transformers__all-mpnet-base-v2", # Text embedding model id
      "model_text": "Comfortable furniture for a large balcony" # Query
    }
  }
}
)

for hit in response['hits']['hits']:
        
  score = hit['_score']
  product = hit['_source']['product']
  category = hit['_source']['category']
  description = hit['_source']['description']
  print(f"\nScore: {score}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")

このコード ブロックは、Elasticsearch の kNN を使用して、製品データセットの「 description 」フィールドの埋め込みを考慮した「 大きなバルコニー用の快適な家具 」のベクトル化されたクエリ (query_vector_build) に類似した説明を持つ最大 2 つの製品を返します。

製品の埋め込みは、以前はパイプラインに取り込まれたデータに対して推論するための all-mpnet-base-v2 テキスト埋め込みモデルを含む推論プロセッサを使用して、取り込みパイプラインで生成されていました。

このモデルは、 sentence_transformers.evaluation を使用した事前学習済みモデルの評価に基づいて選択されました。トレーニング中にさまざまなクラスを使用してモデルを評価します。「all-mpnet-base-v2」モデルは、Sentence-Transformers ランキングで最高の平均パフォーマンスを示し、 Massive Text Embedding Benchmark (MTEB)リーダーボードでも好位置を獲得しました。このモデルは、Microsoft/mpnet ベースのモデルを事前トレーニングし、10 億の文のペアのデータセットで微調整されており、文を 768 次元の密なベクトル空間にマッピングします。

あるいは、特にドメイン固有のデータに合わせて微調整されたモデルなど、使用できる他のモデルも多数あります。

出力

Score: 0.79207325
Product: Patio Sofa Set with Ottoman
Category: Outdoor Furniture
Description: is a versatile and comfortable patio sofa set, including a sofa, ottoman, and coffee table, great for outdoor lounging.

Score: 0.7836937
Product: Patio Sofa Set with Canopy
Category: Outdoor Furniture
Description: is a luxurious and comfortable patio sofa set with a canopy, providing shade and style for outdoor lounging.

出力は、選択したモデル、 フィルター 、および おおよその kNN チューニング によって異なる場合があります 。

kNN 検索結果は両方とも「 Outdoor Furniture 」カテゴリにありますが、クエリの一部として「 outdoor 」という単語が明示的に言及されておらず、コンテキストにおけるセマンティクスの理解の重要性が強調されています。

高密度ベクトル検索にはいくつかの利点があります。

  • セマンティック検索の有効化

  • 非常に大規模なデータセットを処理できるスケーラビリティ

  • 幅広いデータタイプを処理できる柔軟性

しかし、高密度ベクトル探索には独自の課題もあります

  • ユースケースに適した埋め込みモデルの選択

  • モデルが選択されると、ドメイン固有のデータセットでのパフォーマンスを最適化するためにモデルを微調整する必要がある場合があり、このプロセスにはドメイン専門家の関与が求められる。

  • さらに、高次元ベクトルのインデックス作成は計算コストが高くなる可能性がある。

セマンティック検索 - 学習されたスパース検索

別のアプローチとして、セマンティック検索を実行する別の方法である学習済みスパース検索を検討してみましょう。

スパースモデルとして、数十年にわたる最適化の恩恵を受けている Elasticsearch の Lucene ベースの転置インデックスを活用します。ただし、このアプローチは、BM25 などの語彙スコアリング関数を使用して同義語を単純に追加するだけにとどまりません。代わりに、より深い言語スケールの知識を使用して学習した関連性を組み込み、関連性を最適化します。

Elastic Learned Sparse Encoder は、検索クエリを拡張して元のクエリには存在しない関連用語を含めることにより、以下の例に示すように、スパース ベクトル埋め込みを改善します

Elastic Learned Sparse Encoder によるスパースベクトル検索

# Elastic Learned Sparse Encoder

response = client.search(index='ecommerce-search', size=2,
query={
  "text_expansion": {
    "ml.tokens": {
      "model_id":"elser_model",
      "model_text":"Comfortable furniture for a large balcony"                
    }
  }
}
)

for hit in response['hits']['hits']:

  score = hit['_score']
  product = hit['_source']['product']
  category = hit['_source']['category']
  description = hit['_source']['description']
  print(f"\nScore: {score}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")

出力

Score: 14.405318
Product: Garden Lounge Set with Side Table
Category: Garden Furniture
Description: is a comfortable and stylish garden lounge set, including a sofa, chairs, and a side table for outdoor relaxation.

Score: 14.281318
Product: Rattan Patio Conversation Set
Category: Outdoor Furniture
Description: is a stylish and comfortable outdoor furniture set, including a sofa, two chairs, and a coffee table, all made of durable rattan material.

この場合の結果には、「 屋外用家具 」に非常に類似した製品を提供する「 ガーデン家具 」カテゴリが含まれます。

「ml.tokens」を分析すると、学習済みスパース検索によって生成されたトークンを含む「rank_features」フィールドを見ると、生成されたさまざまなトークンの中に、「リラックス」(快適)、「ソファ」(家具)、「屋外」(バルコニー)など、検索クエリの一部ではないものの、意味的には関連している用語があることがわかります。

以下の画像は、用語拡張ありとなしの両方で、クエリとともにこれらの用語の一部を強調表示しています。

ご覧のとおり、このモデルはコンテキスト認識型の検索を提供し、より解釈しやすい結果を提供しながら語彙の不一致の問題を軽減するのに役立ちます。ドメイン固有の再トレーニングが適用されない場合でも、高密度ベクトル モデルよりも優れたパフォーマンスを発揮します。

ハイブリッド検索: 語彙検索と意味検索を組み合わせた関連性の高い結果

検索に関しては、普遍的な解決策はありません。これらの検索方法にはそれぞれ長所がありますが、課題もあります。ユースケースに応じて、最適なオプションは変わる場合があります。多くの場合、複数の検索方法間で最良の結果が補完的になります。したがって、関連性を高めるために、それぞれの方法の長所を組み合わせることを検討します。

ハイブリッド検索を実装する方法は複数あります。線形結合、各スコアへの重み付け、重みの指定が不要な逆ランク融合 (RRF) などです。

Elasticsearch: 語彙検索と意味検索の両方の長所を活用

# BM25 + Elastic Learned Sparse Encoder (Linear Combination)

response = client.search(index='ecommerce-search', size=2,

query= {
  "bool": {
    "should": [
    {
      "match": {
        "description" : {  
          "query": "A dining table and comfortable chairs for a large balcony",
          "boost": 1
        }
      }
    },                   
    {
      "text_expansion": {
        "ml.tokens": {
          "model_id": "elser_model",
          "model_text": "A dining table and comfortable chairs for a large balcony",
          "boost": 1
        }
      }
     }
    ]
  }
}
)

# The boost value is 1 for the text expansion and match query. This means that the relevance score of the results of these queries are not boosted. You can specify a boost value to give a weight to each score in the sum. The scores will be calculated as: score = boost value * match_score + boost value * text_expansion_score

for hit in response['hits']['hits']:

  score = hit['_score']
  product = hit['_source']['product']
  category = hit['_source']['category']
  description = hit['_source']['description']
  print(f"\nScore: {score}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")

このコードでは、「大きなバルコニー用のダイニング テーブルと快適な椅子」という値を持つ 2 つのクエリを使用してハイブリッド検索を実行しました。検索語として「家具」を使用する代わりに、探しているものを指定しており、両方の検索で同じフィールド値「説明」を考慮しています。ランキングは、BM25 スコアと ELSER スコアに等しい重み付けをした線形結合によって決定されます。

出力

Score: 31.628141
Product: Garden Dining Set with Swivel Rockers
Category: Garden Furniture
Description: is a functional and comfortable garden dining set, including a table and chairs with swivel rockers for easy movement.

Score: 31.334227
Product: Garden Dining Set with Swivel Chairs
Category: Garden Furniture
Description: is a functional and comfortable garden dining set, including a table and chairs with swivel seats for convenience.

以下のコードでは、クエリに同じ値を使用しますが、逆ランク融合法を使用して BM25 (クエリ パラメータ) と kNN (knn パラメータ) のスコアを結合し、ドキュメントを結合してランク付けします。

# BM25 + KNN (RRF)

response = client.search(index='ecommerce-search', size=2,
query={
  "bool": {
    "should": [
    {
      "match": {
        "description": {
        "query": "A dining table and comfortable chairs for a large balcony"
        }
      }
    }
    ]
  }
},
knn={
  "field": "description_vector.predicted_value",
  "k": 50,
  "num_candidates": 500,
  "query_vector_builder": {
    "text_embedding": {
      "model_id": "sentence-transformers__all-mpnet-base-v2",
      "model_text": "A dining table and comfortable chairs for a large balcony"
    }
  }
},
rank={
  "rrf": { # Reciprocal rank fusion
    "window_size": 50, # This value determines the size of the individual result sets per query.
    "rank_constant": 20 # This value determines how much influence documents in individual result sets per query have over the final ranked result set.
  }
}
)

for hit in response['hits']['hits']:
        
  rank = hit['_rank']
  category = hit['_source']['category']
  product = hit['_source']['product']
  description = hit['_source']['description']
  print(f"\nRank: {rank}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")

RRF 機能はテクニカル プレビュー段階です。GA の前に構文が変更される可能性があります。

出力

Rank: 1
Product: Patio Dining Set with Bench
Category: Outdoor Furniture
Description: is a spacious and functional patio dining set, including a dining table, chairs, and a bench for additional seating.

Rank: 2
Product: Garden Dining Set with Swivel Chairs
Category: Garden Furniture
Description: is a functional and comfortable garden dining set, including a table and chairs with swivel seats for convenience.

ここでは、異なるフィールドと値を使用することもできます。これらの例のいくつかは、 Python ノートブックで利用できます。

ご覧のとおり、Elasticsearch を使用すると、従来の語彙検索とベクトル検索(疎かでも密かでも)の両方の利点を活用でき、目標を達成して質問に対する最適な回答を見つけることができます。

ここで説明したアプローチについてさらに学習したい場合は、次のブログが役立ちます。

Elasticsearch は、ベクター検索を構築するために必要なすべてのツールとともに、ベクター データベースを提供します。

まとめ

このブログ記事では、Elasticsearch を使用して情報を取得するためのさまざまなアプローチについて検討し、特にテキスト、語彙、意味の検索に焦点を当てました。これを実証するために、eコマース製品情報を含むデータセットを使用してさまざまな検索シナリオを紹介する Python の例を示しました。

BM25 を使用した従来の語彙検索をレビューし、語彙の不一致などの利点と課題について議論しました。この問題を克服するために、意味的知識を取り入れることの重要性を強調しました。さらに、セマンティック検索を可能にする高密度ベクトル検索について説明し、高次元ベクトルのインデックス作成時の計算コストなど、この検索方法に関連する課題についても説明しました。

一方、スパースベクトルは圧縮率が非常に高いことを説明しました。そこで、元のクエリには存在しない関連用語を含めるように検索クエリを拡張する Elastic の Learned Sparse Encoder について説明しました。

検索に関しては、万能の解決策は存在しません。それぞれの検索方法には長所と課題があります。そこで、ハイブリッド検索の概念についても議論しました。

ご覧のとおり、Elasticsearch を使用すると、従来の語彙検索とベクトル検索の両方の長所を活用できます。

始める準備はできましたか?利用可能なPython ノートブックを確認し、 Elastic Cloud の無料トライアルを開始してください。

関連記事

LINQ to Elasticsearch ES|QL:C#を記述してElasticsearchをクエリ

Florian Bernd

Elasticsearch の NLP とベクトル検索によるチャットボット機能の強化

Priscilla Parodi

OpenSearchとElasticsearchの違い:ベクトル検索のパフォーマンス比較

Ugo Sangiorgi

AI盗作:Elasticsearchによる盗作検出

Priscilla Parodi

高度なRAGテクニックパート2:クエリとテスト

Han Xiang Choong

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

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

はじめましょう