テーマを切り替える

Ollama Embedding 実践:ローカル ベクトル検索と RAG 構築

パソコンの中の 200 以上の PDF を片っ端から探し、半年前に読んだ技術案の細部を見つけたい。キーワード検索?無理でした——覚えているのは内容の意味であって、元の言い回しではないからです。1 時間近くかかってようやく見つけ、そのとき思いました。「意味を理解してくれる」検索ツールがあればいいのに、と。

さらに厄介なのは、これらのドキュメントが社内アーキテクチャに関わる点です。クラウドにアップしてベクトル検索?とんでもない。プライバシーの一線はきっちり引かれています。

その後、Ollama の Embedding 機能がちょうどこの問題を解決できると分かりました。ローカルで動かし、データは外に出さず、しかもセマンティック検索ができます。数日かけて mxbai、nomic、Qwen3 の三つのモデルをひと通り試し、ついでにベクトルデータベースの選定も徹底的に調べました。落とし穴は意外と多くて——モデルを選び間違えると検索精度が落ち、データベースを小さく選ぶと後の拡張が面倒になります。

この記事では、踏んだ落とし穴の経験を整理しました。読み終えれば自分でローカル RAG システムを構築でき、ドキュメント処理からセマンティック検索まで、全工程のコードがそろっています。

Ollama Embedding モデル一式

Ollama は今では複数の Embedding モデルに対応しており、最初は私もどれを選べばいいか分かりませんでした。公式ドキュメントは mxbai が OpenAI の text-embedding-3-large を上回ると言っています——なるほど、すごそうですが、実際に使うとどうなのか?ひと通り試したので、ずばり答えをお伝えします。

まずこの比較表を見て、おおまかに把握してください:

モデルベクトル次元コンテキスト長モデルサイズ特徴
mxbai-embed-large1024512 トークン670M汎用の第一候補、MTEB ランキング上位
nomic-embed-text7688192 トークン274M長文対応、コンテキスト拡張に対応
Qwen3 Embedding10248192 トークン約 600M2026 年新登場、中国語に強い

mxbai-embed-large は私が一番よく使うモデルです。なぜか?シンプルで使いやすく、効果が安定しているからです。1024 次元のベクトルはほとんどのシーンで十分で、MTEB(Massive Text Embedding Benchmark)ランキングで上位に入り、確かに OpenAI の text-embedding-3-large よりスコアがわずかに高いです。日常のドキュメント検索やコード検索といったタスクなら、これを選んでまず間違いありません。

nomic-embed-text の魅力は 8192 トークンというコンテキスト長です。記事全体や長い会話ログを扱うなら、これが役立ちます。サイズも小さく、274M パラメータで、mxbai より少し速く動きます。ただしベクトル次元は 768 に下がり、意味表現の能力は理論上やや弱くなります——実際にテストすると、短文の検索では差は小さく、長文シーンでは nomic の方が適しています。

Qwen3 Embedding はアリババが 2026 年 4 月に出したばかりです。中国語の効果は確かに良く、技術記事をいくつかテストしたところ、「分散システムのフォールトトレランス機構」と「フォールトトレランス設計」がマッチでき、mxbai は少し物足りない結果でした。主に中国語コンテンツを扱うなら、試す価値があります。

選定のアドバイスは?初心者はそのまま mxbai でよく、悩む必要はありません。長文シーンは nomic、中国語コンテンツは Qwen3 を優先します。要するに、三つとも試しても 30 分ほどで済むので、結局は効果がすべてです。

ベクトルデータベース選定ガイド

モデルを選んだら、データはどこに保存するか?ベクトルデータベースの選択はモデル以上に落とし穴があります。小さく選ぶと後の拡張がつらく、大きく選ぶとリソースの無駄になります。私は主流の三つの方式を比較しました:

データベース適用シーンデータ規模特徴
ChromaDB入門開発、個人プロジェクト< 10 万件すぐ使える、設定不要
FAISS単一マシン高性能、研究実験10〜100 万件Meta のオープンソース、超高速
Milvus本番デプロイ、エンタープライズ百万件以上分散、拡張可能、機能充実

ChromaDB は私が最もおすすめする入門の選択肢です。インストールはコマンド一つで完了:pip install chromadb。API 設計がとても親切で、データの保存も取得も数行のコードで済みます。HNSW(Hierarchical Navigable Small World)インデックスを使い、検索速度は小規模データには十分です。欠点は単一マシンのデプロイで、データ量が 10 万件を超えると性能が落ち始めることです。

FAISS は Meta がオープンソース化した老舗ツールです。純粋な C++ 実装で、速度は本当に速いです。50 万件のデータをテストしたところ、検索遅延はミリ秒単位で安定していました。ただし完全なデータベースというよりベクトル検索ライブラリに近く——ストレージやインデックスファイルは自分で管理する必要があります。いじるのが好きな人や、性能要求が特に高いシーンに向きます。

Milvus は違います。本当に本番環境向けです。分散デプロイ、永続化ストレージ、複数のインデックスタイプに対応し、クラウドサービス版(Zilliz Cloud)もあります。ただし設定が複雑で、デプロイコストが高いです。百万件規模のデータ、高可用性が必要、チーム協働——こうしたシーンでこそ投資する価値があります。

私の選択戦略はこうです。個人でいじるなら ChromaDB ですぐ動かし、研究プロジェクトや性能重視なら FAISS、本当に本番に乗せるなら Milvus かクラウドサービスを直接使います。ChromaDB で始めて後から Milvus に移行しようとは考えない方がいいです——データ形式も API も違い、移行コストは小さくありません。

完全な RAG フロー実践

理論をいくら語るより、コードを見た方が早いです。以下は完全なローカル RAG の実装で、PDF ドキュメントからセマンティック検索まで、Ollama + ChromaDB で構築します。

環境準備

まず依存関係をインストールします:

pip install ollama chromadb langchain langchain-community pypdf

Ollama がすでに動いていること、モデルも取得済みであることを確認します:

ollama pull mxbai-embed-large
ollama pull qwen2.5:7b  # 回答生成用

コード実装

import ollama
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
import chromadb

# 1. PDF ドキュメントを読み込む
loader = PyPDFLoader("./your_document.pdf")
docs = loader.load()

# 2. ドキュメント分割——ここは手を抜けない。chunk が大きすぎると検索が不正確、小さすぎると情報が欠落
splitter = RecursiveCharacterTextSplitter(
    chunk_size=800,      # 1 ブロック 800 文字
    chunk_overlap=100,   # 100 文字を重複させ、境界の情報欠落を防ぐ
)
chunks = splitter.split_documents(docs)

# 3. Embedding を生成して ChromaDB に保存
client = chromadb.Client()
collection = client.create_collection("my_docs")

for i, chunk in enumerate(chunks):
    # Ollama API を呼び出してベクトルを生成
    response = ollama.embed(
        model="mxbai-embed-large",
        input=chunk.page_content,
    )
    embedding = response["embeddings"][0]

    # ベクトルデータベースに保存
    collection.add(
        ids=[str(i)],
        embeddings=[embedding],
        documents=[chunk.page_content],
        metadatas=[&#123;"source": chunk.metadata.get("source", "unknown")&#125;],
    )

print(f"&#123;len(chunks)&#125; 個のドキュメント断片を保存しました")

# 4. セマンティック検索
query = "分散システムのフォールトトレランス機構とは?"
query_embedding = ollama.embed(
    model="mxbai-embed-large",
    input=query,
)["embeddings"][0]

results = collection.query(
    query_embeddings=[query_embedding],
    n_results=3,  # 最も関連する 3 つの断片を返す
)

# 5. 検索結果を使って回答を生成
context = "\n\n".join(results["documents"][0])
response = ollama.chat(
    model="qwen2.5:7b",
    messages=[
        &#123;
            "role": "system",
            "content": "以下のドキュメント内容に基づいて質問に答え、関連情報がなければ正直にそう述べる。",
        &#125;,
        &#123;"role": "user", "content": f"ドキュメント内容:&#123;context&#125;\n\n質問:&#123;query&#125;"&#125;,
    ],
)

print(f"回答:&#123;response['message']['content']&#125;")

実際に動かしてみてください。全体のフローはそれほど複雑ではありません:ドキュメント分割 → ベクトル生成 → 保存 → クエリ → 回答の組み立て。

いくつかの落とし穴の注意点

一つ目は chunk_size を適当に設定しないこと。私は 200 文字で 1 ブロックを試しましたが、検索結果は断片的な情報ばかりで、完全な答えに組み立てられませんでした。500〜1000 の範囲が比較的安定します。

二つ目は batch 処理です。ドキュメント量が多いとき、1 件ずつ Ollama API を呼び出すと非常に遅くなります。まとめて処理できます:

# まとめて Embedding を生成、効果は明確
batch_texts = [chunk.page_content for chunk in chunks[:50]]
batch_embeddings = ollama.embed(
    model="mxbai-embed-large",
    input=batch_texts,
)["embeddings"]

三つ目は類似度しきい値です。ChromaDB はデフォルトで関連の有無にかかわらず n_results 個の結果を返します。シーンによっては関連しないものを除外する必要があります:

# カスタムの距離しきい値でフィルタリング
results = collection.query(
    query_embeddings=[query_embedding],
    n_results=10,
)
# 距離が 0.3 未満の結果のみ残す(距離が小さいほど類似)
filtered = [
    doc for doc, dist in zip(results["documents"][0], results["distances"][0])
    if dist < 0.3
]

このコードを動かし終えれば、自分専用のローカル RAG が手に入ります。ドキュメントを自分の PDF に差し替え、query を聞きたい質問に変えるだけで、他はいじる必要ありません。

性能チューニングと実践のアドバイス

システムが動き出したら、次はチューニングです。いくつかのパラメータが効果に直結するので、私が踏んだ落とし穴をすべてお伝えします。

chunk_size はいくつにするか?

500〜1000 文字が私の経験値です。小さすぎる落とし穴は意味が不完全になること——1 文が二つに切られ、検索しても質問にマッチしません。大きすぎる落とし穴は検索ノイズ——1 つの chunk に複数の話題が含まれ、境界が曖昧になります。

ドキュメントの種類によっても差があります。技術ドキュメントは構造が明確なので段落単位で切れますし、会話ログのような断片的なものは 500 文字単位がより適切です。実際のシーンで色々試すしかなく、万能の答えはありません。

batch によるバッチ処理で高速化

1 件ずつ Ollama API を呼び出すと、毎回ネットワークの往復を待つことになります。50〜100 件をまとめて送れば、速度は数倍になります。大きくしすぎないよう注意——embedding モデルには入力長の制限があり、超えるとエラーになります。

類似度しきい値の決め方

0.7〜0.85 の範囲が目安で、正確性への要求次第です。しきい値が高い(たとえば 0.85)と、非常に関連する結果だけ残り、再現率は低いが正確です。しきい値が低い(0.7)と、再現率は高いがノイズが入ることがあります。ドキュメント庫が綺麗で質問が明確なら高めに、質問が曖昧で情報が多く必要なら低めにします。

ちょっとしたアドバイス:まず 50〜100 件のデータでひと通り動かし、検索効果を見てからパラメータを決めましょう。全量データを入れてから調整すると、時間コストが大きすぎます。反復的にチューニングし、動かしながら直していきます。

まとめ

長々と述べましたが、核心はいくつかのことだけです:

モデルの選択はシーン次第——汎用なら mxbai、長文なら nomic、中国語優先なら Qwen3。データベースは入門なら ChromaDB、性能重視なら FAISS、本番環境なら Milvus。全工程のコードはそろっているので、少し直せば動きます。

この方式のメリットは明確です:ローカルデプロイでデータのプライバシーを管理でき、Ollama モデルは無料でコストゼロ、ChromaDB はシンプルで導入のハードルが低い。デメリットもあります——単一マシンの性能上限に限界があり、データ量が百万件を超えると方式の強化を検討する必要があります。

まずは記事のコードでひと通り動かし、50 個のドキュメントを入れて効果を試してみることをおすすめします。検索品質が良いか、回答が正確か、実測してこそ分かります。パラメータを調整してから全量データに拡張しましょう。

LangChain を Ollama と組み合わせてより複雑なアプリを作る方法をさらに知りたい方は、以前書いた『LangChain + Ollama 統合実践』を見てみてください——あちらは会話チェーンとツール呼び出しを扱い、この記事はベクトル検索に焦点を当てています。二つをつなげれば、完全なローカル LLM アプリ開発のルートになります。

ローカル RAG システムを構築する

Ollama + ChromaDB を使ってローカルのベクトル検索システムを構築する

⏱️ 目安時間: 30 分

  1. 1

    ステップ 1: 依存関係のインストールとモデルの準備

    以下のコマンドを実行して依存関係をインストールします:

    ```bash
    pip install ollama chromadb langchain langchain-community pypdf
    ollama pull mxbai-embed-large
    ollama pull qwen2.5:7b
    ```

    Ollama サービスが起動していることを確認してください。
  2. 2

    ステップ 2: ドキュメントの読み込みと分割

    PyPDFLoader で PDF を読み込み、RecursiveCharacterTextSplitter で分割します:

    ```python
    loader = PyPDFLoader("./your_document.pdf")
    docs = loader.load()
    splitter = RecursiveCharacterTextSplitter(
    chunk_size=800,
    chunk_overlap=100,
    )
    chunks = splitter.split_documents(docs)
    ```

    chunk_size は 500〜1000 文字を推奨します。
  3. 3

    ステップ 3: ベクトルを生成してデータベースに保存

    Ollama API を呼び出して Embedding を生成し、ChromaDB に保存します:

    ```python
    client = chromadb.Client()
    collection = client.create_collection("my_docs")

    for i, chunk in enumerate(chunks):
    response = ollama.embed(
    model="mxbai-embed-large",
    input=chunk.page_content,
    )
    embedding = response["embeddings"][0]
    collection.add(
    ids=[str(i)],
    embeddings=[embedding],
    documents=[chunk.page_content],
    )
    ```

    ドキュメント量が多い場合はバッチ処理を使います。
  4. 4

    ステップ 4: セマンティック検索と回答生成

    クエリをベクトルに変換し、関連ドキュメントを検索してから LLM で回答を生成します:

    ```python
    query_embedding = ollama.embed(
    model="mxbai-embed-large",
    input=query,
    )["embeddings"][0]

    results = collection.query(
    query_embeddings=[query_embedding],
    n_results=3,
    )

    context = "\n\n".join(results["documents"][0])
    response = ollama.chat(
    model="qwen2.5:7b",
    messages=[
    {"role": "system", "content": "ドキュメントの内容に基づいて質問に答える"},
    {"role": "user", "content": f"ドキュメント:{context}\n質問:{query}"},
    ],
    )
    ```

    必要に応じて類似度しきい値を調整して結果を絞り込みます。

FAQ

Ollama の Embedding モデルはどれが一番使いやすい?
絶対的なベストはなく、シーンに応じて選びます:

• mxbai-embed-large:汎用の第一候補。効果が安定し、ほとんどのシーンに向く
• nomic-embed-text:長文シーン向け、8192 トークン対応
• Qwen3 Embedding:中国語に強く、2026 年新登場

まずは三つとも試して、実測の結果で判断するのがおすすめです。
ChromaDB、FAISS、Milvus はどれを選べばいい?
データ規模と利用シーンに応じて選びます:

• ChromaDB:入門の第一候補、設定不要、10 万件以内に向く
• FAISS:性能優先、単一マシンで百万件規模、ストレージは自分で管理
• Milvus:本番環境、分散デプロイ、百万件以上のデータ

個人プロジェクトは ChromaDB、本番環境は Milvus を使います。
chunk_size はどのくらいに設定すべき?
500〜1000 文字を推奨します。小さすぎると意味が不完全になり、大きすぎると検索ノイズが増えます。技術ドキュメントは段落単位で、会話ログは 500 文字単位で区切ります。実際のシーンで多めにテストして調整してください。
Embedding の生成速度を上げるには?
バッチ処理を使い、50〜100 件まとめて Ollama API を呼び出すと、1 件ずつ呼び出すより数倍速くなります。バッチサイズを制御し、モデルの入力長制限を超えないよう注意してください。
類似度しきい値はどのくらいに設定すべき?
0.7〜0.85 の範囲で調整します。高いしきい値(0.85)は正確ですが再現率が低く、低いしきい値(0.7)は再現率が高いがノイズが入ることがあります。ドキュメント庫が綺麗で質問が明確なら高めに、質問が曖昧なら低めに設定します。
ローカル RAG の主なメリットとデメリットは?
メリット:データのプライバシーを管理でき、モデルは無料でコストゼロ、デプロイが簡単で導入が早い。

デメリット:単一マシンの性能に限界があり、百万件以上のデータでは方式の強化が必要で、サービスの保守を自分で行う必要がある。

6分で読めます · 公開日: 2026年4月8日 · 更新日: 2026年7月9日

関連記事

コメント

GitHubアカウントでログインしてコメントできます

Easton BlogEaston Blog