读书截止时间:2019.04.04
读书次数:1次
- 1. CHAPTER 1 Using neural nets to recognize handwritten digits
1 CHAPTER 1 Using neural nets to recognize handwritten digits
1.1 感知机(Perceptrons)
使用的主要神经元模型是一个称为sigmoid神经元
1.1.1 感知机如何工作
一个感知器可以有多个二进制输入(单一值,如0,1);产生唯一一个二进制输出
增加实数权重,如用来表示输入值的重要性,计算加和同时与阈值()来比较相对大小,从而判断输出0或1。
多层复杂感知机结构
- 输入层
- 第一列称为感知器的第一层,负责做三个非常简单的决定
- 第二列称为感知器的第二层,负责做出比第一层更加复杂抽象的决定
- 第三层感知器可以做出更复杂的决定。这样,感知器的多层网络就可以进行复杂的决策。
假设令
其中w,x分别位权重和输入的向量
感知机的偏置(偏置是用来衡量感知机输出1的容易性)
因此公式(1.1)可改写为:
1.1.2 其余用法
感知器的另一种用法是计算我们通常认为是基础计算的基本逻辑函数,如AND、OR和NAND等函数。
1.2 S型神经元(sigmoid激活函数)
1.2.1 问题引入
当处于学习时,我们希望可以通过微小的权重或偏置的改变来微小的改变输出。但是在上述的感知机中,这种改变将是翻天覆地的。所以,S型神经元就被设计出来了。
1.2.2 S型神经元的工作原理
- 输入可以是处于0到1之间的任意一值
- 输出为
其中
所以公式(1.2.1)可改写为
这个函数可以看成是感知器的阶跃函数平滑后的版本,Sigmoid激活函数的平滑意味着权重和偏置的微小变化,会影响神经元产生一个微小的输出变化
1.2.3 练习
1.2.3.1 练习1
假设我们把一个感知器网络中的所有权重和偏置乘以一个正的常数,c > 0。证明网络的行为并没有改变。
当c>0时,假设z是一个很大的正数,而。假设z是一个的负数, ,而 。所以,网络行为并没有改变。
1.2.3.2 练习2
假设我们有上题中相同的设置 —— 一个感知器网络。同样假设所有输入被选中。我们不需要实际的输入值,仅仅需要固定这些输入。假设对于网络中任何特定感知器的输入,权重和偏置遵循。现在用 S 型神经元替换所有网络中的感知器,并且把权重和偏置乘以一个正的常量。证明在 的极限情况下,S 型神经元网络的行为和感知器网络的完全一致。当一个感知器的时又为什么会不同?
当,则,
恒等于0.5,因此网络行为不同。
当 所以 ,与题1同理,网络行为并没有改变。
1.3 神经网络结构
神经网络结构- 最左边的层为输入层,这一层的神经元为输入神经元
- 最右边的层为输出层,这一层的神经元为输出神经元
- 中间的层为隐藏层,
- 该神经网络为前馈神经⽹络
还存在递归神经网络。这种模型的设计思想,是具有休眠前会在⼀段有限的时间内保持激活状态的神经元
1.4 一个简单的分类手写数字的网络
识别手写数字需要解决两个问题,分隔和识别。分隔相对于识别较为简单,可以采用数字分类器进行评分。
下文将介绍识别算法
将会使用三层的网络来识别数字 神经网络输入值为像素,像素值为灰度值,0.0代表白色,1.0代表黑色,中间的值代表灰色程度值
网络的第二层是一个隐藏层。我们用n表示这个隐层中的神经元数量,我们将用不同的n值来做实验。下面的例子展示了一个小的隐层,它只包含n=15个神经元。
输出层包含10个神经元,代表着从0-9的十个数字
1.4.1 练习
通过在上述的三层神经网络加一个额外的一层就可以实现按位表示数字。额外的一层把原来的输出层转化为一个二进制表示,如下图所示。为新的输出层寻找一些合适的权重和偏置。假定原先的 3 层神经网络在第三层得到正确输出(即原来的输出层)的激活值至少是0.99,得到错误的输出的激活值至多是 0.01。
首先,我们设想0~9用四位二进制如何表示:
十进制 | 二进制 |
---|---|
0 | 0000 |
1 | 0001 |
2 | 0010 |
3 | 0011 |
4 | 0100 |
5 | 0101 |
6 | 0110 |
7 | 0111 |
8 | 1000 |
9 | 1001 |
一个比较简化而合理的权重是,就选用二进制位置上的数值作为权重。假设正确输出为0,这输出层是输入为: (可以竖着看二进制的每一位)
第一个神经元输入 =
同理运算得到:
- 第二个神经元输入 = 0.04
- 第三个神经元输入 = 0.04
- 第四个神经元输入 = 0.05
所以我们可以把偏置设置为 -0.06
1.5 梯度下降学习
成本函数:
忽略,直接取单个训练样本的代价总和,⽽不是取平均值。这对我们不能提前知道训练数据数量的情况下特别有效
目标
1.5.1 双自变量
想象一个球从任意一点滚下
当我们在和⽅向分别将球体移动⼀个很⼩的量,即 和 时,球体将会发⽣什么情况
寻找 和使得为负。
定义
重写为
这帮助我们理解为什么梯度向量,同时也让我们知道如何选取来使变为负值。
假设我们选择其中的为一个小的正的参数(俗称学习率)。
所以得到
所以定义为梯度下降算法的运动定律。也就是说,我们用方程来计算,来移动球体的位置:重复操作就可以到达最低点。通常选择学习率的大小极其重要,如果太小下降的慢,如果太大则会以结束。所以选择一个合适的学习率可以达到最好的近似度,同时又不至于太慢。
1.5.2 多自变量
推广到m个自变量
假设
假设我们正在努力去改变来让C尽可能的减少。相当于最小化,首先限制步长为小的固定值。当步长固定时,我们要找到使得C减小最大的下降方向。可以证明,使得取得最小值的为,这里是由步长限制所决定的。因此,梯度下降法可以被视为一种在C下降最快的方向上做的微小变化的方法
1.5.3 改进梯度下降算法
缺点:需要始终计算C的⼆阶偏导
梯度下降如何工作,更新规则
应用梯度下降规则的挑战,代价函数的形式,即它是遍及每个训练样本代价的平均值。在实践中去计算为了计算梯度,我们需要为每个训练输⼊单独地计算梯度值,然后求平均值,所以及其费时间。
有一种叫随机梯度下降的算法能加速学习速度。思想是从数据中随机的选取小的样本去计算从而估算梯度
随机选取个样本,并令输入值为称他们为小批量数据。假设样本数量⾜够⼤,我们期望的平均值⼤致相等于整个的平均值,即
交换两边可以得到
参数的更新为
然后我们再挑选另⼀随机选定的⼩批量数据去训练。直到我们⽤完了所有的训练输⼊,这被称为完成了⼀个训练迭代期(epoch)。然后我们就会开始⼀个新的训练迭代期。
1.5.3 练习
1.5.3.1 练习1
证明上一段落的推断。提示:可以利用柯西-施瓦茨不等式。
事实上,甚至有一种观点认为梯度下降法是求最小值的最优策略。假设我们正在努力去改变∆v 来让 C 尽可能地减小。这相当于最小化 ∆C ≈ ∇C · ∆v。我们首先限制步⻓为小的固定值,即 ∥∆v∥ = ε,ε > 0。当步⻓固定时,我们要找到使得 C 减小最大的下降方向。可以证明,使得∇C · ∆v 取得最小值的 ∆v 为 ∆v = −η∇C,这里 η = ε/∥∇C∥ 是由步⻓限制 ∥∆v∥ = ε 所决定的。因此,梯度下降法可以被视为一种在 C 下降最快的方向上做微小变化的方法。
柯西-施瓦茨不等式:
因为,由 柯西-施瓦茨不等式得:
所以想让得到最大值,即等于,则:
同理,想得到得到最小值,则:
1.5.3.2 练习2
我已经解释了当 C 是二元及其多元函数的情况。那如果 C 是一个一元函数呢?你能给出梯度下降法在一元函数的几何解释么?
如果 C 是一个一元函数,我们可以几何想象成是一个C为y轴,v为x轴,上的曲线,我们在曲线上寻找C的最低点。
1.5.3.3 练习3
梯度下降算法⼀个极端的版本是把⼩批量数据的⼤⼩设为1。即,假设⼀个训练输⼊x,我们按照规则和更新我们的权重和偏置。然后我们选取另⼀个训练输⼊,再⼀次更新权重和偏置。如此重复。这个过程被称为在线、online、on-line、或者递增学习。在online 学习中,神经⽹络在⼀个时刻只学习⼀个训练输⼊(正如⼈类做的)。对⽐具有⼀个⼩批量输⼊⼤⼩为20 的随机梯度下降,说出递增学习的⼀个优点和⼀个缺点。
优点:一个时刻只学习一个训练输入,能让模型迅速的学习到当前时刻的数据。如根据用户浏览的商品,实时的推荐相关的商品;根据行用卡使用行为数据,实时的预测出欺诈行为。
缺点:对比具有一个小批量输入大小为 20 的随机梯度下降,online 学习的实际上的学习率太大,偶然突发性的噪音数据会极大的影响原本的模型。
1.6 实现字符识别
import numpy as np
import random
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."""
training_data = list(training_data)
n = len(training_data)
if test_data:
test_data = list(test_data)
n_test = len(test_data)
for j in range(epochs):
random.shuffle(training_data)
mini_batches = [
training_data[k:k+mini_batch_size]
for k in range(0, n, mini_batch_size)]
for mini_batch in mini_batches:
self.update_mini_batch(mini_batch, eta)
if test_data:
print("Epoch {} : {} / {}".format(j,self.evaluate(test_data),n_test));
else:
print("Epoch {} 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 range(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))
⽤表⽰矩阵。矩阵的是连接第⼆层的神经元和第三层的神经元的权重。
第三层神经元的激活向量为
是第二层神经元的激活向量,
update_mini_batch
大部分代码由delta_nabla_b, delta_nabla_w = self.backprop(x, y)
完成,调用了反向传播的算法,是一种快速计算代价函数的梯度方法
import pickle
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('mnist.pkl.gz', 'rb')
training_data, validation_data, test_data = pickle.load(f, encoding="latin1")
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
training_data, validation_data, test_data = load_data_wrapper()
net = Network([784, 30, 10])
net.SGD(training_data, 30, 10, 3.0, test_data=test_data)
Epoch 0 : 9143 / 10000
Epoch 1 : 9267 / 10000
Epoch 2 : 9325 / 10000
Epoch 3 : 9378 / 10000
Epoch 4 : 9382 / 10000
Epoch 5 : 9336 / 10000
Epoch 6 : 9416 / 10000
Epoch 7 : 9416 / 10000
Epoch 8 : 9437 / 10000
Epoch 9 : 9426 / 10000
Epoch 10 : 9444 / 10000
Epoch 11 : 9473 / 10000
Epoch 12 : 9470 / 10000
Epoch 13 : 9484 / 10000
Epoch 14 : 9479 / 10000
Epoch 15 : 9500 / 10000
Epoch 16 : 9454 / 10000
Epoch 17 : 9487 / 10000
Epoch 18 : 9501 / 10000
Epoch 19 : 9500 / 10000
Epoch 20 : 9496 / 10000
Epoch 21 : 9518 / 10000
Epoch 22 : 9505 / 10000
Epoch 23 : 9514 / 10000
Epoch 24 : 9484 / 10000
Epoch 25 : 9515 / 10000
Epoch 26 : 9511 / 10000
Epoch 27 : 9471 / 10000
Epoch 28 : 9510 / 10000
Epoch 29 : 9495 / 10000
1.6.1 练习
1.6.1.1 练习1
以分量形式写出,并验证它和计算S 型神经元输出的规则结果相同。
1.6.1.2 练习2
试着创建⼀个仅有两层的⽹络—— ⼀个输⼊层和⼀个输出层,分别有784 和10 个神经元,没有隐藏层。⽤随机梯度下降算法训练⽹络。你能达到多少识别率?
training_data,validation_data,test_data = load_data_wrapper()
net = Network([784,10])
net.SGD(training_data,30,10,3.0,test_data=test_data)
Epoch 0 : 5130 / 10000
Epoch 1 : 6610 / 10000
Epoch 2 : 6836 / 10000
Epoch 3 : 7504 / 10000
Epoch 4 : 7531 / 10000
Epoch 5 : 7547 / 10000
Epoch 6 : 7551 / 10000
Epoch 7 : 7552 / 10000
Epoch 8 : 7564 / 10000
Epoch 9 : 7560 / 10000
Epoch 10 : 7579 / 10000
Epoch 11 : 7570 / 10000
Epoch 12 : 7583 / 10000
Epoch 13 : 7549 / 10000
Epoch 14 : 7602 / 10000
Epoch 15 : 7592 / 10000
Epoch 16 : 8298 / 10000
Epoch 17 : 8365 / 10000
Epoch 18 : 8385 / 10000
Epoch 19 : 8394 / 10000
Epoch 20 : 8391 / 10000
Epoch 21 : 8388 / 10000
Epoch 22 : 8393 / 10000
Epoch 23 : 8385 / 10000
Epoch 24 : 8389 / 10000
Epoch 25 : 8417 / 10000
Epoch 26 : 8397 / 10000
Epoch 27 : 8409 / 10000
Epoch 28 : 8414 / 10000
Epoch 29 : 8420 / 10000
网友评论