Python: tf-idf-cosine: чтобы найти сходство документов
я последовал за учебник, который был доступен в Часть 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()
в результате выше кода у меня есть следующая матрица
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 уже нормализованы по строкам. В составляющей разреженные матрицы 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.k.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,Часть 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
один из подходов, который может быть использует это "мешок слов" подход, где мы рассматриваем каждое слово в документе независимо от других и просто бросаем их все вместе в большой мешок. С одной стороны, он теряет много информации (например, как связаны слова), но с другой точки зрения это делает модель простой.
на английском языке и в любом другом человеке есть много "бесполезных" слов, таких как "а", "в", которые настолько распространены, что они не обладают большим значением. Они называются стоп-слов и это хорошая идея, чтобы удалить их. Другое дело, что можно заметить, что такие слова, как "анализ", "анализатор", "анализ" действительно похожи. Они имеют общий корень и могут быть преобразованы в одно слово. Этот процесс называется 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]
. То же самое с нашими документами (только векторы будут длиннее). Теперь мы смотрите, что мы удалили много слов и остановили другие также, чтобы уменьшить размеры векторов. Здесь есть просто интересное наблюдение. Более длинные документы будут иметь больше положительных элементов, чем более короткие, поэтому хорошо нормализовать вектор. Это называется термином частота 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
теперь минимум будет иметь информацию о лучшем документе и его оценка.
С помощью комментария @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. ]]
вот функция, которая сравнивает ваши тестовые данные с данными обучения, с трансформатором Tf-Idf, оснащенным данными обучения. Преимущество заключается в том, что вы можете быстро повернуть или сгруппировать, чтобы найти 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