【炼丹Trick】EMA的原理与实现
在进行深度学习训练时,同一模型往往可以训练出不同的效果,这就是炼丹这件事的玄学所在。使用一些trick能够让你更容易追上目前SOTA的效果,一些流行的开源代码中已经集成了不少trick,值得学习一番。本节介绍EMA这一方法。
1.原理:
EMA也就是指数移动平均(Exponential moving average)。其公式非常简单,如下所示:
\(\theta_{\text{EMA}, t+1} = (1 - \lambda) \cdot \theta_{\text{EMA}, t} + \lambda \cdot \theta_{t}\)
\(\theta_{t}\)是t时刻的网络参数,\(\theta_{\text{EMA}, t}\)是t时刻滑动平均后的网络参数,那么t+1时刻的滑动平均结果就是这两者的加权融合。这里 \(\lambda\)通常会取接近于1的数,比如0.9995,数字越大平均的效果就比较强。
值得注意的是,这里可以看成有两个模型,基础模型其参数按照常规的前后向传播来更新,另外一个模型则是基础模型的滑动平均版本,它并不直接参与前后向传播,仅仅是利用基础模型的参数结果来更新自己。
EMA为什么会有效呢?大概是因为在训练的时候,会使用验证集来衡量模型精度,但其实验证集精度并不和测试集一致,在训练后期阶段,模型可能已经在测试集最佳精度附近波动,所以使用滑动平均的结果会比使用单一结果更加可靠。感兴趣的话可以看看这几篇论文,论文1,论文2,论文3。
2.实现:
Pytorch其实已经为我们实现了这一功能,为了避免自己造轮子可能引入的错误,这里直接学习一下官方的代码。这个类的名称就叫做AveragedModel。代码如下所示。
我们需要做的是提供avg_fn这个函数,avg_fn用来指定以何种方式进行平均。
class AveragedModel(Module):
"""
You can also use custom averaging functions with `avg_fn` parameter.
If no averaging function is provided, the default is to compute
equally-weighted average of the weights.
"""
def __init__(self, model, device=None, avg_fn=None, use_buffers=False):
super(AveragedModel, self).__init__()
self.module = deepcopy(model)
if device is not None:
self.module = self.module.to(device)
self.register_buffer('n_averaged',
torch.tensor(0, dtype=torch.long, device=device))
if avg_fn is None:
def avg_fn(averaged_model_parameter, model_parameter, num_averaged):
return averaged_model_parameter + \
(model_parameter - averaged_model_parameter) / (num_averaged + 1)
self.avg_fn = avg_fn
self.use_buffers = use_buffers
def forward(self, *args, **kwargs):
return self.module(*args, **kwargs)
def update_parameters(self, model):
self_param = (
itertools.chain(self.module.parameters(), self.module.buffers())
if self.use_buffers else self.parameters()
)
model_param = (
itertools.chain(model.parameters(), model.buffers())
if self.use_buffers else model.parameters()
)
for p_swa, p_model in zip(self_param, model_param):
device = p_swa.device
p_model_ = p_model.detach().to(device)
if self.n_averaged == 0:
p_swa.detach().copy_(p_model_)
else:
p_swa.detach().copy_(self.avg_fn(p_swa.detach(), p_model_,
self.n_averaged.to(device)))
self.n_averaged += 1
@torch.no_grad()
def update_bn(loader, model, device=None):
r"""Updates BatchNorm running_mean, running_var buffers in the model.
It performs one pass over data in `loader` to estimate the activation
statistics for BatchNorm layers in the model.
Args:
loader (torch.utils.data.DataLoader): dataset loader to compute the
activation statistics on. Each data batch should be either a
tensor, or a list/tuple whose first element is a tensor
containing data.
model (torch.nn.Module): model for which we seek to update BatchNorm
statistics.
device (torch.device, optional): If set, data will be transferred to
:attr:`device` before being passed into :attr:`model`.
Example:
>>> loader, model = ...
>>> torch.optim.swa_utils.update_bn(loader, model)
.. note::
The `update_bn` utility assumes that each data batch in :attr:`loader`
is either a tensor or a list or tuple of tensors; in the latter case it
is assumed that :meth:`model.forward()` should be called on the first
element of the list or tuple corresponding to the data batch.
"""
momenta = {}
for module in model.modules():
if isinstance(module, torch.nn.modules.batchnorm._BatchNorm):
module.running_mean = torch.zeros_like(module.running_mean)
module.running_var = torch.ones_like(module.running_var)
momenta[module] = module.momentum
if not momenta:
return
was_training = model.training
model.train()
for module in momenta.keys():
module.momentum = None
module.num_batches_tracked *= 0
for input in loader:
if isinstance(input, (list, tuple)):
input = input[0]
if device is not None:
input = input.to(device)
model(input)
for bn_module in momenta.keys():
bn_module.momentum = momenta[bn_module]
model.train(was_training)
这里同样参考官方的示例代码,给出滑动平均的实现。ExponentialMovingAverage继承了AveragedModel,并且复写了init方法,其实更直接的方法是将ema_avg函数作为参数传递给AveragedModel,这里可能是为了可读性,避免出现一个孤零零的ema_avg函数。
class ExponentialMovingAverage(torch.optim.swa_utils.AveragedModel):
"""Maintains moving averages of model parameters using an exponential decay.
``ema_avg = decay * avg_model_param + (1 - decay) * model_param``
`torch.optim.swa_utils.AveragedModel <https://pytorch.org/docs/stable/optim.html#custom-averaging-strategies>`_
is used to compute the EMA.
"""
def __init__(self, model, decay, device="cpu"):
def ema_avg(avg_model_param, model_param, num_averaged):
return decay * avg_model_param + (1 - decay) * model_param
super().__init__(model, device, ema_avg, use_buffers=True)
如何使用呢?方式是比较简单的,首先是利用当前模型创建出一个滑动平均模型。
model_ema = utils.ExponentialMovingAverage(model, device=device, decay=ema_decay)
然后是进行基础模型的前后向传播,更新结束后再对滑动平均版的模型进行参数更新。
output = model(image)
loss = criterion(output, target)
optimizer.zero_grad()
loss.backward()
optimizer.step()
model_ema.update_parameters(model)
【炼丹Trick】EMA的原理与实现的更多相关文章
- 【优化技巧】指数移动平均EMA的原理
前言 在深度学习中,经常会使用EMA(exponential moving average)方法对模型的参数做平滑或者平均,以求提高测试指标,增加模型鲁棒性. 参考 1. [优化技巧]指数移动平均(E ...
- 炼丹的一些trick
采摘一些大佬的果实: 知乎:如何理解深度学习分布式训练中的large batch size与learning rate的关系? https://blog.csdn.net/shanglianlm/ar ...
- PHP 底层的运行机制与原理
PHP说简单,但是要精通也不是一件简单的事.我们除了会使用之外,还得知道它底层的工作原理. PHP是一种适用于web开发的动态语言.具体点说,就是一个用C语言实现包含大量组件的软件框架.更狭义点看,可 ...
- JSPatch 实现原理详解
原文地址https://github.com/bang590/JSPatch/wiki/JSPatch-%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86%E8%AF%A6%E8 ...
- PHP的运行机制与原理(底层) [转]
说到php的运行机制还要先给大家介绍php的模块,PHP总共有三个模块:内核.Zend引擎.以及扩展层:PHP内核用来处理请求.文件流.错误处理等相关操作:Zend引擎(ZE)用以将源文件转换成机器语 ...
- Linux进程调度原理
Linux进程调度原理 Linux进程调度机制 Linux进程调度的目标 1.高效性:高效意味着在相同的时间下要完成更多的任务.调度程序会被频繁的执行,所以调度程序要尽可能的高效: 2.加强交互性能: ...
- PHP底层的运行机制与原理
PHP说简单,但是要精通也不是一件简单的事.我们除了会使用之外,还得知道它底层的工作原理. PHP是一种适用于web开发的动态语言.具体点说,就是一个用C语言实现包含大量组件的软件框架.更狭义点看,可 ...
- 单片微机原理P0:80C51结构原理
本来我真的不想让51的东西出现在我的博客上的,因为51这种东西真的太low了,学了最多就所谓的垃圾科创利用一下,但是想一下这门课我也要考试,还是写一点东西顺便放博客上吧. 这一系列主要参考<单片 ...
- Kernel PCA 原理和演示
Kernel PCA 原理和演示 主成份(Principal Component Analysis)分析是降维(Dimension Reduction)的重要手段.每一个主成分都是数据在某一个方向上的 ...
随机推荐
- [AcWing 53] 最小的 k 个数
堆排序 点击查看代码 class Solution { public: vector<int> getLeastNumbers_Solution(vector<int> inp ...
- 【CSAPP】Cache Lab 实验笔记
cachelab这节先让你实现个高速缓存模拟器,再在此基础上对矩阵转置函数进行优化,降低高速缓存不命中次数.我的感受如上一节,实在是不想研究这些犄角旮旯的优化策略了. 前期准备 我实验的时候用到了va ...
- vue - vue基础/vue核心内容
终于算是顺利进入vue了,确实也只有学了过后才知道,之前三过vue而不学,确实是对的,现在进来了一点都不后悔,ajax.node.js.promise.webpack.git等等确实是除了三大基础外还 ...
- 团队Arpha5
队名:观光队 组长博客 作业博客 组员实践情况 王耀鑫 **过去两天完成了哪些任务 ** 文字/口头描述 完成服务器连接数据库部分代码 展示GitHub当日代码/文档签入记录 接下来的计划 服务器网络 ...
- 【必看】局域网IP地址冲突罪魁祸首是谁?
开源Linux 长按二维码加关注~ 上一篇:一文详解FTP.FTPS与SFTP的原理 现如今,人们的生活处处离不开网络.企业办公信息化对网络的依赖则更大.为了提升安全管理和信息化水平,很多企业不仅建设 ...
- 什么叫做 Docker
什么叫做 Docker 本文写于 2020 年 11 月 5 日 没有人会喜欢环境配置 在去年的时候我开始学习 Python,并利用 Python 制作了一些小工具.但问题是我很难让别人去用我的软件, ...
- Swift初探02 数组、集合、元组、字典
数组.集合.元组.字典 每一门语言基本都会有这些数据结构,swift也不例外,而作为一门现代的语言,swift提供了很多的现成的方法给我们选择. 一.数组 01 数组的定义 // 三种声明方式 var ...
- 用js给闺女做了一个加减乘除的html
下班回家用二十分钟给闺女做了一个加减乘除的页面,顺便记录下代码,时间仓促,后期再来修改吧 目录结构 -yq --menu.html --yq.html --yq50.html --yq70.html ...
- 图文详解 HDFS 的工作机制及其原理
大家好,我是大D. 今天开始给大家分享关于大数据入门技术栈--Hadoop的学习内容. 初识 Hadoop 为了解决大数据中海量数据的存储与计算问题,Hadoop 提供了一套分布式系统基础架构,核心内 ...
- SQL多表多字段比对方法
目录 表-表比较 整体思路 找出不同字段的明细 T1/T2两表ID相同的部分,是否存在不同NAME 两表的交集与差集:判断两表某些字段是否相同 两表的交集与差集:找出T2表独有的id 字段-字段比较 ...