Como calcular a semelhança entre dois documentos de texto?

207

Estou pensando em trabalhar em um projeto de PNL, em qualquer linguagem de programação (embora o Python seja a minha preferência).

Quero pegar dois documentos e determinar como eles são semelhantes.

Reily Bourne
fonte
1
Pergunta semelhante aqui stackoverflow.com/questions/101569/... bruxa algumas respostas agradáveis

Respostas:

292

A maneira comum de fazer isso é transformar os documentos em vetores TF-IDF e depois calcular a semelhança de cosseno entre eles. Qualquer livro sobre recuperação de informações (RI) cobre isso. Veja esp. Introdução à recuperação de informações , disponível gratuitamente e online.

Computando semelhanças em pares

TF-IDF (e transformações de texto similares) são implementadas nos pacotes Python Gensim e scikit -learn . Neste último pacote, calcular semelhanças de cosseno é tão fácil quanto

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, se os documentos forem 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 

embora o Gensim possa ter mais opções para esse tipo de tarefa.

Veja também esta questão .

[Isenção de responsabilidade: estive envolvido na implementação do scikit-learn TF-IDF.]

Interpretando os resultados

De cima, pairwise_similarityé uma matriz esparsa do Scipy de forma quadrada, com o número de linhas e colunas igual ao número de documentos no corpus.

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

Você pode converter a matriz esparsa em uma matriz 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.        ]])

Digamos que queremos encontrar o documento mais semelhante ao documento final, "Os documentos de aprendizado do scikit são laranja e azul". Este documento possui o índice 4 in corpus. Você pode encontrar o índice do documento mais semelhante, obtendo o argmax dessa linha, mas primeiro precisará mascarar os 1s, que representam a semelhança de cada documento consigo . Você pode fazer o último através np.fill_diagonal(), e o primeiro através 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: o objetivo de usar uma matriz esparsa é economizar (uma quantidade substancial de espaço) para um corpus e vocabulário grandes. Em vez de converter para uma matriz NumPy, você pode fazer:

>>> n, _ = pairwise_similarity.shape                                                                                                                                                                                                                         
>>> pairwise_similarity[np.arange(n), np.arange(n)] = -1.0
>>> pairwise_similarity[input_idx].argmax()                                                                                                                                                                                                                  
3
Fred Foo
fonte
1
@larsmans Você pode explicar a matriz um pouco, se possível, como devo ler essa matriz. As duas primeiras colunas são semelhantes entre as duas primeiras frases?
add-ponto-e-vírgula
1
@ Hipótese nula: na posição (i, j), você encontra a pontuação de similaridade entre o documento ie o documento j. Portanto, na posição (0,2) está o valor de similaridade entre o primeiro documento e o terceiro (usando a indexação com base em zero), que é o mesmo valor encontrado em (2,0), porque a semelhança de cosseno é comutativa.
22812 Fred Foo
1
Se eu calculasse a média de todos os valores fora da diagonal de 1, seria uma maneira sólida de obter uma pontuação única de quão semelhantes os quatro documentos são entre si? Caso contrário, existe uma maneira melhor de determinar a similaridade geral entre vários documentos?
user301752
2
@ user301752: você pode pegar a média dos vetores tf-idf (como faria o k-means) com X.mean(axis=0)e calcular a distância euclidiana média / máxima / mediana (∗) dessa média. (∗) Escolha o que quiser.
11552 Fred Foo
1
@curious: atualizei o código de exemplo para a atual API do scikit-learn; convém tentar o novo código.
Fred Foo
87

Idêntico ao @larsman, mas com algum pré-processamento

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
fonte
@ Renaud, resposta muito boa e clara! Tenho duas dúvidas: I) qual é o [0,1] que você incorpora após tfidf * tfidf.T) e II) A frequência inversa de documentos é formada por todos os artigos ou apenas dois (considerando que você possui mais de 2) ?
Economist_Ayahuasca
2
@AndresAzqueta [0,1] são as posições na matriz para a semelhança, pois duas entradas de texto criarão uma matriz simétrica 2x2.
Philip Bergström
1
@Renaud, obrigado por seu código completo. Para aqueles que encontraram o erro solicitando o nltk.download (), você pode facilmente executar o nltk.download ('punkt'). Você não precisa baixar tudo.
1Man
@Renaud Eu não tenho um problema mais fundamental. Quais cadeias de texto devem fite quais transform?
precisa
@ JohnStrood Não entendi sua pergunta, desculpe, você pode reformular?
Renaud
45

É uma pergunta antiga, mas achei que isso pode ser feito facilmente com Spacy . Depois que o documento é lido, uma API simples similaritypode ser usada para encontrar a semelhança de cosseno entre os vetores do 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
fonte
2
Eu me pergunto por que a semelhança entre doc1 e doc2 é 0,999999954642 e não 1.0
JordanBelf
4
Os números de ponto flutuante @JordanBelf vagam um pouco na maioria dos idiomas - pois eles não podem ter precisão ilimitada nas representações digitais. por exemplo, operações de ponto flutuante ou produção de números irracionais sempre apresentam pequenos erros de arredondamento, os quais se multiplicam. É a desvantagem de uma representação tão flexível em termos de escala.
Scipilot
2
qual é a função de distância que o método de similaridade está usando neste caso?
ikel
Se você tiver problemas para encontrar "en", execute o seguinte pip install spacy && python -m spacy download pt
Cybernetic
1
@Cybernetic Dê uma olhada Como é o método .similarity em SPACY calculado
Walter
17

Geralmente, uma similaridade de cosseno entre dois documentos é usada como uma medida de similaridade de documentos. Em Java, você pode usar o Lucene (se sua coleção é muito grande) ou o LingPipe para fazer isso. O conceito básico seria contar os termos em todos os documentos e calcular o produto escalar dos vetores de termos. As bibliotecas fornecem várias melhorias em relação a essa abordagem geral, por exemplo, usando frequências de documentos inversas e calculando vetores tf-idf. Se você deseja fazer algo copmlex, o LingPipe também fornece métodos para calcular a similaridade de LSA entre documentos, o que fornece melhores resultados do que a similaridade de cosseno. Para Python, você pode usar o NLTK .

Pulkit Goyal
fonte
4
Observe que não há "semelhança com o LSA". O LSA é um método para reduzir a dimensionalidade de um espaço vetorial (para acelerar as coisas ou modelar tópicos em vez de termos). As mesmas métricas de similaridade usadas com BOW e tf-idf podem ser usadas com LSA (similaridade de cosseno, similaridade euclidiana, BM25, ...).
Witiko 29/08/19
16

Se você está procurando algo muito preciso, precisa usar alguma ferramenta melhor que a tf-idf. O codificador de frases universal é um dos mais precisos para encontrar a semelhança entre dois pedaços de texto. O Google forneceu modelos pré-treinados que você pode usar para seu próprio aplicativo sem precisar treinar nada do zero. Primeiro, você deve instalar o tensorflow e o tensorflow-hub:

    pip install tensorflow
    pip install tensorflow_hub

O código abaixo permite converter qualquer texto em uma representação vetorial de comprimento fixo e, em seguida, você pode usar o produto escalar para descobrir a semelhança entre eles

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)

e o código para plotagem:

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

o resultado seria: a matriz de similaridade entre pares de textos

como você pode ver, a maior semelhança é entre textos consigo mesmos e depois com seus textos próximos em sentido.

IMPORTANTE : na primeira vez em que você executar o código, ele será lento porque precisará fazer o download do modelo. se você quiser impedir que ele baixe o modelo novamente e use o modelo local, crie uma pasta para cache e adicione-a à variável de ambiente e, após a primeira execução, use esse caminho:

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)

Mais informações: https://tfhub.dev/google/universal-sentence-encoder/2

Rohola Zandie
fonte
oi obrigado por este exemplo, incentivando-me a experimentar o TF - de onde deve vir o objeto "np"?
Open Food Broker
1
UPD ok, eu instalei o numpy, o matplotlib e também o sistema TK Python para o enredo e funciona !!
Open Food Broker
1
Apenas no caso (não por falta de quebras de linha): tensorflow importação como tensorflow_hub tf importação como matplotlib.pyplot cubo importação como numpy plt importação como NP
dinnouti
5

Aqui está um pequeno aplicativo para você começar ...

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
fonte
4
O difflib é muito lento se você trabalhar com um grande número de documentos.
Phyo Arkar Lwin
2

Convém tentar este serviço on-line para obter semelhança de documentos de cosseno 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
fonte
a API está usando o Matcher seqüencial diferencial? Se sim, uma função simples em python faria o trabalho ____________________________________ from diffflib import SequenceMatcher def isStringSimilar (a, b): ratio = SequenceMatcher (None, a, b) .ratio () return ratio ______________________________
Rudresh Ajgaonkar
2

Se você estiver mais interessado em medir a semelhança semântica de dois pedaços de texto, sugiro dar uma olhada neste projeto do gitlab . Você pode executá-lo como um servidor; também há um modelo pré-criado que você pode usar facilmente para medir a semelhança de dois pedaços de texto; mesmo sendo treinado principalmente para medir a semelhança de duas frases, você ainda pode usá-lo no seu caso. Ele é escrito em java, mas você pode executá-lo como um serviço RESTful.

Outra opção também é DKPro Similarity, que é uma biblioteca com vários algoritmos para medir a similaridade de textos. No entanto, também é escrito em java.

exemplo 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
fonte
2

Para encontrar semelhança de sentença com muito menos conjunto de dados e obter alta precisão, você pode usar o pacote python abaixo, que usa modelos BERT pré-treinados,

pip install similar-sentences
Shankar Ganesh Jayaraman
fonte
Eu apenas tentei isso, mas fornece semelhança de cada sentença com a principal, mas existe alguma maneira de criar todos os dados do treinamento.txt.txt como uma classe e obter uma pontuação de quanto confiança está sendo correspondida a todos os exemplos ?
Guru Teja
1
sim, você pode, tente .batch_predict (BatchFile, NumberOfPrediction) que dará saída como Results.xls com colunas ['Sentença', 'Sugestão', 'Pontuação']
Shankar Ganesh Jayaraman
1

Para similaridade sintática Pode haver três maneiras fáceis de detectar similaridade.

  • Word2Vec
  • Luva
  • Tfidf ou countvectorizer

Para semelhança semântica É possível usar a incorporação de BERT e tentar estratégias diferentes de agrupamento de palavras para obter a incorporação de documentos e aplicar a semelhança de cosseno na incorporação de documentos.

Uma metodologia avançada pode usar o BERT SCORE para obter semelhança. PONTUAÇÃO DO BERT

Artigo: Research Link: https://arxiv.org/abs/1904.09675

shaurya uppal
fonte