Keras深度学习——构建电影推荐系统

1,386 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第19天,点击查看活动详情

前言

推荐系统在用户发现中起主要作用。假设,我们具有数千种不同的产品,每种产品还存在不同的规格、样式等。在这种情况下,对用户进行有关产品的精准推荐将成为增加销量的关键。在本节中,我们将以电影推荐系统为例介绍推荐系统模型构建的方法,从而为用户推荐其真正感兴趣的产品。

模型与数据集分析

在本节中,我们将学习如何根据用户对电影的评分数据库构建电影推荐系统,任务目的是最大限度地提高所推荐电影对用户的相关性。在定义目标时,我们还应该考虑推荐的电影虽然相关,但用户可能并不会立即观看。同时,我们还应该确保所有的推荐并不都是关于同一种类型的,这对于推荐系统至关重要,例如,在零售环境中,我们并不希望一直向用户推荐不同规格的同一种产品。 综合以上分析,我们可以形式化地定义我们的目标和约束条件:

  • 目标:最大限度地提高推荐与用户的相关性
  • 约束:增加推荐的多样性,并向用户提供最多 12 条推荐建议

相关性的定义在不同任务中可能会有所不同,通常需要以实际业务需求为指导。在本任务中,我们狭义地定义相关性,也就是说,如果用户采纳了向用户推荐的前 12 条建议中的任何一个,则认为推荐成功相关。

数据集分析

用于模型训练的数据集中包含用户信息以及他们对其观看过的电影评分信息,其中第一列表示用户编号,第二列表示电影编号,第三列表示用户对电影的评分,最后一列表示时间戳。

模型分析

在实现电影推荐系统前,我们首先梳理构建推荐系统模型的策略流程:

  • 导入数据
  • 推荐用户评价较高的电影,我们根据用户在历史记录中喜欢的电影来训练我们的模型。我们也可以根据用户不喜欢的电影来进一步改善推荐的准确性,但为了简单起见,本任务暂不考虑用户不喜欢的电影
  • 只保留观看过 5 部以上电影的用户
  • 为不同用户和电影分配不同 ID
  • 鉴于用户的偏好可能会随着时间而变化,因此我们需要考虑用户的历史记录,其中历史记录中的不同事件具有与之相关的不同权重。因此,这是一个典型的时间序列分析问题,可以利用循环神经网络 (Recurrent neural networks, RNN) 来解决此问题
  • 预处理数据,以便可以将其传递到长短时记忆网络 (Long Short Term Memory, LSTM) :
    • 输入是用户历史观看的 5 部电影
    • 输出是用户观看的第 6 部电影
  • 构建模型执行以下操作:
    • 为输入影片创建嵌入
    • 将嵌入传递到 LSTM
    • LSTM 层的输出连接到全连接层
    • 在最后一层上应用 softmax 函数,以输出要推荐的电影列表

电影推荐系统

在本节中,我们根据在上一小节中定义的模型策略实现电影推荐模型。

基于 LSTM 实现电影推荐系统

(1) 导入用户-电影数据集,该数据集包含用户列表,并且具有用户为不同电影提供的评分以及用户提供评分时相应的时间戳:

import numpy as np
import pandas as pd

column_names = ['User', 'Movies', 'rating', 'timestamp']
ratings = pd.read_csv('u.data', sep='\t', names=column_names)
print(ratings.head())

数据集的示例如下所示:

   User  Movies  rating  timestamp
0   196     242       3  881250949
1   186     302       3  891717742
2    22     377       1  878887116
3   244      51       2  880606923
4   166     346       1  886397596

(2) 筛选出用户不喜欢的电影(即用户对电影评分较低)的数据样本或没有足够历史记录的用户数据样本。首先,排除用户提供较低评分的电影数据样本:

ratings = ratings[ratings['rating']>3]
ratings = ratings.sort_values(by='timestamp')
ratings.reset_index(inplace=True)
ratings = ratings.drop(['index'],axis=1)

然后,我们仅保留历史记录中评价的电影数量超过 5 的用户:

user_movie_count =ratings.groupby('User').agg({'Movies':'nunique'}).reset_index()
user_movie_count.columns = ['User','Movie_count']
ratings2 = ratings.merge(user_movie_count,on='User',how='inner')
movie_count = ratings2[ratings2['Movie_count']>5]
movie_count = movie_count.sort_values('timestamp')
movie_count.reset_index(inplace=True)
movie_count = movie_count.drop(['index'],axis=1)

(3) 为不同用户和电影分配不同 ID,以便后续使用:

ratings = movie_count
users = ratings.User.unique()
movies = ratings.Movies.unique()
userid2idx = {o:i for i,o in enumerate(users)}
moviesid2idx = {o:i for i,o in enumerate(movies)}
idx2userid = {i:o for i,o in enumerate(users)}
idx2moviesid = {i:o for i,o in enumerate(movies)}

ratings['Movies2'] = ratings.Movies.apply(lambda x: moviesid2idx[x])
ratings['User2'] = ratings.User.apply(lambda x: userid2idx[x])

(4) 预处理数据,使用大小为 5 的滑动窗口构建输入电影,输出为用户已观看的第 6 部电影:

x = []
y = []
user_list = movie_count['User2'].unique()
for i in range(len(user_list)):
    total_user_movies = movie_count[movie_count['User2']==user_list[i]].copy()
    total_user_movies.reset_index(inplace=True)
    total_user_movies = total_user_movies.drop(['index'],axis=1)
    for j in range(total_user_movies.shape[0]-6):
        x.append(total_user_movies.loc[j:(j+4),'Movies2'].tolist())
        y.append(total_user_movies.loc[(j+5),'Movies2'].tolist())

(5) 预处理 xy 变量,以便可以将它们传递到模型,然后创建训练和测试数据集:

from keras.utils import to_categorical

l = list(moviesid2idx.keys())
print(len(l))
set([x for x in l if l.count(x) > 1])

y2 = to_categorical(y, num_classes = max(y)+1)
x_train = np.array(x[:40000])
x_test = np.array(x[40000:])
y_train = np.array(y2[:40000])
y_test = np.array(y2[40000:])

(6) 构建电影推荐模型:

model = Sequential()
model.add(Embedding(src_vocab, n_units, input_length=src_timesteps))
model.add((LSTM(100)))
model.add(Dense(1000,activation='relu'))
model.add(Dense(max(y)+1,activation='softmax'))
model.summary()

该模型的简要信息输出如下:

Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding (Embedding)        (None, 5, 32)             46304     
_________________________________________________________________
lstm (LSTM)                  (None, 100)               53200     
_________________________________________________________________
dense (Dense)                (None, 1024)              103424    
_________________________________________________________________
dense_1 (Dense)              (None, 1447)              1483175   
=================================================================
Total params: 1,686,103
Trainable params: 1,686,103
Non-trainable params: 0
_________________________________________________________________

(7) 编译并拟合模型构建完成的模型:

from keras.optimizers import Adam
adam = Adam(lr=0.0001)
model.compile(optimizer=adam, loss='categorical_crossentropy', metrics = ['acc'])

history = model.fit(x_train, y_train,
                epochs=20,
                batch_size=64,
                validation_data=(x_test, y_test),
                verbose=1)

(8) 对测试数据进行预测:

pred = model.predict(x_test)

我们假设,如果用户将要观看的第 6 部影片在推荐概率最高的前 12 部影片中,则表示该推荐系统能够向用户推荐合适的影片,计算在所有测试用户中,推荐了合适影片的比例:

count = 0
for i in range(x_test.shape[0]):
    rank = np.argmax(np.argsort(pred[i])[::-1]==np.argmax(y_test[i]))
    if rank<12:
        count+=1

print(count/x_test.shape[0])
# 0.1173

我们所构建的电影推荐模型大约有 11.73% 的概率能够为用户推荐合适的影片。