ベクトル検索は意味的な意味を捉えますが、完全なキーワード一致を見逃すことがあります。全文検索は正確なキーワード一致を処理しますが、類義語や言い換えなどの意味的関係を見逃します。双方向取得では、これら両方を組み合わせます。AnalyticDB for PostgreSQL はベクトル検索と全文検索を並行して実行し、その結果をマージして再ランク付けすることで、取得率を最大化します。
V6.0 のインスタンスについては、「ベクトル検索と全文検索に基づく双方向取得」をご参照ください。
仕組み
ベクトル検索 — 密なベクトル表現に対して近似最近傍探索 (ANNS) を使用し、意味的に類似する上位 K 個のアイテムを取得します。
全文検索 — pgsearch ベースの BM25 インデックスを使用して、term frequency と inverse document frequency に基づいてアイテムを照合し、キーワードの関連性が高い結果で補完します。V6.0 では、全文検索は GIN インデックスに依存していました。V7.0 では、pgsearch ベースの BM25 インデックスにより、取得効率と関連性が向上します。
再ランク付けと後処理 — 2 つの結果セットをマージし、ハイブリッドスコアを使用して単一のリストに再ランク付けします。
サポートされるバージョン
AnalyticDB for PostgreSQL V7.0 インスタンスの V7.2.1.0 以降。
インスタンスの [基本情報] ページでマイナーバージョンを確認できます。ご利用のインスタンスが必要なバージョンを満たしていない場合は、インスタンスのマイナーバージョンを更新してください。
前提条件
開始する前に、以下を確認してください:
ご利用の AnalyticDB for PostgreSQL インスタンスでベクトル検索エンジンの最適化機能が有効になっていること
pgsearch 拡張機能がインストールされていること (インストールされている場合、スキーマリストに
pgsearchスキーマが表示されます)
pgsearch 拡張機能がインストールされていない場合は、チケットを送信してインストールのサポートを依頼してください。インストール後、インスタンスを再起動します。
双方向取得の設定
ステップ 1: サンプルテーブルの作成
documents という名前のテーブルを作成し、サンプルデータを挿入します。
-- テーブルを作成します。ベクトル列にはベクトルデータが格納されます。
CREATE TABLE IF NOT EXISTS documents(
id TEXT,
docname TEXT,
title TEXT,
vector real[],
text TEXT);
-- ベクトル列のストレージモードを PLAIN に設定します。
ALTER TABLE documents ALTER COLUMN vector SET STORAGE PLAIN;
-- サンプルデータを挿入します。
INSERT INTO documents (id, docname, title, vector, text) VALUES
('1', 'doc_1', 'Exploring the Universe',
'{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0}',
'The universe is vast, filled with mysteries and astronomical wonders waiting to be discovered.'),
('2', 'doc_2', 'The Art of Cooking',
'{0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1}',
'Cooking combines ingredients artfully, creating flavors that nourish and bring people together.'),
('3', 'doc_3', 'Technology and Society',
'{0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2}',
'Technology transforms society, reshaping communication, work, and our daily interactions significantly.'),
('4', 'doc_4', 'Psychology of Happiness',
'{0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3}',
'Happiness is complex, influenced by relationships, gratitude, and the pursuit of meaningful experiences.'),
('5', 'doc_5', 'Sustainable Living Practices',
'{0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4}',
'Sustainable living involves eco-friendly choices, reducing waste, and promoting environmental awareness.');ステップ 2: インデックスの作成
vector 列にベクトルインデックスを、text 列に BM25 全文検索インデックスを作成します。
-- ベクトルインデックス
CREATE INDEX documents_idx ON documents USING ann(vector) WITH (dim = 10, algorithm = hnswflat, distancemeasure = L2, vector_include = 0);
-- BM25 全文検索インデックス
CALL pgsearch.create_bm25(
index_name => 'documents_bm25_idx',
table_name => 'documents',
text_fields => '{text: {}}'
);ステップ 3: 双方向取得の実行
双方向取得では、両方のインデックスに並行してクエリを実行し、ハイブリッドスコアを使用して結果を結合します。2 つのスコアリングメソッドが利用可能です。
スコアリングメソッドの選択
| メソッド | 仕組み | 使用するケース |
|---|---|---|
| 加重スコア | 設定可能な重みを使用して、生の BM25 スコアとベクトル類似度スコアを結合します | 各検索タイプが最終スコアにどの程度貢献するかを詳細に制御したい場合 |
| 逆順位融合 (RRF) | 結果ランキング(生のスコアではない)を、数式 1 / (k + rank) | 2 つの検索タイプからの生のスコアの尺度が異なり、正規化が困難な場合 |
オプション 1: 加重スコア
このクエリでは、2 つの共通テーブル式 (CTE) を使用します。t1 は @@@ 演算子を介して全文検索を実行し、t2 は <-> 演算子を介してベクトル検索を実行します。FULL OUTER JOIN で結果をマージし、最終的なハイブリッドスコアは BM25 スコアとベクトル類似度スコアの加重和になります。
WITH t1 AS (
SELECT
id,
docname,
title,
text,
text @@@ pgsearch.config('text:astronomical') AS score,
2 AS source
FROM
documents
ORDER BY score
LIMIT 10
),
t2 AS (
SELECT
id,
docname,
title,
text,
cosine_similarity(vector, ARRAY[0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1]::real[]) AS score,
1 AS source
FROM
documents
ORDER BY vector <-> ARRAY[0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1]
LIMIT 10
)
SELECT t2.*, COALESCE(ABS(t1.score), 0.0) * 0.2 + COALESCE(t2.score, 0.0) * 0.8 AS hybrid_score
-- この重み配分はデモンストレーション用です。ユースケースに基づいて重みを調整してください。
FROM t1
FULL OUTER JOIN t2 ON t1.id = t2.id
ORDER BY hybrid_score DESC;この例のデフォルトの重みは、BM25 が 0.2、ベクトル類似度が 0.8 です。取得要件に基づいて重みを調整してください。
オプション 2: 逆順位融合 (RRF)
RRF は、各検索におけるランクに基づいて各結果をスコアリングし、ランクベースのスコアを合計します。両方の検索で上位にランク付けされたアイテムは、より高い結合スコアを受け取ります。平滑化パラメータ k (デフォルト: 60) は、上位ランキングがどの程度評価されるかを制御します。k が大きいほど、ランク間のスコア差が減少します。
ベクトル検索と全文検索からの生のスコアの尺度が異なり、直接的な正規化が非現実的な場合は、RRF を使用します。
WITH bm25 AS (
SELECT
id,
docname,
title,
text,
text @@@ pgsearch.config('text:astronomical') AS score,
2 AS source,
ROW_NUMBER() OVER () AS rank_bm25
FROM
documents
ORDER BY score
LIMIT 10
), hnsw AS (
SELECT
id,
docname,
title,
text,
vector <-> ARRAY[0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1] AS score,
1 AS source,
ROW_NUMBER() OVER (ORDER BY vector <-> ARRAY[0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1]) AS rank_hnsw
FROM
documents
ORDER BY vector <-> ARRAY[0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1]
LIMIT 10
)
SELECT
COALESCE(bm25.id, hnsw.id) AS id,
COALESCE(bm25.docname, hnsw.docname) AS docname,
COALESCE(bm25.title, hnsw.title) AS title,
COALESCE(bm25.text, hnsw.text) AS text,
CASE
WHEN bm25.rank_bm25 > 0 AND hnsw.rank_hnsw > 0 THEN
COALESCE(1.0 / (60 + bm25.rank_bm25), 0) + COALESCE(1.0 / (60 + hnsw.rank_hnsw), 0)
WHEN bm25.rank_bm25 > 0 THEN
COALESCE(1.0 / (60 + bm25.rank_bm25), 0)
WHEN hnsw.rank_hnsw > 0 THEN
COALESCE(1.0 / (60 + hnsw.rank_hnsw), 0)
ELSE 0
END AS hybrid_score
FROM
bm25
FULL OUTER JOIN hnsw ON bm25.id = hnsw.id
ORDER BY hybrid_score DESC;ステップ 4 (任意): クエリの関数化
加重スコアクエリを再利用可能な関数にラップして、繰り返しの呼び出しを簡素化します。
関数を作成します。
CREATE OR REPLACE FUNCTION search_documents( table_name TEXT, vector_column TEXT, text_column TEXT, search_keyword TEXT, search_vector REAL[], limit_size INT, hnsw_weight FLOAT8 DEFAULT 0.8 -- ベクトル検索のデフォルトの重み ) RETURNS TABLE ( id TEXT, docname TEXT, title TEXT, text TEXT, hybrid_score FLOAT8 ) AS $$ DECLARE query_string TEXT; bm25_weight FLOAT8; BEGIN bm25_weight := 1.0 - hnsw_weight; query_string := 'WITH t1 AS ( SELECT id, docname, title, ' || text_column || ', ' || text_column || ' @@@ pgsearch.config(''' || search_keyword || ''') AS score, 2 AS source FROM ' || table_name || ' ORDER BY score LIMIT ' || limit_size || ' ), t2 AS ( SELECT id, docname, title, ' || text_column || ', cosine_similarity(' || vector_column || ', $1) AS score, 1 AS source FROM ' || table_name || ' ORDER BY ' || vector_column || ' <-> $1 LIMIT ' || limit_size || ' ) SELECT t2.id, t2.docname, t2.title, t2.' || text_column || ', COALESCE(ABS(t1.score), 0.0) * ' || bm25_weight || ' + COALESCE(t2.score, 0.0) * ' || hnsw_weight || ' AS hybrid_score FROM t1 FULL OUTER JOIN t2 ON t1.id = t2.id ORDER BY hybrid_score DESC;'; RETURN QUERY EXECUTE query_string USING search_vector; END; $$ LANGUAGE plpgsql;関数を呼び出します。
SELECT * FROM search_documents( 'documents', -- テーブル名 'vector', -- ベクトル列 'text', -- テキスト列 'astronomical', -- 検索キーワード ARRAY[0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1], -- 検索ベクトル 10, -- 結果の上限 0.8 -- ベクトル検索の重み (BM25 の重み = 0.2) );