web-dev-qa-db-fra.com

Comment calculer la similarité entre deux documents texte?

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

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

174
Reily Bourne

La méthode courante consiste à transformer les documents en vecteurs TF-IDF, puis à calculer la similarité en cosinus entre eux. Tout manuel sur la recherche d'information (IR) couvre cela. Voir esp. Introduction à la recherche d'informations , gratuit et disponible en ligne.

Calcul des similarités paires

TF-IDF (et des transformations de texte similaires) sont implémentés dans les Python packages Gensim et scikit-learn . Dans ce dernier logiciel, calculer les similitudes de 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 

bien que Gensim ait plus d'options pour ce type de tâche.

Voir aussi cette question .

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

Interpréter les résultats

D'en haut, pairwise_similarity est un Scipy matrice creuse de forme carrée, avec un nombre de lignes et de colonnes égal au nombre de documents du 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 l'index 4 dans corpus. Vous pouvez trouver l'index du document le plus similaire en en prenant l'argmax de cette ligne, mais vous devrez d'abord masquer les 1, qui représentent la similarité de chaque document avec lui-même . Vous pouvez faire le dernier à travers np.fill_diagonal(), et le premier à 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: l’utilisation d’une matrice clairsemée a pour but d’économiser (une quantité d’espace importante) pour un corpus et un vocabulaire volumineux. Au lieu de convertir en un 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
253
Fred Foo

Identique à @larsman, mais avec certains prétraitements

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')
81
Renaud

C'est une vieille question, mais j'ai trouvé que cela pouvait être fait facilement avec Spacy . Une fois le document lu, un simple api similarity peut être utilisé pour trouver la similarité cosinus entre les vecteurs du 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
33
Koustuv Sinha

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

17
Pulkit Goyal

Si vous recherchez quelque chose de très précis, vous devez utiliser un meilleur outil que tf-idf. niversal Phrase Encoder est l’un des plus précis pour trouver la similarité entre deux morceaux de texte. Google a fourni des modèles pré-entraîné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. Vous pouvez ensuite utiliser le produit scalaire pour rechercher la similitude entre eux.

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: the similarity matrix between pairs of texts

comme vous pouvez le constater, la plupart des similitudes sont entre des textes avec eux-mêmes et ensuite avec leurs textes proches.

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

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

6
Rohola Zandie

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)
4
Dr. KingSchultz

Vous voudrez peut-être essayer ce service en ligne pour la similarité de documents en 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
2
Ekaterina Gorchinsky

Si vous êtes plus intéressé par la mesure de la similarité sémantique de deux morceaux de texte, je vous suggère de regarder ce projet gitlab . Vous pouvez l'exécuter en tant que serveur. Il existe également un modèle prédéfini que vous pouvez facilement utiliser pour mesurer la similarité de deux morceaux de texte. Même s'il est principalement formé pour mesurer la similarité 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 similarité DKPro , qui est une bibliothèque avec différents algorithmes pour mesurer la similarité des textes. Cependant, il est également écrit en Java.

2
Mohammad-Ali