283 votes

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 à quel point ils sont similaires.

387voto

larsmans Points 167484

La méthode la plus courante consiste à transformer les documents en vecteurs TF-IDF, puis à calculer la similarité en cosinus entre eux. Tous les manuels sur la recherche d'information (RI) traitent de ce sujet. Voir notamment Introduction à la recherche d'informations qui est gratuit et disponible en ligne.

Calcul des similarités par paires

TF-IDF (et les transformations de texte similaires) sont implémentés dans les paquets Python suivants Gensim y scikit-learn . Dans ce dernier package, le calcul des 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 de caractères 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 puisse avoir plus d'options pour ce type de tâche.

Voir aussi cette question .

[Avertissement : j'ai été impliqué dans l'implémentation TF-IDF de scikit-learn].

Interprétation des résultats

Depuis le haut, pairwise_similarity est un Scipy matrice éparse de forme carrée, dont le nombre de lignes et de colonnes est é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 sparse en un tableau NumPy via .toarray() o .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 docs scikit-learn sont Orange et Bleu". Ce document a l'index 4 dans corpus . Vous pouvez trouver l'index du document le plus similaire par 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 ce dernier par le biais de np.fill_diagonal() et le premier par 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 éparse est d'économiser (une quantité substantielle d'espace) pour un grand corpus et vocabulaire. Au lieu de convertir en un tableau NumPy, vous pouvez le faire :

>>> n, _ = pairwise_similarity.shape                                                                                                                                                                                                                         
>>> pairwise_similarity[np.arange(n), np.arange(n)] = -1.0
>>> pairwise_similarity[input_idx].argmax()                                                                                                                                                                                                                  
3

109voto

Renaud Points 3226

Identique à @larsman, mais avec quelques 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')

76voto

Koustuv Sinha Points 1460

C'est une vieille question, mais j'ai découvert que cela peut être fait facilement avec Spacy . Une fois le document lu, une simple api similarity peut être utilisé pour trouver la similarité en cosinus entre les vecteurs de documents.

Commencez par installer le paquet et télécharger le modèle :

pip install spacy
python -m spacy download en_core_web_sm

Puis utilisez comme ceci :

import spacy
nlp = spacy.load('en_core_web_sm')
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

38voto

Rohola Zandie Points 105

Si vous cherchez quelque chose de très précis, vous devez utiliser un meilleur outil que tf-idf. Encodeur universel de phrases est l'une des méthodes les plus précises 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 besoin d'entraîner quoi que ce soit à partir de zéro. 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 d'utiliser le produit scalaire pour déterminer la similarité entre les deux.

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 le traçage :

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 voir, la plus grande similitude se trouve entre les textes avec eux-mêmes et ensuite avec leurs textes proches en termes de signification.

IMPORTANT Si vous voulez l'empêcher de télécharger à nouveau le modèle et 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

17voto

Pulkit Goyal Points 3608

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X