AI

【AI】chromaDBを用いた類似検索

フリーランスエンジニアのまさです。

chromaDBを活用した類似検索を行いたい
ベクトルデータベースを活用した類似検索を行いたい

この記事では、ベクトルデータベースの一種であるchromaDBを活用して、テキストデータの類似検索を実現する方法について詳しく解説します。

具体的な導入手順から実際の検索方法までをステップバイステップで説明し、OpenAIのembeddingモデルを用いることで、より精度の高い検索を可能にする方法も紹介します。

それでは順にみていきましょう。

chromaDBの導入

chromaDBの導入については、以下のサイトを順に実行します。

まずは仮想環境にchromadbをインストールします。

pip install chromadb

Chroma クライアントを作成します。

import chromadb
chroma_client = chromadb.Client()

コレクションを作成します。

collection = chroma_client.create_collection(name="my_collection")

コレクションにテキストドキュメントとIDを追加します。

collection.add(
    documents=[
        "This is a document about pineapple",
        "This is a document about oranges"
    ],
    ids=["id1", "id2"]
)

コレクションに問い合わせを行うクエリを作成します。
問い合わせの表示結果の件数の値をnに記載します。
(2件出力したければn_results=2とします)

results = collection.query(
    query_texts=["This is a query document about hawaii"],
    n_results=2 
)
print(results)

以上のように、公式ドキュメント通り実行すると、resultsの結果が表示されます。
動作確認はできたので、次は、用意したPDFデータの類似検索を実施します。

chromaDBを用いて類似検索実行

検索データの準備

類似検索に使用するデータを用意します。
金融庁が公開している、金融行政方針・金融レポート(PDFファイル)を使用して検証しました。

金融行政方針・金融レポート

今回はPDFファイルを使用しましたが、使用するデータについては、テキストデータであれば、何でもよいです。

テキストデータからpdfの情報を抜き出しリストに格納します。

import os
import fitz  # PyMuPDF

def extract_pdf_info(pdf_folder):
    pdf_info_list = []
    
    # 指定されたフォルダ内の全てのPDFファイルをリストアップ
    pdf_files = [f for f in os.listdir(pdf_folder) if f.endswith('.pdf')]
    
    for pdf_file in pdf_files:
        pdf_path = os.path.join(pdf_folder, pdf_file)
        pdf_document = fitz.open(pdf_path)
        
        # 各ページから情報を抽出
        for page_num in range(pdf_document.page_count):
            page = pdf_document.load_page(page_num)
            text = page.get_text()
            text = text.replace('\n', ' ').replace('\r', '')
            pdf_info = {
                'file_name': pdf_file,
                'page_number': page_num + 1,
                'text_content': text
            }
            pdf_info_list.append(pdf_info)
    
    return pdf_info_list

# PDFファイルが含まれているフォルダを指定
pdf_folder = 'pdf'

# PDF情報を抽出
pdf_info_list = extract_pdf_info(pdf_folder)

PDFファイルの各ページからテキスト情報を抜き出し、リストに格納していきます。

documents = [info['text_content'] for info in pdf_info_list]
metadatas = [{'file_nam}e': info['file_name'], 'page_number': info['page_number']} for info in pdf_info_list]
ids = [str(i) for i in range(1, len(pdf_info_list) + 1)]

chromaDBに格納できるようにデータの整形を実施します。
ドキュメントからのデータ取得については、今回のテーマと異なる内容のため詳細の説明は割愛します。

Open AIのembeddingモデルを使用

次に、chromaDBに格納する埋め込みベクトルを作成するモデルの設定を行います。
今回は、OpenAIのtext-embedding-3-largeモデルを使用します。

OpenAIのtext-embedding-3-largeモデルを使用するためには、OpenAI APIキーの取得が必要になります。

chromaDBでは様々な埋め込みベクトルモデルを使用することができます。
使用できるモデルについては、公式ドキュメントにまとまっているため参考にしてください。
Chroma 公式ドキュメント(Embeddings)

from openai import OpenAI
from tqdm import tqdm

client = OpenAI(
    api_key = 自身のOPENAI_API_KEYを記載する
)
def get_embedding(text):
    response = client.embeddings.create(
        model="text-embedding-3-large",
        input=text
    )
    return response.data[0].embedding

embeddings = []
for doc in tqdm(documents, desc="Getting embeddings"):
    embeddings.append(get_embedding(doc))

上記コードを実行することでdocumentsデータの埋め込みベクトルを作成することができました。

chromaDBの設定

chromaDBの設定を実施します。

import chromadb
import chromadb.utils.embedding_functions as embedding_functions

#chromaDBでembeddingに使用するモデルを指定
openai_ef = embedding_functions.OpenAIEmbeddingFunction(
                api_key=os.getenv("OPENAI_API_KEY"),
                model_name="text-embedding-3-large"
            )

#chromaDBに接続
chroma_client = chromadb.Client()
collection = chroma_client.create_collection(name="my_collection", 
                                             metadata={"hnsw:space": "cosine"}, 
                                             embedding_function=openai_ef)

#chromaDBにデータの追加
collection.add(
    documents=documents,
    embeddings=embeddings,
    metadatas=metadatas,
    ids=ids
)

embedding_functions.OpenAIEmbeddingFunction部分で、chromaDB使用するembeddingモデルを指定しています。

chroma_client.create_collection部分で、類似検索の方法(この例ではコサイン類似度)と使用するembeddingモデルを設定しています。

collection.addで、今回PDFから抽出したデータ(documents,metadatas,ids,embeddings)をそれぞれ格納しています。

類似検索の方法については、コサイン類似度以外にも、内積、L2ノルムも使用できます。デフォルトは、L2ノルムのため、変更したい場合は公式ドキュメントを参考にしてください。
Chroma 公式ドキュメント(Usage Guide)

類似検索の実行

最後に、問い合わせクエリーとそのembeddingデータを用意し、類似検索を実行します。

import time

# 問い合わせクエリーの用意
query_text = "職域における資産形成に関する取組について教えてください"

# 問い合わせクエリーのembedding
query_embedding = get_embedding(query_text)

# 検索実行(上位10件を表示)
start_time = time.time()
results = collection.query(
    query_embeddings=[query_embedding],
    n_results=10
)
end_time = time.time()
execution_time = end_time - start_time

print(f'検索時間:{execution_time}')

query_embeddingでは、documentsデータをembeddingする際に使用した関数get_embeddingを使用してembeddingを実施しています。

resultsの内容を確認することで、検索クエリーと似ている上位10件での文書を確認することができます。

また、検索実行と同時に、検索にかかる時間も図りました。
リレーショナルデータベースに格納して類似検索を行った場合と、ChromaDBに格納して類似検索を実行した結果は以下の通りでした。(データの件数は今回500件程度です)

リレーショナルデータベース検索時間:0.33887457847595215
ChromaDB検索時間:0.005000114440917969

やはり、ベクトル検索において、ベクトルデータベースを用いた方がパフォーマンス向上できることがわかりました。

まとめ

今回は、ベクトルデータベースを用いたベクトル類似検索の検証を行いたく、chromaDBを用いた類似検索を行いました。

ベクトルデータベースについては、Chromaデータベース以外にもAzureAISearch、Pinecone、Milvusなど様々なデータベースが存在しています。
ベクトルデータベースはRAGを用いた社内類似検索を行う際、活用されていくことになると考えるため、引き続き調査をしていきたいと思います。