De Python: tf-idf-cosinus: pour rechercher la similarité de document , il est possible de calculer la similarité de document à l'aide de tf-idf cosinus. Sans importer des bibliothèques externes, existe-t-il un moyen de calculer la similarité de cosinus entre 2 chaînes?
s1 = "This is a foo bar sentence ."
s2 = "This sentence is similar to a foo bar sentence ."
s3 = "What is this string ? Totally not related to the other two lines ."
cosine_sim(s1, s2) # Should give high cosine similarity
cosine_sim(s1, s3) # Shouldn't give high cosine similarity value
cosine_sim(s2, s3) # Shouldn't give high cosine similarity value
Une implémentation simple en pur Python serait:
import re, math
from collections import Counter
Word = re.compile(r'\w+')
def get_cosine(vec1, vec2):
intersection = set(vec1.keys()) & set(vec2.keys())
numerator = sum([vec1[x] * vec2[x] for x in intersection])
sum1 = sum([vec1[x]**2 for x in vec1.keys()])
sum2 = sum([vec2[x]**2 for x in vec2.keys()])
denominator = math.sqrt(sum1) * math.sqrt(sum2)
if not denominator:
return 0.0
else:
return float(numerator) / denominator
def text_to_vector(text):
words = Word.findall(text)
return Counter(words)
text1 = 'This is a foo bar sentence .'
text2 = 'This sentence is similar to a foo bar sentence .'
vector1 = text_to_vector(text1)
vector2 = text_to_vector(text2)
cosine = get_cosine(vector1, vector2)
print 'Cosine:', cosine
Impressions:
Cosine: 0.861640436855
La formule de cosinus utilisée ici est décrite ici .
Cela n'inclut pas la pondération des mots par tf-idf, mais pour utiliser tf-idf, vous devez disposer d'un corpus raisonnablement important à partir duquel estimer les poids de tfidf.
Vous pouvez également le développer davantage en utilisant un moyen plus sophistiqué d’extraire des mots d’un texte, de le lier ou de le lemmatiser, etc.
La réponse courte est "non, il n'est pas possible de le faire d'une manière raisonnée qui fonctionne même très bien". Il s’agit d’un problème non résolu dans la recherche sur le traitement du langage naturel et fait aussi l’objet de mon travail de doctorat. Je vais résumer très brièvement où nous en sommes et vous indiquer quelques publications:
Signification des mots
L’hypothèse la plus importante ici est qu’il est possible d’obtenir un vecteur qui représente chaque mot de la phrase en question. Ce vecteur est généralement choisi pour capturer les contextes dans lesquels le mot peut apparaître. Par exemple, si nous considérons uniquement les trois contextes "manger", "rouge" et "moelleux", le mot "chat" pourrait être représenté par [98, 1 , 87], parce que si vous lisiez un très long texte (quelques milliards de mots n’est pas inhabituel selon la norme actuelle), le mot "chat" apparaît très souvent dans le contexte de "duveteux" et "de manger" , mais pas souvent dans le contexte du "rouge". De la même manière, "chien" pourrait être représenté par [87,2,34] et "parapluie" pourrait être [1,13,0]. En imaginant ces vecteurs comme des points dans l'espace 3D, "chat" est clairement plus proche de "chien" que de "parapluie". Par conséquent, "chat" signifie aussi quelque chose de plus similaire à "chien" à un "parapluie".
Cette ligne de travail a été étudiée depuis le début des années 90 (par exemple ceci travail de Greffenstette) et a donné des résultats étonnamment bons. Par exemple, voici quelques entrées aléatoires dans un thésaurus que j'ai construit récemment en lisant wikipedia sur mon ordinateur:
theory -> analysis, concept, approach, idea, method
voice -> vocal, tone, sound, melody, singing
james -> william, john, thomas, robert, george, charles
Ces listes de mots similaires ont été obtenues entièrement sans intervention humaine. Vous intégrez du texte et revenez quelques heures plus tard.
Le problème avec les phrases
Vous pourriez vous demander pourquoi nous ne faisons pas la même chose pour des phrases plus longues, telles que "renards au gingembre aiment les fruits". C'est parce que nous n'avons pas assez de texte. Pour que nous puissions établir de manière fiable quel type de X est similaire, nous devons voir de nombreux exemples d'utilisation de X dans son contexte. Lorsque X est un mot unique comme "voix", ce n'est pas trop difficile. Cependant, à mesure que X s'allonge, les chances de trouver des occurrences naturelles de X deviennent exponentiellement plus lentes. À titre de comparaison, Google contient environ 1B pages contenant le mot "renard" et pas une seule page contenant "rennes gingembre, fruits d'amour", alors qu'il s'agit d'une phrase anglaise parfaitement valide et que nous comprenons tous ce que cela signifie.
Composition
Pour résoudre le problème de la fragmentation des données, nous souhaitons effectuer une composition, c’est-à-dire prendre des vecteurs de mots faciles à obtenir à partir de texte réel et les mettre ensemble de manière à en saisir le sens. La mauvaise nouvelle est que personne n’a été capable de le faire jusqu’à présent.
La méthode la plus simple et la plus évidente consiste à ajouter ou à multiplier les vecteurs Word individuels. Cela conduit à un effet secondaire indésirable que "chats chassent les chiens" et "chiens chassent les chats" signifierait la même chose pour votre système. De plus, si vous multipliez, vous devez faire très attention, sinon toutes les phrases finiront par être représentées par [0,0,0, ..., 0], ce qui annulera le point.
Lectures supplémentaires
Je ne discuterai pas des méthodes plus sophistiquées de composition qui ont été proposées jusqu'à présent. Je vous suggère de lire Katrin Erk "Modèles spatiaux vectoriels du sens de Word et sens de la phrase: une enquête" . C'est une très bonne enquête de haut niveau pour vous aider à démarrer. Malheureusement, n'étant pas librement disponible sur le site Web de l'éditeur, envoyez un courrier électronique directement à l'auteur pour obtenir une copie. Dans cet article, vous trouverez des références à de nombreuses méthodes plus concrètes. Les plus compréhensibles sont de Mitchel et Lapata (2008) et Baroni et Zamparelli (2010) .
Éditer après commentaire par @vpekar: Le but de cette réponse est de souligner le fait que même si des méthodes naïves existent (par exemple addition, multiplication, similarité de surface, etc.), elles sont fondamentalement défectueuses et en général, il ne faut pas s’attendre à de grandes performances de leur part.
Merci @vpekar pour votre implémentation. Cela m'a beaucoup aidé. Je viens de constater qu’il manque le poids tf-idf lors du calcul de la similarité cosinus . Le compteur (Word) renvoie un dictionnaire qui contient la liste des mots avec leur occurrence.
cos (q, d) = sim (q, d) = (q. d)/(| q || d |) = (somme (qi, di)/(sqrt (somme (qi2))) * (sqrt ( somme (vi2))) où i = 1 à v)
S'il vous plaît n'hésitez pas à voir mon code ici . Mais vous devez d'abord télécharger le package anaconda. Il va automatiquement définir votre chemin python dans Windows. Ajoutez cet interpréteur python dans Eclipse.
Eh bien, si vous êtes au courant de incorporations de Word comme Glove/Word2Vec/Numberbatch, votre travail est à moitié fait. Si ce n'est pas le cas, laissez-moi vous expliquer comment résoudre ce problème. Convertissez chaque phrase en jetons Word et représentez chacun d’eux en tant que vecteurs de grande dimension (à l’aide des incorporations Word pré-entraînées, ou vous pourriez les former vous-même!). Donc, maintenant, vous ne capturez pas simplement leur similarité de surface, mais vous extrayez plutôt le sens de chaque mot qui comprend la phrase dans son ensemble. Après cela, calculez leur similitude en cosinus et vous êtes prêt.
Essaye ça. Téléchargez le fichier 'numberbatch-fr-17.06.txt' depuis https://conceptnet.s3.amazonaws.com/downloads/2017/numberbatch/numberbatch-en-17.06.txt.gz et extrayez-le. La fonction 'get_sentence_vector' utilise une simple somme de vecteurs Word. Cependant, il peut être amélioré en utilisant une somme pondérée où les poids sont proportionnels à Tf-Idf de chaque mot.
import math
import numpy as np
std_embeddings_index = {}
with open('path/to/numberbatch-en-17.06.txt') as f:
for line in f:
values = line.split(' ')
Word = values[0]
embedding = np.asarray(values[1:], dtype='float32')
std_embeddings_index[Word] = embedding
def cosineValue(v1,v2):
"compute cosine similarity of v1 to v2: (v1 dot v2)/{||v1||*||v2||)"
sumxx, sumxy, sumyy = 0, 0, 0
for i in range(len(v1)):
x = v1[i]; y = v2[i]
sumxx += x*x
sumyy += y*y
sumxy += x*y
return sumxy/math.sqrt(sumxx*sumyy)
def get_sentence_vector(sentence, std_embeddings_index = std_embeddings_index ):
sent_vector = 0
for Word in sentence.lower().split():
if Word not in std_embeddings_index :
Word_vector = np.array(np.random.uniform(-1.0, 1.0, 300))
std_embeddings_index[Word] = Word_vector
else:
Word_vector = std_embeddings_index[Word]
sent_vector = sent_vector + Word_vector
return sent_vector
def cosine_sim(sent1, sent2):
return cosineValue(get_sentence_vector(sent1), get_sentence_vector(sent2))
J'ai couru pour les phrases données et j'ai trouvé les résultats suivants
s1 = "This is a foo bar sentence ."
s2 = "This sentence is similar to a foo bar sentence ."
s3 = "What is this string ? Totally not related to the other two lines ."
print cosine_sim(s1, s2) # Should give high cosine similarity
print cosine_sim(s1, s3) # Shouldn't give high cosine similarity value
print cosine_sim(s2, s3) # Shouldn't give high cosine similarity value
0.9851735249068168
0.6570885718962608
0.6589335425458225