Python: TF-idf-cosine: aby znaleźć podobieństwo dokumentów

Śledziłem tutorial, który był dostępny w Część 1 & Część 2 . Niestety autor nie miał czasu na ostatnią część, która polegała na użyciu cosinusowego podobieństwa, aby rzeczywiście znaleźć odległość między dwoma dokumentami. Za przykładami w artykule posłużyłem się poniższym linkiem z stackoverflow , w tym kod wymieniony w powyższym linku (tak, aby ułatwić życie)

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

W wyniku powyższego kodu mam następująca macierz

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

Nie jestem pewien, jak użyć tego wyjścia do obliczenia cosinus podobieństwa, wiem, jak zaimplementować cosinus podobieństwa w odniesieniu do dwóch wektorów o podobnej długości, ale tutaj nie jestem pewien, jak zidentyfikować dwa wektory.

Author: Veltzer Doron, 2012-08-25

6 answers

Po pierwsze, jeśli chcesz wyodrębnić funkcje liczenia i zastosować normalizację TF-IDF i normalizację euklidesową, możesz to zrobić w jednej operacji za pomocą 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>

Teraz, aby znaleźć odległości cosinusowe jednego dokumentu (np. pierwszego w zbiorze danych) i wszystkich innych, wystarczy obliczyć produkty kropkowe pierwszego wektora ze wszystkimi innymi, ponieważ wektory tfidf są już znormalizowane. API scipy sparse matrix jest trochę dziwne (nie tak elastyczne jak gęste N-wymiarowe tablice numpy). Aby uzyskać pierwszy wektor, musisz przeciąć rząd macierzy, aby uzyskać podzbiór z jednym rzędem:

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

Scikit-learn dostarcza już pairwise metrics (Alias jądra w uczeniu maszynowym), które działają zarówno dla gęstych, jak i rzadkich reprezentacji zbiorów wektorowych. W tym przypadku potrzebujemy iloczynu kropkowego, który jest również znany jako jądro liniowe:

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

Stąd, aby znaleźć top 5 powiązanych dokumentów, możemy użyć argsort i pewnej tablicy ujemnej krojenie (większość powiązanych dokumentów ma najwyższe wartości cosinusowego podobieństwa, stąd na końcu posortowanej tablicy indeksów):

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

Pierwszy wynik to sprawdzenie rozsądku: znajdujemy dokument zapytania jako najbardziej podobny dokument z wynikiem podobieństwa cosinusowego 1, który ma następujący tekst:

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

Drugim najbardziej podobnym dokumentem jest odpowiedź cytująca oryginalną wiadomość, stąd ma wiele wspólnych słów:

>>> 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 <[email protected]> [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
 127
Author: ogrisel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-27 05:50:43

Wiem, że to stary post. ale próbowałem http://scikit-learn.sourceforge.net/stable / Pakiet. Oto Mój kod, aby znaleźć cosinusowe podobieństwo. Pytanie brzmiało jak obliczysz cosinusowe podobieństwo z tym pakietem i oto mój kod do tego

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

Tutaj Załóżmy, że zapytanie jest pierwszym elementem train_set i doc1, doc2 i doc3 są dokumentami, które chcę uszeregować za pomocą podobieństwa cosinusowego. więc mogę użyć tego kodu.

Również tutoriale podane w pytaniu było bardzo przydatne. Oto wszystkie części do niego część I,Część II,Część III

Wynik będzie następujący:

[[ 1.          0.07102631  0.02731343  0.06348799]]

Tutaj 1 oznacza, że zapytanie jest dopasowane do siebie, a pozostałe trzy To wyniki za dopasowanie zapytania do odpowiednich dokumentów.

 16
Author: Gunjan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-20 11:59:34

Pozwól, że dam ci kolejny tutorial napisany przeze mnie. Odpowiada na twoje pytanie, ale także wyjaśnia, dlaczego robimy niektóre rzeczy. Starałem się też, żeby była zwięzła.

Więc masz list_of_documents, który jest tylko tablicą łańcuchów i inny document, który jest tylko łańcuchem. Musisz znaleźć taki dokument z list_of_documents, który jest najbardziej podobny do document.

Połączmy je razem: documents = list_of_documents + [document]

Zacznijmy od zależności. Stanie się jasne, dlaczego używamy każdy z nich.

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

Jednym z podejść, które można zastosować, jest podejście bag-of-words , w którym traktujemy każde słowo w dokumencie niezależnie od innych i po prostu wrzucamy je wszystkie razem do big bag. Z jednego punktu widzenia traci wiele informacji (na przykład jak słowa są połączone), ale z innego punktu widzenia sprawia, że model jest prosty.

W języku angielskim i w każdym innym ludzkim języku jest wiele "bezużytecznych" słów, takich jak "a", "the", "IN", które są tak powszechne, że nie mają dużego znaczenia. Są one nazywane stop słowa{[26] } i jest to dobry pomysł, aby je usunąć. Inną rzeczą, którą można zauważyć, jest to, że słowa takie jak "analizator", "analizator", "analiza" są naprawdę podobne. Mają wspólny korzeń i wszystkie mogą być zamienione na jedno słowo. Proces ten nazywa się stemming i istnieją różne stemmery, które różnią się szybkością, agresywnością i tak dalej. Przekształcamy więc każdy z dokumentów w listę słowa bez słów stop. Również odrzucamy wszystkie znaki interpunkcyjne.

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]
W jaki sposób pomoże nam ten worek słów? Wyobraź sobie, że mamy 3 torby: [a, b, c], [a, c, a] i [b, c, d]. Możemy je przekonwertować na wektory w bazie [a, b, c, d]. Więc kończymy z wektorami: [1, 1, 1, 0], [2, 0, 1, 0] i [0, 1, 1, 1]. Podobnie jest z naszymi Dokumentami(tylko Wektory będą znacznie dłuższe). Teraz widzimy, że usunęliśmy wiele słów i wprowadziliśmy Inne również w celu zmniejszenia wymiarów wektorów. Tutaj jest tylko ciekawa obserwacja. Dłuższe dokumenty będą miały o wiele więcej elementów dodatnich niż krótszych, dlatego warto znormalizować wektor. Nazywa się to terminem częstotliwość TF, ludzie używali również dodatkowych informacji o tym, jak często słowo jest używane w innych dokumentach - inverse document frequency IDF. Razem mamy metrykę TF-IDF, która ma kilka smaków. Można to osiągnąć za pomocą jednej linii w sklepie: -)
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)

Właściwie vectorizer pozwala na wiele rzeczy jak usuwanie słów stop i małe litery. Zrobiłem je w osobnym kroku tylko dlatego, że sklearn nie ma stoperów nieanglojęzycznych, ale nltk ma.

Więc mamy wszystkie wektory obliczone. Ostatnim krokiem jest znalezienie, który z nich jest najbardziej podobny do poprzedniego. Istnieją różne sposoby, aby to osiągnąć, jednym z nich jest odległość euklidesowa, która nie jest tak wielka z powodu omawianego tutaj . Innym podejściem jest cosinus podobieństwa. We iterate all the dokumenty i obliczanie cosinusowego podobieństwa między dokumentem a ostatnim:

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

Teraz minimum będzie miało informacje o najlepszym dokumencie i jego wyniku.

 15
Author: Salvador Dali,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-09 07:40:58

Z pomocą komentarza @ excray, udaje mi się znaleźć odpowiedź, co musimy zrobić, to napisać prostą pętlę for, aby iterację nad dwiema tablicami, które reprezentują dane pociągu i dane testowe.

Najpierw zaimplementuj prostą funkcję lambda do przechowywania wzoru na obliczenie cosinusa:

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

A potem po prostu napisz prostą pętlę for, aby iterację nad wektorem to, logika jest dla każdego " dla każdego wektora wektorowego, musisz znaleźć podobieństwo cosinusa z wektorem w 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()

Oto wyjście:

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]]
 14
Author: Null-Hypothesis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-25 19:27:53

To powinno ci pomóc.

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

I wyjście będzie:

[[ 0.34949812  0.81649658  1.        ]]
 10
Author: Sam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-04 18:37:00

Oto funkcja, która porównuje dane testowe z danymi treningowymi, z transformatorem TF-Idf wyposażonym w dane treningowe. Zaletą jest to, że można szybko obracać lub grupować, aby znaleźć N najbliższych elementów, i że obliczenia są w dół macierzy mądry.

Def create_tokenizer_score(new_series, train_series, tokenizer): """ zwraca wynik TF idf każdej możliwej pary dokumentów Args: new_series (pd.Seria): nowe dane( do porównanie z danymi dotyczącymi pociągów) train_series (pd.Seria): dane pociągu( pasujące do transformatora TF-idf) Zwroty: 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

 0
Author: Paul Ogier,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-10 17:05:43