深度学习入门(二十)阶段性总结4,调参总结

May 16, 2019


原创:岐山凤鸣,转载请注明本站域名

觉得不错不妨star/follow一下我的Github

Cifar-100心酸调参仓库:Ecohnoch:tensorflow-cifar100

寻找最佳学习率仓库:Ecohnoch:find best learning rate

Initialization初始化

DL界有一句名言: All you need is a bese init. If you can’t find the best init, try Batch Normalization.

当然这句话来自于2016年的ICLR论文 All you need is a best init

这篇论文提出了LSUV这种新的(在15年的时候算新的)CNN初始化方式,并且测试了CNN中不同的非线性激活和不同的初始化方式的结果,直观结果如下表:

image

虽然加了BN后的测试来看并没有差距太多,不过网络权重初始化依然是非常重要的调参的一个环节。

下面我来介绍一下TensorFlow中的一些Init API,以及适用的场景。

常数项初始化

  • tf.zeros_initializer()
  • tf.ones_initializer()

适用场景一般都是bias,例如DenseLayer中初始化偏置为0这种。

正态分布初始化

  • tf.random_normal_initializer()
  • tf.truncated_normal_initializer()

前者为标准正态分布,后者为截断正态分布,意思也就是截取一段正态分布的分布,当生成的值大于两个标准偏差的值后会被丢弃重新绘制,非常常用,很多小CNN网络都会带截断正态分布的。一般还会带一个参数stddev=0.01或附近。

均匀分布初始化

  • tf.random_uniform_initializer(minval=-10, maxval=10)
  • tf.uniform_unit_scaling_initializer()

前者需要指定最大值和最小值,后者不需要

  • tf.variance_scaling_initializer(scale=1.0, mode=’fan_in’, distribution=’normal)

这个mode可选fan_in、fan_out和fan_avg,控制计算标准差stddev

distribution为normal时是截断正态分布,uniform为均匀分布

默认情况下是截断正态分布

其他

  • tf.orthogonal_initializer()
  • tf.glorot_uniform_initializer()
  • tf.glorot_normal_initializer()
  • tf.contrib.layers.xavier_initializer()

第一个是正交矩阵随机数,在CNN中用的最多

第二个也称为Xavier uniform initializer, 均匀分布区间为[-sqrt(6 / (fan_in + fan_out)), + sqrt(6 / (fan_in + fan_out))],要注意!第二个和第四个是一个东西。

第三个的话是stddev = sqrt(2 / (fan_in + fan_out)) 的截断正态分布

测试分布形状的代码在我的研究神经网络初始化的Github仓库中: find_initializer

结果如下图所示,要注意glorot_normal_initializer和xavier_initializer是一个东西。

image

注意

需要注意的一点是,很多开发者有时候会告诉你无脑用xavier就可以了,不过在现在基本都是conv(no bias)-bn-relu的环境下,无脑xavier也许并不好,我是说不适用于这种情况,何凯明在调ResNet的时候,使用的he initializer,也就是msra初始化,很适用于上述这种结构,tf的写法即:

  • tf.contrib.layers.variance_scaling_initializer(dtype=tf.float32)

具体关于Xavier和He 的数学推导即为何适用于各种结构,可以参考这篇文章

学习率衰减

学习率及相关超参数调整是非常麻烦而且能对结果造成很大影响的。

很多人说DL学到半途都是调参侠,虽然说调参侠听起来贬义,而且没学到什么东西,但这是非常重要且内容很深的一个过程。何凯明大大都在调参上弄过很长时间,并且不同调参技巧确实能够对模型性能提高,又为什么不去了解呢?就在最近两年,各大顶刊的调参论文也非常的多,甚至19年ICLR的best paper也与其相关,不过现代的很多网络调参都需要大量的GPU的支持,普通玩家一般只要掌握部分原理原则性的东西,多做点实验就可以了。

关于最初最佳的lr可以参考这篇论文Cyclical Learning Rates for Training Neural Networks,以及我在【人脸识别】和【MNIST】上用TensorFlow测试的代码仓库Find Best Learning Rate

目前广为接受的参数来自以下论文:

Regularizing Neural Networks by Penalizing Confident Output Distributions

这些参数也主要应用在cifar系列,imagenet系列上,有两种策略:

batch_size optimizer lr-(epoch<60) lr-(epoch<120) lr-(epoch<160) lr-(epoch<200)
128 Nesterov Momentum 0.9 0.1 0.02 0.004 0.0008
batch_size optimizer lr-(epoch<150) lr-(epoch<225) lr-(epoch<300)
128 Nesterov Momentum 0.9 0.1 0.01 0.001

实际情况中,我个人的经验(不一定准确),在训练的时候,每100轮训练print上val loss,这样可以看看网络下没下降,要是到瓶颈了不下降,可以酌情将lr除以5或者10,然后跑十几个epoch后,继续降低lr,直到降到1e-6之后就差不多可以了,在达到瓶颈后降低lr,可以有效的提高测试acc。

下面说几个TensorFlow中的学习率衰减的方法:

方法一

手动调整,在模型训练时保存参数,训练到一定中途停掉,换学习率继续跑

方便指数 一颗星

危险指数 零颗星

方法二

将lr作为tf.placeholder(),然后每次sess在跑optimizer.minize或者apply_gradient的时候,把lr也手动带上,可以根据epoch进行调整。

方便指数 四颗星

危险指数 零颗星

参考方式如我的这个调参仓库的train里的lr_schedule写法:tensorflow-cifar100

方法三

使用tf自带的lr的API,有如下一些API:

  • tf.train.piecewise_constant
  • tf.train.inverse_time_decay
  • tf.train.polynomial_decay
  • tf.train.exponential_decay)
  • tf.train.natural_exp_decay
  • tf.train.cosine_decay
  • tf.train.linear_cosine_decay
  • tf.train.noisy_linear_cosine_decay

最上面那个就是梯度学习率,也就是global_step到了多少就计算多少次,我的人脸识别代码中就采用了这种技巧,可以看到代码中带有lr_steps和boundaries,即lr随着global_step的计算次数会更新为多少多少。

但也要注意,有时候会出现一些问题,如我在测试Cifar100时时长会出现各种学习率跳跃,原因不明,比较难操控。

不过也比较方便的说。

方便指数 四颗星

危险指数 三颗星

Warm Up

中文直译“热身”,即在网络最开始训练的时候采用小的learning rate,然后进行一段时间后,开始正常的lr schedule训练,大约能够提高1个多点的准确率,可以参考这个repo

这个非常简单,参考上述lr decay的三种方法,只要设置第一个epoch的lr很小,如0.001就可以了。

Label Smoothing

这是一种Regularization方法,假设原始的Ground Truth的分布为\(p_i\),那么进行Smoothing之后的分布为:

$p_i’ = p_i (1 - \epsilon) + \frac{\epsilon}{K} $

其中\(\epsilon\)是个很小的值,而K是类别,参考代码:label_smoothing

伪代码:

smooth_label(target, n_classes, epsilon):
    one_hot_target = one_hot(target, n_classes, value=1-epsilon)
    one_hot_target += epsilon / n_classes
    return one_hot_target

具体work的原理就是防止过拟合,防止因为label不smothing(也就是分布和想象里的不一样)产生的误差,所以人为的对Ground Truth的值增加了一些正则内容,这样网络就不会【过于信任】我们给的label,从而降低拟合程度。

但实际效果也就能增加1-2个点左右,不过类似的研究有很多。

ECCV2018里一篇论文Pairwise Confusion for Fine-Grained Visual Classification是在Label Smoothing的思想上进行的扩展。通过在label中加入confusion,从而在细粒度分类里产生比较好的效果(也就多一两个点的效果…不过对每个类别来说在88%的情况下都多了两三个点,说明还是很awesome的

其他

  • no bias decay

也就是只对Weights带L2正则,类似bias、BN的学习参数都不带。能够提高1-2个点。

  • BN \(\gamma\)初始置0

BN是个好东西,一般都把它带上,然后gamma值初始设0,初始化的时候训练的更快

  • FP16

用Float16代替原CNN中所有Float32的parameters,不能提高点,但可以大幅度提高速度…慎用。

  • metric learning

这是下一篇要单独讲的,CNNs本质上提取了图像的特征,那么如何进行度量,例如相似度,如何操作,例如不同类的尽量分开,都需要在度量学习的基础上进行做文章。