学习完Deep Learning的5项课程之后,记录一下课程的主要内容以及心得体会
前言
人工智能是现在最火的一个领域了,而深度学习与神经网络则是这一领域中运用最广泛的技术了。 为了让更多人能够快速的入门人工智能,Andrew Ng,也就是吴恩达,在 17 年中辞去百度的职务后,迅速创办了 deeplearning.ai 以及推出了一个新的系列的课程,即 Coursera 上的 Deep Learning Specialization 专项课程。 网易也和吴恩达联手推出了中文字幕版的学习课程。网络情况不好或者英语吃力的同学可以看网易的视频。
其实我是拖了很久,一直到寒假才有时间认真把这一门课程给刷完。虽然从 16 年底就开始接触了 CNN,这一次比较系统的课程给我的收获是非常大的。
最让我惊喜的就是这门课的作业材料。虽然要花费非常大的时间去完成,但是作业的代码质量非常高,容易上手。而且后期复习起来会更加方便。
下面的笔记其实是结合我个人情况总结的。我也推荐大家自己参与这门课的学习,而不是通过收藏/复制他人的笔记。亲自完成作业,会有更加深刻的理解。
按惯例,晒一波证书:
为什么要学习这门课
可能很多人都有这个疑问,现在网上关于 AI 的教程、讲座层出不穷,但质量确实良莠不齐。更多的则是一些商业公司打着 AI 的旗号来敛财的一种方式。 当然,国外也有很多优质的入门教程。比如 fast.ai 课程则是和吴恩达课程的思路相反 —— 先不强调原理,而是通过代码来实现神经网络。这样的好处显而易见,初学者能够更有成就感。 作为想要深入研究 AI 的同学,也应当深入理解神经网络和深度学习背后的一些原理。那么学习吴恩达的这门课就是不二之选。
预备知识
很显然,你要会基础的 Python 编程,以及良好的英文能力。还需要一些求导和矩阵运算的知识。
掌握一些 Python 基本工具的使用也是很有必要的,比如 Jupyter Notebook,Numpy 之类。
学习资源
这门课,已经有很多人写过总结了。
国内的话比较详细的就是黄海广老师的 吴恩达深度学习笔记,目前已经更新了所有的课程。这个 “笔记”,更确切的说是课程的记录和翻译。比较详细,对你之后的复习回顾很有帮助。
GitHub 上也有很多人总结了,比如 DeepLearning.ai-Summary,也有单纯搬运了课程材料的(虽然不推荐大家这么做,因为违反了 Coursera 的荣誉准则):https://github.com/JudasDie/deeplearning.ai
推特网友 Tess Ferrandez 做了一份非常漂亮的 手绘 笔记:notes-from-coursera-deep-learning-courses-by-andrew-ng,我猜这哥们一定是个学艺术的。
第一门课 神经网络和深度学习
第一周 深度学习引言
为什么使用深度学习
深度神经网络的模型对于大规模的数据有着更好的效果,这也是其优于传统的机器学习的方法最重要的特征。所以在有足够标注数据集的时候,使用深度学习的方法会比较合适。如果数据量不大,那么可能使用传统的机器学习算法如 SVM 就 OK 了。
第二周 神经网络的编程基础
逻辑回归(Logistic Regression)
这门课从最基础的逻辑回归(Logistic Regression)开始 $\hat {y}=\sigma (w^Tx+b)$,其中 $x$ 是你的输入样本变量,$w^T$ 是权重参数,$b$ 是偏置值,$\sigma$ 是 sigmoid 函数 $\sigma (z)=\frac {1}{1+e^{-z}}$,用于模拟神经元的非线性,我们之后会称作 激活函数,是深度学习核心的元素之一。$\hat {y}$ 则为输出的预测。那么学习的过程则是调整它的两个参数 $w$ 和 $b$。
损失函数与梯度下降
为了衡量输出,我们需要定义 损失函数。对于上面的问题,通常使用 $$ L(\hat{y},y)=-ylog(\hat{y})-(1-y)log(1-\hat{y}) $$ 接下来需要做的就是使这个损失函数值尽可能小,以达到和标签值一样的值。
梯度下降法(Gradient Descent),使用来优化凸函数(convex function)的方法,那么更新参数的过程就可以用公式表述:$w:=w-\alpha \frac{\partial J(w,b)}{\partial w}$,其中我们通常称 $\alpha$ 为学习速率(Learning Rate),不能太大,也不能太小。玄学之一。【图】
计算图
计算图(Computation Graph),实际上并不复杂,就是把复杂的运算分解。如果你打算使用 TensorFlow,那么你必须要了解这个概念。比如 $d=a* b+c$ 这样一个非常简单的运算可以分解成一个乘法算子和加法算子。我们在计算 $d$ 的这个过程就是前向(Forward)计算,当我们需要做梯度下降优化的时候就需要做反向传播(Backpropagation),即从输出的值沿计算图反向计算偏导数更新节点参数。值得注意的是,并非所有的函数(算子)都有导数。整个深度学习的理论是基于在可微分计算上的。因此我们在自己设计损失函数或其它深度学习中的运算时一定要考虑可微的情况。【图】
向量化
向量化(Vectorization)和计算图一样,也是一个重要的概念。在深度学习的体系里面,我们一定需要有向量化的潜意识。通常我们在以前的编程中会使用 for 循环来完成一些计算,那么在 DL 中,我们需要尽可能把变量组成向量,因为向量在运算的时候可以加速运算,效率比单纯用两层 for 循环会快很多。当然,实现这样的转变其实很多时候不需要我们亲自去实现,Python 中的 numpy 库提供了足够丰富的处理矩阵向量的函数。
第三周 浅层神经网络
浅层神经网络
现在我们知道了最基本的神经元的设计,就是上面的逻辑回归单元 $\hat {y}=\sigma (w^Tx+b)$。将多个这样的单元堆叠起来,就可以实现一个浅层的神经网络,通常指一两层的。再多一些就会称之为多层感知机,MLP(Multi-Layer Perception),或者直接叫做深度网络了(虽然现在看起来并不 “深”)。【图片】
激活函数
激活函数为神经元带来了非线性,这是区别传统的线性方式的地方。目前有很多常见的激活函数:sigmoid、ReLU、tanh、LeakyReLU。sigmoid 最古老,在传统一些的二分类问题会用到。ReLU 在现在的深度网络中会更常用到。它的函数表示也很简单 $relu (x)=max (0,x)$。你可能会问:这函数在 0 处不连续,怎么求它的微分?解决办法就是强行在 0 处定义了一个值 0 作为其导数。使用了 ReLU 之后,大型神经网络在训练上速度会更快。tanh 更多的用在了 RNN 之中。LeakyReLU 会在 GAN 中经常出现。更多奇形怪状的激活函数可以在 Activation Function 中找到。通常在反向传播的时候需要用到激活函数的导数,这里就不具体推导他们了。
随机初始化
在初始化神经网络参数的时候我们也需要注意。如果全部初始化为 0,那么梯度很有可能就会是 0,就会得到恒为零的输出。也不能初始化为同样的数、较大的数。要充分考虑到激活函数及梯度的影响,否则学习过程会很慢。通常让初始化的数服从一个分布。在后面学习中,会接触到 Xavier Initialization 和 He Initialization 的初始化方法,使学习过程更快收敛。
深层神经网络
深层网络
将我们的浅层神经网络多叠加几层,就可以得到一个更深的网络,也就是我们常称作的深度神经网络(DNN)。我们将输入和输出层中间的称为隐藏层(Hidden Layer)。其前向传播的过程(Forward Propagation),即计算过程,通常可以用下面的一般公式来表示:
Z[l] = W[l]A[l-1] + B[l]
A[l] = g[l](A[l])
其中 l
代表层,所以 A [l-1]
为上一层的输出,W [l]
为当前层的权重,B [l]
为偏置。g [l]
为当前层的激活函数,直至输出这一层的输出 A [l]
。
我们需要会计算深度网络每一层参数的形状,和计算参数的数量。W
的形状(维度)是 (n [l],n [l-1])
,b
是 (n [l],1)
。他们的偏导数 dw
和 db
和他们的形状是一样的。对于 Z [l],
A [l]
, dZ [l]
和 dA [l]
则是 (n [l],m)
。m 是输入个数。
参数和超参数
参数(Parameters)是指神经网络学习(优化)过程中调整的那些参数,如 W
和 b
。超参数(Hyperparameters)是指人为设定的一些参数,像学习速率、迭代次数、隐藏层个数 L
、隐藏层单元个数 n
、激活函数的选择等都属于超参数的范畴。深度学习的又一个玄学之处。有时候改变一个参数会有意想不到的效果。
第二门课 改善深层神经网络:超参数调试、正则化以及优化
第一周 深度学习的实用层面
数据集的拆分
在深度学习中,数据集的拆分是实验的第一步,也是比较重要的一步。因为往往数据集会影响整个网络的训练。在一开始的时候,我和很多人一样分不清训练(Training)、验证(Validation/Dev)和测试集(Test)。
训练集是给你的模型训练的数据集,也应该是数据量最多的一个。验证集是在训练中验证你模型泛化能力的集合。测试集则是测试你模型能力的数据集,和验证集有些许重合。那么通常来说,你的数据量在 100 到 100 万,通常按 60/20/20 划分,如果大于 100 万了,就 98/1/1 或 99.5/0.25/0.25。当然,这不是绝对的,而是表明在数据量足够大的时候,只需给验证集/测试集 1% 的数据量就足矣,测试集也可以不需要。你自需要保证你的模型见过训练集的数据和标签,没有见过验证/测试集的标签就行。还有很重要的就是你的数据集必须是 相似或同样的分布,如果你的训练集和验证/测试集差别太大,那么这样评估模型是没有什么意义的。就比如我在 MNIST 上训练了手写数字识别,用来检测生活场景下的数字,很明显是不合适的。对于更复杂的分类任务也是一样。【图】
过拟合与欠拟合
在我们训练神经网络的时候,通常会碰到一些情况(训练集/测试集误差): 过拟合(1%,11%)、欠拟合(15%,14%)、同时过拟合和欠拟合(15%,14%)、最好情况(0.5%,1%)
遇到这些问题,可以尝试不同解决方法。过拟合:更多的数据、正则化、更浅的网络;欠拟合:更深更复杂的网络、训练更长的时间、不同的优化函数。【图】
正则化
正则化是一种预防过拟合的方法,我们需要在损失里面加入正则项来作为惩罚,限制模型的学习能力。通常有 $L1$ 范数: $\Vert {W}\Vert {_1}=\sum\vert {W [i,j]}\vert$ ,以及 $L2$ 范数:$\Vert {W}\Vert {_2}=\sum\vert {W [i,j]}\vert {^2}$,可以通过 $W^TW$ 得到。对于简单的网络来说,以 $L2$ 正则化举例,其损失函数就会呈现下面的形式
$$ J(w,b) = \frac{1}{m}\sum(L(y(i),y'(i))) + \frac{\lambda}{2m} \sum \Vert{W[l]}\Vert ^2 $$
对应的,梯度下降权重更新的过程也会有一些差别。$L2$ 惩罚项通常被称为权重衰减(weight decay)。目前主流的深度学习框架都支持损失函数里加入 weight decay
的参数。具体正则化为什么能够降低过拟合就不在这里详细展开了。可以参考 Goodfellow 的《深度学习》。
Dropout
Dropout 是现在神经网络中经常使用的一种方法,通过随机屏蔽一些权重点来防止网络过拟合。在训练中将权重点随机屏蔽后,相当于你在训练一个更小的网络,那么经过多个训练批次(epoch)之后,你就相当于训练了多个子网络,近似 Bagging 集成,会使结果的鲁棒性更好,因为训练过程没有用到全部的参数点,间接降低了模型过拟合的可能。十分需要注意的是:Dropout 只用于训练,对于推断(Inference)的过程,我们需要把 Dropout 关掉。像 VGG 之类的网络就使用了 Dropout。【图】
其它方法
数据增强(Data Augmentation) 是常用的方法,特别是当你的训练样布不够多时,通过翻转、随机裁剪等方法可以得到近似但又不一样的数据用于训练。 预先停止(Early Stopping) 是指当你的模型在训练集和验证集上表现差异很大的时候,及时停止训练。模型集合(Model Ensembles) 通过训练多个模型,最后取平均预测输出的方法,通常能提升一些指标。但训练多个模型的代价就是花费的时间会很多。
归一化输入(Normalize Input)也是重要的一种方法,最简单的就是所有像素点的值除以255使所有像素点的值在 [0,1]
的区间中。在视觉任务中也会经常见到减去图像集的均值和除以方差,这样数据的均值和方差就为 0,这个过程能够让模型更快的拟合。
权重初始化
若选择了不合适的初始化参数,你很有可能会遇到 梯度消失/爆炸(Vanishing / Exploding gradients) 的问题。因此我们需要更合理的权重初始化方法。通常会选择 ReLU + 带方差的初始化。实际中比较常用的两种是 "He Initialization / Xavier Initialization"。
第二周 优化算法
Mini-batch梯度下降
Mini-batch梯度下降,顾名思义就是将你的训练集划分成分一个一个的小批 mini-batch,因为一次性训练整个训练集在数据量极大的情况下是不现实的。通常 mini-batch 大小的设置会是 2 的次方,因此你会经常见到 32,64,128,256...
的 batch 大小,你需要确保你的一个 batch 能够被装的进 CPU/GPU 的内存。Batch-size 同样也是一个需要调整的超参数,也不是越大越好,因为可能会让你的 GPU 耗尽显存 orz。
下面分别是 Batch Gradient Descent (BGD), Stochastic Gradient Descent (SGD) 和 mini-Batch Gradient Descent (mini-BGD) 的伪代码。
|
|
动量梯度下降
使用带有 momentum 的梯度下降可以加快学习过程,其核心思想是计算梯度的指数加权平均数,并利用该梯度更新
RMSprop
Root mean square prop
Adam
Adaptive momentum esitmation
学习速率衰减
Learning rate decay 也是常用的技巧。在一个数据集上训练的时候,一开始我们需要使用较大的学习速率来使 loss 较快下降一段时间。到后面想要继续提高准确率时再调小学习率。通常我们基于训练的次数(epoch)来调整学习率,一个常用的公式如下,你可以在任何 ImageNet 训练的代码中发现这个技巧,通常从 0.1 开始衰减。 $$ lr = \frac{\text{lr_0}}{1+\text{decay_rate}*\text{epoch_num}} $$
第三周 调参、批归一化及框架
调参
参数调试,英文叫做tuning,是你在优化深度模型中一定会经历的一个过程。抛开模型内部的参数,有一些超参数是我们需要人为干预来设定的,例如:学习速率 Learning Rate、批大小 Mini-batch size、层数 Layers、隐藏单元个数 Hidden Units、优化函数的参数$\beta$、正则化系数等等。
批归一化
在现代神经网络中,一个重要的技巧就是 Batch Normalization,在一些框架中会把它称为 BN 层。思路非常简单,在上一层的输出后,先求小批的均值 $\mu_B=\frac{1}{m}\sum_{i=1}^{m} x_i$,接着求 mini-batch 的方差 $\sigma_B^2=\frac{1}{m}\sum_{i=1}^m{(x_i-\mu_B)^2}$,然后归一化 $\hat{x}_i=\frac{x_i-\mu_B}{\sqrt{\sigma_B^2+\epsilon}}$,最后得到输出 $y_i=\gamma\hat{x}_i+\beta$ BN 之所以能够起到效果是和之前归一化输入差不多,保证一层在输入改变之后输出不会受巨大的影响,也起到了一定正则化的作用。
提一下最近 Kaiming He 的团队又提出了组归一化(Group Normalization):Wu, Yuxin, and Kaiming He. 2018. "Group Normalization". https://arxiv.org/abs/1803.08494.
Softmax 回归
在多分类问题的时候通常需要用到 Softmax,本质上是某一类的预测的概率分布。为了得到归一化的概率,首先以 e 为底数做乘方,保证输出大于 0,接着除以所有结果的和,得到归一化概率。 $$ P(y=j|z^{(i)})=\frac{e^{z^{(i)}}}{\sum_{j=0}^{k}e^{z_k^{(i)}}} $$ 在训练的时候,相应的损失函数:$L(y,\hat{y})=-\sum{y_i log\hat{y}_i}$,就是一个负的对数和 log_sum
框架
为了更方便的实现深度学习的算法,目前有许多深度学习的框架:TensorFlow、PyTorch、Caffe/Caffe2、Keras、MXNet 等。不能说是三足鼎立,但也是五花八门层出不穷。Keras 最适合初学者上手,它的 API 非常友好;TensorFlow 一家独大,但对初学者不是很友好,而且 API 非常复杂;PyTorch 更灵活,介于 Keras 和 TensorFlow 之间;Caffe 安装十分麻烦,不过使用起来还是不错的;MXNet 的 Gluon 像是 Keras,提供了非常友好的 API。还有其它的框架如 Darknet,YOLO 就是基于此,以及百度的 PaddlePaddle 等等。Facebook 最新为物体检测推出的框架 Detectron,Mask-RCNN 就基于此开源。
这门课里主要讲解了 TensorFlow,我认为比较重要的概念是张量(Tensor)、静态图(Graph)和 Session。这里不详细展开。
框架只不过是工具,能够熟练的掌握一个工具就可以了。在研究领域真正的还是需要快速的实现自己的想法并进行验证和迭代。
第三门课 结构化机器学习工程
TODO
第四门课 卷积神经网络
第一周 卷积神经网络基础
边缘检测
卷积网络,顾名思义,就是以卷积运算为基础的架构。
卷积在信号处理中是用来滤波的,在离散序列中 $s(t)=(x*w)(t)=\sum_{a=-\infty}^{\infty} x(a)w(t-a)$,然而在机器学习的任务里,通常我们需要处理的是多维的张量/矩阵,对于图像处理,在二维空间中同样能够定义卷积的运算。CS231n 的课程上有一个 Demo 演示了这个卷积的过程。通常,在 CNN 中,并不会把卷积核做翻转的处理。我想本质上是因为卷积核通常是对称的,因此对运算结果没有实质上的影响。
在图像处理的课程中,一个重要的运算就是边缘提取(Edge Detection),通常使用 Sobel 算子或 Canny 算子。通常大部分课程会以边缘检测为例,用来说明卷积神经网络中的卷积运算主要提取了输入图像的边缘信息。下面是一个 x 方向的 Sobel 算子。
$$ G_x= \begin{bmatrix} +1 & 0 & -1 \\ +2 & 0 & -2 \\ +1 & 0 & -1 \end{bmatrix} $$
在 CNN 中,我们把这种矩阵称为核(Kernel),这其中的参数并非我们手动设置的,而是需要通过优化来得到的。
多维卷积运算
大小为 $(n,n)$ 的矩阵,与大小为 $(f,f)$ 的核进行卷积运算后,不难得到大小变成了 $(n-f+1, n-f+1)$
Padding(填充)和 Stride(步长)也是常用的参数。Padding 的作用是在原始图像周围填充(通常是 0),这样在卷积完之后就可以得到大小和原始输入一样的结果,否则在进行了几次卷积之后,输出形状太小而包含不了有用的信息。在加上 $p$ 单位的 padding 之后,大小变成了 $(n+2p-f+1, n+2p-f+1)$。而 Stride 调整的是步长,即每次卷积核行进的距离。那么输出的形状就变成了 $(\frac{n+2p-f}{s}+1, \frac{n+2p-f}{s}+1)$,对于可能出现的小数情况,向下取整就行了。
对于图像,通常是三维的,输入 6x6x3
与 1 个 3x3x3
核运算可以得到 4x4x1
。更多的时候,可能我们会用 10 个 3x3x3
卷积核来拓展输出的维数,就会得到 4x4x10
的输出。现在,我们就可以计算论文中的卷积层的输出和对应的参数的个数了。
卷积层
现在来举个卷积层的例子,输入大小为 6x6x3
,经过 10 个 3x3x3
的核得到 4x4x10
输出,加上 10x1
的偏置 bias 和 RELU 激活函数,最终得到 4x4x10
的输出。参数数量为 (3x3x3x10) + 10 = 280
池化层
Pooling(池化) 层通常是来对前一层卷积后的结果进行降维操作的。通常有 Max Pooling 和 Average Pooling,前者用的多一些。Pooling 层是不含有可优化的参数的,因此有时候 Conv 和 Pool 会合并起来算一层。
全连接层
Fully connected(全连接) 通常用在最后降维及输出预测的结果,也是参数最多的一层。比如前一层的输出是 5x5x16
,我需要 5x5x16x120
的矩阵和它卷积得到 1x1x120
的输出,即 (120,1)
,实现了全连接层。这样它的参数(算上 bias)就有 5x5x16x120+1=48,001
,是非常大的一个值了。
若后面再跟一个全连接 (84,1)
,则需要一个 (84,120)
的矩阵 W 和 (84,1)
的偏置 b,这样参数就有 84x120+1=10081
个。
因此在很多现代的网络中会避免使用全连接,比如 ResNet、GoogLeNet,采用 GAP (Global Average Pooling) 代替全连接层来降维。
第二周 深度卷积网络:实例探究
经典网络
LeNet-5, AlexNet, VGG 是比较有代表性的经典的卷积网络。
LeNet-5: Conv ==> Pool ==> Conv ==> Pool ==> FC ==> FC ==> softmax
AlexNet: Conv => Max-pool => Conv => Max-pool => Conv => Conv => Conv => Max-pool ==> Flatten ==> FC ==> FC ==> Softmax
VGG: 和AlexNet 相似,采用了更大的 filter 以及更深的网络层数。https://arxiv.org/abs/1409.1556
具体的可以参考 CS231n 的 case 以及它的 Lecture9 的 slide。
残差网络
核心思想:利用 shortcut/skip connection 结构来增加网络深度。在有了 skip connection 之后,最坏的情况是当前层没有学习到任何的有用参数,输出为 0 的话,后一层信息就直接等于了前一层。这样即使增加网络层数,也不见得会影响性能。
不过这种解释听上去很有道理,但还是挺玄学的。
Inception
Inception 是一个简洁高效的网络模型。不同于残差网络,其主要组成是称作 Inception Module。
Network in Network:顾名思义,网络中的网络,核心思想是用 1x1
的卷积
[未完待续]