参考资料:深度学习入门:基于 Python 的理论与实现 (斋藤康毅)
chap4 神经网络的学习
这一章讨论神经网络如何从数据中自动学习参数。在进入损失函数和梯度法之前,先从传统计算机视觉的人工特征提取说起。
想要让计算机识别一类图像,其实就是让其学习这类图像的共有特征。
而在计算机视觉领域,常用的特征量有以下几种:
-
SIFT(scale-invariant feature transform, 尺度不变特征变换)
invariant adj. 无变化的,不变的 n. 不变式,不变量
是一种用于图像处理的算法,它通过在多尺度空间中检测极值点来识别稳定的兴趣点,并为每个点计算一个描述局部外观的128维特征向量,此向量对尺度、旋转和光照变化具有不变性,从而使得这些特征点可以在不同的图像条件下被可靠地匹配,广泛应用于物体识别、图像拼接等领域。
-
SURF(speeded up robust features, 加速稳健特征)
是一种高效的图像特征检测与描述算法,由 Bay 等人在2006年提出。它通过使用积分图像快速计算 Haar 小波响应来简化关键点检测和描述的过程,在保持对尺度、旋转变化的鲁棒性的同时显著提高了 SIFT 算法的速度。SURF 利用确定性的盒状滤波器代替高斯滤波器,并为每个检测到的关键点分配一个64或128维的描述子,该描述子能够捕捉关键点周围区域的梯度信息,适用于快速而准确的图像匹配和物体识别任务。
-
HOG(histogram of oriented gradients, 方向梯度直方图)
histogram n. 柱状图
是一种用于描述图像局部形状特征的技术,通过在图像的小区域(细胞单元)内统计梯度的方向分布来捕捉边缘和轮廓信息,进而构成一个稳定的特征描述子,广泛应用于物体检测尤其是行人检测领域。HOG 特征对光照变化和阴影具有较好的鲁棒性,并且由于其简单性和有效性,在计算机视觉任务中占据重要地位。
使用这些特征量将图像数据转换为向量,然后对转换后的向量使用机器学习中的 SVM、KNN 等分类器进行学习。
-
SVM(support vector machine, 支持向量机)
是一种监督学习算法,旨在找到一个最优超平面以最大化不同类别数据点之间的间隔,从而实现分类;对于非线性数据,SVM 通过核函数将数据映射到高维空间来寻找线性分隔,其核心在于利用支持向量(即离决策边界最近的数据点)构建模型,具有良好的泛化能力,适用于高维空间和较小样本集的分类与回归任务。
-
KNN(K-nearest neighbors, K-近邻算法)
是一种简单直观的监督学习算法,它通过计算新样本与训练集中所有样本的距离,找出最接近的新样本的 K 个邻居,并依据这些邻居的多数类别来预测新样本的类别;其优点在于实现简单且无需显式训练过程,但对大规模数据集和高维数据效率较低,并且对 K 值的选择及数据尺度敏感。
深度学习有时也叫端到端学习(end-to-end machine learning)。
神经网络的优点是对所有的问题都可以用同样的流程来解决。比如,不管要求解的问题是识别数字5,还是识别狗,抑或是识别人脸,神经网络都是通过不断地学习所提供的数据,尝试发现待求解的问题的模式。也就是说,与待处理的问题无关,神经网络可以将数据直接作为原始数据,进行“端对端”的学习。
损失函数
书中介绍的常用损失函数如下:
1. 均方误差
2. 交叉熵误差
yk 是神经网络的输出,tk 是正确解标签。并且,tk 中只有正确解标签的索引为1,其他均为0(one-hot 表示)。因此,实际上只计算对应正确解标签的输出的自然对数。比如,假设正确解标签的索引是“2”,与之对应的神经网络的输出是0.6,则交叉熵误差是−log0.6 =0.51;若“2”对应的输出是0.1,则交叉熵误差为−log0.1=2.30。也就是说,交叉熵误差的值是由正确解标签所对应的输出结果决定的。
上面计算的是单个数据的损失(例如手写数字识别,每一张图片输出的都是一个10维的向量,上式是把每个向量的元素相乘再相加),要求计算所有数据的损失之和,则损失函数变为下式。除以 N 可以得到单个数据的平均损失函数。
Mini-batch 学习
如果遇到大数据,数据量会有几百万、几千万之多,这种情况下以全部数据为对象计算损失函数是不现实的。因此,我们从全部数据中选出一部分,作为全部数据的“近似”。神经网络的学习也是从训练数据中选出一批数据(称为 mini-batch, 小批量),然后对每个 mini-batch 进行学习。比如,从60000个训练数据中随机选择100笔,再用这100笔数据进行学习。这种学习方式称为 mini-batch 学习。
计算电视收视率时,并不会统计所有家庭的电视机,而是仅以那些被选中的家庭为统计对象。比如,通过从关东地区随机选择1000个家庭计算收视率,可以近似地求得关东地区整体的收视率。这1000个家庭的收视率,虽然严格上不等于整体的收视率,但可以作为整体的一个近似值。
和收视率一样,mini-batch 的损失函数也是利用一部分样本数据来近似地计算整体。也就是说,用随机选择的小批量数据(mini-batch)作为全体训练数据的近似值。
梯度法
根据目的是寻找最小值还是最大值,梯度法的叫法有所不同。严格地讲,寻找最小值的梯度法称为梯度下降法(gradient descent method),寻找最大值的梯度法称为梯度上升法(gradient ascent method)。
η 表示更新量,在神经网络的学习中,称为学习率(learning rate)。学习率决定在一次学习中,应该学习多少,以及在多大程度上更新参数。
超参数的概念
像学习率这样的参数称为超参数。这是一种和神经网络的参数(权重和偏置)性质不同的参数。相对于神经网络的权重参数是通过训练数据和学习算法自动获得的,学习率这样的超参数则是人工设定的。一般来说,超参数需要尝试多个值,以便找到一种可以使学习顺利进行的设定。
神经网络的学习步骤
前提:神经网络存在合适的权重和偏置,调整权重和偏置以便拟合训练数据的过程称为“学习”。神经网络的学习分成下面4个步骤。
- 步骤1(mini-batch):从训练数据中随机选出一部分数据,这部分数据称为 mini-batch。我们的目标是减小 mini-batch 的损失函数的值。
- 步骤2(计算梯度):为了减小 mini-batch 的损失函数的值,需要求出各个权重参数的梯度。梯度表示损失函数的值减小最多的方向。
- 步骤3(更新参数):将权重参数沿梯度方向进行微小更新。
- 步骤4(重复):重复步骤1、步骤2、步骤3。
这个方法通过梯度下降法更新参数,不过因为这里使用的数据是随机选择的 mini-batch 数据,所以又称为随机梯度下降法(SGD, stochastic gradient descent)。
stochastic adj.(过程或系统)随机的
epoch
epoch 是一个单位。一个 epoch 表示学习中所有训练数据均被使用过一次时的更新次数。比如,对于10000笔训练数据,用大小为100笔数据的 mini-batch 进行学习时,重复随机梯度下降法100次,所有的训练数据就都被“看过”了。此时,100次就是一个 epoch。
但要补充一点,对于高维度的数据,其参数调整十分复杂,可能100次的调整都无法达到最佳,所以在训练时要进行多次 epoch,200次这种都比较常见。
chap5 误差反向传播法
上一章知道了可以沿着梯度更新参数,这一章解决“梯度怎么高效求出来”的问题——误差反向传播法,并以“层”为单位来搭建和实现网络。
在搭建神经网络的层的过程中,搭建的是运算方法(放在图里就是那个圈圈),有了这种运算方法,改变输入即可。
换句话说,神经网络其实就是在组建一个运算方法的框架。
反向传播
我们已经知道了正向传播就是把输入数据进行一系列的运算,如乘以权重矩阵,进行 RELU 激活,Sigmoid 激活等等运算,最后进行一个 softmax 运算出一个概率向量,那么想要调整参数,就要知道自变量的变化会多大程度影响影响损失函数,也就是求偏导,从最后的损失函数出发,一层一层往前求偏导,最后求到了输入层的权重上,就可以调整这个权重来降低损失函数,从而优化最后结果。
Affine 层
神经网络的正向传播中进行的矩阵的乘积运算在几何学领域被称为“仿射变换”。因此,这里将进行仿射变换的处理实现为“Affine 层”。
affine adj. 仿射的(几何学)
几何中,仿射变换包括一次线性变换和一次平移,分别对应神经网络的加权和运算与加偏置运算。
神经网络的搭建是构建每一个层,然后组装
像这样通过将神经网络的组成元素以层的方式实现,可以轻松地构建神经网络。这个用层进行模块化的实现具有很大优点。因为想另外构建一个神经网络(比如5层、10层、20层……的大的神经网络)时,只需像组装乐高积木那样添加必要的层就可以了。之后,通过各个层内部实现的正向传播和反向传播,就可以正确计算进行识别处理或学习所需的梯度。
小结
本章介绍了将计算过程可视化的计算图,并使用计算图,介绍了神经网络中的误差反向传播法,并以层为单位实现了神经网络中的处理。
我们学过的层有 ReLU 层、Softmax-with-Loss 层、Affine 层、Softmax 层等,这些层中实现了 forward 和 backward 方法,通过将数据正向和反向地传播,可以高效地计算权重参数的梯度。通过使用层进行模块化,神经网络中可以自由地组装层,轻松构建出自己喜欢的网络。
chap6 与学习相关的技巧
学习的基本机制(损失函数、梯度、反向传播)已经齐了,这一章收集让学习更快、更稳的实用技巧:参数更新方法、权重初始值、Batch Normalization、正则化和超参数调优。
SGD 的缺点
如果函数的形状非均向(anisotropic),比如呈延伸状,搜索的路径就会非常低效。因此,我们需要比单纯朝梯度方向前进的 SGD 更聪明的方法。SGD 低效的根本原因是,梯度的方向并没有指向最小值的方向。
为了改正 SGD 的缺点,下面我们将介绍 Momentum、AdaGrad、Adam 这3种方法来取代 SGD。
Momentum
Momentum 是“动量”的意思,和物理有关。用数学式表示 Momentum 方法,如下所示。
和前面的 SGD 一样,W 表示要更新的权重参数,表示损失函数关于 W 的梯度,η 表示学习率。这里新出现了一个变量 v,对应物理上的速度。第一个式子表示了物体在梯度方向上受力,在这个力的作用下,物体的速度增加这一物理限制。如图,Momentum 方法给人的感觉就像是小球在地面上滚动。
有 αv 这一项。在物体不受任何力时,该项承担使物体逐渐减速的任务(α 设定为0.9之类的值),对应物理上的地面摩擦或空气阻力。也就是 v 的组成为上一个 v 的九折加上新的梯度下降。
class Momentum:
def __init__(self, lr=0.01, momentum=0.9):#初始化变量,初始v为0
self.lr = lr
self.momentum = momentum
self.v = None
def update(self, params, grads):
if self.v is None:
self.v = {}
for key, val in params.items():
self.v[key] = np.zeros_like(val)#把v变成和参数矩阵一样的全0矩阵
for key in params.keys():#遍历参数的键,像公式一样更新参数
self.v[key] = self.momentum * self.v[key] - self.lr * grads[key]
params[key] += self.v[key]
AdaGrad(adaptive gradient)
在神经网络的学习中,学习率(数学式中记为 η)的值很重要。学习率过小,会导致学习花费过多时间;反过来,学习率过大,则会导致学习发散而不能正确进行。在关于学习率的有效技巧中,有一种被称为学习率衰减(learning rate decay)的方法,即随着学习的进行,使学习率逐渐减小。实际上,一开始“多”学,然后逐渐“少”学的方法,在神经网络的学习中经常被使用。逐渐减小学习率的想法,相当于将“全体”参数的学习率值一起降低。

这里想表达的意思就是,再参数的迭代过程中,前面累计的变动幅度越大,后面让其变动的幅度就越小。
class AdaGrad:
def __init__(self, lr=0.01):
self.lr = lr
self.h = None
def update(self, params, grads):
if self.h is None:
self.h = {}
for key, val in params.items():
self.h[key] = np.zeros_like(val)#先将h全部赋为0
for key in params.keys():
self.h[key] += grads[key] * grads[key]
params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)
#加上了微小值1e-7。这是为了防止当 self.h[key]中有0时,将0用作除数的情况
Adam
Momentum 参照小球在碗中滚动的物理规则进行移动,AdaGrad 为参数的每个元素适当地调整更新步伐。如果将这两个方法融合在一起会怎么样呢?这就是 Adam 方法的基本思路。
Adam 是2015年提出的新方法。它的理论有些复杂,直观地讲,就是融合了 Momentum 和 AdaGrad 的方法。通过组合前面两个方法的优点,有望实现参数空间的高效搜索。此外,进行超参数的“偏置校正”也是 Adam 的特征。这里不再进行过多的说明,详细内容请参考原作者的论文[8]。
Diederik Kingma and Jimmy Ba.(2014): Adam: A Method for Stochastic Optimization. arXiv:1412.6980 [cs] (December 2014).
class Adam:
"""Adam (http://arxiv.org/abs/1412.6980v8)"""
def __init__(self, lr=0.001, beta1=0.9, beta2=0.999):
self.lr = lr
self.beta1 = beta1
self.beta2 = beta2
self.iter = 0
self.m = None
self.v = None
def update(self, params, grads):
if self.m is None:
self.m, self.v = {}, {}
for key, val in params.items():
self.m[key] = np.zeros_like(val)
self.v[key] = np.zeros_like(val)
self.iter += 1
lr_t = self.lr * np.sqrt(1.0 - self.beta2self.iter) / (1.0 - self.beta1self.iter)
for key in params.keys():
#self.m[key] = self.beta1self.m[key] + (1-self.beta1)grads[key]
#self.v[key] = self.beta2self.v[key] + (1-self.beta2)(grads[key]**2)
self.m[key] += (1 - self.beta1) * (grads[key] - self.m[key])
self.v[key] += (1 - self.beta2) * (grads[key]**2 - self.v[key])
params[key] -= lr_t * self.m[key] / (np.sqrt(self.v[key]) + 1e-7)
#unbias_m += (1 - self.beta1) * (grads[key] - self.m[key]) # correct bias
#unbisa_b += (1 - self.beta2) * (grads[key]*grads[key] - self.v[key]) # correct bias
#params[key] += self.lr * unbias_m / (np.sqrt(unbisa_b) + 1e-7)
Adam 会设置3个超参数。一个是学习率(论文中以 α 出现),另外两个是一次 momentum 系数 β1 和二次 momentum 系数 β2。根据论文,标准的设定值是 β1 为0.9,β2 为0.999。设置了这些值后,大多数情况下都能顺利运行。
梯度消失

上面是随机的数据,标准差为1表示波动幅度为1,所以数据靠近0和1
从图可知,各层的激活值呈偏向0和1的分布。这里使用的 sigmoid 函数是 S 型函数,随着输出不断地靠近0(或者靠近1),它的导数的值逐渐接近0。因此,偏向0和1的数据分布会造成反向传播中梯度的值不断变小,最后消失。这个问题称为梯度消失(gradient vanishing)。层次加深的深度学习中,梯度消失的问题可能会更加严重。

这次呈集中在0.5附近的分布。因为不像刚才的例子那样偏向0和1,所以不会发生梯度消失的问题。但是,激活值的分布有所偏向,说明在表现力上会有很大问题。为什么这么说呢?因为如果有多个神经元都输出几乎相同的值,那它们就没有存在的意义了。比如,如果100个神经元都输出几乎相同的值,那么也可以由1个神经元来表达基本相同的事情。因此,激活值在分布上有所偏向会出现“表现力受限”的问题。
解决以上两个问题的办法:
Xavier 初始值




图6-13的分布中,后面的层的分布呈稍微歪斜的形状。如果用 tanh 函数(双曲线函数)代替 sigmoid 函数,这个稍微歪斜的问题就能得到改善。实际上,使用 tanh 函数后,会呈漂亮的吊钟型分布。tanh 函数和 sigmoid 函数同是 S 型曲线函数,但 tanh 函数是关于原点 (0,0) 对称的 S 型曲线,而 sigmoid 函数是关于 (x,y)=(0,0.5) 对称的 S 型曲线。众所周知,用作激活函数的函数最好具有关于原点对称的性质。
ReLU 权重的初始值

基于 MNIST 数据集的权重初始值的比较
std——标准差(Standard Deviation)

这个实验中,神经网络有5层,每层有100个神经元,激活函数使用的是 ReLU。从图6-15的结果可知,std=0.01时完全无法进行学习。这和刚才观察到的激活值的分布一样,是因为正向传播中传递的值很小(集中在0附近的数据)。因此,逆向传播时求到的梯度也很小,权重几乎不进行更新。相反,当权重初始值为 Xavier 初始值和 He 初始值时,学习进行得很顺利。并且,我们发现 He 初始值时的学习进度更快一些。
综上,在神经网络的学习中,权重初始值非常重要。很多时候权重初始值的设定关系到神经网络的学习能否成功。权重初始值的重要性容易被忽视,而任何事情的开始(初始值)总是关键的,因此在结束本节之际,再次强调一下权重初始值的重要性。
Batch Normalization
Batch Norm 有以下优点:
- 可以使学习快速进行(可以增大学习率);
- 不那么依赖初始值(对于初始值不用那么神经质);
- 抑制过拟合(降低 Dropout 等的必要性)。
Batch Norm,顾名思义,以进行学习时的 mini-batch 为单位,按 mini-batch 进行正规化。具体而言,就是进行使数据分布的均值为0、方差为1的正规化。用数学式表示的话,如下所示。

正则化
过拟合:只能拟合训练数据,不能拟合训练数据以外的数据。而机器学习的目标是提高泛化能力。
原因:1.模型拥有大量参数,表现力强;2.训练数据少。
权值衰减是一直以来经常被使用的一种抑制过拟合的方法。该方法通过在学习的过程中对大的权重进行惩罚,来抑制过拟合。很多过拟合原本就是因为权重参数取值过大才发生的。

Dropout
作为抑制过拟合的方法,前面我们介绍了为损失函数加上权重的 L2 范数的权值衰减方法。该方法可以简单地实现,在某种程度上能够抑制过拟合。但是,如果网络的模型变得很复杂,只用权值衰减就难以应对了。在这种情况下,我们经常会使用 Dropout 方法。
Dropout 是一种在学习的过程中随机删除神经元的方法。训练时,随机选出隐藏层的神经元,然后将其删除。被删除的神经元不再进行信号的传递,如图6-22所示。训练时,每传递一次数据,就会随机选择要删除的神经元。然后,测试时,虽然会传递所有的神经元信号,但是对于各个神经元的输出,要乘上训练时的删除比例后再输出。

超参数的验证
神经网络中,除了权重和偏置等参数,超参数(hyper-parameter)也经常出现。这里所说的超参数是指,比如各层的神经元数量、batch 大小、参数更新时的学习率或权值衰减等。
调整超参数时,必须使用超参数专用的确认数据。用于调整超参数的数据,一般称为验证数据(validation data)。我们使用这个验证数据来评估超参数的好坏。
训练数据用于参数(权重和偏置)的学习,验证数据用于超参数的性能评估。为了确认泛化能力,要在最后使用(比较理想的是只用一次)测试数据。
超参数的最优化
超参数的范围只要“大致地指定”就可以了。所谓“大致地指定”,是指像0.001(10−3)到1000(103)这样,以“10的阶乘”的尺度指定范围(也表述为“用对数尺度(log scale)指定”)。
步骤:
- 步骤0:设定超参数的范围。
- 步骤1:从设定的超参数范围中随机采样。
- 步骤2:使用步骤1中采样到的超参数的值进行学习,通过验证数据评估识别精度(但是要将 epoch 设置得很小)。
- 步骤3:重复步骤1和步骤2(100次等),根据它们的识别精度的结果,缩小超参数的范围。
这里介绍的超参数的最优化方法是实践性的方法。不过,这个方法与其说是科学方法,倒不如说有些实践者的经验的感觉。在超参数的最优化中,如果需要更精炼的方法,可以使用贝叶斯最优化(Bayesian optimization)。贝叶斯最优化运用以贝叶斯定理为中心的数学理论,能够更加严密、高效地进行最优化。详细内容请参考论文“Practical Bayesian Optimization of Machine Learning Algorithms”[16]等
本章所学的内容
- 参数的更新方法,除了 SGD 之外,还有 Momentum、AdaGrad、Adam 等方法。
- 权重初始值的赋值方法对进行正确的学习非常重要。
- 作为权重初始值,Xavier 初始值、He 初始值等比较有效。
- 通过使用 Batch Normalization,可以加速学习,并且对初始值变得健壮。
- 抑制过拟合的正则化技术有权值衰减、Dropout 等。
- 逐渐缩小“好值”存在的范围是搜索超参数的一个有效方法。
chap7 卷积神经网络
前几章的网络都由全连接(Affine)层构成。这一章引入两种新的层——卷积层和池化层,看看 CNN 如何利用图像的空间结构信息。
CNN 被用于图像识别、语音识别等各种场合,在图像识别的比赛中,基于深度学习的方法几乎都以 CNN 为基础。
另外,CNN 中,有时将卷积层的输入输出数据称为特征图(feature map)。其中,卷积层的输入数据称为输入特征图(input feature map),输出数据称为输出特征图(output feature map)。本书中将“输入输出数据”和“特征图”作为含义相同的词使用。
计算输出图的大小

对于有膨胀率的来说

表示方法

而对于多个滤波器,计算结果如下:

批处理

池化层
池化是缩小高、长方向上的空间的运算。比如,如图7-14所示,进行将2×2的区域集约成1个元素的处理,缩小空间大小。

图7-14的例子是按步幅2进行2×2的 Max 池化时的处理顺序。“Max 池化”是获取最大值的运算,“2×2”表示目标区域的大小。如图所示,从2×2的区域中取出最大的元素。此外,这个例子中将步幅设为了2,所以2×2的窗口的移动间隔为2个元素。另外,一般来说,池化的窗口大小会和步幅设定成相同的值。比如,3×3的窗口的步幅会设为3,4×4的窗口的步幅会设为4等。
除了 Max 池化之外,还有 Average 池化等。相对于 Max 池化是从目标区域中取出最大值,Average 池化则是计算目标区域的平均值。在图像识别领域,主要使用 Max 池化。因此,本书中说到“池化层”时,指的是 Max 池化。
池化层的特征
1.没有要学习的参数
池化层和卷积层不同,没有要学习的参数。池化只是从目标区域中取最大值(或者平均值),所以不存在要学习的参数。
2.通道数不发生变化
经过池化运算,输入数据和输出数据的通道数不会发生变化。
3.对微小的位置变化具有鲁棒性(健壮)
输入数据发生微小偏差时,池化仍会返回相同的结果。因此,池化对输入数据的微小偏差具有鲁棒性。比如,3×3的池化的情况下,如图7-16所示,池化会吸收输入数据的偏差(根据数据的不同,结果有可能不一致)。
如果老老实实的进行卷积运算,则就是让卷积核在矩阵的行方向移动,当一行遍历完了,就要换行,这就相当于对行进行遍历。
所以,如果老老实实地实现卷积运算,估计要重复好几层的 for 语句。这样的实现有点麻烦,而且,NumPy 中存在使用 for 语句后处理变慢的缺点(NumPy 中,访问元素时最好不要用 for 语句)。这里,我们不使用 for 语句,而是使用 im2col 这个便利的函数进行简单的实现。
im2col 这个名称是“image to column”的缩写,翻译过来就是“从图像到矩阵”的意思。Caffe、Chainer 等深度学习框架中有名为 im2col 的函数,并且在卷积层的实现中,都使用了 im2col。
im2col 的原理
im2col 会把输入数据展开以适合滤波器(权重)。具体地说,如图7-18所示,对于输入数据,将应用滤波器的区域(3维方块)横向展开为1列。im2col 会在所有应用滤波器的地方进行这个展开处理。

使用 im2col 展开输入数据后,之后就只需将卷积层的滤波器(权重)纵向展开为1列,并计算2个矩阵的乘积即可(参照图7-19)。这和全连接层的 Affine 层进行的处理基本相同。如图7-19所示,基于 im2col 方式的输出结果是2维矩阵。因为 CNN 中数据会保存为4维数组,所以要将2维输出数据转换为合适的形状。以上就是卷积层的实现流程。

im2col 接口:
im2col(input_data, filter_h, filter_w, stride=1, pad=0)
input_data―由(数据量,通道,高,长)的4维数组构成的输入数据filter_h―滤波器的高filter_w―滤波器的长stride―步幅pad―填充
im2col 使用实例
这里举了两个例子。第一个是批大小为1、通道为3的7×7的数据,第二个的批大小为10,数据形状和第一个相同。分别对其应用 im2col 函数,在这两种情形下,第2维的元素个数均为75。这是滤波器(通道为3、大小为5×5)的元素个数的总和。批大小为1时,im2col 的结果是 (9,75)。而第2个例子中批大小为10,所以保存了10倍的数据,即 (90,75)。
import sys, os sys.path.append(os.pardir) from common.util import im2col x1 = np.random.rand(1, 3, 7, 7) col1 = im2col(x1, 5, 5, stride=1, pad=0) print(col1.shape) # (9, 75) x2 = np.random.rand(10, 3, 7, 7) # 10个数据 col2 = im2col(x2, 5, 5, stride=1, pad=0) print(col2.shape) # (90, 75)
现在使用 im2col 来实现卷积层。这里我们将卷积层实现为名为 Convolution 的类。
class Convolution:
def __init__(self, W, b, stride=1, pad=0):
self.W = W
self.b = b
self.stride = stride
self.pad = pad
def forward(self, x):
FN, C, FH, FW = self.W.shape
N, C, H, W = x.shape
out_h = int(1 + (H + 2*self.pad - FH) / self.stride)
out_w = int(1 + (W + 2*self.pad - FW) / self.stride)
col = im2col(x, FH, FW, self.stride, self.pad)
col_W = self.W.reshape(FN, -1).T # 滤波器的展开
out = np.dot(col, col_W) + self.b
out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)
return out
使用 im2col 进行池化操作

像这样展开之后,只需对展开的矩阵求各行的最大值,并转换为合适的形状即可(图7-22)。

池化操作的实现
class Pooling:
def __init__(self, pool_h, pool_w, stride=1, pad=0):
self.pool_h = pool_h
self.pool_w = pool_w
self.stride = stride
self.pad = pad
def forward(self, x):
N, C, H, W = x.shape
out_h = int(1 + (H - self.pool_h) / self.stride)
out_w = int(1 + (W - self.pool_w) / self.stride)
# 展开 (1)
col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad)
col = col.reshape(-1, self.pool_h * self.pool_w)
# 最大值 (2)
out = np.max(col, axis=1)
# 转换 (3)
out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)
return out
CNN 的实现

如图7-23所示,网络的构成是“Convolution - ReLU - Pooling - Affine - ReLU - Affine - Softmax”,我们将它实现为名为 SimpleConvNet 的类。首先来看一下 SimpleConvNet 的初始化(__init__),取下面这些参数。
参数
input_dim―输入数据的维度:(通道,高,长)conv_param―卷积层的超参数(字典)。字典的关键字如下:
filter_num―滤波器的数量filter_size―滤波器的大小stride―步幅pad―填充hidden_size―隐藏层(全连接)的神经元数量output_size―输出层(全连接)的神经元数量weight_init_std―初始化时权重的标准差
CNN 的可视化
这里,我们来比较一下学习前和学习后的权重,结果如图7-24所示。
实现代码如下:
# coding: utf-8
import numpy as np
import matplotlib.pyplot as plt
from simple_convnet import SimpleConvNet
def filter_show(filters, nx=8, margin=3, scale=10):
"""
c.f. https://gist.github.com/aidiary/07d530d5e08011832b12#file-draw_weight-py
"""
FN, C, FH, FW = filters.shape
ny = int(np.ceil(FN / nx))
fig = plt.figure()
fig.subplots_adjust(left=0, right=1, bottom=0, top=1, hspace=0.05, wspace=0.05)
for i in range(FN):
ax = fig.add_subplot(ny, nx, i+1, xticks=[], yticks=[])
ax.imshow(filters[i, 0], cmap=plt.cm.gray_r, interpolation='nearest')
plt.show()
network = SimpleConvNet()
# 随机进行初始化后的权重
filter_show(network.params['W1'])
# 学习后的权重
network.load_params("params.pkl")
filter_show(network.params['W1'])

具有代表性的 CNN——一个是在1998年首次被提出的 CNN 元祖 LeNet,另一个是在深度学习受到关注的2012年被提出的 AlexNet。


AlexNet 叠有多个卷积层和池化层,最后经由全连接层输出结果。虽然结构上 AlexNet 和 LeNet 没有大的不同,但有以下几点差异。
- 激活函数使用 ReLU。
- 使用进行局部正规化的 LRN(Local Response Normalization)层。
- 使用 Dropout。
本章所学的内容
- CNN 在此前的全连接层的网络中新增了卷积层和池化层。
- 使用 im2col 函数可以简单、高效地实现卷积层和池化层。
- 通过 CNN 的可视化,可知随着层次变深,提取的信息愈加高级。
- LeNet 和 AlexNet 是 CNN 的代表性网络。
- 在深度学习的发展中,大数据和 GPU 做出了很大的贡献。
chap8 深度学习
前面几章的部件(层的实现、学习技巧、CNN)都已经齐了,这一章讨论把网络加深之后能得到什么,并介绍几个代表性的深度网络和应用方向。
深度学习是加深了层的深度神经网络。基于之前介绍的网络,只需通过叠加层,就可以创建深度网络。
对于 MNIST 数据集,层不用特别深就获得了(目前)最高的识别精度。一般认为,这是因为对于手写数字识别这样一个比较简单的任务,没有必要将网络的表现力提高到那么高的程度。因此,可以说加深层的好处并不大。
而之后要介绍的大规模的一般物体识别的情况,因为问题复杂,所以加深层对提高识别精度大有裨益。
参考刚才排行榜中前几名的方法,可以发现进一步提高识别精度的技术和线索。
比如,集成学习、学习率衰减、Data Augmentation(数据扩充)等都有助于提高识别精度。尤其是 Data Augmentation,虽然方法很简单,但在提高识别精度上效果显著。
Data Augmentation 基于算法“人为地”扩充输入图像(训练图像)。具体地说,如图8-4所示,对于输入图像,通过施加旋转、垂直或水平方向上的移动等微小变化,增加图像的数量。这在数据集的图像数量有限时尤其有效。

除了如图8-4所示的变形之外,Data Augmentation 还可以通过其他各种方法扩充图像,比如裁剪图像的 “crop 处理”、将图像左右翻转的“flip 处理”等。对于一般的图像,施加亮度等外观上的变化、放大缩小等尺度上的变化也是有效的。
加深层的动机
下面我们说一下加深层的好处。其中一个好处就是可以减少网络的参数数量。说得详细一点,就是与没有加深层的网络相比,加深了层的网络可以用更少的参数达到同等水平(或者更强)的表现力。这一点结合卷积运算中的滤波器大小来思考就好理解了。比如,图8-5展示了由5×5的滤波器构成的卷积层。

对于上图,输出是由一次5×5的卷积得来的,而下图的输出是由两次3×3的卷积得到的,上面涉及到的参数数量为5×5=25个,即卷积核的内容参数,而下面的需要的参数为2×3×3=18个(两个3×3卷积核),减少了参数量。
而且,这个参数数量之差会随着层的加深而变大。比如,重复三次3×3的卷积运算时,参数的数量总共是27。而为了用一次卷积运算“观察”与之相同的区域,需要一个7×7的滤波器,此时的参数数量是49。

加深层的另一个好处就是使学习更加高效。与没有加深层的网络相比,通过加深层,可以减少学习数据,从而高效地进行学习。
VGG
VGG 是由卷积层和池化层构成的基础的 CNN。不过,如图8-9所示,它的特点在于将有权重的层(卷积层或者全连接层)叠加至16层(或者19层),具备了深度(根据层的深度,有时也称为“VGG16”或“VGG19”)。VGG 中需要注意的地方是,基于3×3的小型滤波器的卷积层的运算是连续进行的。如图8-9所示,重复进行“卷积层重叠2次到4次,再通过池化层将大小减半”的处理,最后经由全连接层输出结果。

GoogLeNet
GoogLeNet 的网络结构如图8-10所示。图中的矩形表示卷积层、池化层等。

GoogLeNet 的特征是,网络不仅在纵向上有深度,在横向上也有深度(广度)。
GoogLeNet 在横向上有“宽度”,这称为“Inception 结构”,以图8-11所示的结构为基础。如下图所示,就是通过多种不同大小的卷积核提取到的信息整合到一起。
Concatenation n. 一连串有关联的事件、想法或事物

ResNet
我们已经知道加深层对于提升性能很重要。但是,在深度学习中,过度加深层的话,很多情况下学习将不能顺利进行,导致最终性能不佳。ResNet 中,为了解决这类问题,导入了“快捷结构”(也称为“捷径”或“小路”)。导入这个快捷结构后,就可以随着层的加深而不断提高性能了(当然,层的加深也是有限度的)。
如图8-12所示,快捷结构横跨(跳过)了输入数据的卷积层,将输入 x 合计到输出。以下是快捷结构的具体步骤。


ResNet 以前面介绍过的 VGG 网络为基础,引入快捷结构以加深层,其结果如图8-13所示。

如图8-13所示,ResNet 通过以2个卷积层为间隔跳跃式地连接来加深层。另外,根据实验的结果,即便加深到150层以上,识别精度也会持续提高。并且,在 ILSVRC 大赛中,ResNet 的错误识别率为3.5%(前5类中包含正确解这一精度下的错误识别率),令人称奇。
迁移学习
实践中经常会灵活应用使用 ImageNet 这个巨大的数据集学习到的权重数据,这称为迁移学习,将学习完的权重(的一部分)复制到其他神经网络,进行再学习(fine tuning)。比如,准备一个和 VGG 相同结构的网络,把学习完的权重作为初始值,以新数据集为对象,进行再学习。迁移学习在手头数据集较少时非常有效。
关于分布式学习
假设我们有一个深度学习模型,需要在大量数据上进行训练。我们可以使用以下步骤进行分布式学习:
-
数据划分:将数据集划分为4个子集,每个子集分配给一个 GPU。
-
并行训练:每个 GPU 独立地在其分配的数据子集上进行训练。
-
参数更新:每个 GPU 完成一轮训练后,将更新后的参数发送到中心节点。
-
参数同步:中心节点收集所有 GPU 的参数更新,并计算新的全局参数。
-
参数分发:中心节点将新的全局参数分发给各个 GPU,开始下一轮训练。
如何使用分布式学习——用框架
-
TensorFlow:支持多 GPU 和多机器的分布式学习。
-
CNTK:微软的深度学习框架,也支持分布式学习。
图像分割
有人提出了一个名为 FCN(Fully Convolutional Network)[37](Jonathan Long, Evan Shelhamer, and Trevor Darrell(2015): Fully Convolutional Networks for Semantic Segmentation. In The IEEE Conference on Computer Vision and Pattern Recognition (CVPR))的方法。该方法通过一次 forward 处理,对所有像素进行分类(图8-20)。
FCN 的字面意思是“全部由卷积层构成的网络”。相对于一般的 CNN 包含全连接层,FCN 将全连接层替换成发挥相同作用的卷积层。在物体识别中使用的网络的全连接层中,中间数据的空间容量被作为排成一列的节点进行处理,而只由卷积层构成的网络中,空间容量可以保持原样直到最后的输出。
如图8-20所示,FCN 的特征在于最后导入了扩大空间大小的处理。基于这个处理,变小了的中间数据可以一下子扩大到和输入图像一样的大小。FCN 最后进行的扩大处理是基于双线性插值法的扩大(双线性插值扩大)。FCN 中,这个双线性插值扩大是通过去卷积(逆卷积运算)来实现的(细节请参考 FCN 的论文[37])。

图像标题的生成
有一项融合了计算机视觉和自然语言的有趣的研究,该研究如图8-21所示,给出一个图像后,会自动生成介绍这个图像的文字(图像的标题)。

一个基于深度学习生成图像标题的代表性方法是被称为 NIC(Neural Image Caption)的模型。
如图8-22所示,NIC 由深层的 CNN 和处理自然语言的 RNN(Recurrent Neural Network)构成。RNN 是呈递归式连接的网络,经常被用于自然语言、时间序列数据等连续性的数据上。NIC 基于 CNN 从图像中提取特征,并将这个特征传给 RNN。RNN 以 CNN 提取出的特征为初始值,递归地生成文本。这里,我们不深入讨论技术上的细节,不过基本上 NIC 是组合了两个神经网络(CNN 和 RNN)的简单结构。基于 NIC,可以生成惊人的高精度的图像标题。我们将组合图像和自然语言等多种信息进行的处理称为多模态处理。多模态处理是近年来备受关注的一个领域。

RNN 的 R 表示 Recurrent(递归的)。这个递归指的是神经网络的递归的网络结构。根据这个递归结构,神经网络会受到之前生成的信息的影响(换句话说,会记忆过去的信息),这是 RNN 的特征。比如,生成“我”这个词之后,下一个要生成的词受到“我”这个词的影响,生成了“要”;然后,再受到前面生成的“我要”的影响,生成了“睡觉”这个词。对于自然语言、时间序列数据等连续性的数据,RNN 以记忆过去的信息的方式运行。
深度学习的未来
1. 图像风格变换
输入一张具有某种风格的图片,例如梵高的画,让网络学习其“风格矩阵”,然后给出另外一张图片,就能输出一张具有梵高风格的图片。
([39] Leon A. Gatys, Alexander S. Ecker, and Matthias Bethge(2015): A Neural Algorithm of Artistic Style. arXiv:1508.06576 [cs, q-bio] (August 2015).)
2. 图像的生成
先使用大量的图像进行学习,但在“画”新图像时不需要任何图像。比如,基于深度学习,可以实现从零生成“卧室”的图像。图8-24中展示的图像是基于 DCGAN 方法生成的卧室图像的例子。DCGAN 中使用了深度学习,其技术要点是使用了 Generator(生成者)和 Discriminator(识别者)这两个神经网络。Generator 生成近似真品的图像,Discriminator 判别它是不是真图像(是 Generator 生成的图像还是实际拍摄的图像)。像这样,通过让两者以竞争的方式学习,Generator 会学习到更加精妙的图像作假技术,Discriminator 则会成长为能以更高精度辨别真假的鉴定师。两者互相切磋、共同成长,这是 GAN(Generative Adversarial Network)这个技术的有趣之处。在这样的切磋中成长起来的 Generator 最终会掌握画出足以以假乱真的图像的能力(或者说有这样的可能)。
([41] Alec Radford, Luke Metz, and Soumith Chintala(2015): Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks. arXiv:1511.06434 [cs] (November 2015).)
3. 自动驾驶
基于 CNN 的神经网络 SegNet[42],可以像图8-25那样高精度地识别行驶环境。图8-25中对输入图像进行了分割(像素水平的判别)。观察结果可知,在某种程度上正确地识别了道路、建筑物、人行道、树木、车辆等。今后若能基于深度学习使这种技术进一步实现高精度化、高速化的话,自动驾驶的实用化可能也就没那么遥远了。
[42] Vijay Badrinarayanan, Kendall, and Roberto Cipolla(2015): SegNet: A Deep Convolutional Encoder-Decoder Architecture for Image Segmentation. arXiv preprint arXiv:1511.00561 (2015).
4. Deep Q-Network(强化学习)
就像人类通过摸索试验来学习一样(比如骑自行车),让计算机也在摸索试验的过程中自主学习,这称为强化学习(reinforcement learning)。强化学习和有“教师”在身边教的“监督学习”有所不同。
在使用了深度学习的强化学习方法中,有一个叫作 Deep Q-Network(通称 DQN)[44]的方法。该方法基于被称为 Q 学习的强化学习算法。
这里省略 Q 学习的细节,不过在 Q 学习中,为了确定最合适的行动,需要确定一个被称为最优行动价值函数的函数。为了近似这个函数,DQN 使用了深度学习(CNN)。
在 DQN 的研究中,有让电子游戏自动学习,并实现了超过人类水平的操作的例子。如图8-27所示,DQN 中使用的 CNN 把游戏图像的帧(连续4帧)作为输入,最终输出游戏手柄的各个动作(控制杆的移动量、按钮操作的有无等)的“价值”。之前在学习电子游戏时,一般是把游戏的状态(人物的地点等)事先提取出来,作为数据给模型。但是,在 DQN 中,如图8-27所示,输入数据只有电子游戏的图像。这是 DQN 值得大书特书的地方,可以说大幅提高了 DQN 的实用性。为什么呢?因为这样就无需根据每个游戏改变设置,只要给 DQN 游戏图像就可以了。实际上,DQN 可以用相同的结构学习《吃豆人》、Atari 等很多游戏,甚至在很多游戏中取得了超过人类的成绩。

本章所学的内容
- 对于大多数的问题,都可以期待通过加深网络来提高性能。
- 在最近的图像识别大赛 ILSVRC 中,基于深度学习的方法独占鳌头,使用的网络也在深化。
- VGG、GoogLeNet、ResNet 等是几个著名的网络。
- 基于 GPU、分布式学习、位数精度的缩减,可以实现深度学习的高速化。
- 深度学习(神经网络)不仅可以用于物体识别,还可以用于物体检测、图像分割。
- 深度学习的应用包括图像标题的生成、图像的生成、强化学习等。最近,深度学习在自动驾驶上的应用也备受期待。

