吴恩达深度学习专项课程学习心得

学习完Deep Learning的5项课程之后,记录一下课程的主要内容以及心得体会

前言

人工智能是现在最火的一个领域了,而深度学习与神经网络则是这一领域中运用最广泛的技术了。
为了让更多人能够快速的入门人工智能,Andrew Ng,也就是吴恩达,在17年中辞去百度的职务后,迅速创办了deeplearning.ai以及推出了一个新的系列的课程,即Coursera上的Deep Learning Specialization专项课程。
网易也和吴恩达联手推出了中文字幕版的学习课程。网络情况不好或者英语吃力的同学可以看网易的视频。

其实我是拖了很久,一直到寒假才有时间认真把这一门课程给刷完。虽然从16年底就开始接触了CNN,这一次比较系统的课程给我的收获是非常大的。

最让我惊喜的就是这门课的作业材料。虽然要花费非常大的时间去完成,但是作业的代码质量非常高,容易上手。而且后期复习起来会更加方便。

下面的笔记其实是结合我个人情况总结的。我也推荐大家自己参与这门课的学习,而不是通过收藏/复制他人的笔记亲自完成作业,会有更加深刻的理解。

按惯例,晒一波证书:

为什么要学习这门课

可能很多人都有这个疑问,现在网上关于AI的教程、讲座层出不穷,但质量确实良莠不齐。更多的则是一些商业公司打着AI的旗号来敛财的一种方式。
当然,国外也有很多优质的入门教程。比如fast.ai课程则是和吴恩达课程的思路相反——先不强调原理,而是通过代码来实现神经网络。这样的好处显而易见,初学者能够更有成就感。
作为想要深入研究AI的同学,也应当深入理解神经网络和深度学习背后的一些原理。那么学习吴恩达的这门课就是不二之选。

预备知识

很显然,你要会基础的Python编程,以及良好的英文能力。还需要一些求导和矩阵运算的知识。

掌握一些Python基本工具的使用也是很有必要的,比如 Jupyter NotebookNumpy之类。

学习资源

这门课,已经有很多人写过总结了。

国内的话比较详细的就是黄海广老师的吴恩达深度学习笔记,目前已经更新了所有的课程。这个“笔记”,更确切的说是课程的记录和翻译。比较详细,对你之后的复习回顾很有帮助。

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)开始y^=σ(wTx+b)\hat{y}=\sigma(w^Tx+b),其中xx是你的输入样本变量,wTw^T是权重参数,bb是偏置值,σ\sigma是sigmoid函数σ(z)=11+ez\sigma(z)=\frac{1}{1+e^{-z}},用于模拟神经元的非线性,我们之后会称作激活函数,是深度学习核心的元素之一。y^\hat{y}则为输出的预测。那么学习的过程则是调整它的两个参数wwbb

损失函数与梯度下降

为了衡量输出,我们需要定义损失函数。对于上面的问题,通常使用

L(y^,y)=ylog(y^)(1y)log(1y^)L(\hat{y},y)=-ylog(\hat{y})-(1-y)log(1-\hat{y})

接下来需要做的就是使这个损失函数值尽可能小,以达到和标签值一样的值。

梯度下降法(Gradient Descent),使用来优化凸函数(convex function)的方法,那么更新参数的过程就可以用公式表述:w:=wαJ(w,b)ww:=w-\alpha \frac{\partial J(w,b)}{\partial w},其中我们通常称α\alpha学习速率(Learning Rate),不能太大,也不能太小。玄学之一。【图】

计算图

计算图(Computation Graph),实际上并不复杂,就是把复杂的运算分解。如果你打算使用TensorFlow,那么你必须要了解这个概念。比如d=ab+cd=a*b+c这样一个非常简单的运算可以分解成一个乘法算子和加法算子。我们在计算dd的这个过程就是前向(Forward)计算,当我们需要做梯度下降优化的时候就需要做反向传播(Backpropagation),即从输出的值沿计算图反向计算偏导数更新节点参数。值得注意的是,并非所有的函数(算子)都有导数。整个深度学习的理论是基于在可微分计算上的。因此我们在自己设计损失函数或其它深度学习中的运算时一定要考虑可微的情况。【图】

向量化

向量化(Vectorization)和计算图一样,也是一个重要的概念。在深度学习的体系里面,我们一定需要有向量化的潜意识。通常我们在以前的编程中会使用for循环来完成一些计算,那么在DL中,我们需要尽可能把变量组成向量,因为向量在运算的时候可以加速运算,效率比单纯用两层for循环会快很多。当然,实现这样的转变其实很多时候不需要我们亲自去实现,Python中的numpy库提供了足够丰富的处理矩阵向量的函数。

第三周 浅层神经网络

浅层神经网络

现在我们知道了最基本的神经元的设计,就是上面的逻辑回归单元y^=σ(wTx+b)\hat{y}=\sigma(w^Tx+b)。将多个这样的单元堆叠起来,就可以实现一个浅层的神经网络,通常指一两层的。再多一些就会称之为多层感知机,MLP(Multi-Layer Perception),或者直接叫做深度网络了(虽然现在看起来并不“深”)。【图片】

激活函数

激活函数为神经元带来了非线性,这是区别传统的线性方式的地方。目前有很多常见的激活函数:sigmoid、ReLU、tanh、LeakyReLU。sigmoid最古老,在传统一些的二分类问题会用到。ReLU在现在的深度网络中会更常用到。它的函数表示也很简单relu(x)=max(0,x)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),即计算过程,通常可以用下面的一般公式来表示:

1
2
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)。他们的偏导数dwdb和他们的形状是一样的。对于Z[l], A[l], dZ[l]dA[l]则是(n[l],m)。m是输入个数。

参数和超参数

参数(Parameters)是指神经网络学习(优化)过程中调整的那些参数,如Wb。超参数(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%)

遇到这些问题,可以尝试不同解决方法。过拟合:更多的数据、正则化、更浅的网络;欠拟合:更深更复杂的网络、训练更长的时间、不同的优化函数。【图】

正则化

正则化是一种预防过拟合的方法,我们需要在损失里面加入正则项来作为惩罚,限制模型的学习能力。通常有L1L1范数:W1=W[i,j]\Vert{W}\Vert{_1}=\sum\vert{W[i,j]}\vert,以及L2L2范数:W2=W[i,j]2\Vert{W}\Vert{_2}=\sum\vert{W[i,j]}\vert{^2},可以通过WTWW^TW得到。对于简单的网络来说,以L2L2正则化举例,其损失函数就会呈现下面的形式

J(w,b)=1m(L(y(i),y(i)))+λ2mW[l]2J(w,b) = \frac{1}{m}\sum(L(y(i),y'(i))) + \frac{\lambda}{2m} \sum \Vert{W[l]}\Vert ^2

对应的,梯度下降权重更新的过程也会有一些差别。L2L2惩罚项通常被称为权重衰减(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)的伪代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Batch Gradient Descent (BGD)
for i in range (nb_epoches):
params_grad = evaluate_gradient(loss_function, data, params
params = params - learning_rate * params_grad

# Stochastic Gradient Descent (SGD)
for i in range(np_epochs):
np.random.shuffle(data)
for example in data:
params_grad = evaluate_gradient(loss_function, example, params)
params = params - learning_rate * params_grad

# mini-Batch Gradient Descent (mini-BGD)
for i in range(np_epochs):
np.random.shuffle(data)
for batch in get_batches(data, batch_size=50):
params_grad = evaluate_gradient(loss_function, batch, params)
params = params - learning_rate * params_grad

动量梯度下降

使用带有momentum的梯度下降可以加快学习过程,其核心思想是计算梯度的指数加权平均数,并利用该梯度更新

RMSprop

Root mean square prop

Adam

Adaptive momentum esitmation

学习速率衰减

Learning rate decay也是常用的技巧。在一个数据集上训练的时候,一开始我们需要使用较大的学习速率来使loss较快下降一段时间。到后面想要继续提高准确率时再调小学习率。通常我们基于训练的次数(epoch)来调整学习率,一个常用的公式如下,你可以在任何ImageNet训练的代码中发现这个技巧,通常从0.1开始衰减。

lr=lr_01+decay_rateepoch_numlr = \frac{lr\_0}{1+decay\_rate*epoch\_num}

第三周 调参、批归一化及框架

调参

参数调试,英文叫做tuning,是你在优化深度模型中一定会经历的一个过程。抛开模型内部的参数,有一些超参数是我们需要人为干预来设定的,例如:学习速率 Learning Rate、批大小 Mini-batch size、层数 Layers、隐藏单元个数 Hidden Units、优化函数的参数β\beta、正则化系数等等。

批归一化

在现代神经网络中,一个重要的技巧就是Batch Normalization,在一些框架中会把它称为BN层。思路非常简单,在上一层的输出后,先求小批的均值μB=1mi=1mxi\mu_B=\frac{1}{m}\sum_{i=1}^{m}x_i,接着求mini-batch的方差σB2=1mi=1m(xiμB)2\sigma_B^2=\frac{1}{m}\sum_{i=1}^m{(x_i-\mu_B)^2},然后归一化x^i=xiμBσB2+ϵ\hat{x}_i=\frac{x_i-\mu_B}{\sqrt{\sigma_B^2+\epsilon}},最后得到输出yi=γx^i+β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=jz(i))=ez(i)j=0kezk(i)P(y=j|z^{(i)})=\frac{e^{z^{(i)}}}{\sum_{j=0}^{k}e^{z_k^{(i)}}}

在训练的时候,相应的损失函数:L(y,y^)=yilogy^iL(y,\hat{y})=-\sum{y_i log\hat{y}_i},就是一个负的对数和log_sum

框架

为了更方便的实现深度学习的算法,目前有许多深度学习的框架:TensorflowPyTorchCaffe/Caffe2KerasMXNet 等。不能说是三足鼎立,但也是五花八门层出不穷。Keras最适合初学者上手,它的API非常友好;Tensorflow一家独大,但对初学者不是很友好,而且API非常复杂;PyTorch更灵活,介于Keras和Tensorflow之间;Caffe安装十分麻烦,不过使用起来还是不错的;MXNet的Gluon像是Keras,提供了非常友好的API。还有其它的框架如Darknet,YOLO就是基于此,以及百度的PaddlePaddle等等。Facebook最新为物体检测推出的框架Detectron,MaskRCNN就基于此开源。

这门课里主要讲解了Tensorflow,我认为比较重要的概念是张量(Tensor)、静态图(Graph) 和 Session。这里不详细展开。

框架只不过是工具,能够熟练的掌握一个工具就可以了。在研究领域真正的还是需要快速的实现自己的想法并进行验证和迭代。

第三门课 改善深层神经网络:超参数调试、正则化以及优化

第四门课 卷积神经网络

第一周 卷积神经网络基础

边缘检测

卷积网络,顾名思义,就是以卷积运算为基础的架构。

卷积在信号处理中是用来滤波的,在离散序列中s(t)=(xw)(t)=a=x(a)w(ta)s(t)=(x*w)(t)=\sum_{a=-\infty}^{\infty} x(a)w(t-a),然而在机器学习的任务里,通常我们需要处理的是多维的张量/矩阵,对于图像处理,在二维空间中同样能够定义卷积的运算。CS231n的课程上有一个Demo 演示了这个卷积的过程。通常,在CNN中,并不会把卷积核做翻转的处理。我想本质上是因为卷积核通常是对称的,因此对运算结果没有实质上的影响。

在图像处理的课程中,一个重要的运算就是边缘提取(Edge Detection),通常使用Sobel算子或Canny算子。通常大部分课程会以边缘检测为例,用来说明卷积神经网络中的卷积运算主要提取了输入图像的边缘信息。下面是一个x方向的Sobel算子。

Gx=[+101+202+101]G_x= \begin{bmatrix} +1 & 0 & -1 \\ +2 & 0 & -2 \\ +1 & 0 & -1 \end{bmatrix}

在CNN中,我们把这种矩阵称为核(Kernel),这其中的参数并非我们手动设置的,而是需要通过优化来得到的。

多维卷积运算

大小为(n,n)(n,n)的矩阵,与大小为(f,f)(f,f)的核进行卷积运算后,不难得到大小变成了(nf+1,nf+1)(n-f+1, n-f+1)

Padding(填充)和Stride(步长)也是常用的参数。Padding的作用是在原始图像周围填充(通常是0),这样在卷积完之后就可以得到大小和原始输入一样的结果,否则在进行了几次卷积之后,输出形状太小而包含不了有用的信息。在加上 pp 单位的 padding 之后,大小变成了(n+2pf+1,n+2pf+1)(n+2p-f+1, n+2p-f+1)。而Stride调整的是步长,即每次卷积核行进的距离。那么输出的形状就变成了(n+2pfs+1,n+2pfs+1)(\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的卷积

[未完待续]

打赏支持一下~