質問応答システムを作成する際、最初のステップは関連するドキュメントをデータベースから取得することです。これは通常、クエリをOpenAIのAPIを使用して埋め込みに変換し、データベース内の埋め込みとの距離を計算して近い順に並べることで行われます。
しかし、あるブログポスト では、「OpenAIの埋め込みサービスはコストが高く精度も低い」と主張されています。このポストでは、OpenAIの埋め込みモデルをGoogleの埋め込みモデルやSentence-Transformersモデルと比較し、精度が低くコストが高いことが示されています。ただし、この評価は英語に基づいており、2年前のものなので、現在の日本語の状況がどうなっているかが気になります。
それで今回は、日本語のEmbeddingモデルを比較するベンチマークを作りました。ベンチマークの結果は、下図のようになります。
OpenAIの精度が最も高く、他のモデルより各指標で10%高いです。また、ほかのEmbeddingの中で一番精度が高いのはTensforFlowのUniversal Sentence Encoderです。
そこで、今回は日本語の埋め込みモデルを比較するベンチマークを作成しました。結果は下図の通りで、OpenAIの精度が最も高く、他のモデルに比べて各指標で10%高いです。また、他の埋め込みモデルの中で最も精度が高いのは、TensorFlowのUniversal Sentence Encoderです。
使用したデータセット
今回使用するデータは東京都立大学のeラーニングシステムのQ&Aデータです。このデータは、東京都立大学で導入されたeラーニングシステムのユーザーから2015年4月から2018年7月までに報告された問題点としてのQ&Aデータを収集したものです。427の質問と79の回答が含まれています。質問にどの回答に紐づくかのラベルがあります。
データの様子は下記の通りです。
import pandas as pd
# https://zenodo.org/record/2783642
q_df = pd.read_csv("https://zenodo.org/record/2783642/files/Questions.csv" )
a_df = pd.read_csv("https://zenodo.org/record/2783642/files/Answers.csv" )
print ("q_df.shape:" , q_df.shape)
print ("a_df.shape:" , a_df.shape)
q_df.columns = [c.strip() for c in q_df.columns]
a_df.columns = [c.strip() for c in a_df.columns]
df = q_df.merge(a_df, on= "AID" )
df.columns = ["query" ,"AID" ,"document" ]
metadata = a_df[["AID" ]].to_dict(orient= "records" )
documents = a_df["Text" ].tolist()
query_list = list (zip (q_df["Text" ], q_df["AID" ]))
display(q_df.head(3 ))
display(a_df.head(3 ))
q_df.shape: (427, 2)
a_df.shape: (79, 2)
0
履修している授業で先生が資料をアップロードしているはずだが、コース上に資料が見当たらない。
A001
1
資料をマイページに置いたが、学生からは見えなかった。
A001
2
前期の科目の「資料」を学生から見られないようにするにはどうしたら良いか?
A001
0
A001
資料が見つからない場合は、以下の点を確認してください。<br><br><br>【受講生編】<...
1
A002
資料のアップロードやお知らせ作成時の電子メールでの通知の有無は、各授業の担当教員が設定できま...
2
A003
kibacoにはファイルへパスワードを設定する機能はありません。資料は受講生全員に開示されま...
評価対象と評価方法、評価指標
今回評価対象は4つがあります。
Huggingface hubの「Feature extraction」カテゴリで「Japanese」対応する全てのモデル
Sentence-Transformsersのモデル
TensorFlowのUniversal Sentence Encoderモデル
OpenAIのEmbeddingモデル
評価方法は以下の3つのステップです。
79のドキュメントをEmbeddingに変換し、FAISSのVectorstoreとして保存する。
427の質問をEmbeddingに変換し、FAISSのVectorstoreを使用して、79のドキュメントを近い順に並べる。
並んだ順番でEmbeddingの性能を評価する
評価指標は以下の3つです。
Mean Reciprocal Rank(MRR): 正解ドキュメントの順位の平均の逆数で、ランク全体を評価する指標。
Recall@1: 正解ドキュメントが1番目に並んでいるかどうかを評価する指標。
Recall@5: 正解ドキュメントが上位5位以内に入っているかどうかを評価する指標。
評価に使用したコードは約100行です。興味があれば、展開してご覧ください。
Show the code
from langchain.vectorstores import FAISS
from langchain.embeddings.openai import OpenAIEmbeddings
from tqdm.auto import tqdm
from dataclasses import dataclass
from huggingface_hub import HfApi, ModelFilter
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.embeddings import TensorflowHubEmbeddings
DOC_NUM = len (a_df)
@dataclass
class EvaluationResult:
result_df: pd.DataFrame
mrr: float
recall_at_1: float
recall_at_5: float
class RankEvaluator:
def __init__ (self , vectorstore):
self .vectorstore = vectorstore
def get_query_result_rank(self , txt):
search_result = self .vectorstore.similarity_search(txt, k= DOC_NUM)
rank_result = [r.metadata["AID" ] for r in search_result]
return rank_result
def evaluate(self , query_list, get_rank_func= None ):
if not get_rank_func:
get_rank_func = self .get_query_result_rank
result_list = []
for query, aid in tqdm(query_list):
rank_result = get_rank_func(query)
rank = rank_result.index(aid) + 1
result_list.append((query, rank, rank_result))
result_df = pd.DataFrame(result_list, columns= ["query" , "rank" , "rank_result" ])
return EvaluationResult(result_df, mrr(result_df["rank" ]), recall_at_k(result_df["rank" ], 1 ), recall_at_k(result_df["rank" ], 5 ))
def mrr(rank_array):
return (1 / rank_array).mean()
def recall_at_k(rank_array, k):
return (rank_array <= k).mean()
def evaluate_embedding(embedding, get_rank_func= None , texts= documents, metadata= metadata):
faiss_vectorstore = FAISS.from_texts(
texts= texts,
embedding= embedding,
metadatas= metadata,
)
evaluator = RankEvaluator(faiss_vectorstore)
return evaluator.evaluate(query_list, get_rank_func= get_rank_func)
# fetch feature extraction model ids that support Japanese form HuggingFace Hub
api = HfApi()
models = api.list_models(
filter = ModelFilter(
task= "feature-extraction" ,
library= "pytorch" ,
language= "ja"
)
)
model_id_list = [m.id for m in models]
# add sentence-transformers models
model_id_list += [
"distiluse-base-multilingual-cased-v2" ,
"paraphrase-multilingual-MiniLM-L12-v2" ,
"paraphrase-multilingual-mpnet-base-v2" ,
"sentence-transformers/stsb-xlm-r-multilingual"
]
# evaluate models
# if evaluation fails, set result to None
result_dict = {}
for model_id in model_id_list:
try :
embedding = HuggingFaceEmbeddings(model_name= model_id)
result = evaluate_embedding(embedding)
result_dict[model_id] = result
except :
result_dict[model_id] = None
# evaluate Tensorflow Hub models
embeddings_tfhub = TensorflowHubEmbeddings()
result = evaluate_embedding(embeddings_tfhub)
result_dict["TensorflowHubEmbeddings" ] = result
# evaluate OpenAIEmbeddings
embedding_openai = OpenAIEmbeddings()
reuslt_openai = evaluate_embedding(embedding_openai)
result_dict["OpenAIEmbeddings" ] = reuslt_openai
# summary the result to DataFrame
result_list = []
for model in result_dict.keys():
result = result_dict[model]
if result:
result_list.append([model,result.mrr, result.recall_at_1, result.recall_at_5] )
else :
result_list.append([model,0 , 0 ,0 ] )
result_df = pd.DataFrame(
result_list,
columns = ["model_id" ,"mrr" ,"recall_at_1" ,"recall_at_5" ]
).sort_values("mrr" , ascending= False )
result_df.reset_index(drop= True , inplace= True )
テスト結果
全体の結果を見ると、OpenAIEmbeddingが圧倒的に優れており、54%のQueryで正解のドキュメントが1番目に、そして86%のQueryで正解のドキュメントが5番目にランクインしています。
これに対して2位のTensorFlowのUniversal Sentence Encoderは、各指標で10%以上低いパフォーマンスを示しています。そのため、精度にこのような大きな差があることから、OpenAIEmbedding以外のモデルを使用する意義はほとんどなくなります。
(結果が0になったモデルは実験中にエラーが発生したものです。)
0
OpenAIEmbeddings
0.684147
0.548009
0.868852
1
TensorflowHubEmbeddings
0.560619
0.407494
0.761124
2
paraphrase-multilingual-mpnet-base-v2
0.525899
0.398126
0.676815
3
oshizo/sbert-jsnli-luke-japanese-base-lite
0.520106
0.405152
0.655738
4
paraphrase-multilingual-MiniLM-L12-v2
0.497027
0.370023
0.639344
5
intfloat/multilingual-e5-base
0.481144
0.337237
0.632319
6
sonoisa/sentence-bert-base-ja-mean-tokens-v2
0.465294
0.327869
0.622951
7
setu4993/smaller-LaBSE
0.450434
0.290398
0.632319
8
sonoisa/sentence-bert-base-ja-en-mean-tokens
0.438923
0.304450
0.599532
9
setu4993/LaBSE
0.434725
0.274005
0.625293
10
Blaxzter/LaBSE-sentence-embeddings
0.434725
0.274005
0.625293
11
distiluse-base-multilingual-cased-v2
0.428484
0.264637
0.620609
12
ZurichNLP/unsup-simcse-xlm-roberta-base
0.419397
0.299766
0.526932
13
sentence-transformers/stsb-xlm-r-multilingual
0.361811
0.231850
0.484778
14
sonoisa/clip-vit-b-32-japanese-v1
0.320160
0.203747
0.437939
15
sonoisa/sentence-bert-base-ja-mean-tokens
0.293779
0.177986
0.402810
16
google/canine-s
0.270446
0.159251
0.358314
17
google/canine-c
0.258978
0.159251
0.341920
18
colorfulscoop/sbert-base-ja
0.227531
0.133489
0.295082
19
sonoisa/t5-base-japanese
0.213053
0.135831
0.278689
20
M-CLIP/M-BERT-Distil-40
0.170714
0.084309
0.236534
21
microsoft/unihanlm-base
0.162957
0.098361
0.187354
22
nielsr/lilt-xlm-roberta-base
0.143722
0.074941
0.173302
23
severinsimmler/xlm-roberta-longformer-base-16384
0.129116
0.072600
0.145199
24
facebook/nllb-moe-54b
0.000000
0.000000
0.000000
25
sonoisa/sentence-luke-japanese-base-lite
0.000000
0.000000
0.000000
26
TylorShine/distilhubert-ft-japanese-50k
0.000000
0.000000
0.000000
27
rinna/japanese-hubert-base
0.000000
0.000000
0.000000
28
ArthurZ/nllb-moe-128
0.000000
0.000000
0.000000
29
pkshatech/simcse-ja-bert-base-clcmlp
0.000000
0.000000
0.000000
30
paulhindemith/fasttext-jp-embedding
0.000000
0.000000
0.000000
31
rinna/japanese-cloob-vit-b-16
0.000000
0.000000
0.000000
32
rinna/japanese-clip-vit-b-16
0.000000
0.000000
0.000000
33
sonoisa/sentence-t5-base-ja-mean-tokens
0.000000
0.000000
0.000000
34
megagonlabs/transformers-ud-japanese-electra-b...
0.000000
0.000000
0.000000