Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

FAISS 徹底解説:高速ベクトル検索とインデックス設計

FAISS とは何か

FAISS(Facebook AI Similarity Search)は、Meta(旧 Facebook)の AI Research チームによって開発された、高次元ベクトルの高速な類似度検索(Similarity Search)とクラスタリングを行うオープンソースライブラリです。

C++ で実装されており、Python から高速に呼び出すことができます。CPU の SIMD 命令(AVX2 / AVX-512)や GPU(CUDA)による並列演算を極限まで最適化しており、数百万から数億件規模のベクトルであってもミリ秒単位で検索できます。

多くの RAG システムや推薦システム、画像検索エンジンのバックエンドとして、世界中のプロダクション環境で採用されています。

RAG における FAISS の位置づけ

RAG(検索拡張生成)では、ユーザーの質問を埋め込みモデル(Embedding)によって多次元ベクトルへ変換し、外部ドキュメント群の中から意味の近いテキストチャンクを検索(Dense Retrieval)します。

この「大量のベクトルの中から、質問ベクトルと最も似ているものを瞬時に見つけ出す」計算処理を担うコアエンジンが FAISS です。

flowchart TD
    Doc["ドキュメント群"] --> Chunk["チャンク分割"]
    Chunk --> EmbedModel["Embedding モデル (bge-m3 等)"]
    EmbedModel --> Vectors["文書ベクトル (高次元)"]
    Vectors --> FAISS["FAISS インデックス (IVF / HNSW / PQ)"]

    Query["ユーザーの質問"] --> QueryEmbed["質問ベクトル"]
    QueryEmbed --> Search{"FAISS 近似最近傍探索"}
    FAISS --> Search
    Search --> TopK["類似チャンク (Top-k)"]
    TopK --> LLM["LLM プロンプト生成"]

一般的なベクトルデータベース(Chroma、Qdrant、Pinecone など)と FAISS の違いは、設計方針にあります。

  • ベクトルデータベース:

    ベクトルの保存だけでなく、テキスト本文の保持、メタデータによる絞り込み(フィルター)、ネットワーク API、クラスタ分散管理などを包括的に提供します。

  • FAISS:

    余分なオーバーヘッドを削ぎ落とし、「純粋なベクトル類似度計算の超高速化」と「メモリ圧縮」に特化した計算エンジンライブラリです。

近似最近傍探索(ANN)の必要性

ベクトル検索において、最も単純な方法はすべての登録ベクトルと質問ベクトルの距離を計算する「全件総当たり探索(Exact Search / Flat)」です。

しかし、全探索の計算量はベクトル数を $N$、次元数を $d$ とすると $O(N \cdot d)$ に比例します。 例えば、1024 次元のベクトルが 100 万件ある場合、1 回の検索ごとに 10 億回以上の浮動小数点演算が必要となり、レスポンスが極端に遅延します。

そこで実運用では、近似最近傍探索(ANN: Approximate Nearest Neighbor) というアルゴリズム群が用いられます。 ANN は、探索空間をあらかじめグループ化したりグラフ化したりすることで、検索精度(真の最類似ベクトルを見つけ出せる確率)を 95〜99% 程度に保ちつつ、探索範囲を絞り込んで計算速度を数十倍から数百倍に引き上げます。

主要インデックス構造の徹底比較

FAISS には目的・データ規模・ハードウェア環境に応じて数多くの「インデックス(検索手法)」が用意されています。 RAG で特に重要な代表的インデックスは以下の 4 つです。

インデックスアルゴリズム検索精度検索速度メモリ消費学習 (train)推奨データ規模
IndexFlatL2 / IP全件総当たり (Exact)100%低速小 (生ベクトル)不要〜数万件
IndexIVFFlat転置ファイル (クラスタ分割)90〜98%高速小 (生ベクトル)必要5万〜100万件
IndexHNSW階層型スモールワールドグラフ95〜99%超高速大 (グラフ構造)不要1万〜100万件
IndexIVFPQ直交量子化 (ベクトル圧縮)80〜95%超高速極小 (1/10以下)必要100万件以上

1. IndexFlatL2 / IndexFlatIP(厳密解探索)

近似を行わず、メモリ上の生ベクトルすべてに対して L2 距離(ユークリッド距離)または内積(IP: Inner Product)を計算します。

  • 長所: 100% 正確な結果が得られます。インデックス構築前の学習(train)が不要です。
  • 短所: データ量が増えると検索時間が線形に増加します。
  • 用途: 数万件以下の小規模データ、または ANN インデックスの検索精度を評価・比較するための正解基準(ベースライン)として利用されます。

2. IndexIVFFlat(転置インデックス方式)

ベクトル空間を k-means クラスタリングによって複数の領域(Voronoi セル)に分割(インデックス化)します。

[ベクトル空間全体]
┌─────────┬─────────┬─────────┐
│ クラスタ1│ クラスタ2│ クラスタ3│  ← 中心点 (Centroids) を事前に計算 (train)
│    *    │    *    │    *    │
├─────────┼─────────┼─────────┤
│ クラスタ4│[質問★]  │ クラスタ6│  ← 質問★ に近い「クラスタ5」と「クラスタ2」
│    *    │  * (5)  │    *    │     周辺のみを走査 (nprobe = 2)
└─────────┴─────────┴─────────┘

検索時は、質問ベクトルに近い上位数個の中心点(Centroid)を持つクラスタのみを走査します。 全体のごく一部しか計算しないため、劇的な高速化が実現します。

  • nlist: 作成するクラスタの数(例: データ件数が 10 万件なら 1000 程度)。
  • nprobe: 検索時に走査するクラスタ数。値を大きくすると精度が上がり、小さくすると速度が向上します。

3. IndexHNSW(グラフ探索方式)

ベクトル間に近傍グラフ(網の目状のリンク)を多層(階層型)に構築し、高速道路のように大まかに移動してから詳細な近傍へ降りていく探索アルゴリズムです。

  • 長所: 極めて高速で、高い検索精度(98% 以上)を安定して維持できます。事前のクラスタ学習(train)も不要です。
  • 短所: グラフの接続リンクを保持するため、生ベクトルに加えて 1.5〜2 倍の RAM を消費します。
  • 用途: メモリに余裕があり、ミリ秒単位の超低レイテンシが必須のリアルタイム RAG システム。

4. IndexIVFPQ(直交量子化方式)

高次元ベクトル(例: 1024 次元)を複数の部分ベクトル(サブベクトル)に分割し、それぞれのサブベクトルを代表コード(1 バイトなど)に圧縮(量子化)して保存します。

  • 長所: メモリ使用量を 1/10〜1/20 に激減させます。1 億件規模のベクトルでも単一マシンのメモリに格納可能です。
  • 短所: ベクトルが不可逆圧縮されるため、検索精度が若干低下します。
  • 用途: クラウドの RAM コストを抑えたい大規模 RAG、またはエッジ環境での運用。

類似度尺度(距離メトリック)と L2 正規化

FAISS で主に使用されるメトリックは以下の 2 つです。

  • METRIC_L2: ユークリッド距離。値が小さいほど似ていることを示します。
  • METRIC_INNER_PRODUCT: 内積。値が大きいほど似ていることを示します。

テキストの Embedding においては「コサイン類似度(ベクトルの向きの近さ)」が広く用いられます。 FAISS でコサイン類似度を計算する標準的なアプローチは、ベクトルを L2 正規化(長さを 1 に変換)した上で、内積(IndexFlatIP 等)を計算する ことです。

import faiss
import numpy as np

# ベクトルを長さ 1 に正規化(インプレース変換)
faiss.normalize_L2(vectors)

正規化されたベクトル同士の内積は数学的にコサイン類似度と完全に等しくなります。

Python ネイティブ(faiss-cpu)による基本操作

外部フレームワークを使わず、素の FAISS(Python ライブラリ)を用いてインデックスの作成、学習、検索、ファイル永続化を行う最小コードです。

パッケージのインストール:

pip install faiss-cpu numpy

IndexIVFFlat の構築と検索

import faiss
import numpy as np

# 1. 擬似ベクトルデータの生成(128 次元, 10,000 件)
dimension = 128
num_vectors = 10000
np.random.seed(42)
data = np.random.random((num_vectors, dimension)).astype("float32")

# コサイン類似度計算のため L2 正規化
faiss.normalize_L2(data)

# 2. インデックスの定義(IVFFlat + 内積メトリック)
nlist = 100  # クラスタ数
quantizer = faiss.IndexFlatIP(dimension)  # クラスタ中心の探索用
index = faiss.IndexIVFFlat(quantizer, dimension, nlist, faiss.METRIC_INNER_PRODUCT)

# 3. クラスタリングの学習(train)とベクトルの登録
index.train(data)  # クラスタ中心点を計算
index.add(data)  # ベクトルデータを登録
print(f"登録件数: {index.ntotal}")

# 4. 検索の実行
query = np.random.random((1, dimension)).astype("float32")
faiss.normalize_L2(query)

# 走査クラスタ数の調整(チューニング)
index.nprobe = 10

k = 3  # 上位 3 件を取得
scores, indices = index.search(query, k)

print("類似度スコア (コサイン):", scores[0])
print("該当ドキュメントインデックス:", indices[0])

# 5. インデックスのディスク保存と復元
faiss.write_index(index, "my_vector_index.faiss")
loaded_index = faiss.read_index("my_vector_index.faiss")
print(f"復元後件数: {loaded_index.ntotal}")

LangChain による RAG 実装と FAISS 統合

実務の RAG アプリケーション開発では、LangChain の FAISS ベクターストアを使用すると、テキスト分割、埋め込み計算、メタデータ(ソース URL やページ番号)の管理、ディスクへの保存・復元を極めて直感的に扱えます。

必要なパッケージのインストール:

pip install langchain langchain-community langchain-ollama faiss-cpu

ドキュメントの登録・保存・Retriever 検索

from langchain_community.vectorstores import FAISS
from langchain_core.documents import Document
from langchain_ollama import OllamaEmbeddings

# 1. ローカル埋め込みモデル(bge-m3)の準備
embeddings = OllamaEmbeddings(model="bge-m3")

# 2. ドキュメントチャンクの準備(メタデータ付き)
docs = [
    Document(
        page_content="BM25 は単語の出現頻度と逆文書頻度に基づく伝統的な全文検索アルゴリズムである。",
        metadata={"source": "search_theory.md", "id": 1},
    ),
    Document(
        page_content="FAISS は高次元ベクトルの近傍探索を超高速に行うための C++ ライブラリである。",
        metadata={"source": "vector_engine.md", "id": 2},
    ),
    Document(
        page_content="RAG は検索と生成を組み合わせ、最新知識や社内文書に基づく回答を生成する手法である。",
        metadata={"source": "rag_overview.md", "id": 3},
    ),
]

# 3. FAISS ベクターストアの構築(自動的にベクトル化してインデックス作成)
vectorstore = FAISS.from_documents(docs, embeddings)

# 4. ローカルディスクへの永続化(インデックスとドキュメントストアの両方を保存)
vectorstore.save_local("faiss_index_store")

# 5. ディスクからのロード(安全な読み込みオプション)
loaded_vectorstore = FAISS.load_local(
    "faiss_index_store", embeddings, allow_dangerous_deserialization=True
)

# 6. リトリーバー化して検索を実行
retriever = loaded_vectorstore.as_retriever(search_kwargs={"k": 2})
query = "Meta が作った高速なベクトル検索ライブラリについて教えて"
matched_docs = retriever.invoke(query)

for doc in matched_docs:
    print(f"[スコア対象] {doc.page_content} (出典: {doc.metadata['source']})")

LangChain の FAISS ラッパーは、裏側でテキストとメタデータを InMemoryDocstore に保持し、FAISS の整数 ID とドキュメントの実体を自動的にマッピングしています。

インデックスの永続化(保存と復帰)とメモリマップ

FAISS は、構築や学習に多大な計算コストがかかったインデックスをディスクへバイナリ保存し、再起動時に一瞬で復帰させる永続化機能を備えています。

1. FAISS ネイティブでの保存と復帰

FAISS 標準の write_index および read_index 関数を使用します。

import faiss

# インデックスをファイルへバイナリ保存
faiss.write_index(index, "my_index.faiss")

# ファイルからインデックスを復帰
loaded_index = faiss.read_index("my_index.faiss")
print(f"復帰した登録件数: {loaded_index.ntotal}")

2. メモリマップ読み込み(IO_FLAG_MMAP)

数千万件を超える巨大なインデックスファイル(数十 GB)を扱う場合、ファイル全体を物理メモリ(RAM)に一括展開するとメモリ不足(OOM)を起こすリスクがあります。

FAISS には、OS の仮想メモリ機構を活用するメモリマップモード(IO_FLAG_MMAP)が用意されています。

# メモリマップモードで復帰(必要なページのみオンデマンドで参照)
loaded_index = faiss.read_index("large_index.faiss", faiss.IO_FLAG_MMAP)

このオプションを指定すると、インデックスの起動時間がほぼゼロになり、RAM 消費量を大幅に抑えながらクエリを実行できます(IndexIVF や IndexPQ などで特に有効です)。

3. LangChain での保存構造(index.faiss と index.pkl)

FAISS ネイティブのバイナリファイルには「ベクトルデータと整数 ID」しか保持されず、「元のテキスト文章」や「メタデータ(URL、作成日、ページ番号等)」は保存されません。

LangChain の save_local を実行すると、指定したフォルダ内に以下の 2 つのファイルがペアで生成されます。

  • index.faiss: ベクトルデータ本体と探索木・グラフ構造(FAISS ネイティブバイナリ)
  • index.pkl: ドキュメント本文、メタデータ、および ID との対応表(Python pickle オブジェクト)
# フォルダへ保存
vectorstore.save_local("faiss_store")

# フォルダから復帰(安全な自前ファイルであるため allow_dangerous_deserialization を許可)
loaded_vectorstore = FAISS.load_local(
    "faiss_store", embeddings, allow_dangerous_deserialization=True
)

4. GPU インデックス保存時の注意点

GPU 上で構築・稼働しているインデックス(GpuIndex)は、GPU メモリ空間に依存しているため直接ディスクへ保存することができません。 必ず CPU 用のインデックスへ一度変換してから保存 し、復帰後に再度 GPU へ転送します。

# GPU インデックスを CPU インデックスへ変換して保存
cpu_index = faiss.index_gpu_to_cpu(gpu_index)
faiss.write_index(cpu_index, "gpu_trained.faiss")

# 復帰して再度 GPU へロード
loaded_cpu_index = faiss.read_index("gpu_trained.faiss")
res = faiss.StandardGpuResources()
gpu_index = faiss.index_cpu_to_gpu(res, 0, loaded_cpu_index)

BM25 と FAISS のハイブリッド検索統合

第6章で解説した EnsembleRetriever を用いれば、BM25 の正確なキーワード一致と、FAISS による超高速な意味検索を理想的に融合できます。

from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever

# 1. BM25 リトリーバーの作成
bm25_retriever = BM25Retriever.from_documents(docs)
bm25_retriever.k = 2

# 2. FAISS ベクトルリトリーバーの作成
faiss_retriever = loaded_vectorstore.as_retriever(search_kwargs={"k": 2})

# 3. RRF (Reciprocal Rank Fusion) によるハイブリッド統合
ensemble_retriever = EnsembleRetriever(
    retrievers=[bm25_retriever, faiss_retriever], weights=[0.5, 0.5]
)

results = ensemble_retriever.invoke("FAISS の特徴とアルゴリズム")
for r in results:
    print("-", r.page_content)

画像類似度検索への応用(マルチモーダル RAG)

FAISS は「テキスト専用」の検索エンジンではありません。 FAISS の本質は「多次元浮動小数点数ベクトル(float32 の配列)間の距離を極限の速度で計算する数学的エンジン」です。

したがって、ベクトルの中身がテキストであっても、画像であっても、音声であっても、全く区別なく同様に近傍探索を実行できます。

画像検索の仕組み

画像検索を行う際は、ディープラーニングの画像特徴量抽出モデル(Vision Transformer や CNN)を用いて画像をベクトルに変換します。

[商品画像・写真・図面] ──> [画像特徴量抽出モデル (CLIP / DINOv2)] ──> [画像ベクトル (512次元)]
                                                                               │
                                                                               ▼
                                                                     [ FAISS インデックス ]
                                                                               ▲
                                                                               │
[クエリ (画像 または テキスト)] ──> [特徴量モデル] ──> [クエリベクトル] ───────────┘
                                                                               │
                                                                               ▼ (類似画像 Top-k)

代表的な特徴量抽出モデルには以下があります。

  • CLIP / OpenCLIP:

    画像とテキストを同一のベクトル空間に埋め込むマルチモーダルモデル。「自然言語で画像を検索する(Text-to-Image)」および「画像で似た画像を探す(Image-to-Image)」の双方が可能です。

  • DINOv2 (Meta):

    画像の本質的な構造やテクスチャを捉える自己教師あり学習モデル。同一アングルや類似構図・同一オブジェクトの検出に極めて高い精度を発揮します。

主なユースケース

  • 類似画像・図面検索(Image-to-Image):

    EC サイトでの「この服に似たデザインの商品を探す」機能、製造業における類似設計図・過去不具合図面の横断検索、重複画像の自動検知。

  • マルチモーダル RAG(Text-to-Image):

    「サーバラックの配線図」や「エラーランプ点滅時の画面キャプチャ」など、テキスト化しにくいマニュアル内の図表・画像を自然言語プロンプトから高精度に検索して回答生成に活用する。

実践コード例:CLIP と FAISS による画像特徴量検索

Hugging Face の transformers ライブラリ(CLIP モデル)と FAISS を組み合わせた最小実装例です。

pip install transformers pillow torch faiss-cpu
import faiss
import numpy as np
from PIL import Image
import torch
from transformers import CLIPModel, CLIPProcessor

# 1. CLIP モデルとプロセッサの準備
device = "cuda" if torch.cuda.is_available() else "cpu"
model_name = "openai/clip-vit-base-patch32"
model = CLIPModel.from_pretrained(model_name).to(device)
processor = CLIPProcessor.from_pretrained(model_name)

# 2. 画像のベクトル化(特徴量抽出)
image_paths = ["sample1.jpg", "sample2.jpg", "sample3.jpg"]
images = [Image.open(p).convert("RGB") for p in image_paths]

inputs = processor(images=images, return_tensors="pt").to(device)
with torch.no_grad():
    image_features = model.get_image_features(**inputs)

# numpy 変換とコサイン類似度用の L2 正規化
image_vectors = image_features.cpu().numpy().astype("float32")
faiss.normalize_L2(image_vectors)

# 3. FAISS インデックスの構築(内積ベース)
dimension = image_vectors.shape[1]  # CLIP ViT-B/32 では 512 次元
index = faiss.IndexFlatIP(dimension)
index.add(image_vectors)
print(f"登録画像数: {index.ntotal}")

# 4. 「自然言語テキスト」による画像検索(Text-to-Image)
text_query = "草原を走る黒い犬"
text_inputs = processor(text=[text_query], return_tensors="pt").to(device)
with torch.no_grad():
    text_features = model.get_text_features(**text_inputs)

query_vector = text_features.cpu().numpy().astype("float32")
faiss.normalize_L2(query_vector)

# 最も意味が近い画像を上位 2 件検索
scores, indices = index.search(query_vector, k=2)

for rank, idx in enumerate(indices[0]):
    print(f"順位 {rank + 1}: {image_paths[idx]} (類似度スコア: {scores[0][rank]:.4f})")

Meta 社自身も、Instagram や Facebook における数千億枚規模の類似画像検知や著作権保護システムにおいて、画像特徴量モデルと FAISS の組み合わせを基盤インフラとして運用しています。

インデックス選定の指針とチューニングの勘所

RAG システムの規模と要件に応じた最適なインデックスの選定フローは下図の通りです。

flowchart TD
    Start["データ規模・要件の判定"] --> Q1{"データ件数は 5万件以下か?"}
    Q1 -- Yes --> Flat["IndexFlatL2 / IndexFlatIP<br/>(完全探索・学習不要・100%精度)"]
    Q1 -- No --> Q2{"RAM メモリに厳しい制約があるか?"}
    Q2 -- Yes --> PQ["IndexIVFPQ<br/>(ベクトル圧縮・省メモリ最優先)"]
    Q2 -- No --> Q3{"検索速度と精度の両立を最優先するか?"}
    Q3 -- Yes --> HNSW["IndexHNSW<br/>(超高速・学習不要・高精度・メモリ大)"]
    Q3 -- No --> IVF["IndexIVFFlat<br/>(標準的なバランス・低メモリ・要train)"]

実運用のチューニングポイント

  • 学習データ(Train Data)の品質:

    IVF や PQ を使用する場合、index.train() に渡すベクトル群は実際の検索対象データの分布を代表している必要があります。データ数が少なすぎる状態で学習させると、クラスタ境界の精度が著しく低下します。

  • nprobe パラメータの最適化:

    IndexIVFFlat では、nprobe=1 だと高速ですが精度が落ちます。一般的には nlist の 5〜10% 程度の値を設定し、再現率(Recall)とレイテンシのトレードオフを測定して決定します。

  • GPU 版(faiss-gpu)の活用:

    1000 万件以上の巨大インデックスや、大量のバッチクエリを並列処理したい場合は、pip install faiss-gpu を導入し、faiss.index_cpu_to_gpu を用いて GPU メモリへ展開することで、CPU 比で 10 倍以上のスループットが得られます。