112 votes

Python : tf-idf-cosine : pour trouver la similarité des documents

Je suivais un tutoriel disponible à l'adresse suivante Partie 1 & Partie 2 . Malheureusement, l'auteur n'a pas eu le temps de s'occuper de la dernière partie, qui consistait à utiliser la similarité en cosinus pour déterminer la distance entre deux documents. J'ai suivi les exemples dans l'article avec l'aide du lien suivant de stackoverflow Pour vous faciliter la tâche, nous avons inclus le code mentionné dans le lien ci-dessus.

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

comme résultat du code ci-dessus, j'ai la matrice suivante

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

Je ne sais pas comment utiliser cette sortie pour calculer la similarité du cosinus, je sais comment mettre en œuvre la similarité du cosinus par rapport à deux vecteurs de longueur similaire mais ici je ne sais pas comment identifier les deux vecteurs.

3 votes

Pour chaque vecteur de trainVectorizerArray, vous devez trouver la similarité en cosinus avec le vecteur de testVectorizerArray.

0 votes

@excray Merci, avec votre aide j'ai réussi à comprendre, dois-je mettre la réponse ?

0 votes

@excray Mais j'ai une petite question, en fait le calcul tf*idf n'est pas utile pour cela, car je n'utilise pas les résultats finaux qui sont montrés dans la matrice.

197voto

ogrisel Points 13211

Tout d'abord, si vous voulez extraire des caractéristiques de comptage et appliquer la normalisation TF-IDF et la normalisation euclidienne par rangée, vous pouvez le faire en une seule opération avec 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>

Maintenant, pour trouver les distances en cosinus d'un document (par exemple, le premier de l'ensemble de données) et de tous les autres, il suffit de calculer les produits scalaires du premier vecteur avec tous les autres, car les vecteurs tfidf sont déjà normalisés par ligne.

Comme l'explique Chris Clark dans les commentaires et ici La similitude en cosinus ne tient pas compte de la magnitude des vecteurs. Les vecteurs normalisés en ligne ont une magnitude de 1. Le noyau linéaire est donc suffisant pour calculer les valeurs de similarité.

L'API des matrices éparses de scipy est un peu bizarre (pas aussi flexible que les tableaux denses à N dimensions de numpy). Pour obtenir le premier vecteur, vous devez découper la matrice en lignes pour obtenir une sous-matrice avec une seule ligne :

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

scikit-learn fournit déjà des métriques par paire (aussi appelées noyaux dans le jargon de l'apprentissage automatique) qui fonctionnent pour les représentations denses et éparses des collections de vecteurs. Dans ce cas, nous avons besoin d'un produit scalaire, également connu sous le nom de noyau linéaire :

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

Ainsi, pour trouver les 5 premiers documents liés, nous pouvons utiliser argsort et un découpage négatif du tableau (les documents les plus liés ont les valeurs de similarité en cosinus les plus élevées, donc à la fin du tableau des indices triés) :

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

Le premier résultat est un contrôle de cohérence : nous trouvons le document de la requête comme le document le plus similaire avec un score de similarité cosinusoïdale de 1 qui a le texte suivant :

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

Le deuxième document le plus similaire est une réponse qui cite le message original et comporte donc de nombreux mots communs :

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

0 votes

Une question complémentaire : si j'ai un très grand nombre de documents, la fonction linear_kernel de l'étape 2 peut être le goulot d'étranglement des performances, car elle est linéaire par rapport au nombre de lignes. Avez-vous des idées sur la façon de réduire cette fonction à une fonction sous-linéaire ?

0 votes

Vous pouvez utiliser les requêtes "more like this" d'Elastic Search et de Solr qui devraient donner des réponses approximatives avec un profil d'évolutivité sub-linéaire.

9 votes

Cela vous donnerait-il la similarité en cosinus de chaque document avec tous les autres documents, au lieu du premier seulement ? cosine_similarities = linear_kernel(tfidf, tfidf) ?

25voto

Null-Hypothesis Points 1455

Avec l'aide du commentaire de @excray, j'ai réussi à trouver la réponse. Ce que nous devons faire est d'écrire une simple boucle for pour itérer sur les deux tableaux qui représentent les données de formation et les données de test.

Tout d'abord, implémentez une simple fonction lambda pour contenir la formule du calcul du cosinus :

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

Et ensuite écrire une simple boucle for pour itérer sur le vecteur to, la logique est la suivante pour chaque "Pour chaque vecteur dans trainVectorizerArray, vous devez trouver la similarité en cosinus avec le vecteur dans 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()

Voici le résultat :

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 votes

J'apprends aussi depuis le début et votre question et votre réponse sont les plus faciles à suivre. Je pense que vous pouvez utiliser np.corrcoef() à la place de votre méthode de roulage.

0 votes

Quel est l'objectif de la transformer.fit les opérations et tfidf.todense() ? Vous obtenez vos valeurs de similarité à partir de la boucle et continuez ensuite à faire le tfidf ? Où la valeur du cosinus calculée est-elle utilisée ? Votre exemple est confus.

0 votes

Qu'est-ce que le retour du cosinus, si vous voulez bien m'expliquer. Dans votre exemple, vous obtenez 0.408 et 0.816 Quelles sont ces valeurs ?

21voto

Gunjan Points 255

Je sais que c'est un vieux poste, mais j'ai essayé le programme http://scikit-learn.sourceforge.net/stable/ Voici mon code pour trouver la similarité du cosinus. La question était de savoir comment calculer la similarité du cosinus avec ce paquet et voici mon code pour cela.

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

Supposons que la requête est le premier élément de train_set et que doc1, doc2 et doc3 sont les documents que je veux classer à l'aide de la similarité en cosinus. alors je peux utiliser ce code.

Les tutoriels fournis dans la question étaient également très utiles. Voici toutes les parties de la question partie I , partie-II , partie-III

le résultat sera le suivant :

[[ 1.          0.07102631  0.02731343  0.06348799]]

Ici, 1 représente que la requête est appariée avec elle-même et les trois autres sont les scores pour la correspondance de la requête avec les documents respectifs.

1 votes

Cosinus_similarité(tfidf_matrix_train[0:1], tfidf_matrix_train) Et si ce 1 est changé en plus de milliers. Comment pouvons-nous gérer cela ?

1 votes

Comment gérer ValueError: Incompatible dimension for X and Y matrices: X.shape[1] == 1664 while Y.shape[1] == 2

17voto

Salvador Dali Points 11667

Laissez-moi vous donner un autre tutoriel écrit par moi. Il répond à votre question, mais explique également pourquoi nous faisons certaines choses. J'ai également essayé de le rendre concis.

Donc vous avez un list_of_documents qui est juste un tableau de chaînes de caractères et un autre document qui n'est qu'une chaîne de caractères. Vous devez trouver un tel document à partir du list_of_documents qui est le plus similaire à document .

Combinons-les ensemble : documents = list_of_documents + [document]

Commençons par les dépendances. Nous verrons plus tard pourquoi nous utilisons chacune d'entre elles.

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

L'une des approches qui peut être utilisée est une sac de mots Cette approche consiste à traiter chaque mot du document indépendamment des autres et à les mettre tous ensemble dans un grand sac. D'un certain point de vue, on perd beaucoup d'informations (comme la façon dont les mots sont connectés), mais d'un autre point de vue, cela rend le modèle simple.

En anglais et dans toute autre langue humaine, il existe un grand nombre de mots "inutiles" tels que "a", "the", "in", qui sont si courants qu'ils ne possèdent pas beaucoup de sens. On les appelle mots vides et c'est une bonne idée de les enlever. Une autre chose que l'on peut remarquer est que des mots comme "analyse", "analyseur", "analyse" sont vraiment similaires. Ils ont une racine commune et peuvent tous être convertis en un seul mot. Ce processus s'appelle à partir de et il existe différents "stemmers" qui diffèrent par leur vitesse, leur agressivité, etc. Nous transformons donc chacun des documents en une liste de racines de mots sans mots d'arrêt. Nous éliminons également toute la ponctuation.

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]

Alors comment ce sac de mots va-t-il nous aider ? Imaginons que nous ayons 3 sacs : [a, b, c] , [a, c, a] et [b, c, d] . Nous pouvons les convertir en vecteurs dans la base [a, b, c, d] . On se retrouve donc avec des vecteurs : [1, 1, 1, 0] , [2, 0, 1, 0] et [0, 1, 1, 1] . La même chose se passe avec nos documents (seuls les vecteurs seront beaucoup plus longs). Maintenant, nous voyons que nous avons supprimé beaucoup de mots et que nous en avons corrigé d'autres pour diminuer la dimension des vecteurs. Il y a ici une observation intéressante. Les documents les plus longs auront beaucoup plus d'éléments positifs que les plus courts, c'est pourquoi il est intéressant de normaliser le vecteur. C'est ce qu'on appelle la fréquence des termes TF, on utilise également des informations supplémentaires sur la fréquence d'utilisation du mot dans d'autres documents - la fréquence inverse des documents IDF. Ensemble, nous avons une métrique TF-IDF, qui ont plusieurs variantes . Ceci peut être réalisé avec une seule ligne dans 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)

Vectorisateur actuel permet de faire beaucoup de choses comme la suppression des mots vides et la mise en minuscule. Je les ai fait dans une étape séparée seulement parce que sklearn n'a pas de mots d'arrêt non-anglais, mais nltk en a.

Nous avons donc calculé tous les vecteurs. La dernière étape consiste à trouver celui qui est le plus similaire au précédent. Il y a plusieurs façons d'y parvenir, l'une d'entre elles est la distance euclidienne, qui n'est pas très bonne pour la raison suivante discuté ici . Une autre approche consiste à similarité cosinus . Nous itérons tous les documents et calculons la similarité en cosinus entre le document et le dernier document :

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

Maintenant, minimum aura des informations sur le meilleur document et son score.

3 votes

Signe, ce n'est pas ce qu'op demandait : rechercher le meilleur document pour une requête donnée, pas "le meilleur document" dans un corpus. S'il vous plaît ne le faites pas, des gens comme moi vont perdre du temps à essayer d'utiliser votre exemple pour la tâche de l'op et se laisser entraîner dans la folie du redimensionnement de la matrice.

0 votes

Et en quoi est-ce différent ? L'idée est tout à fait la même. Extraire des caractéristiques, calculer la distance cosinus entre une requête et des documents.

0 votes

Vous calculez ceci sur des matrices de formes égales, essayez un exemple différent, où vous avez une matrice de requête qui est de taille différente, op's train set et test set. Je n'ai pas été capable de modifier votre code pour qu'il fonctionne.

14voto

Sam Points 54

Cela devrait vous aider.

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

et la sortie sera :

[[ 0.34949812  0.81649658  1.        ]]

14 votes

Comment obtient-on la longueur ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X