Python:tf-idf-cosine:ドキュメントの類似性を見つける


90

私はパート1パート2で利用可能なチュートリアルに従っていました。残念ながら、著者には、コサイン類似度を使用して2つのドキュメント間の距離を実際に見つけることを含む最後のセクションの時間はありませんでした。私は記事内の例に従って、stackoverflowからの次のリンクの助けを借りて、上記のリンクで言及されているコードが含まれています(人生を楽にするために)

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
from nltk.corpus import stopwords
import numpy as np
import numpy.linalg as LA

train_set = ["The sky is blue.", "The sun is bright."]  # Documents
test_set = ["The sun in the sky is bright."]  # Query
stopWords = stopwords.words('english')

vectorizer = CountVectorizer(stop_words = stopWords)
#print vectorizer
transformer = TfidfTransformer()
#print transformer

trainVectorizerArray = vectorizer.fit_transform(train_set).toarray()
testVectorizerArray = vectorizer.transform(test_set).toarray()
print 'Fit Vectorizer to train set', trainVectorizerArray
print 'Transform Vectorizer to test set', testVectorizerArray

transformer.fit(trainVectorizerArray)
print
print transformer.transform(trainVectorizerArray).toarray()

transformer.fit(testVectorizerArray)
print 
tfidf = transformer.transform(testVectorizerArray)
print tfidf.todense()

上記のコードの結果として、私は次の行列を持っています

Fit Vectorizer to train set [[1 0 1 0]
 [0 1 0 1]]
Transform Vectorizer to test set [[0 1 1 1]]

[[ 0.70710678  0.          0.70710678  0.        ]
 [ 0.          0.70710678  0.          0.70710678]]

[[ 0.          0.57735027  0.57735027  0.57735027]]

コサイン類似度を計算するためにこの出力を使用する方法がわかりません。長さが類似している2つのベクトルに対してコサイン類似度を実装する方法はわかりますが、ここでは2つのベクトルを識別する方法がわかりません。


3
trainVectorizerArrayの各ベクトルについて、testVectorizerArrayのベクトルとのコサイン類似度を見つける必要があります。
excray

@excrayありがとう、私がなんとか考え出したあなたの有益なポイントで、私は答えを入れるべきですか?
セミコロンを追加する'25

@excrayしかし、私は小さな質問があります。実際のtf * idf計算は、マトリックスに示されている最終結果を使用していないため、これを使用しません。
add-semi-colons 2012

4
ここでは、それが詳細に質問に答える引用チュートリアルの第3部であるpyevolve.sourceforge.net/wordpress/?p=2497を
クレマンルノー

@ClémentRenaud私はあなたが提供したリンクをたどりましたが、私のドキュメントが大きくなると、MemoryErrorがスローされ始めます。
ashim888

回答:


169

まず、カウント特徴を抽出してTF-IDF正規化と行ごとのユークリッド正規化を適用する場合は、次のコマンドを使用して1つの操作で実行できますTfidfVectorizer

>>> from sklearn.feature_extraction.text import TfidfVectorizer
>>> from sklearn.datasets import fetch_20newsgroups
>>> twenty = fetch_20newsgroups()

>>> tfidf = TfidfVectorizer().fit_transform(twenty.data)
>>> tfidf
<11314x130088 sparse matrix of type '<type 'numpy.float64'>'
    with 1787553 stored elements in Compressed Sparse Row format>

1つのドキュメント(データセットの最初のドキュメントなど)と他のすべてのドキュメントの余弦距離を見つけるには、tfidfベクトルが既に行正規化されているため、最初のベクトルと他のすべてのドキュメントの内積を計算する必要があります。

コメントおよびここでのクリスクラークによる説明のとおり、コサイン類似度はベクトルの大きさを考慮していません。行正規化の大きさは1であるため、類似性の値を計算するには線形カーネルで十分です。

scipy sparse matrix APIは少し奇妙です(密なN次元のnumpy配列ほど柔軟ではありません)。最初のベクトルを取得するには、行列を行ごとにスライスして、1行の部分行列を取得する必要があります。

>>> tfidf[0:1]
<1x130088 sparse matrix of type '<type 'numpy.float64'>'
    with 89 stored elements in Compressed Sparse Row format>

scikit-learnはすでに、ベクトルコレクションの密な表現と疎な表現の両方で機能するペアワイズメトリック(別名、機械学習用語ではカーネル)を提供しています。この場合、線形カーネルとも呼ばれるドット積が必要です。

>>> from sklearn.metrics.pairwise import linear_kernel
>>> cosine_similarities = linear_kernel(tfidf[0:1], tfidf).flatten()
>>> cosine_similarities
array([ 1.        ,  0.04405952,  0.11016969, ...,  0.04433602,
    0.04457106,  0.03293218])

したがって、上位5つの関連ドキュメントを見つけるにはargsort、いくつかの負の配列スライシングを使用できます(ほとんどの関連ドキュメントは、コサイン類似度の値が最も高いため、ソートされたインデックス配列の最後にあります)。

>>> related_docs_indices = cosine_similarities.argsort()[:-5:-1]
>>> related_docs_indices
array([    0,   958, 10576,  3277])
>>> cosine_similarities[related_docs_indices]
array([ 1.        ,  0.54967926,  0.32902194,  0.2825788 ])

最初の結果は健全性チェックです。クエリドキュメントは、コサイン類似度スコアが1の最も類似したドキュメントであり、次のテキストが含まれています。

>>> print twenty.data[0]
From: lerxst@wam.umd.edu (where's my thing)
Subject: WHAT car is this!?
Nntp-Posting-Host: rac3.wam.umd.edu
Organization: University of Maryland, College Park
Lines: 15

 I was wondering if anyone out there could enlighten me on this car I saw
the other day. It was a 2-door sports car, looked to be from the late 60s/
early 70s. It was called a Bricklin. The doors were really small. In addition,
the front bumper was separate from the rest of the body. This is
all I know. If anyone can tellme a model name, engine specs, years
of production, where this car is made, history, or whatever info you
have on this funky looking car, please e-mail.

Thanks,
- IL
   ---- brought to you by your neighborhood Lerxst ----

2番目に類似した文書は、元のメッセージを引用する返信であるため、多くの一般的な単語があります。

>>> print twenty.data[958]
From: rseymour@reed.edu (Robert Seymour)
Subject: Re: WHAT car is this!?
Article-I.D.: reed.1993Apr21.032905.29286
Reply-To: rseymour@reed.edu
Organization: Reed College, Portland, OR
Lines: 26

In article <1993Apr20.174246.14375@wam.umd.edu> lerxst@wam.umd.edu (where's my
thing) writes:
>
>  I was wondering if anyone out there could enlighten me on this car I saw
> the other day. It was a 2-door sports car, looked to be from the late 60s/
> early 70s. It was called a Bricklin. The doors were really small. In
addition,
> the front bumper was separate from the rest of the body. This is
> all I know. If anyone can tellme a model name, engine specs, years
> of production, where this car is made, history, or whatever info you
> have on this funky looking car, please e-mail.

Bricklins were manufactured in the 70s with engines from Ford. They are rather
odd looking with the encased front bumper. There aren't a lot of them around,
but Hemmings (Motor News) ususally has ten or so listed. Basically, they are a
performance Ford with new styling slapped on top.

>    ---- brought to you by your neighborhood Lerxst ----

Rush fan?

--
Robert Seymour              rseymour@reed.edu
Physics and Philosophy, Reed College    (NeXTmail accepted)
Artificial Life Project         Reed College
Reed Solar Energy Project (SolTrain)    Portland, OR

追加の質問:非常に多数のドキュメントがある場合、行数に比例するため、ステップ2のlinear_kernel関数はパフォーマンスのボトルネックになる可能性があります。それをサブリニアに減らす方法についての考えはありますか?
Shuo

Elastic SearchとSolrの「これに似た」クエリを使用すると、準線形スケーラビリティプロファイルでおおよその回答が得られます。
ogrisel

7
これにより、最初のドキュメントだけでなく、各ドキュメントと他のすべてのドキュメントとのコサイン類似性が得られますcosine_similarities = linear_kernel(tfidf, tfidf)か?
ionox0 2016年

2
はい、これでペアごとの類似性の正方行列が得られます。
ogrisel

10
TfidfVectorizerが正規化されたベクトルを生成するため、他の人が私と同じように疑問に思っている場合、この場合、linear_kernelはcosine_similarityと同等です。ドキュメントのメモを参照してください:scikit-learn.org/stable/modules/metrics.html#cosine-similarity
Chris Clark

22

@excrayのコメントの助けを借りて、私はなんとか答えを理解することができます。実際に行う必要があるのは、トレーニングデータとテストデータを表す2つの配列を反復処理する単純なforループを作成することです。

最初に、余弦計算の式を保持する単純なラムダ関数を実装します。

cosine_function = lambda a, b : round(np.inner(a, b)/(LA.norm(a)*LA.norm(b)), 3)

そして、単純なforループを記述してtoベクトルを反復するだけです。ロジックは、「trainVectorizerArrayの各ベクトルについて、testVectorizerArrayのベクトルとのコサイン類似性を見つける必要があります」です。

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
from nltk.corpus import stopwords
import numpy as np
import numpy.linalg as LA

train_set = ["The sky is blue.", "The sun is bright."] #Documents
test_set = ["The sun in the sky is bright."] #Query
stopWords = stopwords.words('english')

vectorizer = CountVectorizer(stop_words = stopWords)
#print vectorizer
transformer = TfidfTransformer()
#print transformer

trainVectorizerArray = vectorizer.fit_transform(train_set).toarray()
testVectorizerArray = vectorizer.transform(test_set).toarray()
print 'Fit Vectorizer to train set', trainVectorizerArray
print 'Transform Vectorizer to test set', testVectorizerArray
cx = lambda a, b : round(np.inner(a, b)/(LA.norm(a)*LA.norm(b)), 3)

for vector in trainVectorizerArray:
    print vector
    for testV in testVectorizerArray:
        print testV
        cosine = cx(vector, testV)
        print cosine

transformer.fit(trainVectorizerArray)
print
print transformer.transform(trainVectorizerArray).toarray()

transformer.fit(testVectorizerArray)
print 
tfidf = transformer.transform(testVectorizerArray)
print tfidf.todense()

出力は次のとおりです。

Fit Vectorizer to train set [[1 0 1 0]
 [0 1 0 1]]
Transform Vectorizer to test set [[0 1 1 1]]
[1 0 1 0]
[0 1 1 1]
0.408
[0 1 0 1]
[0 1 1 1]
0.816

[[ 0.70710678  0.          0.70710678  0.        ]
 [ 0.          0.70710678  0.          0.70710678]]

[[ 0.          0.57735027  0.57735027  0.57735027]]

1
いいね。私も最初から学んでいるので、あなたの質問と答えが最もわかりやすい。私はあなたが自分でロールする方法の代わりにnp.corrcoef()を使用できると思います。
wbg 2015年

transformer.fit操作の目的は何tfidf.todense()ですか?ループから類似値を得て、tfidfを続けますか?計算されたコサイン値はどこで使用されますか?あなたの例は混乱しています。
ミネラル

説明しても構わないとしたら、コサインは正確には何を返しますか。あなたの例では、0.4080.816、これらの値は何ですか?
Buydadip

20

私はその古い記事を知っています。しかし、私はhttp://scikit-learn.sourceforge.net/stable/パッケージを試しました。これがコサイン類似度を見つけるための私のコードです。問題は、このパッケージでコサイン類似度をどのように計算するかでした。これがそのための私のコードです。

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.feature_extraction.text import TfidfVectorizer

f = open("/root/Myfolder/scoringDocuments/doc1")
doc1 = str.decode(f.read(), "UTF-8", "ignore")
f = open("/root/Myfolder/scoringDocuments/doc2")
doc2 = str.decode(f.read(), "UTF-8", "ignore")
f = open("/root/Myfolder/scoringDocuments/doc3")
doc3 = str.decode(f.read(), "UTF-8", "ignore")

train_set = ["president of India",doc1, doc2, doc3]

tfidf_vectorizer = TfidfVectorizer()
tfidf_matrix_train = tfidf_vectorizer.fit_transform(train_set)  #finds the tfidf score with normalization
print "cosine scores ==> ",cosine_similarity(tfidf_matrix_train[0:1], tfidf_matrix_train)  #here the first element of tfidf_matrix_train is matched with other three elements

ここで、クエリがtrain_setの最初の要素であり、doc1、doc2とdoc3がコサイン類似性を利用してランク付けしたいドキュメントであると仮定します。次に、このコードを使用できます。

また、質問で提供されたチュートリアルは非常に役に立ちました。ここにすべてのパーツがあります part-Ipart-IIpart-III

出力は次のようになります。

[[ 1.          0.07102631  0.02731343  0.06348799]]

ここで1は、クエリがそれ自体と一致することを表し、他の3つは、クエリをそれぞれのドキュメントと一致させるためのスコアです。


1
cosine_similarity(tfidf_matrix_train [0:1]、tfidf_matrix_train)その1が数千以上に変更された場合はどうなるでしょうか。どうすればそれを処理できますか?
ashim888 2014年

1
処理方法ValueError: Incompatible dimension for X and Y matrices: X.shape[1] == 1664 while Y.shape[1] == 2
pyd

17

私が書いた別のチュートリアルを紹介しましょう。それはあなたの質問に答えますが、なぜ私たちがいくつかのことをしているのかを説明します。私もそれを簡潔にしようとしました。

つまりlist_of_documents、文字列の配列でdocumentあるaと、文字列だけであるaがあります。からlist_of_documents最も類似しているからそのようなドキュメントを見つける必要がありますdocument

それらを一緒に組み合わせましょう: documents = list_of_documents + [document]

依存関係から始めましょう。それぞれを使用する理由が明らかになります。

from nltk.corpus import stopwords
import string
from nltk.tokenize import wordpunct_tokenize as tokenize
from nltk.stem.porter import PorterStemmer
from sklearn.feature_extraction.text import TfidfVectorizer
from scipy.spatial.distance import cosine

使用できるアプローチの1つは、バッグオブワードのアプローチです。このアプローチでは、ドキュメント内の各単語を他の単語から独立して扱い、すべてを大きなバッグに入れます。ある観点からは多くの情報が失われますが(単語がどのように接続されているかなど)、別の観点からはモデルが単純になります。

英語やその他の人間の言語では、「a」、「the」、「in」などの「役に立たない」単語がたくさんあり、あまりにも一般的であるため、あまり意味がありません。これらはストップワードと呼ばれ、削除することをお勧めします。もう1つ気付くのは、「analyze」、「analyzer」、「analysis」のような単語は本当に似ているということです。彼らは共通の根を持っており、すべてを1つの単語に変換することができます。このプロセスはステミングと呼ばれ、速度、攻撃性などが異なるさまざまなステマーが存在します。したがって、各ドキュメントをストップワードなしの語幹のリストに変換します。また、句読点はすべて破棄します。

porter = PorterStemmer()
stop_words = set(stopwords.words('english'))

modified_arr = [[porter.stem(i.lower()) for i in tokenize(d.translate(None, string.punctuation)) if i.lower() not in stop_words] for d in documents]

それでは、この単語の袋はどのように役立ちますか?3つのバッグ[a, b, c]がある[a, c, a]とし[b, c, d]ます。基底で それらをベクトルに変換できます[a, b, c, d]。したがって、最終的には次のベクトル[1, 1, 1, 0][2, 0, 1, 0]なり[0, 1, 1, 1]ます。同様のことが私たちのドキュメントにもあります(ベクトルのみが長くなる方法です)。これで、ベクトルの次元を減らすために、多くの単語を削除し、他の語幹も削除したことがわかります。ここに興味深い観察があります。長いドキュメントは、短いドキュメントよりもポジティブな要素がはるかに多くなるため、ベクトルを正規化するとよいでしょう。これは用語頻度TFと呼ばれ、他のドキュメントで単語が使用される頻度に関する追加情報、つまり逆ドキュメント頻度IDFも使用されました。一緒に、いくつかのフレーバーを持つメトリックTF-IDFがあります。これはsklearnの1行で実現できます:-)

modified_doc = [' '.join(i) for i in modified_arr] # this is only to convert our list of lists to list of strings that vectorizer uses.
tf_idf = TfidfVectorizer().fit_transform(modified_doc)

実際、ベクトライザーストップワードの削除や小文字の使用など、多くのことを実行できます。sklearnに英語以外のストップワードがないので、nltkにはあるので、私はそれらを別のステップで実行しました。

これで、すべてのベクトルが計算されました。最後のステップは、どれが最後のものに最も類似しているかを見つけることです。これを達成する方法はいくつかありますが、そのうちの1つはユークリッド距離です。これは、ここで説明する理由によりそれほど大きくありません。別のアプローチは、コサイン類似度です。すべてのドキュメントを反復処理し、ドキュメントと最後のドキュメント間のコサイン類似度を計算します。

l = len(documents) - 1
for i in xrange(l):
    minimum = (1, None)
    minimum = min((cosine(tf_idf[i].todense(), tf_idf[l + 1].todense()), i), minimum)
print minimum

これで、minimumには最適なドキュメントとそのスコアに関する情報が含まれます。


3
サイン、これはopが求めていたものではありません。コーパス内の「最高のドキュメント」ではなく、クエリを指定して最高のドキュメントを検索します。それをしないでください。私のようなpplは、opタスクの例を使用して時間を無駄にし、マトリックスのサイズ変更の狂気に引き込まれます。
ミネラル

そしてそれはどう違うのですか?考え方はまったく同じです。特徴を抽出し、クエリとドキュメント間のコサイン距離を計算します。
サルバドールダリ

等しい形状の行列でこれを計算しています。別の例を試してください。異なるサイズのクエリ行列があり、演算のトレインセットとテストセットがあります。機能するようにコードを変更できませんでした。
鉱物

@SalvadorDali指摘したように、上記は別の質問に答えます。クエリとドキュメントが同じコーパスの一部であると想定しているため、これは誤りです。これは、同じコーパス(同じ次元を持つ)から導出されたベクトルの距離を使用するという誤ったアプローチにつながります。これは、通常そうである必要はありません。クエリとドキュメントが異なるコーパスに属している場合、それらが生成するベクトルは同じ空間に存在しない可能性があり、上記のように距離を計算しても意味がありません(次元数が同じにならないこともあります)。
gented

12

これはあなたを助けるはずです。

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity  

tfidf_vectorizer = TfidfVectorizer()
tfidf_matrix = tfidf_vectorizer.fit_transform(train_set)
print tfidf_matrix
cosine = cosine_similarity(tfidf_matrix[length-1], tfidf_matrix)
print cosine

出力は次のようになります。

[[ 0.34949812  0.81649658  1.        ]]

9
どのようにして長さを取得しますか?
gogasca 2016

3

Tf-Idfトランスフォーマーをトレーニングデータに適合させて、テストデータをトレーニングデータと比較する関数を次に示します。利点は、n個の最も近い要素を見つけるためにすばやくピボットまたはグループ化できることと、計算が行列方向に下がることです。

def create_tokenizer_score(new_series, train_series, tokenizer):
    """
    return the tf idf score of each possible pairs of documents
    Args:
        new_series (pd.Series): new data (To compare against train data)
        train_series (pd.Series): train data (To fit the tf-idf transformer)
    Returns:
        pd.DataFrame
    """

    train_tfidf = tokenizer.fit_transform(train_series)
    new_tfidf = tokenizer.transform(new_series)
    X = pd.DataFrame(cosine_similarity(new_tfidf, train_tfidf), columns=train_series.index)
    X['ix_new'] = new_series.index
    score = pd.melt(
        X,
        id_vars='ix_new',
        var_name='ix_train',
        value_name='score'
    )
    return score

train_set = pd.Series(["The sky is blue.", "The sun is bright."])
test_set = pd.Series(["The sun in the sky is bright."])
tokenizer = TfidfVectorizer() # initiate here your own tokenizer (TfidfVectorizer, CountVectorizer, with stopwords...)
score = create_tokenizer_score(train_series=train_set, new_series=test_set, tokenizer=tokenizer)
score

   ix_new   ix_train    score
0   0       0       0.617034
1   0       1       0.862012


np.arange(0、len(score))のインデックスの場合:value = score.loc [index、 'score']
ゴールデンライオン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.