作者: Siddhartha Banerjee
创建日期: 2020/05/24
最后修改: 2020/05/24
描述: 使用在Movielens数据集上训练的模型进行电影推荐。
本示例演示了使用 Movielens 数据集 的 协同过滤 来向用户推荐电影。 MovieLens 评级数据集列出了用户对一组电影给出的评级。 我们的目标是能够预测用户尚未观看的电影的评级。 然后可以向用户推荐预测评级最高的电影。
模型的步骤如下:
参考文献:
import pandas as pd
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
from zipfile import ZipFile
import keras
from keras import layers
from keras import ops
# 从 http://files.grouplens.org/datasets/movielens/ml-latest-small.zip 下载实际数据
# 使用 ratings.csv 文件
movielens_data_file_url = (
"http://files.grouplens.org/datasets/movielens/ml-latest-small.zip"
)
movielens_zipped_file = keras.utils.get_file(
"ml-latest-small.zip", movielens_data_file_url, extract=False
)
keras_datasets_path = Path(movielens_zipped_file).parents[0]
movielens_dir = keras_datasets_path / "ml-latest-small"
# 仅在脚本首次运行时提取数据。
if not movielens_dir.exists():
with ZipFile(movielens_zipped_file, "r") as zip:
# 提取文件
print("正在提取所有文件...")
zip.extractall(path=keras_datasets_path)
print("完成!")
ratings_file = movielens_dir / "ratings.csv"
df = pd.read_csv(ratings_file)
从 http://files.grouplens.org/datasets/movielens/ml-latest-small.zip 下载数据
978202/978202 ━━━━━━━━━━━━━━━━━━━━ 0s 0us/step
正在提取所有文件...
完成!
首先,需要进行一些预处理以将用户和电影编码为整数索引。
user_ids = df["userId"].unique().tolist()
user2user_encoded = {x: i for i, x in enumerate(user_ids)}
userencoded2user = {i: x for i, x in enumerate(user_ids)}
movie_ids = df["movieId"].unique().tolist()
movie2movie_encoded = {x: i for i, x in enumerate(movie_ids)}
movie_encoded2movie = {i: x for i, x in enumerate(movie_ids)}
df["user"] = df["userId"].map(user2user_encoded)
df["movie"] = df["movieId"].map(movie2movie_encoded)
num_users = len(user2user_encoded)
num_movies = len(movie_encoded2movie)
df["rating"] = df["rating"].values.astype(np.float32)
# 最低和最高评级将在后面用于标准化评级
min_rating = min(df["rating"])
max_rating = max(df["rating"])
print(
"用户数量: {}, 电影数量: {}, 最低评级: {}, 最高评级: {}".format(
num_users, num_movies, min_rating, max_rating
)
)
用户数量: 610, 电影数量: 9724, 最低评级: 0.5, 最高评级: 5.0
df = df.sample(frac=1, random_state=42)
x = df[["user", "movie"]].values
# 将目标标准化到0和1之间。易于训练。
y = df["rating"].apply(lambda x: (x - min_rating) / (max_rating - min_rating)).values
# 假设在90%的数据上进行训练,在10%的数据上进行验证。
train_indices = int(0.9 * df.shape[0])
x_train, x_val, y_train, y_val = (
x[:train_indices],
x[train_indices:],
y[:train_indices],
y[train_indices:],
)
我们将用户和电影嵌入到50维向量中。
该模型通过点积计算用户和电影嵌入之间的匹配分数,并增加每个电影和每个用户的偏差。匹配分数被缩放到 [0, 1]
interval 通过 sigmoid (因为我们的评分被归一化到这个范围)。
EMBEDDING_SIZE = 50
class RecommenderNet(keras.Model):
def __init__(self, num_users, num_movies, embedding_size, **kwargs):
super().__init__(**kwargs)
self.num_users = num_users
self.num_movies = num_movies
self.embedding_size = embedding_size
self.user_embedding = layers.Embedding(
num_users,
embedding_size,
embeddings_initializer="he_normal",
embeddings_regularizer=keras.regularizers.l2(1e-6),
)
self.user_bias = layers.Embedding(num_users, 1)
self.movie_embedding = layers.Embedding(
num_movies,
embedding_size,
embeddings_initializer="he_normal",
embeddings_regularizer=keras.regularizers.l2(1e-6),
)
self.movie_bias = layers.Embedding(num_movies, 1)
def call(self, inputs):
user_vector = self.user_embedding(inputs[:, 0])
user_bias = self.user_bias(inputs[:, 0])
movie_vector = self.movie_embedding(inputs[:, 1])
movie_bias = self.movie_bias(inputs[:, 1])
dot_user_movie = ops.tensordot(user_vector, movie_vector, 2)
# 添加所有组成部分(包括偏差)
x = dot_user_movie + user_bias + movie_bias
# Sigmoid 激活将评分强制限制在 0 和 1 之间
return ops.nn.sigmoid(x)
model = RecommenderNet(num_users, num_movies, EMBEDDING_SIZE)
model.compile(
loss=keras.losses.BinaryCrossentropy(),
optimizer=keras.optimizers.Adam(learning_rate=0.001),
)
history = model.fit(
x=x_train,
y=y_train,
batch_size=64,
epochs=5,
verbose=1,
validation_data=(x_val, y_val),
)
Epoch 1/5
1418/1418 ━━━━━━━━━━━━━━━━━━━━ 2s 1ms/step - loss: 0.6591 - val_loss: 0.6201
Epoch 2/5
1418/1418 ━━━━━━━━━━━━━━━━━━━━ 1s 894us/step - loss: 0.6159 - val_loss: 0.6191
Epoch 3/5
1418/1418 ━━━━━━━━━━━━━━━━━━━━ 1s 977us/step - loss: 0.6093 - val_loss: 0.6138
Epoch 4/5
1418/1418 ━━━━━━━━━━━━━━━━━━━━ 1s 865us/step - loss: 0.6100 - val_loss: 0.6123
Epoch 5/5
1418/1418 ━━━━━━━━━━━━━━━━━━━━ 1s 854us/step - loss: 0.6072 - val_loss: 0.6121
plt.plot(history.history["loss"])
plt.plot(history.history["val_loss"])
plt.title("模型损失")
plt.ylabel("损失")
plt.xlabel("轮次")
plt.legend(["训练", "测试"], loc="upper left")
plt.show()
movie_df = pd.read_csv(movielens_dir / "movies.csv")
# 让我们获取一位用户并查看前面的推荐。
user_id = df.userId.sample(1).iloc[0]
movies_watched_by_user = df[df.userId == user_id]
movies_not_watched = movie_df[
~movie_df["movieId"].isin(movies_watched_by_user.movieId.values)
]["movieId"]
movies_not_watched = list(
set(movies_not_watched).intersection(set(movie2movie_encoded.keys()))
)
movies_not_watched = [[movie2movie_encoded.get(x)] for x in movies_not_watched]
user_encoder = user2user_encoded.get(user_id)
user_movie_array = np.hstack(
([[user_encoder]] * len(movies_not_watched), movies_not_watched)
)
ratings = model.predict(user_movie_array).flatten()
top_ratings_indices = ratings.argsort()[-10:][::-1]
recommended_movie_ids = [
movie_encoded2movie.get(movies_not_watched[x][0]) for x in top_ratings_indices
]
print("显示用户的推荐:{}".format(user_id))
print("====" * 9)
print("用户的高评分电影")
print("----" * 8)
top_movies_user = (
movies_watched_by_user.sort_values(by="rating", ascending=False)
.head(5)
.movieId.values
)
movie_df_rows = movie_df[movie_df["movieId"].isin(top_movies_user)]
for row in movie_df_rows.itertuples():
print(row.title, ":", row.genres)
print("----" * 8)
print("前 10 个电影推荐")
print("----" * 8)
recommended_movies = movie_df[movie_df["movieId"].isin(recommended_movie_ids)]
for row in recommended_movies.itertuples():
print(row.title, ":", row.genres)
272/272 ━━━━━━━━━━━━━━━━━━━━ 0s 714us/step
为用户:249 显示推荐
====================================
用户评分较高的电影
--------------------------------
搏击俱乐部 (1999) : 动作|犯罪|剧情|惊悚
宁静 (2005) : 动作|冒险|科幻
无间道 (2006) : 犯罪|剧情|惊悚
囚徒 (2013) : 剧情|悬疑|惊悚
降临 (2016) : 科幻
--------------------------------
前 10 名电影推荐
--------------------------------
父亲的名字 (1993) : 剧情
蒙提·派森与圣杯 (1975) : 冒险|喜剧|奇幻
公主新娘 (1987) : 动作|冒险|喜剧|奇幻|浪漫
阿拉伯的劳伦斯 (1962) : 冒险|剧情|战争
现代启示录 (1979) : 动作|剧情|战争
全金属外壳 (1987) : 剧情|战争
莫扎特 (1984) : 剧情
荣耀 (1989) : 剧情|战争
唐人街 (1974) : 犯罪|黑色电影|悬疑|惊悚
上帝之城 (Cidade de Deus) (2002) : 动作|冒险|犯罪|剧情|惊悚