关于代价函数的假设:
(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)