Pythonを使って文字列の類似度を計算する方法についてまとめます。
目次
重要度 tf
tfという指標があります。
「ある単語がその文章に多く登場すればそれは重要である」という考えです。
def calc_tf(term, terms):
ct = 0
for w in terms:
if w == term:
ct += 1
return ct / len(terms)
ct = 0
for w in terms:
if w == term:
ct += 1
return ct / len(terms)
特徴度 idf
idfという指標があります。
「ある単語を含む文章が他の文章には登場しないならばそれは特徴的である」という考えです。
def calc_idf(term, texts):
ct = 0
for terms in texts:
if term in terms:
ct += 1
if ct == 0:
return 0
return math.log(len(texts) / ct) + 1
ct = 0
for terms in texts:
if term in terms:
ct += 1
if ct == 0:
return 0
return math.log(len(texts) / ct) + 1
重要度と特徴度
「重要で特徴のある単語は重要度と特徴度の積である」という考えです。
def calc_tfidf(word, words, texts):
tr = calc_tf(word, words)
idf = calc_idf(word, texts)
return tr * idf
tr = calc_tf(word, words)
idf = calc_idf(word, texts)
return tr * idf
ベクトル化
文章に含まれる単語をベクトル化します。
def vector(terms):
# apple, apple, banana -> apple: 2, banana: 1
v = {}
for term in terms:
v[term] = v.get(term, 0) + 1
return v
# apple, apple, banana -> apple: 2, banana: 1
v = {}
for term in terms:
v[term] = v.get(term, 0) + 1
return v
類似度
文章と文章の類似度を調べます。
2本のベクトルが同じ向きであればコサインは1に、逆の向きであれば-1になります。これを利用します。
def cosine_similarity(vector0, vector1):
# 類似度
temp = vector0.copy()
temp.update(vector1)
a = [vector0.get(k, 0) for k in temp.keys()]
b = [vector1.get(k, 0) for k in temp.keys()]
# x=a1*b1+...+an*bn
# y=a1^2+...+an^2
# z=b1^2+...+bn^2
# cos=x/√y/√z
x, y, z = 0, 0, 0
for u, v in zip(a, b):
x += u * v
y += u ** 2
z += v ** 2
if y == 0 or z == 0:
return 0
return x / (y ** (1 / 2)) / (z ** (1 / 2))
# 類似度
temp = vector0.copy()
temp.update(vector1)
a = [vector0.get(k, 0) for k in temp.keys()]
b = [vector1.get(k, 0) for k in temp.keys()]
# x=a1*b1+...+an*bn
# y=a1^2+...+an^2
# z=b1^2+...+bn^2
# cos=x/√y/√z
x, y, z = 0, 0, 0
for u, v in zip(a, b):
x += u * v
y += u ** 2
z += v ** 2
if y == 0 or z == 0:
return 0
return x / (y ** (1 / 2)) / (z ** (1 / 2))
文章の類似度
対象となる文章(文章を分解したもの)と全体の文章(文章を分解したもののリスト)のそれぞれの文章との類似度を計算します。
def get_similar_tfidf(target_terms, texts):
vector_target = calc_vector_tfidf(target_terms, texts)
results = []
for text in texts:
vector_one = calc_vector_tfidf(text, texts)
result = cosine_similarity(vector_target, vector_one)
results.append([result, text])
results = sorted(results, key=lambda x : x[0], reverse=True)
return results
def calc_vector_tfidf(target_terms, texts):
# https://qiita.com/nmbakfm/items/6bb91b89571dd68fcea6
v = {}
for target in target_terms:
v[target] = calc_tfidf(target, target_terms, texts)
return v
vector_target = calc_vector_tfidf(target_terms, texts)
results = []
for text in texts:
vector_one = calc_vector_tfidf(text, texts)
result = cosine_similarity(vector_target, vector_one)
results.append([result, text])
results = sorted(results, key=lambda x : x[0], reverse=True)
return results
def calc_vector_tfidf(target_terms, texts):
# https://qiita.com/nmbakfm/items/6bb91b89571dd68fcea6
v = {}
for target in target_terms:
v[target] = calc_tfidf(target, target_terms, texts)
return v
サンプル
def main():
target_terms = ["ぶどう", "みかん", "りんご"]
texts = [
["ぶどう", "みかん", "りんご"],
["りんご", "ぶどう", "みかん"],
["ぶどう", "みかん", "りんご", "なし"],
["ぶどう", "みかん", "なし"],
["だいこん", "にんじん", "れんこん"],
]
results = get_similar_tfidf(target_terms, texts)
print(results)
target_terms = ["ぶどう", "みかん", "りんご"]
texts = [
["ぶどう", "みかん", "りんご"],
["りんご", "ぶどう", "みかん"],
["ぶどう", "みかん", "りんご", "なし"],
["ぶどう", "みかん", "なし"],
["だいこん", "にんじん", "れんこん"],
]
results = get_similar_tfidf(target_terms, texts)
print(results)
実行結果
["ぶどう", "みかん", "りんご"]
これが比較元となります。
[0.9999999999999999, ['ぶどう', 'みかん', 'りんご']]
全く同じ文章(リスト)の場合は1.0となります。
[0.9999999999999999, ['りんご', 'ぶどう', 'みかん']]
要素が同じであれば1.0となります。
[0.7678285085833854, ['ぶどう', 'みかん', 'りんご', 'なし']]
新しく「なし」を追加すると類似度は少し下がって0.77となりました。
[0.504667658050028, ['ぶどう', 'みかん', 'なし']]
新しく「なし」を追加して「リンゴ」を削除するとさらに下がって0.50となりました。
[0.0, ['だいこん', 'にんじん', 'れんこん']]
全く別の要素と比較すると0.0となりました。
妥当な結果だと思われます。
コメント
[…] 以前、Pythonで類似度を計算する方法を書きましたが、これをJavaScriptに書き換えてみました。 Pythonの方が書き慣れてしまったこともありますが、JavaScriptも便利な記法が増えていて、移植に時間がかかりました。 […]