关于代价函数的假设:
(1) 代价函数可以被写成一个 在每个训练样本 x上的代价函数Cx 的均值C=1n∑xCx. 反向传播实际上是对一个独立的训练样本计算了∂Cx∂ω和∂Cx∂b. 然后通过在所有训练样本上进行平均化得到∂C∂ω和∂C∂b. 实际上,有了这个假设,我们会认为训练样本x已经被固定住了,丢掉其下标,将代价函数Cx 看作C.
(2) 代价可以写成神经网络输出的函数costC=C(aL) 反向传播的四个基本方程
反向传播其实是对权重和偏差变化影响代价函数过程的理解,最终的目的就是计算偏导数∂C∂ωljk和∂C∂blj. 引入一个中间量,δlj,定义它为lth层第jth个神经元上的误差.
假设,我们对lth层的第jth个神经元的操作进行一些变化,比如,在神经元的带权输入上增加很小的变化Δzlj,使得神经元输出由δ(zlj)变为δ(zlj+Δzlj). 这个变化将会向后面的层进行传播,最终导致整个代价函数产生∂C∂zljΔzlj的变化(可以从微分的数学定义进行验证). 此时,如果∂C∂zlj有一个很大的值(或正或负),则可以通过选择与∂C∂zlj符号相反的Δzlj来降低代价。相反的,如果∂C∂zlj接近0,那么无法通过更改
Δzlj 来降低代价,此时可以认为神经元已经很接近最优了(这两种假设只能在Δzlj很小的时候才能够满足)。因此,可以有一种启发式的认识:∂C∂zlj是神经元误差的度量。根据以上这些描述,定义l层的第jth 个神经元上的误差δlj为:
δlj≡∂C∂zlj
这样,我们就可以使用δl表示关联于l层的误差向量。反向传播算法会告诉我们如何计算每层的δl ,然后将这些误差和最终我们需要的量∂C∂ωljk和∂C∂blj联系起来.输出层误差的方程
δLj=∂C∂aLjσ′(zLj)
这是一个很自然的表达式。右边第一项∂C∂aLj表示代价随着jth输出激活值的变化而变化的速度。如果C不太依赖一个特定的输出神经元j ,那么δLj就会很小。第二项σ′(zLj)刻画了在zLj处激活函数σ变化的速度.证明:
δLj=∂C∂zLj=∑k∂C∂aLk⋅∂aLk∂zLj,这里的求和是对输出层中的所有神经元k进行的. 当然,第k 个神经元的输出激活aLk只依赖于第j个神经元的加权输入(当k==j ), 因此,当k≠j时,∂zLk∂zLj消失。从而有:δLj=∂C∂zLj=∑k∂C∂aLk⋅∂aLk∂zLj=∂C∂aLj⋅∂aLj∂zLj.
同时,我们有aLj=σ(zLj), 则∂aLj∂zLj=σ′(zLj). 最终我们有:
δLj=∂C∂aLj⋅σ′(zLj)证毕.使用下一层的误差δl+1来表示当前层的误差δl
δL=((ωL+1)TδL+1)⊙σ′(zL)
其中(ωL+1)T是(L+1)th层的权重矩阵ωL+1的转置. 有了这个方程与第一个方程,我们就可以计算任何层的误差了:首先使用第一个方程计计算δL,然后使用第二个方程来计算δL−1,然后不断使用第二个方程,就可以一步一步地反向传播完整个网络.
证明:因为δLj=∂C∂zLj,则有δL+1k=∂C∂zL+1k
δLj=∂C∂zLj=∑k∂C∂zL+1k⋅∂zL+1k∂zlj=∑k∂zL+1k∂zLj⋅δL+1k
根据定义,我们有zL+1k∑jωL+1kj+bL+1k=∑jωL+1kjσ(zLJ)+bL+1k则可以得到∂zL+1k∂zLj=ωL+1kjσ′(zLj)
因此,δLj=∑kωL+1kjδL+1kσ′(zLj)
证毕.代价函数关于网络中任意偏差的改变率
∂C∂bLj=δLj代价函数关于任何一个权重的改变率
∂C∂ωLjk=aL−1kδLj
反向传播算法
反向传播方程给出了一种计算代价函数梯度的方法,其显示描述如下:
(1) 输入x:为输入层设置对应的激活值aL
(2) 前向传播:对每个l=2,3,...,L, 计算相应的Zl=ωl⋅al−1+bl和al=σ(zl)
(3) 输出层误差δL: 计算向量δL=∇aC⊙σ′(zL)
(4) 反向误差传播:对每个l=L−1,L−2,...,2, 计算δl=((ωl+1)Tδl+1)⊙σ′(zl)
(5) 输出: 代价函数的梯度由∂C∂ωljk=al−1k⋅δlj和∂C∂blj=δlj.反向传播代码注释
def backprop(self, x, y):
nabla_b = [np.zeros(b.shape) for b in self.biases]
nabla_w = [np.zeros(w.shape) for w in self.weights]
# 前向传播
activation = x
activations = [x] # list to store all the activations, layer by layer
zs = [] # list to store all the z vectors, layer by layer
for b, w in zip(self.biases, self.weights):
z = np.dot(w, activation)+b # weighted input
zs.append(z)
activation = sigmoid(z) $ activations
activations.append(activation)
# 反向传播
delta = self.cost_derivative(activations[-1], y) * sigmoid_prime(zs[-1]) # cost_derivative是代价函数对激活值的导数. 此处的delta是第L层的输出误差.
nabla_b[-1] = delta # 第L层的代价相对于偏置的导数
nabla_w[-1] = np.dot(delta, activations[-2].transpose()) # 第L层的代价相对于权重的导数
#进行反向传播,计算L-1,L-2,...,2的导数
for l in xrange(2, self.num_layers):
z = zs[-l] # 加权输入
sp = sigmoid_prime(z) # 加权输入的导数
delta = np.dot(self.weights[-l+1].transpose(), delta) * sp # 当前层的delta(输出误差)
nabla_b[-l] = delta #当前层的导数(相对于偏置)
nabla_w[-l] = np.dot(delta, activations[-l-1].transpose())#当前层的导数(相对于权重)
return (nabla_b, nabla_w)