您的位置:首页 > 大数据 > 人工智能

大白话5分钟带你走进人工智能-第十一节梯度下降之手动实现梯度下降和随机梯度下降的代码(6)

2019-04-15 11:56 751 查看

                                                        第十一节梯度下降之手动实现梯度下降和随机梯度下降的代码(6)

我们回忆一下,之前咱们讲什么了?梯度下降,那么梯度下降是一种什么算法呢?函数最优化算法。那么它做的是给我一个函数,拿到这个函数之后,我可以求这个函数的导函数,或者叫可以求这个函数的梯度。导函数是一个数儿,梯度是一组数,求出来梯度之后怎么用?把你瞎蒙出来的这组θ值,减去α乘以梯度向量,是不是就得到了新的θ,那么往复这么迭代下去的,是不是越来越小,越来越小,最后达到我们的最优解的数值解? 你拿到数值解之后,我们实际上就得到了我们的一组最好的θ。

回到我们继续学习的场景来说,我们想要找到一组能够使损失函数最小的θ,那么我原来是通过解析解方式能够直接把这θ求出来,但是求的过程太慢了,有可能当你参数太多的时候,所以我们通过梯度下降法可以得到我们损失函数最小那一刻对应的W值,也就是完成了一个我们这种参数型模型的训练。其实这个东西虽然咱们只讲了一个线性回归,但是逻辑回归svm,岭回归,学了之后,你本质就会发现它是不同的损失函数,还是同样使用函数最优化的方法达到最低值,因为都是参数型模型,只要它有损失函数,最终你就能通过梯度下降的方式找到令损失函数最小的一组解,这组解就是你训练完了,想要拿到手,用来预测未来,比如放到拍人更美芯片里面的模型。哪怕深度神经网络也是这样。

在讲梯度下降背后的数学原理之前,我们上午只是从直觉上来讲,梯度为负的时候应该加一点数,梯度为正的时候应该减一点数,而且梯度越大证明我应该越多加一点数,只是这么来解释一下梯度下降,那么它背后实际上是有它的理论所在,为什么要直接把梯度拿过来直接乘上一个数,就能达到一个比较快的收敛的这么一个结果,它有它的理论所在的。

在讲这个之前我们还是先来到代码,我们手工的实现一个batch_gradient_descent。批量梯度下降,还记得批量梯度下降和Stochastic_ gradient_descent什么关系吗?一个是随机梯度下降,一个是批量梯度下降,那么随机跟批量差在哪了?就是计算负梯度的时候,按理说应该用到所有数据,通过所有的数据各自算出一个结果,然后求平均值.现在咱们改成了直接抽选一条数据,算出结果就直接当做负梯度来用了,,这样会更快一点,这是一个妥协。理论向实际的妥协,那么我们先看看实现批量梯度下降来解决。

import numpy as np
#固定随机种子
np.random.seed(1)
#创建模拟训练集
X = 2 * np.random.rand(10000, 1)
y = 4 + 3 * X + np.random.randn(10000, 1)
X_b = np.c_[np.ones((10000, 1)), X]
# print(X_b)

learning_rate = 0.1
n_iterations = 500
#有100条样本
m = 10000

#初始化θ
theta = np.random.randn(2, 1)
count = 0

for iteration in range(n_iterations):
count += 1
gradients = 1/m * X_b.T.dot(X_b.dot(theta)-y)
theta = theta - learning_rate * gradients

print(count)
print(theta)
[/code]
X = 2 * np.random.rand(10000, 1)
y = 4 + 3 * X + np.random.randn(10000, 1)
[/code]

上这两行代码仍然是生成一百个X,模拟出对应了一百个Y,这个代码里边我们不掉现成的包,不像在这用了一个sklearn了,我们不用sklearn的话,还有什么包帮你好心的生成出一个截距来,是不是没有了?所以我们是不是还是要手工的,在X这里面拼出一个全为1的向量作为X0,现在X_b是一个什么形状呢?100行2列,第一列是什么?是不是全是1,第二列是什么?随机生成的数。我们解释下上面代码:

learning_rate = 0.1
n_iterations = 500
[/code]

我们做梯度下降的时候,是不是有一个α?我们命名它为学习率,拿一个变量给它接住,learning_rate=0.1。那么iterations什么意思? 迭代是吧,n_iterations就是说我最大迭代次数是1万次,通常这个超参数也是有的,因为在你如果万一你的学习率设的不好,这个程序是不是就变成死循环了?它一直在走,如果你不设一个终止条件的话,你这个东西一训练你有可能就再也停不下来了。所以通常都会有一个最大迭代次数的。当走了1万次之后没收敛,也给我停在这,最后给我报个警告说并没有收敛,说明你这个东西有点问题。你应该增加点最大次数,或者你调一调你的学习率是不是太小了或者太大了,导致它还没走到或者说走过了震荡震荡出去了,你需要再去调整。M是这个数据的数量,这一会再说它是干嘛的。

theta = np.random.randn(2, 1)
count = 0
[/code]

接下来θ,我们上来梯度下降,是不是要先蒙出两个θ来,于是我生成两个随机数,np.random.randn(2, 1)这个的意思是生成一个两行一列的-1到+1之间的正态分布的随机数。接下来我就进入for循环:

for iteration in range(n_iterations):
count += 1
gradients = 1/m * X_b.T.dot(X_b.dot(theta)-y)
theta = theta - learning_rate * gradients
[/code]

在python2中,如果你输入range(10000),会得到一个列表,从0一直到9999,就实实在在的是一个列表,你把这列表搁进去,进行for循环,会得到什么?第一次循环的时候,这次此时的iteration等于0,第二次等于1,因为列表中的每一个元素要赋值到这个变量里面去。那么在python3里面range就不再直接给你实实在在的生成一个列表了,而生成一个python里面独一无二的类型叫Generator,生成器。生成器是一个什么?你只是想借用这个东西去循环意思,循环一次,你有必要先放一个列表,在那占你的内存空间,没有必要?实际上它是一个懒加载的这么一个东西,你每次迭代第一次返回0第二次返回1,每次迭代就给你返回一个数,生成器本质它就变成一个函数了,你第一次调用它返回零,第二次掉他返回一,第三次调用它返回二,它里边记录了自己当前到哪了,并且生成规则,这样就没有必要去占用你的内存空间了,这个是range通常是用来做for循环用的,就为了有一个序号。 还有另一种高级的用法是enumerate,假如你用一个enumerate(list),它会给你返回两个数,第一个是索引号,第二个是list中的本身的元素。在这用逗号可以把这两个变量分别复制给你指定的两个变量名。

for i,a in enumerate(list):
print(i,a)
那么i在此时实际上就是li1里面的第一个元素的索引号是零,第二个元素就是本身是什么什么,这个是一个很方便的技巧,能够帮你在for循环体内部既需要索引号来计算它的位置,又需要这个数据本身的时候可以直接用enumerate一次性的把它取出来,很简单的一个技巧。[/code]
gradients = 1/m * X_b.T.dot(X_b.dot(theta)-y)
theta = theta - learning_rate * gradients
[/code]

那么在这我们只需要n_iterations给他做个计数器就好了,所以我在这儿只用它。那么我们看这count默认是零,他是一个计数器,每次循环会自己加1,+=大家都应该能看懂,自己自加1,那么此时的gradients实际上就是X_b的转置。乘以它,再乘以1/m。gradients = 1/m * X_b.T.dot(X_b.dot(theta)-y)。这一步里面有没有加和?是批量梯度下降带加和的版本,还是随机梯度下降,不带加和的版本?因为X_B是个矩阵, 实际上你看X_b是不是一个矩阵,Y是一个向量,我们需要看Y是个行向量还是列向量, X是一百行一列的,Y是一百行一列的,它是一个列向量。然后用最开始的X转置去乘以列向量,实际上矩阵乘以一个向量的时候,本身就拿第一行乘以第一列加第二行乘以第一列,本身就把加和融在矩阵乘法里面去了。所以实际上1/m * X_b.T.dot(X_b.dot(theta)-y)这个东西本身就已经是带加和的了,而且如前面所说是不是一定要加一个M分之一?接下来实现梯度下降是不是非常简单,拿上一代的θ减去learnrate,乘以你计算出来的梯度就完事了! 也就是

theta = theta - learning_rate * gradients
[/code]

我们看梯度下降,虽然讲了半天感觉很复杂,其实四行代码结束了,那么在执行完了这1万次迭代之后,我们把θ给打印出来,看看结果,我没有实现那个tolerance,没有判定。假如这样我每一步都让它打印θ。

for iteration in range(n_iterations):
count += 1
gradients = 1/m * X_b.T.dot(X_b.dot(theta)-y)
theta = theta - learning_rate * gradients
if count%20==0:
print(theta)
[/code]

我们看看θ随着更新,它很早你发现是不是都已经收敛了其实?你看她通过多少次收敛的,上来是3.27,3.52,下次慢慢在变化变化,每一步都走的还比较稳健,到4.00,4.04,

是不是越走越慢了,你发现。你看第一部时候从3.27到3.51,到后来4.04,4.07是不是越走越慢了?4.10,4.12,为什么会越走越慢,学过梯度下降,你们现在是不是应该知道了?因为越接近谷底,它的梯度值怎么样?越大还是越小? 越小,它自然就越走越慢,越小走的越慢。那么最后到4.18,收敛了再也不动,但是我们循环是不是还在往下一直继续,只不过每次加的梯度都是怎么样?零。

再有一个问题,刚才我的数据集里面并没有做归一化,那么实际上它需要做最大最小值归一化吗?本质上不太需要,因为它只有一个W,只有一个W的时候自然做归一化是无所谓的,如果你有多个W的情况下,你对每一个X在预先处理之前做都需要做归一化,其实也是两行代码的事。我们加入最大最小值归一化的方式:

X_b[:,1]=X_b[:,1]-X_b[:,1].mean()
[/code]

X_b[:,1]这个冒号什么意思?这个冒号就是一个索引方式,我要取所有的行,我就打一个冒号:,你取所有的行的第一列,就写个1,X_b[:,1]其实是一个一维数组,就是那一堆一百个随机数,那么X_b[:,1].mean()加一个.mean,你可以看到它的平均是多少?0.95。 如果你用X_b[:,1]=X_b[:,1]-X_b[:,1].mean(),会自动的帮我们对位相减,每一个数都减减去它。然后我们此时在看我们剪完了之后的结果,是不是就变成有正有负的了?

此时在做梯度下降,按理说速度应该更快一些,我们看刚才我们迭代次数是多少,取决于你初始化的点,如果你初始化的点好的话是没有区别的,如果初始化的点不好的话就有区别。我们运行一下,你看一下,我期待它会有变化。需要多少次?331.刚才有多少次?500多次,现在只需要300多次,就证明刚才随机那个点,实际上对于能不能正负一起优化,是不是有实际的敏感度的,你现在做了一个均值归一化,实际上发生了什么问题? 迭代次数变得更好了,更少了,就更快地达到了收敛的值。但是你发现它W1和W0也变了,

肯定会变,因为你的整个数值全都变了,但变了没关系。怎么样才能继续用起来?你这变过之后的W,对你新预测的数据拿回来之后先做同样的归一化,你再去预测结果也是正确的。

   有了批量梯度下降的代码,我们再看随机梯度下降的话,就简单多了。先上代码:

import numpy as np
from sklearn.linear_model import SGDRegressor

#固定随机种子
np.random.seed(1)
#生成训练集
X = 2 * np.random.rand(100, 1)
y = 4 + 3 * X + np.random.randn(100, 1)
X_b = np.c_[np.ones((100, 1)), X]
print(X_b)

n_epochs = 1000#迭代次数
t0, t1 = 5, 50

m = 100

#设置可变学习率
def learning_schedule(t):
return t0 / (t + t1)

#初始化θ
theta = np.random.randn(2, 1)

#随机梯度下降
learning_rate = 0.1
for epoch in range(n_epochs):
for i in range(m):
random_index = np.random.randint(m)
# 为了解决维度问题,在索引屈指
xi = X_b[random_index:random_index+1]
yi = y[random_index:random_index+1]
gradients = 2*xi.T.dot(xi.dot(theta)-yi)
learning_rate = learning_schedule(epoch*m + i)#随着迭代次数增加,学习率逐渐减小。
theta = theta - learning_rate * gradients

print(theta)

SGDRegressor
[/code]

解释下:np.random.seed(1),确定随机种子,这是不是万年不变的老三样,没有什么变化,然后我们还是n_iterations,总共迭代一千次。n_epochs = 1000。为什么是双重for循环?我细致的给大家说,首先我是不是随机梯度下降,我要有一个随机,我要随机选出一条数据来,我在这一部分

random_index = np.random.randint(m)
xi = X_b[random_index:random_index+1]
yi = y[random_index:random_index+1]
[/code]

都是在随机的选X和Y,randint(m)代表从0到99随机选出一个数字来作为index,作为索引,选出来之后,我的X是不是要从X里边把这个随机位的索引给取出来,所以我取出来X的索引。那么Y是不是为了随机取出来索引,这两个xi = X_b[random_index:random_index+1], yi = y[random_index:random_index+1]就是把对应的那一条X和对应的Y给搞出来。那么梯度就通过那一个X乘以了一个Y,  gradients = 2*xi.T.dot(xi.dot(theta)-yi)得到了单个计算出来的梯度,你说这个表达式怎么没变,表达式是不用变,只不过原来的X矩阵是一百行两列,现在X矩阵变成一行两列,你表达式是不用变的,一行自动指出来一个数就不再是一个向量了,那么此时用learning_rate,我原来是不是定死了就是0.1,而现在我的learning_rate变成了learning_schedule返回的一个结果,我看我定义的learning_schedule是什么?

def learning_schedule(t):
return t0 / (t + t1)
[/code]

定义了两个超参数,分别是t0和t1,你丢进一个t来,你看t越大,return这个值会怎么样? 越小,那么我们看t总共能到多少?是不是epoch*m+i, epoch从哪来的?是不是从n_epochs来的,也就是上来循环第一次的时候它是多少?0,此时你看return结果这算算是多少,是不是就是零?那么你看此时的t是零,此时的learning_schedule返回的是一个多大的数,是不是0.1?也就是第一次执行的时候学习率是多少?0.1。当我内层循环第101次执行的时候此时epoch等于多少? 等于1。epoch*m+i越来越大,那么此时的学习率learning_schedule是上升了还是下降了?变大了还是变小了? 变小了一点,也就是说随着迭代的加深,epoch是不是越来越大?传到这里边数也越来越大,学习率是越来越小的,所以这个也是梯度下降的一种变种。它会把学习率随着迭代的次数推进,让学习率越来越小,这样保证你就可以设置一个初始的时候比较大的学习率,这样你学习率万一设大了,它也不会一直震荡越远,因为随着迭代越多,梯度越来越小。在我们sklearn 里面的SGDRegressor函数是有相关的超参数可以设置的。

def __init__(self, loss="squared_loss", penalty="l2", alpha=0.0001,
l1_ratio=0.15, fit_intercept=True, max_iter=None, tol=None,
shuffle=True, verbose=0, epsilon=DEFAULT_EPSILON,
random_state=None, learning_rate="invscaling", eta0=0.01,
power_t=0.25, warm_start=False, average=False, n_iter=None):
super(SGDRegressor, self).__init__(loss=loss, penalty=penalty,
alpha=alpha, l1_ratio=l1_ratio,
fit_intercept=fit_intercept,
max_iter=max_iter, tol=tol,
shuffle=shuffle,
verbose=verbose,
epsilon=epsilon,
random_state=random_state,
learning_rate=learning_rate,
eta0=eta0, power_t=power_t,
warm_start=warm_start,
average=average, n_iter=n_iter)
[/code]

loss="squared_loss",这个是什么? 就是说SGDRegressor是一个通用的机器学习的方式,你可以自己告诉他我的损失函数是什么,你甭管损失函数是什么,我给你通过SG的方向一直下降得到一个结果,你要把MSE传给我,你得到就是一个线性回归的结果,你要把一个mse加L2正则的损失函数给我,我得到的就是一个岭回归的结果,就损失函数不同,你的算法其实就改变了,那么在SGD它是不捆绑算法本身的,你给我什么损失函数执行出来什么样,我就是一个帮你下降做计算题的机器。那么默认的就是squared_loss,alpha实际上是指的l1跟l2中间的一个超参数,l1_ratio也一样,这两个超参数是依附在penalty之上的一个超参数,咱们讲完了正则化之后,你就明它什么意思了。

fit_intercept=True,这个什么意思,截距是不是要帮你搞出来。max_iter=None,什么意思?最大迭代次数,它没设。tol是什么意思,收敛次数。shuffle=True,shuffle什么意思?shuffle实际上把数据乱序。你上来不是给了我一堆X吗?我帮你先洗个牌乱一下序再进行训练,对于咱们这种算法来说是否乱序不影响最终计算结果。random_state=None就是随机种子.learning_rate="invscaling", learning_rate是学习率,那么看learning_rate都有哪些可以选择的地方?constant什么意思?常数,那么此时的eta学习率通常也用eta表示就等于eta0,后边是不是还有一个eta0,如果你在这写一个learning_rate=constant,后边eta0赋一个值,实际上就是一个定值的学习率。然后它有两种变种的,一个叫optimal,优化的,用1.0/(alpha*(t+t0))。另外一个是什么?T的power T次方,就是T的N次方,这种方式叫invscaling。它们都是差不多,它们都是想让这学习率越往下走越小,这么一种可变学习率的调整方式,那么默认是使用invscaling倒置缩放的这种方式来做的,也就是说实际上我们现在就了解到,在sklearn中并没有使用这种定值的学习率,默认的实现里面,并不是使用固定的学习率来做梯度下降的,而是使用这种可变学习率来做的。

聊了这么多梯度下降的逻辑和过程,有没有对其底层原理感兴趣,所以下一节我们将讲解梯度下降的底层原理。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐