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