第5篇 Fast AI深度学习课程——推荐算法之协同滤波

本节课的主要内容是针对使用协同滤波算法进行电影评分预测的应用场景,分别使用ExcelFast.AIPytorch、自主构建的网络,来实现相应功能。

所用数据为MovieLens数据,其中中的评分表存储了用户id,电影id,以及5星制的评分。数据格式如下:

图 1. 电影评分数据组织形式
一. 使用Excel与矩阵分解算法实现推荐系统

整理为以用户id为索引的评分表,格式如下:

图 2. 电影评分数据整理后的形式


将缺省数据的空格填充为0。然后对用户和电影使用内置矩阵的形式标识,即每个用户使用一个向量替代,每部电影也使用向量替代。某个用户对某部电影的评论分数表示为用户向量与电影向量的内积。定义损失函数为预测评分与真实评分的均方误差根(RMSERoot Mean Square Error)。在Excel中选择“数据”→“规划求解”,设置目标单元格(即存储均方误差根的单元格),可变单元格,以及求解方法,开始求解即可。

图 3. 使用Excel求分解矩阵


之所以将这种方法称为矩阵分解法,是由于用户评分矩阵最终被表示为用户矩阵和电影矩阵的相乘的形式。

二. 使用Fast.AI实现协同滤波算法

使用Fast.AI的API,构造协同滤波推荐系统的逻辑流程,与前面几节所述的图片分类器、销量预测系统、影评倾向分析等的流程一致,即生成满足模型要求的数据、获得学习器、进行训练,代码如下:

cf = CollabFilterDataset.from_csv(path, 'ratings.csv', 'userId', 'movieId', 'rating')
learn = cf.get_learner(n_factors, val_idxs, 64, opt_fn=optim.Adam)
learn.fit(1e-2, 2, wds=wd, cycle_len=1, cycle_mult=2)

其中n_factors=50,设置了内置矩阵的大小;wd=2E-4是正则化系数。

三. 在Pytorch基础上实现协同滤波算法
1. 整理数据

用户id可能不是从0开始的,而且会非常大。我们以其索引,来进行替代。

# 获取用户id集合
u_uniq = ratings.userId.unique() 
# 生成用户id和索引值的映射
user2idx = {o:i for i,o in enumerate(u_uniq)} 
# 用索引值替换数据中的用户id
ratings.userId = ratings.userId.apply(lambda x: user2idx[x])

对电影id也同样如此操作。

2. 构造网络层对象

网络层对象派生自Pytorchnn.module,在其初始化函数__init__()中,声明对应于用户和电影的内置矩阵,并采用何恺明大神的参数初始化策略进行初始。

定义前向计算函数:forward(),其中为方便,传入了ColumnarModelData数据模型。在定义前向传播计算式时,注意函数传入的为批量数据,尽量使用矢量化表达方式,以充分利用GPU加速。

3. 声明优化策略
opt = optim.SGD(model.parameters(), 1e-1, weight_decay=wd, momentum=0.9)

其中model.parameters()指明了需要优化更新的参数,1E-1是学习速率,接下来的两个参数将在后面叙述。

4. 编写优化循环

一个较为简单的优化循环版本是Fast.AIfit()函数,调用语法如下:

fit(model, data, 3, opt, F.mse_loss)

参数依次是模型、数据、遍历次数、优化方法、损失函数。

5. 改进
  • (1) 加入偏置项
    某个用户可能对某部电影打分整体偏低,而某部电影可能又十分流行,因此得分整体偏高。这些都是和单一对象有关,因此不应该是用户参数和电影参数相乘的形式(相乘即意味着关系的耦合)。基于这个考虑,为用户和电影添加偏置项,即某个用户对某部电影的评分,表示为用户向量和电影向量的内积,再加上二者的偏置项。
  • (2) 使用sigmoid()函数限制输出的评分位于1~5分之间。
四. 协同滤波算法的神经网络版本

上述过程中,我们强制定义评分为用户向量和电影向量的内积再加上偏置项的关系,事实上,这对我们得到的评分系统做了很大的限制。而若采用神经网络的架构,我们可以去除这种约束,并让模型自主学习用户向量和电影向量之间的关系。具体代码实现描述如下:

1. 声明网络结构

使用nn.Linear()定义线性加权层,所需参数为输入向量维度、输出向量维度。其中第一层的输入向量维度为用户向量和电影向量的维度之和。
使用nn.Dropout()定义弃置策略,所需参数为弃置率。

self.lin1 = nn.Linear(n_factors*2, nh)
self.lin2 = nn.Linear(nh, 1)
self.drop1 = nn.Dropout(p1)
self.drop2 = nn.Dropout(p2)
2. 定义前向计算函数

按照神经网络的组织形式,表述前向计算函数,如一个线性加权层的输出,按照一定比例弃置后,通过非线性单元,上述过程可表示为

F.relu(self.lin1(self.drop1(data_batch)))

之后按照上一小节的3.声明优化策略4.编写优化循环步骤继续。

梯度下降算法的改进
1. 冲量算法

现在讨论在声明优化策略时,传入的momentum参数。
假设模型参数为 ωn ω n ,学习速率为 r r ,梯度为gn,则常规的随机梯度算法中,参数更新公式可表示为 ωn=ωn1rgn ω n = ω n − 1 − r ⋅ g n
如果某次求到的梯度比较小,那么参数值的更新就比较小,会使得优化变慢,或是算法陷在某个局部最小值的小范围内而出不来。引入冲量参数就是为了解决这一问题。
其原理是每次更新参数时,更新步长不仅要考虑当前所求得的梯度,还要考虑之前一步所更新的步长: ωn=ωn1rmn,mn=umn1+(1u)gn ω n = ω n − 1 − r ⋅ m n , m n = u ⋅ m n − 1 + ( 1 − u ) ⋅ g n 。(注:课程中给出的线性加权公式,而一般文献中采用的是在梯度上额外再加上冲量项。)

2. Adam算法

Adam算法中,引入梯度平方的冲量形式: sn=tsn1+(1t)g2n s n = t ⋅ s n − 1 + ( 1 − t ) ⋅ g n 2 Adam的参数更新算法: ωn=ωn1rmnsn ω n = ω n − 1 − r ⋅ m n s n
Adam算法的优化速度比随机梯度算法更快,但找到的解不如随机梯度算法找到的解优质。近期,又提出了AdamW算法,即带权重衰减的Adam算法。

3. AdamW算法

即在损失函数中加入了权重系数的2阶正则项。假设原来的损失函数为
L(ω⃗ )=yi^y⃗ i2 L ( ω → ) = ∑ ‖ y i → ^ − y → i ‖ 2 ,那么现在损失函数就变为 L(ω⃗ )=yi^y⃗ i2+λω⃗ 2 L ( ω → ) = ∑ ‖ y i → ^ − y → i ‖ 2 + λ ‖ ω → ‖ 2 。这其实是另外一种避免过拟合的策略,限制了参数 ω ω 的复杂程度,使得优化算法“适可而止”。其中 λ λ 就是在声明优化算法时所传进去的weight_decay参数。

备注

  • 在nn.module中定义的参数均是变量(Variable),而不是张量(Tensor),要获取相应张量,调用使用所定义参数的data属性。
  • 在Pytorch中,针对Tensor,函数有原位操作形式:即函数名后接下划线。
  • 对nn.module对象中定义的变量,调用.squeeze()方法,可以获取矩阵运算的广播特性

一些有用的链接

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值