李宏毅2020机器学习课程笔记——Deep Learning(简单介绍/深度学习Deep的原因/训练技巧/Backpropagation)
Deep Learning
对Deep Learning的简单介绍
对深度学习的发展进行了简单的介绍,从中我知道了perceptron
(感知机),perceptron是最早神经网络,是一个linear model,其结构类似于我们现在的 logistic regression。
深度学习作为解决机器学习问题的方法之一,其步骤和机器学习是一样的,都是首先定义一个模型,如何评判这个模型的好坏,选出最优的function。
深度学习有很多的结构,这是因为神经元不同的链接方式,产生了多种多样的网络结构。比如,最基础的fully connected forward neural network(全连接网络)。
深度学习不同的结构也就是我们第一步中提到的model,我们要如何设计我们的结构?有多少层 hidden layer,每层 hidden layer的神经元有多少个?这基本都是靠我们的经验和直觉定义的。
在向前发展的过程中,可以让机器自己学习如何建立网络的结构。
针对第二步,判断模型的好坏,仍是我们之前的方法-损失函数(交叉熵)
针对第三步,和之前的线性模型一样,也采用梯度下降的方法
深度学习为什么要Deep
在深度学习的过程中,我们可能认为参数越多,深度学习的效果越好,其实不是这样的。
在这个网络的参数一定时,我们设计一个高瘦的网络和一个矮胖的网络。相比之下,高瘦网络的训练效果会优于矮胖的网络。甚至,高瘦的网络在参数更少的情况下的训练结果也是优于参数相对多的矮胖的网络。
经过上述案例的分析,我们也就得到,当网络在向深度方向进行设计时,得到的效果会更好一点。
从更加深度的方向进行解释为什么Deep, 就是因为深度学习的Modularization 模组化 。
就像写程序一样,shallow network实际上就是把所有的程序都写在了同一个main函数中,所以它去检测不同的class使用的方法是相互独立的;而deep network则是把整个任务分为了一个个小任务,每个小任务又可以不断细分下去,以形成modularization,就像下图一样:
在DNN的架构中,实际上每一层layer里的neuron都像是在解决同一个级别的任务,它们的output作为下一层layer处理更高级别任务的数据来源,低层layer里的neuron做的是对不同小特征的检测,高层layer里的neuron则根据需要挑选低层neuron所抽取出来的不同小特征,去检测一个范围更大的特征;neuron就像是一个个classifier ,后面的classifier共享前面classifier的参数。
这样做的好处是,低层的neuron输出的信息可以被高层不同的neuron重复使用,而并不需要像shallow network一样,每次在用到的时候都要重新去检测一遍,因此大大降低了程序的复杂度。
老师举了一个非常形象的两个例子:
-
长发女生,短发男生…的分类
从上图可以看出长发男生的数据量非常的少,则在训练过程中的效果可能就不会很好。
但是我们把网络加深,如下图所示,我们先提取特征是短发/长发还是男孩女孩,再对这些特征进行组合训练来进行具体的分类。简单理解来说,就是把复杂的问题简单化,把小问题各个击破之后,大问题也就迎刃而解了。 -
剪窗花
上图这个例子就是将数据transform 到另一个空间在进行学习,剪窗花也是现将纸进行折叠(折几折,怎么折----定义一个function set);折好之后,再根据训练数据告诉要怎么剪。
同时,从上图中我们污染了窗花,也就是这个黄色点,这个折纸,高维空间上的1个点等于二维空间上的5个点,相当于1笔data发挥出5笔data的作用。
总结:模组化既可以解决某些情况下难以分类的问题,又能够以比较有效率的方式充分利用data。
深度神经网络的训练技巧
主体内容:针对training set和testing set上的performance分别提出针对性的解决方法:
- 在training set上准确率不高:new activation function(新的**函数):ReLU、Maxout ;adaptive learning rate(调整学习率):Adagrad、RMSProp、Momentum、Adam
- 在testing set上准确率不高:Early Stopping、Regularization or Dropout
![]()
当我们结束了基本的三个步骤后,我们接下来要做什么?
- 提高model在training set上的正确率
先检查training set的performance其实是deep learning一个非常unique的地方,如果今天你用的是k-nearest neighbor或decision tree这类非deep learning的方法,做完以后你其实会不太想检查training set的结果,因为在training set上的performance正确率就是100,没有什么好检查的。
有人说deep learning的model里这么多参数,感觉一脸很容易overfitting的样子,但实际上这个deep learning的方法,它才不容易overfitting,我们说的overfitting就是在training
set上performance很好,但在testing set上performance没有那么好;只有像k nearest neighbor,decision tree这类方法,它们在training set上正确率都是100,这才是非常容易overfitting的,而对deep learning来说,overfitting往往不会是你遇到的第一个问题。 因为你在training的时候,deep learning并不是像k nearest neighbor这种方法一样,一训练就可以得到非常好的正确率,它有可能在training set上根本没有办法给你一个好的正确率,所以,这个时候你要回头去检查在前面的step里面要做什么样的修改,好让你在training set上可以得到比较高的正确率。
- 提高model在testing set上的正确率
假设现在你已经在training set上得到好的performance了,那接下来就把model apply到testing set上,我们最后真正关心的,是testing set上的performance,假如得到的结果不好,这个情况下发生的才是Overfitting,也就是在training set上得到好的结果,却在testing set上得到不好的结果, 那你要回过头去做一些事情,试着解决overfitting,但有时候你加了新的technique,想要overcome overfitting这个problem的时候,其实反而会让training set上的结果变坏;所以你在做完这一步的修改以后,要先回头去检查新的model在training set上的结果,如果这个结果变坏的话,你就要从头对network training的process做一些调整,那如果你同时在training set还有testing set上都得到好结果的话,你就成功了,最后就可以把你的系统真正用在application上面了。
在真正训练的时候,一定要确定好是training set上的问题,还是在testing set上的问题。必须要先想清楚现在的问题到底是什么,然后再根据这个问题去找针对性的方法。
上图是对不同问题场景下对应的解决办法的总结,下面一一进行说明
在training set 上出现准确率不高
如何在Training data上得到更好的performance,可有如下解决办法:New activation
function和Adaptive Learning Rate
New activation function:
当**函数为sigmod时,会产生梯度消失的问题。这是因为根据sigmod的函数形状我们可以看出,在参数产生巨大影响时 sigmod将其缩小在一定范围了,随着深度的递进,这些变化的影响也逐渐减小了,导致梯度的变化不够明显,你会觉得已经找到了最优位置,就停掉了训练,导致你的参数没有得到充分的训练,还是更靠近最开始的初始值。
上面这个问题的原因不是overfitting,而是Vanishing Gradient(梯度消失),解释如下:
当你把network叠得很深的时候,在靠近input的地方,这些参数的gradient(即对最后loss function的微分)是比较小的;而在比较靠近output的地方,它对loss的微分值会是比较大的。 因此当你设定同样learning rate的时候,靠近input的地方,它参数的update是很慢的;而靠近output的地方,它参数的update是比较快的。 所以在靠近input的地方,参数几乎还是random的时候,output就已经根据这些random的结果找到了一个local minima,然后就converge(收敛)了. 这个时候你会发现,参数的loss下降的速度变得很慢,你就会觉得gradient已经接近于0了,于是把程序停掉了,由于这个converge,是几乎base on random的参数,所以model的参数并没有被训练充分,那在training data上得到的结果肯定是很差的。
由于sigmod对input的压缩,产生的梯度消失,提出了Relu:
选择ReLU的理由如下:
- 跟sigmoid function比起来,ReLU的运算快很多
- ReLU的想法结合了生物上的观察( Pengel的paper )
- 无穷多bias不同的sigmoid function叠加的结果会变成ReLU
- ReLU可以处理Vanishing gradient的问题( the most important thing )
Relu的变体
Maxout:
学习率learning rate的调整
-
Adagrad
-
RMSProp
-
Momentum
-
Adam(RMSProp + Momentum)
在testing set 上没有较高的准确率
提前停止训练
在训练过程中,找到testing set上训练最好的点,之后保留结果即可。
注:很多时候,我们所讲的“testing set”并不是指代那个未知的数据集,而是一些已知的被你拿来做测试之用的数据集,比如kaggle上的public set,或者是你自己切出来的validation set。
Regularization
- L2 regularization
regularization term可以是参数的L2 norm(L2正规化),所谓的L2 norm,就是把model参数集 θ \theta θ 里的每一个参数都取平方然后求和,这件事被称作L2 regularization,即
L2 regularization具体工作流程如下:
- 我们加上regularization term之后得到了一个新的loss function L ′ ( θ ) = L ( θ ) + λ 1 2 ∣ ∣ θ ∣ ∣ 2 L'(\theta)=L(\theta)+\lambda \frac{1}{2}||\theta||_2 L′(θ)=L(θ)+λ21∣∣θ∣∣2
- 将这个loss function对参数 w i w_i wi求微分: ∂ L ′ ∂ w i = ∂ L ∂ w i + λ w i \frac{\partial L'}{\partial w_i}=\frac{\partial L}{\partial w_i}+\lambda w_i ∂wi∂L′=∂wi∂L+λwi
- 然后update参数 w i w_i wi: w i t + 1 = w i t − η ∂ L ′ ∂ w i = w i t − η ( ∂ L ∂ w i + λ w i t ) = ( 1 − η λ ) w i t − η ∂ L ∂ w i w_i^{t+1}=w_i^t-\eta \frac{\partial L'}{\partial w_i}=w_i^t-\eta(\frac{\partial L}{\partial w_i}+\lambda w_i^t)=(1-\eta \lambda)w_i^t-\eta \frac{\partial L}{\partial w_i} wit+1=wit−η∂wi∂L′=wit−η(∂wi∂L+λwit)=(1−ηλ)wit−η∂wi∂L
如果把这个推导出来的式子和原式作比较,你会发现参数 w i w_i wi在每次update之前,都会乘上一个 ( 1 − η λ ) (1-\eta \lambda) (1−ηλ),而 η \eta η和 λ \lambda λ通常会被设为一个很小的值,因此 ( 1 − η λ ) (1-\eta \lambda) (1−ηλ)通常是一个接近于1的值,比如0.99,;也就是说,regularization做的事情是,每次update参数 w i w_i wi之前,不分青红皂白就先对原来的 w i w_i wi乘个0.99,这意味着,随着update次数增加,参数 w i w_i wi会越来越接近于0
Q:你可能会问,要是所有的参数都越来越靠近0,那最后岂不是 w i w_i wi通通变成0,得到的network还有什么用?
A:其实不会出现最后所有参数都变为0的情况,因为通过微分得到的 η ∂ L ∂ w i \eta \frac{\partial L}{\partial w_i} η∂wi∂L这一项是会和前面 ( 1 − η λ ) w i t (1-\eta \lambda)w_i^t (1−ηλ)wit这一项最后取得平衡的
使用L2 regularization可以让weight每次都变得更小一点,这就叫做Weight Decay(权重衰减)
- L1 regularization
除了L2 regularization中使用平方项作为new term之外,还可以使用L1 regularization,把平方项换成每一个参数的绝对值,即
L1 regularization的工作流程如下:
- 我们加上regularization term之后得到了一个新的loss function: L ′ ( θ ) = L ( θ ) + λ 1 2 ∣ ∣ θ ∣ ∣ 1 L'(\theta)=L(\theta)+\lambda \frac{1}{2}||\theta||_1 L′(θ)=L(θ)+λ21∣∣θ∣∣1
- 将这个loss function对参数 w i w_i wi求微分: ∂ L ′ ∂ w i = ∂ L ∂ w i + λ s g n ( w i ) \frac{\partial L'}{\partial w_i}=\frac{\partial L}{\partial w_i}+\lambda \ sgn(w_i) ∂wi∂L′=∂wi∂L+λ sgn(wi)
- 然后update参数 w i w_i wi: w i t + 1 = w i t − η ∂ L ′ ∂ w i = w i t − η ( ∂ L ∂ w i + λ s g n ( w i t ) ) = w i t − η ∂ L ∂ w i − η λ s g n ( w i t ) w_i^{t+1}=w_i^t-\eta \frac{\partial L'}{\partial w_i}=w_i^t-\eta(\frac{\partial L}{\partial w_i}+\lambda \ sgn(w_i^t))=w_i^t-\eta \frac{\partial L}{\partial w_i}-\eta \lambda \ sgn(w_i^t) wit+1=wit−η∂wi∂L′=wit−η(∂wi∂L+λ sgn(wit))=wit−η∂wi∂L−ηλ sgn(wit)
这个式子告诉我们,每次update的时候,不管三七二十一都要减去一个 η λ s g n ( w i t ) \eta \lambda \ sgn(w_i^t) ηλ sgn(wit),如果w是正的,sgn是+1,就会变成减一个positive的值让你的参数变小;如果w是负的,sgn是-1,就会变成加一个值让你的参数变大;总之就是让它们的绝对值减小至接近于0
对比 L1和L2:
我们来对比一下L1和L2的update过程:
L
1
:
w
i
t
+
1
=
w
i
t
−
η
∂
L
∂
w
i
−
η
λ
s
g
n
(
w
i
t
)
L
2
:
w
i
t
+
1
=
(
1
−
η
λ
)
w
i
t
−
η
∂
L
∂
w
i
L1: w_i^{t+1}=w_i^t-\eta \frac{\partial L}{\partial w_i}-\eta \lambda \ sgn(w_i^t)\\ L2: w_i^{t+1}=(1-\eta \lambda)w_i^t-\eta \frac{\partial L}{\partial w_i}
L1:wit+1=wit−η∂wi∂L−ηλ sgn(wit)L2:wit+1=(1−ηλ)wit−η∂wi∂L
L1和L2,虽然它们同样是让参数的绝对值变小,但它们做的事情其实略有不同:
- L1使参数绝对值变小的方式是每次update减掉一个固定的值
- L2使参数绝对值变小的方式是每次update乘上一个小于1的固定值
因此,当参数w的绝对值比较大的时候,L2会让w下降得更快,而L1每次update只让w减去一个固定的值,train完以后可能还会有很多比较大的参数;当参数w的绝对值比较小的时候,L2的下降速度就会变得很慢,train出来的参数平均都是比较小的,而L1每次下降一个固定的value,train出来的参数是比较sparse的,这些参数有很多是接近0的值,也会有很大的值。
在之前所讲的CNN的task里,用L1做出来的效果是比较合适的,是比较sparse的。
Dropout
在training的时候,每次update参数之前,我们对每一个neuron(也包括input layer的“neuron”)做sampling(抽样) ,每个neuron都有p%的几率会被丢掉,如果某个neuron被丢掉的话,跟它相连的weight也都要被丢掉
实际上就是每次update参数之前都通过抽样只保留network中的一部分neuron来做训练
Dropout真正要做的事情,就是要让你在training set上的结果变差,但是在testing set上的结果是变好的。
在使用dropout方法做testing的时候要注意两件事情:
- testing的时候不做dropout,所有的neuron都要被用到
- 假设在training的时候,dropout rate是p%,从training data中被learn出来的所有weight都要乘上(1-p%)才能被当做testing的weight使用