web-dev-qa-db-ja.com

2つの文の文字列からコサインの類似度を計算する

Python:tf-idf-cosine:ドキュメントの類似性を見つけるため から、tf-idfコサインを使用してドキュメントの類似性を計算することができます。外部ライブラリをインポートせずに、2つの文字列間のコサイン類似度を計算する方法はありますか?

s1 = "This is a foo bar sentence ."
s2 = "This sentence is similar to a foo bar sentence ."
s3 = "What is this string ? Totally not related to the other two lines ."

cosine_sim(s1, s2) # Should give high cosine similarity
cosine_sim(s1, s3) # Shouldn't give high cosine similarity value
cosine_sim(s2, s3) # Shouldn't give high cosine similarity value
65
alvas

単純な純粋なPython実装は次のようになります。

import re, math
from collections import Counter

Word = re.compile(r'\w+')

def get_cosine(vec1, vec2):
     intersection = set(vec1.keys()) & set(vec2.keys())
     numerator = sum([vec1[x] * vec2[x] for x in intersection])

     sum1 = sum([vec1[x]**2 for x in vec1.keys()])
     sum2 = sum([vec2[x]**2 for x in vec2.keys()])
     denominator = math.sqrt(sum1) * math.sqrt(sum2)

     if not denominator:
        return 0.0
     else:
        return float(numerator) / denominator

def text_to_vector(text):
     words = Word.findall(text)
     return Counter(words)

text1 = 'This is a foo bar sentence .'
text2 = 'This sentence is similar to a foo bar sentence .'

vector1 = text_to_vector(text1)
vector2 = text_to_vector(text2)

cosine = get_cosine(vector1, vector2)

print 'Cosine:', cosine

プリント:

Cosine: 0.861640436855

ここで使用されるコサイン式は here で説明されています。

これには、tf-idfによる単語の重み付けは含まれませんが、tf-idfを使用するには、tfidfの重みを推定するためのかなり大きなコーパスが必要です。

より洗練された方法を使用してテキストから単語を抽出したり、それを語幹化または補綴化するなどして、さらに開発することもできます。

149
vpekar

簡単な答えは、「いいえ、それをリモートでもうまく機能する原則的な方法で行うことはできません」です。それは自然言語処理の研究では解決されていない問題であり、偶然にも私の博士論文の主題です。私たちがどこにいるか簡単に要約し、いくつかの出版物を紹介します。

単語の意味

ここで最も重要な仮定は、質問文の各Wordを表すベクトルを取得できることです。このベクトルは通常、Wordが表示されるコンテキストをキャプチャするために選択されます。たとえば、3つのコンテキスト「eat」、「red」、「fluffy」のみを考慮する場合、Wordの「cat」は[98、1 、87]。非常に長いテキスト(今日の基準では数十億語は珍しくありません)を読む場合、「猫」という言葉は「ふわふわ」や「食べる」という文脈で頻繁に現れるからです。 、しかし「赤」の文脈ではそうではありません。同様に、「犬」は[87,2,34]として表され、「傘」は[1,13,0]として表されます。これらのベクトルを3D空間のポイントとしてイメージすると、「猫」は「傘」よりも明らかに「犬」に近いため、「猫」は「傘」よりも「犬」に似たものを意味します。

この作業は、90年代前半から調査されており(例: this Greffenstetteによる作業)、驚くほど良い結果が得られています。たとえば、コンピューターにウィキペディアを読み込ませることで最近作成したシソーラスのランダムなエントリを次に示します。

theory -> analysis, concept, approach, idea, method
voice -> vocal, tone, sound, melody, singing
james -> william, john, thomas, robert, george, charles

これらの類似した単語のリストは、人間の介入なしに完全に取得されました。テキストを入力して、数時間後に戻ってきます。

フレーズの問題

「ジンジャーキツネは果物が大好き」など、長いフレーズに対して同じことをしていない理由を尋ねるかもしれません。十分なテキストがないためです。 信頼性の高いがXに似ているものを確立するために、コンテキストで使用されているXの多くの例を見る必要があります。 Xが「音声」のような単一の単語である場合、これはそれほど難しくありません。ただし、Xが長くなると、Xの自然発生を見つける可能性は指数関数的に遅くなります。比較のために、Googleは「キツネ」という単語を含む約10億のページを持ち、「ジンジャーキツネが大好きな果物」を含む1つのページではありません。

組成

データの希薄性の問題に取り組むために、構成を実行します。つまり、実際のテキストから簡単に取得できる単語のベクトルを取得し、その意味をキャプチャする方法でまとめます。悪いニュースは、これまで誰もそれをうまくできなかったことです。

最も簡単で明白な方法は、個々のWordベクトルを加算または乗算することです。これは、「猫が犬を追いかける」と「犬が猫を追いかける」という副作用を引き起こします。また、乗算する場合は、特に注意する必要があります。そうしないと、すべての文が[0,0,0、...、0]で表され、ポイントが無効になります。

さらに読む

これまで提案されてきた、より洗練された作曲方法については説明しません。 Katrin Erkの "Wordの意味とフレーズの意味のベクトル空間モデル:調査" を読むことをお勧めします。これは、始めるための非常に優れた高レベルの調査です。残念ながら、出版社のウェブサイトでは無料で入手できません。コピーを入手するには著者に直接メールしてください。その論文では、より多くの具体的な方法への参照を見つけるでしょう。よりわかりやすいのは Mitchel and Lapata(2008)Baroni and Zamparelli(2010) です。


@vpekarによるコメントの後の編集:この答えの一番下の行は、単純な方法が存在する(加算、乗算、表面類似性など)であるが、これらは基本的に欠陥そして、一般的に、それらから素晴らしいパフォーマンスを期待すべきではありません。

51
mbatchkarov

これを試して。 https://conceptnet.s3.amazonaws.com/downloads/2017/numberbatch/numberbatch-en-17.06.txt.gz からファイル「numberbatch-en-17.06.txt」をダウンロードして抽出します。関数 'get_sentence_vector'は、Wordベクトルの単純な合計を使用します。ただし、重みが各単語のTf-Idfに比例する重み付き合計を使用することで改善できます。

import math
import numpy as np

std_embeddings_index = {}
with open('path/to/numberbatch-en-17.06.txt') as f:
    for line in f:
        values = line.split(' ')
        Word = values[0]
        embedding = np.asarray(values[1:], dtype='float32')
        std_embeddings_index[Word] = embedding

def cosineValue(v1,v2):
    "compute cosine similarity of v1 to v2: (v1 dot v2)/{||v1||*||v2||)"
    sumxx, sumxy, sumyy = 0, 0, 0
    for i in range(len(v1)):
        x = v1[i]; y = v2[i]
        sumxx += x*x
        sumyy += y*y
        sumxy += x*y
    return sumxy/math.sqrt(sumxx*sumyy)


def get_sentence_vector(sentence, std_embeddings_index = std_embeddings_index ):
    sent_vector = 0
    for Word in sentence.lower().split():
        if Word not in std_embeddings_index :
            Word_vector = np.array(np.random.uniform(-1.0, 1.0, 300))
            std_embeddings_index[Word] = Word_vector
        else:
            Word_vector = std_embeddings_index[Word]
        sent_vector = sent_vector + Word_vector

    return sent_vector

def cosine_sim(sent1, sent2):
    return cosineValue(get_sentence_vector(sent1), get_sentence_vector(sent2))

私は与えられた文を実行しましたが、次の結果が見つかりました

s1 = "This is a foo bar sentence ."
s2 = "This sentence is similar to a foo bar sentence ."
s3 = "What is this string ? Totally not related to the other two lines ."

print cosine_sim(s1, s2) # Should give high cosine similarity
print cosine_sim(s1, s3) # Shouldn't give high cosine similarity value
print cosine_sim(s2, s3) # Shouldn't give high cosine similarity value

0.9851735249068168
0.6570885718962608
0.6589335425458225
1

実装してくれてありがとう@vpekarそれは大いに役立ちました。コサインの類似性を計算するときにtf-idfの重みを逃すことがわかりました。 Counter(Word)は、単語のリストとその出現を含む辞書を返します。

cos(q、d)= sim(q、d)=(q・d)/(| q || d |)=(sum(qi、di)/(sqrt(sum(qi2)))*(sqrt( sum(vi2)))ここで、i = 1からv)

  • qiは、クエリ内の用語iのtf-idf重みです。
  • diはtf-idfです
  • ドキュメント内の用語iの重み。 | q |および| d | qとdの長さです。
  • これは、qとdのコサイン類似度です。 。 。 。 。 。または、同等に、qとdの間の角度のコサイン。

私のコードを自由にご覧ください here 。ただし、最初にanacondaパッケージをダウンロードする必要があります。 Windowsでpythonパスを自動的に設定します。このpythonインタープリターをEclipseに追加します。

1
novice_dev

さて、Glove/Word2Vec/Numberbatchのような Wordの埋め込み を知っていれば、あなたの仕事は半分完了です。もしこれに対処する方法を説明させてください。各文をWordトークンに変換し、これらの各トークンを高次元のベクトルとして表します(事前に訓練されたWord埋め込みを使用するか、 train 自分でも!)。したがって、表面の類似性をキャプチャするのではなく、文全体を構成する各単語の意味を抽出するだけです。この後、コサインの類似度を計算すると、設定されます。

1
TheSN