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

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

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

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

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 ответов

Решение

С помощью комментария @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]]

Прежде всего, если вы хотите извлечь функции подсчета и применить нормализацию 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 уже нормализованы по строкам. API Scipy Sparse Matrix немного странный (не такой гибкий, как плотные N-мерные массивы Numpy). Чтобы получить первый вектор, вам нужно разрезать матрицу по строкам, чтобы получить подматрицу с одной строкой:

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

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

>>> 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, часть-II, часть-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

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

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

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], Аналогичная вещь с нашими документами (только векторы будут дольше). Теперь мы видим, что мы удалили много слов и остановили другие, чтобы уменьшить размеры векторов. Здесь есть просто интересные наблюдения. В более длинных документах будет больше положительных элементов, чем в более коротких, поэтому лучше всего нормализовать вектор. Это называется термином частота TF, люди также использовали дополнительную информацию о том, как часто слово используется в других документах - обратная частота документа 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)

На самом деле векторизатор позволяет делать много вещей, таких как удаление стоп-слов и нижний регистр. Я сделал их в отдельном шаге только потому, что у 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

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

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

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

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

def create_tokenizer_score (new_series, train_series, tokenizer): "" "возвращает значение tf idf для каждой возможной пары документов Args: new_series (pd.Series): новые данные (для сравнения с данными поезда) train_series (pd.Series): train data (для соответствия преобразователю tf-idf) Возвращает: pd.DataFrame "" "

    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

Другие вопросы по тегам