Deep Learning⚓︎
约 6590 个字 预计阅读时间 33 分钟
Introduction⚓︎
在前面介绍的机器学习的训练中,找到合适的参数后,便可以通过模型得到一组较为准确的一组预测值 \(\bm{a}\)(向量


在机器学习中,我们通常为上图的计算过程赋予这些名称:
- 神经元(neuron):包括输入(通过权重和偏移对一组数据求和
) 、激活函数和输出- 网络参数 (network parameter)\(\theta\):神经元的所有权重和偏移
- 神经网络(neural network):由众多这样的神经元构成的集体,类似一张网(模拟人类的大脑)
- 这里的神经网络称为全连接神经网络(fully-connected neural network),这种网络的特点是每个神经元接受所有的输入,且都有各自的权重和偏移,因此相当灵活;但缺点是效率不高
- 层 (layer):相当于一次训练的过程
- 输入层(input layer):初始输入的数据集
- 隐藏层(hidden layer):中间的一排神经元
- 输出层(output layer):最后一层神经元,得到训练结果
多次训练意味着有多层的神经元,看起来就很“深”,因此称之为深度学习(deep learning)。
(有些过时的)例子

根据实际经验,随着层数的加深,训练结果的质量会不断提升,但也不是始终能够提升——到达一定层数后,虽然对于训练数据的预测更准确,但是对未来的预测结果的质量反而会下降,这种情况称为过拟合(overfitting)。因此,我们不会让层数一直深下去的,合适的层数需要通过直觉 (intuition) 和不断的试错 (trial and error) 得到。
Why Deep?⚓︎
现在来思考一个问题:我们为什么要搭建深层的神经网络,而不是更浅更“胖”的神经网络呢?

假设上面的两个神经网络具备相同数量的参数,那么究竟哪一个的表现更好呢?前人对此做过研究了,结果如下(表格前两列表示深层网络,后两列表示浅层网络,同一行的网络参数数量相等

- 随着层数(深度)的增加,神经网络的表现会越来越好
- 在参数数量相等的情况下,深层网络的表现显著优于浅层网络
- 同一层网络规模的增加(即变得更“胖”)并不一定会改善神经网络的表现
因此,深层网络可以用更少的参数达到和浅层网络相等甚至更好的表现,而更少的参数也意味着更不容易发生过拟合等问题,因此我们才会推崇“深度学习”。
但是,为什么更深意味着更好呢?在前面的学习中,我们知道理论上是可以只用一层神经元来表示任意的函数(模型

一些类比

用逻辑门构建一个 \(d\) 位奇偶校验器(这里是偶校验)
- 如果只用两层电路的话,那么需要 \(O(2^d)\) 个逻辑门(每一位都有 0 和 1 两种可能)
- 如果使用多层电路的话,电路图就如上所示,这时只需要 \(O(d)\) 个逻辑门就 OK 了

在编写一个大型项目的时候,我们通常不会将所有的功能都放在主函数中实现(类比一层很胖的神经元

在正式剪纸之前,我们往往会先将纸对折几次(类比深层的神经网络)后再开始剪,这样相比直接在纸上剪(类比浅层的神经网络
下面用一个简单的例子来说明深层的神经网络是如何高效表示出复杂的函数:
例子
假设训练时只用到一个数据 \(x\),通过不同神经元(偏移、权重、激活函数)的组合后,得到输出 \(a_1\),它是一个 2 段的分段函数。

然后将 \(a_1\) 作为第二层的输入,通过与第一层相同的神经元的计算后,得到 \(a_2\),它是一个 4 段的分段函数(至于为什么是 4 段,这点不难推导,所以这里就不解释了

然后将 \(a_2\) 作为第三层的输入,通过与前两层相同的神经元的计算后,得到 \(a_3\),它是一个 8 段的分段函数。

假如我们要表示一个 \(2^k\) 段的分段函数,如果使用上面的深层神经网络(每层只有两个神经元
注
- 当需要的函数是复杂而规整的话,深层的神经网络会优于相等数量参数下的浅层网络
- 当 \(y = x^2\) 时,深层的神经网络相比浅层的表现是指数级的好!
呼应上一讲的结尾:深度学习是一个可以让鱼和熊掌可以兼得的方法。
CNN⚓︎
接下来介绍一种非常有名的,主要为图像(image) 设计的神经网络架构——卷积神经网络(convolutional neural network, CNN)。我们将会从两个角度认识 CNN,对 CNN 有一个全面而深入的理解。
Neuron Version Story⚓︎
现在要训练一个用于图像分类(image classification) 的模型,它的输入和输出为:

- 所有输入图像都应是固定的尺寸,这里假设均为 100 x 100
- 由于是分类问题,所以输出是一个独热向量(可能包含成千上万的元素
) ,并且使用交叉熵来计算损失
我们将一张图像看作是一个三维的张量(tensor)(简单理解成多维数组

现在,将拉直后的一维向量丢给全连接神经网络:

假设网络的第一层有 1000 个神经元,由于有 100 x 100 x 3 = 30000 个输入项,所以第一个隐藏层就有 \(3 \times 10^7\) 个权重,因此产生了很多参数。前面提到过,虽然参数多意味着模型很灵活,但是这会带来过拟合的问题。所以全连接神经网络不太适合做图像处理相关的任务。现在我们来观察一下图像处理任务究竟有什么特性,以帮助我们设计出更合理的神经网络。
Observation and Simplification 1⚓︎
与其让神经元读取图像上的所有数据,不如让每个神经元读取部分图像区域上的数据,用于识别图像上的一些关键模式(patterns)。比如对于下面这张关于鸟的图片,机器可以从它的嘴巴、眼睛、爪子等特征(模式)来作出“这是一只鸟”的判断。实际上,人类也正是基于相似的方法来分辨图像。

基于上述发现,我们可以着手简化神经网络的设计——每个神经元仅读取某一小块区域上的图像数据,因此用到了更少的参数;而我们通常称这一小块区域为感受野(receptive field)。

注
- 不同神经元对应的感受野可以有重叠的地方
- 甚至不同神经元的感受野可以是相同的
- 但由于不同神经元的权重和偏移不同,因此即使对于相同的一块区域,输出也是不一样的

思考
不同的神经元可以有不同大小的感受野吗?
可以的(而且也很常见
感受野可以仅包含部分通道吗?
可以。在其他的网络架构中会有这样的调整,但在一般的 CNN 中不会考虑。
感受野可以不是方的(可以是矩形或其他形状)吗?一定是一块邻近的连续区域吗?
可以。根据自己对任务的理解,可以确定不同形状的感受野。
- 实际上,每个感受野都对应一组不同的神经元
- 而且,我们会让感受野覆盖整张图像,最经典的安排方法是:
- 先在图像左上角取图像的一部分作为感受野(但包含所有通道
) ,这块感受野的大小称为核尺寸(kernel size),一般设置为 3x3 - 然后移动这块感受野,从而得到新的感受野。每次移动都有规定的步幅(stride),一般设为 1 或 2。这可能会带来一些问题:
- 相邻的感受野之间会有重叠,这是正常现象。而且如果没有做到重叠的话,那就有可能会漏掉那些位于重叠区域的模式,从而影响图像分类的效果
- 感受野移动到边界的时候,可能会有一部分不在图像内。一般采取的做法是在图像外围填充(padding) 一圈
0
,也就是说不存在的部分用0
表示即可
- 先在图像左上角取图像的一部分作为感受野(但包含所有通道

Observation and Simplification 2⚓︎
另一个不难发现的事实是:对于不同的图像而言,相同的模式可能出现在图像的不同位置上。比如下面有两张关于鸟的图片,上图鸟嘴出现在图像左上方,而下图鸟嘴出现在图像中央位置。

因此,我们可以让某些神经元共享参数(parameter sharing),那么这些神经元识别的是同一种模式,但针对的是不同的感受野,这样可以在确保识别不同位置上的模式的同时,还能减少一些参数。

前面提到过,每个感受野都对应一组不同的神经元,但其中一些神经元之间共享参数,我们称这样的神经元为滤波器(filter)。

总结
现在总结一下我们目前学到过的有关深度学习的知识:

从这张维恩图中可以看出,我们简单地认为:使用感受野和参数共享这两项简化技术的全连接层就是卷积层(convolutional layer)。
- 虽然经过这两项简化技术后,卷积层的模型偏移会变得更大,但是相比全连接层而言,它还是更适合用于图像处理的任务
- 而全连接层的模型偏移较小,意味着灵活性高,可以胜任各种各样的任务,但这也意味着它没有特别擅长的事
Filter Version Story⚓︎
现在我们知道,图象在经过一些卷积层的处理后,就可以得到关于图像的分类结果;而且在卷积过程中会用到一些滤波器,这些滤波器一般都是规模为 3x3x 通道数量 的张量。为了方便后续的讨论,
- 假定通道数为 1,即处理的是黑白图像。
-
且图像的尺寸为 6x6,还有以下两个滤波器,里面的值就是模型的未知参数:
-
规定步幅 = 1,即滤波器一次只能走一格
滤波结果为:

观察第 1 个滤波器的特征,发现对角线上的值都是 1,这也就意味着这个滤波器寻找的是图像上对角线均为 1 的模式。


观察第 2 个滤波器的特征,发现中间列上的值都是 1,这也就意味着这个滤波器寻找的是图像上中间列均为 1 的模式。
我们将所有使用滤波器得到的结果汇总在一起,构成了一张特征图(feature map)。有多少个滤波器,这张特征图就有多少个通道。
一个神经网络中可以用多个卷积层,前一个卷积层的结果会作为下一个卷积层的输入。以上面的例子来说,假如有 64 个滤波器,那么就会得到一张有 64 个通道的特征图。我们可以将它看作是一张“图像”,传递到下一个卷积层中。而下一个卷积层中的滤波器可以是一个 3x3x64 大小的张量。

思考:为什么一般使用 3x3 大小的滤波器就够了
假如每个卷积层的滤波器的宽和高都是 3。在第一个卷积层得到的特征点之上使用滤波器(此时来到了第二个卷积层

总结
从神经元角度看 | 从滤波器角度看 |
---|---|
每个神经元仅考虑一块感受野 | 有一组用于检测小区域模式的滤波器 |
不同感受野下的神经元可能会共享参数 | 每个滤波器在输入图像上进行卷积操作 |
Pooling⚓︎
对图像的第三个发现是:对原图像的像素进行二次采样 (subsampling) 后,不会改变图像中的物体。比如对下面关于鸟的图片进行二次采样,仅选择部分像素点,得到一张缩小了的图像,但是图像中的鸟没有发生太大的变化,依然可以识别出来。

基于这一发现,我们在卷积神经网络中引入池化(pooling) 的概念。由于它没有任何权重(即无法“学习”


回到整个神经网络,比对池化前后的结果:

完整的 CNN 框架!

使用池化的最大原因是降低特征图的空间维度,减少计算量。但随着技术的发展,计算机的算力会越来越强,因此有时不必使用池化操作;而且过多的池化操作有可能会破坏原图像的特征,所以有些 CNN 甚至直接抛弃了池化(后面介绍的 Alpha Go 就是其中一个例子
Applications⚓︎
事实上,CNN 不仅可以用于图像处理,也能够完成其他领域内的任务,比如下围棋 (playing Go)——著名的 Alpha Go 用的就是 CNN。
- 之所以这样是可行的,是因为可以将棋盘看作一张 19x19 的图像,每个位置上只有 3 种值:黑子(用 1 表示
) 、白子(用 -1 表示) 、空(用 0 表示) - Alpha Go 内将棋盘划分为 48 个通道,每个通道表示不同的棋局
- CNN 根据当前棋盘上的棋局,输出下一次移动的位置(19x19 个类)
此外,围棋也符合一些上述对图像的观察:
-
图像上有一些区域更小的模式
- Alpha Go 在第一层上采用 5x5 大小的感受野
-
相同的模式可能会出现在图像的不同区域上
-
但显然不能在棋盘上进行所谓的“二次采样”,因为随便抽走某一列或某一行,棋局就可能会发生很大的变化。因此 Alpha Go 的 CNN 并没有使用池化。
更多的应用


CNN 的缺陷
如果对同一张图像进行缩放、旋转等操作,CNN 就无法识别出这些变换后的图像(有点笨
不过也有另一种方法:空间变换层 (spatial transformer layer) 能够解决这一问题。
Self-Attention⚓︎
之前我们介绍的模型,都只是将一个向量作为输入(比如 COVID-19 感染人数预测、图像处理等问题

Inputs and Outputs⚓︎
首先需要考虑的一个问题是:如何将实际问题的输入转化为一组向量的形式呢?来看下面几个例子:
例子
一个句子就是一个向量序列,而句子中的每个单词对应一个个的向量。将单词转化为向量的方式有:
-
独热编码(one-hot encoding):假如所有单词的个数为 n,那么我们就用一个大小为 n 的向量来表示每个单词,每个位置上的元素表示唯一的单词
-
这种方式简单粗暴,但是不仅占用内存空间大,而且无法体现出两个单词之间的联系,即没有考虑到语义信息
-
-
词嵌入(word embedding):语义关系越接近的两个单词,它们对应的向量值会更加接近,如下图所示:
在机器学习课程 HW2 中,我们将语音看作一段段的帧(25ms 的语音

我们可以将图上的每个节点看作一个向量,所以整张图就是一组向量。在实际应用中:
-
社交网络:
-
分子结构
而输出的内容则可以分为以下几种情况:
-
每个输入向量对应一个输出标签 (label)(即预测值)
例子
-
对于整组向量,输出一个标签
例子
-
由模型决定输出标签的数量(seq2seq,序列到序列)
不过之后我们只专注第一种输出情况,即对于 N 个向量的输入,输出相应的 N 个标量或类。
Self-Attention⚓︎
接下来考虑如何构建适用于向量序列输入的模型。
Naive Idea
既然 N 个向量输入对应 N 个输出,那么不妨让每个向量单独进入一个全连接层得到一个输出,如下图所示:

显然,这样有一个很大的缺陷:我们没有考虑到向量序列各个向量之间的联系,各个向量都是“各自为政”的。所以,如果有两个向量的值是相等的,那就会被视为意义相同的两个东西,从而产生两个相同的输出。
每个向量输入都有一个对应的全连接层,这个全连接层不仅应该接收对应的向量输入,同时也要顾及整个上下文(context) 中的向量。
-
这个上下文的范围可以是一个局部的窗口,这样就仅考虑与对应输入向量相邻的部分向量
-
也可以将整个向量序列都纳入考虑范围内,这样考虑得更全面些,比如用一个固定大小的窗口覆盖整个向量序列
- 但是向量序列的长度可长可短,所以不能简单地使用窗口来实现这一点
- 再说,就算有一个足够大小的窗口可以容纳所有向量,但是这样也意味着全连接层需要非常多的参数(\(n\) 个向量就会产生 \(n^2\) 个参数
) ,而过多的参数意味着很容易出现过拟合的问题
因此,这里引入一种更好的做法——使用一种名为“自注意”(self-attention) 的机制:
- 将输入向量送入全连接层之前,先让这些向量经过一种“自注意”的运算。对于每个输入向量,经过“自注意”运算后都会得到一个对应的输出,我们可以把这个输出看作是包含整个向量序列上下文信息的新向量,但同时也保留了原来的输入向量特征。
- 然后将这个新向量传给全连接层进行训练
这一过程如下图所示:

当然,在多层神经网络中,可以在每两个隐藏层之间塞一个自注意计算:

在自注意计算这个“盒子”的内部,输入和输出的关系如下所示:

其中输入既可以是最外面的向量序列,也可以是经过几层训练后得到的输出向量序列。现在我们就考虑某一个输出向量,比如 \(\bm{b^1}\),来认识一下自注意的计算过程。
首先,我们要计算 \(\bm{b^1}\) 对应的输入向量 \(\bm{a^1}\) 与其他输入向量的相关程度(用 \(\alpha\) 表示

注
下面我们会将主要考虑的输入向量称为查询(query),而把另外的向量看作键(key),所以它们分别对应的矩阵为 \(W^q\) 和 \(W^k\),与矩阵相乘的结果分别为 \(\bm{q}\) 和 \(\bm{k}\)。
- 点积(dot-product):相关程度 \(\alpha = \bm{q} \cdot \bm{k}\)(向量的点积,得到一个标量)
- 加法 (additive):将 \(\bm{q} + \bm{k}\) 的结果丢给 \(\tanh\) 函数计算,然后再经过一次转换(用矩阵 \(W\) 表示)得到 \(\alpha\)
下面我们仅考虑点积这一方法。
回到前面有 4 个输入的例子,先将 \(\bm{a^1}\) 作为查询,其他几个输入向量(也可以包括 \(\bm{a^1}\))作为键,计算相关程度 \(\alpha_{1, j}\ (j = 1, \dots, 4)\):

实际上,相关程度的值应该在 \([0, 1]\) 这一范围内,所以让这些相关程度值再经过一次 softmax 函数的运算,将它们的值映射到 \([0, 1]\) 上(用其他激活函数也没有问题

另外,对于每个输入向量,我们还要为它们计算第三个向量 \(\bm{v} = W^v \bm{a}\),然后将这个向量与刚刚经过 softmax 得到的相关程度值相乘,再将这些乘积相加(加权和

剩下的 \(\bm{b^2}, \bm{b^3}, \dots\) 等输出向量可以一起计算计算(并行计算
现在我们从矩阵的角度研究一般情况下的计算过程:
-
对于每个输入向量 \(\bm{a^i}\),需要得到三个向量 \(\bm{q^i}, \bm{k^i}, \bm{v^i}\),而这些向量分别通过三个矩阵 \(W^q, W^k, W^v\) 和 \(\bm{a^i}\) 相乘得到。与其让矩阵分别和单个的输入向量相乘,不如将这些向量拼在一起,形成一个矩阵,这样就将多次的矩阵 \(\times\) 向量的运算转化为一次的矩阵 \(\times\) 矩阵的运算,如下所示:
-
我们知道,计算相关程度 \(\alpha\) 的过程是一个向量乘法,但是我们也可以将其转化为一个矩阵乘法,一次性算出所有的相关程度值
-
先将所有的 \(\bm{k}\) 拼在一起,一次性计算某个查询下的所有相关程度:
-
然后将所有的 \(\bm{q}\) 拼在一起,这样就可以将所有的相关程度一次性算出来了!
-
-
同理,将所有的向量 \(\bm{v}\) 拼在一起,与相关程度构成的矩阵 \(A'\) 相乘,得到所有的输出向量:
综上,整个自注意的计算过程可以抽象为以下一系列的矩阵运算:

其中三个矩阵 \(W^q, W^k, W^v\) 是我们需要通过训练学习的参数。而由相关程度构成的(且经过 softmax 归一化处理后的)矩阵称作注意矩阵(attention matrix)。
Variants⚓︎
下面介绍一些自注意机制的变体。
Multi-head Self-Attention⚓︎

Positional Encoding⚓︎
Applications⚓︎
Speech⚓︎
Images⚓︎
Graphs⚓︎
Comparison⚓︎
Self-Attention v.s. CNN⚓︎
Self-Attention v.s. RNN⚓︎
评论区