Python: tf-idf-Косинус: найти сходство документов

я следовал учебнику, который был доступен в Часть 1 & Часть 2. К сожалению, у автора не было времени для заключительного раздела, который включал использование косинусного сходства, чтобы фактически найти расстояние между двумя документами. Я следил за примерами в статье с помощью следующей ссылки из stackoverflow, включен код, упомянутый в приведенной выше ссылке (только для того, чтобы облегчить жизнь)

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

как a в результате приведенного выше кода у меня есть следующая матрица

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]]

Я не уверен, как использовать этот вывод для вычисления косинусного сходства, я знаю, как реализовать косинусное сходство относительно двух векторов одинаковой длины, но здесь я не уверен, как идентифицировать два вектора.

6 ответов


во-первых, если вы хотите извлечь функции подсчета и применить нормализацию TF-IDF и нормализацию евклидовой строки, вы можете сделать это за одну операцию с 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>

теперь, чтобы найти косинусные расстояния одного документа (например, первого в наборе данных) и всех других, вам просто нужно вычислить точечные произведения первого вектора со всеми другими, поскольку векторы tfidf уже нормализованы по строкам. Scipy sparse matrix API немного странный (не такой гибкий как плотные N-мерные массивы numpy). Чтобы получить первый вектор, вам нужно срезать матрицу по строке, чтобы получить подматрицу с одной строкой:

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

scikit-learn уже предоставляет попарные метрики (a.к. a. ядра в машинном обучении), которые работают как для плотных, так и для разреженных представлений векторных коллекций. В этом случае нам нужен точечный продукт, который также известен как линейное ядро:

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

Следовательно, чтобы найти 5 лучших связанных документов, мы можем использовать argsort и некоторые отрицательные срезы массива (большинство связанных документов имеют самые высокие значения косинусного сходства, следовательно, в конце массива отсортированных индексов):

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

первым результатом является проверка здравомыслия: мы находим документ запроса как наиболее похожий документ с оценкой косинусного сходства 1, который имеет следующий текст:

>>> print twenty.data[0]
From: lerxst@wam.umd.edu (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 ----

второй наиболее похожий документ-это ответ, который цитирует исходное сообщение, следовательно, имеет много общих слов:

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

In article <1993Apr20.174246.14375@wam.umd.edu> lerxst@wam.umd.edu (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              rseymour@reed.edu
Physics and Philosophy, Reed College    (NeXTmail accepted)
Artificial Life Project         Reed College
Reed Solar Energy Project (SolTrain)    Portland, OR

Я знаю, это старый пост. но я попробовал http://scikit-learn.sourceforge.net/stable/ пакет. вот мой код, чтобы найти Косинус сходство. Вопрос был как вы вычислить Косинус сходство с этим пакетом и вот мой код

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

здесь предположим, что запрос является первым элементом train_set и doc1, doc2 и doc3-это документы, которые я хочу ранжировать с помощью косинусного сходства. тогда я могу использовать этот код.

также учебники, представленные в вопросе, были очень полезны. Вот все детали для него Часть I,часть-второй,Часть III в

вывод будет следующим :

[[ 1.          0.07102631  0.02731343  0.06348799]]

здесь 1 представляет, что запрос сопоставляется с самим собой, а остальные три-оценки для сопоставления запроса с соответствующими документами.


позвольте мне дать вам еще один учебник, написанный мной. Он отвечает на ваш вопрос, но также объясняет, почему мы делаем некоторые вещи. Я также постарался сделать его кратким.

Итак, у вас есть list_of_documents который является просто массив строк и другой document это просто строка. Вам нужно найти такой документ из list_of_documents это наиболее похоже на document.

давайте объединим их вместе: documents = list_of_documents + [document]

давайте начнем с зависимости. Станет ясно, почему мы используем каждый из них.

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

один из подходов, который может быть использует это "мешок слов" подход, где мы рассматриваем каждое слово в документе независимо от других и просто бросаем их все вместе в большой мешок. С одной точки зрения, он теряет много информации (например, как связаны слова), но с другой точки зрения это делает модель простой.

в английском и в любом другом человеческом есть много "бесполезных" слов, таких как "а", "в", которые настолько распространены, что не имеют большого значения. Они называются стоп-слов и это хорошая идея, чтобы удалить их. Еще одна вещь, которую можно заметить, - это то, что такие слова, как "анализ", "анализатор", "анализ", действительно похожи. Они имеют общий корень и могут быть преобразованы в одно слово. Этот процесс называется stemming и существуют различные модули, которые различаются по скорости, агрессивность и так далее. Таким образом, мы преобразуем каждый из документов в список стеблей слов без стоп-слов. Также мы отбрасываем все знаки препинания.

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]

Итак, как этот мешок слов поможет нам? Представьте, что у нас есть 3 сумки:[a, b, c], [a, c, a] и [b, c, d]. Мы можем преобразовать их в векторы в базисе [a, b, c, d]. Таким образом, мы получаем векторы:[1, 1, 1, 0], [2, 0, 1, 0] и [0, 1, 1, 1]. То же самое с нашими документами (только векторы будут длиннее). Теперь мы смотрите, что мы удалили много слов и остановили другие также, чтобы уменьшить размеры векторов. Здесь просто интересное наблюдение. Более длинные документы будут иметь больше положительных элементов, чем более короткие, поэтому приятно нормализовать вектор. Это называется термином frequency TF, люди также использовали дополнительную информацию о том, как часто слово используется в других документах - inverse document frequency IDF. Вместе у нас есть метрика TF-IDF, которые имеют пару ароматы. Это может быть достигнуто с помощью одной строки в 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)

на самом деле vectorizer позволяет делать много вещей как удаление стоп-слов и строчной. Я сделал их в отдельном шаге только потому, что у sklearn нет неанглийских стоп-слов, а у nltk есть.

таким образом, мы рассчитали все векторы. Последний шаг-найти, какой из них наиболее похож на последний. Существуют различные способы добиться этого, один из них евклидово расстояние, которое не так велико по причине здесь обсуждается. Другой подход Косинус сходство. Мы повторяем все документы и вычисляем косинусное сходство между документом и последним:

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

теперь минимум будет иметь информацию о лучшем документе и его оценка.


С помощью комментария @excray мне удается выяснить ответ, что нам нужно сделать, это на самом деле написать простой цикл for для итерации по двум массивам, которые представляют данные поезда и тестовые данные.

сначала реализуйте простую лямбда-функцию для хранения формулы для вычисления Косинуса:

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

а затем просто напишите простой цикл for для итерации по вектору to, логика для каждого " для каждого вектора в trainVectorizerArray, вы должны найти косинусное сходство с вектором в 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()

Отсюда вывод:

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]]

Это должно помочь вам.

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

и выход будет:

[[ 0.34949812  0.81649658  1.        ]]

здесь функция которая сравнивает ваши данные по теста против данных по тренировки, с трансформатором ТФ-ИДФ приспособленным с данными по тренировки. Преимущество заключается в том, что вы можете быстро свернуть или сгруппировать, чтобы найти n ближайших элементов, и что вычисления идут вниз по матрице.

def create_tokenizer_score(new_series, train_series, tokenizer): """ верните оценку TF idf каждой возможной пары документов Параметр args: new_series (pd.Серии): новые данные (Для сравнения с данными поезда) train_series (pd.Серия): данные по поезда (приспосабливать трансформатор ТФ-ИДФ) Возвращается: палладий.Рамка данных """

    train_tfidf = tokenizer.fit_transform(train_series)
    new_tfidf = tokenizer.transform(new_series)
    X = pd.DataFrame(cosine_similarity(new_tfidf, train_tfidf), columns=train_series.index)
    X['ix_new'] = new_series.index
    score = pd.melt(
        X,
        id_vars='ix_new',
        var_name='ix_train',
        value_name='score'
    )
    return score
train_set = pd.Series(["The sky is blue.", "The sun is bright."])
test_set = pd.Series(["The sun in the sky is bright."])
tokenizer = TfidfVectorizer() # initiate here your own tokenizer (TfidfVectorizer, CountVectorizer, with stopwords...)
score = create_tokenizer_score(train_series=train_set, new_series=test_set, tokenizer=tokenizer)
score

оценка ix_new ix_train 0 0 0 0.617034 1 0 1 0,862012