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

207

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

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

Reily Bourne
fuente
1
Pregunta similar aquí stackoverflow.com/questions/101569/… bruja algunas buenas respuestas

Respuestas:

292

La forma común de hacerlo es transformar los documentos en vectores TF-IDF y luego calcular la similitud de coseno 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 las similitudes de coseno es tan fácil como

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

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 tarea.

Ver también esta pregunta .

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

Interpretando los resultados

Desde arriba, pairwise_similarityhay una matriz dispersa 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 el índice 4 en corpus. Puede encontrar el índice del documento más similar tomando la argmax de esa fila, pero primero deberá enmascarar los 1, que representan la similitud de cada documento consigo mismo . Puede hacer lo último a través 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 usar una matriz dispersa es ahorrar (una cantidad sustancial de espacio) para un corpus y vocabulario de gran tamaño. En lugar de convertir a una matriz NumPy, podría hacer:

>>> n, _ = pairwise_similarity.shape                                                                                                                                                                                                                         
>>> pairwise_similarity[np.arange(n), np.arange(n)] = -1.0
>>> pairwise_similarity[input_idx].argmax()                                                                                                                                                                                                                  
3
Fred Foo
fuente
1
@larsmans ¿Puede explicar la matriz un poco si es posible, cómo debería leer esta matriz? ¿Las primeras dos columnas son similares entre las primeras dos oraciones?
agregar-punto y coma
1
@ Hipótesis nula: en la posición (i, j), se encuentra la puntuación de similitud entre el documento i y el documento j. Entonces, en la posición (0,2) está el valor de similitud entre el primer documento y el tercero (usando indexación basada en cero), que es el mismo valor que encuentra en (2,0), porque la similitud del coseno es conmutativa.
Fred Foo
1
Si promediara todos los valores fuera de la diagonal de 1, ¿sería una buena forma de obtener una puntuación única de cuán similares son los cuatro documentos entre sí? Si no, ¿hay una mejor manera de determinar la similitud general entre múltiples documentos?
user301752
2
@ user301752: podría tomar la media en cuanto a elementos de los vectores tf-idf (como haría k-means) X.mean(axis=0), y luego calcular la distancia euclidiana promedio / máxima / mediana (∗) a partir de esa media. (∗) Elige el que más te guste.
Fred Foo
1
@curious: actualicé el código de ejemplo a la API scikit-learn actual; Es posible que desee probar el nuevo código.
Fred Foo
88

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
fuente
@ Renaud, muy buena y clara respuesta! Tengo dos dudas: I) cuál es el [0,1] que incorporas después de tfidf * tfidf.T) y II) La frecuencia del documento inverso se forma a partir de todos los artículos o solo dos (considerando que tienes más de 2) ?
Economist_Ayahuasca
2
@AndresAzqueta [0,1] son ​​las posiciones en la matriz para la similitud ya que dos entradas de texto crearán una matriz simétrica de 2x2.
Philip Bergström
1
@Renaud, gracias por tu código completo. Para aquellos que encontraron el error pidiendo nltk.download (), pueden hacer fácilmente nltk.download ('punkt'). No necesita descargar todo.
1man
@Renaud No tengo un problema más fundamental. ¿Qué cadenas de texto deberían fity cuáles transform?
John Strood
@ JohnStrood No entiendo tu pregunta, lo siento, ¿podrías reformular?
Renaud
45

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

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
fuente
2
Me pregunto por qué la similitud entre doc1 y doc2 es 0.999999954642 y no 1.0
JordanBelf
44
Los números de coma flotante @JordanBelf deambulan un poco en la mayoría de los idiomas, ya que no pueden tener una precisión ilimitada en las representaciones digitales. por ejemplo, las operaciones de punto flotante en o produciendo números irracionales siempre tienen pequeños errores de redondeo que luego se multiplican. Es la desventaja de una representación tan flexible en términos de escala.
scipilot
2
¿Cuál es la función de distancia que utiliza el método de similitud en este caso?
ikel
Si tiene problemas para encontrar "en", ejecute el siguiente pip install spacy && python -m spacy download es
Cybernetic
17

En general, una similitud de coseno entre dos documentos se utiliza como una medida de similitud de documentos. En Java, puede usar Lucene (si su colección es bastante grande) o LingPipe para hacer esto. El concepto básico sería contar los términos en cada documento y calcular el producto escalar de los vectores de términos. Las bibliotecas proporcionan varias mejoras sobre este enfoque general, por ejemplo, utilizando frecuencias de documentos inversas y calculando vectores tf-idf. Si está buscando hacer algo copmlex, LingPipe también proporciona métodos para calcular la similitud de LSA entre documentos que proporciona mejores resultados que la similitud de coseno. Para Python, puede usar NLTK .

Pulkit Goyal
fuente
44
Tenga en cuenta que no hay "similitud LSA". LSA es un método para reducir la dimensionalidad de un espacio vectorial (ya sea para acelerar las cosas o para modelar temas en lugar de términos). Las mismas métricas de similitud que se usan con BOW y tf-idf se pueden usar con LSA (similitud de coseno, similitud euclidiana, BM25, ...).
Witiko
16

Si está buscando algo muy preciso, necesita usar alguna herramienta mejor que tf-idf. El codificador de oraciones universal es uno de los más precisos para encontrar la similitud entre dos partes de texto. Google proporcionó modelos preentrenados que puede usar para su propia aplicación sin necesidad de entrenar desde cero. Primero, debe 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 de puntos 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 es entre textos con ellos 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 agregarlo a la variable de entorno y luego, después de la primera vez que ejecuta, 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
fuente
hola, gracias por este ejemplo que me anima a probar TF. ¿De dónde debería venir el objeto "np"?
Open Food Broker
1
UPD ok, he instalado numpy, matplotlib y también el sistema TK Python vinculante para la trama y funciona !!
Open Food Broker
1
Por si acaso (lo siento por la falta de saltos de línea): tensorflow importación como tensorflow_hub importación tf como matplotlib.pyplot importación cubo como numpy PLT Import, como NP
dinnouti
5

Aquí hay una pequeña aplicación para comenzar ...

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
fuente
44
difflib es muy lento si va a trabajar con una gran cantidad de documentos.
Phyo Arkar Lwin
2

Es posible que desee probar este servicio en línea para la similitud de documentos coseno 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
fuente
¿Está la API usando Matcher secuencial diferencial? En caso afirmativo, una función simple en python haría el trabajo ____________________________________ desde difflib import SequenceMatcher def isStringSimilar (a, b): ratio = SequenceMatcher (None, a, b) .ratio () ratio de retorno ______________________________
Rudresh Ajgaonkar
2

Si está más interesado en medir la similitud semántica de dos textos, le sugiero que eche un vistazo a este proyecto de gitlab . Puede ejecutarlo como un servidor, también hay un modelo prefabricado que puede usar fácilmente para medir la similitud de dos textos; a pesar de que está principalmente capacitado para medir la similitud de dos oraciones, aún puede usarlo en su caso. Está escrito en Java pero puede ejecutarlo como un servicio RESTful.

Otra opción también es DKPro Similarity, que es una biblioteca con varios algoritmos para medir la similitud de los textos. Sin embargo, también está escrito en java.

ejemplo de código:

// 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
fuente
2

Para encontrar la similitud de las oraciones con un conjunto de datos muy reducido y obtener una alta precisión, puede usar el siguiente paquete de Python que utiliza modelos BERT previamente entrenados,

pip install similar-sentences
Shankar Ganesh Jayaraman
fuente
Acabo de intentarlo, pero da similitud a cada oración con la principal, pero hay alguna forma de crear todos los datos de entrenamiento de oración.txt como una clase y obtener puntaje sobre la confianza que se está haciendo coincidir con todos los ejemplose ?
Guru Teja
1
sí puede, intente .batch_predict (BatchFile, NumberOfPrediction) que dará salida como Results.xls con columnas ['Sentence', 'Suggestion', 'Score']
Shankar Ganesh Jayaraman
1

Para similitud sintáctica Puede haber 3 formas fáciles de detectar similitud.

  • Word2Vec
  • Guante
  • Tfidf o countvectorizer

Para la similitud semántica, uno puede usar la incrustación BERT y probar diferentes estrategias de agrupación de palabras para obtener la incrustación de documentos y luego aplicar la similitud del coseno en la incrustación de documentos.

Una metodología avanzada puede usar BERT SCORE para obtener similitud. PUNTUACIÓN BERT

Enlace del trabajo de investigación: https://arxiv.org/abs/1904.09675

shaurya uppal
fuente