上一篇文章2-5 李宏毅2021春季机器学习教程-类神经网络训练不起来怎么办(四)分类(Classification)介绍了机器学习训练神经网络时的第四个策略:分类时损失函数的选择。接下来本篇介绍批量标准化(Batch Normalization )这个技术。
目录
Changing Landscape
Feature Normalization
Considering Deep Learning
Testing
Comparison
Internal Covariate Shift?
To learn more ……
之前的内容讲过说我们能不能够直接改error surface 的 landscape,当 error surface 如果很崎岖的时候,它比较难 train时,那我们能不能够直接把山铲平,让它变得比较好 train 呢?
Batch Normalization 就是其中一个把山铲平的想法。
我们一开始就讲不要小看 optimization 这个问题,有时候就算你的 error surface 是 convex的,它就是一个碗的形状,都不见得很好训练。假设你的两个参数对 Loss 的斜率差别非常大,在w1这个方向上面的斜率变化很小,在w2这个方向上面斜率变化很大。
如果是固定的 learning rate,你可能很难得到好的结果,所以我们才说你需要adaptive 的 learning rate、 Adam 等等比较进阶的 optimization 的方法,才能够得到好的结果。
现在我们要从另外一个方向想:直接把难做的 error surface 改掉,看能不能够改得好做一点。在做这件事之前,也许我们第一个要问的问题是w1跟w2的斜率差很多的这种状况,到底是从什么地方来的?
假设我现在有一个非常非常非常简单的 model,它的输入是x1跟x2,对应的参数就是w1跟w2,它是一个 linear model,没有 activation function。
通过y=w1x1+w2x2+b计算y,然后计算 y 跟y hat之间的差距e,把所有 training data的e加起来就是你的 Loss,然后去 minimize 你的 Loss。那什么样的状况我们会产生像上面这样子比较不好 train 的 error surface 呢?
当我们对w1有一个小小的改变,比如说加上 delta w1的时候,那 L 也会有一个改变。那什么时候w1 的改变会对 L 的影响很小呢,即它在 error surface 上的斜率会很小呢?(1)一个可能性是当你的 input x1很小的时候,假设w1的值在不同的 training example 里面的值都很小,那因为x1是直接乘上w1,那它对 y 的影响也是小的,对 e 的影响也是小的,它对 L 的影响就会是小的。(2)反之,如果x2的值都很大,当你的w2有一个小小的变化的时候,它乘上了x2, 那 y 的变化就很大,那 e 的变化就很大,那 L 的变化就会很大,就会导致我们在 w 这个方向上变化的时候,把 w 改变一点点,error surface 就会有很大的变化。
所以既然在这个 linear model 里面,当我们 input 的 feature,每一个 dimension 的值,它的 scale 差距很大的时候,我们就可能产生像这样子的 error surface,就可能产生不同方向,斜率非常不同,坡度非常不同的 error surface。所以怎么办呢?我们有没有可能给feature 里面不同的 dimension,让它有同样的数值的范围。
如果我们可以给不同的 dimension同样的数值范围的话,那我们可能就可以制造比较好的 error surface,让 training 变得比较容易一点。其实有很多不同的方法,这些不同的方法,往往就合起来统称为Feature Normalization。
以下所讲的方法只是Feature Normalization 的一种可能性,它并不是 Feature Normalization 的全部。假设x1到xR是我们所有的训练资料的 feature vector。
我们把所有训练资料的 feature vector 统统都集合起来。那我们把不同笔资料即不同 feature vector同一个 dimension 里面的数值,把它取出来(上图的绿框),然后去计算某一个 dimension 的 mean(均值) u,standard deviation(标准差) sigma_i。
那接下来可以做normalization(标准化),也可以叫 standardization,不过我们这统称 normalization。我们把某一个数值x减掉这一个 dimension 算出来的 mean,再除以standard deviation,得到新的数值tilde{x};然后再把新的数值把它代回去。以下都用这个 tilde来代表有被 normalize 后的数值。那做完 normalize 以后有什么好处呢?
做完 normalize 以后,这个 dimension 上的数值就会平均是 0, variance是 1,所以这一排数值的分布就都会在 0 上下。对每一个 dimension都做一样的 normalization,就会发现所有 feature 不同 dimension 的数值都在 0 上下,那你可能就可以制造一个比较好的 error surface。
所以像这样子 Feature Normalization 的方式,往往对你的 training 有帮助,它可以让你在做 gradient descent 时,Loss 收敛更快一点,可以让你的训练更顺利一点。以上是 Feature Normalization的介绍。
tilde{x}代表 normalize 的 feature,把它代入 deep network 里面,去做接下来的计算和训练。
通过第一个 layer 得到 z1,那你有可能通过 activation function,不管是选 Sigmoid 或者 ReLU 都可以,然后再得到a1,然后再通过下一层等等,那就看你有几层 network 就做多少的运算。每一个 x 都做类似的事情。但是如果我们进一步来想,对w2来说,这边的a1 a2 a3,这边的z1 z2 z3,其实也是另外一种 input,如果这边 z1 的数值的分布仍然有很大的差异的话,那我们要 train 第二层的参数,会不会也有困难呢?对w2来说,这边的 a 或这边的 z 其实也是一种 feature,我们应该要对这些 feature 也做 normalization。
那如果你选择的是 Sigmoid激活函数,那可能比较推荐对 z 做 Feature Normalization,因为Sigmoid 是一个 s 的形状,那它在 0 附近斜率比较大,所以对 z 做 Feature Normalization,把所有的值都挪到 0 附近,那你到时候算 gradient 的时候,算出来的值会比较大。那不过因为你不一定用 sigmoid ,你也不一定要把 Feature Normalization放在 z 这个地方,如果是选别的,也许你选 a 也会有好的结果也说不定。总体而言,这个 normalization放在 activation function 之前或之后都可以的,在实作上可能没有太大的差别,那我们这边就是对 z 做一下 Feature Normalization。
那怎么对 z 做 Feature Normalization 呢?
那你就把 z想成是另外一种 feature ,我们这边有z1 z2 z3,我们算一下mean,这边的μ是一个 vector。同样我们也算一个 standard deviation。
接下来就把这边的每一个 z 都进行 Normalization。那这边的μ跟sigma都是矢量,所以除的意思是element wise 的相除,就是把这个两个矢量对应的 element 的值相除。做 Feature Normalization之后得到 z 的 tilde。接下来就看你爱做什么就做什么。通过 activation function得到其他 vector,然后再通过其他 layer 等,这样就可以了。所以这边我们做 Feature Normalization 后,改变了z的值,然后会改变这边 a 的值。
之前我们每一个tilde{x}1 tilde{x}2 tilde{x}3是独立分开处理的,但是我们在做 Feature Normalization 以后,这三个 example变得彼此关联了。我们这边 z1 只要有改变,接下来 z2 a2 z3 a3也都会跟着改变,所以要把这一整个 process,把这堆 feature 算出 μ 跟 sigma当做是 network 的一部分。我们现在有一个比较大的 network。之前的 network只输入一个 input,得到一个 output,现在有一个比较大的 network,它是输入一堆 input,用这堆 input 在这个 network 里面,要算出μ 和 sigma,然后接下来产生一堆 output。这个地方比较抽象。
那这边就会有一个问题了,因为你的训练资料里面的 data 非常多,现在一个 data set,benchmark corpus 都上百万笔资料, GPU 的 memory根本没有办法,把整个 data set 的 data 都 load 进去。在实践中,你不会让这一个 network 考虑整个 training data 里面的所有 example,你只会考虑一个 batch 里面的 example。举例来说,你 batch size 设 64,那这个巨大的 network就是把 64 笔 data 读进去,算这 64 笔 data 的μ和sigma ,对这 64 笔 data 做 normalization。因为我们在实践时只对一个 batch 里面的 data 做 normalization,所以这招叫做 Batch Normalization。
这个 Batch Normalization显然有一个问题,你一定要有一个够大的 batch才算得出μ和sigma。假设你 batch size 设 1,那你就没有必要计算。所以Batch Normalization适用于 batch size 比较大的时候,因为 batch size 比较大的话,那里面的 data就足以表示整个 corpus 的分布,那你就可以把本来要对整个 corpus做 Feature Normalization ,改成只在一个 batch做 Feature Normalization作为 approximation。
在做 Batch Normalization 时,往往还会有上图所示的设计。当算出tilde{z}后,接下来你会再乘上另外一个矢量γ,再加上β这个矢量,得到hat{z}。而你要把γ和β想成是 network 的参数,这是另外再被learn出来的。
那为什么要加上γ和β呢?有人可能会觉得说,如果我们做 normalization 以后,那tilde{z}的平均就一定是 0,那也许会给那 network 一些限制,这个限制可能带来负面影响,所以我们把γ和β加回去,然后现在 hidden layer 的 output平均不是 0 的话,就让 network 去learn这个 γ和β,来调整hat{z}的分布。
但讲到这边又会有人问,之前不是说做 Batch Normalization 就是为了要让每一个不同的 dimension的 range 都是一样吗,现在如果通过 γ和β调整分布,这样不会不同 dimension 的分布的 range 又都不一样了吗?有可能,但是你实际上在训练的时候,γ跟β的初始值会提前设定,把γ的初始值都设为 1,β的初始值都设为0。所以 network 在一开始训练时,每一个 dimension 的分布是比较接近的,也许训练到后来,已经训练够长的一段时间,已经找到一个比较好的 error surface,走到一个比较好的地方以后,那再把γ 跟 β慢慢地加进去。总之Batch Normalization往往对训练是有帮助的。
以上说的都是 training 的部分,接下来讲testing(有时候又叫 inference )。Batch Normalization 在 testing 的时候会有什么样的问题呢?
在 testing 的时候,如果今天你是在做作业,我们一次会把所有的 testing 的资料给你,所以你确实也可以在 testing 的资料上制造一个一个 batch。但是假设有系统上线,有一个真正的线上的 application,比如说你的 batch size 设 64,你一定要等 64 笔资料都进来才一次做运算吗,这显然是不行的。
但是在做 Batch Normalization 时,一个 normalization 后的 feature 进来,然后你有一个 z,你的 要减掉μ,除以sigma,这是用一个 batch 的资料算出来的。但在 testing 时根本就没有 batch,那我们要怎么算这个呢?
真正的实作上的解法是这个样子的,Batch Normalization 在 testing 的时候,你并不需要做什么特别的处理,PyTorch 帮你处理好了。在 training 时,如果你有做 Batch Normalization 的话,在 training 的时候,你每一个 batch 计算出来的μ 跟 sigma,都会拿出来算移动平均(moving average)。
如上图所示,你第一次取一个 batch 出来的时候,你就会算一个μ1,取第二个 batch 出来的时候,你就算个μ2,... ,一直到取第 t 个 batch 出来的时候,你就算一个μt。接下来你会算一个 moving average,你会把你现在算出来的μ的一个平均值,叫做μ bar,乘上某一个 factor p(是一个常数),这也是一个hyper parameter,需要自己调。
在 PyTorch 里面,p设为 0.1,然后加上 (1 - p)μt ,然后来更新μ的平均值,然后最后在 testing 的时候,你就不用算 batch 里面的 μ 跟 sigma。因为 testing 的时候,在真正 application 上没有 batch 这个东西,你就直接拿bar μ跟bar sigma,也就是μ 跟 sigma在训练时得到的 moving average,这个就是 Batch Normalization在 testing 的时候的运作方式。
那下图是从 Batch Normalization原始的文件上面截出来的一个实验结果,在原始的文件上还讲了很多其他的东西。举例来说,我们今天还没有讲的是,Batch Normalization 用在 CNN 上要怎么用呢?那你自己去读一下原始的文献也可以得到答案。
如上图所示,是原始文献上面截出来的一个数据。横轴代表的是训练的过程,纵轴代表的是 validation set 上面的 accuracy。
接下来的问题就是Batch Normalization为什么会有帮助呢?在原始Batch Normalization的 paper 里面,提出来一个概念internal covariate shift(训练集和预测集样本分布不一致的问题就叫做“covariate shift”现象) ,这个词汇是原来就有的,internal covariate shift这个词李宏毅老师认为是Batch Normalization 的作者自己发明的。作者认为在 train network 的时候会有以下这个问题,这个问题是这样的:
如上图所示,network 有很多层。
但是作者认为说,我们在计算 B update 到 B′ 的 gradient 的时候,这个时候前一层的参数是 A ,或者是前一层的 output 是 a 。那当前一层从 A 变成 A′ 的时候,它的 output 就从a 变成a′。但是我们计算这个 gradient 的时候,我们是根据这个 a 算出来的啊,所以这个 update 的方向,也许它适合用在 a 上,但不适合用在 a′ 上面。那如果说 Batch Normalization 的话,因为我们每次都有做 normalization,我们就会让 a 跟 a′ 的分布比较接近,也许这样就会对训练有帮助。
有一篇 paper 叫做How Does Batch Normalization,Help Optimization,然后它就打脸了internal covariate shift 的这一个观点。在这篇 paper 里面,他从各式各样的面向来告诉你internal covariate shift首先它不一定是 training network 的时候的一个问题,然后 Batch Normalization会比较好,可能不见得是因为它解决了 internal covariate shift。
那在这篇 paper 里面做了很多很多的实验,比如说他比较了训练的时候,这个 a 的分布的变化,发现不管有没有做 Batch Normalization,它的变化都不大。然后他又说,就算是变化很大对 training 也没有太大的伤害,不管你是根据 a 算出来的 gradient,还是根据 a′ 算出来的 gradient,方向居然都差不多。所以他告诉我们internal covariate shift可能不是 training network 的时候,最主要的问题它可能也不是,Batch Normalization 会好的一个的关键,那有关更多的实验,你就自己参见这篇文章。
为什么 Batch Normalization 会比较好呢,那在这篇 How Does Batch Normalization,Help Optimization 这篇论文里面,他从实验上和理论上,至少支持了 Batch Normalization,可以改变 error surface,让 error surface 比较不崎岖这个观点。
所以这个观点是有理论的支持,也有实验的佐证的。那在这篇文章里面呢,作者还讲了一个非常有趣的话,他觉得,这个 Batch Normalization 的 positive impact。因为他说,如果我们要让 network,这个 error surface 变得比较不崎岖,其实不见得要做 Batch Normalization,感觉有很多其他的方法,都可以让 error surface 变得不崎岖。那他就试了一些其他的方法,发现跟 Batch Normalization performance 也差不多,甚至还稍微好一点,所以他就讲了下面这句感叹。
他觉得说,positive impact of batchnorm on training可能是 somewhat serendipitous(可以翻译成偶然的),但偶然并没有完全表达这个词汇的意思,这个词汇的意思是说,你发现了一个什么意料之外的东西。举例来说,盘尼西林就是意料之外的发现,大家知道盘尼西林的由来就是,有一个人叫做弗莱明,然后他本来想要培养一些葡萄球菌,然后但是因为他实验没有做好,他的那个葡萄球菌被感染了,有一些霉菌掉到他的培养皿里面,然后发现那些培养皿那些霉菌会杀死葡萄球菌,所以他就发明了盘尼西林,所以这是一种偶然的发现。
那这篇文章的作者也觉得Batch Normalization 也和盘尼西林一样,是一种偶然的发现,但无论如何它是一个有用的方法。
那其实 Batch Normalization不是唯一的 normalization方法,下面列了几个比较知名的方法,有兴趣可以继续研究:
Batch Renormalization https://arxiv.org/abs/1702.03275
Layer Normalization https://arxiv.org/abs/1607.06450
Instance Normalization https://arxiv.org/abs/1607.08022
Group Normalization https://arxiv.org/abs/1803.08494
Weight Normalization https://arxiv.org/abs/1602.07868
Spectrum Normalization https://arxiv.org/abs/1705.10941
说明:记录学习笔记,如果错误欢迎指正!写文章不易,转载请联系我。