[神经网络这次真的搞懂了!] (8) 使用神经网络识别手写数字

2021-10-21  本文已影响0人  砥砺前行的人

英文原文:http://neuralnetworksanddeeplearning.com/
对原文的表达有部分改动

反向传播方程为我们提供了一种计算代价函数梯度的方法。让我们以算法的形式总结一下:

  1. 输入 x:设置输入层 a^1
  2. 前馈:对于每个 l=2,3,…,L 计算 z^l=w^la^{l−1}+b^la^l=σ(z^l)
  3. 输出误差 δ^L:计算向量 δ^L=∇_aC⊙σ^′(z^L)
  4. 反向传播误差:对于每个 l=L−1,L−2,…,2 计算 δ^l=((w^{l+1})^Tδ^{l+1})⊙σ^′(z^l)
  5. 输出:代价函数的梯度由 \frac {∂C}{∂w^l_{jk}}=a^{l−1}_kδ^l_j\frac {∂C}{∂b^l_j} = δ^l_j 给出。

了解上述的算法,您会明白为什么它被称为反向传播。我们从最后一层开始往回计算误差向量 δ^l。逐步了解了代价如何随早期权重和偏差而变化,通过往前追溯以获得可用的表达式。

正如我上面所描述的,反向传播算法计算单个训练示例的代价函数的梯度,C=C_x。在实践中,通常将反向传播与随机梯度下降等学习算法相结合,在这种算法中,我们为许多训练示例计算梯度。特别是,给定 mini-batch m 训练示例,以下算法应用基于该 mini-batch 的梯度下降学习步骤:

  1. 输入训练样本中的一组
  2. 对于每个训练样例 x:设置对应的输入激活 a^{x,1},执行以下步骤:
  1. 梯度下降:对于每个 l=L,L−1,…,2 根据规则更新权重 w^l→w^l−\frac {η} {m}\sum_xδ^{x,l}(a^{x,l−1})^T ,以及根据规则 更新偏差b^l→b^l−\frac {η} {m}\sum_xδ^{x,l} 的。

反向传播的代码

在抽象地理解了反向传播之后,我们现在可以理解上一章中用于实现反向传播的代码。回忆那一章,代码包含在 Network 类的 update_mini_batch 和 backprop 方法中。这些方法的代码是上述算法的直接翻译。特别是,update_mini_batch 方法通过计算当前 mini_batch 训练示例的梯度来更新网络的权重和偏差:

class Network(object):
    def update_mini_batch(self, mini_batch, eta):
        """Update the network's weights and biases by applying
        gradient descent using backpropagation to a single mini batch.
        The ``mini_batch`` is a list of tuples ``(x, y)``, and ``eta``
        is the learning rate."""
        nabla_b = [np.zeros(b.shape) for b in self.biases]
        nabla_w = [np.zeros(w.shape) for w in self.weights]
        for x, y in mini_batch:
            delta_nabla_b, delta_nabla_w = self.backprop(x, y)
            nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)]
            nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)]
        self.weights = [w-(eta/len(mini_batch))*nw
                        for w, nw in zip(self.weights, nabla_w)]
        self.biases = [b-(eta/len(mini_batch))*nb
                       for b, nb in zip(self.biases, nabla_b)]

反向传播的大部分工作由 delta_nabla_b, delta_nabla_w = self.backprop(x, y) 完成,它使用反向传播方法计算偏导数\frac {∂C_x}{∂b^l_j}\frac {∂C_x}{∂w^l_{jk}}。反向传播方法紧密遵循上一节中的算法。有一个小的变化 - 我们使用稍微不同的方法来索引层。进行此更改是为了利用 Python 的一个特性,即使用列表负数索引从列表的末尾往前计数,例如,l[-3] 是列表 l 中的倒数第三个条目。反向传播的代码如下,以及一些辅助函数,用于计算 σ 函数、导数 σ^′ 和代价函数的导数。有了这些内容,您应该能够以一种自包含的方式理解代码。

class Network(object):
    def backprop(self, x, y):
        """Return a tuple ``(nabla_b, nabla_w)`` representing the
        gradient for the cost function C_x.  ``nabla_b`` and
        ``nabla_w`` are layer-by-layer lists of numpy arrays, similar
        to ``self.biases`` and ``self.weights``."""
        nabla_b = [np.zeros(b.shape) for b in self.biases]
        nabla_w = [np.zeros(w.shape) for w in self.weights]
        # feedforward
        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
            zs.append(z)
            activation = sigmoid(z)
            activations.append(activation)
        # backward pass
        # BP1
        delta = self.cost_derivative(activations[-1], y) * \
            sigmoid_prime(zs[-1])
        # BP3
        nabla_b[-1] = delta
        # BP4
        nabla_w[-1] = np.dot(delta, activations[-2].transpose())
        # Note that the variable l in the loop below is used a little
        # differently to the notation in Chapter 2 of the book.  Here,
        # l = 1 means the last layer of neurons, l = 2 is the
        # second-last layer, and so on.  It's a renumbering of the
        # scheme in the book, used here to take advantage of the fact
        # that Python can use negative indices in lists.
        for l in range(2, self.num_layers):
            z = zs[-l]
            sp = sigmoid_prime(z)
            # BP2
            delta = np.dot(self.weights[-l+1].transpose(), delta) * sp
            nabla_b[-l] = delta
            nabla_w[-l] = np.dot(delta, activations[-l-1].transpose())
        return (nabla_b, nabla_w)

    def cost_derivative(self, output_activations, y):
        """Return the vector of partial derivatives \partial C_x /
        \partial a for the output activations."""
        return (output_activations-y)

#### Miscellaneous functions
def sigmoid(z):
    """The sigmoid function."""
    return 1.0/(1.0+np.exp(-z))

def sigmoid_prime(z):
    """Derivative of the sigmoid function."""
    return sigmoid(z)*(1-sigmoid(z))
  1. Network.cost_derivative 是代价函数的代数形式,因为代价函数为 C = \frac {1}{2} (y - a^L)^2,其导数形式为 \frac {∂C}{∂a^L} = a^L - y
  2. sigmoid 是激活函数的代码实现,可以支持向量参数
  3. sigmoid_prime 是 sigmoid 函数的导数,\frac {∂σ}{∂z} = σ(z)(1-σ(z))
上一篇下一篇

猜你喜欢

热点阅读