本文内容参考了 Michael Nielsen 的神经网络与深度学习一书
神经网络简介
神经网络是一种模仿生物神经网络的结构和功能的数学模型或算法模型。神经网络模型使用人工定义的函数替代生物神经网络中的神经元,使用该函数的计算结果值当作生物神经的神经递质,因此,人工定义的这种函数也成为人工神经元,将该函数的计算结果值称为激活值。为了方便称呼,下文中的人工神经元就指代人工神经元函数或人工神经元激活函数。神经网络模型的运行原理和生物神经网络的运行原理类似,神经网络是由多层神经元构成,前面一层的人工神经元的计算值,传递给后一层的每个人工神经元,后一层人工神经元给前面一层神经元的输出值添加不同的权重,并作累加,再加上一个偏差值,这样后面一层人工神经元就得到了前面一层人工神经元输出的一个加权累加和(此称呼忽略了加上的偏差值),然后这一层每个人工神经元对它的加权累加和做激活,即加权累加和带入人工神经元函数求值,这一层的人工神经元激活输出值传递到下一层人工神经元函数,依次类推,直到最后一层神经元输出结果。
神经网络模型
上图中是一个神经网络模型的示意图,从左至右看,竖着排列的圆圈代表人工神经元,一列神经元代表一层,两层神经元之间的带方向的箭头表示每个人工神经元的激活值的输出,从一个神经元出发的箭头都是相同的激活值。
最左侧一列神经元是神经网络的输入层(input layer),其中每个神经元是输入神经元,事实上,输入神经元并不能算是真正的人工神经元,输入层的神经元的作用只是将数据输出到网络中,并没有真正的输入做加权累加和激活。通常每个输入神经元输入一个取值区间为[0,1]的数值,作为神经网络的输入。
中间两列神经元是神经网络的隐藏层(hidden layers),称为隐藏层的原因是这些人工神经元层既不做数据输入,也不做网络结果输出,没有别的意思了。通常称有多个隐藏层( ≥ 2 \ge 2 ≥2)的神经网络为深度神经网络。
最右边一列神经元是神经网络的输出层(output layer),这一层是神经网络计算结果的输出层。
除去输入层人工神经元外,其他人工神经元都是一个激活函数,以Sigmoid激活函数为例:
σ
(
z
)
=
1
1
+
e
−
z
\sigma(z)=\frac{1}{1+e^{-z}}
σ(z)=1+e−z1
其中输入参数
z
z
z就是人工神经元对上一层输出的加权输入:
z
j
l
=
∑
k
a
k
l
−
1
w
j
k
l
+
b
j
l
z^l_j=\sum_k a_k^{l-1}w_{jk}^l+b^l_j
zjl=k∑akl−1wjkl+bjl
上式的含义是第
l
l
l层的第
j
j
j个神经元的加权累加和是上一层激活值
a
l
−
1
a^{l-1}
al−1(上一层的激活输出值组成的向量)与该神经元对输入激活值的权重的加权和,再加上该神经元的偏差值
b
j
l
b_j^l
bjl。
神经网络原理
神经网络作为一种模拟生物学神经网络的算法模型,可以用于更多的人们纯靠传统算法无法解决的问题,比如:手写数字图片的识别、根据已有数据的分类预测未知数据的分类、判断时序数据的动态趋势、人脸识别、图片识别分类、自然语言处理、机器学习等等人工智能领域。
神经网络有如此强大的能力,依靠两个特性,第一,人工神经元源于感知机【参考连接: 神经网络与深度学习第一章】,拥有能够处理任何计算需求的能力,包括:基础的逻辑计算、基于输入因素的决策制定等。第二,神经网络可以根据输入数据的计算结果,通过计算机编程实现每个神经元的权重和偏差值的自我调整,以减小网络输出值与期望值之间的差距,从而使得神经网络对所有的输入值都能产生更加契合期望的输出值(数据标签)。
本文主要是神经网络的有监督学习,即:每个输入数据都有对应的期望输出值(也称为数据标签)。所有的数据被分为训练数据集和测试数据集,训练数据集主要用于调整网络的学习参数,也就是神经元的权重和偏差值。测试数据集用于测试训练好的神经网络的效果,即,查看神经网路是否已经满足我们的期望,是否能对测试集中的输入数据输出其对应的标签值。总而言之,神经网络的好坏主要看神经网络是否得到了充足的训练,训练是否使所有神经元得到了合适的权重和偏差值。
神经网络的第二个特性是通过学习算法来实现的。常用的学习算法称为随机梯度下降:
随机梯度下降算法的目的是为了降低神经网络对每对训练数据的输出结果的误差。误差的公式如下:
C
(
w
,
b
)
≡
1
2
n
∑
x
∥
y
(
x
)
−
a
∥
2
.
C(w,b) \equiv \frac{1}{2n} \sum_x \| y(x) - a\|^2.
C(w,b)≡2n1x∑∥y(x)−a∥2.
其中
C
C
C是成本函数,也称为二次损失函数。
y
y
y是数据的标签值,
a
a
a神经网络的输出值,
∑
x
\sum_x
∑x表示网络输出层神经元的激活值与期望输出值得距离得累加,是对所有输出神经元的累加结果。
为了降低上面的成本函数,我们要使用微积分中的梯度工具:
Δ
C
≈
∂
C
∂
w
Δ
w
+
∂
C
∂
b
Δ
b
.
\Delta C \approx \frac{\partial C}{\partial w} \Delta w + \frac{\partial C}{\partial b} \Delta b.
ΔC≈∂w∂CΔw+∂b∂CΔb.
上式来源于微分的定义,可以理解为通过小的改变量
Δ
w
和
Δ
b
\Delta w和\Delta b
Δw和Δb逐步降低成本函数
Δ
C
\Delta C
ΔC。但是重要的一点是,要保证上式右边的取值为负数。因此我们引入了下面的假设:
w
k
→
w
k
′
=
w
k
−
η
∂
C
∂
w
k
b
l
→
b
l
′
=
b
l
−
η
∂
C
∂
b
l
.
\begin{align*} w_k & \rightarrow & w_k' = w_k-\eta \frac{\partial C}{\partial w_k} \\ b_l & \rightarrow & b_l' = b_l-\eta \frac{\partial C}{\partial b_l}. \end{align*}
wkbl→→wk′=wk−η∂wk∂Cbl′=bl−η∂bl∂C.
即令
Δ
w
=
−
η
∂
C
∂
w
k
\Delta w=-\eta \frac{\partial C}{\partial w_k}
Δw=−η∂wk∂C、
Δ
b
=
−
η
∂
C
∂
b
l
\Delta b=-\eta \frac{\partial C}{\partial b_l}
Δb=−η∂bl∂C。其中
η
\eta
η是大于零的极小数,能够确保上面关于
Δ
C
\Delta C
ΔC的约等式成立,称为学习率。
因此,如果能够计算出
C
C
C的梯度,
∇
C
≡
(
∂
C
∂
w
,
∂
C
∂
b
)
T
.
\nabla C \equiv \left(\frac{\partial C}{\partial w}, \frac{\partial C}{\partial b}\right)^T.
∇C≡(∂w∂C,∂b∂C)T.
我们就能够通过编程实现每个训练数据对都能调整神经网络参数以产生更适合自己的结果。
但是当神经网络拥有上百亿个参数时,使用所有输入数据训练一次网络的参数的代价是巨大的,因此我们提出了一种随机选取固定个数的小批量样本对网络的参数进行分批次的调整修改的算法,也就是随机梯度下降算法。该算法,每次随机选取训练数据集中固定个数的小批量样本来训练网络,直到选取过所有样本训练了网络,才算是一轮训练结束。
反向传播算法
从上面的描述中,我们已经知道了只要计算出成本函数 C C C的梯度,就能实现对网络的训练。因此,接下来,我们就要寻求计算成本函数 C C C的梯度的方法,首先,我们可以看到, C C C的梯度就是 C C C关于其变量的偏导数组成的向量。
但是我们的成本函数只是关于网络的最后一层——输出层参数的函数,要计算成本函数对于神经网络所有层的参数的偏导,我们需要引入一个能够从输出层传播到前面各层的误差:
我们定义第
l
l
l层的第
j
j
j个神经元的误差
δ
j
l
\delta_j^l
δjl为
δ
j
l
≡
∂
C
∂
z
j
l
.
\delta_j^l \equiv \frac{\partial C}{\partial z_j^l}.
δjl≡∂zjl∂C.
首先,成本函数是最后一层人工神经元的加权累加输入的直接函数,因此我们有反向传播的第一个公式:
输出层中神经元的误差方程
δ
L
\delta^L
δL: 下式为
δ
L
\delta^L
δL的组成元素:
δ
j
L
=
∂
C
∂
a
j
L
σ
′
(
z
j
L
)
\delta_j^L=\frac{\partial C}{\partial a_j^L}\sigma'(z_j^L)
δjL=∂ajL∂Cσ′(zjL)
然后,我们需要计算前面各层的误差,即,成本函数对于前面各层神经元加权累加输入的偏导,因此,我们可以通过微积分中的链式求导法则计算得到第二个公式:
误差
δ
l
\delta^l
δl关于下一层误差
δ
l
+
1
\delta^{l+1}
δl+1的方程: 具体如下:
δ
l
=
(
(
w
l
+
1
)
T
δ
l
+
1
)
⊙
σ
′
(
z
l
)
\delta^l=((w^{l+1})^T\delta^{l+1})\odot \sigma'(z^l)
δl=((wl+1)Tδl+1)⊙σ′(zl)
其中
(
w
l
+
1
)
T
(w^{l+1})^T
(wl+1)T是第
(
l
+
1
)
t
h
(l+1)^{th}
(l+1)th层的权重矩阵
w
l
+
1
w^{l+1}
wl+1的转置。这个方程看起来复杂,但是每个元素都有一个较好的解释。假设我们知道在第
l
+
1
t
h
l+1^{th}
l+1th层的误差
δ
l
+
1
\delta^{l+1}
δl+1。当我们使用转置权重矩阵
(
w
l
+
1
)
T
(w^{l+1})^T
(wl+1)T时,我们可以直观的将其当作误差在网络中的反向移动,从而给出我们在第
l
t
h
l^{th}
lth层输出的某种误差的度量。
接下来,就到了计算成本函数对于各个神经元权重的偏导了,第三个公式:
网络中成本关于任一权重值的改变率的方程: 具体如下:
∂
C
∂
w
j
k
l
=
a
k
l
−
1
δ
j
l
\frac{\partial C}{\partial w_{jk}^l}=a_k^{l-1}\delta_j^l
∂wjkl∂C=akl−1δjl
最后,计算成本函数对于各个神经元偏差值的偏导,第四个公式:
网络中成本关于任一偏差值的改变率的方程: 具体如下:
∂
C
∂
b
j
l
=
δ
j
l
\frac{\partial C}{\partial b_j^l}=\delta_j^l
∂bjl∂C=δjl
即,误差
δ
j
l
\delta_j^l
δjl正好等于改变率
∂
C
∂
b
j
l
\frac{\partial C}{\partial b_j^l}
∂bjl∂C。
根据上面的四个公式,我们可以通过从神经网络输出层到第二层的误差的传播,顺便计算出成本函数关于各个参数的偏导,最后只要将所有训练样本对于各个参数的训练的偏导累加起来更新原来的神经网络参数即可。
神经网络算法步骤
反向传播算法步骤
- 输入 x x x:设置对应于输入层的激活值 a 1 a^1 a1
- 前馈计算:对于每一层 l = 2 , 3 , . . . . , L l=2,3,....,L l=2,3,....,L,计算 z l = w l a l − 1 + b l z^{l} = w^l a^{l-1}+b^l zl=wlal−1+bl和 a l = σ ( z l ) a^{l} = \sigma(z^{l}) al=σ(zl)
- 输出层误差 δ L \delta^L δL:计算向量 δ L = ∇ a C ⊙ σ ′ ( z L ) \delta^{L} = \nabla_a C \odot \sigma'(z^L) δL=∇aC⊙σ′(zL)
- 反向传播误差:对于每一层 l = L − 1 , L − 2 , … , 2 l = L-1, L-2, \ldots, 2 l=L−1,L−2,…,2,计算 δ l = ( ( w l + 1 ) T δ l + 1 ) ⊙ σ ′ ( z l ) \delta^{l} = ((w^{l+1})^T \delta^{l+1}) \odot \sigma'(z^{l}) δl=((wl+1)Tδl+1)⊙σ′(zl)
- 输出:给出成本函数的梯度, ∂ C ∂ w j k l = a k l − 1 δ j l \frac{\partial C}{\partial w^l_{jk}} = a^{l-1}_k \delta^l_j ∂wjkl∂C=akl−1δjl和 ∂ C ∂ b j l = δ j l \frac{\partial C}{\partial b^l_j} = \delta^l_j ∂bjl∂C=δjl
基于小批量数据集的随机梯度下降学习算法步骤
- 输入训练样本集合
- 对于每个训练样本
x
x
x:设置对应的输入激活值
a
x
,
1
a^{x,1}
ax,1,并执行以下步骤:
- 前馈计算:对于每一层 l = 2 , 3 , . . . . , L l=2,3,....,L l=2,3,....,L,计算 z x , l = w l a x , l − 1 + b l z^{x,l} = w^l a^{x,l-1}+b^l zx,l=wlax,l−1+bl和 a x , l = σ ( z x , l ) a^{x,l} = \sigma(z^{x,l}) ax,l=σ(zx,l)
- 输出层误差 δ x , L \delta^{x,L} δx,L:计算向量 δ x , L = ∇ a C x ⊙ σ ′ ( z x , L ) \delta^{x,L} = \nabla_a C_x \odot \sigma'(z^{x,L}) δx,L=∇aCx⊙σ′(zx,L)
- 反向传播误差:对于每一层 l = L − 1 , L − 2 , … , 2 l = L-1, L-2, \ldots, 2 l=L−1,L−2,…,2,计算 δ x , l = ( ( w l + 1 ) T δ x , l + 1 ) ⊙ σ ′ ( z x , l ) \delta^{x,l} = ((w^{l+1})^T \delta^{x,l+1}) \odot \sigma'(z^{x,l}) δx,l=((wl+1)Tδx,l+1)⊙σ′(zx,l)
- 梯度下降:对于每一层 l = 2 , 3 , . . . . , L l=2,3,....,L l=2,3,....,L,根据公式 w l → w l − η m ∑ x δ x , l ( a x , l − 1 ) T w^l \rightarrow w^l-\frac{\eta}{m} \sum_x \delta^{x,l} (a^{x,l-1})^T wl→wl−mη∑xδx,l(ax,l−1)T更新权重,根据公式 b l → b l − η m ∑ x δ x , l b^l \rightarrow b^l-\frac{\eta}{m} \sum_x \delta^{x,l} bl→bl−mη∑xδx,l更新偏差值。
代码
import numpy as np
def sigmoid(x):
"""定义激活函数"""
return 1.0/(1.0+np.exp(-x))
def cost_derivative(y,a):
"""计算成本函数关于输出层输出结果的导数"""
return (a-y)
def sigmoid_prime(a):
"""激活函数关于加权累加和的导数"""
return sigmoid(a)*(1.0-sigmoid(a))
class Network_based_matrix(object):
def __init__(self,sizes):
"""神经网络初始化方法,sizes是每层神经元个数的列表,例如: Network_based_matrix([2,3,1]),生成输入层两个神经元,隐藏层三个神经元,输出层一个神经元的神经网络"""
self.sizes = sizes
self.layers = len(self.sizes)
self.weights = [np.random.randn(y,x) for x,y in zip(sizes[:-1],sizes[1:])]
self.biases = [np.random.randn(x,1) for x in sizes[1:]]
def feedforward(self,X):
"""前馈计算,输入X,神经网络输出结果"""
for w,b in zip(self.weights,self.biases):
X = sigmoid(np.einsum("...jk,...kl->...jl",w,X) + b)
return X
def evaluate(self,X):
"""使用测试数据评估模型"""
y=[y for x,y in X]
X=np.array([x for x,y in X])
result=np.argmax(self.feedforward(X),axis=1)
return sum(int(x==y) for (x,y) in zip(result,y))
def SGD(self,train_data,epochs,eta,batch_size,test_data=None):
"""随机梯度下降训练神经网络,epochs为训练轮数"""
if test_data:n_test=len(test_data)
n_train=len(train_data)
for epoch in range(epochs):
np.random.shuffle(list(train_data))
batches=[
train_data[k:k+batch_size]
for k in range(0,n_train,batch_size)
]
for minibatches in batches:
self.update_mini_batches(minibatches,eta)
if test_data:
print(f"Epoch {epoch+1}: {self.evaluate(test_data)} / {n_test}")
else:
print(f"Epoch {epoch+1} complete!")
def update_mini_batches(self, minibatches,eta):
"""使用小批量训练数据,调用反向传播方法backprop_batches(self, minibatches)更新神经网络参数"""
delta_w,delta_b=self.backprop_batches(minibatches)
self.weights=[w-(eta/len(minibatches))*np.sum(nw,axis=0) for w,nw in zip(self.weights,delta_w)]
self.biases=[b-(eta/len(minibatches))*np.sum(nb,axis=0) for b,nb in zip(self.biases,delta_b)]
def backprop_batches(self, minibatches):
"""反向传播算法,计算成本函数关于网络中各个神经元的参数的偏导,使用全部小批量训练数据集,采用全矩阵计算模式,一次性计算出小批量数据集中全部样本对网络参数的调整,降低计算成本。"""
tr_x=[]
tr_y=[]
for x,y in minibatches:
tr_x.append(x)
tr_y.append(y)
a=np.array(tr_x)
a_s=[np.array(tr_x)]
z_s=[]
d_w=[np.zeros(i.shape)*len(minibatches) for i in self.weights]
d_b = [np.zeros(i.shape) * len(minibatches) for i in self.biases]
for w,b in zip(self.weights,self.biases):
zl=np.dot(w,a).swapaxes(0,1)+b
z_s.append(zl)
a=sigmoid(zl)
a_s.append(a)
e_l=cost_derivative(tr_y,a_s[-1])*sigmoid_prime(z_s[-1])
d_w[-1]=np.einsum("...jk,...nm->...jn",e_l,a_s[-2])
d_b[-1]=e_l
for l in range(2,self.layers):
e_l=np.einsum("ji,kjm->kim",self.weights[-l+1],e_l)*sigmoid_prime(z_s[-l])
d_b[-l]=e_l
d_w[-l]=np.einsum("...jk,...nm->...jn",e_l,a_s[-l-1])
return d_w,d_b
参考文献:
Neural Networks and deep learning——https://neuralnetworksanddeeplearning.com
上面内容为作者个人理解制作,如有不足之处,还望各位大佬在评论中指出!多谢!
图片取自连接:http://neuralnetworksanddeeplearning.com/images/tikz11.png ↩︎