【深度学习笔记】神经网络的学习(2)


损失函数表示的时模型有多大程度上与数据不拟合,神经网络学习的过程就是让模型与数据拟合,并且尽可能使模型具有泛化能力。神经网络的损失函数往往很复杂,参数有很多,我们不知道参数为何值时算是函数最小(或者局部最小),我们转变一下参数更新的问题,参数的更新实际上就是在求解损失函数的最小值。根据微积分的知识可以知道,函数的梯度指向的额方向使函数的极小值,所以参数的更新是依靠计算损失函数的梯度来进行的(梯度下降)。

关于梯度的求解,主要有两种方法:

  • 数值微分(导数的定义,实际应用时速度很慢)
  • 误差反向传播

梯度下降

梯度下降的主要思想:

  1. 初始化参数
  2. 不断的改变参数的 值,不断减少 损失函数$L$直至达到最小值(一般为局部最小,很难达到全局最小)。

把梯度下降的过程想象成下山,每次向下走一段距离,最终会走到一个局部(全局)最低点。从图中可以看出,全局最低点为左侧箭头标注的地方,实际轨迹达到的是一个局部最低点,我们在训练模型时的函数往往很复杂,很难达到全局最优(复杂函数中,梯度往往指向的都不是全局最小值,但即使是局部最优,模型的性能也足够优秀)。

梯度下降数的学表达:

$$ W = W - \eta \frac{\\partial L}{\partial W} $$

其中$W$为参数矩阵,$\eta$为学习率,$\frac{\partial f}{\partial W}$为算式函数对参数的偏导数。

假设参数有$x_0$、$x_1$两个,则表达式为:

$$ x_0 = x_0 - \eta\frac{\partial L}{\partial x_0} $$

$$x_1 = x_1 - \eta\frac{\partial L}{\partial x_1}$$

还是以下山为例,学习率就相当于时下山时迈的步子的大小,步子大了,下山速度就会快,步子小了,下山速度就慢。同理,学习率大,参数更新步伐就大,模型收敛的会快,学习率小,收敛速度就慢,但学习率不是太大或者太小都不好:

学习率太大,会导致损失函数发散,导致很难收敛。

学习率太小,会导致收敛速度很慢,可能训练结束了,损失函数的值还很大。

如下图,上方的为学习率刚刚好,不是很大,也不是很小,下方的图是学习率太大,导致在接近最低点的地方由于步幅太大,跨刀了另一边,如此反复,无法达到最低点。我们把神经网络的权重、偏置等称为参数,这些是神经网络自动调整的;像学习率这种人为指定的参数,称为超参数,除了学习率(learning rate),超参数还有批大小(batch size等)等。

使用数值微分进行梯度下降

数值微分

导数的定义:

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

实际应用中,$h$如果取得太小(如$1e-50$),可能会发生舍入误差(因为省略小数的精细部分数值而造成最终结果的误差)。

除了舍入误差,数值微分本身也有误差,为了减小误差,我们计算函数$f(x)$在邻域$(x-h, x+h)$上的差分(中心差分),即:

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

这样,我们就可以求出损失函数的梯度。

代码实现:

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

# 进行测试
data = np.array([3.0, 4.0])
def func(X):
    return X[0]**2 + X[1]**2
print('在点(3, 4)处的值:', func(data))
print('在点(3, 4)处的梯度:', numerical_gradient(func, data))

函数numerical_gradient的参数为一个函数 $f(损失函数)$,和数据 $x$。

假设损失函数为代码中的函数func($L=x_0^2+x_1^2$),计算函数在点(3.0, 4.0)处的函数值和梯度。

运行结果:

在点(3, 4)处的值: 25.0
在点(3, 4)处的梯度: [6. 8.]

声明:迟於|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - 【深度学习笔记】神经网络的学习(2)


栖迟於一丘