PythonのSentenceTransformerを用いた文章類似度比較

2024/02/09に公開

ご挨拶

株式会社Welmoでエンジニアとして働いている進藤です。Welmoは介護業界の様々な社会課題(介護負担の増大・労働者不足など)を、ICTと先端技術の力で解決するために日々努めています。

はじめに

文章の類似度を計算することは、テキストデータを扱う多くのプロジェクトで重要です。このため、Pythonの SentenceTransformer ライブラリは非常に役立つツールです。この記事では、SentenceTransformer ライブラリを使用して、文章の類似度を計算する基本的な手法について解説します。

SentenceTransformerとは?

SentenceTransformer は、Hugging Face(https://huggingface.co/ )が提供するライブラリで、事前学習済みの文脈埋め込みモデル(Contextualized Embeddings)を使用して、文章をベクトルに変換します。これにより、文章の意味や文脈を考慮したベクトル表現を得ることができます。

インストール

まず初めに、SentenceTransformer ライブラリをインストールします。

pip install sentence-transformers

使用例

以下は、SentenceTransformer ライブラリを使用して文章類似度を計算する簡単な例です。

sample.py
from sentence_transformers import SentenceTransformer, util

# モデルのロード
model = SentenceTransformer('paraphrase-MiniLM-L6-v2')

# 比較する文章
sentence1 = "自然言語処理は非常に興味深い分野です。"
sentence2 = "自然言語処理には多くの挑戦がありますが、面白いです。"

# 文章をベクトルに変換
embeddings1 = model.encode(sentence1, convert_to_tensor=True)
embeddings2 = model.encode(sentence2, convert_to_tensor=True)

# コサイン類似度の計算
cosine_score = util.pytorch_cos_sim(embeddings1, embeddings2)[0][0]

print(f"文章1と文章2の類似度: {cosine_score}")

この例では、paraphrase-MiniLM-L6-v2 というモデルを使用しています。このモデルは一般的な文章の意味を理解するために事前学習されています。pytorch_cos_sim 関数を使用してコサイン類似度を計算し、文章の類似度を数値で得ることができます。

日本語モデルの比較実験

実験する日本語モデルは以下のものを比較したいと思います。

  • paraphrase-xlm-r-multilingual-v1
  • paraphrase-multilingual-mpnet-base-v2
  • paraphrase-multilingual-MiniLM-L6-v2
  • paraphrase-multilingual-MiniLM-L12-v2
  • stsb-xlm-r-multilingual
    日本語に対応しているモデルは以下のサイトを参考にしました。

https://tech.yellowback.net/posts/sentence-transformers-japanese-models

類似度比較

まずは2つの文章通しの類似を見てみたいと思います。
比較する文章は以下の文章を用います。

# 比較する文章
文章1: "食事の際に細かな手の使用が困難なため、できるようになりたい。"
文章2 = "食事動作ができるようになりたい。。"

ソースコードは以下の通りです。

experiment.py
from sentence_transformers import SentenceTransformer

# 比較する文章
sentence1 = "食事の際に細かな手の使用が困難なため、できるようになりたい。"
sentence2 = "食事動作ができるようになりたい。"

model = SentenceTransformer("paraphrase-xlm-r-multilingual-v1")
embeddings1 = model.encode(sentence1, convert_to_tensor=True)
embeddings2 = model.encode(sentence2, convert_to_tensor=True)
cosine_score = util.pytorch_cos_sim(embeddings1, embeddings2)[0][0]
print(f"paraphrase-xlm-r-multilingual-v1: 文章1と文章2の類似度: {cosine_score}")

model = SentenceTransformer("paraphrase-multilingual-mpnet-base-v2")
embeddings1 = model.encode(sentence1, convert_to_tensor=True)
embeddings2 = model.encode(sentence2, convert_to_tensor=True)
cosine_score = util.pytorch_cos_sim(embeddings1, embeddings2)[0][0]
print(f"paraphrase-multilingual-mpnet-base-v2 文章1と文章2の類似度: {cosine_score}")

model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")
embeddings1 = model.encode(sentence1, convert_to_tensor=True)
embeddings2 = model.encode(sentence2, convert_to_tensor=True)
cosine_score = util.pytorch_cos_sim(embeddings1, embeddings2)[0][0]
print(f"paraphrase-multilingual-MiniLM-L12-v2 文章1と文章2の類似度: {cosine_score}")

model = SentenceTransformer('stsb-xlm-r-multilingual')
embeddings1 = model.encode(sentence1, convert_to_tensor=True)
embeddings2 = model.encode(sentence2, convert_to_tensor=True)
cosine_score = util.pytorch_cos_sim(embeddings1, embeddings2)[0][0]
print(f"stsb-xlm-r-multilingual 文章1と文章2の類似度: {cosine_score}")

結果の比較がこちら

モデル名 コサイン類似度
paraphrase-xlm-r-multilingual-v1 0.5868266820907593
paraphrase-multilingual-mpnet-base-v2 0.6478358507156372
paraphrase-multilingual-MiniLM-L12-v2 0.669823169708252
stsb-xlm-r-multilingual 0.754802942276001

stsb-xlm-r-multilingualが一番類似度が高い数値で出力されました。

次に、1つの文章に対して2つの文章との類似度を出してみたいと思います。今回は、あえて片方の文章は類似度が低くなるような文章を設定し、もう片方は高くなりそうな文章を設定します。そして、自然言語モデルは期待通り高くなりそうな文章をしっかり判断できるかを確認したいと思います。比較する文章は以下の文章を用います。

# 比較する文章
比較対象の文章: "排尿動作の際に細かな手の使用が困難なため、できるようになりたい。"

文章2(類似度が高くなる想定の文章) : "排尿動作ができるようになりたい。"
文章3(類似度が低くなる想定の文章) : "ズボン等の着脱動作ができるようになりたい。"

ソースコードは以下の通りです。

experiment2.py
from sentence_transformers import SentenceTransformer

# 比較する文章
sentence1 = "排尿動作の際に細かな手の使用が困難なため、できるようになりたい。"
sentence2 = "排尿動作ができるようになりたい。"
sentence3 = "ズボン等の着脱動作ができるようになりたい。"

model = SentenceTransformer("paraphrase-xlm-r-multilingual-v1")
embeddings1 = model.encode(sentence1, convert_to_tensor=True)
embeddings2 = model.encode(sentence2, convert_to_tensor=True)
embeddings3 = model.encode(sentence3, convert_to_tensor=True)
cosine_score = util.pytorch_cos_sim(embeddings1, embeddings2)[0][0]
print(f"paraphrase-xlm-r-multilingual-v1: 文章1と文章2の類似度: {cosine_score}")
cosine_score = util.pytorch_cos_sim(embeddings1, embeddings3)[0][0]
print(f"paraphrase-xlm-r-multilingual-v1 文章1と文章3の類似度: {cosine_score}")

model = SentenceTransformer("paraphrase-multilingual-mpnet-base-v2")
embeddings1 = model.encode(sentence1, convert_to_tensor=True)
embeddings2 = model.encode(sentence2, convert_to_tensor=True)
embeddings3 = model.encode(sentence3, convert_to_tensor=True)
cosine_score = util.pytorch_cos_sim(embeddings1, embeddings2)[0][0]
print(f"paraphrase-multilingual-mpnet-base-v2 文章1と文章2の類似度: {cosine_score}")
cosine_score = util.pytorch_cos_sim(embeddings1, embeddings3)[0][0]
print(f"paraphrase-multilingual-mpnet-base-v2 文章1と文章3の類似度: {cosine_score}")

model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")
embeddings1 = model.encode(sentence1, convert_to_tensor=True)
embeddings2 = model.encode(sentence2, convert_to_tensor=True)
embeddings3 = model.encode(sentence3, convert_to_tensor=True)
cosine_score = util.pytorch_cos_sim(embeddings1, embeddings2)[0][0]
print(f"paraphrase-multilingual-MiniLM-L12-v2 文章1と文章2の類似度: {cosine_score}")
cosine_score = util.pytorch_cos_sim(embeddings1, embeddings3)[0][0]
print(f"paraphrase-multilingual-MiniLM-L12-v2 文章1と文章3の類似度: {cosine_score}")

model = SentenceTransformer('stsb-xlm-r-multilingual')
embeddings1 = model.encode(sentence1, convert_to_tensor=True)
embeddings2 = model.encode(sentence2, convert_to_tensor=True)
embeddings3 = model.encode(sentence3, convert_to_tensor=True)
cosine_score = util.pytorch_cos_sim(embeddings1, embeddings2)[0][0]
print(f"stsb-xlm-r-multilingual 文章1と文章2の類似度: {cosine_score}")
cosine_score = util.pytorch_cos_sim(embeddings1, embeddings3)[0][0]
print(f"stsb-xlm-r-multilingual 文章1と文章3の類似度: {cosine_score}")

結果の比較がこちら

モデル名 コサイン類似度_文章1と2 コサイン類似度_文章1と3 文章1とより類似度が高い文章 正解⭕️不正解✖️
paraphrase-xlm-r-multilingual-v1 0.6936559677124023 0.3747560381889343 文章2 ⭕️
paraphrase-multilingual-mpnet-base-v2 0.77503359317779549 0.51871657371521 文章2 ⭕️
paraphrase-multilingual-MiniLM-L12-v2 0.7568936347961426 0.44228893518447876 文章2 ⭕️
stsb-xlm-r-multilingual 0.7541677951812744 0.3308173418045044 文章2 ⭕️

どのモデルも期待通り、文章2の方が比較対象の文章と類似度が高いと出力されました。かなり優秀なモデルですね。また、中でもstsb-xlm-r-multilingualは一番類似度の差が大きく出ていました。日本語の文章をembeddingする際は、基本的にこのモデルを使うといいかもしれません。

この結果を見ると、どんなモデルでもいい精度が出るように見えてしまうのですが、日本語に対応していないモデルはこのような結果は得られません。日本語対応していないモデルで同じ実験を行った結果を下記に載せます。今回はparaphrase-MiniLM-L12-v2を使用してみたいと思います。

モデル名 コサイン類似度_文章1と2 コサイン類似度_文章1と3 文章1とより類似度が高い文章 正解⭕️不正解✖️
paraphrase-MiniLM-L12-v2 0.9272501468658447 0.9386188983917236 文章3 ✖️

しっかり間違えてますね。なので、日本語文章を扱う場合は、ちゃんと日本語対応したモデルを扱うようにしましょう。

まとめ

この記事では、SentenceTransformer ライブラリを使用して文章の類似度を計算する基本的な手法の紹介と、日本語モデルをを使って、embeddingの正確さについて比較しました。今回の実験では、stsb-xlm-r-multilingualというモデルが最も精度が高かったですが、実際のプロジェクトでは、データによってここまで大きなモデルを使用しなくても十分な精度が得られる場合がありますので、モデルのサイズを考慮した選択が必要です。この例をベースにして応用してみてください。これにより、テキストデータを効果的に処理し、プロジェクトの品質を向上させることができるでしょう。

Welmo Engineer Blog

Discussion