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

je suivais un tutoriel qui était disponible sur Part 1 & Part 2 . Malheureusement, l'auteur n'a pas eu le temps pour la dernière section, qui consistait à utiliser la similarité cosinus pour trouver la distance entre deux documents. J'ai suivi les exemples dans l'article à l'aide du lien suivant de stackoverflow , inclus est le code mentionné dans le lien ci-dessus (juste pour rendre la vie plus facile)

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

que fait le 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 suis pas sûr de savoir comment utiliser cette sortie pour calculer la similarité cosinus, je sais comment implémenter la similarité cosinus par rapport à deux vecteurs de longueur similaire mais ici Je ne suis pas sûr de savoir comment identifier les deux vecteurs.

69
demandé sur Veltzer Doron 2012-08-25 06:41:26

6 réponses

tout d'abord, si vous voulez extraire des fonctionnalités de comptage et appliquer la normalisation TF-IDF et la normalisation euclidienne en ligne, 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 cosinus d'un document (par exemple le premier dans l'ensemble de données) et de tous les autres, vous avez juste besoin de calculer les produits de points du premier vecteur avec tous les autres puisque les vecteurs tfidf sont déjà normalisés en ligne. L'API scipy sparse matrix est un peu bizarre (pas aussi flexible que des matrices n-dimensionnelles n-dimensionnelles). Pour obtenir le premier vecteur, vous avez besoin de trancher la ligne de matrice-sage pour obtenir un submatrix 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 mesures en paires(A. K. A. kernels in machine learning Parliament) qui fonctionnent à la fois pour les représentations denses et éparses des collections vectorielles. Dans ce cas, nous avons besoin d'un produit dot qui est é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])

donc à trouvez les 5 premiers documents connexes, nous pouvons utiliser argsort et quelques découpages de tableaux négatifs (la plupart des documents connexes ont les valeurs de similarité 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 bon sens: nous trouvons le document de requête comme le document le plus similaire avec un score de similarité de cosine 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 similaire est une réponse qui cite le message original a donc beaucoup de 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
127
répondu ogrisel 2012-08-27 05:50:43

je sais que c'est un vieux post. mais j'ai essayé le http://scikit-learn.sourceforge.net/stable / paquet . voici mon code pour trouver la similitude cosinus. La question était comment allez-vous calculer la similitude 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 ici que la requête est le premier élément de train_set et doc1, doc2 et doc3 sont les documents que je veux classer à l'aide de la similarité cosinus. alors je peux utilisez ce code.

les tutoriels fournis dans la question étaient également très utiles. Voici toutes les pièces pour la partie I", 151940920" , partie II , partie III

la sortie sera la suivante:

[[ 1.          0.07102631  0.02731343  0.06348799]]

ici 1 représente que la requête est appariée avec lui-même et les trois autres sont les scores pour apparier la requête avec les documents respectifs.

16
répondu Gunjan 2013-09-20 11:59:34

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

donc vous avez un list_of_documents qui est juste un tableau de cordes et un autre document qui est juste une corde. 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. Il deviendra évident pourquoi nous utilisons chacun d'eux.

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

une des approches qui peut être utilisé est un sac-de-mots approche, où nous traitons chaque mot dans le document indépendant des autres et juste jeter tous ensemble dans le grand sac. D'un point de vue, il perd beaucoup d'informations (comme la façon dont les mots sont reliés), mais d'un autre point de vue, il fait la modèle simple.

En anglais et dans toute autre langue humaine, il y a beaucoup de "inutile" des mots comme 'a', 'la', 'en', qui sont si fréquentes qu'elles ne possèdent pas beaucoup de sens. Ils sont appelés mots d'arrêt et il est une bonne idée de les supprimer. Une autre chose qu'on peut remarquer, c'est que des mots comme 'analyser', 'analyseur', 'analyse' sont vraiment similaires. Ils ont une racine commune et tout peut être converti en un seul mot. Ce processus est appelé "stemming " et il existe différents stemmers qui diffèrent par la vitesse, l'agressivité et ainsi de suite. Nous transformons donc chacun des documents en une liste de tiges de mots sans mots d'arrêt. Aussi, nous écartons toute 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 va ce sac de mots nous aider? Imaginez 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 vecteurs: [1, 1, 1, 0] , [2, 0, 1, 0] et [0, 1, 1, 1] . La même chose s'applique à nos documents (seuls les vecteurs seront beaucoup plus longs). Maintenant, nous voyons que nous avons retiré beaucoup de mots et à tiges autres aussi à diminuer les dimensions des vecteurs. Ici, il n'y a qu'une observation intéressante. Les documents plus longs auront beaucoup plus d'éléments positifs que les documents plus courts, c'est pourquoi il est agréable de normaliser le vecteur. Cela s'appelle la fréquence de terme TF, les gens ont également utilisé des informations supplémentaires sur la façon dont souvent le mot est utilisé dans d'autres documents - inverse de la fréquence de document IDF. Ensemble, nous avons un métrique TF-IDF qui ont un couple de saveurs . Cela peut être réalisé avec une 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)

en fait vectorizer permet de faire beaucoup de choses comme supprimer des mots d'arrêt et de minuscules. Je l'ai fait dans une étape séparée seulement parce que sklearn n'a pas de mot d'arrêt non-anglais, mais nltk a.

Donc, nous avons tous les vecteurs calculés. La dernière étape est de trouver lequel est le plus similaire au dernier. Il y a plusieurs façons d'atteindre cela, l'un d'eux est la distance euclidienne qui n'est pas si grande pour la raison discuté ici . Une autre approche est similarité cosinus . Nous répétons tous les documents et calculons la similitude cosinus entre le document et le dernier:

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.

15
répondu Salvador Dali 2015-09-09 07:40:58

à l'aide du commentaire de @excray, j'arrive à trouver la réponse, ce que nous devons faire c'est en fait écrire une boucle simple pour itérer sur les deux tableaux qui représentent les données du train et les données de test.

d'abord mettre en œuvre une fonction lambda simple pour tenir formule pour le calcul cosine:

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

et puis il suffit d'écrire une boucle simple pour itérer sur le vecteur à, la logique est pour chaque " pour chaque vecteur dans trainVectorizerArray,vous devez trouver la similitude de 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 la sortie:

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
répondu Null-Hypothesis 2012-08-25 19:27:53

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 sortie sera:

[[ 0.34949812  0.81649658  1.        ]]
10
répondu Sam 2014-04-04 18:37:00

voici une fonction qui compare vos données d'essai avec les données d'entraînement, avec le transformateur TF-Idf équipé des données d'entraînement. L'avantage est que vous pouvez rapidement pivoter ou grouper pour trouver les N éléments les plus proches, et que les calculs sont vers le bas matrice-Sage.

def create_tokenizer_score(new_series, train_series, tokenizer): """ retourner le score TSF idf de chaque paire possible de documents Args: new_series (pd.Séries): nouvelles données (à comparer avec les données des trains)) train_series (pd.Série): données du train (pour s'adapter au transformateur TF-idf) Retourner: 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 0.617034 1 0 1 0.862012

0
répondu Paul Ogier 2018-09-10 17:05:43