来吧!动手搭建一个基于 CNN 的电影推荐系统-阿里云开发者社区

开发者社区> 人工智能> 正文

来吧!动手搭建一个基于 CNN 的电影推荐系统

简介: 本文中,我们将从深度学习卷积神经网络入手,基于 Github 的开源项目来完成 MovieLens 数据集的电影推荐系统。

云栖号资讯:【点击查看更多行业资讯
在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来!

本文中,我们将从深度学习卷积神经网络入手,基于 Github 的开源项目来完成 MovieLens 数据集的电影推荐系统。

什么是推荐系统?

什么是推荐系统呢?首先我们来看看几个常见的推荐场景。

如果你经常通过豆瓣电影评分来找电影,你会发现下图所示的推荐:

image

如果你喜欢购物,根据你的选择和购物行为,平台会给你推荐相似商品:

image

在互联网的很多场景下都可以看到推荐的影子。因为推荐可以帮助用户和商家满足不同的需求:

对用户而言:找到感兴趣的东西,帮助发现新鲜、有趣的事物。
对商家而言:提供个性化服务,提高信任度和粘性,增加营收。
常见的推荐系统主要包含两个方面的内容,基于用户的推荐系统(UserCF)和基于物品的推荐系统(ItemCF)。两者的区别在于,UserCF 给用户推荐那些和他有共同兴趣爱好的用户喜欢的商品,而 ItemCF 给用户推荐那些和他之前喜欢的商品类似的商品。这两种方式都会遭遇冷启动问题。

下面是 UserCF 和 ItemCF 的对比:

image

CNN 是如何应用在文本处理上的?

提到卷积神经网络(CNN),相信大部分人首先想到的是图像分类,比如 MNIST 手写体识别,CAFRI10 图像分类。CNN 已经在图像识别方面取得了较大的成果,随着近几年的不断发展,在文本处理领域,基于文本挖掘的文本卷积神经网络被证明是有效的。

首先,来看看 CNN 是如何应用到 NLP 中的,下面是一个简单的过程图:

image

和图像像素处理不一样,自然语言通常是一段文字,那么在特征矩阵中,矩阵的每一个行向量(比如 word2vec 或者 doc2vec)代表一个 Token,包括词或者字符。如果一段文字包含有 n 个词,每个词有 m 维的词向量,那么我们可以构造出一个 n*m 的词向量矩阵,在 NLP 处理过程中,让过滤器宽度和矩阵宽度保持一致整行滑动。

动手实战基于 CNN 的电影推荐系统

将 CNN 的技术应用到自然语言处理中并与电影推荐相结合,来训练一个基于文本的卷积神经网络,实现电影个性化推荐系统。

首先感谢作者 chengstone 的分享,源码请访问下面网址:

https://github.com/chengstone/movie_recommender

在验证了 CNN 应用在自然语言处理上是有效的之后,从推荐系统的个性化推荐入手,在文本上,把 CNN 成果应用到电影的个性化推荐上。并在特征工程中,对训练集和测试集做了相应的特征处理,其中有部分字段是类型性变量,特征工程上可以采用 one-hot 编码,但是对于 UserID、MovieID 这样非常稀疏的变量,如果使用 one-hot,那么数据的维度会急剧膨胀,对于这份数据集来说是不合适的。

具体算法设计如下。

  1. 定义用户嵌入矩阵
    用户的特征矩阵主要是通过用户信息嵌入网络来生成的,在预处理数据的时候,我们将 UserID、MovieID、性别、年龄、职业特征全部转成了数字类型,然后把这个数字当作嵌入矩阵的索引,在网络的第一层就使用嵌入层,这样数据输入的维度保持在(N,32)和(N,16)。然后进行全连接层,转成(N,128)的大小,再进行全连接层,转成(N,200)的大小,这样最后输出的用户特征维度相对比较高,也保证了能把每个用户所带有的特征充分携带并通过特征表达。

具体流程如下:

image

  1. 生成用户特征
    生成用户特征是在用户嵌入矩阵网络输出结果的基础上,通过2层全连接层实现的。第一个全连接层把特征矩阵转成(N,128)的大小,再进行第二次全连接层,转成(N,200)的大小,这样最后输出的用户特征维度相对比较高,也保证了能把每个用户所带有的特征充分携带并通过特征表达。

具体流程如下:

image

  1. 定义电影 ID 嵌入矩阵
    通过电影 ID 和电影类型分别生成电影 ID 和电影类型特征,电影类型的多个嵌入向量做加和输出。电影 ID 的实现过程和上面一样,但是对于电影类型的处理相较于上面,稍微复杂一点。因为电影类型有重叠性,一个电影可以属于多个类别,当把电影类型从嵌入矩阵索引出来之后是一个(N,32)形状的矩阵,因为有多个类别,这里采用的处理方式是矩阵求和,把类别加上去,变成(1,32)形状,这样使得电影的类别信息不会丢失。

具体流程如下:

image

  1. 文本卷积神经网络设计
    文本卷积神经网络和单纯的 CNN 网络结构有点不同,因为自然语言通常是一段文字与图片像素组成的矩阵是不一样的。在电影文本特征矩阵中,矩阵的每一个行构成的行向量代表一个 Token,包括词或者字符。如果一段文字有 n 个词,每个词有 m 维的词向量,那么我们可以构造出一个 n*m 的矩阵。而且 NLP 处理过程中,会有多个不同大小的过滤器串行执行,且过滤器宽度和矩阵宽度保持一致,是整行滑动。在执行完卷积操作之后采用了 ReLU 激活函数,然后采用最大池化操作,最后通过全连接并 Dropout 操作和 Softmax 输出。这里电影名称的处理比较特殊,并没有采用循环神经网络,而采用的是文本在 CNN 网络上的应用。

对于电影数据集,我们对电影名称做 CNN 处理,其大致流程,从嵌入矩阵中得到电影名对应的各个单词的嵌入向量,由于电影名称比较特殊一点,名称长度有一定限制,这里过滤器大小使用时,就选择 2、3、4、5 长度。然后对文本嵌入层使用滑动 2、3、4、5 个单词尺寸的卷积核做卷积和最大池化,然后 Dropout 操作,全连接层输出。

具体流程如下:

image

具体过程描述:

(1)首先输入一个 32*32 的矩阵;

(2)第一次卷积核大小为 22,得到 3131 的矩阵,然后通过 [1,14,1,1] 的 max-pooling 操作,得到的矩阵为 18*31;

(3)第二次卷积核大小为 33,得到 1629的矩阵,然后通过[1,13,1,1] 的 max-pooling 操作,得到的矩阵为 4*29;

(4)第三次卷积核大小 44,得到 126 的矩阵,然后通过 [1,12,1,1] 的 max-pooling 操作,得到的矩阵为 1*26;

(5)第四次卷积核大小 55,得到 122 的矩阵,然后通过 [1,11,1,1] 的 max-pooling 操作,得到的矩阵为 1*22;

(6)最后通过 Dropout 和全连接层,len(window_sizes) filter_num =32,得到 132的矩阵。

  1. 电影各层做一个全连接层
    将上面几步生成的特征向量,通过2个全连接层连接在一起,第一个全连接层是电影 ID 特征和电影类型特征先全连接,之后再和 CNN 生成的电影名称特征全连接,生成最后的特征集。

具体流程如下:

image

  1. 完整的基于 CNN 的电影推荐流程
    把以上实现的模块组合成整个算法,将网络模型作为回归问题进行训练,得到训练好的用户特征矩阵和电影特征矩阵进行推荐。

image

基于 CNN 的电影推荐系统代码调参过程

在训练过程中,我们需要对算法预先设置一些超参数,这里给出的最终的设置结果:

# 设置迭代次数
num_epochs = 5
# 设置BatchSize大小
batch_size = 256
#设置dropout保留比例
dropout_keep = 0.5
# 设置学习率
learning_rate = 0.0001
# 设置每轮显示的batches大小
show_every_n_batches = 20

首先对数据集进行划分,按照 4:1 的比例划分为训练集和测试集,下面给出的是算法模型最终训练集合测试集使用的划分结果:

#将数据集分成训练集和测试集,随机种子不固定
train_X,test_X, train_y, test_y = train_test_split(features,
                                             targets_values,
                                             test_size = 0.3,
                                             random_state = 0)

接下来是具体模型训练过程。训练过程,要不断调参,根据经验调参粒度可以选择从粗到细分阶段进行。

调参过程对比:

(1)第一步,先固定,learning_rate=0.01 和 num_epochs=10,测试 batch_size=128 对迭代时间和 Loss 的影响;

(2)第二步,先固定,learning_rate=0.01 和 num_epochs=10,测试 batch_size=256 对迭代时间和 Loss 的影响;

(3)第三步,先固定,learning_rate=0.01 和 num_epochs=10,测试 batch_size=512 对迭代时间和 Loss 的影响;

(4)第四步,先固定,learning_rate=0.01 和 num_epochs=5,测试 batch_size=128 对迭代时间和 Loss 的影响;

(5)第五步,先固定,learning_rate=0.01 和 num_epochs=5,测试 batch_size=256 对迭代时间和 Loss 的影响;

(6)第六步,先固定,learning_rate=0.01 和 num_epochs=5,测试 batch_size=512 对迭代时间和 Loss 的影响;

(7)第七步,先固定,batch_size=256 和 num_epochs=5,测试 learning_rate=0.001 对 Loss 的影响;

(8)第八步,先固定,batch_size=256 和 num_epochs=5,测试 learning_rate=0.0005 对 Loss 的影响;

(9)第九步,先固定,batch_size=256 和 num_epochs=5,测试 learning_rate=0.0001 对 Loss 的影响;

(10)第十步,先固定,batch_size=256 和 num_epochs=5,测试 learning_rate=0.00005 对 Loss 的影响。

得到的调参结果对比表如下:

通过上面(1)-(6)步调参比较,在 learning_rate、batch_size 相同的情况下,num_epochs 对于训练时间影响较大;而在 learning_rate、num_epochs 相同情况下,batch_size 对 Loss 的影响较大,batch_size 选择512,Loss 有抖动情况,权衡之下,最终确定后续调参固定采用 batch_size=256、num_epochs=5 的超参数值,后续(7)-(10)步,随着 learning_rate 逐渐减小,发现 Loss 是先逐渐减小,而在 learning_rate=0.00005 时反而增大,最终选择出学习率为 learning_rate=0.0001 的超参数值。

扫描下方二维码,即可订阅本专栏
基于 CNN 的电影推荐系统电影推荐
在上面,完成模型训练验证之后,实际来进行推荐电影,这里使用生产的用户特征矩阵和电影特征矩阵做电影推荐,主要有三种方式的推荐。

  1. 推荐同类型的电影
    思路是:计算当前看的电影特征向量与整个电影特征矩阵的余弦相似度,取相似度最大的 top_k 个,这里加了些随机选择在里面,保证每次的推荐稍稍有些不同。
def recommend_same_type_movie(movie_id_val, top_k = 20):

    loaded_graph = tf.Graph()  #
    with tf.Session(graph=loaded_graph) as sess:  #
        # Load saved model
        loader = tf.train.import_meta_graph(load_dir + '.meta')
        loader.restore(sess, load_dir)

        norm_movie_matrics = tf.sqrt(tf.reduce_sum(tf.square(movie_matrics), 1, keep_dims=True))
        normalized_movie_matrics = movie_matrics / norm_movie_matrics

        #推荐同类型的电影
        probs_embeddings = (movie_matrics[movieid2idx[movie_id_val]]).reshape([1, 200])
        probs_similarity = tf.matmul(probs_embeddings, tf.transpose(normalized_movie_matrics))
        sim = (probs_similarity.eval())
        print("您看的电影是:{}".format(movies_orig[movieid2idx[movie_id_val]]))
        print("以下是给您的推荐:")
        p = np.squeeze(sim)
        p[np.argsort(p)[:-top_k]] = 0
        p = p / np.sum(p)
        results = set()
        while len(results) != 5:
            c = np.random.choice(3883, 1, p=p)[0]
            results.add(c)
        for val in (results):
            print(val)
            print(movies_orig[val])
        return result
  1. 推荐您喜欢的电影
    思路是:使用用户特征向量与电影特征矩阵计算所有电影的评分,取评分最高的 top_k 个,同样加了些随机选择部分。
def recommend_your_favorite_movie(user_id_val, top_k = 10):

    loaded_graph = tf.Graph()  #
    with tf.Session(graph=loaded_graph) as sess:  #
        # Load saved model
        loader = tf.train.import_meta_graph(load_dir + '.meta')
        loader.restore(sess, load_dir)

        #推荐您喜欢的电影
        probs_embeddings = (users_matrics[user_id_val-1]).reshape([1, 200])
        probs_similarity = tf.matmul(probs_embeddings, tf.transpose(movie_matrics))
        sim = (probs_similarity.eval())

        print("以下是给您的推荐:")
        p = np.squeeze(sim)
        p[np.argsort(p)[:-top_k]] = 0
        p = p / np.sum(p)
        results = set()
        while len(results) != 5:
            c = np.random.choice(3883, 1, p=p)[0]
            results.add(c)
        for val in (results):
            print(val)
            print(movies_orig[val])

        return results
  1. 看过这个电影的人还看了(喜欢)哪些电影
    (1)首先选出喜欢某个电影的 top_k 个人,得到这几个人的用户特征向量;

(2)然后计算这几个人对所有电影的评分 ;

(3)选择每个人评分最高的电影作为推荐;

(4)同样加入了随机选择。

def recommend_other_favorite_movie(movie_id_val, top_k = 20):
    loaded_graph = tf.Graph()  #
    with tf.Session(graph=loaded_graph) as sess:  #
        # Load saved model
        loader = tf.train.import_meta_graph(load_dir + '.meta')
        loader.restore(sess, load_dir)
        probs_movie_embeddings = (movie_matrics[movieid2idx[movie_id_val]]).reshape([1, 200])
        probs_user_favorite_similarity = tf.matmul(probs_movie_embeddings, tf.transpose(users_matrics))
        favorite_user_id = np.argsort(probs_user_favorite_similarity.eval())[0][-top_k:]

        print("您看的电影是:{}".format(movies_orig[movieid2idx[movie_id_val]]))

        print("喜欢看这个电影的人是:{}".format(users_orig[favorite_user_id-1]))
        probs_users_embeddings = (users_matrics[favorite_user_id-1]).reshape([-1, 200])
        probs_similarity = tf.matmul(probs_users_embeddings, tf.transpose(movie_matrics))
        sim = (probs_similarity.eval())
        p = np.argmax(sim, 1)
        print("喜欢看这个电影的人还喜欢看:")
        results = set()
        while len(results) != 5:
            c = p[random.randrange(top_k)]
            results.add(c)
        for val in (results):
            print(val)
            print(movies_orig[val])
        return results

基于 CNN 的电影推荐系统不足
这里讨论一下基于上述方法所带来的不足:

由于一个新的用户在刚开始的时候并没有任何行为记录,所以系统会出现冷启动的问题;
由于神经网络是一个黑盒子过程,我们并不清楚在反向传播的过程中的具体细节,也不知道每一个卷积层抽取的特征细节,所以此算法缺乏一定的可解释性;
一般来说,在工业界,用户的数据量是海量的,而卷积神经网络又要耗费大量的计算资源,所以进行集群计算是非常重要的。但是由于本课程所做实验环境有限,还是在单机上运行,所以后期可以考虑在服务器集群上全量跑数据,这样获得的结果也更准确。
总结
上面通过 Github 上一个开源的项目,梳理了 CNN 在文本推荐上的应用,并通过模型训练调参,给出一般的模型调参思路,最后建议大家自己把源码下载下来跑跑模型,效果更好。

参考文献及推荐阅读
文章《推荐系统》,zakexu。
Deep Convolutional Neural Networks for Sentiment Analysis of ShortTexts,CND Santos ,M Gattit ,2014.
推荐系统实践,p50-60,p120-130,项亮。

【云栖号在线课堂】每天都有产品技术专家分享!
课程地址:https://yqh.aliyun.com/live

立即加入社群,与专家面对面,及时了解课程最新动态!
【云栖号在线课堂 社群】https://c.tb.cn/F3.Z8gvnK

原文发布时间:2020-05-26
本文作者:佚名
本文来自:“人工智能头条”,了解相关信息可以关注“人工智能头条

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
人工智能
使用钉钉扫一扫加入圈子
+ 订阅

了解行业+人工智能最先进的技术和实践,参与行业+人工智能实践项目

其他文章