🗂

似た文書をベクトル検索で探し出したい ~SentenceTransformersとFaissで効率的にベクトル検索~

2024/03/17に公開

はじめに

この記事では、ベクトル検索で似た文書を検索するコードを解説します。具体的には、Sentence Transformersライブラリを用いてベクトル化、Faissという近似最近傍探索ライブラリを用いて高速な検索を行います。

用語説明

ベクトル検索

... 文書の検索にベクトルを使用する方法。例えば、文書に映画に関する内容が 10 %、音楽が 2 %、俳優が30%含まれていた時、シンプルにそれを表すと [0.1, 0.02, 0.3]というベクトルを作ることができる。Googleの説明が詳しい

Sentence Transformers

... ベクトル検索に必要なベクトル化を行うためのライブラリ

https://www.ogis-ri.co.jp/otc/hiroba/technical/similar-document-search/part18.html

Faiss

... ベクトル同士の類似度を高速に検索してくれるライブラリ。以下のHakkyさんのページが詳しい。

https://book.st-hakky.com/data-science/faiss-overview/

コード

入力:検索したい文字のリスト、検索される文字のリスト

出力:どの文字が最も類似しているかとそのスコア

という関数を作成してみました。

Github, Google Colab から実行可能です。

# !pip install -Uqq sentence-transformers faiss-gpu

import faiss
import numpy as np
import pandas as pd
from sentence_transformers import SentenceTransformer
     

def search_nearest_sentence(sentence_refs, sentence_queries, model_name="intfloat/multilingual-e5-base", path_out_faiss_full_index_path=None):
    # モデルの選択と読み込み
    model = SentenceTransformer(model_name)

    # 英語ならば他にもよいモデルがある
    # https://huggingface.co/spaces/mteb/leaderboard など参照
    # model_name = "thenlper/gte-base"
    # model = SentenceTransformer(model_name)

    # データのエンコード
    embeddings_refrence = model.encode(
        sentence_refs,
        normalize_embeddings=True,
        show_progress_bar=True,
        convert_to_tensor=True
    )

    embeddings_query = model.encode(
        sentence_queries,
        normalize_embeddings=True,
        show_progress_bar=True,
        convert_to_tensor=True
    )

    # Faiss インデックスの構築と保存
    # faiss.IndexFlatL2の初期設定として、次元数を設定
    faiss_index = faiss.IndexFlatL2(len(embeddings_refrence[0]))
    faiss_index.add(embeddings_refrence.detach().cpu().numpy())

    if path_out_faiss_full_index_path is not None:
        faiss.write_index(faiss_index, path_out_faiss_full_index_path)

    # 類似度検索と結果の保存
    # 今回は最も類似しているものを返すため1
    search_score, idx_list = faiss_index.search(embeddings_query.detach().cpu().numpy().astype(np.float32), 1)

    df_out = pd.DataFrame([search_score.flatten(), idx_list.flatten()]).T
    df_out.columns = ["Score", "ID"]
    df_out["ID"] = df_out["ID"].astype(int)
    df_out["Sentence"] = [sentence_refs[idx] for idx in df_out["ID"]]

    return df_out

# 実行例
# 1つ目を正例として想定して入れた
article_titles = [
    "Vector Searchによる検索",
    "Firebase Authのリダイレクトログインを使っている人は今年の6月までに対応しないと大変ですよという注意喚起",
    "データ分析基盤まとめ(随時更新)",
]

search_nearest_sentence(article_titles, ["似た文書をベクトル検索で探し出したい ~SentenceTransformersとFaissで効率的にベクトル検索~"])
# => 以下のpandas dataframeが返ってくる
# Score	ID	Sentence
# 0.278252	0	Vector Searchによる検索

クエリ文書では「ベクトル検索」と日本語なのに対し、リファレンスではVector searchと英語であるにもかかわらず検索を行えていることがわかる

補足説明

ベクトル化を行うモデルについて

Transformerの様々なモデルが公開されています。Massive Text Embedding Benchmark(MTEB)というリーダーボードなどで最新のモデルが比較されていますので、参考にしてください。

https://huggingface.co/spaces/mteb/leaderboard

なお、日本語特化のモデルはあまり見たことがないため、基本的には多言語化モデルを使う必要があります。(今回の関数では multilingual-e5-base という多言語モデルを使用しています)

https://huggingface.co/intfloat/multilingual-e5-base

faissでのインデックス化について

上記のコードのインデックス化(faiss.IndexFlatL2(len(embeddings_refrence[0])))のところ。

今回は(もともとKaggleのコードでIndexFlatL2が使われていたことから)IndexFlatL2を使用したが、他のインデックス化の方法も考えられる。以下の公式ページに詳しい。

https://github.com/facebookresearch/faiss/wiki/Faiss-indexes

Discussion