django

Про смарт агрегатор

  • пятница, 4 июля 2014 г. в 03:10:43
http://habrahabr.ru/post/228475/

Хотелось бы написать позитивный текст о том, как пришла идея в голову и за пару часов ее реализовал, уложившись в 40 строчек. Но, на самом деле, это довольно старая история и первые мысли на эту тему родились еще во времена, когда появился Google Reader. Потому что Google Reader был первым агрегатором, который не напрягал, и как любая хорошая вешь, вызывал желание чтобы он становился еще лучше. И проблема, как и у всех читающих людей, была одна ‒ обилие информационного мусора. Потом появились социальные сети и казалось, что они, наконец-то решат эту проблему, но на деле они ее только усугубили. Так, со временем сформировалось видение агрегатора, которым хотелось обладать, и как минимум он должен был уметь:
  • агрегировать не только RSS фиды, но и ленты из социальных сетей
  • суммаризировать текст так, чтобы из короткой словесной выжимки была понятна суть
  • тагировать статьи, то есть выбирать из статей ключевые слова с возможностью навигации по ним
  • рейтинговать статьи относительно пользовательских действий и временного фактора
  • выводить тренды не только глобально, относительно активности всех пользователей, но и локально, относительно интересов конкретного пользователя

Так как на горизонте ничего подобного не предвиделось, решено было попробовать реализовать это самому. Как это делалось и что из этого получилось ‒ под катом.

Ридер состоит из четырех основопологающих блоков:
  • обработка фидов (на данный момент реализованы RSS и Twitter) и извлечение документов (на данный момент поддерживаются русский и английский языки)
  • очистка документов от HTML-мусора и извлечение заголовков
  • суммаризация документа
  • тагирование документа (извлечение ключевых слов)
  • вывод документов на пользователя согласно их рейтингу
  • пользовательские и глобальные тренды

Суммаризатор


Главная цель суммаризатора ‒ сократить текст, но при этом не потерять суть написанного. После новостей о покупке «Summly» за 30 млн. казалось, что тема предстоит еще та, но на самом деле все оказалось достаточно просто. Фактически первый запрос к Гуглу расставил все точки над i, и решено было не мудрить и остановиться на TextRank. Идея, на самом деле, достаточно проста, и как видно из названия, основные идеи позаимствованы из PageRank от Гугла. Текст преобразуется в граф и каждой вершине этого графа присваивается какой либо рейтинг, исходя из двух составляющих:
  1. количество ребер, идущих из других вершин
  2. рейтинг ребер

Фактически как и PageRank, одни вершины рекомендуют другие и наше дело просто просчитать рейтинги этих вершин.

Единственное, что я в нес своего в этот алгоритм ‒ это не учитывал стоп слова, частицы, предлоги и прочее, что не несет смысловой нагрузки.

Разумеется перед суммаризацией текст необходимо очистить от различного HTML-мусора (хедеры, футеры, реклама, комментарии и прочее). Как это делается неоднократно описывалось и ничего нового я не привнес. Использовался стандартный и всеми используемый алгоритм от Readability.

Ключевые слова (тэги)


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

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

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

На данном этапе результаты вполне себе неплохие, хотя и есть куда расти. Как минимум, для полноты картины необходимы еще три вещи:
  1. машинообучаемость
  2. работа с именами собственными и географическими понятиями
  3. морфология на уровне предложения

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

Но, как минимум, это сделать реально, и вопрос только во времени.

Рейтингование текстов


По сути это была самая простая задача. Есть Reddit, где все это превосходно работает, и мало того, хорошо описано, хотя и редко где используется. В свое время я даже писал об этом на Хабре. Если не уходить далеко от темы, то речь идет о старом добром алгоритме Вилсона, а в качестве переменных используем показы/просмотры плюс временной фактор.

Немного про технологии


В принципе, все достаточно стандартно:
  • Python3
  • Django
  • Celery
  • Postgresql
  • Redis
  • Elasticsearch

Весь программный код реализован на Python. Celery отрабатывает таски и складывает их в очереди дожидаться исполнения. Redis хранит очереди, кэши и сессии пользователей. Elasticsearch используется в несколько необычной роли. Не смотря на то, что это все-таки поисковый движок, я его использую в качестве NoSQL хранища. Фактически он отрабатывает запросы, а на выходе выдает массивы ключей, которые уходят напрямую во фронтэнд и непосредственно там обрабатываются. И уже сам фронтэнд, оперируя полученными ключами, запрашивает необходимые данные из Postgresql. Роль Postgresql здесь ‒ писать и выполнять до примитива простые запросы. Немного запутанно, но производительность данное решение ускорило на порядки.

И немного про NLTK. Скажу честно, если бы не NLTK, то навряд ли все написанное выше увидело бы свет. Все эксперименты проводились на нем, но в дальнейшем от него пришлось отказаться, хотя практически все, что касается обработки текстов позаимствовано из него. Главная причина ‒ это производительность, и очень хотелось третий питон. На данный момент идеологически все фактически так же, но работа с корпусами вынесена на сторону Elasticsearch.

P.S.


На данный момент сервис существует в виде прототипа или, проще говоря, реализованы фундаментальные вещи, но вышивания крестиком еще предстоит много. Там нет ни дизайна, ни юзабилити, и уверен, что всплывет куча проблем и на фронтэнде, и на бэкэнде. Как вы сами догадались, работа ведется в свободное от основной работы время, фактически по вечерам и ночам. Конечно, очень хотелось бы заниматься в фултайм режиме, но понимаю, что речь идет не о дэйтинге с геошарингом, хотя, и не был бы против появления заинтересованных людей. Так что на данный момент процесс будет проходит в прежнем режиме и никакие временные рамки пока определить не могу. Если тема интересна, то буду продолжать держать в курсе происходящего. И был бы очень благодарен за конструктивный фидбэк. Спасибо.

www.feedo.re