# ハイブリッド検索
注意
このガイドは、DBバージョン1.5.0以降にのみ適用されます。
このガイドでは、MyScaleでの全文検索とベクトル検索のハイブリッド検索の方法について説明します。
全文検索とベクトル検索は、それぞれ独自の強みと弱点があります。全文検索は基本的なキーワードの検索とテキストのマッチングに適しており、ベクトル検索はドキュメント間の意味的なマッチングや深い意味の理解に優れていますが、短いテキストクエリでは効率が低下する場合があります。両方のアプローチの利点を活用するために、ハイブリッド検索が開発されました。全文検索のインデックスとベクトル検索の特性を組み合わせることで、さまざまなテキスト検索の要件に効果的に対応し、テキストの検索精度と速度を向上させ、正確な結果を効率的に提供することができます。
# チュートリアルの概要
このドキュメントでは、次の3つの検索実験をガイドします:
すべてのドキュメントとクエリテキストは、all-MiniLM-L6-v2 (opens new window)モデルを使用してベクトルを生成し、類似度の測定にはコサイン距離を使用します。
MyScaleは、全文検索(FTS)のためのBM25インデックスを有効にするためにTantivy (opens new window)ライブラリを統合しています。テーブル内のすべてのテキストデータをインデックス化することにより、MyScaleのFTS機能はBM25スコアに基づいて検索結果をランク付けし、より関連性の高い正確な検索体験を提供します。
✅ RRF戦略を使用してベクトル検索とテキスト検索の結果を結合する
ベクトル検索とテキスト検索の結果を結合するために、広く使用されているPythonライブラリranx (opens new window)を使用します。この結合により、さまざまなシナリオでのテキスト検索の適応性が向上し、検索精度が最終的に向上します。
開始する前に、MyScaleクラスターが設定されていることを確認してください。セットアップ手順については、クイックスタートガイド (opens new window)を参照してください。
# データセット
この実験では、RediSearchが提供するWikipediaの要約データセット (opens new window)を使用しました。このデータセットには5,622,309のドキュメントエントリが含まれています。テキストはall-MiniLM-L6-v2 (opens new window)モデルを使用して生成され、body_vector
列に格納された384次元のベクトルとして保存されます。ベクトル間の類似度はコサイン距離を使用して計算されます。
TIP
all-MiniLM-L6-v2の使用方法の詳細については、HuggingFaceのドキュメント (opens new window)を参照してください。
データセットwiki_abstract_with_vector.parquet (opens new window)のサイズは8.2GBです。後続の実験では、このデータセットを直接S3経由でMyScaleにインポートするため、ローカルにダウンロードする必要はありません。
id | body | title | url | body_vector |
---|---|---|---|---|
... | ... | ... | ... | ... |
77 | Jake Rodkin is an American .... and Puzzle Agent. | Jake Rodkin | https://en.wikipedia.org/wiki/Jake_Rodkin (opens new window) | [-0.081793934,....,-0.01105572] |
78 | Friedlandpreis der Heimkehrer is ... of Germany. | Friedlandpreis der Heimkehrer | https://en.wikipedia.org/wiki/Friedlandpreis_der_Heimkehrer (opens new window) | [0.018285718,...,0.03049711] |
... | ... | ... | ... | ... |
# テーブルの作成とデータのインポート
MyScaleのSQLワークスペースでwiki_abstract_5m
というテーブルを作成するには、次のSQLステートメントを実行します。
CREATE TABLE default.wiki_abstract_5m(
`id` UInt64,
`body` String,
`title` String,
`url` String,
`body_vector` Array(Float32),
CONSTRAINT check_length CHECK length(body_vector) = 384
)
ENGINE = MergeTree
ORDER BY id
SETTINGS index_granularity = 128;
S3からデータをテーブルにインポートします。データのインポートプロセス中は、お待ちください。
INSERT INTO default.wiki_abstract_5m
SELECT * FROM s3('https://myscale-datasets.s3.ap-southeast-1.amazonaws.com/wiki_abstract_with_vector.parquet', 'Parquet');
注意
データのインポートには約10分かかる見込みです。
次のクエリを実行して、テーブルに5,622,309行のデータがあるかどうかを確認します。
SELECT count(*) FROM default.wiki_abstract_5m;
出力:
count() |
---|
5622309 |
ベクトル検索のパフォーマンスを向上させるために、テーブルのデータパーツを1つのパーツにマージするためにテーブルを最適化する(オプション)には、次のSQLコマンドをSQLワークスペースで実行します。
OPTIMIZE TABLE default.wiki_abstract_5m FINAL;
この最適化ステップには時間がかかる場合があります。
次のクエリを実行して、このテーブルのデータパーツが1つのパーツに圧縮されたかどうかを確認します。
SELECT COUNT(*) FROM system.parts WHERE table='wiki_abstract_5m' AND active=1;
成功した場合、次のように表示されます。
count() |
---|
1 |
# インデックスの作成
# FTSインデックスの作成
TIP
FTSインデックスの作成方法については、FTSインデックスのドキュメントを参照してください。
FTSインデックスを設定する際に、ユーザーはトークナイザーをカスタマイズするオプションがあります。この例では、stem
トークナイザーを使用し、stop words
を適用します。stem
トークナイザーは、テキスト内の単語の時制を無視してより正確な検索結果を得ることができます。stop words
を使用することで、「a」、「an」、「of」、「in」などの一般的な単語をフィルタリングして検索の精度を向上させることができます。
ALTER TABLE default.wiki_abstract_5m
ADD INDEX body_idx (body)
TYPE fts('{"body":{"tokenizer":{"type":"stem", "stop_word_filters":["english"]}}}');
インデックスのマテリアライズを実行します。
ALTER TABLE default.wiki_abstract_5m MATERIALIZE INDEX body_idx;
# ベクトルインデックスの作成
TIP
MSTGベクトルインデックスについての詳細は、ベクトル検索のドキュメントを参照してください。
default.wiki_abstract_5m
テーブルのbody_vec_idx
にCosineを距離計算方法として使用してMSTG
ベクトルインデックスを構築するには、次のSQLステートメントを実行します。
ALTER TABLE default.wiki_abstract_5m
ADD VECTOR INDEX body_vec_idx body_vector
TYPE MSTG('metric_type=Cosine');
ベクトルインデックスの構築には時間がかかる場合があります。進行状況を監視するために、次のSQLクエリを実行します。ステータス列が「Built」になっている場合、インデックスが正常に作成されたことを意味します。進行中の場合は「InProgress」と表示されるはずです。
SELECT * FROM system.vector_indices;
# ハイブリッド検索の実行
# 事前準備
クエリテキストをベクトルに変換するために、all-MiniLM-L6-v2 (opens new window)モデルを使用します。
まず、次のコマンドを実行してsentence-transformers
ライブラリをインストールします。
pip install -U sentence-transformers
次に、次のコマンドを実行してranx (opens new window)ライブラリをインストールします。
pip install -U ranx
ハイブリッド検索の実践は、Pythonファイルで実装されます。この実践を進める前に、提供されたサンプルコードにMyScaleクラスターの情報を入力してください。
TIP
MyScaleクラスターの接続詳細は、Webコンソールで確認できます。
import clickhouse_connect
from numba import NumbaTypeSafetyWarning
from prettytable import PrettyTable
from ranx import Run, fuse
from ranx.normalization import rank_norm
from sentence_transformers import SentenceTransformer
import warnings
warnings.filterwarnings('ignore', category=NumbaTypeSafetyWarning)
# Use transfromer all-MiniLM-L6-v2
model = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')
# MyScale information
host = "your cluster end-point"
port = 443
username = "your user name"
password = "your password"
database = "default"
table = "wiki_abstract_5m"
# Init MyScale client
client = clickhouse_connect.get_client(host=host, port=port,
username=username, password=password)
# Use a table to output your content
def print_results(result_rows, field_names):
x = PrettyTable()
x.field_names = field_names
for row in result_rows:
x.add_row(row)
print(x)
# ベクトル検索の実行
TIP
British Graham Land expedition(BGLE)は、イギリスの探検隊です。詳細については、Wikipedia (opens new window)を参照してください。
このデータセットで、BGLE探検隊が訪れ、マッピングした場所を特定することを目指します。そのために、ベクトル検索クエリを実行する際に「"Charted by BGLE"」を検索文字列として使用します。
TIP
前の準備作業中に作成したPythonファイルに、このメッセージで提供されたサンプルコードを含めて、プログラムのスムーズな実行を確保してください。また、他のステップも同様の方法で処理するようにしてください。
terms = "Charted by BGLE"
terms_embedding = model.encode([terms])[0]
vector_search = f"SELECT id, title, body, distance('alpha=3')" \
f"(body_vector,{list(terms_embedding)}) AS distance FROM {database}.{table} " \
f"ORDER BY distance ASC LIMIT 100"
vector_search_res = client.query(query=vector_search)
print("\nVectorSearch results:")
print_results(vector_search_res.result_rows[:10], ["ID", "Title", "Body", "Distance"])
ベクトル検索によって取得されたドキュメントは、トップ10の結果に「Charted by BGLE」と関連する情報は含まれていません。ベクトル検索は、短いテキストクエリに対しては性能が低下する傾向があるためです。
ID | Title | Body | Distance |
---|---|---|---|
1177089 | B.G. discography | This is the discography of B.G. | 0.44516414403915405 |
2710950 | Ruy Blas and the Blasé Roué | Ruy Blas and the Blasé Roué is a burlesque written by A. C. | 0.4736078381538391 |
4571962 | BLL | BLL may refer to: | 0.48070353269577026 |
3797998 | Bles | Bles may refer to: | 0.4849556088447571 |
4558187 | The Complete Blam Blam Blam | The Complete Blam Blam Bam is a compilation LP by Blam Blam Blam. It was released on November, 1992. | 0.4863016605377197 |
5556733 | BLG | BLG may refer to: | 0.49544525146484375 |
4591273 | Blagg | Blagg may refer to: | 0.5007722973823547 |
3087541 | Paul Bley discography | This is the discography for Canadian jazz musician Paul Bley. | 0.5030192136764526 |
3389086 | Blot (album) | Blot is the fourth full-length album by the Norwegian black/Viking metal band Einherjer. It was released on 21 November 2003 by Tabu Recordings. | 0.5040180087089539 |
1876940 | Blåljus! | Blåljus! is a six-volume light pocket series of novels by Margit Sandemo and published by the Swedish subsidiade Boknöje AB of the Norwegian publication Bladkompaniet in year 2004. | 0.5080643892288208 |
# テキスト検索の実行
MyScaleのTextSearch()
関数を使用して、関連する結果セットとBM25スコアを取得します。後で結果の結合を容易にするために、このドキュメントデータを一時的に保存する必要があります。
text_search = f"SELECT id, title, body, TextSearch(body, '{terms}') AS score " \
f"FROM {database}.{table} " \
f"ORDER BY score DESC LIMIT 100"
text_search_res = client.query(query=text_search)
# 検索結果を一時的に保存します。
stored_data = {}
for row in vector_search_res.result_rows:
stored_data[str(row[0])] = {"title": row[1], "body": row[2]}
for row in text_search_res.result_rows:
if str(row[0]) not in stored_data:
stored_data[str(row[0])] = {"title": row[1], "body": row[2]}
# ベクトル検索とテキスト検索の結果を結合するためにRRF戦略を使用する
ranx (opens new window)ライブラリを使用して、ベクトル検索とテキスト検索の結果を結合し、検索精度を向上させるためにRRF(Reciprocal Rank Fusion)結合戦略を実装します。他の結合戦略については、ranx/fusion (opens new window)を参照してください。
# クエリ結果からidとスコアを抽出します。
bm25_dict = {"query-0": {str(row[0]): float(row[3]) for row in text_search_res.result_rows}}
# ranxライブラリでは、スコアが高いほど関連性が高いとされるため、CosineやL2などのベクトル距離計算方法の場合は前処理が必要です。
max_value = max(float(row[3]) for row in vector_search_res.result_rows)
vector_dict = {"query-0": {str(row[0]): max_value - float(row[3]) for row in vector_search_res.result_rows}}
# クエリ結果のスコアを正規化します。
vector_run = rank_norm(Run(vector_dict, name="vector"))
bm25_run = rank_norm(Run(bm25_dict, name="bm25"))
# RRFを使用してクエリ結果を結合します。
combined_run = fuse(
runs=[vector_run, bm25_run],
method="rrf",
params={'k': 10}
)
print("\nFusion results:")
pretty_results = []
for id_, score in combined_run.get_doc_ids_and_scores()[0].items():
if id_ in stored_data:
pretty_results.append([id_, stored_data[id_]["title"], stored_data[id_]["body"], score])
print_results(pretty_results[:10], ["ID", "Title", "Body", "Score"])
結合クエリの結果は次のとおりです。ハイブリッド検索は、BGLE探検隊がチャートされた場所に関連する5つの記事を正確に一致させ、短いテキストクエリの処理におけるハイブリッド検索の利点を示しています。
ID | Title | Body | Score |
---|---|---|---|
200245 | Salmon Island | Salmon Island () is the westernmost of the Fish Islands, lying off the west coast of Graham Land. Charted by the British Graham Land Expedition (BGLE) under Rymill, 1934-37. | 0.09090909090909091 |
1177089 | B.G. discography | This is the discography of B.G. | 0.09090909090909091 |
5024941 | Woozle Hill | Woozle Hill () is a hill near the center of Galindez Island, in the Argentine Islands in the Wilhelm Archipelago. First charted by the British Graham Land Expedition (BGLE) under Rymill, 1934-37. | 0.08333333333333333 |
2710950 | Ruy Blas and the Blasé Roué | Ruy Blas and the Blasé Roué is a burlesque written by A. C. | 0.08333333333333333 |
4571962 | BLL | BLL may refer to: | 0.07692307692307693 |
4426976 | Symington Islands | Symington Islands () is a group of small islands lying west-northwest of Lahille Island, in the Biscoe Islands. Charted by the British Graham Land Expedition (BGLE) under Rymill, 1934-37. | 0.07692307692307693 |
3797998 | Bles | Bles may refer to: | 0.07142857142857142 |
197443 | Tadpole Island | Tadpole Island () is an island just north of Ferin Head, off the west coast of Graham Land. Charted by the British Graham Land Expedition (BGLE) under Rymill, 1934-37. | 0.07142857142857142 |
202128 | Sohm Glacier | Sohm Glacier () is a glacier flowing into Bilgeri Glacier on Velingrad Peninsula, the west coast of Graham Land. Charted by the British Graham Land Expedition (BGLE) under Rymill, 1934-37. | 0.06666666666666667 |
4558187 | The Complete Blam Blam Blam | The Complete Blam Blam Bam is a compilation LP by Blam Blam Blam. It was released on November, 1992. | 0.06666666666666667 |
# 結論
このドキュメントでは、テキスト検索シナリオでのMyScaleハイブリッド検索の使用方法についての洞察を提供し、非構造化テキストデータの検索における方法と技術に焦点を当てました。実践的な演習では、Wikipediaの要約を使用して例を開発しました。MyScaleの高度な全文検索とベクトル検索の機能を使用することで、ハイブリッド検索はキーワードと意味情報の両方を組み合わせることで、より正確な結果を提供します。