说明:1.5讲的是梯度下降法,之前笔者对其已经有了总结,故此不再翻译学习啦。
原文地址:http://neuralnetworksanddeeplearning.com/chap1.html#a_simple_network_to_classify_handwritten_digits
好吧,让我们编写一个程序,学习如何识别手写的数字,使用随机梯度下降法和MNIST训练数据。点击下载训练数据我们将做一个简短的Python(2.7)程序,只是74行代码!我们需要做的第一件事是获取MNIST数据。如果你是一个git用户就可以通过克隆获得的数据的代码库这本书,
git clone https://github.com/mnielsen/neural-networks-and-deep-learning.git
如果你不使用Git,那么请从这里下载。
顺便说一下,当我描述MNIST数据前,我说这是分成60000训练图像,和10000个测试图像。这是官方MNIST描述。实际上,我们分割数据稍有不同。我们将不使用测试数据,将60000个图像MNIST训练集分割成两个部分:一组50000张图片(用其训练神经网络)和一个单独的10000图像验证集。在这一章我们不会使用验证的数据,但后来在书中我们会发现它在搞清楚如何设置神经网络的某些hyper-parameters是有用的——诸如学习速率等等,这不是直接由我们的学习算法决定。尽管验证数据不是原始MNIST规范的一部分,但是许多人还是这样使用和验证数据的使用的方式在神经网络中很常见。从现在开始,当我提到“MNIST训练数据”,指的是50000个的图像数据集,不是原来的60000图像数据集。
除了MNIST数据我们还需要一个叫做Numpy的Python库,进行快速线性代数。如果你还没有安装了Numpy,你可以在这里得到它(点击下载)。
在给出完整列表之前,让我先解释下神经网络的核心代码。核心是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:])]
在这段代码中,list类型的sizes 包含了每一层神经元的个数。举个例子,如果你想创建一个第一层有2个神经元,第二层有3个神经元,最后一层有1个神经元的网络,那么我们将这样编写代码:
net = Network([2, 3, 1])
神经网络的偏差和权重在初始化的时候,是随机的,使用Numpy的np.random.randn函数去随机产生一个满足0~1之间的数(按照高斯分布产生)。这个随机的初始化给了我们一个随机梯度下降算法的起始点。在后面的章节我们将会有更好的方式来初始化权重和偏差,但现在不用那种方式。注意网络的初始化代码假定网络的第一层是输入层,并且省去了在那些神经元上设置偏差。因为偏差只用于输出层的计算。
也需要注意的是,偏差和权重以lists被保存在Numpy的矩阵中。因此举个例子,net.weights[1]是保存着连接着第二层和第三层权重的Numpy矩阵(它不是第一层和第二层,因为Pyhon的list坐标从0开始)。用net.weights[1]是相当啰嗦的,让我们将其定义为矩阵 w 。比如矩阵 wjk 代表着第二层第 k 个神经元到第三层第 j 个神经元的权重。 j 和 k 的顺序看起来可能有点奇怪-是不是应该交换 j 和 k 的顺序?使用这种排序最大的优势是,它意味着向量第三层的神经元的激活:
a′=σ(wa+b) (22)
有相当一部分符号在这个方程中,所以我们把它一块一块分割开。 a 是第二层神经元的激活。为了获得 a′ 我们用 a 乘以权重矩阵 w ,并且加上偏差矩阵 b 。我们应用函数 σ 作用于每一个 wa+b 的实体上。很好验证计算神经元输出的方程(22)给了我们和方程(4)( 11+exp(−∑jwjxj−b) )相同的结果。
将方程(22)写成构件形式,并且验证它给出了和方程(4)一样结果的证明。
记住这一切,很容易编写代码计算从网络实例的输出。我们通过定义的Sigmoid函数开始:
def sigmoid(z):
return 1.0/(1.0+np.exp(-z))
注意,当输入 z 是一个向量或是Numpy数组时,Numpy会自动对每个元素应用sigmoid函数的,也就是说,以向量化的形式。
然后我们给Network类添加一个前馈方法,即给一个网络的输入 a ,返回一个对应的输出。所有的方法都是适用于每一层的方程(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是训练输入和对应的渴望的输出,以tuples (x,y)的list集合的形式。变量 epochs和mini_batch_size是我们所希望的-训练的epochs的数量和mini_batches的大小当sampling.eta是训练率 η 。如果优化参数test_data提供了,那么程序将在所有的epoch训练结束后评估网络并且打印进度。这是用于跟踪进度,但是会使得进度放缓。
代码工作如下。在每一个时期,它通过随机打乱训练数据,然后分区成mini_batches适当的大小。这是一个简单的方法从训练数据随机抽样。然后对每一个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)
这个会反射到一个被称之为反向传播的算法,该算法是一个快速的方法用于计算成本函数的梯度。因此update_mini_batch工作简单,对于每个mini_batch中训练样本计算梯度,并且适当更新self.weights和self.biases。
现在我不会展示self_backprop的代码。我们将在下一章研究反向传播是如何工作的,以及self_backprop的代码。 现在,假设它的行为作为声明,对于训练样本x的成本返回一个适当的梯度。
让我们来看下全部的程序,包括上面我删除掉的文本注释。除了在self_SGD中的self_backprop代码以及我们讨论过的self_update_mini_batch代码。self_backprop方法使用了一些其它的函数去计算梯度,即sigmoid_prime,它用于计算 σ 函数的导数,这里我们也不描述。通过查看代码和文档你可以得到这些的要点(也许细节)。我们将在下一章详细看他们。注意,虽然程序显得冗长,大部分的代码文档字符串旨在使代码易于理解。事实上,程序只包含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数据后,我们将建立包含30个隐藏神经元的网络。我们将在导入Network后,执行程序。如下。
>>> import network
>>> net = network.Network([784, 30, 10])
最后,我们使用30个epoch,mini_batch大小为10,并且训练率 η 为3.0来随机梯度下降来学习MNIST训练数据。
>>> net.SGD(training_data, 30, 10, 3.0, test_data=test_data)
注意,如果你是边看边执行程序,那么将会花费一些时间去执行-对于普通的机器你可能会花费几分钟的时间去执行。我建议你执行后,继续阅读,并且间隔性的查看代码的输出结果。如果你赶时间你可以加快速度减少时代的数量,减少隐藏神经元的数量,或只使用训练数据的一部分。注意代码产生的会更多,快得多:这些Python脚本的目的是帮助您理解神经网络工作,不是高性能代码!并且,当然,一旦我们训练了一个网络,在几乎所有的计算平台,它都可以非常迅速地运行。例如,一旦我们学到一套好的重量和偏见对一个网络,它可以很容易地移植到Javascript在web浏览器中运行,或者移动设备上的本地应用。在任何情况下,这是一部分记录的神经网络训练运行的输出。记录显示在每个epoch中正确识别的数量。然后后就可以看到,第一个epoch已经达到了9129/10000,并且持续增长。
Epoch 0: 9129 / 10000
Epoch 1: 9295 / 10000
Epoch 2: 9348 / 10000
...
Epoch 27: 9528 / 10000
Epoch 28: 9542 / 10000
Epoch 29: 9534 / 10000
也就是说,训练的神经网络识别率大约是95%-95.42是最大峰值!这给予我们第一次尝试一个大的鼓励。然而,我需要提醒你,如果你运行代码的结果和我的不完全一样,可能是因为我们初始化网络使用(不同的)随机权重和偏见造成的。我们用最好的三个结果生成最后的结果(To generate results in this chapter I’ve taken best-of-three runs,感觉翻译不太对啊)。
让我们改变隐藏层的神经元数量后,重新运行上面实验。正如前面的情况,应该提醒你的是,应该执行程序的同时继续阅读,因为实验将进行几分钟的时间。
>>> net = network.Network([784, 100, 10])
>>> net.SGD(training_data, 30, 10, 3.0, test_data=test_data)
足够明确的,这个将结果的正确性提高到了96.59%。至少在这种情况下,使用更多的隐藏层神经元帮助我们得到更好的结果。
当然,为了获得更精确的结果我们尝试不同数量的训练epochs、mini-batch大小以及学习速度 η 。正如上面提到的那样,这些别称为我们神经网络的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(或者调整到3.0),这个很接近于我们最先的实验。所以即使我们最初的hyper-parameters不是一个好的选择,我们至少有足够的信息来帮助我们改善我们的hyper-parameters的选择。
一般来说,调试一个神经网络是很有挑战性的。特别是当初始选择hyper-parameters产生结果不比随机噪声好。假设我们之前使用的30个隐藏神经元和学习速度 η=100 :
>>> 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
现在想象一下,我们是第一次来这个问题。当然,我们从早期的实验中知道,正确的方法是降低学习速度。但是,如果我们是第一次来这个问题,那么就不会有太多的输出,以指导我们做什么。我们可能不只是担心学习速度,而是我们的神经网络的每一个方面。我们可能不知道,是否是因为初始化的权重和偏见的方式,使得它很难网络学习?或者也许我们没有足够的训练数据来获得有意义的学习?也许我们还没足够的epotch?或可能是不可能的用神经网络的架构来学习识别手写数字?也许学习率太低了?或者,也许,学习率太高了?当你第一次来到一个问题的时候,你总是不确定。
从这一过程得到的教训是,调试神经网络不仅仅是一门普通的编程,它也是一门艺术。你需要学会更加艺术的调试,以获得神经网络良好的结果。更一般地说,我们需要开发启发式来选择好的hyper-parameters和一个良好的架构。最后通过这本书,我们将讨论所有这些,包括如何选择hyper-parameters。
试着建立一个仅仅包含2层的神经网络-一个输入层和一个输出层,没有隐藏层-分别有784和10个神经元。用随机梯度下降训练神经网络。你可以训练到什么样精确的网络?
早之前,我跳过了MNIST数据的加载细节。这是很直接的,下面的完整的代码。简单明了的,使用元组和列表的Numpy ndarray对象(认为如果你不熟悉ndarrays向量)的数据结构来存储MNIST数据文档中描述的字符串:
""" 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具有更多的黑像素,只因为更多的像素被变黑了,因为下面的例子说明:
这表明,使用训练数据来计算0,1,2,…,9的平均黑暗,当出现一个新的图像,我们计算的图像是多么的黑暗,然后猜测,这是任何一个数字有最接近平均的黑暗。这是一个简单的程序,并且很容易实现如果你有兴趣,可以从Github上获取到代码(点击获取)对于随机猜测,这是一个巨大的进步,得到2225的10000测试图像正确,即。22.25%的准确率。
不难发现其他想法实现精度在20 - 50%的范围内。如果你工作有点困难你可以得到50%以上。但它有助于获得更高精度使用机器学习算法。要找到20到百分之50个范围内的准确度的其他想法并不难。如果你更努力地工作,你可以得到高达百分之50。但要获得更高的精度,可以使用机器学习算法。让我们试着用一个最著名的算法,支持向量机。如果你不熟悉支持向量机,别担心,我们不需要了解如何支持向量机的工作细节。相反,我们将使用一个Python库即scikit-learn,它提供了一种基于C库的支持向量机称为一个LIBSVM。
如果我们使用默认设置运行scikit-learn的SVM分类器,然后可以得到9435/10000测试图像的正确(代码可以从这里获取)。这是我们天真的基于它是多么黑暗的图像分类方法的一大改进。在后面的章节中,我们将介绍新的技术,使我们能够提高我们的神经网络,使他们的表现远远优于支持向量机。
然而,这并不是故事的结尾。9435/10000的结果是scikit-learn支持向量机的默认设置。支持向量机有一些可调参数,通过修改参数可以提高性能。我不会说如何做,可以参考Andreas Mueller的博文。Mueller展示了修改参数可以使得SVM的准确率达到98.5%。换句话说,70个数字,只会识别错1个。那是相当好的!神经网络能做得更好吗?
事实上,他们可以。目前精心设计的神经网络比包括支持向量机等其他技术解决MINST可以得到更好的结果。目前的世界纪录(2013年)可以达到9979/10000。这是由Li Wan, Matthew Zeiler, Sixin Zhang, Yann LeCun, and Rob Fergus完成的。我们将在书中看到他们使用的大部分技术。在这一水平的性能接近于人类,并可能更好,因为相当多的MNIST图像很难。例如:
我相信你同意这些数字是多么难被识别!MNIST数据集中的这21数字(10000-9979=21)是引人注目的。通常,编程时我们相信解决一个复杂的问题,比如识别MNIST数字需要复杂的算法。但是即使在Wan et的文献中神经网络中,也仅仅提到了几个简单的算法,在本章我们将看到变化。所有的复杂性都是从训练数据中学习的,自动的。我们的结果和一些其他的文献中,都是这样的观点:
sophisticated algorithm ≤
simple learning algorithm + good training data
PS:总算翻译完了这一段,有很多地方翻译的不对,毕竟还在学习过程中,对于神经网络并不是很懂,后面再回来读一遍原文,再修改!