本文主要是介绍梯度法有哪些?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
为啥要提出梯度法?因为机器学习的主要任务就是在学习时寻找最优参数。啥叫最优参数?就是使损失函数值最小时的权重和偏置。而损失函数很复杂,参数空间庞大,使用梯度来寻找函数最小值的方法就是梯度法。梯度法使用梯度信息决定前进方向。由全部变量的偏导数汇总而成的向量成为梯度(gradient)。梯度指示方向是各点处函数值减少最多的方向,因此无法保证梯度所指方向就是函数最小值或真正应该前进的方向。因为函数的极小值、最小值以及被成为鞍点(saddle point)的地方,梯度都为0。极小值是局部最小值,也就是限定在某个范围内的最小值。鞍点是从某个方向上看是极大值,从另一个方向上看则是极小值的点。虽然梯度法是要寻找梯度为0的地方,但是那个地方不一定就是最小值,也有可能是极小值或鞍点。此外,当函数很复杂且呈扁平状时,学习可能会进入一个平坦地区,陷入被成为“学习高原”的无法前进的停滞期。
在梯度法(gradient method)中,函数的取值从当前位置沿着梯度方向前进一定距离,然后在新地方重新求梯度,再沿着新梯度方向前进,如此反复不断沿梯度方向前进。梯度法是解决机器学习中最优化问题的常用方法,特别在神经网络的学习中经常被用到。根据目的是寻找最小值还是最大值,梯度法的叫法也不同。寻找最小值的梯度法称为梯度下降法(gradient descent method ),寻找最大值的梯度法称为梯度上升法(gradient ascent method)。神经网络中梯度法主要是指梯度下降法。
梯度法主要有以下两种。一种是数值微分,一种是误差反向传播。
1、数值微分
导数就是表示某个瞬间的变化量。
上式表示x的微小变化将导致函数f(x)的值在多大程度上发生变化。但是这种数值微分法是有缺陷的,因为在Python中h不可能无限接近0,当h小的一定程度时就会出现舍入误差(rounding error)。为了减小这个误差,可以计算函数f在(x+h)和(x-h)之间的差分,由于是以x为中心计算左右两边的差分,所以也成为中心差分(而(x+h)和之间的差分成为前向差分)。
def numerical_diff(f, x):h = 1e-4 # 0.0001return (f(x+h) - f(x-h)) / (2*h)
这样Python实现代码是利用微小的差分求导数的,所以也叫数值微分(numerical differentiation)。
比如实现一个二次函数的求导,分别在x=5和x=10处求切线。
# coding: utf-8
import numpy as np
import matplotlib.pylab as pltdef numerical_diff(f, x):h = 1e-4 # 0.0001return (f(x+h) - f(x-h)) / (2*h)def function_1(x):return 0.01*x**2 + 0.1*x def tangent_line(f, x):#画x点处的切线d = numerical_diff(f, x)print(d)y = f(x) - d*xreturn lambda t: d*t + y#返回一个t为自变量的函数x = np.arange(0.0, 20.0, 0.1)
y = function_1(x)
plt.xlabel("x")
plt.ylabel("f(x)")tf = tangent_line(function_1, 5)
y2 = tf(x)plt.plot(x, y)
plt.plot(x, y2, linestyle = "--", label="x=5")
plt.legend()
plt.show()
数值微分求的导数是有误差的,不是真的导数。想要求真的导数要通过数学式推导,也成为解析性求导,这样求出来的导数是不含误差的。
当求有多个变量的函数的导数时,就要使用偏导数。对哪个变量求导就固定住其他变量,以该变量求数值微分。比如对求偏导数
# coding: utf-8
# cf.http://d.hatena.ne.jp/white_wheels/20100327/p3
import numpy as np
import matplotlib.pylab as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cmdef _numerical_gradient_no_batch(f, x):h = 1e-4 # 0.0001grad = np.zeros_like(x)for idx in range(x.size):tmp_val = x[idx]x[idx] = float(tmp_val) + hfxh1 = f(x) # f(x+h)x[idx] = tmp_val - h fxh2 = f(x) # f(x-h)grad[idx] = (fxh1 - fxh2) / (2*h)x[idx] = tmp_val # 还原值return graddef numerical_gradient(f, X):if X.ndim == 1:return _numerical_gradient_no_batch(f, X)else:grad = np.zeros_like(X)for idx, x in enumerate(X):grad[idx] = _numerical_gradient_no_batch(f, x)return graddef function_2(x):if x.ndim == 1:return np.sum(x**2)else:return np.sum(x**2, axis=1)def tangent_line(f, x):d = numerical_gradient(f, x)print(d)y = f(x) - d*xreturn lambda t: d*t + yif __name__ == '__main__':x0 = np.arange(-2, 2.5, 0.25)x1 = np.arange(-2, 2.5, 0.25)X, Y = np.meshgrid(x0, x1)Z = X**2 + Y**2fig = plt.figure()ax = fig.add_subplot(111, projection='3d')ax.plot_surface(X, Y, Z, rstride=4, cstride=4, cmap=cm.YlGnBu_r)plt.show()X = X.flatten()Y = Y.flatten()grad = numerical_gradient(function_2, np.array([X, Y]) )plt.figure()plt.quiver(X, Y, -grad[0], -grad[1], angles="xy",color="#666666")#,headwidth=10,scale=40,color="#444444")plt.xlim([-2, 2])plt.ylim([-2, 2])plt.xlabel('x0')plt.ylabel('x1')plt.grid()plt.legend()plt.draw()plt.show()
可以看到梯度呈现为有向向量,梯度指向函数的最低处(最小值),所有剪头指向同一点,并且离最低处越远剪头越大。
用数学式表示梯度法
表示更新量,在神经网络中称为学习率 (learning rate) ,学习率决定在一次学习中应该学习多少,以及在多大程度上更新参数。学习率需要实现确定某个值,过大过小都无法抵达一个最小值,过大会发散成一个很大的值,过小的话没怎么更新就结束了。神经网络中一般都是边改变学习率的值变确认学习是否正确进行。像学习率这样的参数称为超参数,需要人工设定,因此需要尝试多个值。
对于神经网络求梯度是指损失函数关于权重参数的梯度。比如权重W的神经网络,损失函数L,梯度为,即
# coding: utf-8
import numpy as npdef softmax(x):if x.ndim == 2:x = x.Tx = x - np.max(x, axis=0)y = np.exp(x) / np.sum(np.exp(x), axis=0)return y.T x = x - np.max(x) # 溢出对策return np.exp(x) / np.sum(np.exp(x))def numerical_gradient(f, x):h = 1e-4 # 0.0001grad = np.zeros_like(x)it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])while not it.finished:idx = it.multi_indextmp_val = x[idx]x[idx] = float(tmp_val) + hfxh1 = f(x) # f(x+h)x[idx] = tmp_val - h fxh2 = f(x) # f(x-h)grad[idx] = (fxh1 - fxh2) / (2*h)x[idx] = tmp_val # 还原值it.iternext() return graddef cross_entropy_error(y, t):if y.ndim == 1:t = t.reshape(1, t.size)y = y.reshape(1, y.size)# 监督数据是one-hot-vector的情况下,转换为正确解标签的索引if t.size == y.size:t = t.argmax(axis=1)batch_size = y.shape[0]return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_sizeclass 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 lossx = np.array([0.6, 0.9])
t = np.array([0, 0, 1])net = simpleNet()f = lambda w: net.loss(x, t)
dW = numerical_gradient(f, net.W)print(dW)
这里以softmax为激活函数,交叉熵作为损失函数求关于权重的梯度。这里的权重是随机生成的,梯度采用数值微分的方法求解。
当训练数据庞大时,通常会随机选出一部分数据来求梯度,因此称为随机梯度下降法(stochastic gradient descent)。在很多深度学习的框架中,随机梯度下降法通常使用一个名为SGD的函数实现。
2、误差反向传播
使用计算图反向传播高效计算导数。反向传播的关键是链式法则,也就是复合函数求导,可以计算局部导数并传递。比如,
由上例可以推导出,加法节点的反向传播将上游传过来的导数原封不动的传给下游。
左边为正向传播,右边为反向传播。
乘法节点的反向传播会将上游的值乘以正向传播时输入信号的“翻转值”后传递给下游。
python代码实现
# coding: utf-8class MulLayer:def __init__(self):self.x = Noneself.y = Nonedef forward(self, x, y):self.x = xself.y = y out = x * yreturn outdef backward(self, dout):dx = dout * self.ydy = dout * self.xreturn dx, dyclass AddLayer:def __init__(self):passdef forward(self, x, y):out = x + yreturn outdef backward(self, dout):dx = dout * 1dy = dout * 1return dx, dy
这篇关于梯度法有哪些?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!