Построение модели совместной фильтрации для рекомендации продуктов клиентам

Пошаговое руководство по созданию системы рекомендаций по машинному обучению

Обзор проекта

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

Постановка задачи

Цель этой Системы рекомендаций по продуктам или «Рекомендателя книг» - предоставить покупателю интересные рекомендации по книгам.
Рекомендации по книгам особенно полезны для тех клиентов, которые заходят в книжные магазины без название книги в виду.

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

Соревнование

Чтобы предоставить клиентам интересные рекомендации, мы построим модель совместной фильтрации с использованием приобретенных данных.
При совместной фильтрации мы будем рекомендовать товары на основе того, как похожие пользователи покупают товары.
Например, если покупатель 1 и покупатель 2 купили похожие товары, например 1 купил A, B, C и 2 купил A, B, мы бы порекомендовали товар C покупателю 2.

Мы подробно рассмотрим пошаговый процесс построения системы рекомендаций с помощью Python. Этот процесс будет включать:

  • Загрузка и обработка приобретенных данных
  • Тренировочные модели
  • Оценка производительности моделей
  • Выбор оптимальной модели

Применение

Система рекомендаций сможет искать список рекомендаций по продукту на основе указанного клиента:

  • Ввод: заказчик
  • Возврат: ранжированный список продуктов, которые покупатель, скорее всего, захочет положить в свою корзину.

AWS SageMaker

Я использую большой набор данных с более чем 500 000 транзакций, совершенных в книжном магазине. Если вы также используете большой набор данных, я рекомендую вам запустить экземпляр AWS SageMaker Notebook, чтобы избежать каких-либо ошибок «нехватки памяти».

Чтобы создать экземпляр блокнота Amazon SageMaker:

Https://docs.aws.amazon.com/sagemaker/latest/dg/howitworks-create-ws.html

Выполнение

1. Импортировать модули

  • pandas и numpy для обработки данных
  • _3 _ (*) ​​для выполнения выбора и оценки модели
  • sklearn для разделения данных на поезд и тестовый набор

(*) turicreate поддерживает только Apple / Linux. В случае Windows вы можете создать экземпляр блокнота AWS SageMaker.

import pandas as pd
import numpy as np
import time
import turicreate as tc
from sklearn.model_selection import train_test_split
import sys
sys.path.append("..")

2. Загрузить данные

Я буду использовать набор данных из книжного магазина. Вы можете использовать любой собственный набор данных.

  • data.csv состоящий из пользовательских транзакций

Формат следующий.

transactions = pd.read_csv('data.csv', dtype={'customerId': np.int32, 'products': np.int32})
print(transactions.shape)
transactions.head()

3. Подготовка данных

3.1. Создайте функцию для преобразования данных

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

data = pd.melt(transactions.set_index('customerId')['products'].apply(pd.Series).reset_index(), 
             id_vars=['customerId'],
             value_name='products') \
    .dropna().drop(['variable'], axis=1) \
    .groupby(['customerId', 'products']) \
    .agg({'products': 'count'}) \
    .rename(columns={'products': 'purchase_count'}) \
    .reset_index() \
    .rename(columns={'products': 'productId'})
data['productId'] = data['productId'].astype(np.int64)
print(data.shape)
data.head()

3.2 Создание макета столбца

Здесь мы запрограммируем функцию, чтобы создать столбец «Purchase_dummy» (для отметки, купил ли покупатель этот товар или нет). Это потому, что в моем домене, книжном магазине, «Purchase_count» не дает никакой информации о предпочтениях клиентов. На самом деле не имеет значения, купил ли покупатель книгу более одного раза. Все могло бы быть совсем иначе, если бы мы создавали модель супермаркета. Я буду использовать столбец «Purchase_dummy» в своей модели, но не стесняйтесь использовать «Purchase_count», если считаете, что он лучше подходит для вашего домена.

def create_data_dummy(data):
    data_dummy = data.copy()
    data_dummy['purchase_dummy'] = 1
    return data_dummy
data_dummy = create_data_dummy(data)
data_dummy.head()

4. Разделите данные на обучающие и тестовые наборы.

  • Мы разбиваем данные на обучающие и тестовые наборы.
  • Мы используем соотношение 70:30 для размера нашего тестового набора поездов.
  • Наша обучающая часть будет использоваться для обучения модели, а вторая - для оценки производительности модели.

Давайте определим функцию расщепления.

def split_data(data, testsize):
    '''
    Splits dataset into train and test sets.
    
    Args:
        data (pandas.DataFrame)
        
    Returns
        train_data (tc.SFrame)
        test_data (tc.SFrame)
    '''
    train, test = train_test_split(data, test_size = testsize)
    train_data = tc.SFrame(train)
    test_data = tc.SFrame(test)
    return train_data, test_data
#train_data, test_data = split_data(data, .3)
train_data_dummy, test_data_dummy = split_data(data_dummy, .3)

5. Модели

5.1 Базовый показатель: популярность

Мы хотели бы использовать базовую модель для сравнения и оценки моделей совместной фильтрации.

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

5.2 Совместная фильтрация: косинус

  • При совместной фильтрации мы рекомендуем товары на основе того, как похожие пользователи покупают товары.
  • Чтобы вычислить сходство, мы будем использовать три разных типа: косинус, Пирсона и Жаккара.
  • Косинусное подобие - это косинус угла между двумя векторами векторов элементов A и B. Он определяется по следующей формуле.

5.3 Пирсон

  • Сходство Пирсона - это коэффициент Пирсона между двумя векторами.
  • Он определяется следующей формулой.

5.4 Жаккар

  • Сходство Жаккара используется для измерения сходства между двумя наборами элементов.
  • В контексте рекомендации сходство Жаккара между двумя элементами рассчитывается как:

Определим функцию выбора модели.

def model(train_data, name, user_id, item_id, target, users_to_recommend, n_rec, n_display):
    if name == 'popularity':
        model = tc.popularity_recommender.create(train_data, 
                                                    user_id=user_id, 
                                                    item_id=item_id, 
                                                    target=target)
    elif name == 'cosine':
        model = tc.item_similarity_recommender.create(train_data, 
                                                    user_id=user_id, 
                                                    item_id=item_id, 
                                                    target=target, 
                                                    similarity_type='cosine')
    elif name == 'pearson':
        model = tc.item_similarity_recommender.create(train_data, 
                                                    user_id=user_id, 
                                                    item_id=item_id, 
                                                    target=target, 
                                                    similarity_type='pearson')        
    elif name == 'jaccard':
        model = tc.item_similarity_recommender.create(train_data, 
                                                    user_id=user_id, 
                                                    item_id=item_id, 
                                                    target=target, 
                                                    similarity_type='jaccard')
        
        
    recom = model.recommend(users=users_to_recommend, k=n_rec)
    recom.print_rows(n_display)
    return model

Теперь давайте вызовем функцию для:

i) Популярность

name = 'popularity'
#target = 'purchase_count'
target = 'purchase_dummy'
pop_dummy = model(train_data_dummy, name, user_id, item_id, target, users_to_recommend, n_rec, n_display)
+------------+-----------+-------+------+
| customerId | productId | score | rank |
+------------+-----------+-------+------+
|     1      |   29441   |  1.0  |  1   |
|     1      |   19250   |  1.0  |  2   |
|     1      |   31536   |  1.0  |  3   |
|     1      |   25292   |  1.0  |  4   |
|     1      |   32880   |  1.0  |  5   |
|     2      |   29441   |  1.0  |  1   |
|     2      |   19250   |  1.0  |  2   |
|     2      |   31536   |  1.0  |  3   |
|     2      |   25292   |  1.0  |  4   |
|     2      |   32880   |  1.0  |  5   |
|     3      |   29441   |  1.0  |  1   |
|     3      |   19250   |  1.0  |  2   |
|     3      |   31536   |  1.0  |  3   |
|     3      |   25292   |  1.0  |  4   |
|     3      |   32880   |  1.0  |  5   |
|     4      |   29441   |  1.0  |  1   |
|     4      |   19250   |  1.0  |  2   |
|     4      |   31536   |  1.0  |  3   |
|     4      |   25292   |  1.0  |  4   |
|     4      |   32880   |  1.0  |  5   |
|     5      |   29441   |  1.0  |  1   |
|     5      |   19250   |  1.0  |  2   |
|     5      |   31536   |  1.0  |  3   |
|     5      |   25292   |  1.0  |  4   |
|     5      |   32880   |  1.0  |  5   |
|     6      |   29441   |  1.0  |  1   |
|     6      |   19250   |  1.0  |  2   |
|     6      |   31536   |  1.0  |  3   |
|     6      |   25292   |  1.0  |  4   |
|     6      |   32880   |  1.0  |  5   |
+------------+-----------+-------+------+

Обратите внимание, что каждому пользователю рекомендуется один и тот же список из 5 продуктов. Это потому, что популярность рассчитывается путем отбора самых популярных товаров среди всех клиентов. Здесь нет персонализации!

ii) Косинус

name = 'cosine'
#target = 'purchase_count'
target = 'purchase_dummy'
cos_dummy = model(train_data_dummy, name, user_id, item_id, target, users_to_recommend, n_rec, n_display)
+------------+-----------+----------------------+------+
| customerId | productId |        score         | rank |
+------------+-----------+----------------------+------+
|     1      |   17440   | 0.09622504313786824  |  1   |
|     1      |   43977   | 0.09622504313786824  |  2   |
|     1      |    6034   | 0.04811253150304159  |  3   |
|     1      |   15145   | 0.04811253150304159  |  4   |
|     1      |    595    | 0.04303314288457235  |  5   |
|     2      |   32738   | 0.047377943992614746 |  1   |
|     2      |   55484   |  0.0370370348294576  |  2   |
|     2      |    5678   |  0.0370370348294576  |  3   |
|     2      |   22268   |  0.0370370348294576  |  4   |
|     2      |   55744   |  0.0370370348294576  |  5   |
|     3      |    9861   | 0.020689308643341064 |  1   |
|     3      |    1825   | 0.02044650912284851  |  2   |
|     3      |   42787   | 0.01811191439628601  |  3   |
|     3      |   41231   | 0.01669451594352722  |  4   |
|     3      |   56688   | 0.01669451594352722  |  5   |
|     4      |   10277   | 0.08219948410987854  |  1   |
|     4      |   58322   | 0.08219948410987854  |  2   |
|     4      |   51223   | 0.08219948410987854  |  3   |
|     4      |   27126   | 0.08219948410987854  |  4   |
|     4      |   15330   | 0.08219948410987854  |  5   |
|     5      |    5386   |  0.1666666567325592  |  1   |
|     5      |   14798   | 0.09622505307197571  |  2   |
|     5      |   45484   | 0.09622505307197571  |  3   |
|     5      |   31795   |  0.0833333432674408  |  4   |
|     5      |   15238   |  0.0833333432674408  |  5   |
|     6      |    6808   | 0.052486387166109955 |  1   |
|     6      |   33029   | 0.03711348230188543  |  2   |
|     6      |   58456   | 0.03711348230188543  |  3   |
|     6      |   56403   | 0.034977604042400016 |  4   |
|     6      |   50717   | 0.03214121948588978  |  5   |
+------------+-----------+----------------------+------+

iii) Пирсон

name = 'pearson'
#target = 'purchase_count'
target = 'purchase_dummy'
pear_dummy = model(train_data_dummy, name, user_id, item_id, target, users_to_recommend, n_rec, n_display)
+------------+-----------+-------+------+
| customerId | productId | score | rank |
+------------+-----------+-------+------+
|     1      |   29441   |  0.0  |  1   |
|     1      |   19250   |  0.0  |  2   |
|     1      |   31536   |  0.0  |  3   |
|     1      |   25292   |  0.0  |  4   |
|     1      |   32880   |  0.0  |  5   |
|     2      |   29441   |  0.0  |  1   |
|     2      |   19250   |  0.0  |  2   |
|     2      |   31536   |  0.0  |  3   |
|     2      |   25292   |  0.0  |  4   |
|     2      |   32880   |  0.0  |  5   |
|     3      |   29441   |  0.0  |  1   |
|     3      |   19250   |  0.0  |  2   |
|     3      |   31536   |  0.0  |  3   |
|     3      |   25292   |  0.0  |  4   |
|     3      |   32880   |  0.0  |  5   |
|     4      |   29441   |  0.0  |  1   |
|     4      |   19250   |  0.0  |  2   |
|     4      |   31536   |  0.0  |  3   |
|     4      |   25292   |  0.0  |  4   |
|     4      |   32880   |  0.0  |  5   |
|     5      |   29441   |  0.0  |  1   |
|     5      |   19250   |  0.0  |  2   |
|     5      |   31536   |  0.0  |  3   |
|     5      |   25292   |  0.0  |  4   |
|     5      |   32880   |  0.0  |  5   |
|     6      |   29441   |  0.0  |  1   |
|     6      |   19250   |  0.0  |  2   |
|     6      |   31536   |  0.0  |  3   |
|     6      |   25292   |  0.0  |  4   |
|     6      |   32880   |  0.0  |  5   |
+------------+-----------+-------+------+

iv) Жаккар

name = 'jaccard'
#target = 'purchase_count'
target = 'purchase_dummy'
jacc_dummy = model(train_data_dummy, name, user_id, item_id, target, users_to_recommend, n_rec, n_display)
+------------+-----------+-----------------------+------+
| customerId | productId |         score         | rank |
+------------+-----------+-----------------------+------+
|     1      |   17440   |  0.02777777115503947  |  1   |
|     1      |   43977   |  0.02777777115503947  |  2   |
|     1      |    6034   |  0.02222222089767456  |  3   |
|     1      |   15145   |  0.02222222089767456  |  4   |
|     1      |    595    |  0.020833333333333332 |  5   |
|     2      |   23708   |  0.013550142447153727 |  1   |
|     2      |   32738   |  0.006734013557434082 |  2   |
|     2      |   22433   |  0.006472488244374593 |  3   |
|     2      |   40050   |  0.00584795077641805  |  4   |
|     2      |   24461   |  0.005108555157979329 |  5   |
|     3      |    9861   | 0.0072921812534332275 |  1   |
|     3      |   42787   |  0.007282644510269165 |  2   |
|     3      |   19852   |  0.005385607481002808 |  3   |
|     3      |   53642   |  0.004958689212799072 |  4   |
|     3      |   50172   |  0.004638224840164185 |  5   |
|     4      |   10277   |  0.013513505458831787 |  1   |
|     4      |   51223   |  0.013513505458831787 |  2   |
|     4      |   27126   |  0.013513505458831787 |  3   |
|     4      |   58322   |  0.013513505458831787 |  4   |
|     4      |   15330   |  0.013513505458831787 |  5   |
|     5      |    5386   |   0.0555555522441864  |  1   |
|     5      |   14798   |  0.04545453190803528  |  2   |
|     5      |   45484   |  0.04545453190803528  |  3   |
|     5      |   31795   |  0.041666656732559204 |  4   |
|     5      |   15238   |  0.041666656732559204 |  5   |
|     6      |    6808   |  0.030303028496828945 |  1   |
|     6      |   33029   |  0.022727272727272728 |  2   |
|     6      |   58456   |  0.022727272727272728 |  3   |
|     6      |   56671   |  0.018181817098097366 |  4   |
|     6      |   20593   |  0.015151516957716509 |  5   |
+------------+-----------+-----------------------+------+

6. Оценка модели

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

i. Точность

  • Также называется положительной прогностической ценностью - это доля релевантных экземпляров среди извлеченных экземпляров.
  • Если покупателю было рекомендовано 5 продуктов, из которых он покупает 3, то точность составляет 0,6.

ii. Напомним

  • Также известная как чувствительность, это доля от общего количества релевантных экземпляров, которые были фактически извлечены.
  • Если покупатель покупает 5 товаров, а рекомендация решила показать 4 из них, то отзыв составляет 0,8.

Наша цель - оптимизировать как точность, так и отзывчивость.
Давайте сравним модели, которые мы построили на основе точного отзыва, с данными тестирования:

models = [pop_dummy, cos_dummy, pear_dummy, jacc_dummy]
names = ['Baseline Popularity Model', 'Cosine Similarity', 'Pearson Similarity', 'Jaccard Similarity']
eval_dummy = tc.recommender.util.compare_models(test_data_dummy, models, model_names=names)

6.1. Результат оценки

6.2. Резюме оценки

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

Сходство Жаккара. Глядя на приведенное выше резюме, мы видим, что модель Жаккара работает лучше, чем модель Косинуса и Пирсона.

6.3. Примечания

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

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

Это базовый пример, поэтому вы можете увидеть, как работает оценка сходства:

В этом примере два клиента купили продукты A, B и C, а один покупатель купил продукты A, B и D.

Запустив модель совместной фильтрации, чтобы получить рекомендации для клиента, который купил только продукты A и B, мы получаем следующие результаты:

Продукт C → оценка 0,64, а продукт D → оценка 0,45.

Обратите внимание, что рекомендация для продукта C имеет более высокий балл.

7. Вывод

Наконец, нам нужно перезапустить модель с использованием подобия Жаккарда и всего набора данных.

final_model = tc.item_similarity_recommender.create(tc.SFrame(data_dummy), 
                                            user_id=user_id, 
                                            item_id=item_id, 
                                            target='purchase_dummy', 
                                            similarity_type='jaccard')

recom = final_model.recommend(users=users_to_recommend, k=5)
recom.print_rows(n_display)

7.1. Выходной файл CSV

Давайте напишем CSV-файл со всеми рекомендациями:

df_rec = recom.to_dataframe()
df_rec.to_csv('recommendation.csv')

7.2. Функция рекомендации клиентов

И давайте определим функцию для возврата списка рекомендаций клиенту:

df_rec['recommendedProducts'] = df_rec.groupby([user_id])[item_id].transform(lambda x: '|'.join(x.astype(str)))
df_output = df_rec[['customerId', 'recommendedProducts']].drop_duplicates().sort_values('customerId').set_index('customerId')

def customer_recomendation(customer_id):
    if customer_id not in df_output.index:
        print('Customer not found.')
        return customer_id
    return df_output.loc[customer_id]
customer_recomendation(101)
#recommendedProducts    11685|51032|49437|55977|36001
#Name: 101, dtype: object

Резюме

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