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.
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.
Respostas:
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.
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.]
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
X.mean(axis=0)
e calcular a distância euclidiana média / máxima / mediana (∗) dessa média. (∗) Escolha o que quiser.
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')
fit
e quais transform
?
É uma pergunta antiga, mas achei que isso pode ser feito facilmente com Spacy . Depois que o documento é lido, uma API simples similarity
pode 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
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 .
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()
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
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)
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
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);
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
Para similaridade sintática Pode haver três maneiras fáceis de detectar similaridade.
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.
Artigo: Research Link: https://arxiv.org/abs/1904.09675