Дружбы — одна из важнейших механик любой социальной сети. Подавляющее большинство взаимодействий происходит между пользователями, которые дружат: мы видим и комментируем записи друг друга в лентах, заходим в список друзей, чтобы найти знакомых и написать сообщение. Именно поэтому рост социального графа так важен.
Меня зовут Женя Замятин, я работаю в команде Core ML ВКонтакте. Хочу рассказать, как устроены рекомендации, которые делают ближе пользователей самой крупной социальной сети рунета.
Обзор
Современные рекомендательные системы зачастую состоят из двух уровней, и наша не исключение. Задача первого уровня — искать наиболее релевантных кандидатов среди всего множества пользователей (у нас их сотни миллионов). Такая постановка задачи подразумевает высокую скорость работы. Обычно здесь используют простые в применении модели — вроде матричных факторизаций или эвристики на базе числа общих друзей. Полученные на первом уровне кандидаты отправляются на второй. Здесь на модель уже не накладываются такие жёсткие ограничения по скорости, её главная задача — обеспечить максимальную точность предсказаний и сформировать список, который увидит пользователь. В этой статье мы рассмотрим только первый этап — уровень отбора кандидатов.
Прежде всего сформулируем задачу, которую будем решать: для каждого пользователя необходимо найти k кандидатов, которых он с наибольшей вероятностью добавит в друзья. Метрика, на которую будем ориентироваться, — recall@k. Она идеально описывает задачу: на первом уровне нам не интересен порядок кандидатов, но важна их релевантность.
Сначала рассмотрим базовые решения, придуманные десятки лет назад, но до сих пор актуальные. Первым приходит на ум одно из самых логичных — эвристика на основе числа общих друзей. Для каждого пользователя отбираются кандидаты с наибольшим таким значением. Этот подход просто реализуется и неплох по качеству.
Ещё один важный метод рекомендаций — Adamic/Adar. В его основе лежит всё тот же анализ общих друзей, но с модификацией: авторы предлагают учитывать число друзей у «общего» друга. Чем больше это значение, тем меньше информации о релевантности он несёт.
Кроме методов на основе анализа общих друзей, довольно распространены рекомендации на базе эмбеддингов. В Лаборатории искусственного интеллекта ВКонтакте в МФТИ мы провели исследование: сравнили эффективность разных подходов к задаче предсказания дружб в VK. Результаты совпали с нашим опытом — решения на базе графовых эмбеддингов у нас работают плохо. Учитывая это, мы стали развивать систему отбора кандидатов по пути анализа общих друзей.
EGOML
Общая схема нашего метода продолжает идеи числа общих друзей и Adamic/Adar. Финальная мера релевантности E(u, v)
, с помощью которой мы будем отбирать кандидатов, всё так же раскладывается в сумму по общим друзьям u
и v
. Ключевое отличие — в форме слагаемого под суммой: в нашем случае это мера ez_c(u, v)
.
Сначала попробуем понять «физический» смысл меры ez_c(u, v)
. Представим, что мы взяли пользователя c
и спросили у него: «Насколько вероятно, что два твоих друга, u
и v
, подружатся?» Чем больше информации для оценки он учтёт, тем точнее будет его предсказание. Например, если c
сможет вспомнить только число своих друзей, его рассуждения могут выглядеть следующим образом: «Чем больше у меня друзей, тем менее вероятно, что случайные двое из них знакомы». Тогда оценка вероятность дружбы u
и v
(с точки зрения c
) может выглядеть как 1/log(n)
, где n
— число друзей. Именно так устроен Adamic/Adar. Но что если c
возьмёт больше контекста?
Прежде чем отвечать на этот вопрос, разберёмся, почему ez_c(u, v)
важно определять через пользователя c
. Дело в том, что в таком виде очень удобно решать задачу распределённо. Представим, что теперь мы разослали всем пользователям платформы анкету с просьбой оценить вероятность дружбы в каждой паре их друзей. Получив все ответы, мы можем подставить значения в формулу E(u, v)
. Именно так выглядит вычисление E(u, v)
с помощью MapReduce:
Подготовка. Для каждого
c
выделяется тот контекст, который он будет учитывать для вынесения оценок. Например, в Adamic/Adar это будет просто список друзей.Map. «Спрашиваем» у каждого
c
, что он думает про возможность дружбы в каждой паре его друзей. По сути, вычисляемez_c(u, v)
и сохраняем в виде(u, v) > ez_c(u, v)
для всехu, v in N(c)
. В случае Adamic/Adar:(u, v) > 1/log|N(c)|
.Reduce. Для каждой пары
(u, v)
суммируем все соответствующие ей значения. Их будет ровно столько, сколько общих друзей уu
иv
.
Таким образом мы получаем все ненулевые значения E(u, v)
. Заметим: необходимое условие того, что E(u, v) > 0
, — существование хотя бы одного общего друга у u
и v
.
Контекстом пользователя c
в случае меры ez_c
будет тот же список друзей, но дополненный информацией о связях внутри этого списка. Такую структуру в науке называют эго-графом. Если более формально, эго-граф вершины x
— это такой подграф исходного графа, вершинами которого являются все соседи x
и сама x
, а рёбрами — все рёбра исходного графа между этими вершинами. Коллеги из Одноклассников написали подробную статью об эго-графах и затронули в ней вопрос их эффективного построения.
Ключевая идея меры ez_c
в том, что её можно сделать обучаемой. Для каждого пользователя c
, его эго-графа и всех пар пользователей u
, v
внутри него мы можем посчитать много разных признаков, например:
число общих друзей
u
иv
внутри эго-графаc
;число общих друзей
u
иc
;интенсивность взаимодействий между
v
иc
;время, прошедшее с последней дружбы между
u
и кем-либо из эго-графаc
;плотность эго-графа
c
;и другие.
Таким образом мы получим датасет с признаками. Но для обучения нужны ещё и метки. Пусть датасет был построен по состоянию графа на момент времени T
. Тогда в качестве положительных примеров возьмём те пары пользователей, которые не были друзьями на момент T
, но подружились к T + ?T
. А как отрицательные — все остальные, не подружившиеся, пары пользователей. Заметим: поскольку мы решаем задачу предсказания новых дружб, те пары пользователей, которые уже дружат на момент T
, учитывать не нужно ни на обучении, ни на применении.
В конечном счёте мы получаем датасет следующего вида:
для каждой пары пользователей
u
иv
, а также их общего другаc
, посчитаны признаки по эго-графуc
;пара пользователей
u
иv
встречается в датасете ровно столько раз, сколько у них общих друзей;все пары пользователей в датасете не являются друзьями на момент времени
T
;для каждой пары
u
иv
проставлена метка — подружились ли они в течение определённого промежутка времени начиная сT
.
По такому датасету мы и будем обучать нашу меру ez_c
. В качестве модели выбрали градиентный бустинг с pairwise функцией потерь, где идентификатором группы выступает пользователь u
.
По сути, мера ez_c(u, v)
определяется как предсказание описанной выше модели. Но есть один нюанс: при pairwise-обучении распределение предсказаний модели похоже на нормальное. Поэтому, если в качестве определения меры ez_c(u, v)
взять «сырое» предсказание, может возникнуть ситуация, когда мы будем штрафовать финальную меру E(u, v)
за общих друзей, так как значения предсказаний бывают отрицательными. Это выглядит не совсем логично — хочется, чтобы с ростом числа общих друзей мера E(u, v)
не убывала. Так что поверх предсказания модели мы решили взять экспоненту:
Такой подход хорошо себя показывает на небольших графах. Но чтобы применить его на реальных данных, необходимо выполнить ещё одно действие. Суть проблемы такая: мы не можем вычислять признаки и применять модель для каждой пары пользователей всех эго-графов — это слишком долго. Для решения мы придумали специальный трюк. Представим, что наш градиентный бустинг обучился таким образом, что каждое дерево использует признаки только одного пользователя: либо u
, либо v
. Тогда мы могли бы разделить весь ансамбль на две группы: к группе A
мы бы отнесли деревья, которые используют только признаки пользователя u
, к B
— пользователя v
. Предсказание такой модели можно представить в виде:
Имея такую модель, мы могли бы получить предсказания для всех пар пользователей одного эго-графа быстрее. Достаточно применить модели A
и B
для каждого пользователя, а затем сложить соответствующие парам предсказания. Таким образом, для эго-графа из n
вершин мы могли бы сократить число применений модели с O(n^2)
до O(n)
. Но как получить такую модель, каждое дерево которой зависит только от одного пользователя? Для этого сделаем следующее:
Исключим из датасета все признаки, которые одновременно зависят и от
u
и отv
. Например, от признака «число общих друзейu
иv
внутри эго-графаc
» придётся отказаться.Обучим модель
A
, используя только признаки на базеu
,c
и эго-графаc
.Для обучения модели
B
оставим только признаки на базеv
,c
и эго-графаc
. Также в качестве базовых предсказаний передадим предсказания моделиA
.
Если объединим модели A
и B
, получим то что нужно: первая часть использует признаки u
, вторая — признаки v
. Совокупность моделей осмысленна, поскольку B
была обучена «корректировать» предсказания A
. Эта оптимизация позволяет ускорить вычисления в сотни раз и делает подход применимым на практике. Финальный вид ez_c(u, v)
и E(u, v)
выглядит так:
Вычисление меры E в онлайне
Заметим, что E(u, v)
можно представить в виде:
Эта формула — скалярное произведение разреженных векторов, индексами которых являются пользователи, а значениями — экспоненты предсказаний модели. Ненулевые значения здесь проставлены только у друзей u
— по сути это просто списки друзей с дополнительными значениями.
При построении рекомендаций мы уже вычислили предсказания моделей для всех существующих дружб. Поэтому для каждого пользователя мы можем собрать векторы и сложить их в доступное онлайн key-value хранилище. После этого сможем получать значение E(u, v)
для любой пары пользователей в онлайне простой операцией перемножения векторов. Это даёт возможность использовать E(u, v)
как лёгкую функцию релевантности в нагруженных местах либо как дополнительный признак финальной модели ранжирования.
Итог
В результате система EGOML позволяет:
Распределённо отбирать кандидатов для каждого пользователя в офлайне. Асимптотическая сложность оптимизированного алгоритма составляет
O(|E|)
вычислений признаков и применений модели, где|E|
— число связей в графе. На кластере из 250 воркеров время работы алгоритма составляет около двух часов.Быстро вычислять меру релевантности
E(u, v)
для любой пары пользователей в онлайне. Асимптотическая сложность операцииO(|N(u)| + |N(v)|)
.Улучшать качество рекомендаций, расширяя количество учтённых графов (по дружбам, скрытиям рекомендаций, отправленным сообщениям и другим графам) и добавляя всевозможные метки на рёбра и вершины. Например, интенсивность взаимодействий на ребре, дату образования ребра, город, место работы или учёбы пользователя.
В конечном счёте мы перешли со способа отбора кандидатов с использованием Adamic/Adar к системе EGOML и внедрили в модель второй уровень признаков на основе меры E(u, v)
. И это позволило увеличить количество подтверждённых дружб со всей платформы на несколько десятков процентов.
Благодарность
Хочу сказать спасибо руководителю команды Core ML Андрею Якушеву за помощь в разработке метода и подготовке статьи, а также всей команде Core ML — за поддержку на разных этапах этой работы.
sanneo
Мне надавно ВК рекомендовал в друзья человека, который у меня есть ТОЛЬКО в whatsapp. У нас с ним нет и небылообщих друзей. Как так?
ezamyatin Автор
Мы используем ориентированные графы, и иногда, в том числе благодаря подходу с эго-графами, рекомендуемый пользователь влияет на то, что вы видите. Такое возможно благодаря тому, что мы используем разные типы связей между пользователями.
sanneo
Единсвенное общее у меня с этим пользователем, это номер телефона и переписка в вотсап раз в 0,5 года
TheRaven
Приложуха на смартфоне стоит? Скорее всего ВК просто угнал адресную книгу и предлагает «друзей» по номеру телефона.
sanneo
Нет, я в вк только через комп захожу, на телефоне даже в браузере не открываю.
diogen4212
Попробуйте в настройке «Кто может найти мой профиль при импорте контактов» выставить «Никто».
Лично мне рекомендации друзей вообще не нужны, в VkOpt отключил их показ, как и рекламу.
HighFlyer
Версия: тот человек что-то сделал: дал доступ к телефонной книжке для ВК, добавил ваш профиль в закладки, может ещё что-то.