第十六章 Python 机器学习 入门之 协同过滤算法
目录
系列文章目录
前言
一、均值归一化
二、利用TensorFlow 来实现协同过滤算法
1 构建代价函数
2 更新参数
三、 寻找相关特征
总结
本文主要讲解如何使用tensorflow 来实现协同过滤算法,以及如何使用均值归一化来优化协同过滤算法和寻找相关特征。
均值归一化 mean normalization 有什么用?
在协同过滤算法中加入均值归一化可以使得算法运行的更多,结果更准确。
下面来看看均值归一化的使用
还是以之前的评价电影的例子来说,如果我们添加了一个人Eve, 他并没有对任何电影进行评论。
如果我们在这个数据上训练一个协调过滤算法,通过代价函数我们得到参数的值,对于第五个人Eve ,我们的到的参数可以是 w=[0,0],b(5)=0。
因为他没有给任何电影评分,使用参数w,b 不影响代价函数中的第一项,在这个平方误差代价函数中,我们希望代价函数的值尽可能的小,所以也就是希望参数w 的值尽可能的小,最后参数w 会取到0,参数b 的值我们并没有进行规定,也可以默认它的值为0。
如果我们使用计算出来的参数值来预测评分,那么w*x+b ,最终5部电影的评分都是0,这就是使用均值归一化的原因,使用均值归一化可以避免这种情况。
下面,我们来看看什么是均值归一化
在这里,我们计算每一行的均值,然后写成一个列向量μ,表示每部电影的平均收视率,
然后将每个评分减去评分的平均值,随后使用均值归一化后的值进行预测,
为了避免预测结果出现负值,我们就可以在后面加上μ_i
有了这些铺垫,我们再来看均值归一化后的数据对第五个用户Eve,对5部电影的预测,预测结果不是0了,而是评分均值了,很明显这样的评分比0评分是要好的。
事实证明,通过将不同电影评分的均值归一化为0,优化算法推荐的系统也会运行得更快一点。
它对于没有评价电影和评价电影数量很少的用户表现更好,预测结果也更合理。
这里我们使用的是对矩阵的每一行标准化为均值为0,对于不同的问题,我们也可以对矩阵的列进行标准化。
比如对于一部没有人看过的电影,想对其进行预测评分,那么对矩阵的列进行标准化效果是比较好的。
代码如下:
# 方法一(按公式)
def cost_func(X, W, b, Y, R, lambda_):
nm, nu = Y.shape
J = 0
for j in range(nu):
w = W[j, : ]
b_j = b[0, j]
for i in range(nm):
x = X[i, : ]
y = Y[i, j]
r = R[i, j]
J += np.square(r * (np.dot(w,x) + b_j - y))
J += lambda_*(np.sum(np.square(W)) + np.sum(np.square(X)))
J = J/2
return J
# 方法二 (使用矢量化)
def cost_func(X, W, b, Y, R, lambda_):
j = (tf.linalg.matmul(X, tf.transpose(W)) + b - Y)*R
J = 0.5 * tf.reduce_sum(j**2) + (lambda_/2) * (tf.reduce_sum(X**2) + tf.reduce_sum(W**2))
return J
Tensor Flow 不仅可以构建神经网络模型,也可以用它来实现协同过滤算法。
其关键点就在于,使用tensorflow可以自动找到代价函数的导数。这是tensorflow 的一个非常强大的功能,称为AuTo Diff, 它有时也被叫做AuTo Grad ,但是专业的术语还是AuTo Diff,
而AuTo Grad 其实是做自动微分,自动取倒数的专用软件包的名称
下面使用AuTo Diff来实现上面协同过滤算法的简化例子。
代码如下:
import tensorflow as tf
w = tf.Variable(3.0) # 告诉tensorflow w 是一个变量,它是我们想要优化的参数
# 设置其他参数
x = 1.0
y = 1.0 # y是目标值
alpha = 0.01
iterations = 30 #迭代次数
for iter in range(iterations):
with tf.GradientTape() as tape: # 使用GradientTape()梯度磁带功能,tensorflow会自动记录步骤顺序,
fwb = w*x # 计算代价J 所需的成本序列,将序列保持在 tape 磁带中
costJ = (fwb - y)**2
[dJdw] = tape.gradient(costJ, [w]) # tensorflow 自动计算w 的倒数
# tensorflow 变量,层变量需要特殊处理,使用assign_add() 函数来更新参数
w.assign_add(-alpha * dJdw)
# 使用tensorflow 的 梯度磁带功能,我们需要做的是告诉它,如何计算代价函数J
正常情况下使用AuTo Diff来实现协同过滤算法
代码如下:
import tensorflow as tf
# 知指定优化器Adams, 设置学习速率
optimizer = keras.optimizers.Adam(learning_rate = 1e-1)
# 设置其他参数
iterations = 200 #迭代次数
for iter in range(iterations):
with tf.GradientTape() as tape: # 使用GradientTape()梯度磁带功能,tensorflow会自动记录步骤顺序,
# 计算代价J 所需的成本序列将序列保持在 tape 磁带中
cost_value = cofiCostFuncV(X, W, b, Ynorm, R, num_users, num_movies, lambda) # 提供代码计算代价函数
grads = tape.gradient(cost_valuw, [X, W, b]) # tensorflow 自动计算X, W, b 的倒数
#将优化器与刚刚计算的梯度一起使用
optimizer.apply_gradients(zip(grads, [X, W, b]))
我们学习了每个项目I 的特征x^(i), 它很难明确的学习到各个特征,比如说x1学习到这个电影是否是动作片,x2学习到这个电影是否是美国片等,这些是很难的。
我们将学到的特征,统称为 x1、x2…… 等 ,表示关于这部电影的某些特征,
虽然学习不到哪些准确的特征,但是找到的这些特征都是与这部电影相关的。
给定项目i的特征x^(i),如果我们想找到其它项目,比如说与电影i相关的其他电影,那么我们可以做的就是尝试找到项目k的x^(k), 类似于x^(i)。
计算x^(k),与 x^(i)的平方距离,如果计算出来的距离值小的话,那就是相关的特征。
如果我们发现不只是一部电影x^(k) 与 x^(i)之间的距离最小,比如说找到了5个相关的项目,那么如果用户在找某个电影Ii时,我们就可以将这几个相似的电影k推荐给他。
使用tensorflow 来实现协同过滤算法还是比较简单的,因为auto diff 功能可以让计算机自己计算代价函数中的导数值,在进行参数更新时,这无疑是为我们省了不少事。除了协同过滤算法,还有一个推荐算法叫基于内容过滤算法,它可以很好的解决本文所指出的协同过滤算法存在的问题,虽然可以使用均值归一化来解决,但是还是会影响精度,而基于内容过滤算法就可以很好的解决这一点,后面我们会学到。