<jupyter_start><jupyter_text>Задание к занятию «Рекомендации на основе скрытых факторов»Описание задания:Что делать1. Установить implicit;2. Взять датасет last.fm (урезанный или полный);3. Разбить датасет на обучающую и тестовую выборки;4. Построить на обучающей выборке хотя бы две модели из пакета implicit:    − kNN по косинусной мере    − ALS5. Получить рекомендации на тестовой выборке для обученных моделей;6. Сравнить метрики качества обученных моделей на тестовой выборке с помощью mrec (или иным способом).<jupyter_code>import pandas as pd
import numpy as np
# plots snippet
from matplotlib import pyplot as plt
import seaborn as sns
from pylab import rcParams

%config InlineBackend.figure_format = 'retina'
%matplotlib inline
%config InlineBackend.figure_format = 'png'
rcParams['figure.figsize'] = (16, 6)
! ls
column_names = ["user", "artist-mbid", "artist-name", "total-plays"]
data = pd.read_csv('lastfm_small.tsv', sep='\t', header=None, names=column_names)
data.head()<jupyter_output><empty_output><jupyter_text>Подготовка данных<jupyter_code>data.fillna("None", inplace=True)
data["user_id"] = data["user"].astype("category").cat.codes.copy() + 1
data["artist_id"] = data["artist-mbid"].astype("category").cat.codes.copy() + 1
data["plays"] = data["total-plays"].astype(np.double)
data.drop(["artist-name", "artist-mbid", "user", "total-plays"], axis=1, inplace=True)
data.head()
data.describe()
test_indices = np.random.choice(data.index.values, replace=False, size=int(len(data.index.values) * 0.2))
test_data = data.iloc[test_indices]
train_data = data.drop(test_indices)
train_data.shape, test_data.shape
test_user_set = set(test_data["user_id"].unique())
train_user_set = set(train_data["user_id"].unique())
print("нет в обучающей выборке, но есть в тестовой: {}".format(len(test_user_set - train_user_set)))
print("нет в тестовой выборке, но есть в обучающей: {}".format(len(train_user_set - test_user_set)))
print("всего пользователей: {}".format(len(data["user_id"].unique())))
user_ids_to_exclude = (test_user_set - train_user_set).union(train_user_set - test_user_set)
test_data.drop(test_data[test_data["user_id"].isin(user_ids_to_exclude).values].index, inplace=True)
train_data.drop(train_data[train_data["user_id"].isin(user_ids_to_exclude).values].index, inplace=True)
train_data.shape, test_data.shape
test_data.to_csv('lastfm.test.tsv', sep='\t', index=False, header=False)
from scipy.sparse import csr_matrix
from scipy.sparse import coo_matrix
# функция, которая красиво печатает информацию о разреженных матрицах

def sparse_info(sparse_matrix: csr_matrix) -> None:
    print("Размерности матрицы: {}".format(sparse_matrix.shape))
    print("Ненулевых элементов в матрице: {}".format(sparse_matrix.nnz))
    print("Доля ненулевых элементов: {}"
          .format(sparse_matrix.nnz / sparse_matrix.shape[0] / sparse_matrix.shape[1])
    )
    print("Среднее значение ненулевых элементов: {}".format(sparse_matrix.data.mean()))
    print("Максимальное значение ненулевых элементов: {}".format(sparse_matrix.data.max()))
    print("Минимальное значение ненулевых элементов: {}".format(sparse_matrix.data.min()))
plays = coo_matrix((
    train_data["plays"].astype(np.float),
    (
        train_data["artist_id"],
        train_data["user_id"]
    )
))

sparse_info(plays.tocsr())<jupyter_output>Размерности матрицы: (66800, 20467)
Ненулевых элементов в матрице: 799776
Доля ненулевых элементов: 0.0005849755514134189
Среднее значение ненулевых элементов: 216.31050319089346
Максимальное значение ненулевых элементов: 135392.0
Минимальное значение ненулевых элементов: 1.0<jupyter_text>Обучение и предсказания  ALS<jupyter_code>from implicit.als import AlternatingLeastSquares
from multiprocessing import cpu_count
model = AlternatingLeastSquares(factors=100, num_threads=cpu_count())
model.fit(plays)
user_plays = plays.T.tocsr()

predict_artist = []

for user_id in test_data['user_id'].unique():
    for artist_id, score in model.recommend(user_id, user_plays):        
        predict_artist.append((user_id, artist_id, score))

als_predictions = pd.DataFrame(predict_artist, columns=['user_id', 'artist_id', 'plays'])
als_predictions.to_csv('lastfm.als.pred.tsv', sep='\t', index=False, header=False)<jupyter_output><empty_output><jupyter_text>KNN<jupyter_code>from implicit.nearest_neighbours import CosineRecommender
model = CosineRecommender()
model.fit(plays)
sparse_info(model.similarity)
user_plays = plays.T.tocsr()

predict_artist = []

for user_id in test_data['user_id'].unique():
    for artist_id, score in model.recommend(user_id, user_plays):        
        predict_artist.append((user_id, artist_id, score))

knn_predictions = pd.DataFrame(predict_artist, columns=['user_id', 'artist_id', 'plays'])
knn_predictions.to_csv('lastfm.knn.pred.tsv', sep='\t', index=False, header=False)<jupyter_output><empty_output><jupyter_text>Метрики качества<jupyter_code>from sklearn.metrics import mean_squared_error
from math import sqrt
als_test_predictions = pd.merge(test_data, als_predictions, on=['user_id', 'artist_id'], suffixes=('_test', '_pred'))
knn_test_predictions = pd.merge(test_data, knn_predictions, on=['user_id', 'artist_id'], suffixes=('_test', '_pred'))<jupyter_output><empty_output><jupyter_text>RMSE<jupyter_code>print('ALS', sqrt(mean_squared_error(als_test_predictions['plays_test'], als_test_predictions['plays_pred'])))
print('KNN', sqrt(mean_squared_error(knn_test_predictions['plays_test'], knn_test_predictions['plays_pred'])))<jupyter_output>ALS 969.9084665130302
KNN 1419.5128903400305<jupyter_text>MREC<jupyter_code>from collections import defaultdict
from mrec import load_sparse_matrix
from mrec.evaluation.metrics import compute_main_metrics, compute_hit_rate
from mrec.evaluation import Evaluator
from mrec.evaluation.metrics import print_report

evaluator = Evaluator(compute_main_metrics, max_items=20)
testdata = load_sparse_matrix('tsv', 'lastfm.test.tsv').tocsr()<jupyter_output><empty_output><jupyter_text>ALS<jupyter_code>all_metrics = defaultdict(list)

cum_metrics, count = evaluator.process(testdata, 'lastfm.als.pred.tsv', 0, testdata.shape[0])
if cum_metrics is not None:
    for m in cum_metrics:
        all_metrics[m].append(float(cum_metrics[m]) / count)
        
print_report(['ALS'], [all_metrics])<jupyter_output>ALS
mrr            0.3132 +/- 0.0000
prec@5         0.1377 +/- 0.0000
prec@10        0.1128 +/- 0.0000
prec@15        0.0752 +/- 0.0000
prec@20        0.0564 +/- 0.0000<jupyter_text>KNN<jupyter_code>all_metrics = defaultdict(list)

cum_metrics, count = evaluator.process(testdata, 'lastfm.knn.pred.tsv', 0, testdata.shape[0])
if cum_metrics is not None:
    for m in cum_metrics:
        all_metrics[m].append(float(cum_metrics[m]) / count)
        
print_report(['KNN'], [all_metrics])<jupyter_output>KNN
mrr            0.0483 +/- 0.0000
prec@5         0.0164 +/- 0.0000
prec@10        0.0147 +/- 0.0000
prec@15        0.0098 +/- 0.0000
prec@20        0.0074 +/- 0.0000
{'fraction_non_alphanumeric': 0.1320916905444126, 'fraction_numerical': 0.0335243553008596, 'mean_word_length': 6.226708074534161, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 34, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '356311', 'license': 'MIT', 'n_tokens_mistral': 2848, 'n_tokens_neox': 2716, 'n_words': 463, 'path': '4.3-latent-factors/latent-factors.ipynb', 'repo_name': '0xBADC0FFEE/netology-practice', 'size': '6980'}