2つのテキストドキュメント間の類似度を計算する方法


207

任意のプログラミング言語でのNLPプロジェクトに取り組んでいます(ただし、Pythonが私の好みです)。

私は2つの文書を取り、それらがどの程度類似しているかを判断したいと思います。


1
ここでの同様の質問がstackoverflow.com/questions/101569/...の利いた答え魔女

回答:


292

これを行う一般的な方法は、ドキュメントをTF-IDFベクトルに変換し、それらの間のコサイン類似度を計算することです。情報検索(IR)に関するあらゆるテキストがこれをカバーしています。特に参照してください。無料でオンラインで入手できる情報検索の紹介

ペアワイズ類似度の計算

TF-IDF(および同様のテキスト変換)は、PythonパッケージGensimおよびscikit-learnに実装されています。後者のパッケージでは、コサイン類似度の計算は次のように簡単です

from sklearn.feature_extraction.text import TfidfVectorizer

documents = [open(f) for f in text_files]
tfidf = TfidfVectorizer().fit_transform(documents)
# no need to normalize, since Vectorizer will return normalized tf-idf
pairwise_similarity = tfidf * tfidf.T

または、ドキュメントがプレーンな文字列の場合、

>>> corpus = ["I'd like an apple", 
...           "An apple a day keeps the doctor away", 
...           "Never compare an apple to an orange", 
...           "I prefer scikit-learn to Orange", 
...           "The scikit-learn docs are Orange and Blue"]                                                                                                                                                                                                   
>>> vect = TfidfVectorizer(min_df=1, stop_words="english")                                                                                                                                                                                                   
>>> tfidf = vect.fit_transform(corpus)                                                                                                                                                                                                                       
>>> pairwise_similarity = tfidf * tfidf.T 

Gensimはこの種のタスクのためのより多くのオプションを持っているかもしれませんが。

この質問も参照してください。

[免責事項:scikit-learn TF-IDFの実装に関与しました。]

結果の解釈

上から、行と列の数がコーパス内のドキュメントの数に等しい、正方形のpairwise_similarityScipy スパース行列です。

>>> pairwise_similarity                                                                                                                                                                                                                                      
<5x5 sparse matrix of type '<class 'numpy.float64'>'
    with 17 stored elements in Compressed Sparse Row format>

.toarray()またはを使用して、スパース配列をNumPy配列に変換できます.A

>>> pairwise_similarity.toarray()                                                                                                                                                                                                                            
array([[1.        , 0.17668795, 0.27056873, 0.        , 0.        ],
       [0.17668795, 1.        , 0.15439436, 0.        , 0.        ],
       [0.27056873, 0.15439436, 1.        , 0.19635649, 0.16815247],
       [0.        , 0.        , 0.19635649, 1.        , 0.54499756],
       [0.        , 0.        , 0.16815247, 0.54499756, 1.        ]])

「scikit-learnのドキュメントはオレンジとブルーです」という最終的なドキュメントに最も類似したドキュメントを見つけたいとしましょう。このドキュメントのインデックスは4ですcorpusその行のargmaxを取得することで最も類似したドキュメントのインデックスを見つけることができますが、最初に1をマスクする必要があります。これは、各ドキュメント自体との類似性を表します。後者はからnp.fill_diagonal()、前者はnp.nanargmax()

>>> import numpy as np     

>>> arr = pairwise_similarity.toarray()     
>>> np.fill_diagonal(arr, np.nan)                                                                                                                                                                                                                            

>>> input_doc = "The scikit-learn docs are Orange and Blue"                                                                                                                                                                                                  
>>> input_idx = corpus.index(input_doc)                                                                                                                                                                                                                      
>>> input_idx                                                                                                                                                                                                                                                
4

>>> result_idx = np.nanargmax(arr[input_idx])                                                                                                                                                                                                                
>>> corpus[result_idx]                                                                                                                                                                                                                                       
'I prefer scikit-learn to Orange'

注:スパースマトリックスを使用する目的は、大規模なコーパスと語彙を節約することです(相当量のスペース)。NumPy配列に変換する代わりに、次のようにすることができます。

>>> n, _ = pairwise_similarity.shape                                                                                                                                                                                                                         
>>> pairwise_similarity[np.arange(n), np.arange(n)] = -1.0
>>> pairwise_similarity[input_idx].argmax()                                                                                                                                                                                                                  
3

1
@larsmans可能であれば、配列について少し説明してください。この配列をどのように読み取ればよいですか。最初の2列は最初の2つの文の類似性ですか?
add-semi-colons 2012

1
@Null仮説:位置(i、j)で、ドキュメントiとドキュメントjの間の類似度スコアを見つけます。したがって、(0,2)の位置は、最初のドキュメントと3番目のドキュメントの間の類似性の値(ゼロベースのインデックスを使用)です。これは、コサイン類似性が交換可能であるため、(2,0)で見つかるのと同じ値です。
Fred Foo

1
1の対角線の外側にあるすべての値を平均するとしたら、それは4つのドキュメントが互いにどの程度類似しているかの単一のスコアを取得するための適切な方法でしょうか?そうでない場合、複数のドキュメント間の全体的な類似性を判断するより良い方法はありますか?
user301752

2
@ user301752:tf-idfベクトルの要素ごとの平均(k-meansの場合と同様)をX.mean(axis=0)使用して、平均から最大値/中央値(∗)のユークリッド距離を計算できます。(※)好きな方を選んでください。
Fred Foo

1
@curious:サンプルコードを現在のscikit-learn APIに更新しました。新しいコードを試してみてください。
Fred Foo

87

@larsmanと同じですが、いくつかの前処理あり

import nltk, string
from sklearn.feature_extraction.text import TfidfVectorizer

nltk.download('punkt') # if necessary...


stemmer = nltk.stem.porter.PorterStemmer()
remove_punctuation_map = dict((ord(char), None) for char in string.punctuation)

def stem_tokens(tokens):
    return [stemmer.stem(item) for item in tokens]

'''remove punctuation, lowercase, stem'''
def normalize(text):
    return stem_tokens(nltk.word_tokenize(text.lower().translate(remove_punctuation_map)))

vectorizer = TfidfVectorizer(tokenizer=normalize, stop_words='english')

def cosine_sim(text1, text2):
    tfidf = vectorizer.fit_transform([text1, text2])
    return ((tfidf * tfidf.T).A)[0,1]


print cosine_sim('a little bird', 'a little bird')
print cosine_sim('a little bird', 'a little bird chirps')
print cosine_sim('a little bird', 'a big dog barks')

@ルノー、本当に良いと明確な答え!私には2つの疑問があります:I)tfidf * tfidf.Tの後に組み込む[0,1]とは何か、およびII)逆ドキュメント頻度は、すべての記事または2つだけから形成されます(2つ以上あると見なします) ?
Economist_Ayahuasca

2
@AndresAzqueta [0,1]は、2つのテキスト入力が2x2対称行列を作成するため、類似性の行列内の位置です。
フィリップベルクストローム2016年

1
@Renaud、完全なコードをありがとう。nltk.download()を要求するエラーが発生した場合は、nltk.download( 'punkt')を簡単に実行できます。すべてをダウンロードする必要はありません。
2016年

@Renaudこれ以上根本的な問題はありません。これは、テキストの文字列べきfit、とされtransform
ジョンストラット

@JohnStrood私はあなたの質問を理解していません、申し訳ありませんが再定式化できますか?
ルノー

45

それは古い質問ですが、これはSpacyで簡単にできることがわかりました。ドキュメントが読み取られると、シンプルなAPI similarityを使用して、ドキュメントベクトル間のコサイン類似度を見つけることができます。

import spacy
nlp = spacy.load('en')
doc1 = nlp(u'Hello hi there!')
doc2 = nlp(u'Hello hi there!')
doc3 = nlp(u'Hey whatsup?')

print doc1.similarity(doc2) # 0.999999954642
print doc2.similarity(doc3) # 0.699032527716
print doc1.similarity(doc3) # 0.699032527716

2
なぜdoc1とdoc2の類似性が1.0ではなく0.999999954642であるのか
JordanBelf

4
@JordanBelf浮動小数点数は、デジタル表現で無制限の精度を持つことができないため、ほとんどの言語で少し動き回ります。たとえば、無理数の浮動小数点演算や無理数を生成すると、常に小さな丸め誤差が発生し、そこで乗算されます。これは、スケールの面でのこのような柔軟な表現の欠点です。
scipilot 2017

2
この場合、相似法が使用する距離関数は何ですか?
ikel

「en」の検索に問題がある場合は、次のpip install spacy && python -m spacy download en
Cyber​​netic


17

一般に、2つのドキュメント間のコサイン類似度は、ドキュメントの類似度として使用されます。Javaでは、Lucene(コレクションがかなり大きい場合)またはLingPipeを使用してこれを行うことができます。基本的な概念は、すべてのドキュメントの用語を数え、用語ベクトルのドット積を計算することです。ライブラリは、この一般的なアプローチに対していくつかの改善を提供します。たとえば、ドキュメントの逆頻度を使用したり、tf-idfベクトルを計算したりします。copmlexを実行する場合、LingPipeは、ドキュメント間のLSA類似度を計算する方法も提供します。これにより、コサイン類似度よりも優れた結果が得られます。Pythonの場合、NLTKを使用できます。


4
「LSAの類似性」がないことに注意してください。LSAは、ベクトル空間の次元数を減らす方法です(高速化するため、または用語ではなくトピックをモデル化するため)。BOWおよびtf-idfで使用されるのと同じ類似性メトリックをLSAで使用できます(コサイン類似性、ユークリッド類似性、BM25など)。
Witiko 2017

16

非常に正確なものを探している場合は、tf-idfよりも優れたツールを使用する必要があります。ユニバーサルセンテンスエンコーダーは、任意の2つのテキスト間の類似性を見つけるための最も正確なものの1つです。Googleは、独自のアプリケーションで使用できる事前トレーニング済みのモデルを提供しており、ゼロからトレーニングする必要はありません。まず、テンソルフローとテンソルフローハブをインストールする必要があります。

    pip install tensorflow
    pip install tensorflow_hub

以下のコードでは、任意のテキストを固定長のベクトル表現に変換し、ドット積を使用してそれらの間の類似性を見つけることができます

import tensorflow_hub as hub
module_url = "https://tfhub.dev/google/universal-sentence-encoder/1?tf-hub-format=compressed"

# Import the Universal Sentence Encoder's TF Hub module
embed = hub.Module(module_url)

# sample text
messages = [
# Smartphones
"My phone is not good.",
"Your cellphone looks great.",

# Weather
"Will it snow tomorrow?",
"Recently a lot of hurricanes have hit the US",

# Food and health
"An apple a day, keeps the doctors away",
"Eating strawberries is healthy",
]

similarity_input_placeholder = tf.placeholder(tf.string, shape=(None))
similarity_message_encodings = embed(similarity_input_placeholder)
with tf.Session() as session:
    session.run(tf.global_variables_initializer())
    session.run(tf.tables_initializer())
    message_embeddings_ = session.run(similarity_message_encodings, feed_dict={similarity_input_placeholder: messages})

    corr = np.inner(message_embeddings_, message_embeddings_)
    print(corr)
    heatmap(messages, messages, corr)

そしてプロットのためのコード:

def heatmap(x_labels, y_labels, values):
    fig, ax = plt.subplots()
    im = ax.imshow(values)

    # We want to show all ticks...
    ax.set_xticks(np.arange(len(x_labels)))
    ax.set_yticks(np.arange(len(y_labels)))
    # ... and label them with the respective list entries
    ax.set_xticklabels(x_labels)
    ax.set_yticklabels(y_labels)

    # Rotate the tick labels and set their alignment.
    plt.setp(ax.get_xticklabels(), rotation=45, ha="right", fontsize=10,
         rotation_mode="anchor")

    # Loop over data dimensions and create text annotations.
    for i in range(len(y_labels)):
        for j in range(len(x_labels)):
            text = ax.text(j, i, "%.2f"%values[i, j],
                           ha="center", va="center", color="w", 
fontsize=6)

    fig.tight_layout()
    plt.show()

結果は次のようになります。 テキストのペア間の類似度行列

ご覧のとおり、最も類似しているのは、テキスト自体とテキストの意味が近いテキストです。

重要:モデルをダウンロードする必要があるため、コードを初めて実行するときは遅くなります。モデルが再度ダウンロードされてローカルモデルが使用されないようにする場合は、キャッシュ用のフォルダーを作成して環境変数に追加し、初めて実行した後にそのパスを使用する必要があります。

tf_hub_cache_dir = "universal_encoder_cached/"
os.environ["TFHUB_CACHE_DIR"] = tf_hub_cache_dir

# pointing to the folder inside cache dir, it will be unique on your system
module_url = tf_hub_cache_dir+"/d8fbeb5c580e50f975ef73e80bebba9654228449/"
embed = hub.Module(module_url)

詳細:https : //tfhub.dev/google/universal-sentence-encoder/2


こんにちはTFを試すように勧めるこの例に感謝します-オブジェクト "np"はどこから来るべきですか?
Open Food Broker、

1
UPD OK、私はnumpy、matplotlib、およびプロット用のシステムTK Pythonバインディングをインストールしました、そしてそれは動作します!!
Open Food Broker、

1
念のため(改行がないため申し訳ありません):tfをtfとしてインポートtensorflow_hubをハブとしてインポートmatplotlib.pyplotをpltとしてインポートnumpyをnp
dinnouti

5

ここにあなたを始めるための小さなアプリがあります...

import difflib as dl

a = file('file').read()
b = file('file1').read()

sim = dl.get_close_matches

s = 0
wa = a.split()
wb = b.split()

for i in wa:
    if sim(i, wb):
        s += 1

n = float(s) / float(len(wa))
print '%d%% similarity' % int(n * 100)

4
多数のドキュメントを処理する場合、difflibは非常に遅くなります。
Phyo Arkar Lwin 2012

2

コサインドキュメントの類似性については、このオンラインサービスを試してみてください。http://www.scurtu.it/documentSimilarity.html

import urllib,urllib2
import json
API_URL="http://www.scurtu.it/apis/documentSimilarity"
inputDict={}
inputDict['doc1']='Document with some text'
inputDict['doc2']='Other document with some text'
params = urllib.urlencode(inputDict)    
f = urllib2.urlopen(API_URL, params)
response= f.read()
responseObject=json.loads(response)  
print responseObject

Apiは差分順次マッチャーを使用していますか?yesの場合は、Pythonでシンプルな機能は、仕事をするだろう____________________________________ difflibインポートからはSequenceMatcherデフisStringSimilar(A、B):比=はSequenceMatcher(なし、a、b)は.ratio()の戻り率______________________________
Rudresh Ajgaonkar

2

2つのテキストの意味的類似性の測定に関心がある場合は、このgitlabプロジェクトをご覧になることをお勧めします。サーバーとして実行することもできます。また、2つのテキストの類似性を簡単に測定できる事前作成されたモデルもあります。ほとんどの場合、2つの文の類似性を測定するようにトレーニングされていますが、ケースで使用することもできます。Javaで記述されていますが、RESTfulサービスとして実行できます。

別のオプションもDKProの類似性です。これは、テキストの類似性を測定するためのさまざまなアルゴリズムを備えたライブラリです。ただし、Javaでも記述されています。

コード例:

// this similarity measure is defined in the dkpro.similarity.algorithms.lexical-asl package
// you need to add that to your .pom to make that example work
// there are some examples that should work out of the box in dkpro.similarity.example-gpl 
TextSimilarityMeasure measure = new WordNGramJaccardMeasure(3);    // Use word trigrams

String[] tokens1 = "This is a short example text .".split(" ");   
String[] tokens2 = "A short example text could look like that .".split(" ");

double score = measure.getSimilarity(tokens1, tokens2);

System.out.println("Similarity: " + score);

2

非常に少ないデータセットで文の類似性を見つけ、高精度を得るには、事前トレーニング済みのBERTモデルを使用している以下のpythonパッケージを使用できます。

pip install similar-sentences

私はそれを試してみましたが、各文と1つの主要な文との類似性が得られますが、すべてのセンテンス.txtトレーニングデータを1つのクラスとして作成し、すべての例に一致する信頼度のスコアを取得する方法はありますか?
グルテジャ

1
はい、できます。.batch_predict(BatchFile、NumberOfPrediction)を試してください。出力は列を含むResults.xlsとして出力されます['Sentence'、 'Suggestion'、 'Score']
Shankar Ganesh Jayaraman

1

構文の類似性について類似性を検出するには、3つの簡単な方法があります。

  • Word2Vec
  • グローブ
  • Tfidfまたはcountvectorizer

意味的類似性の場合、BERT埋め込みを使用して別の単語プーリング戦略を試し、ドキュメントの埋め込みを取得してから、ドキュメントの埋め込みにコサイン類似性を適用できます。

高度な方法論では、BERT SCOREを使用して類似性を取得できます。 BERT SCORE

研究論文リンク: https //arxiv.org/abs/1904.09675

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.