Статьи

scitkit-learn: сходство TF / IDF и косинуса для статей по информатике

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

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

Давайте быстро проверим определение Wikipedia контентной фильтрации :

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

Другими словами, эти алгоритмы пытаются рекомендовать элементы, которые похожи на те, которые пользователь любил в прошлом (или рассматривает в настоящем).

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

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

1
2
3
for each paper:
  generate a TF/IDF vector of the terms in the paper's title
  calculate the cosine similarity of each paper's TF/IDF vector with every other paper's TF/IDF vector

Это очень легко сделать с помощью библиотеки Python scikit-learn, и я фактически выполнил первую часть этого процесса, выполняя некоторый предварительный анализ интересных фраз в телешоу «Как я встретил вашу маму» .

Давайте начнем.

У нас есть один файл на бумагу, который содержит название статьи. Сначала нам нужно перебрать этот каталог и создать массив, содержащий документы:

1
2
3
4
5
6
import glob
  
corpus = []
for file in glob.glob("papers/*.txt"):
    with open(file, "r") as paper:
        corpus.append((file, paper.read()))

Далее мы построим матрицу TF / IDF для каждой статьи:

1
2
3
4
from sklearn.feature_extraction.text import TfidfVectorizer
  
tf = TfidfVectorizer(analyzer='word', ngram_range=(1,3), min_df = 0, stop_words = 'english')
tfidf_matrix =  tf.fit_transform([content for file, content in corpus])

Далее мы напишем функцию, которая найдет нам первые n похожих статей на основе косинусного сходства:

1
2
3
4
5
6
from sklearn.metrics.pairwise import linear_kernel
  
def find_similar(tfidf_matrix, index, top_n = 5):
    cosine_similarities = linear_kernel(tfidf_matrix[index:index+1], tfidf_matrix).flatten()
    related_docs_indices = [i for i in cosine_similarities.argsort()[::-1] if i != index]
    return [(index, cosine_similarities[index]) for index in related_docs_indices][0:top_n]

Давайте попробуем это:

01
02
03
04
05
06
07
08
09
10
11
>>> corpus[1619]
('papers/221215.txt', 'TOTEM: a reliable ordered delivery protocol for interconnected local-area networks')
  
>>> for index, score in find_similar(tfidf_matrix, 1619):
       print score, corpus[index]
  
0.917540397202 ('papers/852338.txt', 'A reliable ordered delivery protocol for interconnected local area networks')
0.248736845733 ('papers/800897.txt', 'Interconnection of broadband local area networks')
0.207309089025 ('papers/103726.txt', 'High-speed local area networks and their performance: a survey')
0.204166719869 ('papers/161736.txt', 'High-speed switch scheduling for local-area networks')
0.198514433132 ('papers/627363.txt', 'Algorithms for Distributed Query Processing in Broadcast Local Area Networks')

Это очень хорошо для поиска дубликатов документов!

01
02
03
04
05
06
07
08
09
10
11
>>> corpus[1599]
('papers/217470.txt', 'A reliable multicast framework for light-weight sessions and application level framing')
  
>>> for index, score in find_similar(tfidf_matrix, 1599):
       print score, corpus[index]
  
1.0            ('papers/270863.txt', 'A reliable multicast framework for light-weight sessions and application level framing')
0.139643354066 ('papers/218325.txt', 'The KryptoKnight family of light-weight protocols for authentication and key distribution')
0.134763799612 ('papers/1251445.txt', 'ALMI: an application level multicast infrastructure')
0.117630311817 ('papers/125160.txt', 'Ordered and reliable multicast communication')
0.117630311817 ('papers/128741.txt', 'Ordered and reliable multicast communication')

Но иногда он идентифицирует дубликаты, которые не идентичны:

01
02
03
04
05
06
07
08
09
10
11
>>> corpus[5784]
('papers/RFC2616.txt', 'Hypertext Transfer Protocol -- HTTP/1.1')
  
>>> for index, score in find_similar(tfidf_matrix, 5784):
       print score, corpus[index]
  
1.0 ('papers/RFC1945.txt', 'Hypertext Transfer Protocol -- HTTP/1.0')
1.0 ('papers/RFC2068.txt', 'Hypertext Transfer Protocol -- HTTP/1.1')
0.232865694216 ('papers/131844.txt', 'XTP: the Xpress Transfer Protocol')
0.138876842331 ('papers/RFC1866.txt', 'Hypertext Markup Language - 2.0')
0.104775586915 ('papers/760249.txt', 'On the transfer of control between contexts')

Сказав это, если вы читали и любили HTTP 1.0 RFC, HTTP 1.1 RFC, вероятно, не плохая рекомендация.

Очевидно, есть также некоторые документы, которые идентифицируются как похожие, но это не так. Я создал CSV-файл, содержащий 5 одинаковых статей для каждой статьи, если сходство превышает 0,5. Вы также можете увидеть скрипт, который генерирует этот файл на github.

На данный момент это все, что я имею, но я собираюсь изучить пару вещей:

  • Как мы узнаем, хороши ли предложения сходства? Как мы измеряем хорошо? Будет ли использование вектора подсчета терминов работать лучше, чем TF / IDF?
  • Сходство на основе тезисов, а также / вместо названий

Весь код из этого поста для вычисления сходства и записи их в CSV также есть на github, так что не стесняйтесь поиграть с ним.