回到神经网络
前面介绍了损失函数、Mini-batch、导数和梯度,现在我们做一个整合,将前面的内容整合起来并重新回到神经网络的学习过程中。
我们现在知道,损失函数描述的是真实数据与预测结果的误差,真实数据,比方说一张手写的5,我们的输入,比方说一张手写的3,我们将这个图像作为输入(忽略所有的图像处理的内容),这张输入的图片通过与神经网络与参数(可能包含多层,每层还有可能相同可能不相同的激活函数)的组合运算得到一个输出,我们将这个输出(假设预测结果是一个手写的6)将真实数据(手写的5)使用一种合理的函数,即损失函数来表示这个输出和这个输入的差距(在整个过程中我们注意到损失函数的可变项,即可学习项,即参数,为神经网络中的参数,而不是输入或输出数据),我们并不将这一个单独的数据作为优化目标,而是将很多个相似的差距之和表示为一个大的优化目标,即代价函数,作为优化的目标。
现在,代价函数是一个复杂的有很多误差项的大的函数,我们的优化目标是使得代价函数值最小,而为了达成这个目标,我们要做的是调整代价函数的参数空间,这个参数空间是神经网络中的所有可学习参数(即可优化参数),我们不可能对这么复杂的损失韩式和参数空间直接寻找到最小值,而根据梯度的意义,我们可以通过使用梯度来迭代寻找这个最小值,这个方法就是梯度法(梯度法在最优化理论和神经网络里只是泛指在迭代优化过程中使用到了梯度的一类算法,它有很多的演进算法)。其中,最为简单的是梯度下降法。
以一个二元函数的例子来演示梯度下降法:
由于我们知道,一个函数在某个点的最快下降方法就是该函数在该点的梯度方向,用数学表达式可以表示为:
x
=
x
−
η
▽
f
(
x
)
\textbf{x} = \boldsymbol{\textbf{x}} - \eta \bigtriangledown f({\textbf{x}})
x=x−η▽f(x)
其中,X表示(x0,x1 … xn),η表示学习率,它决定在一次学习过程中,应该学习多少,以及在多大程度上更新参数。上面这个式子,会反复的执行,逐渐减小函数值,让我们对一个二元函数:
f
(
x
0
,
x
1
)
=
x
0
2
+
x
1
2
f(x_0,x_1) = x_0^2+x_1^2
f(x0,x1)=x02+x12
使用梯度下降法求最小值:
import numpy as np
import matplotlib.pyplot as plt
def numerical_gradient(f,x):
#负责求梯度的函数;
h = 1e-4
grad = np.zeros_like(x)
for idx in range(x.size):
tmp_val = x[idx]
x[idx] = tmp_val + h
fxh1 = f(x)
x[idx] = tmp_val - h
fxh2 = f(x)
grad[idx] = (fxh1 - fxh2) / (2*h)
x[idx] = tmp_val
return grad
def gradient_descent(f,init_x,lr=0.01,step_num=100):
#负责迭代计算最终结果,其中init_x为迭代的初始值,lr表示学习率,
#step_num表示迭代次数。
x = init_x
#设定迭代初始值
for i in range(step_num):
#进行迭代,每次迭代都要重新计算一次在当前参数值下的梯度
#并且计算迭代后的参数值。
grad = numerical_gradient(f,x)
x -= lr * grad
return x
def func(x):
#目标函数。
return x[0]**2 + x[1]**2
if __name__ == '__main__':
init_x = np.array([-3.0,4.0])
print(gradient_descent(func,init_x=init_x,lr=0.1,step_num=100))
#输出结果为:[-6.11110793e-10 8.14814391e-10]
需要注意的是代码中的所谓学习率,它是一个很重要的参数,如果学习率过大,可能在一次迭代过程中就会越过最小值,会使迭代陷入到一个在最优值两侧”荡秋千“的情况,反之如果该参数过小,则需要非常多的迭代次数才会达到最优值,所以设定一个合理的学习率是深度学习中非常重要的内容。但是另一方面学习率和神经网络中的参数性质不同,它不是通过训练数据和学习算法自动获得的,这样的参数称为超参数,以后会专门讨论超参数的设定,目前可以通过经验和多次尝试的方法来设定超参数。
神经网络的梯度
神经网络的学习也要求梯度,即损失函数关于权重等参数的梯度,比如,有一个形状为2X3的权重W的神经网络,损失函数可以用L表示,则权重W和损失函数的数学表达为:
W
=
(
w
11
w
12
w
13
w
21
w
22
w
23
)
W = \begin{pmatrix} w_{11} & w_{12} & w_{13}\\ w_{21} & w_{22} & w_{23} \end{pmatrix}
W=(w11w21w12w22w13w23)
∂
L
∂
W
=
(
∂
L
∂
w
11
∂
L
∂
w
12
∂
L
∂
w
13
∂
L
∂
w
21
∂
L
∂
w
22
∂
L
∂
w
23
)
\frac{\partial L}{\partial W} = \begin{pmatrix} \frac{\partial L}{\partial w_{11}} & \frac{\partial L}{\partial w_{12}} & \frac{\partial L}{\partial w_{13}}\\ \frac{\partial L}{\partial w_{21}} & \frac{\partial L}{\partial w_{22}} & \frac{\partial L}{\partial w_{23}} \end{pmatrix}
∂W∂L=(∂w11∂L∂w21∂L∂w12∂L∂w22∂L∂w13∂L∂w23∂L)
其中的每个元素,表示当该权重参数稍微变化时,损失函数L发生多大的变化,以下,我们以一个简单的神经网络为例,来实现求梯度的代码,为此,我们实现一个简单的神经网络类:
import numpy as np
from pyDL.common_function import softmax,cross_entropy_error,sigmoid
from pyDL.common_gradient import numerical_gradient
#类中需要使用到的激活函数、输出函数、梯度计算函数在外部实现
class TwoLayerNet:
'''
实现一个简单的二层神经网络;
'''
def __init__(self,input_size,hidden_size,output_size,weight_init_std = 0.01):
self.param = {}
#随机初始化权重和偏置;
self.param['W1'] = weight_init_std*np.random.randn(input_size,hidden_size)
self.param['B1'] = weight_init_std*np.random.randn(hidden_size)
self.param['W2'] = weight_init_std*np.random.randn(hidden_size,output_size)
self.param['B2'] = weight_init_std*np.random.randn(output_size)
def predict(self,x):
#向前传播完成预测;
a1 = sigmoid(np.dot(x,self.param['W1']+self.param['B1']))
a2 = sigmoid(np.dot(a1,self.param['W2']+self.param['B2']))
y = softmax(a2)
return y
def loss(self,x,t):
#计算损失值
y = self.predict(x)
return cross_entropy_error(y,t)
def numrical_gradient(self,x,t):
#计算权重参数的梯度。
loss_W = lambda W:self.loss(x,t)
grads= {}
grads['W1'] = numerical_gradient(loss_W,self.param['W1'])
grads['B1'] = numerical_gradient(loss_W,self.param['B1'])
grads['W2'] = numerical_gradient(loss_W,self.param['W2'])
grads['B2'] = numerical_gradient(loss_W,self.param['B2'])
return grads
'''
以上已经实现了一个简单的二层网络以及必要的函数:预测、计算误差、计算梯度;
实际的神经网络中可能还需要类似评估精度等函数,后面会加入实现。
下面我们利用这个神经网络进行一次完整的学习过程:
'''
if __name__ == '__main__':
#实例化一个网络,输入规模为3,隐藏层有100个节点,输出规模为5
#因此第一层权重为3*100,第二层权重为100*5
net = TwoLayerNet(input_size=3,hidden_size=100,output_size=5)
#输入数据:
x = np.array([0.3,0.5,0.2])
#预测结果:
y = net.predict(x)
print(y) #[0.22827787 0.18163036 0.20331617 0.2129186 0.173857 ]
#计算损失:
t = np.array([1,0,0,0,0])
l = net.loss(x,t)
print(l) #损失值为:1.477191208588704
#计算梯度并进行一次梯度下降:
g = net.numrical_gradient(x,t)
lr = 0.3
for key in('W1','B1','W2','B2'):
net.param[key] -= lr*g[key]
new_loss = net.loss(x,t)
print(new_loss) #学习后的新损失值为:0.9048321944027032
从以上实例中可以看出,经过一次学习(梯度下降)后,一样的输入,预测后的损失值有所下降,这往往被称为更精准的预测。
同样,二层网络模型,可以对mini-batch实现,只要使得我们的输入变为(batchsize,datasizi)即可。
但是新的问题出现了,数值微分是一种很慢的计算梯度的方式,特别是损失函数中包含各式各样的复杂的激活函数等情况时,稍微规模大一些的输入,或者小规模的中等batch输入,都会导致梯度计算的时间难以预计,为了加速梯度的计算,误差反向传播算法应运而生。