Calcular la similitud de coseno dadas cadenas de 2 oraciones

Desde Python: tf-idf-cosine: para encontrar la similitud del documento , es posible calcular la similitud del documento utilizando el coseno tf-idf. Sin importar bibliotecas externas, ¿hay alguna forma de calcular la similitud de coseno entre 2 cadenas?

s1 = "This is a foo bar sentence ." s2 = "This sentence is similar to a foo bar sentence ." s3 = "What is this string ? Totally not related to the other two lines ." cosine_sim(s1, s2) # Should give high cosine similarity cosine_sim(s1, s3) # Shouldn't give high cosine similarity value cosine_sim(s2, s3) # Shouldn't give high cosine similarity value 

Una implementación simple de Python puro sería:

 import re, math from collections import Counter WORD = re.compile(r'\w+') def get_cosine(vec1, vec2): intersection = set(vec1.keys()) & set(vec2.keys()) numerator = sum([vec1[x] * vec2[x] for x in intersection]) sum1 = sum([vec1[x]**2 for x in vec1.keys()]) sum2 = sum([vec2[x]**2 for x in vec2.keys()]) denominator = math.sqrt(sum1) * math.sqrt(sum2) if not denominator: return 0.0 else: return float(numerator) / denominator def text_to_vector(text): words = WORD.findall(text) return Counter(words) text1 = 'This is a foo bar sentence .' text2 = 'This sentence is similar to a foo bar sentence .' vector1 = text_to_vector(text1) vector2 = text_to_vector(text2) cosine = get_cosine(vector1, vector2) print 'Cosine:', cosine 

Huellas dactilares:

 Cosine: 0.861640436855 

La fórmula de coseno utilizada aquí se describe aquí .

Esto no incluye la ponderación de las palabras por tf-idf, pero para usar tf-idf, necesita tener un corpus razonablemente grande para estimar las ponderaciones de tfidf.

También puede desarrollarlo aún más, utilizando una forma más sofisticada de extraer palabras de un texto, separarlas o minimizarlas, etc.

La respuesta corta es “no, no es posible hacerlo de una manera basada en principios que funcione incluso de forma remota”. Es un problema no resuelto en la investigación del procesamiento del lenguaje natural y también es el tema de mi trabajo de doctorado. Resumiré brevemente dónde estamos y le indicaré algunas publicaciones:

Significado de las palabras

El supuesto más importante aquí es que es posible obtener un vector que represente cada palabra en la oración en cuestión. Este vector generalmente se elige para capturar los contextos en los que puede aparecer la palabra. Por ejemplo, si solo consideramos los tres contextos “comer”, “rojo” y “esponjoso”, la palabra “gato” podría representarse como [98, 1 , 87], porque si leyeras un texto muy largo (unos mil millones de palabras no son infrecuentes para el estándar de hoy), la palabra “gato” aparecerá muy a menudo en el contexto de “esponjoso” y “comer” , pero no tan a menudo en el contexto de “rojo”. De la misma manera, “perro” podría representarse como [87,2,34] y “paraguas” podría ser [1,13,0]. Al representar estos vectores como puntos en el espacio 3D, “gato” está claramente más cerca de “perro” que de “paraguas”, por lo que “gato” también significa algo más parecido a “perro” que a “paraguas”.

Esta línea de trabajo se ha investigado desde principios de los años 90 (por ejemplo, este trabajo de Greffenstette) y ha dado algunos resultados sorprendentemente buenos. Por ejemplo, aquí hay algunas entradas aleatorias en un diccionario de sinónimos que construí recientemente haciendo que mi computadora lea wikipedia:

 theory -> analysis, concept, approach, idea, method voice -> vocal, tone, sound, melody, singing james -> william, john, thomas, robert, george, charles 

Estas listas de palabras similares se obtuvieron en su totalidad sin intervención humana: usted alimenta el texto y vuelve unas horas más tarde.

El problema con las frases

Puede preguntar por qué no hacemos lo mismo con frases más largas, como “los zorros de jengibre aman las frutas”. Es porque no tenemos suficiente texto. Para que podamos establecer de manera confiable con qué X es similar, necesitamos ver muchos ejemplos de X que se usan en contexto. Cuando X es una sola palabra como “voz”, esto no es demasiado difícil. Sin embargo, a medida que X se hace más larga, las posibilidades de encontrar ocurrencias naturales de X se vuelven exponencialmente más lentas. A modo de comparación, Google tiene alrededor de 1B páginas que contienen la palabra “fox” y no una sola página que contenga “ginger foxes love fruit”, a pesar de que es una oración en inglés perfectamente válida y todos entendemos lo que significa.

Composición

Para abordar el problema de la escasez de datos, queremos realizar una composición, es decir, tomar vectores para las palabras, que son fáciles de obtener del texto real, y juntarlos de una manera que capture su significado. La mala noticia es que nadie ha podido hacerlo tan bien hasta ahora.

La forma más simple y obvia es sumr o multiplicar los vectores de palabras individuales juntos. Esto conduce a un efecto secundario indeseable que “gatos persiguen a los perros” y “perros persiguen a los gatos” significaría lo mismo para su sistema. Además, si está multiplicando, debe tener mucho cuidado o cada oración terminará representada por [0,0,0, …, 0], lo que anula el punto.

Otras lecturas

No discutiré los métodos de composición más sofisticados que se han propuesto hasta ahora. Le sugiero que lea “Modelos vectoriales espaciales del significado de las palabras y el significado de las frases: una encuesta” de Katrin Erk. Esta es una muy buena encuesta de alto nivel para comenzar. Desafortunadamente, no está disponible gratuitamente en el sitio web del editor, envíe un correo electrónico al autor directamente para obtener una copia. En ese artículo encontrarás referencias a muchos más métodos concretos. Los más comprensibles son de Mitchel y Lapata (2008) y Baroni y Zamparelli (2010) .


Editar tras el comentario de @vpekar: la conclusión de esta respuesta es enfatizar el hecho de que, si bien existen métodos ingenuos (por ejemplo, la sum, la multiplicación, la similitud de la superficie, etc.), estos son fundamentalmente defectuosos y, en general, no se debe esperar un gran rendimiento. ellos.

Gracias @vpekar por tu implementación. Ayudó mucho. Acabo de descubrir que pierde el peso tf-idf al calcular la similitud del coseno. El Contador (palabra) devuelve un diccionario que contiene la lista de palabras junto con su aparición.

cos (q, d) = sim (q, d) = (q · d) / (| q || d |) = (sum (qi, di) / (sqrt (sum (qi2))) * (sqrt ( sum (vi2))) donde i = 1 a v)

  • qi es el peso tf-idf del término i en la consulta.
  • di es el tf-idf
  • Peso del término i en el documento. | q | y | d | son las longitudes de q y d.
  • Esta es la similitud coseno de q y d. . . . . . o, equivalentemente, el coseno del ángulo entre q y d.

Por favor, siéntase libre de ver mi código aquí . Pero primero tendrás que descargar el paquete anaconda. Te establecerá automáticamente la ruta de acceso de python en Windows. Agrega este intérprete de python en Eclipse.

Bueno, si está al tanto de incrustaciones de palabras como Glove / Word2Vec / Numberbatch, su trabajo está hecho a medias. Si no, déjame explicarte cómo se puede abordar esto. Convierta cada oración en tokens de palabras, y represente cada uno de estos tokens como vectores de alta dimensión (utilizando incrustaciones de palabras pre-entrenadas, ¡o puede entrenarlas usted mismo incluso!). Por lo tanto, ahora simplemente no captas su similitud superficial, sino que extraes el significado de cada palabra que comprende la oración en su totalidad. Después de esto calcula su similitud de coseno y se establece.

Prueba esto. Descargue el archivo ‘numberbatch-en-17.06.txt’ de https://conceptnet.s3.amazonaws.com/downloads/2017/numberbatch/numberbatch-en-17.06.txt.gz y extráigalo. La función ‘get_sentence_vector’ usa una sum simple de vectores de palabras. Sin embargo, se puede mejorar utilizando una sum ponderada donde los pesos son proporcionales a Tf-Idf de cada palabra.

 import math import numpy as np std_embeddings_index = {} with open('path/to/numberbatch-en-17.06.txt') as f: for line in f: values = line.split(' ') word = values[0] embedding = np.asarray(values[1:], dtype='float32') std_embeddings_index[word] = embedding def cosineValue(v1,v2): "compute cosine similarity of v1 to v2: (v1 dot v2)/{||v1||*||v2||)" sumxx, sumxy, sumyy = 0, 0, 0 for i in range(len(v1)): x = v1[i]; y = v2[i] sumxx += x*x sumyy += y*y sumxy += x*y return sumxy/math.sqrt(sumxx*sumyy) def get_sentence_vector(sentence, std_embeddings_index = std_embeddings_index ): sent_vector = 0 for word in sentence.lower().split(): if word not in std_embeddings_index : word_vector = np.array(np.random.uniform(-1.0, 1.0, 300)) std_embeddings_index[word] = word_vector else: word_vector = std_embeddings_index[word] sent_vector = sent_vector + word_vector return sent_vector def cosine_sim(sent1, sent2): return cosineValue(get_sentence_vector(sent1), get_sentence_vector(sent2)) 

Corrí para las oraciones dadas y encontré los siguientes resultados

 s1 = "This is a foo bar sentence ." s2 = "This sentence is similar to a foo bar sentence ." s3 = "What is this string ? Totally not related to the other two lines ." print cosine_sim(s1, s2) # Should give high cosine similarity print cosine_sim(s1, s3) # Shouldn't give high cosine similarity value print cosine_sim(s2, s3) # Shouldn't give high cosine similarity value 0.9851735249068168 0.6570885718962608 0.6589335425458225