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

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

作者: 砥砺前行的人 | 来源:发表于2021-10-21 11:36 被阅读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},执行以下步骤:
    • 前馈:对于每个 l=2,3,…,L 计算 z^{x,l}=w^la^{x,l−1}+b^la^{x,l}=σ(z^{x,l})
    • 输出误差δ^{x,L}:计算向量δ^{x,L}=∇_aC_x⊙σ^′(z^{x,L})
    • 反向传播误差:对于每个 l=L−1,L−2,…,2 计算 δ^{x,l}=((w^{l+1})^Tδ^{x,l+1})⊙σ^′(z^{x,l})
    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))

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/kwffaltx.html