白话梯度下降:
梯度下降的官方概念网上有很多,说了也没用,反正我刚学的时候是没太看懂。
需要的背景知识,偏导率(很重要)
两个坐标点的距离,是两个向量的点乘积
以实际问题为例,一个电影推荐系统,向量v(v1,v2)代表这个电影本应存在的位置,用户向量u(u1,u2)代表用户存在的位置,现在有实际数据是每个电影的网站评分,和一些用户对某些电影的评分,去评估每个电影应该存在的位置以及每个用户应该存在的位置。
用梯度下降的方法就是,首先得出一个目标方程,这里的目标方程我们选择的是误差方程,所以如下图:
目标方程中 s-ij 代表 0 或者 1, 如果第i个用户对第j个电影有评分,则是1,如果这个用户没有评价这个电影则为0(因为不可能每个用户对所有的电影都有评分,所以这个coefficient 是调整矩阵值的。
图片中的最后一个公式就是对u求的偏导,以下的代码都是根据这个公式展开的。
这里用到梯度下降的原理就是,这个偏导数是现有值对真实值的方向指向,因为方程是根据误差方程得出来的,所以每次用这个偏导值 * 学习速率,就可以使得这个点在固定的步长下,一步一步的逼近正确的值。(在计算过程中,除了电影本身评分和用户的打分 这些观测值不变之外,其他的数值在每一次迭代的时候都是会更新的。
而对v求偏导的结果,和对u的偏导差不多,过程也是假设除v外其他的值都是常量,然后求v的导数。
null——————这部分是数据处理,老师给的代码,就不贴了。
数据处理完成之后,我们需要把数据读取出来:import pandas as pd
import os
movies = pd.read_csv(os.path.join('/Users/zhoumeng/Documents/Sheffield/machine learning/mlai17/labs/class_movie', 'movies.csv'),encoding='latin-1').set_index('index')
user_names = list(set(movies.columns)-set(movies.columns[:9]))
Y = pd.melt(movies.reset_index(), id_vars=['Film', 'index'], var_name='user', value_name='rating', value_vars=user_names)
Y = Y.dropna(axis=0)
我们可以看一下原始数据的样子:
Film | index | user | rating | |
---|---|---|---|---|
148 | Ghost Rider | 148 | David | 1.0 |
275 | Pearl Harbor | 275 | David | 3.0 |
319 | The Scorpion King | 319 | David | 2.0 |
392 | V for Vendetta | 392 | David | 5.0 |
409 | X-Men | 409 | David | 3.0 |
424 | 8MM | 3 | Terry | 1.0 |
461 | Ying hung boon sik II | 40 | Terry | 4.0 |
下面我们随机生成两个二维数组 分别是用户和电影的预测值,然后通过不断迭代找到他们的值应该存在的位置:
U = pd.DataFrame(np.random.normal(size=(len(user_names), 2))*0.001, index=user_names)
V = pd.DataFrame(np.random.normal(size=(len(movies.index), 2))*0.001, index=movies.index)
将原始数组的评分减去他们的均值:#在我看来 这样做,高于均值的rating为正,低于它的为负,计算的时候自带符号
Y['rating'] -= Y['rating'].mean()
下面就开始描述如何用代码表达梯度下降了:
def objective_gradient(Y, U, V):
gU = pd.DataFrame(np.zeros((U.shape)), index=U.index)
gV = pd.DataFrame(np.zeros((V.shape)), index=V.index)
obj = 0.
for ind, series in Y.iterrows():
film = series['index']
user = series['user']
rating = series['rating']
prediction = np.dot(U.loc[user], V.loc[film]) # u^T v
diff = prediction - rating # u^T v - y
obj += diff*diff
gU.loc[user] += 2*diff*V.loc[film]##??????
gV.loc[film] += 2*diff*U.loc[user]
return obj, gU, gV
定义了一个随机梯度下降的方法,用pd数组的series将所有数据都迭代一遍,因为一个用户可能评论多个电影,不同用户评论的次数也有所不同,这里将所有次数全部迭代进去的意义在于,评论次数多的用户,我们默认他活跃,所以斜率比重占得高。
接下来是梯度下降的迭代:
learn_rate = 0.01
iterations = 100
for i in range(iterations):
obj, gU, gV = objective_gradient(Y, U, V)
print("iteration", i+1, " objective function:", obj)
U -= learn_rate*gU
V -= learn_rate*gV
uv被不断修正,整体误差稳定在两位数。
下面是自己探索的一个随机梯度下降:
def stochastic_descent(Y,U,V):
gU = pd.DataFrame(np.zeros((U.shape)), index=U.index)
gV = pd.DataFrame(np.zeros((V.shape)), index=V.index)
#get a random name
names = np.array(list(set(np.array(Y.user))))
random_name = names[np.random.randint(names.shape[0]-1)]
#get films rated by this user
films = Y[Y.user==random_name]
for ind, serise in films.iterrows():#using index to represent the film, because one user are connected to more than one film
rating = serise.rating
pre = np.dot(U_new.loc[random_name], V_new.loc[serise['index']])
diff = pre - rating
gU.loc[random_name] += 2*diff*V.loc[serise['index']]
gV.loc[serise['index']] += 2*U.loc[random_name]
return gU, gV
主要思想是取用户,然后更新这个用户以及该用户评论的电影们,但是效果不太理想,可能是我的想法有问题,希望大神们给我评论指出。
这里是我不断修改learning_rate 以及iterations 得到的不同结果:
learn_rate = 0.01 #0.01 3700 384.~~~ # 0.03 848 404.~~ #0.05 411 428.~~~ #0.1 335 416.~~~ #0.005 6500 385.~~
# 0.015 2180 393.~~ # 0.013 2553 389.~~ #0.11 3331 384.~~ # 0.009 3936 385.~~ #0.001 35507 385.~~
# 0.005 40000 386.~~# 0.8 56 474.~~
算上没写的,总过得迭代了超过十万次,也找不到一个很好的rate使得整个误差方程收敛,所以,很无力。。。