0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Chromaを使った埋め込み検索+ドキュメント分割のベストプラクティス

Posted at

LLMアプリや検索システムにおいて「ベクトル検索」を導入したい方向けに、ChromaDB(Chroma)を用いた実装方法と、検索精度に直結する「ドキュメント分割(Chunking)」および「埋め込み(Embedding)」のベストプラクティスを整理しました。


📌 TL;DR

  • Chromaは軽量・ローカル完結型のベクトルDB
  • 検索精度は「チャンク設計」と「埋め込みモデルの選定」が鍵
  • 文単位でトークン数を管理しつつチャンク分割するのが基本
  • 埋め込みは「意味空間へのマッピング」であり、類似検索の核となる

🧠 ChromaDBとは?

Pythonから簡単に扱えるローカル完結型のベクトルデータベースです。主にテキストをベクトル化(Embedding)し、クエリとの類似度に基づいて検索する用途に使われます。

主な特徴

  • DuckDB + Parquet ベースで、PostgreSQLなど外部DB不要
  • 永続化可能でありながら、ファイルベースで軽量
  • OpenAI APIやLangChainと親和性が高い

🔧 Chromaの導入と基本的な使い方

インストール

pip install chromadb sentence-transformers

初期化

import chromadb
from chromadb.config import Settings

client = chromadb.Client(Settings(
    chroma_db_impl="duckdb+parquet",
    persist_directory="./chroma_store"
))
collection = client.get_or_create_collection(name="manuals")

ドキュメント+Embeddingの追加

from sentence_transformers import SentenceTransformer

model = SentenceTransformer("all-MiniLM-L6-v2")

documents = [
    "社員は毎朝9時までに出社してください。",
    "有給休暇は年間20日間取得可能です。",
    "業務報告書は毎週金曜日に提出すること。",
]

embeddings = model.encode(documents).tolist()

collection.add(
    documents=documents,
    embeddings=embeddings,
    ids=[f"id_{i}" for i in range(len(documents))]
)

類似検索(クエリ → Embedding → 検索)

query = "いつ業務報告書を提出すべき?"
query_embedding = model.encode([query]).tolist()[0]

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

for doc in results['documents'][0]:
    print("🔍 関連ドキュメント:", doc)

🧩 ドキュメントのチャンク分割 ベストプラクティス

ベクトル検索の精度を最も左右するのが、テキストのチャンク(分割)方法です。

✅ チャンク設計のポイント

項目 推奨内容
チャンクサイズ 256〜512トークン程度
オーバーラップ 10〜50トークン(文脈保持に有効)
分割単位 センテンス単位または段落単位
日本語対応 句点(。)、改行単位が有効
トークン制御方法 tiktokennltk等で実現可能

日本語向け チャンク処理例(簡易版)

import re

def chunk_text(text, max_tokens=300, overlap=30):
    sentences = re.split("(?<=[。!?])", text)
    chunks = []
    current_chunk = ""
    current_len = 0

    for sentence in sentences:
        sentence_len = len(sentence)

        if current_len + sentence_len > max_tokens:
            chunks.append(current_chunk.strip())
            current_chunk = current_chunk[-overlap:] + sentence
            current_len = len(current_chunk)
        else:
            current_chunk += sentence
            current_len += sentence_len

    if current_chunk:
        chunks.append(current_chunk.strip())

    return chunks

🎯 Embeddingとは?

テキストを「意味空間上のベクトル(数値の配列)」に変換するプロセスです。

例えば:

  • 「猫はかわいい動物です」
  • 「犬もまた人懐っこい動物です」

この2文は意味的に類似しており、Embedding空間上では近い距離に配置されます。


🔍 代表的な埋め込みモデル

モデル名 特徴 多言語対応 トークン上限
all-MiniLM-L6-v2 軽量・高速・中精度 約512
multilingual-MiniLM 多言語対応(日本語含む) 約512
text-embedding-3-small OpenAI製、高精度 8192
jina-embeddings-v2-ja 日本語特化、RAGに最適化 約512

📐 類似度の計算(Cosine Similarity)

from sklearn.metrics.pairwise import cosine_similarity

# 2つのEmbeddingベクトルの類似度を計算
similarity = cosine_similarity([embedding1], [embedding2])

🧩 Embedding × Chunking × Retrieval(RAG構成)

典型的なRAG構成では、次のようなフローになります:

  1. ドキュメントをチャンク分割(トークン数を意識)
  2. 各チャンクをEmbeddingしてChromaに格納
  3. ユーザークエリもEmbeddingして検索
  4. 上位チャンクをLLMに渡して回答を生成

この精度は、チャンク設計 × 埋め込みモデル精度の掛け合わせで決まります。


🧪 応用・実戦投入パターン

用途 内容
社内PDF資料の検索 PDF → テキスト抽出 → Chunk → Embedding → 検索
顧客対応履歴のFAQ化 チャットログから有用な応答を抽出し、ナレッジ化
コードスニペット検索 関数やコメントを文脈ごとにEmbeddingして構造化

✅ まとめ

  • Chromaはベクトル検索の学習・試作に最適な選択肢
  • チャンク処理は、トークン数と意味の滑らかさのトレードオフ設計が重要
  • Embeddingモデルは用途と対象言語で慎重に選定すべき
  • 本番運用では、FastAPIやLangChainとの統合も視野に

📎 参考リンク


👋 最後に

この構成はPoC(概念実証)だけでなく、本番運用にも十分適用可能です。
今後、LangChainとの統合、FastAPIによるAPI化、OpenAI APIとの連携例なども別途ご紹介予定です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?