————————————————————————————
原文发表于夏木青 | JoselynZhao Blog,欢迎访问博文原文。
————————————————————————————
深度学习教程与实战案列系列文章
深度学习 | 绪论
深度学习 | 线性代数基础
深度学习 | 机器学习基础
深度学习 | 实践方法论
深度学习 | 应用
深度学习 | 安装conda、opencv、pycharm以及相关问题
深度学习 | 工具及实践(TensorFlow)
深度学习 | TensorFlow 命名机制和变量共享、变量赋值与模型封装
深度学习 | TFSlim介绍
深度学习 | TensorFlow可视化
深度学习 | 训练及优化方法
深度学习 | 模型评估与梯度下降优化
深度学习 | 物体检测
深度学习| 实战1-python基本操作
深度学习 | 实战2-TensorFlow基础
深度学习 | 实战3-设计变量共享网络进行MNIST分类
深度学习 | 实战4-将LENET封装为class,并进行分类
深度学习 | 实战5-用slim 定义Lenet网络,并训练测试
深度学习 | 实战6-利用tensorboard实现卷积可视化
深度学习 | 实战7- 连体网络MINIST优化
深度学习 | 实战8 - 梯度截断
深度学习 | 实战9- 参数正则化
深度网络优化方法
优化及调试策略:实践流程
- 确定目标:误差度量及期望目标
- 建立端到端流程:
数据读取、预处理流程
端到端模型构建:基准模型(传统方法、经典模型)vs 设计模型
性能度量、评测脚本
三个脚本先写好:数据处理、模型训练、评测脚本
- 搭建系统,确定性能瓶颈:
调通系统:是否出现正确结果
预期判断:过拟合、欠拟合?
调通系统:是否出现正确结果?
我们最关心的是泛化性能,所以评测脚本很重要,得出目前结果是过拟合还是欠拟合。
- 根据观察进行增量式改进:
收集新数据、调参、改进算法
错误来源: 算法本身还是编程实现
难点: 1. 无法预测算法行为;2. 模型很多地方自适应
重要的调试检测手段:
- 可视化计算中模型的行为(看中间,最后输出结果)
- 可视化最严重的错误(看做错的例子)
- 训练、测试误差分析:
训练低,测试高:过拟合、训练模型未正确加载
训练高,测试高:欠拟合、程序错误 - 缩小问题规模,测试程序正确性
- 可视化网络参数、梯度等
梯度消失与梯度爆炸
梯度消失: 随着网络层数的增加,网络从后往前,传回的梯度越来越小,导致之前的网络层“训不动”
图中三张图表示网络层数分别为2、3、4的情况,
越靠近前面的层,变化越小
梯度爆炸:梯度下降训练过程中遇到损失突变的位置(墙、悬崖 等形态),梯度瞬间增大,指向某处不理想的位置。可能现象: 训练无法收敛; 损失函数、权重:变化非常大 or 出现 NaN 值; 梯 度:每层的每个节点训练时梯度一直大于 1.0
链式法则? 在RNN中更常见。
问题本质:梯度的乘积积累
期望效果:连续乘积都刚好平衡大约等于 1,但几率很小
梯度爆炸更多来源于不合理的参数初始化及过大的学习率
梯度消失在更深的网络中更具有普遍性
梯度的乘积积累路径变长
激活函数的问题:例如 Sigmoid 易饱和,考虑采用 ReLU 等
消失和爆炸都在于累乘。
sigmoid 对信息的保存更好,更平和(Relu小于0的部分全都丢掉了)
Sigmoid 激活函数问题
1.exp() 计算复杂
2. 激活函数易饱和,梯度易消失:$z^(l−1) $偏大或偏小的地方 (-4>,>4) 接近 0,梯度传递接近 0 .
3.sigmoid 输出范围 (0 1), 非 0 中心,σ(zl−1) 总大于 0,由
权重更新呈现锯齿形
ReLU
不易出现激活函数饱和问题 (为什么), 计算高效
相比 sigmoid/tanh 收敛速度快 (为什么?)
问题: 依然是非 0 中心输出; 存在 dying ReLU 问题; 信息丢失
dead 节点连接的下面的 参数不再更新了。
激活函数输入 zn 小于 0,梯度也为 0,zn 之前权重 w 不能更新
权重未初始化好 (使得某些节点对所有样本输出都是负数),或训练过
程中,一股剧变梯度使得 w 分布突然改变在一定范围,使得 zn 输出一直小于0,w永远不更新。
参数初始化
- 优化结果优劣(不只是最小值问题,梯度学习行为)
- 训练是否可行(梯度消失、爆炸、训不动。。。)
参数设得大,容易炸。
破坏对称性:
随机初始化权重
不能 0 初始化(回顾之前可视化中的例子,为什么?)
随机初始化权重 0 初始化权重失败(回顾之前可视化中的例子)
权重大小:相对大的情况
大权重破坏对称性更强 +
有益于缓解梯度消失 +
梯度爆炸等不稳定问题-(权重累积存在反向传播链上)
激活函数易饱和-
不同层的缩放因子
一开始都是同样的01初始化,后面层的分布可能会更摘一点
偏置可以 0 初始化
偏置不会出现在链上,所以没有影响
随机、不能全0、不能太大也不能太小
LeNet 训练 MNIST
案例 1, 送分题:全 0 初始化 LeNet 导致参数不更新。为什么?
案例 2:错误的层间参数数值范围比例:初始化最后全连接层参数 均值 100,方差 10;其他层变量均值 0,方差 0.1。导致无法训练: 学习率太小,学不动;太大,爆炸,或不收敛。
权值大,则学习率应该大一点,才能匹配,不然学不懂。
训不动,但又没有炸,则可以抬高学习率。
参数随机初始化的问题:
10 层的神经网络,非线性变换为 tanh,每一层的参数都是随机正态分布,均值为 0,标准差为 0.01
训练后,每一层输出值分布的直方图
输出集中在 0 附近,对样本变化太敏感,网络表达能力差,且本层w 难以更新(反向传播公式)
均值仍然为 0,标准差现在变为 1
这样的网络近似为一个二值网络
输出集中在-1,1, 网络表达能力依然弱 (极端情况下,相当于 2 值网络),且 tanh 激活函数饱和,w 依然无法更新
无法更新的原因: 0值的地方穿不过来,1的地方饱和了。
问题出在哪里?
参数太大,饱和;太小,不激活:激活函数的特性(relu 也存在相 关问题)
如何配合激活函数特性初始化参数?
Xavier 或者 Glorot initialization 方案
基本思想: 保持输入和输出的方差一致,避免所有输出值都趋向于0
针对不同激活函数的 Xavier 初始化形式
不同激活函数对应的初始化区别主要是根号外的系数
Tensorflow 中可以根据公式手动缩放初始化值,也有类似的 xavier_initializer 初始化方法:
initializer = tf.contrib.layers.xavier_initializer()
W = tf.get_variable("W", shape=[784, 256], initializer= initializer)
w1 = tf.Variable(initializer(w1_shape))
大多数都是采用的这个初始化方法
ReLU 实验效果:xavier 初始化 (注意:下面写法是 node_in ≈ node_out 的近似)
另一种方差平衡初始化:He initialization
基本思想:ReLU 网络中,假定每层一半神经元被激活,另一半为 0 保持 variance 不变,需在 Xavier 的基础上再除以2
这样的效果更好一些。
node_in 和node_out 是什么?
学习率
学习率选择
- 过大: 振动、不收敛;过小: 收敛速度慢
- 理想的学习率设计是:前期较大学习率搜索,后期小学习率调优。
以及对参数的个性化调整:优化频率高的参数小学习率调整,优化频率低的参数大学习率调整。 - 初期选择 :指数级改变学习率观察 loss 变化
- 其他手段:
自适应学习率算法: Nesterov ,Adagrad , Adadelta , Adam… 热启动
有的网络会先以小学习率训练一段时间,再调大学习率,这个叫做热身。
蓝色,下坡缓慢,学习率太低了。
红色,是比较理想的情况
学习率不光影响学习的速率,还影响最后的收敛效果。
想调整成为红色,但可能会调整成为绿色。 收敛并不代表最优。
学习率对模型训练的影响
学习率大了之后,毛刺比较多。在lr=0.003的时候,收敛速度慢了一些,但较为平稳了。
lr = 0.00001的时候,训练效果是最好。 为了得到更好的效果,慢也得等。
学习率分析举例:LeNet 训练 MNIST
案例 1:lr(ε 步长)=0.0001;0.001;1 各是哪个loss?
橙色线对应着 0.0001 ,因为收敛得最为缓慢。
lr(ε 步长)=0.0001;0.001;1 各是哪个 w?
学习率越小,波动越大。
学习率衰减策略:learning rate decay 学习过程中,学习率并不是一成不变,而是根据学习的情况进行调整 开始大,快速收敛,后期小,优化收敛位置
学习率衰减策略很多,主要包括持续衰减和周期学习率两种,其代表: Exponential decay: 学习率随一定步长阶段指数减少,最常用
Cycle learning rate: 防止网络后期 lr 十分小导致一直在某个局部最小值 中振荡,突然调大 lr 可以跳出注定不会继续增长的区域探索其他区域
另外,很多时候,由于网络或模型中一些部件的特性,需要先用比较 小的学习率优化其参数 (例 BatchNorm 种的 moving avg),之后再开始 训练,对此类网络常先用一个较小学习率进行“Warm up”, 再用一般 学习率衰减策略
迭代次数
由于泛化界限的存在,数据在迭代训练时,会逐渐产生过拟合现象
变化趋势表现为:
- test loss 与 train loss 均缓慢下降并趋于平稳
- train loss 虽然下降,但 test loss 已经开始上升
- train loss 与 test loss 一直上升 (呈现 U 形)
提前终止(early stop)
- 一种常用的正则化形式
- 当验证集上的误差在事先指定循环次数内没有进一步改善时,停止训练算法
- 一般可取一个窗口的误差均值,当验证集上误差均值开始上升时,停止
将训练步数视作超一个超参数,提前终止通过控制拟合训练集的步数来控制模型的有效容量
LeNet 训练 MNIST
案例 1:总共只随机取 1000 个训练样本,缩小训练集的规模,人为制 造过拟合
问题:
- 为什么说现在的训练过拟合了?
- 为什么没有发生 U 型 test 上升?
训练收敛,训练 loss 没有进一步往下降, 过拟合就不再有进展。否则 loss 继续降,过拟合继续进展,呈现 U 型上升。
batch size
(batch size 和学习率的关系)
Batch 方差与 learning rate 关系
- Batch size 变大的效果:
梯度的均值越接近期望,方向更准
梯度的方差期望越小,估计更稳
学习率增大会增大梯度估计方差 - 另一方面,如果 batch size 比较大,可 以用比较大的 lr 而同时可以保持一定 的方差(保持证明见右公式),这样加 快学习。
Large-batch 魔咒
- Large batch 会有更准的梯度,方差越小,应该有更高的准确性
- Batch size 不见得越大越好**:large batch 可能会降低准确率!**
- ICLR 2017: ON LARGE-BATCH TRAINING FOR DEEP LEARNING: GENERALIZATION GAP AND SHARP MINIMA
(i) LB methods over-fit the model;
(ii) LB methods are attracted to saddle points;
(iii) LB methods lack the explorative properties of SB methods and tend to zoom-in on the minimizer closest to the initial point;
(iv) SB and LB methods converge to qualitatively different minimizers with differing generalization properties. - LB 陷入的是比较 sharp 的极值点,SB 陷入的是比较 flat 的极值点, 泛化性能更好
- 另一种声音:Facebook: Accurate, Large Minibatch SGD: Training ImageNet in 1 Hour
- LB 之所以不 work,不是泛化能力的问题,而是优化的问题
- 学习过程要维护好学习率和 batch size 的关系
- Linear Scaling Learning Rate:简单点:batch size 翻多少倍, learning rate 就翻多少倍
- LB 方法,开始用大的 learning rate,效果差;只要开始把 LR 设小, 之后逐步提高 LR 到正常大小,LB 能到跟 SB 几乎一样的 training curve、基本相同的准确度
linear scale rule
- baeline(batch size B) 走 k 步 (3), large batch(batch size kB) 走 1 步 (4) 更新公式比较 (#B = n):
(4):过一步相当于(3):过 k 步
为使更新效果一致,(4)的学习率应该再乘以 k 倍
在 weight 变化较快的时候,W(t+j) W(t) 不满足
故在开始变化很快的时候,一般不是 k 倍,后面逐渐放大
lr 也不能无限放大,batchsize 变大后,合适的 lr 范围在变小
large batch 问题一般在分布式训练中非常 large 的情景一般情况下,batch size 选取有以下建议
- 小 batch 训练,受噪音影响大,不容易收敛,学习慢;泛化误差较小;
- 大 batch 训练,梯度估计更准确,训练震荡越小,收敛速度快,但 是容易陷入局部最小(非凸函数优化);容易过拟合,过大的 large batch 准确率会低;
- 在实践过程中,在 GPU 显存容许的情况下,一般采用较大的 batch size。注意和 learning rate 的配合
解决毛刺大的方法: batch_size 调大,或者学习率调小
正则化
正则化技术概述
-
机器学习核心问题:如何提高泛化能力?
- 泛化:一个假设模型应用到新样本上的能力
- 欠拟合可直接从训练情况中观察到,并设法改进
- 如何有效防止过拟合行为:模型过度拟合了训练数据的噪音,导致 测试集性能不佳,泛化能力差
-
正则化:减少测试误差(可能以增加训练误差为代价),提高模型 泛化能力
-
广义讲,提高算法测试性能的一切手段都可以视为正则化技术
-
主要目标:减少泛化误差而不是训练误差
-
正则化技术在深度学习中的必要性:
- 小规模神经网络模型可以降低对数据量的需求并缓解过拟合,但是性能短板明显
- 收集数据集尚无法满足复杂模型避免过拟合的要求 - - 关键还是模型能力与问题复杂度、数据量的匹配方式 (regularization)
-
必须引入正则化以提升泛化能力
-
正则化主要手段:
- 数据处理:数据规范化技术/数据集扩充技术
- 目标牵引:目标函数正则化项
- 模型技巧:dropout 技术/batch normalization 技术
- 策略选择与具体任务相关
数据增强技术
主要包括两方面:
-
数据集扩充:问题是否被数据有效覆盖?
-
数据集规范化:数据形态是否可被现有模型有效学习?
-
数据集扩充与模型过拟合:
- 过拟合本质是模型相对问题复杂,但数据的不充分,会加剧这种不 匹配
- 完善数据,可以有效约束 (regularization) 过拟合的行为
- 过拟合本质是模型相对问题复杂,但数据的不充分,会加剧这种不 匹配
-
几何变换:丰富数据集中目标的变化,更多适应卷积网的特性
刚性几何变换:目标内部的相对空间关系不变旋转变换:当需要分类或被识别的目标可能存在多种不同角度时
尺度变换:当任务可能面对不同的尺度
水平/垂直翻转:任务没有更多旋转条件时,翻转更为常见
平移变换:未限制学习目标在视
非刚性几何变换:未来检测目标可能展示柔性的变化
例:薄样板条插值法 Thin Plate Spline(TPS)
TPS 是一种插值算法,常用于图像变形,以达到数据增广的作用。
TPS 功能强大,即可涵盖之前的刚性变换,放射变换,也可以产生 更多的柔性变换。
TPS 应用举例:人脸关键点预测
人脸、动物等可有一定柔性空间的关键点相关算法,可用 TPS 增强算法鲁棒性
- 截取:从输入裁剪出更多保持尺寸比例样本,或突出学习重点 应对网络输入的限制 (224x224),截取采样保持目标比例
方法:采样 (保持目标尺寸比例)、遮挡 (突出重点)
方法:随机截取、监督式截取
在细分类、关键点标注等任务中,遮挡对于提升局部敏感度有帮助
随机截取:难以控制截取内容
一些样本质量有限,有一定冗余,也存在一定错误样本
监督式截取:训练如何截取
- 随机初始化训练较为粗糙的类别激活模型
- 生成的特征图反映了图像内部各区域与标签的相关性
- 基于特征图截取样本训练高性能分类网络
- 加噪和色彩变换:
- 对颜色进行轻微的变化(如对每个像素点的 RGB 值乘以 0.95-1.05
间的随机系数) - 目标具有多样颜色的情况,但要控制色彩范围
- 对存在曝光过度或过暗的任务,增加亮度变换
- 对颜色进行轻微的变化(如对每个像素点的 RGB 值乘以 0.95-1.05
- 添加噪声可有效提升识别的鲁
相关工具 DeepAugment 等:pip install deepaugment
数据集规范化
从模型学习的角度,规范数据集数据的分布范围
规范的是数值,常有:零均值化,归一化,去相关 (白化)
减去均值之后,做归一化
零均值化:对数据集内所有样本,减去数据的均值
- 利用训练集得到图像均值
- 每个样本均减去图像均值
- 移除图像共同部分,凸显个体差异
- 从数据分布来说,使数据的中心点移到了原点
归一化:零均值化的数据除以归一化值 (例如标准差), 使数据分布范围 限制在固定范围,例如 (-1,1) 例如标准差规范化:
- 利用训练集得到图像的标准差
- 每个零均值化的样本均除以图像标准差
- 从数据分布来说,只是限制了分布的范围,并未改变分布形态,
例如不一定归一化后的就服从正太分布!
归一化减少模型偏置学习压力,零均值化使权值学习范围无明显偏向 零均值化和归一化后的数据和模型参数初始化范围已经接近,是比较 好的训练输入
但数据如果不做零均值化和归一化,模型训练将非常困难
去相关 (白化):去除数据相关性、降低冗余
- 数据中,不同维度之间存在一定的相关性,例如图像中目标局部 的一定相关性
- 训练时相关数据带来冗余,使得模型对区分特性不敏感
- 从数据分布来说,去相关改变了数据维度上的分布形态:1. 使数 据的不同维度去相关;2. 使数据每个维度的方差为 1;
- 常用:PCA 白化,ZCA 白化
维度去相关,并且维度上的方差为1 ,则为白化
PCA 白化: 应用 PCA 主成分分析,将数据在主成分特征向量上进行投 影,并用主成分特征代替原特征
ZCA 白化:PCA 白化后,数据原来的维度转换成了主成分维度,为了使 白化后的数据尽可能接近原数据,可以把处理过的数据再变换回原空 间,也就是 ZCA 白化。
白化效果举例:可见 ZCA 相比 PCA 更接近原数据 (相同的维度定义)
目标函数及正则化
目标函数:模型的学习引导
- 想让模型表达什么?分类、特征分布、流形提取、对抗。。
- 想让模型的表达有什么特点?稀疏、降低结构风险。。
目标函数的设计直接影响了学习效果
Siamense Net Loss 思路:正样本对的 Ew 要尽量小,负样本对的尽量大 也就是,Loss 要鼓励正样本对的 Ew 减小,鼓励负样本对的 Ew 增大
按此 loss 引导,网络学习的 Ew 最终会根据正负样本对两个阵营,形成 两个峰的分布。分类问题就变为对这两个聚集分布的划分:
Triplets Loss
区别:多类型的时候
- 假设不一样:CL 考察距离: 正例要求往一起集中 yd2,TL 考察
距离的差: 不要求集中,只要正例距离与负例距离差小就可以 - CL 限制了类间距 D 的范围,(d 比 a 大的时候反而会反向惩罚), 实际上会将不同类团进行聚集,实际对各个类型的分布有了潜在 的假设,同时拉动整个类型也会增加网络学习难度;
- TL 只要求正例与正例的距离小于正例与负例的距离 (考察的是 距离的差) 即可,更灵活适应多类分布。
Hard negative mining
- Hard negative mining: 难例!
- 难例:错误分类样本,而且预测的置信度很高
- 网络训好需要难例,尤其后期,简单样本已难以提升了:(回忆我 们正负例数据不平衡的时候的 Siamese 作业)
- 简单方案:找到难例,训一波。或者适当时候找难例,训一波
- CL:可以很快收敛;TL:会有坍塌现象(TL 梯度 han := f(xa) − f(xp),对于难例,很近,往哪个方向推?可能同时推了 f(xa), 原来 训好的也搞坏了)
- 问题:怎么定义难例?尤其后期训不动的时候找难例?
难例不仅是数据集未充分采集的结果: 样本缺乏、样本不平衡 更多时候,在训练过程中,模型会对大多数样本损失很小
- 不平衡的类型效果;
- 类中比较困难的样本
此时 gradient 被 easy example 支配,长期不改变
OHEM: online hard example mining
目标函数引导:根据不同样本的 loss 大小,增多 loss 比较大的样本的 学习机会
- class 比例加权重:最常用处理类别不平衡问题的方式
- OHEM: 只保留 loss 最高的那些样本,完全忽略掉简单样本 (梯度置为 0)
- OHEM+ 按 class 比例 sample:在前者基础上,再保证正负样本的比例
Focal Loss:
OHEM 需要根据 loss 选样本,再去对选择的样本进行训练
另一个角度,直接在 loss 上将难例的惩罚增大, 简单例的惩罚减小
在交叉熵上根据样本难度加权,(正样本难例 Pi 小,负样本难例 Pi 大)
参数正则化:
过拟合:模型相对问题复杂
增加数据可以减轻过拟合现象,但并不是解决根本问题
如何在不改变网络结构的情况下降低复杂度?
正则化:在目标函数中增加约束项损失函数:L(θ) → L(θ) + λ ∗ R(w) R(w): 正则化项, 常用 L1 范数:R(w) = ∥w∥21 = Σ|wi| 和 L2 范数: R(w) = ∥w∥2 = Σw2i
参数正则化目标:网络在降低样本损失的同时,偏好较小的权值
实际上牺牲了一些作对的样本,以换取泛化性能
L1 更易产生稀疏解 (解容易发生在轴上);L2 的解较平滑,较 L1 好解
L1、L2 正则化实例:手写数字分类(MNIST 数据集)
网络结构:全连接神经网络(3 隐层,节点数为 1500、1000、500)
目标函数:交叉熵目标函数
正则化方法:无正则化 (1.25%),L1(1.16%),L2(1.11%)
L1、L2 正则化技术的使用可以带来一定的性能改善 L1 正则化得到更加稀疏的权值矩阵
参数正则化技术 tensorflow 实现示例
#例 如, 对 所 有 参 数 进 行 正 则 化 #首先选定所有训练参数tv作为正则化对象
tv = tf.trainable_variables()
Regularization_term = lambda*tf.reduce_sum( [ tf.nn.l2_loss(v)
for v in tv ])
New_loss = original_loss + Regularization_term
train_step = tf.train.GradientDescentOptimizer(0.1).minimize(
New_loss)
#也 可 将 参 数 加 入 指 定 正 则 化 损 失 函 数 集 合, 加 入 的 同 时, 指 定 其 正 则 化 方 式 regularizer
regularizer = tf.contrib.layers.l2_regularizer(0.001) ...
tf.add_to_collection('reg_loss', regularizer(W_fc1)))) ...
#最终loss是原loss和l2正则项列表相加
loss = original_loss + tf.add_n(tf.get_collection('losses'))
weight_decay = tf.constant(0.0005, dtype=tf.float32) # your
weight decay rate, must be a scalar tensor.
W = tf.get_variable(name='weight', shape=[4, 4, 256, 512],
regularizer=tf.contrib.layers.l2_regularizer(weight_decay))
Weight decay 效果举例:
dropout
深度神经网络学习过程中的依赖现象:
学习过程中,如果隐含层节点较多,可能出现相互依赖的现象
- 隐含节点之间出现固定关系
- 若测试数据无法激活具有固定关系的隐含节点,则分类性能可能大大降低
- 实际是一种过拟合的表现
- 同一类型只有有限种组合的节点激活方式
可以采用模型平均的方式
缺点:需要训练多个模型,效率较低
Dropout:一种随机丢弃模型节点的方法,消除节点相互依赖
- 不修改损失函数, 直接修改神经网络的结构
- 训练过程中以概率 p 随机“丢弃”一部分节点:消除依赖
- 未被丢弃的节点需以 1/p 为权重进行缩放
- 每个迭代实际上使用了不同的网络结构
- 测试时使用完整网络 (p=1),事实上起到了多个模型合并的作用, 可看做一种集成学习
测试的时候设成1,为什么要设成1啊?
从集成学习角度理解 Dropout
- 假设网络中每个节点都有 50% 的概率被丢弃,网络共有 N 个节点,则一共可能有 2N 种不同的网络
- 当 dropout 不同的神经元时,相当于训练的不同的神经网络
- 测试时使用所有的网络节点,可以近似视为模型平均 (每个模型训 练时权值乘以 50%,节点规模测试时为训练 2 倍,相当于平均两 个训练时只有一半的节点网络)
Dropout tensorflow 应用示例:训练时:keep_prob 设为 p(0.5,0.3…); 测试 时:keep_prob=1
net = tf.nn.relu(tf.matmul(net, W2) + b2)
net = tf.nn.dropout(net, keep_prob=keep_prob)
Dropout 主要用于全连接网络,一般设为 0.5 或者 0.3
卷积网层中由于卷积自身的稀疏化以及稀疏化的 ReLu 函数的大量使 用,Dropout 使用较少
Batch Norm
moving_mean,moving_variance 为什么需要学习和更新?
BN 对数据进行归一化,需要的是整个样本空间的 mean,var BN 实际上一次迭代只能估计一个 batch 中的 mean 和 var
- batch 中的 mean 和 var 是局部的,是相对全局的有噪 (还很大) 估计
- 故用 moving_mean 和 moving_variance 通过多次 batch 的局部统计
来累积对全局的统计
μ, σ 是否需要在测试时更新?
理想情况下,train_data 和 test_data 数据分布应该一致
也就是,测试时,应该用训练得到的全局 μ, σ
但实际上,测试数据与训练数据分布可能存在一定鸿沟:1. 训练与测 试数据分布由于采样引起的不完全对应;2. 不同的 batch size:这些都 会使训练得到的 μ, σ 成为有偏估计
解决方案:1. 使用当前 batch 统计;2. 使用指数加权平均来逐步得到测 试阶段 μ, σ 估计
TF Batch Norm 接口
TF 主要调用关系:
- tf.nn.batch_normalization: low-level API, 用户负责管理 mean,variance
- tf.layers.batch_normalization: high-level 封装. 自动创建、管理 mean,variance. 一般常用
- tf.contrib.layers.batch_norm: BN 早期实现,contrib 的一般都不推荐 使用,在未来版本中都有可能被放弃
- slim.batch_norm:slim 实现,实际使用 tf.contrib.layers.batch_norm
- keras.layers.BatchNormalization: 实际后端调用
tf.nn.batch_normalization.
TF Batch Norm 使用方式:
TF BN 使用分为训练、测试两个阶段: 测试阶段:
- 参数 training=True, 以训练 gamma(γ),beta(β) 两个参数
- moving_mean 和 moving_variance 默认只是” 需更新的”(更新操 作集合 tf.GraphKeys.UPDATE_OPS),却不是” 需学习的” 而实际上滑动平均是需要更新学习的,为此,需手动添加更新操 作集合作为 train_op 依赖项,即在 minimize 优化前,要先做 update_ops 操作
update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
with tf.control_dependencies(update_ops): train_op = optimizer.minimize(loss)
训练参数保持
moving_mean,moving_variance 因为不是 trainable variable,所以是不在 tf.trainable_variables() 中的,或者将其填入保存变量列表 var_list 后,建 立 saver(var_list) 保存;或用 saver() 保存 global_variable()
TF Batch Norm 使用方式:
TF BN 使用分为训练、测试两个阶段: 测试阶段:
- 参数 training=False, 不训练 gamma(γ),beta(β) 两个参数
- 如果要用训练得到的全局 moving_mean 和 moving_variance,建 议用指数加权平均来逐步得到测试阶段 μ, σ 估计 也有用以下来恢复保存的指数加权平均影子参数来实现,此时需 要训练阶段将影子参数也同样保存。
ema = tf.train.ExponentialMovingAverage(0.999) vars = ema.variables_to_restore()
保存形式:
{’xxx/varibleExponentialMovingAverage’: <tf.Variable ’xxx/varible:0’ shape=(136,) dtype=float32_ref>,…}
或不用 ema.variables_to_restore() 而将全局 moving_mean 和 moving_variance 赋予新建的影子参数
指数加权平均
tf.train.ExponentialMovingAverage 采用滑动平均的方法更新参数 需衰减速率(decay),用于控制模型的更新速度 维护一个影子变量(也就是更新参数后的参数值),这个影子变量的初 始值就是这个变量的初始值,影子变量值的更新方式如下: shadow_variable = decay * shadow_variable + (1-decay) * variable shadow_variable 是影子变量,variable 表示待更新的变量,也就是变量 被赋予的值,decay 为衰减速率。decay 一般设为接近于 1 的数
(0.99,0.999)
decay 越大模型越稳定,因为 decay 越大,参数更新的速度就越慢,趋 于稳定
tf.train.ExponentialMovingAverage 这个函数还提供了自己动更新 decay 的计算方式:
decay= min(decay,(1+steps)/(10+steps))
steps 是迭代的次数,可以自己设定,可在前期加速迭代
Batch Norm 实现细节剖析
def bacthnorm(inputs, scope, epsilon=1e-05, momentum=0.99, is_training=True): inputs_shape = inputs.get_shape().as_list()
params_shape = inputs_shape[-1:]
axis = list(range(len(inputs_shape) - 1))
with tf.variable_scope(scope):
beta = create_variable("beta", params_shape, initializer=tf.zeros_initializer())
gamma = create_variable("gamma", params_shape, initializer=tf.ones_initializer())
# for inference
moving_mean = create_variable("moving_mean", params_shape, initializer=tf.zeros_initializer(), trainable=False) moving_variance = create_variable("moving_variance", params_shape, initializer=tf.ones_initializer(), trainable=False)
if is_training:
mean, variance = tf.nn.moments(inputs, axes=axis)
update_move_mean = moving_averages.assign_moving_average(moving_mean,
mean, decay=momentum)
update_move_variance = moving_averages.assign_moving_average(moving_variance, variance, decay=momentum)
tf.add_to_collection(UPDATE_OPS_COLLECTION, update_move_mean) tf.add_to_collection(UPDATE_OPS_COLLECTION, update_move_variance)
else:
mean, variance = moving_mean, moving_variance
return tf.nn.batch_normalization(inputs, mean, variance, beta, gamma, epsilon)