Contact
CoCalc Logo Icon
StoreFeaturesDocsShareSupport News AboutSign UpSign In
| Download
Views: 270
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()
views bay Early sortByCountView Rec1 ByCountView Precision1 ByCountView Recall1 ByCountView Rec5 ByCountView Precision5 ByCountView Recall5 ByCountView Early sortByCountBay Rec1 ByCountBay Precision1 ByCountBay Recall1 ByCountBay Rec5 ByCountBay Precision5 ByCountBay Recall5 ByCountBay
7 59,60,61,62,60,63,64,65,66,61,67,68,67 67,60,63 63,64,60,61,65,66,67,68,59,62 63 1 0.333333 63,64,60,61,65 0.4 0.666667 60,63,67,59,61,62,64,65,66,68 60 1 0.333333 60,63,67,59,61 0.6 1.0
10 84,85,86,87,88,89,84,90,91,92,93,86 86 85,93,89,90,84,92,86,87,91,88 85 0 0.000000 85,93,89,90,84 0.0 0.000000 86,85,93,84,87,88,89,90,91,92 86 1 1.000000 86,85,93,84,87 0.2 1.0
19 138,198,199,127 199 127,138,198,199 127 0 0.000000 127,138,198,199 0.2 1.000000 138,199,127,198 138 0 0.000000 138,199,127,198 0.2 1.0
30 303,304,305,306,307,308,309,310,311,312 303 303,306,304,307,309,310,305,308,311,312 303 1 1.000000 303,306,304,307,309 0.2 1.000000 303,304,305,306,307,308,309,310,311,312 303 1 1.000000 303,304,305,306,307 0.2 1.0
33 352,353,352 352 352,353 352 1 1.000000 352,353 0.2 1.000000 352,353 352 1 1.000000 352,353 0.2 1.0
dft.head(20)
views bay Early sortByCountView Rec1 ByCountView Precision1 ByCountView Recall1 ByCountView Rec5 ByCountView Precision5 ByCountView Recall5 ByCountView Early sortByCountBay Rec1 ByCountBay Precision1 ByCountBay Recall1 ByCountBay Rec5 ByCountBay Precision5 ByCountBay Recall5 ByCountBay
7 59,60,61,62,60,63,64,65,66,61,67,68,67 67,60,63 63,64,60,61,65,66,67,68,59,62 63 1 0.333333 63,64,60,61,65 0.4 0.666667 60,63,67,59,61,62,64,65,66,68 60 1 0.333333 60,63,67,59,61 0.6 1.000000
10 84,85,86,87,88,89,84,90,91,92,93,86 86 85,93,89,90,84,92,86,87,91,88 85 0 0.000000 85,93,89,90,84 0.0 0.000000 86,85,93,84,87,88,89,90,91,92 86 1 1.000000 86,85,93,84,87 0.2 1.000000
19 138,198,199,127 199 127,138,198,199 127 0 0.000000 127,138,198,199 0.2 1.000000 138,199,127,198 138 0 0.000000 138,199,127,198 0.2 1.000000
30 303,304,305,306,307,308,309,310,311,312 303 303,306,304,307,309,310,305,308,311,312 303 1 1.000000 303,306,304,307,309 0.2 1.000000 303,304,305,306,307,308,309,310,311,312 303 1 1.000000 303,304,305,306,307 0.2 1.000000
33 352,353,352 352 352,353 352 1 1.000000 352,353 0.2 1.000000 352,353 352 1 1.000000 352,353 0.2 1.000000
55 519 519 519 519 1 1.000000 519 0.2 1.000000 519 519 1 1.000000 519 0.2 1.000000
64 599,600,601,602 603,604,602,599,605,606,600 601,599,602,600 601 0 0.000000 601,599,602,600 0.6 0.428571 602,599,600,601 602 1 0.142857 602,599,600,601 0.6 0.428571
72 687,688,689,690,691,690,688,690,688,692 690,688 687,688,691,690,689,692 687 0 0.000000 687,688,691,690,689 0.4 1.000000 688,690,687,689,691,692 688 1 0.500000 688,690,687,689,691 0.4 1.000000
89 850,851,852 851 852,850,851 852 0 0.000000 852,850,851 0.2 1.000000 851,850,852 851 1 1.000000 851,850,852 0.2 1.000000
93 879,884,170,137,170,879,884,879,885,886,879,88... 879 137,170,343,879,884,887,886,885 137 0 0.000000 137,170,343,879,884 0.2 1.000000 170,137,887,879,884,885,886,343 170 0 0.000000 170,137,887,879,884 0.2 1.000000
125 1118 1118 1118 1118 1 1.000000 1118 0.2 1.000000 1118 1118 1 1.000000 1118 0.2 1.000000
180 1545 1545 1545 1545 1 1.000000 1545 0.2 1.000000 1545 1545 1 1.000000 1545 0.2 1.000000
201 1727 1727 1727 1727 1 1.000000 1727 0.2 1.000000 1727 1727 1 1.000000 1727 0.2 1.000000
219 99 99 99 99 1 1.000000 99 0.2 1.000000 99 99 1 1.000000 99 0.2 1.000000
228 1907,1908,1909,1910,1911,1912,1913,1914,1907,1... 1907 1911,1915,1907,1908,1909,1910,1912,1913,1914 1911 0 0.000000 1911,1915,1907,1908,1909 0.2 1.000000 1907,1908,1909,1910,1911,1912,1913,1914,1915 1907 1 1.000000 1907,1908,1909,1910,1911 0.2 1.000000
231 1945,1957,1958,1959,1960,1961,1962,1963 1959 1962,1958,1957,1959,1961,1945,1960,1963 1962 0 0.000000 1962,1958,1957,1959,1961 0.2 1.000000 1957,1959,1961,1962,1945,1958,1960,1963 1957 0 0.000000 1957,1959,1961,1962,1945 0.2 1.000000
233 1997,1998,244,1998,1999,1969,2000,1339 1998 1339,1999,244,1998,2000,1969,1997 1339 0 0.000000 1339,1999,244,1998,2000 0.2 1.000000 1998,1999,1997,244,1969,2000,1339 1998 1 1.000000 1998,1999,1997,244,1969 0.2 1.000000
236 2013,2016,2013,2016,2013 2013 2013,2016 2013 1 1.000000 2013,2016 0.2 1.000000 2013,2016 2013 1 1.000000 2013,2016 0.2 1.000000
237 2019 2019 2019 2019 1 1.000000 2019 0.2 1.000000 2019 2019 1 1.000000 2019 0.2 1.000000
290 2462,2463,2462,2463,2462 2462 2462,2463 2462 1 1.000000 2462,2463 0.2 1.000000 2462,2463 2462 1 1.000000 2462,2463 0.2 1.000000
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