¿Cómo calcular la similitud entre dos documentos de texto?

Resuelto Reily Bourne asked hace 54 años • 14 respuestas

Estoy pensando en trabajar en un proyecto de PNL, en cualquier lenguaje de programación (aunque mi preferencia será Python).

Quiero tomar dos documentos y determinar qué tan similares son.

Reily Bourne avatar Jan 01 '70 08:01 Reily Bourne
Aceptado

La forma común de hacer esto es transformar los documentos en vectores TF-IDF y luego calcular la similitud de cosenos entre ellos. Cualquier libro de texto sobre recuperación de información (IR) cubre esto. Ver especialmente Introducción a la recuperación de información , que es gratuita y está disponible en línea.

Calcular similitudes por pares

TF-IDF (y transformaciones de texto similares) se implementan en los paquetes de Python Gensim y scikit-learn . En el último paquete, calcular similitudes de cosenos es tan fácil como

from sklearn.feature_extraction.text import TfidfVectorizer

documents = [open(f).read() 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

o, si los documentos son cadenas 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 

aunque Gensim puede tener más opciones para este tipo de tareas.

Véase también esta pregunta .

[Descargo de responsabilidad: estuve involucrado en la implementación de scikit-learn TF-IDF.]

Interpretación de los resultados

Desde arriba, pairwise_similarityhay una matriz dispersa de Scipy que tiene forma cuadrada, con el número de filas y columnas igual al número de documentos en el corpus.

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

Puede convertir la matriz dispersa en una matriz NumPy mediante .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.        ]])

Digamos que queremos encontrar el documento más similar al documento final, "Los documentos de scikit-learn son naranja y azul". Este documento tiene índice 4 en corpus. Puedes encontrar el índice del documento más similar tomando el argmax de esa fila, pero primero necesitarás enmascarar los unos, que representan la similitud de cada documento consigo mismo . Puedes hacer lo último a través de np.fill_diagonal()y lo primero a través de 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'

Nota: el propósito de utilizar una matriz dispersa es ahorrar (una cantidad sustancial de espacio) para un corpus y vocabulario grandes. En lugar de convertir a una matriz NumPy, podrías hacer:

>>> n, _ = pairwise_similarity.shape                                                                                                                                                                                                                         
>>> pairwise_similarity[np.arange(n), np.arange(n)] = -1.0
>>> pairwise_similarity[input_idx].argmax()                                                                                                                                                                                                                  
3
Fred Foo avatar Jan 17 '2012 15:01 Fred Foo

Idéntico a @larsman, pero con algo de preprocesamiento.

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 avatar Jun 09 '2014 21:06 Renaud

Es una vieja pregunta, pero descubrí que esto se puede hacer fácilmente con Spacy . Una vez leído el documento, similarityse puede utilizar una API simple para encontrar la similitud del coseno entre los vectores del documento.

Comience instalando el paquete y descargando el modelo:

pip install spacy
python -m spacy download en_core_web_sm

Luego usa así:

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
Koustuv Sinha avatar May 21 '2017 22:05 Koustuv Sinha

Si busca algo muy preciso, necesita utilizar alguna herramienta mejor que tf-idf. El codificador de oraciones universal es uno de los más precisos para encontrar la similitud entre dos fragmentos de texto. Google proporcionó modelos previamente entrenados que puede usar para su propia aplicación sin necesidad de entrenar nada desde cero. Primero, debes instalar tensorflow y tensorflow-hub:

    pip install tensorflow
    pip install tensorflow_hub

El siguiente código le permite convertir cualquier texto a una representación vectorial de longitud fija y luego puede usar el producto escalar para descubrir la similitud entre ellos.

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)

y el código para trazar:

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

el resultado sería: la matriz de similitud entre pares de textos

Como puede ver, la mayor similitud se da entre los textos consigo mismos y luego con sus textos cercanos en significado.

IMPORTANTE : la primera vez que ejecute el código será lento porque necesita descargar el modelo. Si desea evitar que descargue el modelo nuevamente y use el modelo local, debe crear una carpeta para el caché y agregarla a la variable de entorno y luego, después de la primera ejecución, use esa ruta:

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)

Más información: https://tfhub.dev/google/universal-sentence-encoder/2

Rohola Zandie avatar Apr 17 '2019 16:04 Rohola Zandie