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.
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.
Respuestas:
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.
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.]
Desde arriba, pairwise_similarity
hay 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
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.
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')
fit
y cuáles transform
?
Es una vieja pregunta, pero descubrí que esto se puede hacer fácilmente con Spacy . Una vez que se lee el documento, similarity
se 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
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 .
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()
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
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)
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
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);
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
Para similitud sintáctica Puede haber 3 formas fáciles de detectar similitud.
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.
Enlace del trabajo de investigación: https://arxiv.org/abs/1904.09675