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

Часто клиенты обращаются к инструментам, которые помогают контролировать каналы по мере роста компании со 100 до 1000 до 10000 человек, использующих Slack. Это то, что побудило нас работать над рекомендациями по каналам: как мы можем помочь пользователям настроить свой список каналов, чтобы максимально упростить работу со Slack? Эта функция - одна из первых крупных инициатив недавно сформированной группы Search, Learning and Intelligence, базирующейся в Нью-Йорке.

Эта проблема

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

Но что значит быть актуальным для канала? К сожалению, нет простого способа напрямую измерить релевантность, но, попробовав несколько разных прокси-метрик, мы в конечном итоге остановились на приблизительном времени, проведенном в канале, на основе активности чтения и записи. Чтобы вычислить эти оценки, мы построили конвейер Spark, который принимает данные журнала чтения и записи, хранящиеся в хранилище данных Hive, поддерживаемом нашей замечательной командой инженеров данных, и для каждой команды выводит матрицу активности пользовательского канала, где каждый Строка соответствует каналу, каждый столбец соответствует пользователю, и каждая запись соответствует вычисленной нами оценке активности.

В качестве простого примера возьмем команду с тремя пользователями U1, U2 и U3 и тремя каналами C1, C2 и C3. В действительности мы имеем дело с тысячами гораздо более крупных матриц, каждая из которых содержит до миллионов записей, но концепции те же. В нашем примере матрица активности пользовательских каналов может выглядеть следующим образом:

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

Чтобы оценить наше решение, мы построили набор тестов из случайного подмножества заполненных записей. Мы также включаем случайную выборку пустых ячеек в набор, устанавливая их уровень активности равным нулю. Этот метод, заимствованный из One-Class Collaborative Filtering, моделирует как возможность того, что пользователь может не находиться в канале, потому что он не знает об этом, так и то, что он может просто не интересоваться этим. Затем мы можем оценить наше решение, вычислив ряд показателей, таких как среднеквадратичная ошибка или RMSE, между нашими прогнозируемыми уровнями активности и наблюдаемыми уровнями активности в нашем тестовом наборе.

Похожие каналы

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

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

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

Затем мы вычисляем косинусное сходство между ними.

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

Ближайшие соседи

Теперь, когда у нас есть мера сходства между каждой парой каналов в команде, мы можем использовать эту информацию для заполнения нашей исходной матрицы. Для этого мы используем регрессию k -Ближайшие соседи (KNN). То есть для данного пользователя U и канала C мы ищем k каналов, которые наиболее похожи на C согласно нашей матрице сходства каналов. Затем мы вычисляем средневзвешенное значение оценок активности между каждым из первых k похожих каналов и пользователем U.

Например, предполагая k = 2, чтобы вычислить, насколько релевантен C2 U3, мы сначала смотрим на строку C2 в матрице сходства. В этом случае k наиболее похожих каналов, за исключением самого C2, - это C1 и C3 с сходством 0,89 и 0,0 соответственно. Затем мы ищем оценки активности U3 для C1 и C3 в нашей исходной матрице активности. В этом случае баллы составляют 0,5 и 0,3. Затем мы можем вычислить средневзвешенное значение оценок с учетом сходства.

Таким образом, мы ожидаем, что U3 будет иметь 0,5 балла за активность в C2. Затем мы можем повторить тот же подход, чтобы заполнить оставшиеся пустые места в нашей исходной матрице.

Преобразования деятельности

Описанный выше подход хорошо работает на нашем тестовом наборе, но мы можем добиться большего. Возвращаясь к нашей исходной матрице, из-за масштабной инвариантности косинусного подобия C1 и C2 считаются очень похожими, хотя U1 и U2 в два раза менее активны в C2, чем в C1. При вычислении прогнозируемой оценки активности U3 для C2 мы не принимали во внимание эту разницу в масштабе. Это, вероятно, приводит к переоценке прогнозируемого уровня активности U3 в C2.

Чтобы смягчить эту проблему, мы не только вычисляем показатель сходства между каждой парой каналов, но также изучаем линейную зависимость между уровнями активности в каждом канале. Для этого мы смотрим на оценки активности для общего набора членов между двумя каналами и запускаем линейную регрессию наименьших квадратов, используя оценки для первого канала в качестве входных данных и оценки для второго в качестве выходных. Результатом является функция вида Ay = α ∙ Ax + β, которую можно использовать для преобразования оценки активности в канале Cx в ожидаемую оценку активности в канале Cy.

Возвращаясь к C1 и C2, набор общих пользователей для этих каналов - U1 и U2.

В этом случае результат регрессии наименьших квадратов от активности в C1 к активности в C2 будет:

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

В этом случае результат регрессии наименьших квадратов:

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

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

Фактически, используя эту простую настройку, мы смогли снизить RMSE в нашем тестовом наборе еще на 35%!

Но есть еще кое-что

В конце концов, модель машинного обучения не делает продукт функцией. Помимо улучшения качества рекомендаций, мы работали над их оформлением. Это включало создание инфраструктуры для индексации и обслуживания рекомендаций, выбор формулировок и модели взаимодействия, используемых Slackbot, и разработку логики запуска, чтобы добиться конечного результата таким образом, чтобы это не выглядело слишком разрушительным. Наконец, чтобы контролировать развертывание, а также количественно оценить успех функции, мы использовали внутренние системы экспериментов и журналов Slack. После предоставления этой функции только 10% команд мы уже измерили показатель CTR для этих рекомендаций 22%.

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

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