当前位置:   article > 正文

深度神经网络DNN(七)——梯度下降法_神经网络梯度下降法

神经网络梯度下降法

梯度法的概念

机器学习的主要任务是在学习时寻找最优参数。同样地,神经网络也必须在学习时找到最优参数(权重和偏置)。这里所说的最优参数是指损失函数取最小值时的参数。但是,一般而言,损失函数很复杂,参数空间庞大,我们不知道它在何处能取得最小值。而通过巧妙地使用梯度来寻找函数最小值(或者尽可能小的值)的方法就是梯度法
这里需要注意的是,梯度表示的是各点处的函数值减小最多的方向。因此,无法保证梯度所指的方向就是函数的最小值或者真正应该前进的方向。实际上,在复杂的函数中,梯度指示的方向基本上都不是函数值最小处。
函数的极小值最小值以及被称为鞍点(saddle point)的地方,梯度为0。极小值是局部最小值,也就是限定在某个范围内的最小值。鞍点是从某个方向上看是极大值,从另一个方向上看则是极小值的点。虽然梯度法是要寻找梯度为0的地方,但是那个地方不一定就是最小值(也有可能是极小值或者鞍点)。此外,当函数很复杂且呈扁平状时,学习可能会进入一个(几乎)平坦的地区,陷入被称为学习高原的无法前进的停滞期。
虽然梯度的方向并不一定指向最小值,但沿着它的方向能够最大限度地减小函数的值。因此,在寻找函数的最小值(或者尽可能小的值)的位置的任务中,要以梯度的信息为线索,决定前进的方向。
此时梯度法就派上用场了。在梯度法中,函数的取值从当前位置沿着梯度方向前进一定距离,然后在新的地方重新求梯度,再沿着新梯度方向前进,如此反复,不断地沿梯度方向前进。像这样,通过不断地沿梯度方向前进,逐渐减小函数值的过程就是梯度法(gradient method)。梯度法是解决机器学习中最优化问题的常用方法,特别是在神经网络的学习中经常被使用。
现在,我们以两个变量为例,尝试用数学式来表示梯度法:
在这里插入图片描述
上式中的η表示更新量,在神经网络的学习中,称为学习率(learning rate)。学习率决定在一次学习中,应该学习多少,以及在多大程度上更新参数。这个式子是表示更新一次的式子,这个步骤会反复执行。也就是说,每一步都按这个式子更新变量的值,通过反复执行此步骤,逐渐减小函数值。虽然这里只展示了有两个变量时的更新过程,但是即便增加变量的数量,也可以通过类似的式子(各个变量的偏导数)进行更新。
学习率需要事先确定为某个值,比如0.01 或0.001。一般而言,这个值过大或过小,都无法抵达一个“好的位置”。在神经网络的学习中,一般会一边改变学习率的值,一边确认学习是否正确进行了。

梯度法编程实现

下面,我们用Python来实现梯度下降法:

def gradient_descent(f, init_x, lr=0.01, step_num=100):
	x = init_x
	for i in range(step_num):
		grad = numerical_gradient(f, x)
		x -= lr * grad
	return x
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

其中,参数f是要进行最优化的函数,init_x是初始值,lr是学习率learning ratestep_num是梯度法的重复次数。numerical_gradient(f,x)会求函数的梯度,用该梯度乘以学习率得到的值进行更新操作,由step_num指定重复的次数。使用这个函数可以求函数的极小值,顺利的话,还可以求函数的最小值。
下面以一个例子来帮助理解:
用梯度法求f(x0+x1)=x02+x12的最小值
用Python来编程实现:

>>> def function_2(x):
... return x[0]**2 + x[1]**2
...
>>> init_x = np.array([-3.0, 4.0])
>>> gradient_descent(function_2, init_x=init_x, lr=0.1, step_num=100)

# 运行结果
array([-6.11110793e-10, 8.14814391e-10])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这里,设初始值为(-3.0, 4.0),开始使用梯度法寻找最小值。最终的结果是(-6.1e-10, 8.1e-10),非常接近(0,0)。实际上,真的最小值就是(0,0),所以说通过梯度法我们基本得到了正确结果。如果用图来表示梯度法的更新过程,则如下图所示:
在这里插入图片描述
可以发现,原点处是最低的地方,函数的取值一点点在向其靠近。
前面说过,学习率过大或者过小都无法得到好的结果。我们来看另一个例子:

# 学习率过大的例子:lr=10.0
>>> init_x = np.array([-3.0, 4.0])
>>> gradient_descent(function_2, init_x=init_x, lr=10.0, step_num=100)
# 运行结果
array([-2.58983747e+13, -1.29524862e+12])

# 学习率过小的例子:lr=1e-10
>>> init_x = np.array([-3.0, 4.0])
>>> gradient_descent(function_2, init_x=init_x, lr=1e-10, step_num=100)
# 运行结果
array([-2.99999994, 3.99999992])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

实验结果表明,学习率过大的话,会发散成一个很大的值;反过来,学习率过小的话,基本上没怎么更新就结束了。也就是说,设定合适的学习率是一个很重要的问题。
像学习率这样的参数称为超参数。这是一种和神经网络的参数(权重和偏置)性质不同的参数。相对于神经网络的权重参数是通过训练数据和学习算法自动获得的,学习率这样的超参数则是人工设定的。一般来说,超参数需要尝试多个值,以便找到一种可以使学习顺利进行的设定。

神经网络的梯度

神经网络的学习也要求梯度。这里所说的梯度是指损失函数关于权重参数的梯度。比如,有一个只有一个形状为2×3的权重W的神经网络,损失函数用L表示。此时,梯度可以用dL/dW表示。用数学式表示的话,如下所示:
在这里插入图片描述
dL/dW的元素由各个元素关于W的偏导数构成。比如,第1行第1列的元素dL/dW11表示当W11稍微变化时,损失函数L会发生多大变化。
下面,我们以一个简单的神经网络为例,来实现求梯度的代码。这里,构建了一个名为simpleNet的类:

import numpy as np
class simpleNet:
	def __init__(self):
		self.W = np.random.randn(2,3) # 用高斯分布进行初始化
	
	def predict(self, x):
		return np.dot(x, self.W)
	
	def loss(self, x, t):
		z = self.predict(x)
		y = softmax(z)
		loss = cross_entropy_error(y, t)
		return loss
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

这里使用了之前讲到的softmaxcross_entropy_error以及numerical_gradient方法。simpleNet类只有一个实例变量,即形状为2×3 的权重参数。它有两个方法,一个是用于预测的predict(x),另一个是用于求损失函数值的loss(x,t)。这里参数x接收输入数据,t接收正确解标签。现在来尝试使用这个simpleNet:

>>> net = simpleNet()
>>> print(net.W) # 权重参数
[[ 0.47355232 0.9977393 0.84668094],
[ 0.85557411 0.03563661 0.69422093]])

>>> x = np.array([0.6, 0.9])
>>> p = net.predict(x)
>>> print(p)
[ 1.05414809 0.63071653 1.1328074]

>>> np.argmax(p) # 最大值的索引
2

>>> t = np.array([0, 0, 1]) # 正确解标签
>>> net.loss(x, t)
0.92806853663411326
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

接下来求梯度。和前面一样,我们使用numerical_gradient(f, x)求梯度:

>>> def f(W):
... return net.loss(x, t)
...
>>> dW = numerical_gradient(f, net.W)
>>> print(dW)
[[ 0.21924763 0.14356247 -0.36281009]
[ 0.32887144 0.2153437 -0.54421514]]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

numerical_gradient(f, x) 的参数f是函数,x是传给函数f的参数。因此,这里参数x取net.W,并定义一个计算损失函数的新函数f,然后把这个新定义的函数传递给numerical_gradient(f, x)。
numerical_gradient(f, net.W)的结果是dW,一个形状为2×3的二维数组。观察一下dW的内容,例如,会发现dL/dW中的dL/dW11的值大约是0.2,这表示如果将w11增加h,那么损失函数的值会增加0.2h。dL/dW23对应的值大约是−0.5,这表示如果将w23增加h,损失函数的值将减小0.5h。因此,从减小损失函数值的观点来看,w23应向正方向更新,w11应向负方向更新。至于更新的程度,w23比w11的贡献要大。
求出神经网络的梯度后,接下来只需根据梯度法,更新权重参数即可。

小结

  • 通过不断地沿梯度方向前进,逐渐减小函数值的过程就是梯度法
  • 梯度法是解决机器学习中最优化问题的常用方法,特别是在神经网络的学习中
  • 虽然梯度的方向并不一定指向最小值,但沿着它的方向能够最大限度地减小函数的值
  • 设定合适的学习率是一个很重要的问题
  • 超参数是人工设定的,与神经网络参数性质不同,可尝试多个值使学习顺利进行
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/338852
推荐阅读
相关标签
  

闽ICP备14008679号