Python: tf-idf-cosine: belge benzerliğini bulmak için


93

Bölüm 1 ve Bölüm 2'de bulunan bir öğreticiyi takip ediyordum . Maalesef yazarın, iki belge arasındaki mesafeyi gerçekten bulmak için kosinüs benzerliğini kullanmayı içeren son bölüm için zamanı yoktu. Stackoverflow'dan aşağıdaki link yardımıyla makaledeki örnekleri takip ettim , yukarıdaki linkte belirtilen kod dahil (sadece hayatı kolaylaştırmak için)

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

Yukarıdaki kodun bir sonucu olarak aşağıdaki matrise sahibim

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

Kosinüs benzerliğini hesaplamak için bu çıktının nasıl kullanılacağından emin değilim, benzer uzunluktaki iki vektöre göre kosinüs benzerliğini nasıl uygulayacağımı biliyorum ama burada iki vektörü nasıl tanımlayacağımı bilmiyorum.


3
TrainVectorizerArray'deki her vektör için, testVectorizerArray'deki vektörle kosinüs benzerliğini bulmanız gerekir.
2012'de 3

@excray Teşekkürler, yardımcı noktanızla bunu çözmeyi başardım, cevabı yazmalı mıyım?
noktalı virgül ekle

@excray Ama küçük bir sorum var, actuall tf * idf hesaplamasının buna bir faydası yok çünkü matriste gösterilen nihai sonuçları kullanmıyorum.
noktalı virgül ekle

4
İşte sorularınızı ayrıntılı olarak yanıtlayan, alıntı yaptığınız öğreticinin 3. kısmı pyevolve.sourceforge.net/wordpress/?p=2497
Clément Renaud

@ ClémentRenaud sağladığınız bağlantı ile takip ettim ama belgelerim büyüdükçe MemoryError atmaya başlıyor Bunu nasıl halledebiliriz?
ashim888

Yanıtlar:


173

Öncelikle, sayım özelliklerini çıkarmak ve TF-IDF normalizasyonu ve satır bazında öklid normalizasyonu uygulamak istiyorsanız, bunu tek bir işlemle yapabilirsiniz 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>

Şimdi, bir belgenin kosinüs mesafelerini (örneğin, veri kümesindeki ilk) ve diğerlerinin tümünü bulmak için, ilk vektörün nokta çarpımlarını diğerlerinin tümü ile hesaplamanız yeterlidir, çünkü tfidf vektörleri zaten satıra göre normalize edilmiştir.

Chris Clark tarafından yorumlarda ve burada açıklandığı gibi Cosine Similarity vektörlerin büyüklüğünü hesaba katmaz. Satır normalleştirilmiş 1 büyüklüğündedir ve bu nedenle Doğrusal Kernel benzerlik değerlerini hesaplamak için yeterlidir.

Scipy sparse matrix API'si biraz tuhaftır (yoğun N-boyutlu uyuşuk diziler kadar esnek değildir). İlk vektörü elde etmek için tek satırlı bir alt matris elde etmek üzere matrisi satır şeklinde dilimlemeniz gerekir:

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

scikit-learn, vektör koleksiyonlarının hem yoğun hem de seyrek temsilleri için çalışan çiftli ölçümler (makine öğreniminde diğer adıyla çekirdekler) sağlar. Bu durumda, doğrusal çekirdek olarak da bilinen bir iç çarpıma ihtiyacımız var:

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

Bu nedenle, ilgili ilk 5 belgeyi bulmak için argsortve bazı negatif dizi dilimlemeyi kullanabiliriz (ilgili belgelerin çoğu en yüksek kosinüs benzerlik değerlerine sahiptir, dolayısıyla sıralanan indeks dizisinin sonunda):

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

İlk sonuç bir akıl sağlığı kontrolüdür: sorgu belgesini, aşağıdaki metne sahip kosinüs benzerlik puanı 1 olan en benzer belge olarak buluruz:

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

En benzer ikinci belge, orijinal mesajı alıntılayan bir cevaptır, bu nedenle birçok ortak kelime vardır:

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

Takip eden bir soru: Çok fazla sayıda belgem varsa, 2. adımdaki linear_kernel işlevi, satır sayısına göre doğrusal olduğu için performans darboğazı olabilir. Alt çizgiye nasıl indirileceğine dair herhangi bir fikrin var mı?
Shuo

Alt doğrusal ölçeklenebilirlik profiliyle yaklaşık yanıtlar vermesi gereken Elastic Search ve Solr'un "buna benzer" sorgularını kullanabilirsiniz.
ogrisel

7
Bu yerine birincinin, her belgeyle size her belgenin kosinüs benzerliği verir misiniz: cosine_similarities = linear_kernel(tfidf, tfidf)?
ionox0

2
Evet, bu size ikili benzerliklerden oluşan bir kare matris verecektir.
ogrisel

10
Başkalarının benim yaptığım gibi merak etmesi durumunda, bu durumda lineer_kernel, kosinüs benzerliğine eşdeğerdir çünkü TfidfVectorizer normalize vektörler üretir. Belgelerdeki nota bakın: scikit-learn.org/stable/modules/metrics.html#cosine-similarity
Chris Clark

22

@ Excray'in yorumunun Yardımıyla, cevabı bulmayı başardım, Yapmamız gereken aslında tren verilerini ve test verilerini temsil eden iki dizi üzerinde yinelemek için basit bir for döngüsü yazmak.

Öncelikle, kosinüs hesaplaması formülünü tutmak için basit bir lambda işlevi uygulayın:

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

Ve sonra vektör üzerinde yinelemek için basit bir for döngüsü yazın, mantık her "trainVectorizerArray'deki her vektör için testVectorizerArray'deki vektörle kosinüs benzerliğini bulmanız gerekir."

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

İşte çıktı:

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
güzel .. Ben de baştan öğreniyorum ve sizin sorunuz ve cevabınız takip etmesi en kolay olanlar. Kendi roll-your-own yönteminiz yerine np.corrcoef () 'i kullanabileceğinizi düşünüyorum.
wbg

transformer.fitOperasyonların amacı nedir ve tfidf.todense()? Döngüden benzerlik değerlerinizi aldınız ve sonra tfidf yapmaya devam mı ediyorsunuz? Hesaplanan kosinüs değeriniz nerede kullanılır? Örneğiniz kafa karıştırıcı.
minerals

Açıklamakta sakınca yoksa kosinüs tam olarak ne dönüyor? Örneğinizde elde edersiniz 0.408ve 0.816bu değerler nelerdir?
buydadip

20

Eski bir gönderi olduğunu biliyorum. ancak http://scikit-learn.sourceforge.net/stable/ paketini denedim . kosinüs benzerliğini bulmak için kodum burada. Soru, bu paketle kosinüs benzerliğini nasıl hesaplayacağınızdı ve işte bunun için kodum

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

Burada, sorgunun train_set'in ilk öğesi olduğunu ve doc1, doc2 ve doc3'ün kosinüs benzerliği yardımıyla sıralamak istediğim belgeler olduğunu varsayalım. o zaman bu kodu kullanabilirim.

Ayrıca soruda verilen eğitimler çok faydalı oldu. İşte bunun için tüm parçalardır yarı I , bölüm II , yarı III

çıktı aşağıdaki gibi olacaktır:

[[ 1.          0.07102631  0.02731343  0.06348799]]

buradaki 1, sorgunun kendisiyle eşleştiğini ve diğer üçü, sorguyu ilgili belgelerle eşleştirme puanlarıdır.


1
cosine_similarity (tfidf_matrix_train [0: 1], tfidf_matrix_train) Ya bu 1 binden fazla olarak değiştirilirse. Bunu nasıl halledebiliriz?
ashim888

1
nasıl idare edilirValueError: Incompatible dimension for X and Y matrices: X.shape[1] == 1664 while Y.shape[1] == 2
pyd

17

Size benim yazdığım başka bir eğitim vereyim. Sorunuzu yanıtlar, aynı zamanda bazı şeyleri neden yaptığımızı da açıklar. Ben de özetlemeye çalıştım.

Yani list_of_documentssadece bir dizi dizisinden oluşan bir diziniz var ve diğeri documentde sadece bir dizge. list_of_documentsEn çok benzeyen belgeden böyle bir belge bulmanız gerekir document.

Onları bir araya getirelim: documents = list_of_documents + [document]

Bağımlılıklarla başlayalım. Her birini neden kullandığımız anlaşılacaktır.

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

Kullanılabilecek yaklaşımlardan biri, belgedeki her kelimeyi diğerlerinden bağımsız olarak ele aldığımız ve sadece hepsini büyük çantaya attığımız bir kelime torbası yaklaşımıdır. Bir bakış açısına göre, pek çok bilgiyi kaybediyor (kelimelerin nasıl bağlandığı gibi), ancak başka bir bakış açısından modeli basitleştiriyor.

İngilizcede ve başka herhangi bir insan dilinde "a", "the", "in" gibi pek çok "yararsız" kelime vardır ve bunlar o kadar yaygındır ki, pek fazla anlamı yoktur. Durdurma kelimeleri olarak adlandırılırlar ve bunları kaldırmak iyi bir fikirdir. Farkına varabileceğiniz bir diğer şey de 'analiz', 'analizci', 'analiz' gibi kelimelerin gerçekten benzer olmasıdır. Ortak bir kökleri vardır ve hepsi tek bir kelimeye dönüştürülebilir. Bu süreç denir edilir kaynaklanan saldırganlık ve benzeri ve hız bakımından farklılık gösteren farklı sözcük köklerini de bulunmaktadır. Bu nedenle, belgelerin her birini durdurma sözcükleri olmadan sözcük kökleri listesine dönüştürüyoruz. Ayrıca tüm noktalama işaretlerini de atıyoruz.

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]

Peki bu kelime çuvalı bize nasıl yardımcı olacak? Biz 3 çanta var düşünün: [a, b, c], [a, c, a]ve [b, c, d]. Bunları temelde vektörlere dönüştürebiliriz [a, b, c, d]. Bu yüzden vektörler ile sona: [1, 1, 1, 0], [2, 0, 1, 0]ve [0, 1, 1, 1]. Aynı şey bizim belgelerimizde de geçerlidir (yalnızca vektörler daha uzun olacaktır). Şimdi, vektörlerin boyutlarını küçültmek için birçok kelimeyi çıkardığımızı ve diğerlerinden sapladığımızı görüyoruz. Burada sadece ilginç bir gözlem var. Daha uzun belgeler, kısadan çok daha fazla pozitif öğeye sahip olacaktır, bu yüzden vektörü normalleştirmek güzeldir. Bu terim sıklığı TF olarak adlandırılır, insanlar ayrıca kelimenin diğer belgelerde ne sıklıkla kullanıldığına dair ek bilgiler kullandılar - ters belge frekansı IDF. Birlikte , birkaç tada sahip metrik bir TF-IDF'ye sahibiz. Bu sklearn'de bir satır ile elde edilebilir :-)

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)

Aslında vektörleştirici , durdurma sözcüklerini kaldırmak ve küçük harfleri küçültmek gibi birçok şeyi yapmaya izin verir . Bunları ayrı bir adımda yaptım çünkü sklearn ingilizce olmayan engellenecek kelimelere sahip değil, nltk'de var.

Böylece tüm vektörleri hesapladık. Son adım, hangisinin bir öncekine en çok benzediğini bulmaktır. Bunu başarmanın çeşitli yolları vardır, bunlardan biri Öklid uzaklığıdır ki burada tartışılan nedenden dolayı o kadar da büyük değildir . Diğer bir yaklaşım kosinüs benzerliğidir . Tüm belgeleri yineliyoruz ve belge ile son belge arasındaki kosinüs benzerliğini hesaplıyoruz:

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

Artık minimum, en iyi belge ve puanı hakkında bilgi sahibi olacak.


3
İşaret, operasyonun istediği şey bu değil: bir külliyatta "en iyi belge" değil, sorgu verilen en iyi dokümanı aramak. Lütfen yapma, benim gibi ppl, op görevi için örneğinizi kullanmaya çalışmakla zaman kaybedecek ve matris yeniden boyutlandırma deliliğine sürüklenecek.
minerals

Ve nasıl farklı? Fikir tamamen aynı. Unsurları çıkarın, sorgu ve belgeler arasındaki kosinüs mesafesini hesaplayın.
Salvador Dali

Bunu eşit şekillerdeki matrisler üzerinde hesaplıyorsunuz, farklı bir örnek deneyin, burada farklı büyüklükte bir sorgu matrisiniz, işlem tren seti ve test seti var. Kodunuzu çalışacak şekilde değiştiremedim.
minerals

@SalvadorDali Belirtildiği gibi, yukarıdaki soru farklı bir soruyu yanıtlıyor: Sorgu ve belgelerin aynı külliyatın parçası olduğunu varsayıyorsunuz, bu da yanlış. Bu, aynı korpustan (aynı boyutlara sahip) türetilen vektörlerin uzaklıklarının kullanılmasına yönelik yanlış yaklaşıma yol açar, ki bu genellikle gerekli değildir. Sorgu ve belgeler farklı kurumlara aitse, oluşturdukları vektörler aynı alanda yaşamayabilir ve yukarıda yaptığınız gibi mesafeleri hesaplamak bir anlam ifade etmeyecektir (hatta aynı boyutlara sahip olmayacaklar).
2017

12

Bu sana yardımcı olmalı.

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

ve çıktı:

[[ 0.34949812  0.81649658  1.        ]]

9
uzunluğu nasıl elde edersiniz?
gogasca

3

Eğitim verileriyle donatılmış Tf-Idf transformatörü ile test verilerinizi eğitim verileriyle karşılaştıran bir işlev. Avantajı, en yakın n elemanı bulmak için hızlı bir şekilde pivot veya gruplama yapabilmeniz ve hesaplamaların matris bazında olmasıdır.

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 (puan)) dizin için: değer = score.loc [dizin, 'puan']
Golden Lion
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.