1. 简介
Dropout 被广泛地用作全连接层的正则化技术,但是对于卷积层,通常不太有效。Dropout 在卷积层不 work 的原因可能是由于卷积层的特征图中相邻位置元素在空间上共享语义信息, 所以尽管某个单元被 dropout 掉,但与其相邻的元素依然可以保有该位置的语义信息,信息 仍然可以在卷积网络中流通。因此,针对卷积神经网络就需要一种结构形式的 dropout 来正 则化,即按块来丢弃。
Dropout 是一种结构化的 dropout 形式,它将 feature map 相邻区域中的单元放在一起 drop 掉。除了卷积层外,在跳跃连接中应用 DropBlock 可以提高精度。此外,在训练过程中,逐 渐增加 dropped unit 的数量会有更好的准确性和对超参数选择的鲁棒性。
2. DropBlock
DropBlock 模块主要有两个参数:block_size,γ。
● block_size:为所有的特征图设置一个恒定的块大小,不管特征图的分辨率如何。当 block_size=1 时 , DropBlock 类 似 于 dropout , 当 block_size=1 时 , DropBlock 类 似 于 SpatialDropBlock(dropout 正常可以取 3,5,7)。
● γ(表示 drop 过程中的概率,伯努利分布概率):没有显式的设置γ,γ控制要删除的特征数。假设希望保持每个激活单元的概率为 keep_prob,考虑到 block_size*block_size 中的元素都要置为 0,所以γ可以计算为:
通常:
其中:
解释如下:
传统的 dropout drop 的元素个数为 drop 概率乘以全部的元素个数((1-keep_prob)表示dorp掉的比例):
在实现中,为了保证 drop 的 block 不会超出原始图像,需要先设置一个 drop 的有效区域,如下图(a)中的绿色区域,也就是原始的图减去 block,即:
这里的γ为伯努利函数的概率,而这个概率只表示了随机 drop 的中间点的概率,如下图 (a)中红色的 X,实际需要的是要 drop 掉 X 周围的 block_size 大小的区域,也就是一个 X 对应一个 block_size 大小的区域(假设不互相重叠)。所以 drop 的概率即:
有效的区域面积为:
最终得到 drop 掉的元素数目为:
最后,Dropout=DropBlock,即:
算法流程如下:
3. Pytorch代码
class Drop(nn.Module):
# drop_rate : 1-keep_prob (all droped feature points)
# block_size : drop掉的block大小
def __init__(self, drop_rate=0.1, block_size=7):
super(Drop, self).__init__()
self.drop_rate = drop_rate
self.block_size = block_size
def forward(self, x):
if self.drop_rate == 0:
return x
# 设置gamma,比gamma小的设置为1,大于gamma的为0(得到丢弃比率的随机点个数)算法第五步
# all droped feature center points
gamma = self.drop_rate / (self.block_size**2)
# torch.rand(*sizes, out=None) : 返回一个张量,包含了从区间[0, 1)的均匀分布中抽取的一组随机数。张量的形状由参数sizes定义
mask = (torch.rand(x.shape[0], *x.shape[2:]) < gamma).float()
mask = mask.to(x.device)
# compute block mask
block_mask = self._compute_block_mask(mask)
# apply block mask,为算法图的第六步
out = x * block_mask[:, None, :, :]
# Normalize the features,对应第七步
out = out * block_mask.numel() / block_mask.sum()
return out
def _compute_block_mask(self, mask):
# 取最大值,这样就能够取出一个block的块大小的1作为drop,当然需要翻转大小,使得1为0,0为1
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大小是2的话,会边界会多出1,要去掉才能输出与原图一样大小.
block_mask = block_mask[:, :, :-1, :-1]
block_mask = 1 - block_mask.squeeze(1)
return block_mask