<jupyter_start><jupyter_text>Вебинар 5. Ранжирование и Гибридные рекоммендательные системы  1. Ранжирование  1.1 BPR - Bayesian Personalized Ranking loss$$loss = \sigma(p^{+} - p^{-})$$1. Случайная пара user - купленный им item_i = позитивная пара2. Для того же юзера сэмплируем item_j (не купил или вес в user-item матрице ниже, у item_i из 1.) - негативная пара3. Прогнозы модели $p^{+}$, $p^{-}$ --> $p^{+} - p^{-}$4. loss = $\sigma(p^{+} - p^{-})$ - это и есть наша ошибка5. Обновляем эмбеддинги  1.2 WARP = Weighted Approximate-Rank Pairwise lossНеплохое [объяснение](https://medium.com/@gabrieltseng/intro-to-warp-loss-automatic-differentiation-and-pytorch-b6aa5083187a) на игрушечном примере$$loss = ln(\frac{K-1}{N})*(p^- - p^+)$$1. Сэмплируем случайную позитивную пару юзер-item, прогноз модели для этой пары $p^+$2. Сюмплируем далее до тех пор, пока не найдем негативную пару, гду прогнозная вероятность вероятность $p^-$ > $p^+$3. K - кол-во сэмплирований (зависит от качества модели!)4. N - общее количество рекомендаций**Note**  loss зависит только от 2-ух сэмплированных пар, эмбеддинги только для этих пар будут обновлены  Зачем нужно ранжирование? Есть же классификация (предсказание 0 и 1)- Ранжирование гораздо лучше выбирает топ-k товаров, особенно когда k велико- Ранжирование позволяет напрямую ранжировать 5 релевантных юзеру товаров (какой из 5 поставить выше?) ---  2. LightFM ![mf.png](attachment:mf.png) Формула эмбеддинга в ALS (матричной факторизации) $$user_{emb} = \theta_1 * i_1 + ... + \theta_n * i_n$$    $$p = user_{emb} * item_{emb}^T + b_u + b_i$$ Формула в LightFM $$user_{emb} = \theta_1 * i_1 + ... + \theta_n * i_n + \alpha_1 * f_1 + ... + \alpha_k * f_k$$    $$p = user_{emb} * item_{emb}^T + b_u + b_i$$  Зачем нужны гибридные системы?*Холодный старт* - ситуация, в которой с товаром/юзером в матрице мало взаимодействийХолодный страт для товаров - обычно введение нового товара (YouTube, Lamoda, hh.ru)  Холодный старт для юзера - к нам пришел новый юзер  Холодного юзера/товар можно описать фичами --> мы больше о нем знаем --> он уже не такой холодный<jupyter_code>!pip install lightfm
import pandas as pd
import numpy as np
import random
import matplotlib.pyplot as plt
%matplotlib inline

# Для работы с матрицами
from scipy.sparse import csr_matrix, coo_matrix

# Матричная факторизация
from implicit.als import AlternatingLeastSquares
from implicit.nearest_neighbours import bm25_weight, tfidf_weight

from lightfm import LightFM
from lightfm.evaluation import precision_at_k, recall_at_k

# Функции из 1-ого вебинара
import os, sys

module_path = os.path.abspath(os.path.join(os.pardir))
if module_path not in sys.path:
    sys.path.append(module_path)
    
# from src.metrics import precision_at_k, recall_at_k
from utils import prefilter_items
data = pd.read_csv('../2 урок/retail_train.csv')

item_features = pd.read_csv('../2 урок/product.csv')
user_features = pd.read_csv('../2 урок/hh_demographic.csv')

# column processing
item_features.columns = [col.lower() for col in item_features.columns]
user_features.columns = [col.lower() for col in user_features.columns]

item_features.rename(columns={'product_id': 'item_id'}, inplace=True)
user_features.rename(columns={'household_key': 'user_id'}, inplace=True)

# train test split
test_size_weeks = 3

data_train = data[data['week_no'] < data['week_no'].max() - test_size_weeks]
data_test = data[data['week_no'] >= data['week_no'].max() - test_size_weeks]

data_train.head(2)
item_features.head(2)
user_features.head(2)
user_features['age_desc'].unique()
user_features['marital_status_code'].unique()
user_features['household_size_desc'].unique()<jupyter_output><empty_output><jupyter_text>1. Filter items<jupyter_code>data_train.head()
n_items_before = data_train['item_id'].nunique()

data_train_filtered = prefilter_items(data_train, take_n_popular=5000, item_features=item_features)

n_items_after = data_train_filtered['item_id'].nunique()
print('Decreased # items from {} to {}'.format(n_items_before, n_items_after))<jupyter_output>Decreased # items from 86865 to 5001<jupyter_text>2. Prepare data set  2.1 Prepare csr train matrix<jupyter_code>user_item_matrix = pd.pivot_table(data_train, 
                                  index='user_id', columns='item_id', 
                                  values='quantity', # Можно пробовать другие варианты
                                  aggfunc='count', 
                                  fill_value=0
                                 )

user_item_matrix = user_item_matrix.astype(float) # необходимый тип матрицы для implicit

# переведем в формат saprse matrix
sparse_user_item = csr_matrix(user_item_matrix).tocsr()

user_item_matrix.head(2)<jupyter_output><empty_output><jupyter_text>2.2 Prepare CSR test matrix<jupyter_code>data_test = data_test[data_test['item_id'].isin(data_train['item_id'].unique())]

test_user_item_matrix = pd.pivot_table(data_test, 
                                  index='user_id', columns='item_id', 
                                  values='quantity', # Можно пробоват ьдругие варианты
                                  aggfunc='count', 
                                  fill_value=0
                                 )

test_user_item_matrix = test_user_item_matrix.astype(float) # необходимый тип матрицы для implicit
userids = user_item_matrix.index.values
itemids = user_item_matrix.columns.values

matrix_userids = np.arange(len(userids))
matrix_itemids = np.arange(len(itemids))

id_to_itemid = dict(zip(matrix_itemids, itemids))
id_to_userid = dict(zip(matrix_userids, userids))

itemid_to_id = dict(zip(itemids, matrix_itemids))
userid_to_id = dict(zip(userids, matrix_userids))<jupyter_output><empty_output><jupyter_text>3. Prepare user and item features<jupyter_code>user_feat = pd.DataFrame(user_item_matrix.index)
user_feat = user_feat.merge(user_features, on='user_id', how='left')
user_feat.set_index('user_id', inplace=True)
user_feat.head(2)
user_feat.shape
item_feat = pd.DataFrame(user_item_matrix.columns)
item_feat = item_feat.merge(item_features, on='item_id', how='left')
item_feat.set_index('item_id', inplace=True)

item_feat.head(2)
item_feat.shape<jupyter_output><empty_output><jupyter_text>Encoding features<jupyter_code>user_feat_lightfm = pd.get_dummies(user_feat, columns=user_feat.columns.tolist())
item_feat_lightfm = pd.get_dummies(item_feat, columns=item_feat.columns.tolist())
user_feat_lightfm.head(2)<jupyter_output><empty_output><jupyter_text>Init model<jupyter_code>model = LightFM(no_components=40,
#                 loss='bpr',
                loss='warp',
                learning_rate=0.05, 
                item_alpha=0.1,
                user_alpha=0.1, 
                random_state=42)

model<jupyter_output><empty_output><jupyter_text>Train<jupyter_code>model.fit((sparse_user_item > 0) * 1,  # user-item matrix из 0 и 1
          sample_weight=coo_matrix(user_item_matrix),
          user_features=csr_matrix(user_feat_lightfm.values).tocsr(),
          item_features=csr_matrix(item_feat_lightfm.values).tocsr(),
          epochs=15, 
          num_threads=4,
          verbose=False)<jupyter_output><empty_output><jupyter_text>Evaluation<jupyter_code>user_emb = model.get_user_representations(features=csr_matrix(user_feat_lightfm.values).tocsr())
user_emb[0]  # biases
user_emb[0].shape
user_emb[1]  # embeddings
user_emb[1].shape
item_emb = model.get_item_representations(features=csr_matrix(item_feat_lightfm.values).tocsr())
item_emb[0].shape
item_emb[1].shape<jupyter_output><empty_output><jupyter_text>Train precision<jupyter_code>train_precision = precision_at_k(model, sparse_user_item, 
                                 user_features=csr_matrix(user_feat_lightfm.values).tocsr(),
                                 item_features=csr_matrix(item_feat_lightfm.values).tocsr(),
                                 k=5).mean()

train_precision
test_item_ids = np.array([1, 2, 3, 200, 1200])

predictions = model.predict(user_ids=0,
                            item_ids=test_item_ids,
                            user_features=csr_matrix(user_feat_lightfm.values).tocsr(),
                            item_features=csr_matrix(item_feat_lightfm.values).tocsr(),
                            num_threads=4)
predictions<jupyter_output><empty_output><jupyter_text>Test precision<jupyter_code>test_precision = precision_at_k(model, csr_matrix(test_user_item_matrix).tocsr(), 
                                 user_features=csr_matrix(user_feat_lightfm.values).tocsr(),
                                 item_features=csr_matrix(item_feat_lightfm.values).tocsr(),
                                 k=5).mean()

test_precision<jupyter_output><empty_output><jupyter_text>Neural networks for RS: http://d2l.ai/chapter_recommender-systems/mf.html LigthFM -> https://arxiv.org/pdf/1507.08439.pdfhttps://making.lyst.com/lightfm/docs/home.html predictions - некоторые числа.   Домашнее задание 1) Прочитать статьи про BPR, WARP loss2) Сделать грид серч текущей модели<jupyter_code>import random
random.random()
import itertools
from lightfm.evaluation import auc_score

        
# def random_search(train, test, num_samples=10, num_threads=1):
#     for hyperparams in itertools.islice(sample_hyperparameters(), num_samples):
#         num_epochs = hyperparams.pop("num_epochs")

#         model = LightFM(**hyperparams)
        
#         model.fit((sparse_user_item > 0) * 1,  # user-item matrix из 0 и 1
#             sample_weight=coo_matrix(user_item_matrix),
#             user_features=csr_matrix(user_feat_lightfm.values).tocsr(),
#             item_features=csr_matrix(item_feat_lightfm.values).tocsr(),
#             epochs=15, 
#             num_threads=4,
#             verbose=False)

#         score = auc_score(model, test, train_interactions=train, num_threads=num_threads).mean()

#         hyperparams["num_epochs"] = num_epochs

#         yield (score, hyperparams, model)
        
# (score, hyperparams, model) = max(random_search(data_train, data_test, num_threads=2), key=lambda x: x[0])

# print("Best score {} at {}".format(score, hyperparams))
params = {"loss": ['bpr', 'warp'],
            'learning_rate': [0.01, 0.05, 0.07, 0.1],
            'item_alpha': [0.01, 0.05, 0.1],
            'user_alpha': [0.01, 0.05, 0.1]}
param_list = []
param_names = ('loss', 'learning_rate', 'item_alpha', 'user_alpha')
for v in params.values():
    param_list.append(v)


def grid_search(train, test, param_list, num_threads=1):
    
    for par in list(itertools.product(*param_list)):
        hyperparametrs = dict(zip(param_names, par))
        model = LightFM(**hyperparametrs)
        model.fit((sparse_user_item > 0) * 1,  # user-item matrix из 0 и 1
            sample_weight=coo_matrix(user_item_matrix),
            user_features=csr_matrix(user_feat_lightfm.values).tocsr(),
            item_features=csr_matrix(item_feat_lightfm.values).tocsr(),
            epochs=15, 
            num_threads=4,
            verbose=False)
        
        score = auc_score(model, test, train_interactions=train, num_threads=num_threads).mean()
        print(score)
    
    yield (score, hyperparams, model)
    
(score, hyperparams, model) = max(grid_search(data_train, data_test, param_list, num_threads=2), key=lambda x: x[0])
print("Best score {} at {}".format(score, hyperparams))
params1 = {"loss": ['bpr', 'warp'],
            'learning_rate': [0.01, 0.05, 0.07, 0.1],
            'item_alpha': [0.01, 0.05, 0.1],
            'user_alpha': [0.01, 0.05, 0.1],
            'num_epochs': [5, 10, 15, 20]}
param1_list = []
for v in params1.values():
    param1_list.append(v)
print(param1_list)

param_names = ('loss', 'learning_rate', 'items_alpha', 'user_alpha', 'num_epochs')
for i in itertools.product(*param1_list):
    print(dict(zip(param_names, i)))<jupyter_output>{'loss': 'bpr', 'learning_rate': 0.01, 'items_alpha': 0.01, 'user_alpha': 0.01, 'num_epochs': 5}
{'loss': 'bpr', 'learning_rate': 0.01, 'items_alpha': 0.01, 'user_alpha': 0.01, 'num_epochs': 10}
{'loss': 'bpr', 'learning_rate': 0.01, 'items_alpha': 0.01, 'user_alpha': 0.01, 'num_epochs': 15}
{'loss': 'bpr', 'learning_rate': 0.01, 'items_alpha': 0.01, 'user_alpha': 0.01, 'num_epochs': 20}
{'loss': 'bpr', 'learning_rate': 0.01, 'items_alpha': 0.01, 'user_alpha': 0.05, 'num_epochs': 5}
{'loss': 'bpr', 'learning_rate': 0.01, 'items_alpha': 0.01, 'user_alpha': 0.05, 'num_epochs': 10}
{'loss': 'bpr', 'learning_rate': 0.01, 'items_alpha': 0.01, 'user_alpha': 0.05, 'num_epochs': 15}
{'loss': 'bpr', 'learning_rate': 0.01, 'items_alpha': 0.01, 'user_alpha': 0.05, 'num_epochs': 20}
{'loss': 'bpr', 'learning_rate': 0.01, 'items_alpha': 0.01, 'user_alpha': 0.1, 'num_epochs': 5}
{'loss': 'bpr', 'learning_rate': 0.01, 'items_alpha': 0.01, 'user_alpha': 0.1, 'num_epochs': 10}
{'loss': 'bpr', 'learning[...]
{'fraction_non_alphanumeric': 0.1314557653300047, 'fraction_numerical': 0.024730847246060228, 'mean_word_length': 3.9058553386911594, 'pattern_counts': {'":': 2, '<': 48, '<?xml version=': 0, '>': 56, 'https://': 3, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 14, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '12', 'dir': 'jupyter-structured-clean-dedup', 'id': '137448', 'license': 'MIT', 'n_tokens_mistral': 4899, 'n_tokens_neox': 4820, 'n_words': 860, 'path': 'gb/webinar_5.ipynb', 'repo_name': 'QuackingKnox/geekbrains', 'size': '12818'}