メインコンテンツへスキップ
見出し画像

最近話題のVector Searchを実現するFaissって何? #1

    Faissを使ったFAQ検索システムの構築

    Facebookが開発した効率的な近似最近傍検索ライブラリFaissを使用することで、FAQ検索システムを構築することができます。

    まずは、SQLiteデータベースを準備し、FAQの本文とそのIDを保存します。次に、sentence-transformersを使用して各FAQの本文の埋め込みベクトルを計算し、そのベクトルをFaissインデックスに追加します。新しいクエリが入力されたときは、sentence-transformersを使用してクエリの埋め込みベクトルを計算し、Faissインデックスを使用して、クエリの埋め込みベクトルに最も類似したFAQの埋め込みベクトルを検索します。

    検索結果は、FAQのIDのリストとして返され、最後に返されたIDを使用して、SQLiteデータベースから関連するFAQの本文を取得し、検索結果としてユーザーに表示されます。

    Faissの基本とデータベースとの違い

    Facebookが開発したFaiss(Facebook AI Similarity Search)は、大規模なベクトルデータセットから類似性に基づいて最も近いアイテムを検索するのに使用される効率的な近似最近傍検索ライブラリです。

    Faissでは、ベクトル量子化やインデックス圧縮などの技術を使用して、メモリ使用量を削減し、スケーラブルで効率的な検索を可能にします。

    Faissはオープンソースであり、様々なアプリケーションやデータベース管理システムに統合されていますが、Faiss自体はデータベースではありません。Faissはインデックス構造とアルゴリズムを提供するライブラリであり、データの永続化やトランザクション管理、アクセス制御などの従来のデータベース管理システムの機能は提供しません。

    FAQ検索システムの概要とデータストレージ

    FAQ検索システムは、Facebookが開発したFaiss(Facebook AI Similarity Search)というライブラリを使用して、文章の埋め込みを求めるためにsentence-transformersを使用してモデルを作成することで構築できます。

    まずSQLiteデータベースを用意し、FAQの本文とそのIDを保存します。次に、sentence-transformersを使用して、各FAQの本文の埋め込みベクトルを計算します。その後、Faissインデックスを作成し、FAQの埋め込みベクトルを追加します。

    新しいクエリが入力されたときは、sentence-transformersを使用してクエリの埋め込みベクトルを計算し、Faissインデックスを使用して、クエリの埋め込みベクトルに最も類似したFAQの埋め込みベクトルを検索します。

    最後に、検索結果として返されるFAQのIDを使用して、SQLiteデータベースから関連するFAQの本文を取得し、ユーザーに表示されます。

    Faissとsentence-transformersを使ったFAQ検索システムの構築

    このようなシステムを構築するために必要なサンプルコードを紹介します。

    サンプルコードでは、Pythonを使用して、SQLiteデータベースを使ってFAQを記録し、sentence-transformersを使ってFAQの埋め込みベクトルを計算し、それをFaissインデックスに挿入して類似のFAQを検索し、検索結果をSQLiteデータベースから取得し、表示する方法を示しています。

    まず、必要なライブラリをインストールします。

    pip install faiss-cpu
    pip install sentence-transformers

    次に、サンプルコードを実行します。

    import sqlite3
    import numpy as np
    from sentence_transformers import SentenceTransformer
    import faiss
    
    # 1. SQLiteデータベースの準備
    def create_faq_database():
        conn = sqlite3.connect("faq.db")
        cursor = conn.cursor()
        cursor.execute("CREATE TABLE IF NOT EXISTS faq (id INTEGER PRIMARY KEY, question TEXT)")
        cursor.execute("DELETE FROM faq")
    
        # サンプルデータを追加
        sample_faqs = [
            "How do I reset my password?",
            "What payment methods do you accept?",
            "Can I return an item I bought?",
            "How can I track my order?"
        ]
    
        cursor.executemany("INSERT INTO faq (question) VALUES (?)", [(q,) for q in sample_faqs])
        conn.commit()
        return conn
    
    # 2. 文章の埋め込みの作成
    def compute_faq_embeddings(faq_questions, model):
        return np.array(model.encode(faq_questions))
    
    # 3. Faissインデックスの作成
    def create_faiss_index(model, embeddings, doc_ids):
        dimension = model.get_sentence_embedding_dimension()
        index_flat_l2 = faiss.IndexFlatL2(dimension)
        index = faiss.IndexIDMap(index_flat_l2)
        index.add_with_ids(embeddings, doc_ids)
        return index, index_flat_l2
    
    # 4. クエリ処理
    def search_faq(query, model, index, k=3):
        query_embedding = model.encode([query])
        distances, indices = index.search(np.array(query_embedding), k)
        return indices[0]
    
    # 5. 結果の取得
    def get_faq_results(faq_ids, conn):
        cursor = conn.cursor()
        cursor.execute("SELECT * FROM faq WHERE id IN ({})".format(",".join("?" * len(faq_ids))), faq_ids)
        return cursor.fetchall()
    
    # データベース作成
    conn = create_faq_database()
    
    # sentence-transformersモデルを読み込む
    # model = SentenceTransformer("sentence-transformers/paraphrase-xlm-r-multilingual-v1")
    model = SentenceTransformer("sentence-transformers/paraphrase-distilroberta-base-v1")
    
    # FAQデータを読み込み、埋め込みベクトルを計算
    cursor = conn.cursor()
    cursor.execute("SELECT id, question FROM faq")
    faq_data = cursor.fetchall()
    faq_ids, faq_questions = zip(*faq_data)
    faq_embeddings = compute_faq_embeddings(faq_questions, model)
    
    # Faissインデックスを作成
    index, index_flat_l2 = create_faiss_index(model, faq_embeddings, faq_ids)
    
    # クエリを入力して検索
    query = "How can I change my password?"
    faq_indices = search_faq(query, model, index_flat_l2)
    
    # 結果を取得して表示
    results = get_faq_results([faq_ids[i] for i in faq_indices], conn)
    print("Results for query:", query)
    for r in results:
        print(f"ID: {r[0]}, Question: {r[1]}")
    
    # データベース接続を閉じる
    # conn.close()

    サンプルコードは、sentence-transformersを使用してFAQの埋め込みベクトルを計算し、Faissインデックスを作成して類似のFAQを検索する方法を示しています。そして、検索結果をSQLiteデータベースから取得し、表示します。

    このコードは、FAQデータが増えるときにも対応できるように、適切なデータベース管理とインデックス管理を実装する必要があります。また、新しいFAQが追加されたときにインデックスを更新する機能や、検索結果のランキングやフィルタリングを改善するための追加のロジックを実装することもできます。

    このサンプルコードは、基本的なFAQ検索システムを作成するためのスタートポイントとして使用できます。要件に応じて、機能を拡張してください。

    データの追加と削除、インデックスの更新

    Faissを使用してデータベースのデータをインデックス化することで、データの検索が高速化されます。

    Faissを使用してインデックスを作成すると、データの追加や削除を行う際に、インデックスを更新する必要があります。インデックスの更新は、データを追加する場合にはデータを追加し、削除する場合にはインデックスから削除する必要があります。

    def add_faq(question, conn, model, index):
        cursor = conn.cursor()
        cursor.execute("INSERT INTO faq (question) VALUES (?)", (question,))
        faq_id = cursor.lastrowid
        conn.commit()
    
        embedding = model.encode([question])
        index.add_with_ids(np.array(embedding), np.array([faq_id]))
    
        return faq_id
    
    def remove_faq(faq_id, conn, index):
        cursor = conn.cursor()
        cursor.execute("DELETE FROM faq WHERE id=?", (faq_id,))
        conn.commit()
    
        # FaissインデックスからIDを削除
        id_selector = faiss.IDSelectorArray(np.array([faq_id], dtype=np.int64))
        index.remove_ids(id_selector)
    
    def get_answer(query, model, index):
      faq_indices = search_faq(query, model, index)
      results = get_faq_results([faq_ids[i] for i in faq_indices], conn)
      return results
    
    # FAQを追加
    new_question = "What is your refund policy?"
    new_faq_id = add_faq(new_question, conn, model, index)
    print(f"Added new FAQ with ID: {new_faq_id}")
    
    # 確認
    query = "What's the refund polcy?"
    faq_indices = search_faq(query, model, index)
    print(f"{query}: {faq_indices}")
    
    # FAQを削除
    faq_id_to_remove = new_faq_id
    remove_faq(faq_id_to_remove, conn, index)
    print(f"Removed FAQ with ID: {faq_id_to_remove}")
    
    # 確認
    query = "What's the refund polcy?"
    faq_indices = search_faq(query, model, index)
    print(f"{query}: {faq_indices}")
    
    

    サンプルコードを使用すれば、データの追加や削除、およびFaissインデックスの更新を行うことができます。
    add_faq関数では、質問をデータベースに追加し、Faissインデックスにその埋め込みベクトルを追加します。
    remove_faq関数では、指定されたIDの質問をデータベースから削除し、そのIDをFaissインデックスから削除します。

    Faissを利用したデータの追加や削除、およびインデックスの更新は、データベース操作の高速化を図る上で重要な作業です。したがって、適切なインデックスタイプを選択し、データベース操作が頻繁に行われる場合には、インデックスの更新を行うことをおすすめします。


    これはChatGPTと会話して出てきたことを、Davinci-003でまとめて記事にしています。コードにミスがあったので修正して実行を確認しています。

    Google Colabなどで実行可能なコードはこちら


    あなたへのおすすめ