2012-08-25 117 views
60

Estaba siguiendo un tutorial que estaba disponible en Part 1 & Part 2 lamentablemente autor no tenía tiempo para el tramo final que implica el uso del coseno para realmente encontrar la similitud entre dos documentos . Seguí los ejemplos en el artículo con la ayuda del siguiente enlace desde stackoverflow He incluido el código que se menciona en el enlace anterior solo para que las respuestas sean fáciles.Python: TF-IDF-coseno: encontrar similitud de documentos

from sklearn.feature_extraction.text import CountVectorizer 
from sklearn.feature_extraction.text import TfidfTransformer 
from nltk.corpus import stopwords 
import numpy as np 
import numpy.linalg as LA 

train_set = ["The sky is blue.", "The sun is bright."] #Documents 
test_set = ["The sun in the sky is bright."] #Query 
stopWords = stopwords.words('english') 

vectorizer = CountVectorizer(stop_words = stopWords) 
#print vectorizer 
transformer = TfidfTransformer() 
#print transformer 

trainVectorizerArray = vectorizer.fit_transform(train_set).toarray() 
testVectorizerArray = vectorizer.transform(test_set).toarray() 
print 'Fit Vectorizer to train set', trainVectorizerArray 
print 'Transform Vectorizer to test set', testVectorizerArray 

transformer.fit(trainVectorizerArray) 
print 
print transformer.transform(trainVectorizerArray).toarray() 

transformer.fit(testVectorizerArray) 
print 
tfidf = transformer.transform(testVectorizerArray) 
print tfidf.todense() 

como resultado del código anterior he siguiendo matriz

Fit Vectorizer to train set [[1 0 1 0] 
[0 1 0 1]] 
Transform Vectorizer to test set [[0 1 1 1]] 

[[ 0.70710678 0.   0.70710678 0.  ] 
[ 0.   0.70710678 0.   0.70710678]] 

[[ 0.   0.57735027 0.57735027 0.57735027]] 

No estoy seguro de cómo utilizar esta salida para calcular la similitud del coseno, sé cómo poner en práctica el respeto similitud del coseno de dos vectores con longitud similar, pero aquí no estoy seguro de cómo identificar los dos vectores.

+3

Para cada vector en trainVectorizerArray, debe encontrar la similitud del coseno con el vector en testVectorizerArray. – excray

+0

@excray Gracias, con su punto de ayuda que logro resolver, ¿debería dar la respuesta? –

+0

@excray Pero tengo una pequeña pregunta, el cálculo reall tf * idf no tiene ningún uso para esto, porque no estoy usando los resultados finales que se muestran en la matriz. –

Respuesta

13

Con la ayuda del comentario de @exyray, me las arreglo para encontrar la respuesta, lo que tenemos que hacer es escribir un ciclo simple para iterar sobre las dos matrices que representan los datos del tren y los datos de prueba.

Primera implementar una función lambda sencilla de mantener la fórmula para el cálculo del coseno:

cosine_function = lambda a, b : round(np.inner(a, b)/(LA.norm(a)*LA.norm(b)), 3) 

Y entonces acaba de escribir un simple bucle for para iterar sobre la que el vector, la lógica es que cada "Para cada vector en trainVectorizerArray , tienes que encontrar la similitud del coseno con el vector en testVectorizerArray ".

from sklearn.feature_extraction.text import CountVectorizer 
from sklearn.feature_extraction.text import TfidfTransformer 
from nltk.corpus import stopwords 
import numpy as np 
import numpy.linalg as LA 

train_set = ["The sky is blue.", "The sun is bright."] #Documents 
test_set = ["The sun in the sky is bright."] #Query 
stopWords = stopwords.words('english') 

vectorizer = CountVectorizer(stop_words = stopWords) 
#print vectorizer 
transformer = TfidfTransformer() 
#print transformer 

trainVectorizerArray = vectorizer.fit_transform(train_set).toarray() 
testVectorizerArray = vectorizer.transform(test_set).toarray() 
print 'Fit Vectorizer to train set', trainVectorizerArray 
print 'Transform Vectorizer to test set', testVectorizerArray 
cx = lambda a, b : round(np.inner(a, b)/(LA.norm(a)*LA.norm(b)), 3) 

for vector in trainVectorizerArray: 
    print vector 
    for testV in testVectorizerArray: 
     print testV 
     cosine = cx(vector, testV) 
     print cosine 

transformer.fit(trainVectorizerArray) 
print 
print transformer.transform(trainVectorizerArray).toarray() 

transformer.fit(testVectorizerArray) 
print 
tfidf = transformer.transform(testVectorizerArray) 
print tfidf.todense() 

Aquí está la salida:

Fit Vectorizer to train set [[1 0 1 0] 
[0 1 0 1]] 
Transform Vectorizer to test set [[0 1 1 1]] 
[1 0 1 0] 
[0 1 1 1] 
0.408 
[0 1 0 1] 
[0 1 1 1] 
0.816 

[[ 0.70710678 0.   0.70710678 0.  ] 
[ 0.   0.70710678 0.   0.70710678]] 

[[ 0.   0.57735027 0.57735027 0.57735027]] 
+1

agradable ... Estoy aprendiendo desde el principio también y su pregunta y respuesta son las más fáciles de seguir. Creo que puede usar np.corrcoef() en su lugar, su método de rollo propio. – wbg

+1

, de nuevo, su método permite cualquier norma ... que es genial ... – wbg

+1

@spicyramen para redondear hasta 3 decimales –

113

primer lugar, si se desea extraer características de conteo y aplicar TF-IDF normalización y el modo de fila euclidiana normalización se puede hacer en una sola operación con TfidfVectorizer :

>>> from sklearn.feature_extraction.text import TfidfVectorizer 
>>> from sklearn.datasets import fetch_20newsgroups 
>>> twenty = fetch_20newsgroups() 

>>> tfidf = TfidfVectorizer().fit_transform(twenty.data) 
>>> tfidf 
<11314x130088 sparse matrix of type '<type 'numpy.float64'>' 
    with 1787553 stored elements in Compressed Sparse Row format> 

Ahora para encontrar las distancias coseno de un documento (por ejemplo, la primera en el conjunto de datos) y todos los demás sólo tiene que calcular los productos escalares de la f primer vector con todos los demás ya que los vectores tfidf ya están normalizados en filas. La matriz escasa scipy API es un poco raro (no tan flexible como las densas matrices N-dimensional numpy). Para obtener el primer vector que necesita para cortar la matriz de modo de fila para conseguir una submatriz con una sola fila:

>>> tfidf[0:1] 
<1x130088 sparse matrix of type '<type 'numpy.float64'>' 
    with 89 stored elements in Compressed Sparse Row format> 

scikit-learn ya proporciona métricas por parejas (también conocido como granos en la jerga de la máquina de aprendizaje) que trabajan para ambos densa y escasas representaciones de colecciones de vectores.En este caso, necesitamos un producto de punto que también se conoce como el núcleo lineal:

>>> from sklearn.metrics.pairwise import linear_kernel 
>>> cosine_similarities = linear_kernel(tfidf[0:1], tfidf).flatten() 
>>> cosine_similarities 
array([ 1.  , 0.04405952, 0.11016969, ..., 0.04433602, 
    0.04457106, 0.03293218]) 

Por lo tanto para encontrar los 5 primeros documentos relacionados, podemos utilizar argsort y algunos variedad rebanar negativo (documentos más relacionados tienen mayor similitud del coseno valores, por lo tanto, al final de la matriz de índices ordenados):

>>> related_docs_indices = cosine_similarities.argsort()[:-5:-1] 
>>> related_docs_indices 
array([ 0, 958, 10576, 3277]) 
>>> cosine_similarities[related_docs_indices] 
array([ 1.  , 0.54967926, 0.32902194, 0.2825788 ]) 

el primer resultado es una comprobación de validez: nos encontramos con el documento de consulta como el documento más similar con una puntuación de coseno similitud de 1 que tiene el texto siguiente :

>>> print twenty.data[0] 
From: [email protected] (where's my thing) 
Subject: WHAT car is this!? 
Nntp-Posting-Host: rac3.wam.umd.edu 
Organization: University of Maryland, College Park 
Lines: 15 

I was wondering if anyone out there could enlighten me on this car I saw 
the other day. It was a 2-door sports car, looked to be from the late 60s/ 
early 70s. It was called a Bricklin. The doors were really small. In addition, 
the front bumper was separate from the rest of the body. This is 
all I know. If anyone can tellme a model name, engine specs, years 
of production, where this car is made, history, or whatever info you 
have on this funky looking car, please e-mail. 

Thanks, 
- IL 
    ---- brought to you by your neighborhood Lerxst ---- 

El segundo documento más parecido es una respuesta que cita el mensaje original por lo tanto tiene muchas palabras comunes:

>>> print twenty.data[958] 
From: [email protected] (Robert Seymour) 
Subject: Re: WHAT car is this!? 
Article-I.D.: reed.1993Apr21.032905.29286 
Reply-To: [email protected] 
Organization: Reed College, Portland, OR 
Lines: 26 

In article <[email protected]> [email protected] (where's my 
thing) writes: 
> 
> I was wondering if anyone out there could enlighten me on this car I saw 
> the other day. It was a 2-door sports car, looked to be from the late 60s/ 
> early 70s. It was called a Bricklin. The doors were really small. In 
addition, 
> the front bumper was separate from the rest of the body. This is 
> all I know. If anyone can tellme a model name, engine specs, years 
> of production, where this car is made, history, or whatever info you 
> have on this funky looking car, please e-mail. 

Bricklins were manufactured in the 70s with engines from Ford. They are rather 
odd looking with the encased front bumper. There aren't a lot of them around, 
but Hemmings (Motor News) ususally has ten or so listed. Basically, they are a 
performance Ford with new styling slapped on top. 

> ---- brought to you by your neighborhood Lerxst ---- 

Rush fan? 

-- 
Robert Seymour    [email protected] 
Physics and Philosophy, Reed College (NeXTmail accepted) 
Artificial Life Project   Reed College 
Reed Solar Energy Project (SolTrain) Portland, OR 
+3

excelente respuesta! gracias olivier! –

+0

Una pregunta de seguimiento: si tengo una gran cantidad de documentos, la función linear_kernel en el paso 2 puede ser el cuello de botella de rendimiento, ya que es lineal al número de filas. ¿Alguna idea sobre cómo reducirla a sublineal? – Shuo

+0

Puede usar las consultas "más como esta" de Elastic Search y Solr que deberían arrojar respuestas aproximadas con un perfil de escalabilidad sublineal. – ogrisel

15

Sé que es un antiguo puesto. pero probé el paquete http://scikit-learn.sourceforge.net/stable/. aquí está mi código para encontrar la similitud del coseno. La pregunta era ¿cómo va a calcular el coseno similitud con este paquete y aquí está mi código para que

from sklearn.feature_extraction.text import CountVectorizer 
from sklearn.metrics.pairwise import cosine_similarity 
from sklearn.feature_extraction.text import TfidfVectorizer 

f = open("/root/Myfolder/scoringDocuments/doc1") 
doc1 = str.decode(f.read(), "UTF-8", "ignore") 
f = open("/root/Myfolder/scoringDocuments/doc2") 
doc2 = str.decode(f.read(), "UTF-8", "ignore") 
f = open("/root/Myfolder/scoringDocuments/doc3") 
doc3 = str.decode(f.read(), "UTF-8", "ignore") 

train_set = ["president of India",doc1, doc2, doc3] 

tfidf_vectorizer = TfidfVectorizer() 
tfidf_matrix_train = tfidf_vectorizer.fit_transform(train_set) #finds the tfidf score with normalization 
print "cosine scores ==> ",cosine_similarity(tfidf_matrix_train[0:1], tfidf_matrix_train) #here the first element of tfidf_matrix_train is matched with other three elements 

Aquí supongamos que la consulta es el primer elemento de train_set y doc1, doc2 y doc3 son los documentos que quiero rango con la ayuda de la similitud del coseno. entonces puedo usar este código.

También los tutoriales proporcionados en la pregunta fueron muy útiles. Aquí están todas las piezas para que part-I, part-II, part-III

la salida será como sigue:

[[ 1.   0.07102631 0.02731343 0.06348799]] 

aquí 1 representa que la consulta se empareja consigo misma y con los otros tres son las puntuaciones para que coincida con el consulta con los documentos respectivos.

+1

cosine_similarity (tfidf_matrix_train [0: 1], tfidf_matrix_train) ¿Qué ocurre si ese 1 se cambia a más de miles? ¿Cómo podemos manejar eso? – ashim888

8

Esto debería ayudarlo.

from sklearn.feature_extraction.text import TfidfVectorizer 
from sklearn.metrics.pairwise import cosine_similarity 

tfidf_vectorizer = TfidfVectorizer() 
tfidf_matrix = tfidf_vectorizer.fit_transform(train_set) 
print tfidf_matrix 
cosine = cosine_similarity(tfidf_matrix[length-1], tfidf_matrix) 
print cosine 

y la salida será:

[[ 0.34949812 0.81649658 1.  ]] 
+3

¿cómo se obtiene la longitud? – spicyramen

12

te voy a dar otro tutorial escrito por mí. Responde su pregunta, pero también explica por qué estamos haciendo algunas de las cosas. También traté de hacerlo conciso.

Así que tiene un list_of_documents que es simplemente una matriz de cadenas y otra document que es solo una cadena. Debe encontrar dicho documento en el list_of_documents que sea el más similar al document.

Vamos a combinar entre sí: documents = list_of_documents + [document]

Vamos a empezar con las dependencias. Se aclarará por qué usamos cada uno de ellos.

from nltk.corpus import stopwords 
import string 
from nltk.tokenize import wordpunct_tokenize as tokenize 
from nltk.stem.porter import PorterStemmer 
from sklearn.feature_extraction.text import TfidfVectorizer 
from scipy.spatial.distance import cosine 

Uno de los enfoques que se pueden usos es un enfoque bag-of-words, donde tratamos a cada palabra en el documento independiente de los demás y sólo un tiro de todos ellos juntos en la gran bolsa.Desde un punto de vista, pierde mucha información (por ejemplo, cómo se conectan las palabras), pero desde otro punto de vista simplifica el modelo.

En inglés y en cualquier otro idioma humano hay muchas palabras "inútiles" como 'a', 'the', 'in' que son tan comunes que no poseen mucho significado. Se llaman stop words y es una buena idea eliminarlos. Otra cosa que uno puede notar es que palabras como 'analizar', 'analizador', 'análisis' son realmente similares. Tienen una raíz común y todos pueden convertirse en una sola palabra. Este proceso se llama stemming y existen diferentes patrones que difieren en velocidad, agresividad, etc. Así que transformamos cada uno de los documentos en una lista de tallos de palabras sin palabras finales. También descartamos toda la puntuación.

porter = PorterStemmer() 
stop_words = set(stopwords.words('english')) 

modified_arr = [[porter.stem(i.lower()) for i in tokenize(d.translate(None, string.punctuation)) if i.lower() not in stop_words] for d in documents] 

Entonces, ¿cómo nos ayuda esta bolsa de palabras? Imagine que tenemos 3 bolsas: [a, b, c], [a, c, a] y [b, c, d]. Podemos convertirlos a vectors in the basis[a, b, c, d]. Así que terminamos con vectores: [1, 1, 1, 0], [2, 0, 1, 0] y [0, 1, 1, 1]. Lo similar es con nuestros documentos (solo los vectores serán más largos). Ahora vemos que eliminamos muchas palabras y detuvimos otras también para disminuir las dimensiones de los vectores. Aquí solo hay una observación interesante. Los documentos más largos tendrán mucho más elementos positivos que más cortos, por eso es bueno normalizar el vector. Esto se conoce como frecuencia de término TF, las personas también usaron información adicional sobre la frecuencia con la que se usa la palabra en otros documentos: frecuencia de documento inversa IDF. Juntos tenemos una métrica TF-IDF which have a couple of flavors. Esto se puede lograr con una línea en sklearn :-)

modified_doc = [' '.join(i) for i in modified_arr] # this is only to convert our list of lists to list of strings that vectorizer uses. 
tf_idf = TfidfVectorizer().fit_transform(modified_doc) 

En realidad Vectorizer allows to do a lot of things como la eliminación de palabras vacías y en minúscula. Los he hecho en un paso separado solo porque sklearn no tiene palabras vacías que no sean en inglés, pero nltk sí.

Así que tenemos todos los vectores calculados. El último paso es encontrar cuál es el más similar al último. Hay varias formas de lograrlo, una de ellas es la distancia euclidiana, que no es tan buena por el motivo discussed here. Otro enfoque es cosine similarity. Iteramos todos los documentos y calcular el coseno similitud entre el documento y el último:

l = len(documents) - 1 
for i in xrange(l): 
    minimum = (1, None) 
    minimum = min((cosine(tf_idf[i].todense(), tf_idf[l + 1].todense()), i), minimum) 
print minimum 

Ahora mínimo tendrá información sobre el mejor documento y su puntaje.

+2

Firmar, esto no es lo que estaba pidiendo: buscar la mejor consulta dada por el documento, no el "mejor documento" en un corpus. Por favor, no lo hagas, las personas como yo perderán el tiempo tratando de usar tu ejemplo para la tarea op y te arrastrarán a la locura de cambio de tamaño de la matriz. – minerals

+0

¿Y cómo es diferente? La idea es completamente la misma. Extraiga funciones, calcule la distancia del coseno entre una consulta y documentos. –

+0

Está calculando esto en matrices de formas iguales, pruebe con un ejemplo diferente, donde tiene una matriz de consulta que es de diferente tamaño, conjunto de trenes de operaciones y conjunto de pruebas. No pude modificar tu código para que funcione. – minerals

Cuestiones relacionadas