TLDR:
Проблема: задан набор данных о пользователях, фильмах и рейтингах. Можем ли мы создать модель, которая предсказывает рейтинги фильмов для пользователей?
Набор данных: ml-latest-small.zip из https://grouplens.org/datasets/movielens/
Решение (2 части):
1) Базовая матричная факторизация
2) Расширенная матричная факторизация (термины смещения, смещение, инициализация весов, сигмовидный_диапазон) https://medium. com/@datadote/matrix-factorization-advanced-pictures-code-part-2-3072450879c1
Код: 01_matrix_fact_simple.ipynb https://github.com/Datadote/ матрица-факторизация-pytorch
Шаги:
1) Опишите проблему и изучите набор данных
2) Предварительно обработайте набор данных для обучения и проверки
3) Создайте модель матричной факторизации
4) Обучите модель
5) Проверить результаты
6) Дальнейшие действия
1) Опишите проблему и изучите набор данных
Проблема. Дан набор данных о пользователях, фильмах и оценках. Можем ли мы создать модель, которая предсказывает рейтинги фильмов для пользователей?
Набор данных: ml-latest-small.zip из https://grouplens.org/datasets/movielens/
Данные состоят из пользователей, фильмов, рейтингов, меток времени, заголовков и жанров.
DATA_DIR = './data/ml-latest-small/' dfm = pd.read_csv(DATA_DIR+'movies.csv') df = pd.read_csv(DATA_DIR+'ratings.csv') df = df.merge(dfm, on='movieId', how='left') df = df.sort_values(['userId', 'timestamp'], ascending=[True, True]).reset_index(drop=True) df.head(3)
2) Предварительно обработать набор данных для обучения и проверки
i) Преобразование столбцов в категориальные с помощью defaultdict(LabelEncoder)
Это переназначает диапазон столбцов на [0, len(unique(column))]. Например, необработанный максимальный идентификатор фильма равен 193609, но существует только 9723 уникальных идентификатора фильма. Это переназначение (193609 -> 9723) важно для уменьшения использования памяти во встраиваниях модели.
d = defaultdict(LabelEncoder) cols_cat = ['userId', 'movieId'] for c in cols_cat: d[c].fit(df[c].unique()) df[c] = d[c].transform(df[c]) df.head(3)
ii) Разделить данные на обучение/проверку. Создайте наборы данных MovieDatasets и загрузчики данных
Каждый пользователь имеет не менее 20 оценок. Используйте последние 5 оценок для проверки. Используйте оставшиеся данные для поезда.
df_train = df.groupby('userId').head(-5).reset_index(drop=True) df_val = df.groupby('userId').tail(5).reset_index(drop=True) class MovieDataset(Dataset): def __init__(self, df): super().__init__() self.df = df[['userId', 'movieId', 'rating']] self.x_user_movie = list(zip(df.userId.values, df.movieId.values)) self.y_rating = self.df.rating.values def __len__(self): return len(self.df) def __getitem__(self, idx): return self.x_user_movie[idx], self.y_rating[idx] BS = 8192 ds_train = MovieDataset(df_train) ds_val = MovieDataset(df_val) dl_train = DataLoader(ds_train, BS, shuffle=True, num_workers=4) dl_val = DataLoader(ds_val, BS, shuffle=True, num_workers=4) xb, yb = next(iter(dl_train)) print(xb) print(yb)
3) Создайте матричную модель факторизации
i) идея совместной фильтрации
Набор данных может быть представлен в виде таблицы пользователей и фильмов. Можем ли мы найти похожих пользователей и предсказать рейтинги непросмотренных фильмов?
ii) Реализация PyTorch
Каждый пользователь и фильм (элемент) проходят через слой nn.Embedding. Этот слой создает векторное представление. Затем векторы user_emb и item_emb перемножаются и суммируются (эквивалентно скалярному произведению).
class MF(nn.Module): """ Matrix factorization model simple """ def __init__(self, num_users, num_items, emb_dim): super().__init__() self.user_emb = nn.Embedding(num_embeddings=num_users, embedding_dim=emb_dim) self.item_emb = nn.Embedding(num_embeddings=num_items, embedding_dim=emb_dim) def forward(self, user, item): user_emb = self.user_emb(user) item_emb = self.item_emb(item) element_product = (user_emb*item_emb).sum(1) return element_product n_users = len(df.userId.unique()) n_items = len(df.movieId.unique()) mdl = MF(n_users, n_items, emb_dim=32) mdl.to(device) print(mdl)
4) Модель поезда
Оптимизатор AdamW и среднеквадратичные потери (MSE) используются для обучения модели.
LR = 0.2 NUM_EPOCHS = 10 opt = optim.AdamW(mdl.parameters(), lr=LR) loss_fn = nn.MSELoss() epoch_train_losses, epoch_val_losses = [], [] for i in range(NUM_EPOCHS): train_losses, val_losses = [], [] mdl.train() for xb,yb in dl_train: xUser = xb[0].to(device, dtype=torch.long) xItem = xb[1].to(device, dtype=torch.long) yRatings = yb.to(device, dtype=torch.float) preds = mdl(xUser, xItem) loss = loss_fn(preds, yRatings) train_losses.append(loss.item()) opt.zero_grad() loss.backward() opt.step() mdl.eval() for xb,yb in dl_val: xUser = xb[0].to(device, dtype=torch.long) xItem = xb[1].to(device, dtype=torch.long) yRatings = yb.to(device, dtype=torch.float) preds = mdl(xUser, xItem) loss = loss_fn(preds, yRatings) val_losses.append(loss.item()) # Start logging epoch_train_loss = np.mean(train_losses) epoch_val_loss = np.mean(val_losses) epoch_train_losses.append(epoch_train_loss) epoch_val_losses.append(epoch_val_loss) print(f'Epoch: {i}, Train Loss: {epoch_train_loss:0.1f}, Val Loss:{epoch_val_loss:0.1f}')
5) Проверить результаты
Давайте проведем некоторые проверки на вменяемость. Диапазон оценок модели составляет [-8,3, 9,8], что выходит за пределы фактического диапазона оценок [0,5, 5]. Некоторые рейтинги прогнозов выглядят близкими к фактическим рейтингам.
user_emb_min_w = mdl.user_emb.weight.min().item() user_emb_max_w = mdl.user_emb.weight.max().item() item_emb_min_w = mdl.item_emb.weight.min().item() item_emb_max_w = mdl.item_emb.weight.max().item() print(f'Emb user min/max w: {user_emb_min_w:0.3f} / {user_emb_max_w:0.3f}') print(f'Emb item min/max w: {item_emb_min_w:0.3f} / {item_emb_max_w:0.3f}') print(f'Preds min/max: {preds.min().item():0.2f} / {preds.max().item():0.2f}') print(f'Rating min/max: {yRatings.min().item():0.2f} / {yRatings.max().item():0.2f}') print(preds.detach().cpu().numpy()[:6]) print(yRatings.detach().cpu().numpy()[:6])
6) Следующие шаги
Во второй части мы улучшим эту матричную модель факторизации, добавив термины смещения пользователя и элемента, смещение, инициализацию веса и sigmoid_range. Для части 2 https://medium.com/@datadote/matrix-factorization-advanced-pictures-code-part-2-3072450879c1