たねやつの木

Photographs, Keyboards and Programming

EmbeddingGemmaでRAG構築! (第1回) ~FAISSによるベクトルデータベース入門~

こんにちは、たねやつです。

ローカルで動くLLMは非常に強力ですが、学習データに含まれない情報や、最新の情報については答えることができず、もっともらしい嘘(ハルシネーション)をついてしまうことがあります。

この課題を解決する技術として注目されているのが、RAG (Retrieval-Augmented Generation) です。これは、LLMが回答を生成する(Generation)前に、関連する情報を外部の知識源から検索(Retrieval)し、その内容を参考にして回答を生成する仕組みです。

この連載では、3回にわたってRAGによるQ&Aシステムをローカル環境に構築する方法を解説します。第1回となる今回は、RAGの心臓部である「知識源(ベクトルデータベース)」の構築に挑戦します。

この記事でできること

  • RAG(Retrieval-Augmented Generation)の基本的な概念が理解できます。
  • 手持ちのテキスト文書を、AIが理解できる「ベクトル」に変換する方法を学べます。
  • 高性能な埋め込みモデルEmbeddingGemmaと、高速なベクトル検索ライブラリFAISSを使って、ベクトルデータベースを構築・保存できるようになります。

準備するもの

1. Pythonライブラリ

以下のライブラリをインストールします。

# sentence-transformers: EmbeddingGemmaを簡単に使うため
# faiss-cpu: ベクトルデータベースを構築・検索するため (GPU版はfaiss-gpu)
# numpy: FAISSでベクトルを扱うため
pip install -U sentence-transformers faiss-cpu numpy

2. 知識源となるテキストファイル

今回は、AIに関する架空の解説テキストを3つ用意しました。knowledgeというフォルダを作成し、その中に以下の3つのファイルを保存してください。

knowledge/01_llm.txt

大規模言語モデル(LLM)は、膨大なテキストデータを用いてトレーニングされた人工知能モデルです。人間のように自然な文章を生成したり、要約したり、質問に答えたりする能力を持っています。代表的なモデルに、OpenAIのGPTシリーズや、GoogleのGeminiなどがあります。

knowledge/02_rag.txt

RAG(Retrieval-Augmented Generation)は、LLMの回答精度を向上させるための技術です。ユーザーからの質問に対し、まず関連する情報をデータベースから検索(Retrieval)し、その検索結果を基にLLMが回答を生成(Generation)します。これにより、LLMが知らない情報についても、正確な回答を生成できるようになります。

knowledge/03_embedding.txt

埋め込み(Embedding)とは、単語や文章を「ベクトル」と呼ばれる数値の配列に変換する技術です。このベクトルは、単語や文章の意味的な近さを表現しており、意味が近いほどベクトル空間上での距離が近くなります。セマンティック検索やRAGの文書検索など、様々な応用で利用されています。

ステップ1: テキストの読み込みと分割

まず、用意したテキストファイルをプログラムに読み込みます。今回は簡単のため、1ファイル全体を1つの文書(ドキュメント)として扱います。

import os
import numpy as np
from sentence_transformers import SentenceTransformer
import faiss

# 知識源となるテキストファイルが格納されているディレクトリ
KNOWLEDGE_DIR = "knowledge"

# ドキュメントを格納するリスト
documents = []

# ディレクトリ内の全ファイルをループ
for filename in os.listdir(KNOWLEDGE_DIR):
    if filename.endswith(".txt"):
        filepath = os.path.join(KNOWLEDGE_DIR, filename)
        with open(filepath, "r", encoding="utf-8") as f:
            documents.append(f.read())

print(f"{len(documents)}個のドキュメントを読み込みました。")
# 出力例: 3個のドキュメントを読み込みました。

ステップ2: EmbeddingGemmaによるベクトル化

次に、読み込んだ各ドキュメントをEmbeddingGemmaを使ってベクトルに変換します。sentence-transformersを使えば、数行のコードで簡単に実現できます。

# 埋め込みモデルのロード
model = SentenceTransformer("google/embeddinggemma-300m")

# ドキュメントをベクトル化
print("ドキュメントをベクトル化しています...")
embeddings = model.encode(documents)

# ベクトルの形状を確認
print(f"ベクトル化完了。Embeddings shape: {embeddings.shape}")
# 出力例: ベクトル化完了。Embeddings shape: (3, 768)
# (3つのドキュメントが、それぞれ768次元のベクトルに変換されたことを示す)

ステップ3: FAISSによるベクトルデータベースの構築と保存

最後に、ベクトル化したデータをFAISSに登録し、インデックスを作成します。FAISSは、大量のベクトルデータから高速に類似ベクトルを検索するためのライブラリです。

# FAISSインデックスの構築
# ベクトルの次元数を取得
d_model = embeddings.shape[1]

# IndexFlatL2: L2距離(ユークリッド距離)で類似度を計算する最も基本的なインデックス
index = faiss.IndexFlatL2(d_model)

# ベクトルをインデックスに追加
index.add(embeddings.astype('float32')) # FAISSはfloat32を要求

print(f"FAISSインデックスに{index.ntotal}個のベクトルを追加しました。")

# インデックスの保存
INDEX_FILE = "faiss_index.bin"
faiss.write_index(index, INDEX_FILE)

print(f"インデックスを'{INDEX_FILE}'に保存しました。")

これで、faiss_index.binというファイルが作成されていれば成功です。このファイルに、3つのドキュメントがベクトル化されて格納されています。

全体のコード

ここまでのステップをまとめた、ベクトルデータベースを構築・保存するための全体のコードは以下のようになります。

import os
import numpy as np
from sentence_transformers import SentenceTransformer
import faiss

# --- 1. テキストの読み込みと分割 ---
KNOWLEDGE_DIR = "knowledge"
documents = []
print(f"'{KNOWLEDGE_DIR}'からドキュメントを読み込んでいます...")
for filename in os.listdir(KNOWLEDGE_DIR):
    if filename.endswith(".txt"):
        filepath = os.path.join(KNOWLEDGE_DIR, filename)
        with open(filepath, "r", encoding="utf-8") as f:
            documents.append(f.read())
print(f"{len(documents)}個のドキュメントを読み込みました。")

# --- 2. EmbeddingGemmaによるベクトル化 ---
model = SentenceTransformer("google/embeddinggemma-300m")
print("ドキュメントをベクトル化しています...")
embeddings = model.encode(documents)
print(f"ベクトル化完了。Embeddings shape: {embeddings.shape}")

# --- 3. FAISSによるベクトルデータベースの構築と保存 ---
d_model = embeddings.shape[1]
index = faiss.IndexFlatL2(d_model)
index.add(embeddings.astype('float32'))
print(f"FAISSインデックスに{index.ntotal}個のベクトルを追加しました。")

INDEX_FILE = "faiss_index.bin"
faiss.write_index(index, INDEX_FILE)
print(f"インデックスを'{INDEX_FILE}'に保存しました。")

まとめ

今回は、RAGシステムの基礎となるベクトルデータベースを構築しました。手持ちのテキストをEmbeddingGemmaでベクトル化し、FAISSに登録・保存するまでの一連の流れを解説しました。

次回の【実践編】では、今回作成したデータベースを使い、ユーザーからの質問に最も関連性の高い文書を検索する「Retriever」を実装します。