梯度法有哪些?

本文介绍了机器学习中寻找最优参数的重要方法——梯度法,包括梯度下降和梯度上升,以及在神经网络学习中的应用。讨论了数值微分在求导数中的作用和误差,展示了如何通过数值微分求解二次函数和多元函数的导数。同时,提到了误差反向传播作为高效计算导数的手段,利用链式法则在计算图中进行反向传播。最后,通过实例展示了在神经网络中计算损失函数关于权重的梯度过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

为啥要提出梯度法?因为机器学习的主要任务就是在学习时寻找最优参数。啥叫最优参数?就是使损失函数值最小时的权重和偏置。而损失函数很复杂,参数空间庞大,使用梯度来寻找函数最小值的方法就是梯度法。梯度法使用梯度信息决定前进方向。由全部变量的偏导数汇总而成的向量成为梯度(gradient)。梯度指示方向是各点处函数值减少最多的方向,因此无法保证梯度所指方向就是函数最小值或真正应该前进的方向。因为函数的极小值、最小值以及被成为鞍点(saddle point)的地方,梯度都为0。极小值是局部最小值,也就是限定在某个范围内的最小值。鞍点是从某个方向上看是极大值,从另一个方向上看则是极小值的点。虽然梯度法是要寻找梯度为0的地方,但是那个地方不一定就是最小值,也有可能是极小值或鞍点。此外,当函数很复杂且呈扁平状时,学习可能会进入一个平坦地区,陷入被成为“学习高原”的无法前进的停滞期。

 在梯度法(gradient method)中,函数的取值从当前位置沿着梯度方向前进一定距离,然后在新地方重新求梯度,再沿着新梯度方向前进,如此反复不断沿梯度方向前进。梯度法是解决机器学习中最优化问题的常用方法,特别在神经网络的学习中经常被用到。根据目的是寻找最小值还是最大值,梯度法的叫法也不同。寻找最小值的梯度法称为梯度下降法(gradient descent method ),寻找最大值的梯度法称为梯度上升法(gradient ascent method)。神经网络中梯度法主要是指梯度下降法。

梯度法主要有以下两种。一种是数值微分,一种是误差反向传播。

1、数值微分

导数就是表示某个瞬间的变化量。

\frac{df(x)}{dx}= \lim_{h\rightarrow 0}\frac{f(x+h)-f(x)}{h}

上式表示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.0001
    return (f(x+h) - f(x-h)) / (2*h)

这样Python实现代码是利用微小的差分求导数的,所以也叫数值微分(numerical differentiation)。

比如实现一个二次函数y=0.01x^{2}+0.1x的求导,分别在x=5和x=10处求切线。

# coding: utf-8
import numpy as np
import matplotlib.pylab as plt


def numerical_diff(f, x):
    h = 1e-4 # 0.0001
    return (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*x
    return 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()

数值微分求的导数是有误差的,不是真的导数。想要求真的导数要通过数学式推导,也成为解析性求导,这样求出来的导数是不含误差的。

当求有多个变量的函数的导数时,就要使用偏导数。对哪个变量求导就固定住其他变量,以该变量求数值微分。比如对f(x_{0},x_{1})=x_{0}^{2}+x_{1}^{2}求偏导数(\frac{\partial f}{\partial x_{0}},\frac{\partial f}{\partial x_{1}})

# 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 cm

def _numerical_gradient_no_batch(f, x):
    h = 1e-4 # 0.0001
    grad = np.zeros_like(x)
    
    for idx in range(x.size):
        tmp_val = x[idx]
        x[idx] = float(tmp_val) + h
        fxh1 = 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 grad


def 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 grad


def 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*x
    return lambda t: d*t + y
     
if __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**2
    fig = 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()

可以看到梯度呈现为有向向量,梯度指向函数的最低处(最小值),所有剪头指向同一点,并且离最低处越远剪头越大。

用数学式表示梯度法

x_{0}=x_{0}-\eta \frac{\partial f}{\partial x_0}

x_{1}=x_{1}-\eta \frac{\partial f}{\partial x_1}

\eta表示更新量,在神经网络中称为学习率 (learning rate) ,学习率决定在一次学习中应该学习多少,以及在多大程度上更新参数。学习率需要实现确定某个值,过大过小都无法抵达一个最小值,过大会发散成一个很大的值,过小的话没怎么更新就结束了。神经网络中一般都是边改变学习率的值变确认学习是否正确进行。像学习率这样的参数称为超参数,需要人工设定,因此需要尝试多个值。                                                                                             

对于神经网络求梯度是指损失函数关于权重参数的梯度。比如权重W的神经网络,损失函数L,梯度为\frac{\partial L}{\partial W},即

W=\begin{pmatrix} w11 &w12 &w13 \\ w21& w22 & w23 \end{pmatrix}

\frac{\partial L}{\partial W}=\begin{pmatrix} \frac{\partial L}{\partial W11} &\frac{\partial L}{\partial W12} &\frac{\partial L}{\partial W13} \\ \frac{\partial L}{\partial W21}&\frac{\partial L}{\partial W22} &\frac{\partial L}{\partial W23} \end{pmatrix}

# coding: utf-8
import numpy as np

def softmax(x):
    if x.ndim == 2:
        x = x.T
        x = 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.0001
    grad = np.zeros_like(x)
    
    it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
    while not it.finished:
        idx = it.multi_index
        tmp_val = x[idx]
        x[idx] = float(tmp_val) + h
        fxh1 = 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 grad

def 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_size

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

x = 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、误差反向传播

使用计算图反向传播高效计算导数。反向传播的关键是链式法则,也就是复合函数求导,可以计算局部导数并传递。比如,z=(x+y)^{2}

z=t^{2}

\\ t=x+y

由上例可以推导出,加法节点的反向传播将上游传过来的导数原封不动的传给下游。

 左边为正向传播,右边为反向传播。

乘法节点的反向传播会将上游的值乘以正向传播时输入信号的“翻转值”后传递给下游。

 python代码实现

# coding: utf-8


class MulLayer:
    def __init__(self):
        self.x = None
        self.y = None

    def forward(self, x, y):
        self.x = x
        self.y = y                
        out = x * y

        return out

    def backward(self, dout):
        dx = dout * self.y
        dy = dout * self.x

        return dx, dy


class AddLayer:
    def __init__(self):
        pass

    def forward(self, x, y):
        out = x + y

        return out

    def backward(self, dout):
        dx = dout * 1
        dy = dout * 1

        return dx, dy

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值