Python: tf-idf-cosine: щоб знайти схожість документа


93

Я дотримувався підручника, який був доступний у Частинах 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]]

Я не впевнений, як використовувати цей вихід для обчислення подібності косинусів, я знаю, як реалізувати подібність косинусів щодо двох векторів однакової довжини, але тут я не впевнений, як ідентифікувати два вектори.


3
Для кожного вектора у trainVectorizerArray потрібно знайти подібність косинуса з вектором у testVectorizerArray.
випромінювання

@excray Дякую, з вашим корисним зауваженням мені вдалося це зрозуміти, чи слід мені відповідати?
доповнення крапками з комою

@excray Але у мене є невеличке запитання, насправді обчислення tf * idf не мають для цього ніякої користі, оскільки я не використовую кінцеві результати, показані в матриці.
доповнення крапками з комою

4
Ось 3-та частина підручника, який ви цитуєте, який детально відповідає на ваше запитання pyevolve.sourceforge.net/wordpress/?p=2497
Клеман Рено

@ ClémentRenaud, я перейшов із посиланням, яке ви вказали, але оскільки мої документи більші, він починає видавати MemoryError. Як ми можемо це впоратись?
ashim888

Відповіді:


172

По-перше, якщо ви хочете витягти функції підрахунку та застосувати нормалізацію 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 вже нормалізовані по рядках.

Як пояснив у коментарях Кріс Кларк, тут подібність косинусів не враховує величину векторів. Нормовані по рядках мають величину 1, тому лінійного ядра достатньо для обчислення значень подібності.

API розрідженої матриці scipy трохи дивний (не такий гнучкий, як щільні 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

Наступне запитання: якщо у мене дуже велика кількість документів, функція linear_kernel на кроці 2 може бути вузьким місцем продуктивності, оскільки вона є лінійною до кількості рядків. Будь-які думки про те, як звести його до сублінійного?
Шуо,

Ви можете використовувати "більш подібні" запити Elastic Search and Solr, які повинні дати приблизні відповіді з профілем підлінійної масштабованості.
ogrisel

7
Чи дасть вам це косинусну схожість кожного документа з кожним іншим документом, а не лише першим cosine_similarities = linear_kernel(tfidf, tfidf):?
ionox0

2
Так, це дасть вам квадратну матрицю попарних подібностей.
ogrisel

10
Якщо інші задавались питанням, як і я, у цьому випадку linear_kernel еквівалентно схожості косинусів, оскільки TfidfVectorizer виробляє нормалізовані вектори. Див. Примітку в документах: scikit-learn.org/stable/modules/metrics.html#cosine-s similarity
Кріс Кларк

22

З допомогою коментаря @ 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]]

1
приємно..Я теж вчусь з самого початку, і за вашими питаннями та відповідями найпростіше дотримуватися. Я думаю, що ви можете використовувати np.corrcoef () замість вашого методу рулону.
wbg

Яка мета transformer.fitоперацій та tfidf.todense()? Ви отримали свої значення подібності з циклу, а потім продовжуєте робити tfidf? Де використовується ваше обчислене значення косинуса? Ваш приклад бентежить.
корисні копалини

Що саме повертається косинус, якщо ви не проти пояснити. У вашому прикладі ви отримуєте 0.408і 0.816, що це за значення?
buydadip

20

Я знаю його старий пост. але я спробував пакет 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 означає, що запит відповідає самому собі, а решта три - оцінки для відповідності запиту відповідним документам.


1
косинус_подібність (tfidf_matrix_train [0: 1], tfidf_matrix_train) Що робити, якщо цей 1 змінити на більше, ніж тисячі. Як ми можемо з цим впоратися ??
ashim888

1
як поводитисьValueError: Incompatible dimension for X and Y matrices: X.shape[1] == 1664 while Y.shape[1] == 2
pyd

17

Дозвольте мені дати вам ще один підручник, написаний мною. Він відповідає на ваше запитання, але також дає пояснення, чому ми робимо деякі речі. Я також намагався зробити це стислим.

Отже, у вас є a 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

Тепер мінімум матиме інформацію про найкращий документ та його оцінку.


3
Підпишіть, це не те, що просив ОП: пошук найкращого документу за запитом - не „найкращий документ” у корпусі. Будь ласка, не робіть цього, ppl, як і я, буде витрачати час на те, щоб використати ваш приклад для оперативного завдання і потрапити в божевілля, що змінює розмір матриці.
корисні копалини

І чим воно відрізняється? Ідея абсолютно однакова. Витягніть функції, обчисліть косинусну відстань між запитом та документами.
Сальвадор Далі

Ви обчислюєте це на матрицях однакових фігур, спробуйте інший приклад, де у вас є матриця запитів, яка має різний розмір, набір поїздів оператора та набір тестів. Я не зміг змінити ваш код, щоб він працював.
корисні копалини

@SalvadorDali Як зазначалося, вищесказане відповідає на інше питання: ви припускаєте, що запит та документи є частиною одного корпусу, що є неправильним. Це призводить до неправильного підходу до використання відстаней векторів, отриманих від одного корпусу (з однаковими розмірами), що, як правило, не має бути. Якщо запит та документи належать різним корпусам, вектори, які вони походять, можуть жити не в одному просторі, і обчислення відстаней, як ви робите вище, не має сенсу (вони навіть не матимуть однакову кількість розмірів).
генеровано

12

Це має вам допомогти.

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

9
як ви отримуєте довжину?
gogasca 02

3

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

def create_tokenizer_score(new_series, train_series, tokenizer):
    """
    return the tf idf score of each possible pairs of documents
    Args:
        new_series (pd.Series): new data (To compare against train data)
        train_series (pd.Series): train data (To fit the tf-idf transformer)
    Returns:
        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    score
0   0       0       0.617034
1   0       1       0.862012

pandas.pydata.org/pandas-docs/stable/reference/api/… пояснює, що робить pd.melt
Golden Lion

для індексу в np.arange (0, len (score)): value = score.loc [index, 'score']
Золотий Лев
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.