《神经网络与深度学习》第一章 使用神经网络来识别手写数字(三)- 用Python代码实现

 

实现咱们分类数字的网络

 

好,让咱们使用随机梯度降低和 MNIST训练数据来写一个程序来学习怎样识别手写数字。 咱们用Python (2.7) 来实现。只有 74 行代码!咱们须要的第一个东西是 MNIST数据。若是有 github 帐号,你能够将这些代码库克隆下来,php

 

git clone https://github.com/mnielsen/neural-networks-and-deep-learning.git

 

或者你能够到这里 下载html

顺便说一下, 当我先前说到 MNIST 数据集时,我说它被分红 60,000 个训练图片,和 10,000张测试图片。这是官方的说法。实际上,咱们准备用不一样的分法。 咱们将这60,000张图片的MNIST训练数据集分红两部分:一部分有50,000 张图片,咱们用这些图片来训练咱们的神经网络,另外的10,000 张的图片用来做确认数据集,用来验证识别是否准确。 在这一章节咱们不会使用确认数据,在本系列文章的后面,咱们会发现它对于计算出怎样设置神经网络的hyper-parameters是颇有用的 - 例如学习率等等,咱们的学习算法中可能不会直接用到这些hyper-parameters。虽然确认数据不是源MNIST规格的一部分,不少人按这种方式使用MNIST,确认数据的使用在神经网络中是很常见的。当我提到"MNIST" 从如今起,它表示咱们的 50,000个图片数据集,而不是原来的 60,000 张图片数据集*早前提到的, MNIST数据集基于NIST收集的两种数据。为了构建MNIST,数据集被NIST 的Yann LeCun, Corinna Cortes和 Christopher J. C. Burges几我的拆开,放进更方便的格式 点击 此连接 查看更多详情。在我数据集中的数据集是以一种容易加载的格式出现的,而且是用Python来处理这些 MNIST 数据。我是从Montreal大学LISA 机器学习实验室 (连接)得到这些特定格式的数据的。
git

 

除了MNIST数据,咱们还须要一个Python库Numpy,用来作快速线性代数运算。若是你还没安装这个库,你能够到这里下载: heregithub

让咱们讲述一下神经网络代码的核心功能,在我给出完整清单前。核心是一个 Network 类,咱们用了表现一个神经网络。下面这些代码是用来初始化一个Network对象:算法

 

class Network(object):

    def __init__(self, sizes):
        self.num_layers = len(sizes)
        self.sizes = sizes
        self.biases = [np.random.randn(y, 1) for y in sizes[1:]]
        self.weights = [np.random.randn(y, x) 
                        for x, y in zip(sizes[:-1], sizes[1:])]

 

 

这些代码,列表的 sizes 包含各个层的神经元的数量。例如,若是咱们想建立一个第一层有有两个神经元,第二层有3个神经元,最后一层有一个神经元 的 Network对象,咱们这样设置:shell

net = Network([2, 3, 1])

 

偏移量和权重,用随机数来初始化,使用 Numpy np.random.randn函数来生成 0均值高斯分布 和标准误差1. 这个初始的随机数是为了给随机梯度降低算法一个开始点。在接下来的章节咱们会找到一种更好的方式来初始化权重和偏移量,但如今暂时用随机数。注意网络的初始化代码假定第一层是输入层,省略这些神经元偏移量的设置,由于偏移量只是用来计算下一层网络的输出值。编程

也要注意偏移量和权重以Numpy数据矩阵的方式存储。所以,例如 net.weights[1]是一个Numpy矩阵用来储存链接第二层和第三层神经网络的权重。(它不是第一次和第二层,由于Python List 是从0开始算起的)。既然 net.weights[1] 是至关冗长的,让咱们用矩阵w来表明。它是一个矩阵,wjk是权重 for the链接在第二层的第k个神经元和 在第三层的第j个神经元。 j 和k 指数的排序可能会看起来很奇怪 - 交换j和k指数会更好吗?使用这个排序的好处是它意味着第三层神经元激活变量是:数组

a=σ(wa+b(22)

这个方程有点长,让我一点一点分析。a是一个激活第二层网络的向量。为了获取a′咱们用a乘以权重矩阵w加上偏移量b的和。咱们用函数σ来算每一个wa+b。 浏览器

  

以上记住以后,很容易写出代码来计算网络的输出。我先定义S型函数开始:网络

def sigmoid(z):
    return 1.0/(1.0+np.exp(-z))

 

注意当输入 z 是一个向量或者 Numpy 数组, Numpy 自动将函数sigmoid 依次应用到数组的每一个元素,以向量化的形式。

 

咱们添加一个 feedforward 方法到 Network 类, 给神经网络一个输入 a ,返回对应的输入*。加入输入值 a 是一个 (n, 1)Numpy ndarray,不是一个 (n,) 向量。这里, n 是神经网络输入的数字。若是你尝试使用一个 (n,) 向量做为输入,你会获得一个奇怪的结果。虽然使用(n,)向量看起来是一个更天然的选择,可是使用 (n, 1) ndarray可让代码改成前馈一次性多输入更加容易, 有时候很方便。全部这些方法都是应用方程 (22) 到每一层:

 def feedforward(self, a):
        """Return the output of the network if "a" is input."""
        for b, w in zip(self.biases, self.weights):
            a = sigmoid(np.dot(w, a)+b)
        return a

 

 固然,咱们想让咱们的Network对象作得主要事情是去学习。为了达到这个目的,咱们给它们一个SGD方法,这个方法实现了随机梯度降低算法。这里是它的代码。它在有些地方有点神秘,但我会分红一个个小点来解释。

 

 def SGD(self, training_data, epochs, mini_batch_size, eta,
            test_data=None):
        """Train the neural network using mini-batch stochastic
        gradient descent.  The "training_data" is a list of tuples
        "(x, y)" representing the training inputs and the desired
        outputs.  The other non-optional parameters are
        self-explanatory.  If "test_data" is provided then the
        network will be evaluated against the test data after each
        epoch, and partial progress printed out.  This is useful for
        tracking progress, but slows things down substantially."""
        if test_data: n_test = len(test_data)
        n = len(training_data)
        for j in xrange(epochs):
            random.shuffle(training_data)
            mini_batches = [
                training_data[k:k+mini_batch_size]
                for k in xrange(0, n, mini_batch_size)]
            for mini_batch in mini_batches:
                self.update_mini_batch(mini_batch, eta)
            if test_data:
                print "Epoch {0}: {1} / {2}".format(
                    j, self.evaluate(test_data), n_test)
            else:
                print "Epoch {0} complete".format(j)

 

 

training_data 是一个元组(x, y)列表,表明训练数据输入和 相应想要的输出。变量epochs 和mini_batch_size 是你指望的 - 训练次数, 当取样时用到的最小批次。 eta是学习率,η。若是有可选的参数test_data,那么程序会在每次训练结束后评估网络,而后打印出部分进度。这对于跟着进度颇有用,但会影响训练效率,让训练进度变慢。

这段代码的做用以下。在每一个时期,它会将训练数据随机洗牌,而后分红适当的几批训练数据。这是将训练数据随机抽样的一种简单方式。而后对于每个mini_batch,咱们作一次梯度降低。这由代码self.update_mini_batch(mini_batch, eta)来完成,这段代码经过使用mini_batch的训练数据作一次随机降低循环更新网络的偏移量和权重。下面是update_mini_batch 方法的代码:

 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)

 

这句代码调用了一个叫作反向传播( backpropagation )的算法,它是一个快速计算代价函数(cost function)梯度的算法。 所以 update_mini_batch works simply 经过计算这些在 mini_batch里面的每个训练样本的梯度,而后适当地更新self.weights 和self.biases。

 

我不许备如今展现 self.backprop 的代码。 在下一个章节我会介绍反向传播怎样学习,包括 self.backprop的代码。如今,咱们假设它能表现的如它声称的那样返回恰当的训练样本x的代价Cost梯度。

让咱们看一下整个程序,包括文档注释,上面我省略了不少东西。除了self.backprop,这个程序是自解释的( self-explanatory )- 咱们上面已经提到过,全部的累活都在self.SGD和self.update_mini_batch里面给你完成好了。 self.backprop方法利用一些额外的函数来帮助计算梯度,例如sigmoid_prime方法是用来计算σ函数的导数的。还有self.cost_derivative这个方法也是 ,我就不过多描述了。你能够从代码和注释中看出大致的含义。咱们会在下一章做更加详细的解释。注意虽然程序看起来很长,大多数代码都是文档注释来的,只是为了让你更容易读懂代码。事实上,整个程序排除了空行和注释以后,只包含了74行代码 。全部代码能够在GitHub找到,点击 这里

 

 

"""
network.py
~~~~~~~~~~

A module to implement the stochastic gradient descent learning
algorithm for a feedforward neural network.  Gradients are calculated
using backpropagation.  Note that I have focused on making the code
simple, easily readable, and easily modifiable.  It is not optimized,
and omits many desirable features.
"""

#### Libraries
# Standard library
import random

# Third-party libraries
import numpy as np

class Network(object):

    def __init__(self, sizes):
        """The list ``sizes`` contains the number of neurons in the
        respective layers of the network.  For example, if the list
        was [2, 3, 1] then it would be a three-layer network, with the
        first layer containing 2 neurons, the second layer 3 neurons,
        and the third layer 1 neuron.  The biases and weights for the
        network are initialized randomly, using a Gaussian
        distribution with mean 0, and variance 1.  Note that the first
        layer is assumed to be an input layer, and by convention we
        won't set any biases for those neurons, since biases are only
        ever used in computing the outputs from later layers."""
        self.num_layers = len(sizes)
        self.sizes = sizes
        self.biases = [np.random.randn(y, 1) for y in sizes[1:]]
        self.weights = [np.random.randn(y, x)
                        for x, y in zip(sizes[:-1], sizes[1:])]

    def feedforward(self, a):
        """Return the output of the network if ``a`` is input."""
        for b, w in zip(self.biases, self.weights):
            a = sigmoid(np.dot(w, a)+b)
        return a

    def SGD(self, training_data, epochs, mini_batch_size, eta,
            test_data=None):
        """Train the neural network using mini-batch stochastic
        gradient descent.  The ``training_data`` is a list of tuples
        ``(x, y)`` representing the training inputs and the desired
        outputs.  The other non-optional parameters are
        self-explanatory.  If ``test_data`` is provided then the
        network will be evaluated against the test data after each
        epoch, and partial progress printed out.  This is useful for
        tracking progress, but slows things down substantially."""
        if test_data: n_test = len(test_data)
        n = len(training_data)
        for j in xrange(epochs):
            random.shuffle(training_data)
            mini_batches = [
                training_data[k:k+mini_batch_size]
                for k in xrange(0, n, mini_batch_size)]
            for mini_batch in mini_batches:
                self.update_mini_batch(mini_batch, eta)
            if test_data:
                print "Epoch {0}: {1} / {2}".format(
                    j, self.evaluate(test_data), n_test)
            else:
                print "Epoch {0} complete".format(j)

    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)]

    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
        delta = self.cost_derivative(activations[-1], y) * \
            sigmoid_prime(zs[-1])
        nabla_b[-1] = delta
        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 xrange(2, self.num_layers):
            z = zs[-l]
            sp = sigmoid_prime(z)
            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 evaluate(self, test_data):
        """Return the number of test inputs for which the neural
        network outputs the correct result. Note that the neural
        network's output is assumed to be the index of whichever
        neuron in the final layer has the highest activation."""
        test_results = [(np.argmax(self.feedforward(x)), y)
                        for (x, y) in test_data]
        return sum(int(x == y) for (x, y) in test_results)

    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))

 

 

这个程序识别手写数字的效果有多好?让咱们先加载MNIST训练数据。我用一个工具程序来帮忙加载,它是 mnist_loader.py,下面介绍一下它。咱们在Python shell命令行中输入下面的命令:

 

>>> import mnist_loader
>>> training_data, validation_data, test_data = \
... mnist_loader.load_data_wrapper()

 

 

固然,这些能够用其它的Python程序来完成,但在 Python shell中执行多是最容易的方法。

加载了 MNIST 数据以后,咱们在导入network模块, 用30个隐藏的神经元来搭建网络。

 

>>> import network
>>> net = network.Network([784, 30, 10])

 

 

最后,咱们会使用随机梯度降低来学习。用 MNIST training_data 训练30次, mini-batch是10,学习率为η=3.0,

 

>>> net.SGD(training_data, 30, 10, 3.0, test_data=test_data)

 

 

注意若是你运行上面的代码,可能会花一点时间来执行 - 通常的电脑 (2015年时期) 会可能花几分钟来运行。我建议你先用程序代码跑一遍再继续往下看,按期检查一下代码的输出。若是你时间仓促,你能够经过减小训练次数,或者减小隐藏神经元的数量,又或者只使用小部分训练数据来加快程序运行。 注意实际生产环境的代码会快不少:这些Python脚本旨在帮助你理解神经网络的工做原理,并非高性能的代码!固然一旦你完成了网络的训练,它几乎在全部计算平台都会运行得很是快。例如咱们一旦的网络训练好了权重和偏移量, 它能够很容易移植到浏览器上的网页用Javascript来运行,或者移动设备的本地app。不管如何,这里只是神经网络训练输出的代码副本。这个副本展现了测试图片在每一个训练周期内能够被正确地识别。如你所见,单单一个训练周期就能识别10,000张图片中 9,129张图片,数量还会继续增加。

 

Epoch 0: 9129 / 10000
Epoch 1: 9295 / 10000
Epoch 2: 9348 / 10000
...
Epoch 27: 9528 / 10000
Epoch 28: 9542 / 10000
Epoch 29: 9534 / 10000

 

 

跟进上面的训练结果,能够看到训练后的神经网络的分类率classification rate大概是95% - 在第28次训练的时候达到峰值95.42% ! 第一次尝试使用神经网络就能获得这样的效果是否是很受鼓舞。我应该警告你,然而若是你本身运行代码的时候没有必要让训练结果和个人如出一辙,由于咱们用了随机的权重和偏移量来初始化咱们的网络,我运行的时候和你运行的时候初始值通常状况是不一样的。并且为了节目效果,上面看到的结果实际上是我重复搞了三次选出的最好的结果。

让咱们从新运行上面的试验,将隐藏神经元的数量改为100。 正如上面提到程序运行会花很多时间 (在个人机器上每一个训练周期( epoch)花了几十秒),因此在代码执行的时候你还有空一边继续阅读。 

>>> net = network.Network([784, 100, 10])
>>> net.SGD(training_data, 30, 10, 3.0, test_data=test_data)

 

果真,改善后的结果是96.5996.59%。 至少在这种状况,使用更多隐藏神经元帮助咱们得到了更好的结果*读者反馈的效果各不相同,有些人训练的结果可能更糟。使用第三章的技术以后会减小这些差别。

固然,为了得到这些准确性,我必须调整各类训练的参数,例如训练次数,最新批次the mini-batch size,和学习率 the learning rate  η。 正如我上面提到的,这些就是所谓的区别于普通的参数 (权重和偏移量)的神经网络hyper-parameters 。若是hyper-parameters选的很差,咱们会获得较差的结果。例如假定咱们将学习率设置为η=0.001

 

>>> net = network.Network([784, 100, 10])
>>> net.SGD(training_data, 30, 10, 0.001, test_data=test_data)

 

 

结果就很不理想

Epoch 0: 1139 / 10000
Epoch 1: 1136 / 10000
Epoch 2: 1135 / 10000
...
Epoch 27: 2101 / 10000
Epoch 28: 2123 / 10000
Epoch 29: 2142 / 10000

 

你能够看到网络的性能增加像蜗牛同样慢。建议你要增长学习率,例如改为 η=0.01吧。改了学习率,就能得到更好的效果了,若是增大学习率有效,多增长几回试试,最后发现学习率为 η=1.0的效果最佳,若是利用别人训练好的模型( fine tune)来学习,可能要将学习率设为3.0。所以即便咱们选择一个非最佳的hyper-parameters,也不要紧,只是咱们能够知道怎么去改进hyper-parameters参数的设置。

 

总的来讲,调试一个神经网络多是一项挑战,尤为是当初始hyper-parameters参数的结果比随机的噪音产生的结果要差的时候。假如咱们30个神经元的网络设置学习率为η=100.0:

>>> net = network.Network([784, 30, 10])
>>> net.SGD(training_data, 30, 10, 100.0, test_data=test_data)

 

此次咱们走得太远了,学习率过高了:

Epoch 0: 1009 / 10000
Epoch 1: 1009 / 10000
Epoch 2: 1009 / 10000
Epoch 3: 1009 / 10000
...
Epoch 27: 982 / 10000
Epoch 28: 982 / 10000
Epoch 29: 982 / 10000

 如今想象一下,咱们第一次遇到这种问题。固然,咱们根据以前的试验将 学习率下降才是正确的。但若是第一次遇到这种问题,咱们没法根据输出结果获知怎么调整参数。咱们可能不只单选学习率,还担忧神经网络其它方面的参数。咱们可能会疑惑是否权重和偏移量的初始值使神经网络难以训练?或者咱们没有足够的训练数据来进行有意义的学习?仍是没有足够的训练次数?或者这种架构的神经网络不可能适用于识别手写数字?学习率定得过低或者过高?当你第一次遇到问题,你不肯定是什么缘由致使的。

 

这节内容以调试神经网络结束,调试神经网络并非小事,像编程同样重要,是一门艺术。你须要学会经过调试来使神经网络得到良好的输出结果。通常来讲咱们须要提升选择合适的 hyper-parameters 和好架构的探索能力。做者的整本书都会讨论这些,包括怎样选择合适的hyper-parameters。

 

练习

 

 

  • 尝试创建一个只有两层的神经网络 - 只有输入和输出层,没有隐藏层 - 输入层784个神经元,输出层10 个神经元,respectively. 用随机梯度降低来训练这个网络。看看你能达到怎样的分类精度?

 

早前,我跳过了,没有解释怎样加载MNIST数据。很直接,为了完整一点,我给出了代码。用来存储MNIST 的数据结构在代码注释中说的很清楚了- 很直接了当的东西。 Numpy ndarray 对象的元组和列表 (若是你熟悉 ndarray,把它们想象成向量):

 

"""
mnist_loader
~~~~~~~~~~~~

A library to load the MNIST image data.  For details of the data
structures that are returned, see the doc strings for ``load_data``
and ``load_data_wrapper``.  In practice, ``load_data_wrapper`` is the
function usually called by our neural network code.
"""

#### Libraries
# Standard library
import cPickle
import gzip

# Third-party libraries
import numpy as np

def load_data():
    """Return the MNIST data as a tuple containing the training data,
    the validation data, and the test data.

    The ``training_data`` is returned as a tuple with two entries.
    The first entry contains the actual training images.  This is a
    numpy ndarray with 50,000 entries.  Each entry is, in turn, a
    numpy ndarray with 784 values, representing the 28 * 28 = 784
    pixels in a single MNIST image.

    The second entry in the ``training_data`` tuple is a numpy ndarray
    containing 50,000 entries.  Those entries are just the digit
    values (0...9) for the corresponding images contained in the first
    entry of the tuple.

    The ``validation_data`` and ``test_data`` are similar, except
    each contains only 10,000 images.

    This is a nice data format, but for use in neural networks it's
    helpful to modify the format of the ``training_data`` a little.
    That's done in the wrapper function ``load_data_wrapper()``, see
    below.
    """
    f = gzip.open('../data/mnist.pkl.gz', 'rb')
    training_data, validation_data, test_data = cPickle.load(f)
    f.close()
    return (training_data, validation_data, test_data)

def load_data_wrapper():
    """Return a tuple containing ``(training_data, validation_data,
    test_data)``. Based on ``load_data``, but the format is more
    convenient for use in our implementation of neural networks.

    In particular, ``training_data`` is a list containing 50,000
    2-tuples ``(x, y)``.  ``x`` is a 784-dimensional numpy.ndarray
    containing the input image.  ``y`` is a 10-dimensional
    numpy.ndarray representing the unit vector corresponding to the
    correct digit for ``x``.

    ``validation_data`` and ``test_data`` are lists containing 10,000
    2-tuples ``(x, y)``.  In each case, ``x`` is a 784-dimensional
    numpy.ndarry containing the input image, and ``y`` is the
    corresponding classification, i.e., the digit values (integers)
    corresponding to ``x``.

    Obviously, this means we're using slightly different formats for
    the training data and the validation / test data.  These formats
    turn out to be the most convenient for use in our neural network
    code."""
    tr_d, va_d, te_d = load_data()
    training_inputs = [np.reshape(x, (784, 1)) for x in tr_d[0]]
    training_results = [vectorized_result(y) for y in tr_d[1]]
    training_data = zip(training_inputs, training_results)
    validation_inputs = [np.reshape(x, (784, 1)) for x in va_d[0]]
    validation_data = zip(validation_inputs, va_d[1])
    test_inputs = [np.reshape(x, (784, 1)) for x in te_d[0]]
    test_data = zip(test_inputs, te_d[1])
    return (training_data, validation_data, test_data)

def vectorized_result(j):
    """Return a 10-dimensional unit vector with a 1.0 in the jth
    position and zeroes elsewhere.  This is used to convert a digit
    (0...9) into a corresponding desired output from the neural
    network."""
    e = np.zeros((10, 1))
    e[j] = 1.0
    return e
 

  

上面我说过咱们的程序得到了很好的结果。是什么意思呢?这个好是跟什么比较?用一下简单的 (非神经网络的) 基准测试来做比较,才能明白这个好是什么意思。这个基准测试固然是随机猜数字。随机猜中的准确度是10%。咱们用另一种方法来稍微提升一下准确度。

有没有更简单易懂的基准呢?让咱们来尝试一个很是简单的想法:比较图片灰度。例如,一个2的图片会比1的图片黑,由于2的黑点比较多。 以下图:

 

 

建议试用训练数据来计算每一个像素的平均灰度0,1,2,…,9。 当出现一个新图片,咱们计算这个图片到底有多黑,而后猜想最接近哪一个数字的平均灰度。这是一个简单的过程,代码也容易实现,全部我就不给出明确的代码了 - 若是你感兴趣就到GitHub去看 GitHub repository。 这是对随机猜想的改善,若是10,000次测试有2,225次正确,那么精度就为22.25%。

用上面的方法实现精度达20%到50%之间并不难。若是你努力一点能够超过50%。可是要得到更高的进度就有借助于机器学习算法了。 让咱们使用一个著名的算法 support vector machine 简称 SVM算法。若是你不熟悉SVM,不用担忧,咱们不用了解算法的细节,咱们直接用实现了Python接口的C语言类库 scikit-learn,里面提供了SVM的具体算法实现 LIBSVM

若是你使用默认设置运行scikit-learn的 SVM 分类器,精度大概是94.35% (代码在这里 here) 比起上面的利用灰度来分类有天大的改善。事实上这里的 SVM 的性能比神经网络稍微查一点。在后面的一章咱们会引进一种新的技术来改善神经网络,让它的性能比SVM出色。

然而,这不是故事的结尾。94.35%这个结果scikit-learn的SVM默认设置时的性能。 SVM有一大堆可调的参数,有可能找到一些参数来提升性能。我不会明确地去作这件事,看这里由 Andreas Mueller写的 这篇博客 若是你想了解更多。Mueller给咱们演示了经过一些方法来优化SVM的参数,能够将精度提升到98.5%。换句话讲,一个好的可调的SVM出错率大0七十分之一。这很是厉害!神经网络能作得更好吗?

事实上,神经网络能够作得更好。如今,一个设计良好的神经网络处理MNIST数据方面的精度比其它算法要好,包括SVM。 当前时间 (2013年)的记录的分类的精度达到99.79%( 9,979/10,000)。这是 Li WanMatthew Zeiler, Sixin Zhang, Yann LeCun, 和Rob Fergus作到的。在本书的后面,咱们会看到他们使用的大多数技术。这个水平的性能已经接近人类的水平了,甚至可能比人类还好一点,由于有少许的MNIST图片甚至人类都没有信心识别出来,例如:

 

 

我相信你会赞成上面这些图片很难区分! 上面这些MNIST图片, 21 张这样的图片放在10,000图片中神经网络能准确地识别出来。一般,编程的时候咱们相信解决一个诸如识别MNIST图片数字须要一种深奥的算法。但关于咱们在本章节看到算法原型,即便在Wan et al 论文中也提到神经网络仅涉及一种很是简单的算法。全部的复杂性在于神经网络从训练数据中自动学习。在某种意义上,咱们实现的神经网络和其它更深奥的论文是为了解决如下问题:

深奥的算法 ≤ 简单的学习算法 + 好的训练数据(三个臭皮匠顶一个诸葛亮)

 

 

向深度学习迈进

 译者注:最后翻译进度的时间是:2017-01-11 00:41,我会继续往下翻译的:

咱们的神经网络的性能使人印象深入,性能有点神秘。权重和偏移会自动调整。这意味着咱们不能一会儿解释出神经网络是怎样作到的。咱们能够找到一些方法类理解咱们的神经网络怎样分类手写数字的法则吗?若是有一些法则咱们会作得更好吗?

为了使这个问题更加分明,假定几十年后神经网络致使人工智能(AI)出现了。咱们能够知道这种智能地神经网络是怎样工做的吗?或许网络对咱们来讲是透明的,权重偏移量咱们不能理解,由于他们自主学习了。早些时候的AI研究,人们但愿创建AI的努力能够帮助咱们理解智能背后的法则和人类大脑的运行机理。最后结果多是咱们既不了解大脑的运行机制也不知道人工智能怎么工做!

为了解决这个问题,让咱们回想一下我再第一章开头提到的人工神经元的解释,衡量证据的一种手段。假如咱们想判断一张图片是不是人脸:

  

咱们能够用手写数字识别的相同方法类解决这个问题 - 使用图片中的像素做为神经网络的输入,一个神经元输入"是的这是一张脸" 或者 "不是,这不是脸"(这翻译有点硬)

让咱们假设咱们来作这件事,但咱们不使用现有的学习算法。咱们准备尝试手动设计一个网络,选择合适的权重和偏移量。咱们应该怎么作? 先把神经网络的概念彻底忘掉,咱们能够将问题分解成一个个小问题:图片左上角有没有一个眼睛?右上角有没有一个眼睛?中间有鼻子吗?下边中间有没有一个嘴巴等等。

如上上面的问题的答案是 "yes",或者极可能是"yes",那么咱们认为这张图片极可能是一张脸。相反,若是大多数答案都是 "no",那么图片极可能不是一张脸。

固然这只是一个粗暴的思惟探索,有不少缺陷。也许这我的是光头,所以没有头发。也许咱们只能看到半张脸,或者脸的某个角度,所以不少面部特征模糊不清。但这个思惟探索代表若是咱们用神经网络来解答这些子问题,经过这些子问题组合造成的网络,那么极可能咱们能够创建一个用于脸部识别的神经网络。这是大概的架构,用矩形来表明子网络。注意这不是一个解决面部识别问题的现实中应用的方法;只是一个帮助咱们创建神经网络直觉。这是架构图:

 

 

子网络貌似也能够分解。假如咱们考虑一个问题:"左上角有一个眼睛吗?" 这个问题能够分解为:"是否有眼珠?"; "是否有眼睫毛?"; "是否有虹膜?";以及其它等等。固然这些问题也真的包含位置信息 - "眼珠在左上方,在睫毛的上面?", 诸如此类- 但咱们为了保持简单。网络只分解一个问题, "左上方是否眼睛?" 如今能够分解成:

 

 

这些问题能够经过多层网络一步步分解。最后咱们子网络能够回答到能从像素级别的回答的问题。这些问题可能,例如在图片的特定的点上的很是简单的形状。这些问题能够用一个链接到图片像素的神经元来回答。

最后的结果是一个复杂问题的网络 - (用来判断图片是不是一张脸的网络) - 分解成一个个能在单个像素级别回答的很是简单的问题。它经过分红不少层来分解问题。前几层回答图片输入的特定的简单问题,后面的层创建更复杂和抽象的概念。这种多层结构的网络 - 有两个或者更多的隐藏层 - 被叫作深度神经网络。 

固然,我没有说过怎样递归分解成子网络。固然不是手工来设计权重和偏移量,咱们用学习算法来搞,这样网络就能够从训练数据中自动学习调整权重和偏移量了。研究人员在1980和1990年代尝试使用随机梯度降低和反向传播算法来训练深度网络。不幸的是,除了少许特殊的架构,其它的就没有那么幸运得出心仪的结果。 网络会学习,可是太慢,在实践中没有多大做用。

2006年以来,一系列可用的深度学习神经网络的新技术被开发出来。这些深度学习技术也是基于随机梯度降低算法和反向传播算法的,但也引入了新的思想。 这些技术可以训练更深更大型的网络 - 人们如今一般能训练有5到10个隐藏层的网络,性能比原来的浅层网络(例如只有一个隐藏层的网络)要好不少。理由固然是深度网络的能力能创建复杂的概念。这有点像传统的编程语言使用模块化设计思想来抽象来构造一个复杂的程序。对比深度网络和浅层网络有点像对比有函数封装概念和没有函数概念的编程语言。固然神经网络的抽象和传统编程的抽象是不一样的,只是想说明抽象真的很是重要。

译者注:至此全部浅层神经网络部分翻译都完成了,翻译完成的时间是:2017-01-22 23:35,接下来将会翻译有关深度神经网络和深度学习方面的知识,敬请期待。因为没太多时间,可能会有翻译不通顺,错别字等状况,请见谅,后面我会逐步回头检查修正,请见谅!

相关文章
相关标签/搜索