web-dev-qa-db-fra.com

Python: tf-idf-cosin: pour rechercher la similarité d'un document

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

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

à la suite 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é de cosinus, je sais comment implémenter la similarité de cosinus en ce qui concerne deux vecteurs de même longueur, mais je ne suis pas sûr de savoir comment identifier ces deux vecteurs.

79
add-semi-colons

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

Commencez par implémenter une fonction lambda simple pour tenir la formule pour le calcul du cosinus:

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

Ensuite, écrivez une simple boucle for à parcourir sur le vecteur, la logique est la suivante: "Pour chaque vecteur de trainVectorizerArray, vous devez rechercher la similarité 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]]
18
add-semi-colons

Tout d'abord, si vous souhaitez extraire les fonctions de comptage et appliquer la normalisation euclidienne TF-IDF et 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 cosinus d'un document (par exemple, le premier du jeu de données) et de tous les autres, il vous suffit de calculer les produits scalaires du premier vecteur avec tous les autres, car les vecteurs tfidf sont déjà normalisés par lignes. L'API Scipy Sparse Matrix est un peu bizarre (pas aussi flexible que les tableaux de Numpy denses à N dimensions). Pour obtenir le premier vecteur, vous devez découper la matrice par rangée 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 paires (noyaux a.k.a. dans le langage de l'apprentissage automatique) qui fonctionnent pour les représentations à la fois denses et éparses des collections de vecteurs. Dans ce cas, nous avons besoin d’un produit scalaire, également appelé 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])

Par conséquent, pour trouver les 5 principaux documents liés, nous pouvons utiliser argsort et un découpage en tableau négatif (la plupart des documents associés ont les valeurs de similarité de cosinus les plus élevées, donc à la fin du tableau des index 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 requête comme étant le document le plus similaire avec un score de similarité au cosinus de 1, qui contient le texte suivant:

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

Le deuxième document le plus similaire est une réponse qui cite le message d'origine qui contient donc beaucoup de mots communs:

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

Je sais que c'est un vieux post. mais j'ai essayé le package http://scikit-learn.sourceforge.net/stable/ . voici mon code pour trouver la similitude de cosinus. La question était de savoir comment allez-vous calculer la similarité de 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 soit le premier élément de train_set et que doc1, doc2 et doc3 soient les documents que je souhaite 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 pour le part-I , part-II , part-III

la sortie sera comme suit:

[[ 1.          0.07102631  0.02731343  0.06348799]]

ici 1 représente que la requête est associée à elle-même et les trois autres sont les scores pour faire correspondre la requête aux documents respectifs.

19
Gunjan

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

Donc vous avez un list_of_documents qui n'est qu'un tableau de chaînes et un autre document qui n'est qu'une chaîne. Vous devez trouver ce document dans le list_of_documents qui ressemble le plus à document.

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

Commençons par les dépendances. Nous comprendrons 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 pouvant être utilisées est une approche sac de mots , où nous traitons chaque mot du document indépendamment des autres et les jetons tous ensemble dans le grand sac. D'un certain point de vue, il perd beaucoup d'informations (comme la manière dont les mots sont connectés), mais d'un autre point de vue, il simplifie le modèle.

En anglais et dans toute autre langue humaine, il y a beaucoup de mots "inutiles" comme "a", "le", "dans" qui sont si courants qu'ils ne possèdent pas beaucoup de sens. Ils s'appellent mots vides et c'est une bonne idée de les supprimer. Une autre chose que l'on peut remarquer est que les mots comme "analyser", "analyser", "analyser" sont vraiment similaires. Ils ont une racine commune et tous peuvent être convertis en un seul mot. Ce processus s'appelle stemming 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 tiges de mots sans mots vides. De plus, nous éliminons 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 nous aidera-t-il? 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). Nous voyons maintenant que nous avons enlevé beaucoup de mots et en avons utilisé d'autres pour réduire les dimensions des vecteurs. Ici, il y a juste une observation intéressante. Les documents plus longs auront beaucoup plus d'éléments positifs que de courts, c'est pourquoi il est agréable de normaliser le vecteur. C'est ce qu'on appelle le terme de fréquence TF, les gens ont également utilisé des informations supplémentaires sur la fréquence d'utilisation du mot dans d'autres documents - fréquence de document inverse IDF. Ensemble, nous avons une métrique TF-IDF qui a 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)

En fait, le vectoriseur permet de faire beaucoup de choses comme supprimer les mots vides et les minuscules. Je les ai faites dans une étape séparée uniquement parce que sklearn n'a pas de mots vides non anglais, mais nltk en a.

Nous avons donc tous les vecteurs calculés. La dernière étape consiste à trouver laquelle est la plus semblable à la dernière. Il y a différentes manières d'y parvenir, l'une d'elles est la distance euclidienne qui n'est pas si grande pour la raison discuté ici . Une autre approche est similarité en cosinus . Nous itérons tous les documents et calculons la similarité en 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.

16
Salvador Dali

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

Voici une fonction qui compare vos données de test avec les données d’apprentissage, avec le transformateur Tf-Idf équipé des données d’apprentissage. L’avantage est que vous pouvez rapidement faire pivoter ou grouper pour trouver les n éléments les plus proches, et que les calculs sont effectués dans une matrice inférieure.

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
2
Paul Ogier