本文主要是介绍PaddleDetection算法分析(10),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
2021SC@SDUSC
接上文数据增强相关,接下来分析算法的yolov4
2 特征增强相关
2.1 DropBlock
论文题目:DropBlock: A regularization method for convolutional networks
论文地址
github
由于dropBlock其实是dropout在卷积层上的推广,故很有必须先说明下dropout操作。
dropout,训练阶段在每个mini-batch中,依概率P随机屏蔽掉一部分神经元,只训练保留下来的神经元对应的参数,屏蔽掉的神经元梯度为0,参数不参数与更新。而测试阶段则又让所有神经元都参与计算。
dropout操作流程:参数是丢弃率p
1)在训练阶段,每个mini-batch中,按照伯努利概率分布(采样得到0或者1的向量,0表示丢弃)随机的丢弃一部分神经元(即神经元置零)。用一个mask向量与该层神经元对应元素相乘,mask向量维度与输入神经一致,元素为0或1。
2)然后对神经元rescale操作,即每个神经元除以保留概率1-P,也即乘上1/(1-P)。
3)反向传播只对保留下来的神经元对应参数进行更新。
4)测试阶段,Dropout层不对神经元进行丢弃,保留所有神经元直接进行前向过程。
为啥要rescale呢?是为了保证训练和测试分布尽量一致,或者输出能量一致。可以试想,如果训练阶段随机丢弃,那么其实dropout输出的向量,有部分被屏蔽掉了,可以等下认为输出变了,如果dropout大量应用,那么其实可以等价为进行模拟遮挡的数据增强,如果增强过度,导致训练分布都改变了,那么测试时候肯定不好,引入rescale可以有效的缓解,保证训练和测试时候,经过dropout后数据分布能量相似。
dropout方法多是作用在全连接层上,在卷积层应用dropout方法意义不大。文章认为是因为每个feature map的位置都有一个感受野范围,仅仅对单个像素位置进行dropout并不能降低feature map学习的特征范围,也就是说网络仍可以通过该位置的相邻位置元素去学习对应的语义信息,也就不会促使网络去学习更加鲁邦的特征。
既然单独的对每个位置进行dropout并不能提高网络的泛化能力,那么很自然的,如果我们按照一块一块的去dropout,就自然可以促使网络去学习更加鲁邦的特征。思路很简单,就是在feature map上去一块一块的找,进行归零操作,类似于dropout,叫做dropblock。(小声哔哔一句,这跟上面数据增强的方法不是一个道理呢。。。不过就是操作方式上有点区别而已。)
绿色阴影区域是语义特征,b图是模拟dropout的做法,随机丢弃一些位置的特征,但是作者指出这中做法没啥用,因为网络还是可以推断出来,©是本文做法。
dropblock有三个比较重要的参数,一个是block_size,用来控制进行归零的block大小;一个是γ,用来控制每个卷积结果中,到底有多少个channel要进行dropblock;最后一个是keep_prob,作用和dropout里的参数一样。
M大小和输出特征图大小一致,非0即1,为了保证训练和测试能量一致,需要和dropout一样,进行rescale。
上述是理论分析,在做实验时候发现,block_size控制为7*7效果最好,对于所有的feature map都一样,γ通过一个公式来控制,keep_prob则是一个线性衰减过程,从最初的1到设定的阈值(具体实现是dropout率从0增加到指定值为止),论文通过实验表明这种方法效果最好。如果固定prob效果好像不好。
实践中,并没有显式的设置 γ 的值,而是根据keep_prob(具体实现是反的,是丢弃概率)来调整。
DropBlock in ResNet-50 DropBlock加在哪?最佳的DropBlock配置是block_size=7,在group3和group4上都用。将DropBlock用在skip connection比直接用在卷积层后要好,具体咋用,可以看代码。
class DropBlock2D(nn.Module):
r"""Randomly zeroes 2D spatial blocks of the input tensor.As described in the paper
`DropBlock: A regularization method for convolutional networks`_ ,
dropping whole blocks of feature map allows to remove semantic
information as compared to regular dropout.Args:
drop_prob (float): probability of an element to be dropped.
block_size (int): size of the block to dropShape:
- Input: `(N, C, H, W)`
- Output: `(N, C, H, W)`.. _DropBlock: A regularization method for convolutional networks:
https://arxiv.org/abs/1810.12890"""
def __init__(self, drop_prob, block_size):
super(DropBlock2D, self).__init__()self.drop_prob = drop_prob
self.block_size = block_sizedef forward(self, x):
# shape: (bsize, channels, height, width)assert x.dim() == 4, \
"Expected input with 4 dimensions (bsize, channels, height, width)"if not self.training or self.drop_prob == 0.:
return x
else:
# get gamma value
gamma = self._compute_gamma(x)# sample mask
mask = (torch.rand(x.shape[0], *x.shape[2:]) < gamma).float()# place mask on input device
mask = mask.to(x.device)# compute block mask
block_mask = self._compute_block_mask(mask)# apply block mask
out = x * block_mask[:, None, :, :]# scale output
out = out * block_mask.numel() / block_mask.sum()return out
def _compute_block_mask(self, mask):
# 比较巧妙的实现,用max pool来实现基于一点来得到全0区域
block_mask = F.max_pool2d(input=mask[:, None, :, :],
kernel_size=(self.block_size, self.block_size),
stride=(1, 1),
padding=self.block_size // 2)if self.block_size % 2 == 0:
block_mask = block_mask[:, :, :-1, :-1]block_mask = 1 - block_mask.squeeze(1)
return block_mask
def _compute_gamma(self, x):
return self.drop_prob / (self.block_size ** 2)
2.1 BN、GN、IN和LN
这4种归一化技术非常有名,网上分析文章非常多,故本文不打算从头到尾详细分析一遍,而是从计算角度分析4种归一化手段的计算区别。
假设输入维度是(N,C,H,W),不管哪一层归一化手段,都不会改变输出大小,即输出维度也是(N,C,H,W)。
(1) BN
对于BN,其归一化维度是N、HxW维度,故其可学习权重维度是(C,),其实就是BN的weight和bias维度,也就是论文中的 α,β 。
BN本质意思就是在Batch和HxW维度进行归一化,可以看出和batch相关,如果batch比较小,那么可能统计就不准确。并且由于测试时候batch可能和训练不同,导致分布不一致,故还多了两个参数:全局统计的均值和方差值,从而eval模式是必须开的,其调用实例如下所示:
上述中C=100 其流程是:对batch输入计算均值和方差(N、H和W维度求均值),得到维度为(C,),然后对输入(N,C,H,W)采用计算出来的(C,)个值进行广播归一化操作,最后再乘上可学习的(C,)个权重参数即可
(2) LN
对于LN,其归一化维度是C、HxW维度或者HxW维度或者W维度,但是不可以归一化维度为H,可以设置,比较灵活,其对每个batch单独进行各自的归一化操作,归一化操作时候不考虑batch,所以可以保证训练和测试一样。 例如:
m = nn.LayerNorm(normalized_shape=[100 ,35 ,45])
input = torch.randn(20, 100, 35, 45)
如上所示,其可学习权重维度是(100,35,45):对batch输入计算均值和方差(C、H和W维度求均值),输出维度为(N,),然后对输入(N,C,H,W)采用计算出来的(N,)个值进行广播归一化操作,最后再乘上可学习的(C,H,W)个权重参数即可。当然也可以设置为(35,45),意思同样理解。
可以看出其归一化是在指定输入shape情况下的归一化,和batch无关。故可以保证训练和测试一致,不需要强制开启eval模式。
通过设置输入参数shape为(H,W),其实就是IN归一化了,比较灵活。
(3) IN
对于IN,其归一化维度最简单,就是HxW,如下所示:
输入参数必须且只能是C,其内部计算是:对batch输入计算均值和方差(H,W维度求均值方差),输出维度为(N,C),然后对输入(N,C,H,W)采用计算出来的(N,C)个值进行广播归一化操作,最后再乘上可学习的(C,)个权重参数即可。
由于其计算均值和方差和batch没有关系,故也不需要强制开启eval模式。
(4) GN
GN是介于LN和IN之间的操作,多了一个group操作,例子如下:
注意第一个参数分组数必须能够将输入通道整除,否则会报错,因为无法均匀分组。其内部计算是:对batch输入计算均值和方差(C/组数、H,W维度求均值方差),输出维度为(N,组数),然后对输入(N,C,H,W)采用计算出来的(N,组数)个值进行广播归一化操作,最后再乘上可学习的(C,)个权重参数即可。不需要强制开启eval模式。
2.2 FRN
论文名称:Filter response normalization layer: Eliminating batch dependence in the training of deep neural networks
论文地址
虽然GN解决了小batch size时的问题,但在正常的batch size时,其精度依然比不上BN层。有什么办法能解决归一化既不依赖于batch,又能使精度高于BN呢?FRN就是为了解决这个问题。
要解决batch依赖问题,则不能对batch维度进行归一化。FRN层由两部分组成,Filtere Response Normalization (FRN)和Thresholded Linear Unit (TLU)。
(1) FRN
N是HxW,表面看起来计算方式非常类似IN,计算过程是:对输入的batch个样本在HxW维度上计算方差,不计算均值,得到输出维度(batch,c),然后对(batch,c,h,w)进行除方差操作,并且引入可学习参数,权重维度是(C,),最后对上述输出乘以可学习参数即可输出。
其中,ϵ是一个很小的正常数,防止除以零。
(2) TLU
由于在FRN操作中没有减去均值,会导致“归一化”后的特征值不是关于零对称,可能会以任意的方式偏移零值。如果使用ReLU作为激活函数的话,会引起误差,产生很多零值,性能下降。所以需要对ReLU进行增强,即TLU,引入一个可学习的阈值τ
从上面来看,FRN层引入了γ、β和τ三个可学习的参数,分别学习变换重构的尺度、偏移和阈值,他们都具有C个值,对应每一个通道。
一般情况下,特征图的大小N=H×W都比较大,但也有N=1的情况(全连接或者特征图为1×1)。在N=1的情况下,若ϵ很小,则会变成一个sign函数,梯度值变得很小,不利于优化;若ϵ相对较大,则曲线会平滑一点,容易优化。
故在实现方面,在N=1的情况下,将ϵ变成一个可学习的参数(初始化为10−4);而对于N≠1时,将其固定为10−6。为了保证可学习参数ϵ>0,对其进行一定限制
其tf实现如下所示:
另外在实验上,存在几个细节:
1 由于FRN层没有均值中心化,所以会有一些模型对初始学习率的选择十分敏感,特别是那些使用了多个最大池化层的网络。为了缓解这个问题,作者建议使用warm-up来对学习率进行调整。
2 一般而言,FC层后一般都不会接归一化层,这是因为均值和方差计算的数量太少,难以正确估计。但如果FC层后接FRN层,性能不会下降,反而会有上升。
3 作者对BN+TLU或者GN+TLU或者FRN+ReLU等都做过实验对比,还是发现FRN+TLU的搭配是最好。
在一些大佬实践中表明warm-up策略比较关键,如果不用效果可能不太稳定。同时整片论文都是实验性质的,没有啥原理性解释,不太好理解。而且本文看起来也蛮麻烦的,对目前的代码结构还是有蛮大的侵入性,还需要配合warm-up,用到的地方好像没有很多。
2.3 CBN
论文名称:Cross-Iiteration Batch Normalization
github
大家都知道当batch比较小时候,BN在batch维度统计不准确,导致准确率下降,前面的FRN也是为了解决该问题,而本文从另一个角度解决问题,思想比较make sense。在无法扩大batch训练的前提下,是否可以通过收集最近几次迭代信息来更新当前迭代时刻的均值和方差,这样就变向实现了扩大batch目的? 但是我们知道在当前迭代时刻,参数已经更新了N次,存储的前几个迭代的参数肯定无法直接和当前迭代次数进行合并计算,也就是由于网络权重的变化,不同迭代产生的网络激活无法相互比较。故需要找到一种解决办法。所幸作者指出:由于梯度下降机制,模型训练过程中相近的几个iter所对应的模型参数的变化是平滑的(smoothly),其权重变化可以用泰勒级数拟合出来,因此通过基于泰勒多项式的拟合来补偿网络权重的变化,从而可以准确地估计统计量,并可以有效地应用批次归一化。
在训练yolo中,常用的一个技巧是设置mini batch和batch,即网络前向batch/ mini batch次,然后再进行一次梯度更新,也是为了变相扩大batch size,但是其缺点是bn操作无法实现等价的扩大N倍,本文就相当于可以解决这个问题。通常在多卡情况下一般采用SyncBN,其也叫作Cross-GPU Batch Normalization ,主要是解决batch特别小的场景,例如语义分割中batch通常都是1的情况训练效果不够好的问题,其在多个gpu上计算BN,实现了跨GPU上计算,使用多卡构造了大batch训练,属于技术改进。而本文想在单卡下实现同样效果,因为不是每个人都有多张卡。
上述图表是基于ResNet-18在ImageNet上面训练得到的top 准确率,可以看出当batch大于16后,BN的精度就蛮好了,随着batch减少,精度快速下降,GN虽然性能还可以,但是batch大的时候精度不如BN,而Naive版本的CAN效果其实和BN差不多,Naive版本是指收集最近K个迭代信息,然后用于计算当前迭代时刻的统计量,可以发现由于梯度更新原因,直接计算统计量其实没有效果,而本文的CBN可以比较好的克服。
假设当前迭代时刻为t,则 t-τ 时刻中的统计量在t时刻的值,可以用泰勒级数近似:
对于上述式子,代码就比较好写了,只要存储前k个时刻的统计量及其梯度即可。最后进行汇总就行:
注意求方差时候,采用了max操作,作者指出这样可以保留有用的统计信息(不太好理解)。得到统计量后,后面直接进行归一化即可,和标准BN计算方式一样。
上图可以很好的反映出计算过程,BN就是仅仅利用当前迭代时刻信息进行norm,而CBN在计算当前时刻统计量时候会考虑前k个时刻统计量,从而实现扩大batch size操作。同时作者指出CBN操作不会引入比较大的内存开销,训练速度不会影响很多,但是训练时候会慢一些,比GN还慢。
论文做的实验比较多,这里不详细说了,有兴趣的可以下载原文查看。有一个细节是:CBN多了一个window size,实验中设定为8。并且需要在网络训练初期要用较小的窗大小,随着网络的训练,模型参数也会越来越稳定,再用较大的窗大小可以获得更好的结果。
2.4 CmBN
CmBN是yolov4中提出的,属于CBN的小改动,但是作者论文图绘制的比较隐晦,不太好理解,本文详细说下流程。
注意此时 [公式] 是没有进行更新的,使用的是前面时刻梯度更新得到的值。橙色流程的意思其实就是前面提到的yolo中常用的变相扩大batch size做法,其网络前向batch/ mini batch次,然后再第N-1迭代时刻进行统一的梯度更新,包括更新权重W以及BN可学习参数[公式],可以看出其无法变相扩大batch大小,实现更加精确的batch维度统计,但是实际上用起来还是有点效果的,不然大家训练时候也就不会用了。最好的办法其实应该还是同步BN好用,跨卡统计batch参数,但是不是人人都有多卡的,所以CBN还是有用武之地的。
在理解了BN流程基础上,理解CBN就非常容易了,CBN由于在计算每个迭代时刻统计量时候会考虑前3个时刻的统计量,故变相实现了大batch,然后在每个mini batch内部,都是标准的BN操作即:1 计算BN统计量;2 应用BN;3 更新可学习参数和网络权重
而CmBN的做法和前面两个都不一样,其把大batch内部的4个mini batch当做一个整体,对外隔离,主要改变在于BN层的统计量计算方面,具体流程是:假设当前是第t次迭代时刻,也是mini-batch的起点,
(1) 在第t时刻开始进行梯度累加操作
(2) 在第t时刻开始进行BN统计量汇合操作,这个就是和CBN的区别,CBN在第t时刻,也会考虑前3个时刻的统计量进行汇合,而CmBN操作不会,其仅仅在mini batch内部进行汇合操作
(3) 就是正常的应用BN,对输入进行变换输出即可
(4) 在mini batch的最后一个时刻,进行参数更新和可学习参数更新
可以明显发现CmBN是CBN的简化版本,其唯一差别就是在计算第t时刻的BN统计量时候,CBN会考虑前一个mini batch内部的统计量,而CmBN版本,所有计算都是在mini batch内部。我怀疑是为了减少内存消耗,提高训练速度,既然大家都是近似,差距应该不大,而且本身yolo训练时候,batch也不会特别小,不至于是1-2,所以CmBN的做法应该是为了yolov4专门设计的,属于实践性改进。
这篇关于PaddleDetection算法分析(10)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!