教程 | 预测电影偏好?如何利用自编码器实现协同过滤方法

选自Medium

作者:Artem Oppermann

机器之心编译

参与:白妤昕、李泽南

深度自编码器(Deep Autoencoder)由两个对称的深度信念网络组成,它相比常见的自编码器加入了更多隐藏层。在本文中,作者将尝试使用该工具进行协同过滤,帮助人们研究和预测大量用户对于不同电影的喜好。

推荐系统使用协同过滤的方法,通过收集用户的偏好信息来预测特定用户的兴趣。协同过滤技术的基本假设是,如果用户 A 对某个问题与人 B 有相同的口味或意见,那么 A 就更有可能在其他问题上拥有与 B 的相同的意见。

本文将介绍如何根据用户的偏好、观看历史、相同评级和其他电影的其他用户的评价预测用户对电影的评分。

目录:

  • 本文简介
  • 深度自动编码器
  • 模型实施

1 介绍

自动编码器是一种深度学习神经网络架构,可实现协同过滤领域最佳的性能。文章的第一部是理论概述,将会介绍简单的自动编码器及深度自编码器的基础数学概念。在第二部分中,我们将深入实际展示如何在 TensorFlow 中逐步应用这一技术。本文仅覆盖和评价模型中最重要的部分。

整个模型的输入渠道和预处理可以在相应的 GitHub 中查看:https://github.com/artem-oppermann/Deep-Autoencoders-For-Collaborative-Filtering

2. 深度自编码器

自编码器

讨论深度自编码器之前,我们先来介绍它稍微简单些的版本。自编码器(Autoencoder)是一种人工神经网络,用于学习一组输入数据的表示(编码),通常用于实现降维。

在结构上,自编码器的形式是一个前馈神经网络,由输入层、一个隐藏层和一个输出层(图 1)构成。输出层与输入层的神经元数量相同,因此自编码器属于无监督学习,这意味着它不需要标记数据——只需要一组输入数据即可,而不是输入—输出对。

图 1. 典型的 AutoEncoder 架构。

自编码器的隐藏层比输入层小,这使得模型可以通过学习数据中的相关性在隐藏层中创建数据的压缩表示。

输入层到隐藏层的转换被称为编码步骤,从隐藏层到输出层的转换称为解码步骤。我们也可以在数学上将这些转换定义为映射:

该映射是通过将输入数据向量乘以权重矩阵,添加一个偏差项并将所得到的向量应用于非线性运算,如 sigmoid,tanh 或整流线性单元来实现的。

自编码器的训练

在训练期间,编码器接收输入数据样本 x 并将其映射到所谓的隐藏层或隐层表示 z 上。然后解码器将 z 映射到输出向量 x' 上,后者是(在最好的情况下)输入数据 x 的准确表示。需要注意的是,通常情况下准确地重建 x 是不可能的。

具有输出 x' 的训练包括应用随机梯度下降以最小化预定损失,例如均方误差:

深度自编码器

简单自动编码器的扩展版是 Deep Autoencoder(图 2)。从图 2 中可以看出,它与简单的计数器部分唯一的区别在于隐藏层的数量。

图 2. 深度自编码器的架构。

额外的隐藏层使自编码器可以从数学上学习数据中更复杂的底层模式。深度自编码器的第一层可以学习原始输入中的一阶特征(例如图像中的边缘)。第二层可以学习对应于一阶特征的外观中的图案的二阶特征(例如,根据哪些边缘倾向于一起发生——例如以形成轮廓或角检测器)。深度自编码器更深层的特性往往可以学习到更高阶的特性。

把所有东西放在一起:我们需要更多的层来处理更为复杂的数据——比如我们在协作过滤中使用的数据。

3. 实现

如前文所述,你将学会预测用户对电影的评级。就此而言,我们将使用著名的 MovieLens 数据集(https://grouplens.org/datasets/movielens/)。MovieLensis 是一个基于网络的推荐系统和推荐用户观看电影的在线社区。

更具体地说,我们将使用 ml_1m.zip 数据集,该数据集包含 6,040 个 MovieLens 用户制作的,约 3,900 部电影的 1,000,209 个匿名评级。我们需要的导入文件是 ratings.dat。该文件包含 1,000,209 行,全部格式如下:user_id :: movie_id :: rating:time_stamp。

例如 ratings.dat 中的第一行:

1::595::5::978824268  

这意味着用户 1 给了 595 号电影打了五星评分。评分时间可以被忽略,因为在这里我们不会使用它。

我们的深度学习模型需要一个特定的数据结构来进行训练和测试。这种数据结构是一个 UxM 矩阵,其中 U 是用户数量,M 是电影数量。每行 i∈U 是唯一的用户 ID,每列 j∈M 是唯一的电影 ID。这种矩阵的可视化效果如图 3 所示。

此矩阵中的每个条目都是用户给出特定电影的评分。输入 0 意味着用户没有给这部电影任何评价。例如。上图中,1 号用户给电影 3 的评级为四星,而电影第 1 则根本没有评级。

由于本教程将重点介绍深度学习模型的实现,因此不会在这里介绍使用 User-Movie-Matrix 超出 ratings.dat 文件的步骤。对于关于这个主题的进一步问题,你可以去我的 GitHub 页面(https://github.com/artem-oppermann/Deep-Autoencoders-For-Collaborative-Filtering/blob/master/data/preprocess_data.py), 查看相应的 python 脚本。

训练和测试数据集

在模型实现和训练之前,我们需要对数据进行其他重新处理步骤,将数据划分为训练和测试数据集。这一步骤简单明了。到目前为止,我们有一个 User-Movie Matrix,其中每一行都是评级列表。要从列表中获得训练和测试集,我们需要从每一行中取一部分评级,并将它们用于训练,其余子集则用于测试。

作为描述过程的一个例子,我们考虑一个仅包含 15 部电影的小得多的数据集。一个特定的用户可能出给这样的电影评级:

请记住,0 表示该电影未被评级。现在我们将前 10 部电影中的一部分作为训练集并假设其余的还没有被评分:

因此,原始数据的最后 5 个电影等级被用作测试数据,而电影 1-10 被掩盖为未被评级:

此处仅仅简单演示了如何获得不同的组合。在原始的 MovieLens 数据集中,我仅使用每个用户的 10 个电影评级进行测试,其余(绝大多数)用于模型的训练。

TensorFlow 实现

1.模型架构

深度自编码器在这里作为一个类来实现,其中包含所有必需的操作,如类内的推理、优化、损失、准确性等。

在构造器中,内核初始化器设置了权重和偏差。下一步,网络中的所有权重和偏差都会被初始化。权重是遵从正态分布的,平均值为 0.0,方差为 0.02,而偏差在开始时都设置为 0.0。

在这个特定的例子中,网络有三个隐藏层,每层包含 128 个神经元。输入层(和输出层)的大小对应于数据集中所有当前影片的数量。

class DAE:
    ''' Implementation of Deep Autoencoder class'''

    def __init__(self, FLAGS):    
        self.FLAGS=FLAGS
        self.weight_initializer=model_helper._get_weight_initializer()
        self.bias_initializer=model_helper._get_bias_initializer()
        self.init_parameters()


    def init_parameters(self):
        ''' Initializing the weights and biasis of the neural network.'''

        with tf.name_scope('weights'):
            self.W_1=tf.get_variable(name='weight_1', shape=(self.FLAGS.num_v,self.FLAGS.num_h), 
                                     initializer=self.weight_initializer)
            self.W_2=tf.get_variable(name='weight_2', shape=(self.FLAGS.num_h,self.FLAGS.num_h), 
                                     initializer=self.weight_initializer)
            self.W_3=tf.get_variable(name='weight_3', shape=(self.FLAGS.num_h,self.FLAGS.num_h), 
                                     initializer=self.weight_initializer)
            self.W_4=tf.get_variable(name='weight_5', shape=(self.FLAGS.num_h,self.FLAGS.num_v), 
                                     initializer=self.weight_initializer)

        with tf.name_scope('biases'):
            self.b1=tf.get_variable(name='bias_1', shape=(self.FLAGS.num_h), 
                                    initializer=self.bias_initializer)
            self.b2=tf.get_variable(name='bias_2', shape=(self.FLAGS.num_h), 
                                    initializer=self.bias_initializer)
            self.b3=tf.get_variable(name='bias_3', shape=(self.FLAGS.num_h), 
                                    initializer=self.bias_initializer)

2.训练

给定一个输入数据样本 x(用户—电影矩阵的一行),正向传递并计算网络输出。隐藏层使用 sigmoid 作为激活函数。请注意,最后一层没有非线性或偏置项。

def _inference(self, x):
    '''Making one forward pass. Predicting the outputs, given the inputs.'''

    with tf.name_scope('inference'):
         a1=tf.nn.sigmoid(tf.nn.bias_add(tf.matmul(x, self.W_1),self.b1))
         a2=tf.nn.sigmoid(tf.nn.bias_add(tf.matmul(a1, self.W_2),self.b2))
         a3=tf.nn.sigmoid(tf.nn.bias_add(tf.matmul(a2, self.W_3),self.b3))   
         a4=tf.matmul(a3, self.W_4) 
    return a4

通过网络预测,我们可以计算这些预测与相应标签(网络输入 x)之间的损失。为了计算损失的平均值,我们还需要知道非零标签的数量——也就是训练集中用户的总评分数。

def _compute_loss(self, predictions, labels,num_labels):
   ''' Computing the Mean Squared Error loss between the input and output of the network.

    @param predictions: predictions of the stacked autoencoder
    @param labels: input values of the stacked autoencoder which serve as labels at the same time
    @param num_labels: number of labels !=0 in the data set to compute the mean

    @return mean squared error loss tf-operation
    '''
         with tf.name_scope('loss'):
         loss_op=tf.div(tf.reduce_sum(tf.square(tf.subtract(predictions,labels))),num_labels)
         return loss_op

网络的优化/训练步骤似乎有点棘手,让我们一步一步讨论。给定输入 x,计算相应的输出。你可能已经注意到,输入 x 中的大部分值都是零值,因为用户肯定没有观看和评估数据集中的所有 5953 部电影。因此,建议不要直接使用网络的原始预测。相反,我们必须确定数据输入 x 中零值的索引,并将与这些索引相对应的预测向量中的值也设置为零。这种预测操纵极大地减少了网络的训练时间,使网络有机会将训练努力集中在用户实际给出的评分上。

在此步骤之后,可以计算损失以及正则化损失(可选)。AdamOptimizer 会将损失函数最小化。请注意,该方法会返回一个均方根误差(RMSE)而不是均方误差(MSE),以测得更好的精度。

def _optimizer(self, x):
        '''Optimization of the network parameter through stochastic gradient descent.

            @param x: input values for the stacked autoencoder.

            @return: tensorflow training operation
            @return: ROOT!! mean squared error
        '''

        outputs=self._inference(x)
        mask=tf.where(tf.equal(x,0.0), tf.zeros_like(x), x) # indices of zero values in the training set (no ratings)
        num_train_labels=tf.cast(tf.count_nonzero(mask),dtype=tf.float32) # number of non zero values in the training set
        bool_mask=tf.cast(mask,dtype=tf.bool) # boolean mask
        outputs=tf.where(bool_mask, outputs, tf.zeros_like(outputs)) # set the output values to zero if corresponding input values are zero

        MSE_loss=self._compute_loss(outputs,x,num_train_labels)

        if self.FLAGS.l2_reg==True:
            l2_loss = tf.add_n([tf.nn.l2_loss(v) for v in tf.trainable_variables()])
            MSE_loss = MSE_loss +  self.FLAGS.lambda_ * l2_loss

        train_op=tf.train.AdamOptimizer(self.FLAGS.learning_rate).minimize(MSE_loss)
        RMSE_loss=tf.sqrt(MSE_loss)

        return train_op, RMSE_loss

3.测试

训练几个 epoch 之后,神经网络已经在训练集中看到每个用户的所有评分以及时间了。此时该模型应该已经了解数据中潜在的隐藏模式以及用户对应的电影评级规律。给定用户评分训练样本 x,该模型预测输出 x'。该向量由输入值 x 的重构(如预期)组成,但现在还包含输入 x 中先前为零的值。这意味着该模型在给未评分的电影打分。这个评级对应于用户的偏好——模型从数据中已识别和学习到的偏好。

为了能够测量模型的准确性,我们需要训练和测试数据集。根据训练集进行预测。类似于训练阶段,我们只考虑对应于测试集中非零值的索引的输出值。

现在我们可以计算预测值与实际评分之间的均方根误差损失(RMSE)。RMSE 表示预测值与观测值之间差异的样本标准偏差。例如,RMSE 为 0.5 意味着平均预测评分与实际评分相差 0.5 星。

def _validation_loss(self, x_train, x_test):
        ''' Computing the loss during the validation time.
        @param x_train: training data samples
        @param x_test: test data samples
        @return networks predictions
        @return root mean squared error loss between the predicted and actual ratings
        '''
        outputs=self._inference(x_train) # use training sample to make prediction
        mask=tf.where(tf.equal(x_test,0.0), tf.zeros_like(x_test), x_test) # identify the zero values in the test ste
        num_test_labels=tf.cast(tf.count_nonzero(mask),dtype=tf.float32) # count the number of non zero values
        bool_mask=tf.cast(mask,dtype=tf.bool) 
        outputs=tf.where(bool_mask, outputs, tf.zeros_like(outputs))

        MSE_loss=self._compute_loss(outputs, x_test, num_test_labels)
        RMSE_loss=tf.sqrt(MSE_loss)

        return outputs, RMSE_loss

4.训练结果

最后一步包括执行训练过程并检查模型性能。在这一点上,我不会详细讨论构建数据输入管道、图表、会话等细节。因为这些步骤通常是已知的。对此主题感兴趣的读者可以在我的 GitHub 中查看这些步骤:https://github.com/artem-oppermann/Deep-Autoencoders-For-Collaborative-Filtering/blob/master/train.py

在这里,你可以看到前 50 个迭代次数的训练和测试表现。50 次后,测试集的预测和实际评分间的偏差是 0.929 星。

原文发布于微信公众号 - 机器之心(almosthuman2014)

原文发表时间:2018-05-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏人工智能

在Keras中如何对超参数进行调优?

由于没有一个成熟的理论来解释神经网络,所以配置神经网络通常是困难的,经常被同学们调侃为“炼丹”。

1.6K8
来自专栏机器学习算法与Python学习

机器学习(6)之朴素贝叶斯NB及实例

关键字全网搜索最新排名 【机器学习算法】:排名第一 【机器学习】:排名第二 【Python】:排名第三 【算法】:排名第四 贝叶斯定理是以英国数学家贝叶斯命名...

3897
来自专栏开心的学习之路

奇异值分解

最近两天都在看奇异值分解及其在推荐系统和图像压缩方面的应用,这部分知识比较散也比较难理解,看代码不是很好懂,所以通过编学边整理的方式帮助大脑理解这部分知识。 ...

27810
来自专栏人工智能LeadAI

奇异值分解(SVD)

最近两天都在看奇异值分解及其在推荐系统和图像压缩方面的应用,这部分知识比较散也比较难理解,看代码不是很好懂,所以通过编学边整理的方式帮助大脑理解这部分知识。 ...

3596
来自专栏机器学习算法与Python学习

教你如何用python解决非平衡数据建模(附代码与数据)

2396
来自专栏磐创AI技术团队的专栏

使用Keras进行深度学习:(五)RNN和双向RNN讲解及实践

1693
来自专栏新智元

谷歌GAN 实验室来了!迄今最强可视化工具,在浏览器运行GAN

Google AI和乔治亚理工学院的研究人员发布了一个学习GAN的交互式网站:GAN Lab!由TensorFlow.js 驱动,在浏览器就可以运行GAN,非常...

613
来自专栏PaddlePaddle

技术|深度学习技术黑话合辑

902
来自专栏大数据风控

R分类算法-神经网络算法

神经网络(Artifical Neural Network) 神经网络(人工神经网络),是一种模仿生物网络(动物的中枢神经系统,特别是大脑)的结构和功能的数学模...

2139
来自专栏专知

教你使用Keras一步步构建深度神经网络:以情感分析任务为例

【导读】Keras是深度学习领域一个非常流行的库,通过它可以使用简单的代码构建强大的神经网络。本文介绍基于Keras构建神经网络的基本过程,包括加载数据、分析数...

5697

扫码关注云+社区