本記事について
本記事は、2024年1月に社内で実施した勉強会の内容をもとに、外部向けに加筆・再構成したものです。
記載の内容は執筆当時の情報であり、現在の仕様やベストプラクティスと異なる可能性があります。
実装にあたっては、必ず最新の公式ドキュメントをご確認いただくようお願いいたします。
FAISSとは
FAISSは、Facebook AI が開発した、高速なベクトル類似検索およびクラスタリングのためのライブラリです。
大量のベクトルデータから、指定したベクトルに最も近いものを効率的に検索できます。
この仕組みは、画像検索・テキスト検索・レコメンドシステムなど、さまざまな AI アプリケーションで活用されています。
ベクトルとは


ベクトルデータの類似度を算出してみる
今回は SentenceTransformer を用いて、Wikipedia の説明文の類似度を検証するプログラムを作成しました。
参考URL:
SBテクノロジー (SBT), 高橋 良輔, AI で文章をかしこく比較! Sentence-Transformers のご紹介(2024年1月閲覧)https://www.softbanktech.co.jp/special/blog/dx_station/2022/0032/
参考URL:
Google Colab Notebook(実行例)
https://colab.research.google.com/drive/1zzXDJT88QpW9JO9TA3Iw-SU567wYfWVM#scrollTo=0Gaj2xlLlLF8
# 必要なライブラリのインポート
import numpy as np
import pandas as pd
import wikipedia
from sentence_transformers import SentenceTransformer
import faiss
# Wikipedia APIの初期化
wiki_wiki = wikipedia.set_lang("jp")
# SentenceTransformerモデルの初期化
model = SentenceTransformer('cl-tohoku/bert-base-japanese')
# CSVファイルからキーワードを読み込む
df = pd.read_csv('keyword_data.csv')
df.head()
# Wikipediaの文章を取得し、ベクトルデータに変換してリストに保存
document_vectors = []
for keyword in df['keyword']:
try:
summary = wikipedia.summary(keyword)
vector = model.encode(summary)
document_vectors.append(vector)
except:
print(keyword)
# ベクトルデータをnumpy arrayに変換
vectors_np = np.array(document_vectors).astype('float32')
# FAISSインデックスを作成
dimension = len(vectors_np[0])
index = faiss.IndexFlatL2(dimension)
index.add(vectors_np)
# 実施の運用では下記のように作成したインデックスをファイルに保存しておくことが可能
# faiss.write_index(index, 'faiss_距離による類似度.index')
# 検索
search_word = 'すき焼き'
search_summary = wikipedia.summary(search_word)
search_vector = np.array([model.encode(search_summary)]).astype('float32')
D, I = index.search(search_vector, 50) # 上位50件を表示
ベクトルデータのコサイン類似度を算出してみる
参考URL:
@IT, 一色政彦, コサイン類似度(Cosine Similarity)とは?(2024年1月閲覧)
https://atmarkit.itmedia.co.jp/ait/articles/2112/08/news020.html
参考URL:
Google Colab Notebook(実行例)
https://colab.research.google.com/drive/1PXqowMByUEGeX7fUgPNMmtcM8jiSYgUq#scrollTo=oNeIyYhUl66F
# FAISSインデックスを作成(コサイン類似度)
# 10個のクラスタリングに分けて、3個のクラスタ分を検索対象とする
dimension = len(vectors_np[0])
index_cos = faiss.IndexIVFFlat(quantizer, dimension, 10, faiss.METRIC_INNER_PRODUCT)
index_cos.nprobe = 3
# クラスタリングのためのトレーニング
train_vectors = vectors_np[:int(vectors_np.shape[0]/2)].copy()
faiss.normalize_L2(train_vectors)
index_cos.train(train_vectors)
# 全データのインデックスを作成
all_vectors = vectors_np.copy()
faiss.normalize_L2(all_vectors)
index_cos.set_direct_map_type(faiss.DirectMap.Hashtable)
index_cos.add_with_ids(all_vectors, df['no'])
# faiss.write_index(index_cos, 'faiss_コサイン類似度.index')
# 検索
search_word = 'すき焼き'
search_summary = wikipedia.summary(search_word)
search_vector = np.array([model.encode(search_summary)]).astype('float32')
faiss.normalize_L2(search_vector)
D, I = index_cos.search(search_vector, 50)
# 類似度が高い順にキーワードを表示
for i, keyword_index in enumerate(I[0]):
similarity_score = D[0][i] # 類似度スコア
similar_keyword = df.loc[keyword_index, 'keyword']
print(similar_keyword + ' score:' + str(similarity_score))
※本実装は、FAISS 公式の Issue を参考にしました。
参考URL:
FAISS公式Issue(2024年1月閲覧)
https://github.com/facebookresearch/faiss/issues/1119
距離とコサイン類似度の違い
- 距離は、類似しているほど 0 に近づき、異なるほど値が大きくなります。
値の上限はありません。 - コサイン類似度は、類似しているほど 1 に近づき、0〜1 の範囲で表現されます。
% 表現も可能です。 - 実務においては、距離とコサイン類似度による精度の差異を気にするケースは少ないです。
まとめ
本記事では、Wikipedia の説明文をベクトルデータに変換し、FAISS を用いて類似文書検索を行うまでの一連のプロセスを、実際のコード例とともに整理しました。SentenceTransformerによるベクトル変換から、距離ベース検索、さらにコサイン類似度検索まで、FAISS の基本的な使い方を俯瞰できたのではないかと思います。
特に、距離とコサイン類似度の違い、FAISS での正規化処理やクラスタリングの必要性、nprobe 設定による検索範囲の調整など、実際に触れてみることで理解が深まるポイントを確認できました。
FAISS を用いたベクトル類似検索及びクラスタリングは、ベクトルデータの扱い方やインデックス構造の選択によって、検索精度や性能が変動する可能性があります。
今後の開発や検証の際、本記事の内容がその第一歩として役立てば幸いです。
