跳至主要内容
读书笔记约 42 分钟

《深度学习入门》读书笔记

围绕神经网络学习、反向传播、优化技巧与卷积神经网络,整理《深度学习入门》的核心知识。

#深度学习#反向传播#优化器#卷积神经网络
本文目录
  1. chap4 神经网络的学习
  2. 损失函数
  3. Mini-batch 学习
  4. 梯度法
  5. 超参数的概念
  6. 神经网络的学习步骤
  7. epoch
  8. chap5 误差反向传播法
  9. 反向传播
  10. Affine 层
  11. 神经网络的搭建是构建每一个层,然后组装
  12. 小结
  13. chap6 与学习相关的技巧
  14. SGD 的缺点
  15. Momentum
  16. AdaGrad(adaptive gradient)
  17. Adam
  18. 梯度消失
  19. Xavier 初始值
  20. ReLU 权重的初始值
  21. 基于 MNIST 数据集的权重初始值的比较
  22. Batch Normalization
  23. 正则化
  24. Dropout
  25. 超参数的验证
  26. 超参数的最优化
  27. 本章所学的内容
  28. chap7 卷积神经网络
  29. 计算输出图的大小
  30. 表示方法
  31. 批处理
  32. 池化层
  33. im2col 的原理
  34. 使用 im2col 进行池化操作
  35. CNN 的实现
  36. CNN 的可视化
  37. 本章所学的内容
  38. chap8 深度学习
  39. 加深层的动机
  40. VGG
  41. GoogLeNet
  42. ResNet
  43. 迁移学习
  44. 关于分布式学习
  45. 图像分割
  46. 图像标题的生成
  47. 深度学习的未来
  48. 本章所学的内容

参考资料:深度学习入门:基于 Python 的理论与实现 (斋藤康毅)

chap4 神经网络的学习

这一章讨论神经网络如何从数据中自动学习参数。在进入损失函数和梯度法之前,先从传统计算机视觉的人工特征提取说起。

想要让计算机识别一类图像,其实就是让其学习这类图像的共有特征。

而在计算机视觉领域,常用的特征量有以下几种:

  1. SIFT(scale-invariant feature transform, 尺度不变特征变换)

    invariant adj. 无变化的,不变的 n. 不变式,不变量

    是一种用于图像处理的算法,它通过在多尺度空间中检测极值点来识别稳定的兴趣点,并为每个点计算一个描述局部外观的128维特征向量,此向量对尺度、旋转和光照变化具有不变性,从而使得这些特征点可以在不同的图像条件下被可靠地匹配,广泛应用于物体识别、图像拼接等领域。

  2. SURF(speeded up robust features, 加速稳健特征)

    是一种高效的图像特征检测与描述算法,由 Bay 等人在2006年提出。它通过使用积分图像快速计算 Haar 小波响应来简化关键点检测和描述的过程,在保持对尺度、旋转变化的鲁棒性的同时显著提高了 SIFT 算法的速度。SURF 利用确定性的盒状滤波器代替高斯滤波器,并为每个检测到的关键点分配一个64或128维的描述子,该描述子能够捕捉关键点周围区域的梯度信息,适用于快速而准确的图像匹配和物体识别任务。

  3. HOG(histogram of oriented gradients, 方向梯度直方图)

    histogram n. 柱状图

    是一种用于描述图像局部形状特征的技术,通过在图像的小区域(细胞单元)内统计梯度的方向分布来捕捉边缘和轮廓信息,进而构成一个稳定的特征描述子,广泛应用于物体检测尤其是行人检测领域。HOG 特征对光照变化和阴影具有较好的鲁棒性,并且由于其简单性和有效性,在计算机视觉任务中占据重要地位。

使用这些特征量将图像数据转换为向量,然后对转换后的向量使用机器学习中的 SVM、KNN 等分类器进行学习。

  1. SVM(support vector machine, 支持向量机)

    是一种监督学习算法,旨在找到一个最优超平面以最大化不同类别数据点之间的间隔,从而实现分类;对于非线性数据,SVM 通过核函数将数据映射到高维空间来寻找线性分隔,其核心在于利用支持向量(即离决策边界最近的数据点)构建模型,具有良好的泛化能力,适用于高维空间和较小样本集的分类与回归任务。

  2. KNN(K-nearest neighbors, K-近邻算法)

    是一种简单直观的监督学习算法,它通过计算新样本与训练集中所有样本的距离,找出最接近的新样本的 K 个邻居,并依据这些邻居的多数类别来预测新样本的类别;其优点在于实现简单且无需显式训练过程,但对大规模数据集和高维数据效率较低,并且对 K 值的选择及数据尺度敏感。

深度学习有时也叫端到端学习(end-to-end machine learning)。

神经网络的优点是对所有的问题都可以用同样的流程来解决。比如,不管要求解的问题是识别数字5,还是识别狗,抑或是识别人脸,神经网络都是通过不断地学习所提供的数据,尝试发现待求解的问题的模式。也就是说,与待处理的问题无关,神经网络可以将数据直接作为原始数据,进行“端对端”的学习。

损失函数

书中介绍的常用损失函数如下:

1. 均方误差

E=12k(yktk)2E = \frac{1}{2} \sum_k (y_k - t_k)^2

2. 交叉熵误差

E=ktklnykE = - \sum_k t_k \ln y_k

yk 是神经网络的输出,tk 是正确解标签。并且,tk 中只有正确解标签的索引为1,其他均为0(one-hot 表示)。因此,实际上只计算对应正确解标签的输出的自然对数。比如,假设正确解标签的索引是“2”,与之对应的神经网络的输出是0.6,则交叉熵误差是−log0.6 =0.51;若“2”对应的输出是0.1,则交叉熵误差为−log0.1=2.30。也就是说,交叉熵误差的值是由正确解标签所对应的输出结果决定的。

上面计算的是单个数据的损失(例如手写数字识别,每一张图片输出的都是一个10维的向量,上式是把每个向量的元素相乘再相加),要求计算所有数据的损失之和,则损失函数变为下式。除以 N 可以得到单个数据的平均损失函数。

E=1NnktnklogynkE = - \frac{1}{N} \sum_n \sum_k t_{nk} \log y_{nk}

Mini-batch 学习

如果遇到大数据,数据量会有几百万、几千万之多,这种情况下以全部数据为对象计算损失函数是不现实的。因此,我们从全部数据中选出一部分,作为全部数据的“近似”。神经网络的学习也是从训练数据中选出一批数据(称为 mini-batch, 小批量),然后对每个 mini-batch 进行学习。比如,从60000个训练数据中随机选择100笔,再用这100笔数据进行学习。这种学习方式称为 mini-batch 学习。

计算电视收视率时,并不会统计所有家庭的电视机,而是仅以那些被选中的家庭为统计对象。比如,通过从关东地区随机选择1000个家庭计算收视率,可以近似地求得关东地区整体的收视率。这1000个家庭的收视率,虽然严格上不等于整体的收视率,但可以作为整体的一个近似值。

和收视率一样,mini-batch 的损失函数也是利用一部分样本数据来近似地计算整体。也就是说,用随机选择的小批量数据(mini-batch)作为全体训练数据的近似值。

梯度法

根据目的是寻找最小值还是最大值,梯度法的叫法有所不同。严格地讲,寻找最小值的梯度法称为梯度下降法(gradient descent method),寻找最大值的梯度法称为梯度上升法(gradient ascent method)。

x0=x0ηfx0x_0 = x_0 - \eta \frac{\partial f}{\partial x_0}

x1=x1ηfx1x_1 = x_1 - \eta \frac{\partial f}{\partial x_1}

η 表示更新量,在神经网络的学习中,称为学习率(learning rate)。学习率决定在一次学习中,应该学习多少,以及在多大程度上更新参数。

超参数的概念

像学习率这样的参数称为超参数。这是一种和神经网络的参数(权重和偏置)性质不同的参数。相对于神经网络的权重参数是通过训练数据和学习算法自动获得的,学习率这样的超参数则是人工设定的。一般来说,超参数需要尝试多个值,以便找到一种可以使学习顺利进行的设定。

神经网络的学习步骤

前提:神经网络存在合适的权重和偏置,调整权重和偏置以便拟合训练数据的过程称为“学习”。神经网络的学习分成下面4个步骤。

  1. 步骤1(mini-batch):从训练数据中随机选出一部分数据,这部分数据称为 mini-batch。我们的目标是减小 mini-batch 的损失函数的值。
  2. 步骤2(计算梯度):为了减小 mini-batch 的损失函数的值,需要求出各个权重参数的梯度。梯度表示损失函数的值减小最多的方向。
  3. 步骤3(更新参数):将权重参数沿梯度方向进行微小更新。
  4. 步骤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 方法,如下所示。

vαvηLW\mathbf{v} \leftarrow \alpha \mathbf{v} - \eta \frac{\partial L}{\partial \mathbf{W}}

WW+v\mathbf{W} \leftarrow \mathbf{W} + \mathbf{v}

和前面的 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)的方法,即随着学习的进行,使学习率逐渐减小。实际上,一开始“多”学,然后逐渐“少”学的方法,在神经网络的学习中经常被使用。逐渐减小学习率的想法,相当于将“全体”参数的学习率值一起降低。

hh+LWLW\mathbf{h} \leftarrow \mathbf{h} + \frac{\partial L}{\partial \mathbf{W}} \odot \frac{\partial L}{\partial \mathbf{W}}

WWη1hLW\mathbf{W} \leftarrow \mathbf{W} - \eta \frac{1}{\sqrt{\mathbf{h}}} \frac{\partial L}{\partial \mathbf{W}}

《深度学习入门》读书笔记图示

这里想表达的意思就是,再参数的迭代过程中,前面累计的变动幅度越大,后面让其变动的幅度就越小。

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的正规化。用数学式表示的话,如下所示。

μB1mi=1mxi\mu_B \leftarrow \frac{1}{m} \sum_{i=1}^{m} x_i

σB21mi=1m(xiμB)2\sigma_B^2 \leftarrow \frac{1}{m} \sum_{i=1}^{m} (x_i - \mu_B)^2

x^ixiμBσB2+ε\hat{x}_i \leftarrow \frac{x_i - \mu_B}{\sqrt{\sigma_B^2 + \varepsilon}}

《深度学习入门》读书笔记图示

正则化

过拟合:只能拟合训练数据,不能拟合训练数据以外的数据。而机器学习的目标是提高泛化能力。

原因: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―初始化时权重的标准差

simple_convnet.py

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 相同结构的网络,把学习完的权重作为初始值,以新数据集为对象,进行再学习。迁移学习在手头数据集较少时非常有效。

关于分布式学习

假设我们有一个深度学习模型,需要在大量数据上进行训练。我们可以使用以下步骤进行分布式学习:

  1. 数据划分:将数据集划分为4个子集,每个子集分配给一个 GPU。

  2. 并行训练:每个 GPU 独立地在其分配的数据子集上进行训练。

  3. 参数更新:每个 GPU 完成一轮训练后,将更新后的参数发送到中心节点。

  4. 参数同步:中心节点收集所有 GPU 的参数更新,并计算新的全局参数。

  5. 参数分发:中心节点将新的全局参数分发给各个 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、分布式学习、位数精度的缩减,可以实现深度学习的高速化。
  • 深度学习(神经网络)不仅可以用于物体识别,还可以用于物体检测、图像分割。
  • 深度学习的应用包括图像标题的生成、图像的生成、强化学习等。最近,深度学习在自动驾驶上的应用也备受期待。