日本語Embeddingモデルのベンチマーク比較: OpenAIが圧倒的な精度でリード

NLP
LLMs
LangChain
Published

June 1, 2023

質問応答システムを作成する際、最初のステップは関連するドキュメントをデータベースから取得することです。これは通常、クエリを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)
Text AID
0 履修している授業で先生が資料をアップロードしているはずだが、コース上に資料が見当たらない。 A001
1 資料をマイページに置いたが、学生からは見えなかった。 A001
2 前期の科目の「資料」を学生から見られないようにするにはどうしたら良いか? A001
AID Text
0 A001 資料が見つからない場合は、以下の点を確認してください。<br><br><br>【受講生編】<...
1 A002 資料のアップロードやお知らせ作成時の電子メールでの通知の有無は、各授業の担当教員が設定できま...
2 A003 kibacoにはファイルへパスワードを設定する機能はありません。資料は受講生全員に開示されま...

評価対象と評価方法、評価指標

今回評価対象は4つがあります。

  1. Huggingface hubの「Feature extraction」カテゴリで「Japanese」対応する全てのモデル
  2. Sentence-Transformsersのモデル
  3. TensorFlowのUniversal Sentence Encoderモデル
  4. OpenAIのEmbeddingモデル

評価方法は以下の3つのステップです。

  1. 79のドキュメントをEmbeddingに変換し、FAISSのVectorstoreとして保存する。
  2. 427の質問をEmbeddingに変換し、FAISSのVectorstoreを使用して、79のドキュメントを近い順に並べる。
  3. 並んだ順番でEmbeddingの性能を評価する

評価指標は以下の3つです。

  1. Mean Reciprocal Rank(MRR): 正解ドキュメントの順位の平均の逆数で、ランク全体を評価する指標。
  2. Recall@1: 正解ドキュメントが1番目に並んでいるかどうかを評価する指標。
  3. 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になったモデルは実験中にエラーが発生したものです。)

result_df
model_id mrr recall_at_1 recall_at_5
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