Недавно мы внедрили систему рекомендаций для Yap.TV : вы можете увидеть ее в действии после установки приложения и перехода на вкладку «Только для вас». Мы используем Apache Mahout в качестве основы для выполнения рекомендаций. Mahout — это «масштабируемая библиотека машинного обучения», которая содержит как локальные, так и распределенные реализации рекомендаций для пользователей и элементов, использующих алгоритмы совместной фильтрации.
Сейчас мы сосредоточимся на локальной реализации на одной машине. Он должен хорошо работать, если у вас есть до 10 миллионов значений предпочтений. Кроме того, вы, вероятно, должны рассмотреть реализацию на основе Hadoop, поскольку данные просто не помещаются в память.
Написание базового рекомендации с Mahout довольно просто; Поскольку Mahout очень настраиваем, обычно есть разные реализации на выбор; Я просто опишу то, что я считаю «хорошими отправными точками».
основы
Сначала вам нужен файл с входными данными. Формат довольно прост: либо разделенные запятыми (идентификатор пользователя, идентификатор элемента) пары или (идентификатор пользователя, идентификатор элемента, значение предпочтения) тройки. Это выражает то, что вы уже знаете: что нравится пользователям, какие элементы и, если необходимо, сколько (например, в масштабе 1-5). Идентификаторы должны быть целыми числами, значение предпочтения рассматривается как число с плавающей точкой.
Давайте сначала создадим рекомендацию для пользователя: это рекомендация, которая, когда ее запрашивают рекомендации для пользователя A, сначала ищет «похожих» пользователей на A, а затем пытается найти лучшие элементы, которые эти похожие пользователи оценили, но A не имеет. Для этого нам нужно создать 4 компонента:
- модель данных : это будет использовать файл
- сходство пользователей : показатель, который задан двумя пользователями, вернет число, представляющее, насколько они похожи
- соседство : для поиска окрестности данного пользователя
- Рекомендатель : который объединяет эти части для выработки рекомендаций
Для унарных входных данных (когда пользователям нравятся элементы или мы не знаем), хорошей отправной точкой является:
1
2
3
4
|
val dataModel = new FileDataModel(file) val userSimilarity = new LogLikelihoodSimilarity(dataModel) val neighborhood = new NearestNUserNeighborhood( 25 , userSimilarity, dataModel) val recommender = new GenericBooleanPrefUserBasedRecommender(dataModel, neighborhood, userSimilarity) |
Если у нас есть значения предпочтений (тройки во входных данных):
1
2
3
4
|
val dataModel = new FileDataModel(file) val userSimilarity = new PearsonCorrelationSimilarity(dataModel) val neighborhood = new NearestNUserNeighborhood( 25 , userSimilarity, dataModel) val recommender = new GenericUserBasedRecommender(dataModel, neighborhood, userSimilarity) |
Теперь мы готовы получить некоторые рекомендации; это так просто, как:
1
2
3
4
5
6
|
// Gets 10 recommendations val result = recommender.recommend(userId, 10 ) // We get back a list of item-estimated preference value, // sorted from the highest score result.foreach(r = > println(r.getItemID() + ": " + r.getValue())) |
В сети
А как насчет онлайн-аспекта? Вышеуказанное отлично подойдет для существующих пользователей; как насчет новых пользователей, которые регистрируются в сервисе? Конечно, мы хотим дать некоторые разумные рекомендации для них. Создание экземпляра рекомендателя стоит дорого (наверняка, он занимает больше времени, чем «обычный» сетевой запрос), поэтому мы не можем просто каждый раз создавать новый рекомендатель.
К счастью, у Mahout есть возможность добавления временных пользователей в модель данных. Общая настройка тогда:
- периодически пересоздавайте весь рекомендатель, используя текущие данные (например, каждый день или каждый час — в зависимости от того, сколько времени это займет)
- при выполнении рекомендации проверьте, существует ли пользователь в системе
- если да, сделайте рекомендацию как всегда
- если нет, создайте временного пользователя, заполните настройки и сделайте рекомендацию
Первая часть (периодически воссоздающая рекомендатель) может быть довольно сложной, если вы ограничены в памяти: при создании нового рекомендателя вам нужно хранить две копии данных в памяти (чтобы по-прежнему иметь возможность обрабатывать запросы сервера от Старый). Но так как это не имеет никакого отношения к рекомендациям, я не буду вдаваться в подробности.
Что касается временных пользователей, мы можем PlusAnonymousConcurrentUserDataModel
нашу модель данных в экземпляр PlusAnonymousConcurrentUserDataModel
. Этот класс позволяет получить временный идентификатор пользователя; идентификатор должен быть позже освобожден, чтобы его можно было использовать повторно (количество таких идентификаторов ограничено). После получения идентификатора, мы должны заполнить настройки, а затем мы можем продолжить с рекомендацией, как всегда:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
val dataModel = new PlusAnonymousConcurrentUserDataModel( new FileDataModel(file), 100 ) val recommender : org.apache.mahout.cf.taste.recommender.Recommender = ... // we are assuming a unary model: we only know which items a user likes def recommendFor(userId : Long, userPreferences : List[Long]) = { if (userExistsInDataModel(userId)) { recommendForExistingUser(userId) } else { recommendForNewUser(userPreferences) } } def recommendForNewUser(userPreferences : List[Long]) = { val tempUserId = dataModel.takeAvailableUser() try { // filling in a Mahout data structure with the user's preferences val tempPrefs = new BooleanUserPreferenceArray(userPreferences.size) tempPrefs.setUserID( 0 , tempUserId) userPreferences.zipWithIndex.foreach { case (preference, idx) = > tempPrefs.setItemID(idx, preference) } dataModel.setTempPrefs(tempPrefs, tempUserId) recommendForExistingUser(tempUserId) } finally { dataModel.releaseUser(tempUserId) } } def recommendForExistingUser(userId : Long) = { recommender.recommend(userId, 10 ) } |
Включение бизнес-логики
Часто бывает так, что мы хотим повысить оценку выбранных предметов из-за некоторых бизнес-правил. В нашем случае использования, например, если у шоу есть новый эпизод, мы хотим дать ему более высокий балл. Это возможно с IDRescorer
интерфейса IDRescorer
для Mahout. Экземпляр восстановителя предоставляется при вызове Recommender.recommend
. Например:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
val rescorer = new IDRescorer { def rescore(id : Long, originalScore : Double) = { if (showIsNew(id)) { originalScore * 1.2 } else { originalScore } } def isFiltered(id : Long) = false } // Gets 10 recommendations val result = recommender.recommend(userId, 10 , rescorer) |
Резюме
Mahout — отличная основа для создания рекомендаций. Это очень настраиваемый и предоставляет множество точек расширения. По-прежнему довольно много работы по подбору правильных значений параметров конфигурации, настройке восстановления и оценке результатов рекомендаций, но алгоритмы надежны, поэтому беспокоиться не о чем.
Есть также очень хорошая книга, Mahout в действии , которая охватывает рекомендательные системы и другие компоненты Mahout. Он основан на версии 0.5 (текущая версия 0.8), но примеры кода в основном работают и основная логика проекта та же.