Pythonで類似度を計算する方法

Pocket

Pythonを使って文字列の類似度を計算する方法についてまとめます。


目次


重要度 tf

tfという指標があります。
「ある単語がその文章に多く登場すればそれは重要である」という考えです。

def calc_tf(term, 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

重要度と特徴度

「重要で特徴のある単語は重要度と特徴度の積である」という考えです。

def calc_tfidf(word, words, texts):
  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

類似度

文章と文章の類似度を調べます。
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))

文章の類似度

対象となる文章(文章を分解したもの)と全体の文章(文章を分解したもののリスト)のそれぞれの文章との類似度を計算します。

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

サンプル

def main():
  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となりました。
妥当な結果だと思われます。

[ 2022年3月30日 | カテゴリー: Python | タグ: ]

« | »

コメント

  1. […] 以前、Pythonで類似度を計算する方法を書きましたが、これをJavaScriptに書き換えてみました。 Pythonの方が書き慣れてしまったこともありますが、JavaScriptも便利な記法が増えていて、移植に時間がかかりました。 […]

コメントを残す

メールアドレスが公開されることはありません。

送信してください。


タグ

カテゴリー

最近の投稿

最近のコメント

固定ページ

アーカイブ

stabucky

写真

メタ情報