Python: tf-idf-cosine: untuk mencari kesamaan dokumen

93

Saya mengikuti tutorial yang tersedia di Bagian 1 & Bagian 2 . Sayangnya penulis tidak memiliki waktu untuk bagian terakhir yang menggunakan kesamaan kosinus untuk benar-benar menemukan jarak antara dua dokumen. Saya mengikuti contoh dalam artikel dengan bantuan tautan berikut dari stackoverflow , termasuk kode yang disebutkan di tautan di atas (hanya untuk membuat hidup lebih mudah)

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()

sebagai hasil dari kode di atas saya memiliki matriks berikut

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]]

Saya tidak yakin bagaimana menggunakan output ini untuk menghitung kesamaan kosinus, saya tahu bagaimana menerapkan kesamaan kosinus sehubungan dengan dua vektor dengan panjang yang sama tetapi di sini saya tidak yakin bagaimana mengidentifikasi dua vektor.

tambahkan titik koma
sumber
3
Untuk setiap vektor di trainVectorizerArray, Anda harus menemukan kesamaan kosinus dengan vektor di testVectorizerArray.
keluarkan
@excray Terima kasih, dengan poin yang sangat membantu saya berhasil menemukan jawabannya, haruskah saya memberikan jawabannya?
tambahkan titik koma
@excray Tapi saya punya pertanyaan kecil, sebenarnya perhitungan tf * idf tidak berguna untuk ini, karena saya tidak menggunakan hasil akhir yang ditampilkan dalam matriks.
tambahkan titik koma
4
Berikut adalah bagian ke-3 dari tutorial yang Anda kutip yang menjawab pertanyaan Anda secara rinci pyevolve.sourceforge.net/wordpress/?p=2497
Clément Renaud
@ ClémentRenaud saya mengikuti dengan tautan yang Anda berikan tetapi karena dokumen saya lebih besar, ia mulai membuang MemoryError Bagaimana kita bisa mengatasinya?
ashim888

Jawaban:

173

Pertama, jika Anda ingin mengekstrak fitur penghitungan dan menerapkan normalisasi TF-IDF dan normalisasi euclidean berdasarkan baris, Anda dapat melakukannya dalam satu operasi dengan 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>

Sekarang untuk mencari jarak cosinus dari satu dokumen (misalnya yang pertama dalam dataset) dan yang lainnya, Anda hanya perlu menghitung perkalian titik dari vektor pertama dengan yang lainnya karena vektor tfidf sudah dinormalisasi baris.

Seperti dijelaskan oleh Chris Clark dalam komentar dan di sini Cosine Similarity tidak memperhitungkan besarnya vektor. Baris dinormalisasi memiliki besaran 1 sehingga Linear Kernel cukup untuk menghitung nilai kesamaan.

API matriks jarang scipy agak aneh (tidak sefleksibel array numpy dimensi-N yang padat). Untuk mendapatkan vektor pertama, Anda perlu mengiris baris matriks untuk mendapatkan submatriks dengan satu baris:

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

scikit-learn sudah menyediakan metrik berpasangan (alias kernel dalam bahasa machine learning) yang berfungsi untuk representasi koleksi vektor yang padat dan jarang. Dalam hal ini kita membutuhkan perkalian titik yang juga dikenal sebagai kernel linier:

>>> 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])

Karenanya untuk menemukan 5 dokumen terkait teratas, kita dapat menggunakan argsort dan beberapa pemotongan array negatif (sebagian besar dokumen terkait memiliki nilai kesamaan kosinus tertinggi, maka di akhir array indeks yang diurutkan):

>>> 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 ])

Hasil pertama adalah pemeriksaan kewarasan: kami menemukan dokumen kueri sebagai dokumen yang paling mirip dengan skor kesamaan kosinus 1 yang memiliki teks berikut:

>>> print twenty.data[0]
From: [email protected] (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 ----

Dokumen kedua yang paling mirip adalah balasan yang mengutip pesan asli sehingga memiliki banyak kata yang sama:

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

In article <1993Apr20.174246.14375@wam.umd.edu> [email protected] (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              [email protected]
Physics and Philosophy, Reed College    (NeXTmail accepted)
Artificial Life Project         Reed College
Reed Solar Energy Project (SolTrain)    Portland, OR
ogrisel
sumber
Sebuah pertanyaan tindak lanjut: jika saya memiliki jumlah dokumen yang sangat besar, fungsi linear_kernel pada langkah 2 dapat menjadi penghambat kinerja, karena ini linier terhadap jumlah baris. Ada pemikiran tentang cara menguranginya menjadi sublinier?
Shuo
Anda dapat menggunakan kueri "lainnya seperti ini" dari Penelusuran Elastis dan Solr yang akan menghasilkan perkiraan jawaban dengan profil skalabilitas sub-linier.
ogrisel
7
Apakah ini memberikan kesamaan kosinus dari setiap dokumen dengan setiap dokumen lainnya, bukan hanya yang pertama: cosine_similarities = linear_kernel(tfidf, tfidf)?
ionox0
2
Ya, ini akan memberi Anda matriks persegi dari persamaan berpasangan.
ogrisel
10
Jika orang lain bertanya-tanya seperti yang saya lakukan, dalam hal ini linear_kernel setara dengan cosine_similarity karena TfidfVectorizer menghasilkan vektor yang dinormalisasi. Lihat catatan di dokumen: scikit-learn.org/stable/modules/metrics.html#cosine-similarity
Chris Clark
22

Dengan Bantuan komentar @ excray, saya berhasil menemukan jawabannya, Apa yang perlu kita lakukan sebenarnya adalah menulis loop for sederhana untuk mengulangi dua array yang mewakili data kereta dan data pengujian.

Pertama, terapkan fungsi lambda sederhana untuk menampung rumus untuk perhitungan kosinus:

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

Dan kemudian tulis saja loop for sederhana untuk mengulang ke vektor ke, logikanya adalah untuk setiap "Untuk setiap vektor di trainVectorizerArray, Anda harus menemukan kesamaan kosinus dengan vektor di 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()

Inilah hasilnya:

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]]
tambahkan titik koma
sumber
1
bagus .. Saya juga belajar dari awal dan pertanyaan serta jawaban Anda adalah yang paling mudah diikuti. Saya pikir Anda dapat menggunakan np.corrcoef () sebagai gantinya metode roll-your-own.
wbg
Apa tujuan transformer.fitoperasi dan tfidf.todense()? Anda mendapatkan nilai kesamaan Anda dari loop dan kemudian melanjutkan melakukan tfidf? Di mana nilai kosinus terhitung Anda digunakan? Teladan Anda membingungkan.
mineral
Apa sebenarnya kosinus yang kembali jika Anda tidak keberatan menjelaskannya. Dalam contoh Anda, Anda mendapatkan 0.408dan 0.816, apakah nilai-nilai ini?
buydadip
20

Saya tahu ini posting lama. tetapi saya mencoba paket http://scikit-learn.sourceforge.net/stable/ . berikut adalah kode saya untuk menemukan kesamaan kosinus. Pertanyaannya adalah bagaimana Anda menghitung kemiripan kosinus dengan paket ini dan inilah kode saya untuk itu

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

Di sini misalkan kueri adalah elemen pertama dari train_set dan doc1, doc2 dan doc3 adalah dokumen yang ingin saya rangking dengan bantuan cosine similarity. maka saya dapat menggunakan kode ini.

Juga tutorial yang diberikan dalam pertanyaan itu sangat berguna. Ini semua bagian untuk itu bagian-I , bagian-II , bagian-III

outputnya adalah sebagai berikut:

[[ 1.          0.07102631  0.02731343  0.06348799]]

di sini 1 menunjukkan bahwa kueri cocok dengan dirinya sendiri dan tiga lainnya adalah skor untuk mencocokkan kueri dengan dokumen terkait.

Gunjan
sumber
1
cosine_similarity (tfidf_matrix_train [0: 1], tfidf_matrix_train) Bagaimana jika 1 itu diubah menjadi lebih dari ribuan. Bagaimana kita bisa mengatasinya ??
ashim888
1
bagaimana menanganiValueError: Incompatible dimension for X and Y matrices: X.shape[1] == 1664 while Y.shape[1] == 2
pyd
17

Izinkan saya memberi Anda tutorial lain yang saya tulis. Ini menjawab pertanyaan Anda, tetapi juga menjelaskan mengapa kami melakukan beberapa hal. Saya juga mencoba membuatnya ringkas.

Jadi Anda memiliki list_of_documentsyang hanya berupa larik string dan lainnya documentyang hanya berupa string. Anda perlu menemukan dokumen seperti itu dari list_of_documentsyang paling mirip dengan document.

Mari kita gabungkan keduanya: documents = list_of_documents + [document]

Mari kita mulai dengan dependensi. Ini akan menjadi jelas mengapa kami menggunakan masing-masing.

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

Salah satu pendekatan yang bisa digunakan adalah kantong kata-kata , di mana kita memperlakukan setiap kata dalam dokumen secara terpisah dan hanya memasukkan semuanya ke dalam tas besar. Dari satu sudut pandang, ia kehilangan banyak informasi (seperti bagaimana kata-kata itu dihubungkan), tetapi dari sudut pandang lain, modelnya menjadi sederhana.

Dalam bahasa Inggris dan bahasa manusia lainnya ada banyak kata "tidak berguna" seperti 'a', 'the', 'in' yang sangat umum sehingga tidak memiliki banyak arti. Mereka disebut kata - kata berhenti dan merupakan ide yang baik untuk menghapusnya. Hal lain yang bisa diperhatikan adalah kata-kata seperti 'analyzer', 'analyzer', 'analysis' sangat mirip. Mereka memiliki akar yang sama dan semuanya dapat diubah menjadi hanya satu kata. Proses ini disebut stemming dan terdapat perbedaan stemmer yang berbeda dalam kecepatan, agresivitas dan sebagainya. Jadi kami mengubah setiap dokumen menjadi daftar batang kata tanpa kata henti. Juga kami membuang semua tanda baca.

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]

Jadi bagaimana tas berisi kata-kata ini akan membantu kita? Bayangkan kita memiliki 3 tas: [a, b, c], [a, c, a]dan [b, c, d]. Kita dapat mengonversinya menjadi vektor dalam basis [a, b, c, d] . Jadi kita berakhir dengan vektor: [1, 1, 1, 0], [2, 0, 1, 0]dan [0, 1, 1, 1]. Hal yang sama juga terjadi pada dokumen kita (hanya vektor yang lebih panjang). Sekarang kita melihat bahwa kita menghapus banyak kata dan menangkis lainnya juga untuk mengurangi dimensi vektor. Di sini hanya ada pengamatan menarik. Dokumen yang lebih panjang akan memiliki lebih banyak elemen positif daripada yang lebih pendek, itulah mengapa bagus untuk menormalkan vektor. Ini disebut TF frekuensi istilah, orang juga menggunakan informasi tambahan tentang seberapa sering kata tersebut digunakan dalam dokumen lain - IDF frekuensi dokumen terbalik. Bersama-sama kami memiliki metrik TF-IDF yang memiliki beberapa rasa. Ini dapat dicapai dengan satu baris di sklearn :-)

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)

Sebenarnya vectorizer memungkinkan untuk melakukan banyak hal seperti menghapus kata-kata berhenti dan huruf kecil. Saya telah melakukannya dalam langkah terpisah hanya karena sklearn tidak memiliki stopwords non-Inggris, tetapi nltk memilikinya.

Jadi kita memiliki semua vektor yang dihitung. Langkah terakhir adalah menemukan mana yang paling mirip dengan yang terakhir. Ada berbagai cara untuk mencapainya, salah satunya adalah jarak Euclidean yang tidak terlalu jauh untuk alasan yang dibahas disini . Pendekatan lain adalah kesamaan kosinus . Kami mengulangi semua dokumen dan menghitung kesamaan kosinus antara dokumen dan yang terakhir:

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

Sekarang minimum akan memiliki informasi tentang dokumen terbaik dan nilainya.

Salvador Dali
sumber
3
Tanda, ini bukan yang diminta op: mencari dokumen terbaik yang diberi kueri bukan "dokumen terbaik" dalam korpus. Tolong jangan lakukan itu, ppl seperti saya akan membuang waktu untuk mencoba menggunakan contoh Anda untuk tugas operasi dan terseret ke dalam kegilaan mengubah ukuran matriks.
mineral
Dan apa bedanya? Idenya sama sekali. Ekstrak fitur, hitung jarak kosinus antara kueri dan dokumen.
Salvador Dali
Anda menghitung ini pada matriks dengan bentuk yang sama, coba contoh lain, di mana Anda memiliki matriks kueri dengan ukuran berbeda, set rangkaian operasi, dan set pengujian. Saya tidak dapat mengubah kode Anda agar dapat berfungsi.
mineral
@SalvadorDali Seperti yang ditunjukkan, di atas menjawab pertanyaan yang berbeda: Anda mengasumsikan bahwa kueri dan dokumen adalah bagian dari korpus yang sama, yang mana salah. Hal ini mengarah pada pendekatan yang salah dalam menggunakan jarak vektor yang diturunkan dari korpus yang sama (dengan dimensi yang sama), yang umumnya tidak perlu demikian. Jika kueri dan dokumen milik korpora yang berbeda, vektor asalnya mungkin tidak tinggal di ruang yang sama dan menghitung jarak seperti yang Anda lakukan di atas tidak masuk akal (mereka bahkan tidak akan memiliki jumlah dimensi yang sama).
Tuan
12

Ini akan membantu Anda.

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

dan keluarannya adalah:

[[ 0.34949812  0.81649658  1.        ]]
Sam
sumber
9
bagaimana Anda mendapatkan panjang?
gogasca
3

Berikut adalah fungsi yang membandingkan data pengujian Anda dengan data pelatihan, dengan transformator Tf-Idf yang dilengkapi dengan data pelatihan. Keuntungannya adalah Anda dapat dengan cepat melakukan pivot atau mengelompokkan untuk menemukan n elemen terdekat, dan perhitungannya turun berdasarkan matriks.

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
Paul Ogier
sumber
pandas.pydata.org/pandas-docs/stable/reference/api/… menjelaskan apa yang dilakukan pd.melt
Golden Lion
untuk indeks di np.arange (0, len (skor)): nilai = skor.loc [indeks, 'skor']
Singa Emas