Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
364 views
Kernel: Python 2 (SageMath)

Задание по программированию: Рекомендательные системы

Описание задачи

Небольшой интернет-магазин попросил вас добавить ранжирование товаров в блок "Смотрели ранее" - в нем теперь надо показывать не последние просмотренные пользователем товары, а те товары из просмотренных, которые он наиболее вероятно купит. Качество вашего решения будет оцениваться по количеству покупок в сравнении с прошлым решением в ходе А/В теста, т.к. по доходу от продаж статзначимость будет достигаться дольше из-за разброса цен. Таким образом, ничего заранее не зная про корреляцию оффлайновых и онлайновых метрик качества, в начале проекта вы можете лишь постараться оптимизировать recall@k и precision@k.

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

Входные данные

Вам дается две выборки с пользовательскими сессиями - id-шниками просмотренных и id-шниками купленных товаров. Одна выборка будет использоваться для обучения (оценки популярностей товаров), а другая - для теста.

В файлах записаны сессии по одной в каждой строке. Формат сессии: id просмотренных товаров через , затем идёт ; после чего следуют id купленных товаров (если такие имеются), разделённые запятой. Например, 1,2,3,4; или 1,2,3,4;5,6.

Гарантируется, что среди id купленных товаров все различные.

Важно:

  • Сессии, в которых пользователь ничего не купил, исключаем из оценки качества.

  • Если товар не встречался в обучающей выборке, его популярность равна 0.

  • Рекомендуем разные товары. И их число должно быть не больше, чем количество различных просмотренных пользователем товаров.

  • Рекомендаций всегда не больше, чем минимум из двух чисел: количество просмотренных пользователем товаров и k в recall@k / precision@k.

Задание

  1. На обучении постройте частоты появления id в просмотренных и в купленных (id может несколько раз появляться в просмотренных, все появления надо учитывать)

  2. Реализуйте два алгоритма рекомендаций:

    • сортировка просмотренных id по популярности (частота появления в просмотренных),

    • сортировка просмотренных id по покупаемости (частота появления в покупках).

  3. Для данных алгоритмов выпишите через пробел AverageRecall@1, AveragePrecision@1, AverageRecall@5, AveragePrecision@5 на обучающей и тестовых выборках, округляя до 2 знака после запятой. Это будут ваши ответы в этом задании. Посмотрите, как они соотносятся друг с другом. Где качество получилось выше? Значимо ли это различие? Обратите внимание на различие качества на обучающей и тестовой выборке в случае рекомендаций по частотам покупки.

Если частота одинаковая, то сортировать нужно по возрастанию момента просмотра (чем раньше появился в просмотренных, тем больше приоритет)

import pandas as pd import collections import warnings

Читаем файл с обучающими датастом

df = pd.read_csv('coursera_sessions_train.txt',';', header=None) # после того как получены результаты по train, нужно раскоментить следующую строку и не обновлять и не запускать следующую ячейку с countV, countB. # Остальные ячейки запускать так же #df = pd.read_csv('coursera_sessions_test.txt',';', header=None) dfv = df[0] # список из просмотреных товаров dfb = df[1] # список из купленных товаров dfb.dropna(inplace=True) # не помню зачем :)

Собираем Счётчики

countV = collections.Counter((','.join(dfv.tolist())).split(',')) countB = collections.Counter((','.join(dfb.tolist())).split(','))

Функции для получения отсортированного списка товаров по разному ранжированию: Просмотры и Покупки

# li - строка из id товаров через запятую # k - какой длинны список вернуть? Если 0, то вернують все def sortByCountView(li, k=0): # разбираем строку в список li = li.split(',') # формируем датафрейм d = pd.DataFrame({'id': li, # номера товаров 'num': range(len(li)), # порядковый номер товара, чтобы можно было использовать его как дополнительный ранжирующий фактор при равном Count 'count': [countV[li[x]] for x in range(len(li))]}) # количество просмотров товара с id = li[x] # 1. группируем по id и count # 2. агрегируем и оставляем только с первым просмотром товара в сессии - т.е. num -> min # 3. reset_index() - вытаскиваем из индексов groupby в столбцы # 4. сортируем по count - убывание, num - возрастание # итог - получаем отсортированный датафрейм r = d.groupby(['id','count']).agg({'num': min}).reset_index().sort_values(by=(['count','num']), ascending=[0,1]) # собираем в строку и возвращаем обратно k-элементов if k<=0: return ','.join(r.id) else: return ','.join(r.id[:k]) def sortByCountBay(li, k=0): li = li.split(',') d = pd.DataFrame({'id': li, 'num': range(len(li)), 'count': [countB[li[x]] for x in range(len(li))]}) r = d.groupby(['id','count']).agg({'num': min}).reset_index().sort_values(by=(['count','num']), ascending=[0,1]) if k<=0: return ','.join(r.id) else: return ','.join(r.id[:k])
dft = df.dropna() # удаляем сессии в которых не было покупок dft.columns = ['views','bay'] print(dft.shape) print(dft.head())
(3608, 2) views bay 7 59,60,61,62,60,63,64,65,66,61,67,68,67 67,60,63 10 84,85,86,87,88,89,84,90,91,92,93,86 86 19 138,198,199,127 199 30 303,304,305,306,307,308,309,310,311,312 303 33 352,353,352 352
warnings.filterwarnings('ignore') # dft = dft[:10] # отсортированный список просмотренных товаров по Просмотрам dft['Early sortByCountView']=dft['views'].apply(lambda x: sortByCountView(x, k=0)) # рекомендация Одного товара dft['Rec1 ByCountView']=dft['views'].apply(lambda x: sortByCountView(x, k=1)) # Precision@1 - точность по рекомендации одного товара k = 1 # len(set.intersection(set(row['bay'].split(',')), set(row['Rec1 ByCountView'].split(',')))) - находим пересечение множеств - купленных товаров и тех что были в рекомендации dft['Precision1 ByCountView']=dft.apply(lambda row: len(set.intersection(set(row['bay'].split(',')), set(row['Rec1 ByCountView'].split(',')))), axis=1) # Recall@1 - полнота по одному товару k=1 dft['Recall1 ByCountView']=dft.apply(lambda row: len(set.intersection(set(row['bay'].split(',')), set(row['Rec1 ByCountView'].split(',')))) /float(len(set(row['bay'].split(',')))), # количество купленных товаров axis=1) # рекомендация Пяти товаров dft['Rec5 ByCountView']=dft['views'].apply(lambda x: sortByCountView(x, k=5)) # Precision@5 - точность по рекомендации Пяти товаров k = 5 dft['Precision5 ByCountView']=dft.apply(lambda row: len(set.intersection(set(row['bay'].split(',')), set(row['Rec5 ByCountView'].split(','))))/float(5), axis=1) # Recall@5 - полнота по Пяти товарам k=5 dft['Recall5 ByCountView']=dft.apply(lambda row: len(set.intersection(set(row['bay'].split(',')), set(row['Rec5 ByCountView'].split(',')))) /float(len(set(row['bay'].split(',')))), axis=1) # отсортированный список просмотренных товаров по Покупкам dft['Early sortByCountBay']=dft['views'].apply(lambda x: sortByCountBay(x, k=0)) # рекомендация Одного товара dft['Rec1 ByCountBay']=dft['views'].apply(lambda x: sortByCountBay(x, k=1)) # Precision@1 - точность по рекомендации одного товара k = 1 dft['Precision1 ByCountBay']=dft.apply(lambda row: len(set.intersection(set(row['bay'].split(',')), set(row['Rec1 ByCountBay'].split(',')))), axis=1) # Recall@1 - полнота по одному товару k=1 dft['Recall1 ByCountBay']=dft.apply(lambda row: len(set.intersection(set(row['bay'].split(',')), set(row['Rec1 ByCountBay'].split(',')))) /float(len(set(row['bay'].split(',')))), axis=1) # рекомендация Пяти товаров dft['Rec5 ByCountBay']=dft['views'].apply(lambda x: sortByCountBay(x, k=5)) # Precision@5 - точность по рекомендации Пяти товаров k = 5 dft['Precision5 ByCountBay']=dft.apply(lambda row: len(set.intersection(set(row['bay'].split(',')), set(row['Rec5 ByCountBay'].split(','))))/float(5), axis=1) # Recall@5 - полнота по Пяти товарам k=5 dft['Recall5 ByCountBay']=dft.apply(lambda row: len(set.intersection(set(row['bay'].split(',')), set(row['Rec5 ByCountBay'].split(',')))) /float(len(set(row['bay'].split(',')))), axis=1) dft.head()
dft.head(20)
AverageRecall1View = dft['Recall1 ByCountView'].mean() AveragePrecision1View = dft['Precision1 ByCountView'].mean() AverageRecall5View = dft['Recall5 ByCountView'].mean() AveragePrecision5View = dft['Precision5 ByCountView'].mean() print(AverageRecall1View) print(AveragePrecision1View) print(AverageRecall5View) print(AveragePrecision5View) print AverageRecall1Bay = dft['Recall1 ByCountBay'].mean() AveragePrecision1Bay = dft['Precision1 ByCountBay'].mean() AverageRecall5Bay = dft['Recall5 ByCountBay'].mean() AveragePrecision5Bay = dft['Precision5 ByCountBay'].mean() print(AverageRecall1Bay) print(AveragePrecision1Bay) print(AverageRecall5Bay) print(AveragePrecision5Bay)
0.442634316595 0.512195121951 0.824691824713 0.212527716186 0.688449492427 0.80376940133 0.926307302423 0.252549889135