Comment calculer la similitude entre deux documents texte?

207

Je cherche à travailler sur un projet NLP, dans n'importe quel langage de programmation (bien que Python sera ma préférence).

Je veux prendre deux documents et déterminer leur similitude.

Reily Bourne
la source
1
Question similaire ici stackoverflow.com/questions/101569/… avec de jolies réponses

Réponses:

292

La manière courante de procéder consiste à transformer les documents en vecteurs TF-IDF, puis à calculer la similitude en cosinus entre eux. Tout manuel sur la recherche d'informations (IR) couvre cela. Voir esp.Introduction à la recherche d'informations , qui est gratuite et disponible en ligne.

Calcul des similitudes par paire

TF-IDF (et des transformations de texte similaires) sont implémentés dans les packages Python Gensim et scikit-learn . Dans ce dernier package, le calcul des similitudes cosinus est aussi simple que

from sklearn.feature_extraction.text import TfidfVectorizer

documents = [open(f) for f in text_files]
tfidf = TfidfVectorizer().fit_transform(documents)
# no need to normalize, since Vectorizer will return normalized tf-idf
pairwise_similarity = tfidf * tfidf.T

ou, si les documents sont des chaînes simples,

>>> corpus = ["I'd like an apple", 
...           "An apple a day keeps the doctor away", 
...           "Never compare an apple to an orange", 
...           "I prefer scikit-learn to Orange", 
...           "The scikit-learn docs are Orange and Blue"]                                                                                                                                                                                                   
>>> vect = TfidfVectorizer(min_df=1, stop_words="english")                                                                                                                                                                                                   
>>> tfidf = vect.fit_transform(corpus)                                                                                                                                                                                                                       
>>> pairwise_similarity = tfidf * tfidf.T 

mais Gensim peut avoir plus d'options pour ce genre de tâche.

Voir aussi cette question .

[Avertissement: J'ai participé à la mise en œuvre de TF-IDF scikit-learn.]

Interprétation des résultats

De dessus, pairwise_similarityest une matrice clairsemée Scipy qui est de forme carrée, avec le nombre de lignes et de colonnes égal au nombre de documents dans le corpus.

>>> pairwise_similarity                                                                                                                                                                                                                                      
<5x5 sparse matrix of type '<class 'numpy.float64'>'
    with 17 stored elements in Compressed Sparse Row format>

Vous pouvez convertir le tableau fragmenté en tableau NumPy via .toarray()ou .A:

>>> pairwise_similarity.toarray()                                                                                                                                                                                                                            
array([[1.        , 0.17668795, 0.27056873, 0.        , 0.        ],
       [0.17668795, 1.        , 0.15439436, 0.        , 0.        ],
       [0.27056873, 0.15439436, 1.        , 0.19635649, 0.16815247],
       [0.        , 0.        , 0.19635649, 1.        , 0.54499756],
       [0.        , 0.        , 0.16815247, 0.54499756, 1.        ]])

Disons que nous voulons trouver le document le plus similaire au document final, "Les documents scikit-learn sont Orange et Bleu". Ce document a un index de 4 po corpus. Vous pouvez trouver l'index du document le plus similaire en prenant l'argmax de cette ligne, mais vous devrez d'abord masquer les 1, qui représentent la similitude de chaque document avec lui-même . Vous pouvez faire ce dernier à travers np.fill_diagonal(), et le premier à travers np.nanargmax():

>>> import numpy as np     

>>> arr = pairwise_similarity.toarray()     
>>> np.fill_diagonal(arr, np.nan)                                                                                                                                                                                                                            

>>> input_doc = "The scikit-learn docs are Orange and Blue"                                                                                                                                                                                                  
>>> input_idx = corpus.index(input_doc)                                                                                                                                                                                                                      
>>> input_idx                                                                                                                                                                                                                                                
4

>>> result_idx = np.nanargmax(arr[input_idx])                                                                                                                                                                                                                
>>> corpus[result_idx]                                                                                                                                                                                                                                       
'I prefer scikit-learn to Orange'

Remarque: le but de l'utilisation d'une matrice clairsemée est d'économiser (une quantité substantielle d'espace) pour un grand corpus et vocabulaire. Au lieu de convertir en tableau NumPy, vous pouvez faire:

>>> n, _ = pairwise_similarity.shape                                                                                                                                                                                                                         
>>> pairwise_similarity[np.arange(n), np.arange(n)] = -1.0
>>> pairwise_similarity[input_idx].argmax()                                                                                                                                                                                                                  
3
Fred Foo
la source
1
@larsmans Pouvez-vous expliquer le tableau si possible, comment dois-je lire ce tableau. Les deux premières colonnes sont la similitude entre les deux premières phrases?
add-semi-colons
1
@ Hypothèse nulle: à la position (i, j), vous trouvez le score de similitude entre le document i et le document j. Ainsi, à la position (0,2) se trouve la valeur de similitude entre le premier document et le troisième (en utilisant une indexation à base zéro), qui est la même valeur que vous trouvez à (2,0), car la similitude en cosinus est commutative.
Fred Foo
1
Si je devais faire la moyenne de toutes les valeurs en dehors de la diagonale de 1, serait-ce un bon moyen d'obtenir un score unique de la similitude des quatre documents entre eux? Sinon, existe-t-il un meilleur moyen de déterminer la similitude globale entre plusieurs documents?
user301752
2
@ user301752: vous pouvez prendre la moyenne élémentaire des vecteurs tf-idf (comme le ferait k-means) avec X.mean(axis=0), puis calculer la distance euclidienne moyenne / maximale / médiane (∗) à partir de cette moyenne. (∗) Choisissez celui qui vous convient.
Fred Foo
1
@curious: J'ai mis à jour l'exemple de code vers l'API scikit-learn actuelle; vous voudrez peut-être essayer le nouveau code.
Fred Foo
88

Identique à @larsman, mais avec un certain prétraitement

import nltk, string
from sklearn.feature_extraction.text import TfidfVectorizer

nltk.download('punkt') # if necessary...


stemmer = nltk.stem.porter.PorterStemmer()
remove_punctuation_map = dict((ord(char), None) for char in string.punctuation)

def stem_tokens(tokens):
    return [stemmer.stem(item) for item in tokens]

'''remove punctuation, lowercase, stem'''
def normalize(text):
    return stem_tokens(nltk.word_tokenize(text.lower().translate(remove_punctuation_map)))

vectorizer = TfidfVectorizer(tokenizer=normalize, stop_words='english')

def cosine_sim(text1, text2):
    tfidf = vectorizer.fit_transform([text1, text2])
    return ((tfidf * tfidf.T).A)[0,1]


print cosine_sim('a little bird', 'a little bird')
print cosine_sim('a little bird', 'a little bird chirps')
print cosine_sim('a little bird', 'a big dog barks')
Renaud
la source
@ Renaud, réponse vraiment bonne et claire! J'ai deux doutes: I) quel est le [0,1] que vous incorporez après tfidf * tfidf.T) et II) La fréquence inverse du document est formée de tous les articles ou seulement deux (considérant que vous en avez plus de 2) ?
Economist_Ayahuasca
2
@AndresAzqueta [0,1] correspond aux positions dans la matrice pour la similitude, car deux entrées de texte créeront une matrice symétrique 2x2.
Philip Bergström
1
@Renaud, Merci pour votre code complet. Pour ceux qui ont rencontré l'erreur demandant à nltk.download (), vous pouvez facilement faire nltk.download ('punkt'). Vous n'avez pas besoin de tout télécharger.
homme
@Renaud Je n'ai pas de problème plus fondamental. Quelles chaînes de texte devraient fitet lesquelles transform?
John Strood
@JohnStrood Je ne comprends pas votre question, désolé pourriez-vous reformuler?
Renaud
45

C'est une vieille question, mais j'ai trouvé que cela peut être fait facilement avec Spacy . Une fois le document lu, une simple api similaritypeut être utilisée pour trouver la similitude cosinus entre les vecteurs de document.

import spacy
nlp = spacy.load('en')
doc1 = nlp(u'Hello hi there!')
doc2 = nlp(u'Hello hi there!')
doc3 = nlp(u'Hey whatsup?')

print doc1.similarity(doc2) # 0.999999954642
print doc2.similarity(doc3) # 0.699032527716
print doc1.similarity(doc3) # 0.699032527716
Koustuv Sinha
la source
2
Je me demande pourquoi la similitude entre doc1 et doc2 est de 0,999999954642 et non de 1,0
JordanBelf
4
Les nombres à virgule flottante @JordanBelf errent un peu dans la plupart des langues - car ils ne peuvent pas avoir une précision illimitée dans les représentations numériques. Par exemple, les opérations en virgule flottante sur ou produisant des nombres irrationnels ont toujours de minuscules erreurs d'arrondi qui se multiplient ensuite. C'est l'inconvénient d'une représentation aussi flexible en termes d'échelle.
scipilot
2
quelle est la fonction de distance que la méthode de similitude utilise dans ce cas?
ikel
Si vous rencontrez des problèmes pour trouver "en" exécutez le pip install suivant spacy && python -m spacy download en
Cybernetic
1
@Cybernetic Jetez un œil à Comment est calculée la méthode de .similarité dans SpaCy
Walter
17

Généralement, une similitude en cosinus entre deux documents est utilisée comme mesure de similitude des documents. En Java, vous pouvez utiliser Lucene (si votre collection est assez grande) ou LingPipe pour ce faire. Le concept de base serait de compter les termes dans chaque document et de calculer le produit scalaire des termes vecteurs. Les bibliothèques fournissent plusieurs améliorations par rapport à cette approche générale, par exemple en utilisant des fréquences de document inverses et en calculant des vecteurs tf-idf. Si vous cherchez à faire quelque chose de copmlex, LingPipe fournit également des méthodes pour calculer la similitude LSA entre les documents, ce qui donne de meilleurs résultats que la similitude cosinus. Pour Python, vous pouvez utiliser NLTK .

Pulkit Goyal
la source
4
Notez qu'il n'y a pas de "similitude LSA". LSA est une méthode pour réduire la dimensionnalité d'un espace vectoriel (soit pour accélérer les choses, soit pour modéliser des sujets plutôt que des termes). Les mêmes métriques de similitude utilisées avec BOW et tf-idf peuvent être utilisées avec LSA (similitude cosinus, similitude euclidienne, BM25,…).
Witiko
16

Si vous cherchez quelque chose de très précis, vous devez utiliser un meilleur outil que tf-idf. L'encodeur de phrases universel est l'un des plus précis pour trouver la similitude entre deux morceaux de texte. Google a fourni des modèles pré-formés que vous pouvez utiliser pour votre propre application sans avoir à vous entraîner à partir de rien. Tout d'abord, vous devez installer tensorflow et tensorflow-hub:

    pip install tensorflow
    pip install tensorflow_hub

Le code ci-dessous vous permet de convertir n'importe quel texte en une représentation vectorielle de longueur fixe, puis vous pouvez utiliser le produit scalaire pour découvrir la similitude entre eux

import tensorflow_hub as hub
module_url = "https://tfhub.dev/google/universal-sentence-encoder/1?tf-hub-format=compressed"

# Import the Universal Sentence Encoder's TF Hub module
embed = hub.Module(module_url)

# sample text
messages = [
# Smartphones
"My phone is not good.",
"Your cellphone looks great.",

# Weather
"Will it snow tomorrow?",
"Recently a lot of hurricanes have hit the US",

# Food and health
"An apple a day, keeps the doctors away",
"Eating strawberries is healthy",
]

similarity_input_placeholder = tf.placeholder(tf.string, shape=(None))
similarity_message_encodings = embed(similarity_input_placeholder)
with tf.Session() as session:
    session.run(tf.global_variables_initializer())
    session.run(tf.tables_initializer())
    message_embeddings_ = session.run(similarity_message_encodings, feed_dict={similarity_input_placeholder: messages})

    corr = np.inner(message_embeddings_, message_embeddings_)
    print(corr)
    heatmap(messages, messages, corr)

et le code pour tracer:

def heatmap(x_labels, y_labels, values):
    fig, ax = plt.subplots()
    im = ax.imshow(values)

    # We want to show all ticks...
    ax.set_xticks(np.arange(len(x_labels)))
    ax.set_yticks(np.arange(len(y_labels)))
    # ... and label them with the respective list entries
    ax.set_xticklabels(x_labels)
    ax.set_yticklabels(y_labels)

    # Rotate the tick labels and set their alignment.
    plt.setp(ax.get_xticklabels(), rotation=45, ha="right", fontsize=10,
         rotation_mode="anchor")

    # Loop over data dimensions and create text annotations.
    for i in range(len(y_labels)):
        for j in range(len(x_labels)):
            text = ax.text(j, i, "%.2f"%values[i, j],
                           ha="center", va="center", color="w", 
fontsize=6)

    fig.tight_layout()
    plt.show()

le résultat serait: la matrice de similitude entre les paires de textes

comme vous pouvez le voir, la plus grande similitude est entre les textes avec eux-mêmes puis avec leurs textes proches en termes de sens.

IMPORTANT : la première fois que vous exécutez le code, il sera lent car il doit télécharger le modèle. si vous voulez l'empêcher de télécharger à nouveau le modèle et d'utiliser le modèle local, vous devez créer un dossier pour le cache et l'ajouter à la variable d'environnement, puis après la première exécution, utilisez ce chemin:

tf_hub_cache_dir = "universal_encoder_cached/"
os.environ["TFHUB_CACHE_DIR"] = tf_hub_cache_dir

# pointing to the folder inside cache dir, it will be unique on your system
module_url = tf_hub_cache_dir+"/d8fbeb5c580e50f975ef73e80bebba9654228449/"
embed = hub.Module(module_url)

Plus d'informations: https://tfhub.dev/google/universal-sentence-encoder/2

Rohola Zandie
la source
salut merci pour cet exemple m'encourageant à essayer TF - d'où doit provenir l'objet "np"?
Open Food Broker
1
UPD ok, j'ai installé numpy, matplotlib et aussi le système de liaison TK Python pour l'intrigue et ça marche !!
Open Food Broker
1
Juste au cas où (désolé pour le manque de sauts de ligne): importation tensorflow en tant que tf import tensorflow_hub en tant que hub import matplotlib.pyplot en tant que plt import numpy as np
dinnouti
5

Voici une petite application pour vous aider à démarrer ...

import difflib as dl

a = file('file').read()
b = file('file1').read()

sim = dl.get_close_matches

s = 0
wa = a.split()
wb = b.split()

for i in wa:
    if sim(i, wb):
        s += 1

n = float(s) / float(len(wa))
print '%d%% similarity' % int(n * 100)
Ben
la source
4
difflib est très lent si vous allez travailler avec un grand nombre de documents.
Phyo Arkar Lwin
2

Vous voudrez peut-être essayer ce service en ligne pour la similitude des documents cosinus http://www.scurtu.it/documentSimilarity.html

import urllib,urllib2
import json
API_URL="http://www.scurtu.it/apis/documentSimilarity"
inputDict={}
inputDict['doc1']='Document with some text'
inputDict['doc2']='Other document with some text'
params = urllib.urlencode(inputDict)    
f = urllib2.urlopen(API_URL, params)
response= f.read()
responseObject=json.loads(response)  
print responseObject
Ekaterina Gorchinsky
la source
l'API utilise-t-elle Matcher séquentiel différentiel? Si oui, une fonction Simple en python ferait le travail ____________________________________ à partir de l'importation difflib SequenceMatcher def isStringSimilar (a, b): ratio = SequenceMatcher (None, a, b) .ratio () ratio de retour ______________________________
Rudresh Ajgaonkar
2

Si vous êtes plus intéressé à mesurer la similitude sémantique de deux morceaux de texte, je vous suggère de jeter un œil à ce projet gitlab . Vous pouvez l'exécuter en tant que serveur, il existe également un modèle prédéfini que vous pouvez utiliser facilement pour mesurer la similitude de deux morceaux de texte; même s'il est principalement formé pour mesurer la similitude de deux phrases, vous pouvez toujours l'utiliser dans votre cas.Il est écrit en java mais vous pouvez l'exécuter en tant que service RESTful.

Une autre option est également DKPro Similarity qui est une bibliothèque avec divers algorithmes pour mesurer la similitude des textes. Cependant, il est également écrit en java.

exemple de code:

// this similarity measure is defined in the dkpro.similarity.algorithms.lexical-asl package
// you need to add that to your .pom to make that example work
// there are some examples that should work out of the box in dkpro.similarity.example-gpl 
TextSimilarityMeasure measure = new WordNGramJaccardMeasure(3);    // Use word trigrams

String[] tokens1 = "This is a short example text .".split(" ");   
String[] tokens2 = "A short example text could look like that .".split(" ");

double score = measure.getSimilarity(tokens1, tokens2);

System.out.println("Similarity: " + score);
Mohammad-Ali
la source
2

Pour trouver la similitude de phrase avec un ensemble de données très réduit et pour obtenir une grande précision, vous pouvez utiliser le package python ci-dessous qui utilise des modèles BERT pré-formés,

pip install similar-sentences
Shankar Ganesh Jayaraman
la source
Je viens d'essayer cela, mais cela donne la similitude de chaque phrase avec celle principale, mais y a-t-il un moyen de créer toutes les données d'entraînement de phrase.txt en une seule classe et d'obtenir un score sur la confiance qui est assortie à tous les exemples ?
Guru Teja
1
oui vous pouvez, essayez .batch_predict (BatchFile, NumberOfPrediction) qui donnera la sortie comme Results.xls avec des colonnes ['Phrase', 'Suggestion', 'Score']
Shankar Ganesh Jayaraman
1

Pour la similitude syntaxique Il peut y avoir 3 façons simples de détecter la similitude.

  • Word2Vec
  • Gant
  • Tfidf ou countvectorizer

Pour la similitude sémantique, on peut utiliser l'incorporation BERT et essayer différentes stratégies de regroupement de mots pour obtenir l'incorporation de documents, puis appliquer la similitude en cosinus à l'incorporation de documents.

Une méthodologie avancée peut utiliser BERT SCORE pour obtenir la similitude. BERT SCORE

Lien vers le document de recherche: https://arxiv.org/abs/1904.09675

shaurya uppal
la source