Sentence Transformers 2.2 : 使用方法 : 意味検索 (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 11/26/2022 (v2.2.2)
* 本ページは、UKPLab/sentence-transformers の以下のドキュメントを翻訳した上で適宜、補足説明したものです:
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
- 人工知能研究開発支援
- 人工知能研修サービス(経営者層向けオンサイト研修)
- テクニカルコンサルティングサービス
- 実証実験(プロトタイプ構築)
- アプリケーションへの実装
- 人工知能研修サービス
- PoC(概念実証)を失敗させないための支援
- お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
◆ お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。
- 株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション
- sales-info@classcat.com ; Web: www.classcat.com ; ClassCatJP
Sentence Transformers 2.2 : 使用方法 : 意味検索
意味検索は検索クエリの内容を理解することにより検索精度を改良することを求めます。語彙の (lexical) 一致に基づいてドキュメントを検索するだけの従来の検索エンジンとは対照的に、意味検索は同義語 (synonyms) も見つけることができます。
背景
意味検索の背後のアイデアは、貴方のコーパスのすべてのエントリを (それらがセンテンス、パラグラフやドキュメントのいずれであろうとも) ベクトル空間に埋め込むことです。
検索時には、質問は同じベクトル空間に埋め込まれて、コーパスから最も近い埋め込みが見つかります。これらのエントリは質問と高い意味的なオーバーラップを持つはずです。
対称的 vs 非対称的意味検索
セットアップの 重要な違い は対照的 vs 非対称的意味検索です :
- 対照的意味検索 については、クエリーとコーパスのエントリはほぼ同じ長さで、同じ量のコンテンツを持っています。例として類似の質問を検索します : 貴方のクエリーは例えば “How to learn Python online?” で、“How to learn Python on the web?” のようなエントリを見つけることを望みます。対照的タスクに対しては、クエリーとコーパスのエントリーを潜在的には反転できるでしょう。
- 非対称的検索 については、通常は (質問や幾つかのキーワードのような) 短いクエリーを持ち、質問に答える長いパラグラフを見つけることを望みます。例としては、“What is Python” のようなクエリーで、パラグラフ “Python is an interpreted, high-level and general-purpose programming language. Python’s design philosophy …” を見つけたいです。非対称的検索に対しては、クエリーとコーパスのエントリーの反転は通常は意味がありません。
タスクの型に対して 正しいモデルを選択する ことは重要です。
対照的意味検索 に対して適切なモデル : 事前訓練済みセンテンス埋め込みモデル
非対称的検索 に対して適切なモデル : 事前訓練済み MS MARCO モデル
Python
(約 100 万エントリまでの) 小さいコーパスに対しては、クエリーとコーパスのすべてのエントリ間のコサイン類以度を計算できます。
以下の例では、幾つかの例文を持つ小さいコーパスを定義して、クエリーとコーパスに対して埋め込みを計算します。
そして util.cos_sim() 関数を使用してクエリーとすべてのコーパス・エントリ間のコサイン類以度を計算します。
大規模なコーパスに対しては、すべてのスコアのソートは非常に時間がかかります。そのため、top k エントリだけを取得するために torch.topk を使用します。
単純な例については、semantic_search.py をご覧ください :
"""
This is a simple application for sentence embeddings: semantic search
We have a corpus with various sentences. Then, for a given query sentence,
we want to find the most similar sentence in this corpus.
This script outputs for various queries the top 5 most similar sentences in the corpus.
"""
from sentence_transformers import SentenceTransformer, util
import torch
embedder = SentenceTransformer('all-MiniLM-L6-v2')
# Corpus with example sentences
corpus = ['A man is eating food.',
'A man is eating a piece of bread.',
'The girl is carrying a baby.',
'A man is riding a horse.',
'A woman is playing violin.',
'Two men pushed carts through the woods.',
'A man is riding a white horse on an enclosed ground.',
'A monkey is playing drums.',
'A cheetah is running behind its prey.'
]
corpus_embeddings = embedder.encode(corpus, convert_to_tensor=True)
# Query sentences:
queries = ['A man is eating pasta.', 'Someone in a gorilla costume is playing a set of drums.', 'A cheetah chases prey on across a field.']
# Find the closest 5 sentences of the corpus for each query sentence based on cosine similarity
top_k = min(5, len(corpus))
for query in queries:
query_embedding = embedder.encode(query, convert_to_tensor=True)
# We use cosine-similarity and torch.topk to find the highest 5 scores
cos_scores = util.cos_sim(query_embedding, corpus_embeddings)[0]
top_results = torch.topk(cos_scores, k=top_k)
print("\n\n======================\n\n")
print("Query:", query)
print("\nTop 5 most similar sentences in corpus:")
for score, idx in zip(top_results[0], top_results[1]):
print(corpus[idx], "(Score: {:.4f})".format(score))
"""
# Alternatively, we can also use util.semantic_search to perform cosine similarty + topk
hits = util.semantic_search(query_embedding, corpus_embeddings, top_k=5)
hits = hits[0] #Get the hits for the first query
for hit in hits:
print(corpus[hit['corpus_id']], "(Score: {:.4f})".format(hit['score']))
util.semantic_search
意味検索を貴方自身で実装する代わりに、util.semantic_search 関数を利用できます。
この関数は以下のパラメータを受け取ります :
sentence_transformers.util.semantic_search(
query_embeddings: torch.Tensor,
corpus_embeddings: torch.Tensor,
query_chunk_size: int = 100,
corpus_chunk_size: int = 500000,
top_k: int = 10,
score_function: typing.Callable[[torch.Tensor, torch.Tensor], torch.Tensor] = <function cos_sim>
)
この関数はクエリー埋め込みのリストとコーパス埋め込みのリストの間のコサイン類以度検索を実行します。約 100 万エントリまでのコーパスに対する情報検索 (Information Retrieval) / 意味的検索のために使用できます。
パラメータ
- query_embeddings – クエリー埋め込みを含む 2 次元テンソル。
- corpus_embeddings – コーパス埋め込みを含む 2 次元テンソル。
- query_chunk_size – 100 クエリーを同時に処理します。その値を増やせば速度が上がりますが、より多くのメモリを必要とします。
- corpus_chunk_size – 一度にコーパスの 100k エントリをスキャンします。その値を増やせば速度が上がりますが、より多くのメモリを必要とします。
- top_k – top k のマッチング・エントリを取得します。
- score_function – スコアを計算するための関数。デフォルトは、コサイン類以度。
戻り地
各クエリーに対して 1 つのエントリを含むリストを返します。各エントリはキー ‘corpus_id’ と ‘score’ を持つ辞書のリストで、コサイン類似度スコアにより降順にソートされています。
デフォルトでは、100 クエリーまで並列に処理されます。更に、コーパスは 500k エントリまでのセットに切り分けられます (chunked)。query_chunk_size と corpus_chunk_size を増やすことができて、これは大規模なコーパスに対して高速化に繋がりますが、メモリ要件も増やします。
スピードの最適化
util.semantic_search メソッドに対して最適な速度を得るには、query_embeddings と corpus_embeddings を同じ GPU デバイス上に持つことが望ましいです。これはパフォーマンスを本質的にブーストします。
更に、コーパス埋め込みを正規化することができて、その結果各コーパス埋め込みは長さ 1 になります。この場合、スコアを計算するためにドット積を使用できます。
corpus_embeddings = corpus_embeddings.to('cuda')
corpus_embeddings = util.normalize_embeddings(corpus_embeddings)
query_embeddings = query_embeddings.to('cuda')
query_embeddings = util.normalize_embeddings(query_embeddings)
hits = util.semantic_search(query_embeddings, corpus_embeddings, score_function=util.dot_score)
ElasticSearch
バージョン 7.3 から、ElasticSearch は密ベクトルをインデックス化してドキュメントのスコアリングのために使用することができるようになりました。こうして、ドキュメントに沿った埋め込みをインデックス化するために ElasticSearch を使用することができて、クエリー埋め込みを関連エントリを取得するために利用できます。
ElasticSearch の利点は、新しいドキュメントをインデックスに追加することが簡単で、ベクトルと一緒に他のデータもストアできることです。欠点は遅いパフォーマンスです、それはクエリー埋め込みをすべてのストアされた埋め込みと比較するからです。これは線形実行時間で、大規模な (>100k) コーパスに対しては非常に遅いかもしれません。
詳細は、semantic_search_quora_elasticsearch.py をご覧ください。
近似最近傍探索 (Approximate Nearest Neighbor)
数百万の埋め込みを含む大規模なコーパスの探索は、(util.semantic_search により使用されているような) 正確な最近傍探索が使用される場合、非常に時間がかかる可能性があります。
その場合、近似最近傍探索 (ANN) が役立つ可能性があります。ここでは、データは類似の埋め込みの小さい断片に分割されています。このインデックスは効率的に検索できて、数百万のベクトルを持つ場合でさえも最高の類以度を持つ埋め込み (最近傍) がミリ秒内に取得できます。
けれども、結果は必ずしも正確ではありません。高い類似性を持つ幾つかのベクトルが見落とされる可能性があります。これが近似最近傍探索と呼ばれる理由です。
すべての ANN メソッドについて、通常は recall-速度のトレードオフを決定する、調整すべき 1 つまたはそれ以上のパラメータがあります。最高速度を望む場合、ヒットを見逃す高い可能性があります。高い recall を望む場合、検索速度は低下します。
近似最近傍検索のための 3 つのポピュラーなライブラリは Annoy, FAISS と hnswlib です。個人的には hnswlib が最も適切なライブラリであることを見い出しました : それは簡単に使用できて、素晴らしいパフォーマンスを提供し、そして実アプリケーションに重要な特徴が含まれます。
サンプル:
検索 & 再ランク付け
複雑な意味的検索シナリオについては、検索 & 再ランク付けパイプラインが望ましいです :
詳細は、検索 & 再ランク付け をご覧ください。
サンプル
以下で様々なユースケースのためにサンプルをリストアップします :
類似質問検索
semantic_search_quora_pytorch.py [ Colab 版 ] は Quora 重複質問 データセットに基づいたサンプルを示しています。ユーザは質問を入力することができて、コードは util.semantic_search メソッドを使用して最も類似の質問をデータセットから取得します。モデルとしては、distilbert-multilingual-nli-stsb-quora-ranking を使用しています、これは類似の質問を識別するために訓練され、50+ 言語をサポートしています。そのため、ユーザは質問を 50+ 言語のいずれかで入力できます。これは対称的検索タスクで、検索クエリーはコーパスの質問と同じ長さとコンテンツを持ちます。
類似発表論文 (Publication) 検索
semantic_search_publications.py [ Colab 版 ] は類似の科学発表論文をどのように見つけるかのサンプルを示します。コーパスとしては、EMNLP 2016 – 2018 カンファレンスで発表されたすべての発表論文を使用します。検索クエリーとしては、最近の発表論文のタイトルとアブストラクトを入力して、コーパスから関連論文を見つけます。SPECTER モデルを使用します。これは 対照的な検索タスク です、コーパスの論文はタイトル & アブストラクトから構成され、私たちはタイトル & アブストラクトを検索するからです。
質問 & 回答検索
semantic_search_wikipedia_qa.py [ Colab 版 ]: このサンプルは Natural Questions データセット で訓練されたモデルを使用しています。それは約 100k の実際の Google 検索クエリーと、回答を提供する Wikipedia からのアノテートされたパッセージから構成されます。それは 非対称的な検索タスク の例です。コーパスとしては、小さい Simple English Wikipedia を使用していますので、メモリに容易に収まります。
retrieve_rerank_simple_wikipedia.py (訳注: リンク切れ) [ Colab 版 ]: このスクリプトは 検索 & 再ランク付け ストラテジーを使用する、非対称的な検索タスク の例です。すべての Wikipedia 記事をパラグラフに分割してそれらを bi-エンコーダでエンコードします。新しいクエリー / 質問が入力された場合、同じ bi-エンコーダでエンコードされて、最も高いコサイン類以度を持つパラグラフが取得されます (意味検索 参照)。次に、取得された候補は Cross-Encoder re-ranker によりスコア付けされて、Cross-Encoder からの最も高いスコアを持つ 5 パッセージがユーザに提示されます。私たちは (Bing 検索からの約 500k 実クエリーを含む) MS Marco Passage Reranking データセットで訓練されたモデルを使用しています、
以上