Pull to refresh

Рекомендательная система на коленке как средство против экзистенциального кризиса

Reading time 6 min
Views 12K
Может быть отсылка к экзистенциальному кризису звучит слишком громко, но лично для меня проблема поиска и выбора (или выбора и поиска, это имеет значение) как в мире интернета так и в мире простых вещей по мучениям иногда приближается к нему. Выбор фильма на вечер, книги неизвестного автора, сосисок в магазине, нового утюга — дикое количество вариантов. Особенно когда не очень знаешь чего хочешь. Да и когда знаешь, но не можешь попробовать — тоже не праздник — мир разнообразен и все сразу не перепробуешь.

image

Рекомендательные системы сильно помогают в выборе, но не везде и не всегда так как хотелось бы. Часто не учитывается семантика содержания. Кроме того, во весь рост встает проблема "длинного хвоста", когда рекомендации сосредоточены только на самых популярных позициях, а интересные, но не очень популярные в массе вещи ими не охвачены.

Cвой эксперимент в этом направлении я решил начать с поиска интересных текстов взяв для этого довольно небольшое, но пишущее сообщество авторов, которые еще остались на блоговой платформе Живой Журнал. О том как сделать собственную рекомендательную систему а в результате получить еще и помощник в выборе вина на вечер — под катом.

Почему Живой Журнал?


Ну, во-первых, это блог-платформа и часто там пишут интересные вещи. Даже несмотря на миграцию пользователей в фейсбук в ЖЖ еще осталось по моим оценкам порядка 35-40 тысяч активных блогов. Вполне посильный объем для экспериментов без привлечения инструментов big data. Ну и сам процесс краулинга весьма прост, в отличии от работы с тем же фейсбуком.

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

Краулинг


Как уже говорилось краулинг блогов на платформе ЖЖ дело весьма простое — можно легко получить тексты записей, картинки (если вдруг они нужны) и даже комментарии к постам с их структурой. Надо сказать что именно система иерархического комментирования как на Хабре и удерживает некоторых авторов на этой платформе.

Итак, несложный краулер на Perl'е, код которого можно взять у меня на github'е за пару недель неспешной работы выгрузил мне тексты и комментарии из примерно где-то 40 тысяч блогов за период лето 2017 и лето 2016. Эти два периода выбраны для параллельного сравнения активности в ЖЖ год от года.

Почему Perl
Потому что мне нравится этот язык, я на нем работаю и считаю его очень подходящим для задач с непредсказуемым течением и результатом — как раз то что нужно в экспериментальном information retrieval и data mining когда не нужна максимальная производительность, но нужна максимальная гибкость. Для обработки данных лучше, конечно, использовать python с кучей библиотек под него.

Итак, исходные данные для исследования получились примерно такие:

— Период анализа: 6 месяцев
— Авторов: примерно 45 тыс.
— Публикаций: примерно 2.5 млн.
— Слов: примерно 740 млн.

Аналитическая модель и обработка


image


Так как я не data scientist (хотя и смотрю конкурсы на kaggle время от времени) то и алгоритмы выбирались весьма простые — считать близость авторов как косинусное расстояние между векторами, характеризующими их тексты.

Было несколько подходов к построению векторов с характеристиками текстов авторов:

1. Очевидная — строим вектора с метрикой TF-IDF по словам собранного текстового корпуса. Недостаток — векторное пространство получается очень уж многомерным (~60 тыс.) и сильно разреженным.

2. Хитрая — кластеризовать текстовый корпус с помощью word2vec и взять вектора как проекцию текстов на получившиеся кластеры. Достоинство — относительно короткие вектора по числу кластеров (обычно ~1000). Недостаток — надо обучать word2vec, подбирать число кластеров для кластеризации, думать как проецировать текст на кластеры, заморочено.

3. И тоже неплохая — строить вектора с gensim doc2vec. Практически то же самое что и второй вариант, но вид с боку и все проблемы кроме обучения уже решены. Но на python. А мне этого не хотелось.

В итоге была выбрана первая модель дополненная методами LSA (Латентно-семантический анализ) в виде SVD разложения полученной матрицы «документы» — «термы». Метод много раз описывался на Хабре, останавливаться не буду.

Для лемматизации термов, а точнее приведения русских слов к базовой форме, использовалась утилита mystem от Яндекса и обертка вокруг нее, позволяющая объединять несколько документов в единый файл для скармливания его на вход утилиты. Можно было вызывать ее на каждый документ, что я и делал раньше, но это не очень эффективно. В корпус отбирались существительные, прилагательные, глаголы и наречия длинной больше 3-х символов и с частотой употребления более 100 по всему корпусу.

Вот, например, топ существительных, приведенных к базовой форме:

1. Человек
2. Время
3. Россия
4. День
5. Страна

SVD разложение делалось с помощью внешней утилиты SVDLIBC, которую пришлось пропатчить на предмет использования очень больших матриц, так как она падала при попытке создать «плотную матрицу» с количеством элементов больше чем максимальное значения типа int. Но утилита хороша тем что она умеет работать в разреженными матрицами и вычислять только необходимое количество сингулярных векторов, что существенно экономит время. Кстати, для Perl есть модуль PDL::SVDLIBC основанный на коде этой утилиты.

Выбор длины вектора (количества сингулярных векторов в разложении) делался «экспертным анализом» (читай на глаз) на тестовом множестве журналов. В итоге остановился на n=500.

Результат анализа — матрица коэффициентов схожести авторов.

Визуализация результата


На начальном этапе хотелось глазами оценить структуру тематических сообществ чтобы понять что с этим делать дальше.

Я попытался использовать кластеризацию и визуализацию в виде самоорганизующихся карт (SOM) из аналитического пакета Deductor подав на вход вектора из 500 значений после SVD. Результат заставил себя ждать (многопоточность? нет, не слышали) и не порадовал — прослеживалось три больших кластера и достаточно плотным размещением авторов по всему полю карты.

Было очевидно что матрицу расстояний можно преобразовать в граф сохраняя ребра с значением коэффициента близости больше определенного порога. Написав конвертер матрицы в граф формата GDF (он показался мне наиболее подходящим для визуализации разных параметров), скормив этот граф пакету визуализации графов Gephi и поэкспериментировав с разными layout'ами и параметрами отображений получил от такую картинку:

image


При клике на картинку и вот тут можно помедитировать на нее в разрешении 3000x3000. И если вы иногда заходите в блоги ЖЖ возможно вы найдете там знакомые имена.

Картинка эта вполне соответствует моим представлением о тематической структуре авторов хотя и не идеальна (слишком плотная). Осталось малое — дать себе и другим возможность получать рекомендации на основе персональных авторских предпочтений: задал имя автора и получил рекомендации похожих на него.

С этой целью была написана небольшая визуализация областей графа прилегающих к интересующему автору на d3js с использованием force layout. Для каждого автора (а их чуть больше 40 тыс. как я уже говорил) был сгенерирован json-файл с описанием близлежащих узлов графа, «теговая аннотация» на основе отсортированного списка популярных слов для этого автора и аналогичная аннотация его группы, в которую входят близлежащие узлы. Аннотации должны дать некоторое понимание о чем же пишет автор и какие темы актуальны у похожих на него блогеров.

Получилась вот такая мини-поисковая система similarity.me с рекомендациями и графической картой:

image

Что можно улучшить?


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

А что насчет винишка?


Заглавная картинка — это как раз винная карта по вкусовым характеристикам ))

Побочным результатом исследования стала мысль «а не использовать этот подход для рекомендаций каких-нибудь товаров по их описаниям». И для этого идеально подошли дегустационные заметки винного критика Дениса Руденко в его блоге в ЖЖ «Ежедневный Винный Телеграф». К сожалению он давно забросил их публикацию, но накопившиеся там более 2000 описаний вин — отличный материал для такого эксперимента.

Особой предобработки текстов не делалось. Скормив их системе получил вкусовую карту 700 лучших вин с рекомендацией похожих:

image

На ней можно подсвечивать вина из самых популярных сортов винограда и по их субъективным (с точки зрения дегустатора) вкусам.

В планах немного оптимизировать рекомендации за счет преобразования некоторых прилагательных в существительные, например «черничный» — «черника». И разместить на карте все более чем 2000 продегустированных вин.

Вот как-то так можно бороться с проблемой выбора (если вдруг она актуальна для вас) расширив эти подходы и на другие области.
Tags:
Hubs:
+15
Comments 7
Comments Comments 7

Articles