Pineconeとpgvectorでハイブリッド検索
Pineconeではsparse-denseインデックスモード、pgvectorではRRF統合を用いた並列クエリでハイブリッド検索を構成し、データセット上で検索品質をベンチマークします。
「Pineconeとpgvectorでハイブリッド検索」はCoddyKit上の無料AI Engineering Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはAI Engineering Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 AI Engineering Academyコースには全4レッスンが含まれています。
ベクトルデータベースのネイティブハイブリッド検索
2つの別々のインデックスとRRF融合を使ってハイブリッド検索を自分で実装することもできますが、現在では本番環境向けのベクトルデータベースで、スパース検索と密ベクトル検索を1つのクエリで処理する組み込みハイブリッド検索が提供されるようになっています。Pineconeとpgvectorはいずれもハイブリッドモードをサポートしていますが、アーキテクチャの選択肢とトレードオフは異なります。両方の選択肢を理解することで、インフラに適したツールを選べるようになります。
Pineconeのスパース・密ベクトルインデックスモード
Pineconeはスパース・密ベクトルインデックスをサポートしています。このインデックスでは、各ベクトルレコードに、密ベクトル埋め込み(浮動小数点配列)とスパースベクトル(トークンIDから重みへの辞書)の両方が保存されます。クエリでは密ベクトルとスパースベクトルの両方を指定でき、Pineconeは重み付き線形結合によって結果を統合します。これはRRFとは異なり、Pineconeではalphaと呼ばれる設定可能な重みを使って生のスコアを融合します。
from pinecone import Pinecone, ServerlessSpec
pc = Pinecone(api_key='YOUR_API_KEY')
# Create a sparse-dense index
pc.create_index(
name='hybrid-index',
dimension=1536, # dense vector dimension
metric='dotproduct', # must use dotproduct for hybrid
spec=ServerlessSpec(cloud='aws', region='us-east-1'),
)
index = pc.Index('hybrid-index')SPLADEでスパースベクトルを生成する
Pineconeのスパース・密ベクトルモードを使うには、各文書のスパースベクトルを生成する必要があります。最も効果的な方法はSPLADE(Sparse Lexical and Expansion)です。これは、テキストに文字どおり現れる語だけでなく、意味的に関連する語もスパースベクトルに追加する、拡張機能付きの学習済みスパース表現を生成するニューラルモデルです。別の方法として、単純なBM25の語の重みをスパースベクトルとして使うこともできます。
from transformers import AutoTokenizer, AutoModelForMaskedLM
import torch
# Load SPLADE model
tokenizer = AutoTokenizer.from_pretrained('naver/splade-cocondenser-ensembledistil')
model = AutoModelForMaskedLM.from_pretrained('naver/splade-cocondenser-ensembledistil')
def generate_sparse_vector(text: str) -> dict:
tokens = tokenizer(text, return_tensors='pt', truncation=True, max_length=512)
with torch.no_grad():
output = model(**tokens)
logits = output.logits
# Max-pool over sequence length and apply log1p activation
sparse = torch.max(torch.log1p(torch.relu(logits)), dim=1).values.squeeze()
# Return non-zero indices and values as a sparse dict
nz_indices = sparse.nonzero().squeeze().tolist()
nz_values = sparse[nz_indices].tolist()
return {'indices': nz_indices, 'values': nz_values}ハイブリッドベクトルをPineconeにアップサートする
ハイブリッドインデックス内の各Pineconeレコードには、id、密ベクトルのvalues配列、indicesとvaluesを含むsparse_values辞書、そしてオプションのmetadataが保存されます。Pineconeのリクエストサイズ制限を守りながらスループットを最大化するには、100件ずつバッチでレコードをアップサートしてください。
def upsert_hybrid_docs(index, documents: list[dict], batch_size: int = 100):
records = []
for doc in documents:
dense_vec = embed(doc['text']) # OpenAI embedding
sparse_vec = generate_sparse_vector(doc['text']) # SPLADE
records.append({
'id': doc['id'],
'values': dense_vec,
'sparse_values': sparse_vec,
'metadata': {'text': doc['text'], 'source': doc['source']},
})
for i in range(0, len(records), batch_size):
batch = records[i:i + batch_size]
index.upsert(vectors=batch)
print(f'Upserted batch {i//batch_size + 1}')Pineconeのハイブリッドインデックスにクエリを実行する
ハイブリッドクエリでは、密ベクトルのvectorとスパースベクトルのsparse_vectorの両方をPineconeのクエリAPIに渡します。alphaパラメーター(0~1)がバランスを制御します。alpha=1.0は完全な密ベクトル検索、alpha=0.0は完全なスパース検索、alpha=0.5は両方に等しい重みを与える設定です。検証セットでalphaを調整してください。一般的な最適値は0.3~0.7の範囲です。
def hybrid_query_pinecone(index, query: str, alpha: float = 0.5, top_k: int = 5):
dense_vec = embed(query)
sparse_vec = generate_sparse_vector(query)
# Scale vectors by alpha for weighted fusion
scaled_dense = [v * alpha for v in dense_vec]
scaled_sparse = {
'indices': sparse_vec['indices'],
'values': [v * (1 - alpha) for v in sparse_vec['values']],
}
results = index.query(
vector=scaled_dense,
sparse_vector=scaled_sparse,
top_k=top_k,
include_metadata=True,
)
return results.matchespgvector:PostgreSQLでのベクトル検索
pgvector拡張機能は、vector列型と距離演算子をPostgreSQLに追加します。ハイブリッド検索では、2つのクエリを並列実行します。1つはベクトル列に対する近似最近傍クエリ、もう1つはPostgreSQL組み込みのtsvectorとtsqueryを使う全文検索クエリです。その後、アプリケーションコードでRRFを使って結果をマージします。
-- Setup: enable extensions
CREATE EXTENSION IF NOT EXISTS vector;
CREATE EXTENSION IF NOT EXISTS pg_trgm;
-- Create hybrid-capable table
CREATE TABLE documents (
id SERIAL PRIMARY KEY,
content TEXT NOT NULL,
embedding vector(1536),
content_tsv tsvector GENERATED ALWAYS AS (to_tsvector('english', content)) STORED
);
-- Indexes for both search modes
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops);
CREATE INDEX ON documents USING GIN (content_tsv);pgvectorで密ベクトルクエリとスパースクエリを実行する
pgvectorでは、<=>コサイン距離演算子を使って密ベクトルクエリを実行し、クエリ埋め込みに最も近い近傍を見つけます。また、tsvector列に対してts_rank_cdを使ってスパースクエリを実行し、キーワードの一致度をスコアリングします。両方の結果セットを独立して取得した後、PythonでRRFを使って融合します。
import psycopg2
import json
def pgvector_hybrid_search(conn, query: str, top_k: int = 5):
dense_vec = embed(query)
tsquery = ' & '.join(query.split()) # simple AND query
# Dense query
dense_sql = '''
SELECT id, content, 1 - (embedding <=> %s::vector) AS score
FROM documents
ORDER BY embedding <=> %s::vector
LIMIT 20
'''
# Sparse query
sparse_sql = '''
SELECT id, content, ts_rank_cd(content_tsv, to_tsquery('english', %s)) AS score
FROM documents
WHERE content_tsv @@ to_tsquery('english', %s)
ORDER BY score DESC
LIMIT 20
'''
with conn.cursor() as cur:
cur.execute(dense_sql, [json.dumps(dense_vec), json.dumps(dense_vec)])
dense_rows = cur.fetchall()
cur.execute(sparse_sql, [tsquery, tsquery])
sparse_rows = cur.fetchall()
return fuse_with_rrf(dense_rows, sparse_rows, top_k)pgvectorの結果にRRFを適用する
両方のPostgreSQLクエリから順位付けされた行を取得したら、各結果セットから順位順に文書IDを取り出し、融合アルゴリズムを実行してRRFを適用します。最終的なマージ済みリストには、意味の類似性とキーワードの重複の両方の観点で最も関連性の高い上位K件の文書が含まれます。
def fuse_with_rrf(dense_rows, sparse_rows, top_k: int = 5, k: int = 60):
from collections import defaultdict
doc_texts = {}
scores = defaultdict(float)
for rank, row in enumerate(dense_rows, start=1):
doc_id, text, _ = row
scores[doc_id] += 1.0 / (k + rank)
doc_texts[doc_id] = text
for rank, row in enumerate(sparse_rows, start=1):
doc_id, text, _ = row
scores[doc_id] += 1.0 / (k + rank)
doc_texts[doc_id] = text
ranked = sorted(scores.items(), key=lambda x: x[1], reverse=True)
return [{'id': doc_id, 'text': doc_texts[doc_id], 'rrf_score': s}
for doc_id, s in ranked[:top_k]]検索品質をベンチマークする
ハイブリッド検索を実装したら、ベースラインである密ベクトルのみの検索と比較して、厳密にベンチマークしてください。関連文書が既知のクエリを少なくとも100件含む、正解付きテストセットを使用します。NDCG@5、MRR、hit rate@3を測定してください。ハイブリッド検索によるNDCG@5の一般的な改善幅は5~20%で、密ベクトルモデルが捉えにくい正確な技術用語を含むクエリで最も大きな効果が得られます。
def evaluate_retrieval(search_fn, test_queries, golden_relevant, k=5):
ndcg_scores = []
hit_count = 0
for query, relevant_ids in zip(test_queries, golden_relevant):
results = search_fn(query, top_k=k)
retrieved_ids = [r['id'] for r in results]
# Hit rate
if any(rid in relevant_ids for rid in retrieved_ids):
hit_count += 1
# Simplified NDCG (binary relevance)
dcg = sum(
1.0 / (i + 1)
for i, rid in enumerate(retrieved_ids)
if rid in relevant_ids
)
idcg = sum(1.0 / (i + 1) for i in range(min(len(relevant_ids), k)))
ndcg_scores.append(dcg / idcg if idcg > 0 else 0)
return {
'hit_rate': hit_count / len(test_queries),
'ndcg': sum(ndcg_scores) / len(ndcg_scores),
}Pineconeのハイブリッドモードでalphaを調整する
Pineconeのハイブリッドクエリにおけるalphaパラメーターは、体系的に調整する必要があります。有効な方法の1つがクエリタイプによる層別化です。テストクエリを、主に意味的なクエリ(言い換え、概念)と、主に字句的なクエリ(正確な用語、コード)に分類し、グループごとに最適なalphaを測定します。本番システムの中には、動的alpha選択を実装し、実行時にクエリを分類して適切なalphaを自動的に選ぶものもあります。
def find_optimal_alpha(index, test_queries, golden_relevant, alphas=None):
if alphas is None:
alphas = [0.0, 0.2, 0.4, 0.5, 0.6, 0.8, 1.0]
best_alpha, best_score = 0.5, 0
for alpha in alphas:
hits = 0
for query, relevant in zip(test_queries, golden_relevant):
results = hybrid_query_pinecone(index, query, alpha=alpha, top_k=5)
if any(r['id'] in relevant for r in results):
hits += 1
hit_rate = hits / len(test_queries)
print(f'alpha={alpha}: hit_rate={hit_rate:.3f}')
if hit_rate > best_score:
best_score, best_alpha = hit_rate, alpha
return best_alphaPineconeとpgvectorのハイブリッド検索を選ぶ
Pineconeハイブリッドは、マネージドインフラストラクチャ、自動スケーリング、そしてスパースベクトル生成の複雑さを単一のAPIに任せたい場合に適しています。pgvectorハイブリッドは、すでにPostgreSQLのインフラストラクチャがあり、文書とメタデータ間のトランザクション整合性が必要な場合、フィルタリングにおける完全なSQLの柔軟性が必要な場合、またはベンダーロックインを避けたい場合に適しています。どちらの方法も、適切に調整すれば優れたハイブリッド検索品質を実現できます。
クイックチェック
このレッスンで学んだハイブリッド検索の実装についての理解度を確認しましょう。
レッスンのまとめ
このレッスンでは、Pineconeのスパース・密ベクトルモードが各レコードに密ベクトルとスパースベクトルの両方を保存し、設定可能なalphaパラメーターで融合すること、pgvectorのハイブリッド検索がSQL全文検索とベクトルクエリを組み合わせ、アプリケーションコードでRRFを使って融合すること、そして検証セットでのalpha調整がクエリ分布に最適なバランスを見つけるために不可欠であることを学びました。次は、再ランキングを使った2段階検索について学びます。
よくある質問
「Pineconeとpgvectorでハイブリッド検索」レッスンは無料ですか?
はい。「Pineconeとpgvectorでハイブリッド検索」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、AI Engineering Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 AI Engineering Academyコースには全4レッスンが含まれています。
「Pineconeとpgvectorでハイブリッド検索」で何を学びますか?
Pineconeではsparse-denseインデックスモード、pgvectorではRRF統合を用いた並列クエリでハイブリッド検索を構成し、データセット上で検索品質をベンチマークします。 ブラウザで直接実行するハンズオンコードでAI Engineering Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
AI Engineering Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのAI Engineering Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。
「Pineconeとpgvectorでハイブリッド検索」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このAI Engineering Academyレッスンでコードを書いて実行できますか?
はい。すべてのAI Engineering Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- 密検索と疎検索の比較:トレードオフ
- BM25キーワード検索を実装する
- スコア統合のための逆順位融合
- Pineconeとpgvectorでハイブリッド検索