RAGやセマンティック検索の解説では「ベクトル検索」という言葉が頻出するが、埋め込みで数値化したあと、実際にどうやって「近い文書」を見つけているのかが見えにくい。
この記事は、ベクトル検索の全体像と、代表的な類似度指標であるコサイン類似度を、初心者向けに整理する。
結論:ベクトル検索は「意味の近さで探す検索」
| 観点 | 内容 |
|---|---|
| 定義 | テキストを埋め込みベクトルに変換し、クエリベクトルに近いベクトルを高速に探す検索方式 |
| 従来検索との違い | キーワード一致ではなく、意味の類似性でヒットする |
| 核心の計算 | ベクトル間の距離・類似度(コサイン類似度が最もよく使われる) |
| 典型用途 | RAGの検索ステップ、FAQ検索、重複文書検出、レコメンド |
| 覚え方 | Googleのキーワード検索の「意味版」 |
ポイント:ベクトル検索は単体の魔法ではない。事前にドキュメントを埋め込み化してベクトルDBに保存し、検索時にクエリも同じモデルで埋め込む——この2段階がセットだ。
ベクトル検索とは何か
ベクトル検索(vector search / similarity search)とは、高次元ベクトル空間の中で、クエリに最も近いベクトルを探し出す処理だ。
従来の全文検索(キーワード検索)との違いを表にまとめる。
| 方式 | マッチの基準 | 例:「退勤手順を教えて」 |
|---|---|---|
| キーワード検索 | 単語の一致・出現頻度 | 「退勤」「手順」が含まれる文書をヒット |
| ベクトル検索 | ベクトル空間上の距離 | 「業務終了時のチェックリスト」もヒットしうる |
キーワードが完全一致しなくても、意味が近ければ検索結果に現れる。RAGで「ユーザーの自然文の質問」から関連ドキュメントを引くとき、この性質が重要になる。
ベクトル検索の流れ(全体像)
[インデックス構築(オフライン)]
ドキュメント群
→ チャンク分割
→ 各チャンクを埋め込みモデルでベクトル化
→ ベクトルDBに保存(ベクトル + メタデータ + 元テキスト)
[検索(オンライン)]
ユーザーの質問
→ 同じ埋め込みモデルでベクトル化
→ ベクトルDBで類似ベクトルを検索(Top-K)
→ 上位K件の元テキストを取得
→ LLMのコンテキストに渡す(RAGの場合)
| フェーズ | やること | 注意点 |
|---|---|---|
| インデックス構築 | 全チャンクをベクトル化して保存 | モデルと次元数を記録しておく |
| 検索 | クエリベクトルと全ベクトルの類似度を計算 | インデックス時と同じモデルを使う |
| 結果の利用 | 上位K件をLLMに渡す | 閾値以下は除外する設計も有効 |
類似度の計算:コサイン類似度を中心に
2つのベクトルが「どれだけ近いか」を数値化する指標を類似度メトリックという。ベクトル検索で最もよく使われるのが**コサイン類似度(cosine similarity)**だ。
コサイン類似度とは
コサイン類似度は、2つのベクトルがなす角度のコサイン値を取る指標だ。
cosine_similarity(A, B) = (A · B) / (|A| × |B|)
A · B : ベクトルAとBの内積(各要素の積の総和)
|A| : ベクトルAの長さ(ユークリッドノルム)
値の範囲は -1〜1 で、1に近いほど方向が一致=意味が近いと解釈する。
| コサイン類似度 | 意味 |
|---|---|
| 1.0 | 完全に同じ方向(最も類似) |
| 0.0 | 直交(無関係) |
| -1.0 | 正反対の方向 |
テキスト埋め込みでは、ほとんどの場合 0.5〜0.9 の範囲にスコアが分布する。閾値はデータセットごとに調整が必要だ。
なぜコサイン類似度がよく使われるのか
| 理由 | 説明 |
|---|---|
| 文の長さに頑健 | ベクトルの「方向」だけを見るため、短い文と長い文の比較でも極端に不利になりにくい |
| 計算が軽い | 内積とノルムだけで求まる |
| 業界標準 | Pinecone、Weaviate、Qdrant、pgvector など主要ツールがデフォルトまたは推奨でサポート |
他の距離指標との比較
| 指標 | 計算の考え方 | 向いているケース |
|---|---|---|
| コサイン類似度 | ベクトルの角度 | テキスト埋め込み全般(最も一般的) |
| ユークリッド距離(L2) | 座標間の直線距離 | 画像埋め込みなど、ノルムが意味を持つ場合 |
| 内積(ドット積) | 方向と長さの両方 | 正規化済みベクトルではコサインと等価 |
| マンハッタン距離(L1) | 各次元の差の絶対値の総和 | スパースベクトル(TF-IDF等)のレガシー用途 |
多くの埋め込みモデルは出力ベクトルを正規化(長さを1に揃える)している。この場合、コサイン類似度と内積は同じ結果になる。
コサイン類似度の計算例(Python)
最小限の例で、3つの文の類似度を計算してみる。
import numpy as np
def cosine_similarity(a: np.ndarray, b: np.ndarray) -> float:
dot = np.dot(a, b)
norm_a = np.linalg.norm(a)
norm_b = np.linalg.norm(b)
if norm_a == 0 or norm_b == 0:
return 0.0
return dot / (norm_a * norm_b)
# 架空の3次元ベクトル(実際は数百〜数千次元)
vec_cat = np.array([0.8, 0.6, 0.1]) # 「猫はかわいい」
vec_kitten = np.array([0.75, 0.65, 0.05]) # 「ネコは愛らしい」
vec_stock = np.array([0.1, 0.2, 0.95]) # 「株価は上昇した」
print(f"猫 vs ネコ: {cosine_similarity(vec_cat, vec_kitten):.3f}") # → 高い値
print(f"猫 vs 株価: {cosine_similarity(vec_cat, vec_stock):.3f}") # → 低い値
実務では sentence-transformers や各社の埋め込みAPIでベクトル化してから、同じ計算を行う。
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")
texts = [
"退勤時の手順を教えて",
"業務終了時のチェックリスト",
"有給休暇の申請方法",
]
embeddings = model.encode(texts)
# クエリ(0番)と各文書の類似度
query_vec = embeddings[0].reshape(1, -1)
for i, text in enumerate(texts):
score = cosine_similarity(query_vec, embeddings[i].reshape(1, -1))[0][0]
print(f"[{score:.3f}] {text}")
「退勤」と「業務終了」のペアは高スコア、「有給休暇」は低スコアになる傾向が見られるはずだ。
大量データでの高速検索(近似最近傍探索)
ドキュメントが数千件なら全件と類似度を計算しても問題ない。しかし数百万〜数億件になると、クエリごとに全件計算する**総当たり(brute-force / exact search)**は遅すぎる。
そこで使われるのが**近似最近傍探索(ANN: Approximate Nearest Neighbor)**だ。
| 方式 | 概要 | 代表例 |
|---|---|---|
| 総当たり(Exact) | 全ベクトルと類似度を計算 | 小規模データ向け |
| HNSW | グラフ構造で近傍を高速探索 | pgvector、Qdrant、Weaviate |
| IVF | クラスタに分割して探索範囲を絞る | Faiss、Milvus |
| LSH | ハッシュで近いベクトルを同じバケットに | 大規模・許容誤差あり |
ANNは「完全に正確なTop-K」ではなく「ほぼ正しいTop-K」を高速に返すトレードオフだ。RAGの実務では、99%以上のケースで十分な精度が出る。
ベクトルDBで検索するときの設定項目
主要なベクトルDBには、検索時に次のパラメータを指定する。
| パラメータ | 意味 | 典型値 |
|---|---|---|
top_k |
返す件数 | 3〜10(RAGでは多めに取りリランキングも) |
metric |
類似度の種類 |
cosine(テキスト向け) |
score_threshold |
閾値以下を除外 | 0.7〜0.8(データセットで調整) |
ef / nprobe
|
ANNの精度と速度のトレードオフ | DBのドキュメント参照 |
Pineconeの例(概念):
# 概念例:実際のSDKはバージョンで異なる
results = index.query(
vector=query_embedding,
top_k=5,
include_metadata=True,
# metric="cosine" はインデックス作成時に指定
)
pgvectorの例:
-- コサイン距離(1 - コサイン類似度)で近い順に取得
SELECT content, 1 - (embedding <=> query_embedding) AS similarity
FROM documents
ORDER BY embedding <=> query_embedding
LIMIT 5;
pgvectorでは <=> がコサイン距離演算子だ。距離が小さいほど類似度が高い。
実装チェックリスト
- インデックス構築と検索で同じ埋め込みモデル・同じバージョンを使っているか確認する
- ベクトルDBのmetric設定がコサイン類似度(または内積)になっているか確認する
-
top_kを決めたうえで、score_threshold で無関係な結果を除外する設計を検討する - テストクエリ10〜20件で、期待する文書が上位に来るか検索品質を評価する
- データ量が増えたら ANN インデックス(HNSW 等)の構築を検討する
- 検索結果のメタデータ(元ファイル名、ページ番号等)を保存し、出典表示に使えるようにする
失敗パターン
パターン1:metricの不一致
インデックス作成時にユークリッド距離、検索時にコサイン類似度を使うなど、設定が食い違う。
→ 対策:インデックス作成時の metric をドキュメント化し、検索コードと一致させる。
パターン2:閾値なしでtop_kの結果をすべてLLMに渡す
類似度0.3の無関係チャンクまでコンテキストに入り、回答品質が下がる。
→ 対策:コサイン類似度0.7以上(目安)のみ採用し、全件下回る場合は「関連情報なし」と返す。
パターン3:top_kが小さすぎてリランキング前に正解が落ちる
ANNの近似誤差で、本当は関連するチャンクがtop_3に入らない。
→ 対策:まず top_k=20 程度で取得し、クロスエンコーダ等でリランキングして上位5件に絞る。
パターン4:埋め込みモデルとDBの次元数不一致
1536次元のベクトルを768次元用のインデックスに入れようとしてエラーになる。
→ 対策:モデルの dimensions パラメータとDBスキーマをデプロイ前に検証する。
まとめ
- ベクトル検索は、埋め込みベクトル空間でクエリに近い文書を探す方式だ。
- コサイン類似度は「ベクトルの角度」で意味の近さを測る、テキスト検索で最も一般的な指標だ。
- 小規模なら総当たり、大規模ならANN(HNSW等)で高速化する。
- モデルの一致、metricの一致、閾値設計が検索品質の前提になる。
次のステップとして、10件程度の短文をベクトル化し、コサイン類似度のスコアを実際に出力してみると、閾値設計の感覚がつかめる。
参考リンク
- Pinecone — What is Vector Search?
- Weaviate — Vector similarity search
- pgvector — GitHub
- Qdrant — Similarity Metrics
- Faiss — Facebook AI Similarity Search
- OpenAI Embeddings Guide
この記事を書いた人✏️@YushiYamamoto
ITPRODX.com代表 / AIアーキテクト
Next.js / TypeScript / n8nを活用した自律型アーキテクチャ設計を専門としています。
日々の自動化の検証結果や、ビジネス側の視点(ROI等)に関するより深い考察は、以下の公式サイトおよびnoteで発信しています。
