В конце прошлого года я поучаствовал в хакатоне "Лидеры цифровой трансформации" при поддержке Правительства Москвы. Мы решали задачу от Департамента культуры - рекомендательную систему для его услуг, то есть книг в библиотеках, а также кружков и мероприятий в культурных центрах. Особая пикантность в том, что по одним из этих сервисов нужно было рекомендовать другие. Наше решение заняло только второе место, но делать его было познавательно.
О хакатоне
Вообще, хакатон состоял из 10 направлений, и по каждому направлению первое место получало в награду миллион, а второе - ничего. Так что второе место занять было очень обидно. Сам формат хакатона был стандартный - два дня напряжённой работы в команде, потом презентация. Приятно, что в ходе этих двух дней было несколько регулярных созвонов с организаторами хакатона и авторами задачи. Неприятно, что на всех этих созвонах организаторы наотрез отказывались озвучить критерии оценки или какие-то ожидания от хорошего решения. Довольно сложно соревноваться, когда не понимаешь, чем именно вы соревнуетесь. И ещё не хватило открыть API с этими самыми сервисами, на что изначально организаторы намекали - вместо этого пришлось пользоваться данными, выгруженными в Excel, где, например, не было актуальных мероприятий. Впрочем, список мероприятий мы в итоге сами научились вытягивать с сайта.
Наша команда
Команду мы собрали в соцсетях чисто под этот хакатон: MLщики Тима и Андрей, фронтэндер Игорь, и ваш покорный слуга. Назвались Artificial Intelligentsia. Поскольку типов рекомендуемого контента было несколько, то разделились мы очень органично: Тима сел учиться рекомендовать книги на основе прочитанных книг же, Андрей - рекомендовать кружки по кружкам, а я стал рекомендовать мероприятия. Но поскольку данных по посещению мероприятий людьми нам не дали, рекомендации я строил тоже на основе прочитанных книг. Игорь сделал для всего этого простенький сайтик, и, как водится, к последнему моменту мы собрали всё это воедино. Очень понравилось, что трое из четырех членов команды были в Москве, и мы, несмотря на карантин, смогли собраться в одной квартире - вместе работается гораздо продуктивнее.
Наше решение
По истории взятия книг в библиотеках данных было очень много, едва ли не миллионы. Поэтому Тима реализовал алгоритм коллаборативной фильтрации, когда модель выучивает вкусы читателя на основе прочитанных им книг, а выводы об особенностях книг делает по людям, которые их читают. Звучит как несколько подозрительная рекурсия, но если данных достаточно, такой подход вполне успешен. Чисто математически это работает так: и книги, и читатели представлены числовыми векторами, которые получаются из разложения разреженной матрицы, где каждая строка - читатель, каждый столбец - книга, и единички стоят в клетках, соответствующих реально прочитанным книгам. Тима ещё добавил какую-то хитрую байесовскую функцию потерь, и рекомендации стали довольно осмысленными. В принципе, можно было ещё вместо простых линейных проекций обучить нейронки, но важнее было выкатить полноценный продукт, чем перебирать алгоритмы. Для чистого решения не хватило умной дедупликации: единицей учитываемого контента является печатная книга, и некоторые книги могут содержать одни и те же произведения, но иметь разные заголовки. Важно не порекомендовать "Войну и мир" в первой строке, и "Войну и мир, том 1" - во второй.
Андрей пробовал делать нечто похожее для кружков, но получилось хуже. Во-первых, по кружкам данных меньше: книги люди читают десятками и сотнями, а кружки посещают в единицах. Во-вторых, кружки имеют иерархию: условно, "Аккордеон базовый" можно проходить только после "Аккордеона начального", а "Аккордеон продвинутый 2" - после них обоих и первого продвинутого. В исходных данных такой разметки нет, и, по-хорошему, её надо выучивать из данных самостоятельно. Та же проблема есть и в книгах (трилогии надо читать последовательно), но там она менее выражена.
Для мероприятий я решил делать content-based рекомендации, потому что о них нам известно немного: название, описание, место и время. Из названия и описания я тоже слепил векторные представления, усреднив fasttext-эмбеддинги всех их слов. Справка: fasttext - это векторная модель слов, как word2vec, только ещё способная угадывать смысл незнакомых слов по их написанию, и за счёт этого сжимаемая до весьма малых размеров без существенной потери в точности. Для книг я скачал в интернете базу аннотаций, и слепил из них точно такие же векторные представления. Теперь книги и мероприятия представлены в одном и том же пространстве, и их можно сравнивать друг с другом. Например, если юзер любит исторические книги, их векторы окажутся геометрически близкими к вектору фестиваля реконструкторов, и юзеру можно предложить посетить этот фестиваль. И, конечно, при ранжировании мероприятий надо учитывать географию, отдавая предпочтения тем, что проходят рядом с домом пользователя. К кружкам это тоже относится, а ещё для кружков важен возраст юзера. Поэтому и адрес, и возраст перед началом работы мы спрашиваем.
Чем всё закончилось
К этому решению можно было добавить ещё миллион плюшек: тонкую настройку рекомендаций под лайки/дизлайки юзера или введённое им текстовое описание своих предпочтений, умный текстовый поиск, гибридный рекомендер для кружков… Но два дня, отведенные на хакатон, протекли слишком уж быстро, и пришло время представлять нашу работу. На небольшое демо нашей системы можно посмотреть в коротком видосе. Как я уже сказал, мы заняли второе место, но чем именно наше решение оказалось хуже, чем у победителей, нам не объяснили. Впрочем, всем участникам подарили неплохие толстовки и термосы с термометром, так что совсем ни с чем мы не ушли.
После хакатона нас позвали на буткэмп - допиливать наше решение и, собственно, внедрять его. Забавно, что от сотрудничества с победителями организаторы в итоге по какой-то причине отказались. А мы в итоге отказались от сотрудничества сами: с одной стороны, мы все были загружены, с другой - оставалась обида на непрозрачность в хакатоне, с третьей, не очень понятны были сроки, размер, и порядок оплаты нашего труда. В итоге после непродолжительного общения мы с депкультом окончательно разошлись. Но смутное желание довести всё это до какого-то работающего приложения осталось, ибо и датасет библиотечный чудесен, и сама идея персонализированных культурных рекомендаций зело благородная.
Заключение
В целом, хакатон мне понравился: ворвался в целом новую для меня тему рекомендательных систем (и притащил туда свое любимое NLP), поработал с бодрой командой и довольно интересными данными. Оказалось, что стандартные алгоритмы collaborative filtering действительно работают, это не миф. Оказалось, что систему мультидоменных рекомендаций несложно собрать из подручных материалов. Оказалось, что если собрать команду незнакомых программистов и посадить в одну комнату с неограниченным количеством снеков, то за два дня можно накодить нечто прикольное.
Будем хакатонить дальше!
iiwabor
Хорошая идея! Большая редкость в наше время, где все только и стремятся, используя ML, впарить тебе ненужную хрень с «70% скидкой»