点击上方“3D视觉车间,选择星标
第一时间送达干货
作者丨ChaucerG
来源集智书童
池化层是卷积神经网络的基本构建模块。它不仅可以降低网络的计算成本,还可以扩大卷积操作的感觉领域。池化的目标是产生和下采样效果。同时,在理想情况下,还应具有计算和存储效率。但同时满足这两个要求是一个挑战。
为此,本文提出了自适应指数加权池的方法。提出的方法是基于两组池化核的参数化融合。dice-S?rensen指数和指数的最大值。
AdaPool其关键属性之一是它的双向性。与普通的池化方法不同,权重值可用于上采样下采样的激活映射。作者将该方法命名为。
作者示了作者AdaPool如何通过图像和视频分类以及目标检测等一系列任务改进细节的保存。然后,评估AdaUnPool在图像和视频帧的超分辨率和帧插值任务。为了进行基准测试,作者提出了,这是一个新颖的高质量、高帧率视频数据集。
实验表明,AdaPool只引入少量额外的计算和内存费用,在任务和主要架构之间取得了更好的效果。
1简介
池化方法将空间输入采样到较低的分辨率。目标是通过捕获最重要的信息和保留结构,如对比度和纹理,尽量减少信息损失。池化操作在图像和视频处理方法中至关重要,包括基于卷积神经网络的方法。在cnn池化操作有助于减轻计算负担,增加深层卷积的感觉野。池化实际上是所有流行的CNN该架构中的一个关键部件具有较低的计算和内存成本。
提出了一系列的池方法,每种方法都有不同的属性。大多数网络架构使用最大池或平均池,这两种方法都是快速和高效的内存,但仍有改进信息的空间。另一种方法是使用训练有素的子网络。这些方法比平均池或最大池有一定的改进,但由于其先验参数,它们通常效率低,通常适用。
在这项工作中,作者研究了如何利用基于指数加权的低计算方法来解决池化方法的缺点。本文介绍了加权方法kernel regions 方法,通过Dice-S?rensen基于每个系数获得系数kernel activation 与mean kernel activation相似度指数之间。然后,如图1所示,作者建议 AdaPool 作为这两种方法的参数融合方法。
许多任务,包括实例分割、图像生成和超分辨率,都需要对输入进行向上采样,这与池化反。除了LiftPool 此外,许多池化操作不能反转,因为这会导致稀疏的上采样结果。插值、转移卷积、反卷积等常用的上采样方法都不是高维特征的重构。缺乏先验知识是一个障碍,因为当信息被编码到较低的维数时,较高维数中的局部信息就会丢失。相反,作者认为包含局部先验知识有利于采样。基于与AdaPool同一公式引入向上采样过程 AdaUnPool。
本文证明了AdaPool保留描述性激活特性的良好效果。因此,允许使用AdaPool该模型继续提高分类和识别性能。AdaPool保持较低的计算成本,并提供保留先验信息的方法。作者进一步介绍AdaUnPool并解决超分辨率和插值任务。本文作出了以下贡献:
逆距加权(IDW)通过引入类似的平均池化方法。Dice-S?rensen系数(DSC)基于向量距离IDW扩展基于相似的向量IDW,并利用其指数eDSC加权核元素;
提出了 AdaPool,这是一种参数化的学习融合,融合了最大值和平均值的平滑度和近似度。采用逆公式开发向上采样过程 AdaUnPool;
在图像和视频分类、目标检测等多项基于全局和局部任务的实验中,并通过使用进行了实验AdaPool更换原池化层显示出一致的改进。它还显示了AdaUnPool提高图像和视频的超分辨率和视频帧插值;
介绍了高分辨率和帧速率视频处理数据集Inter4K,用于基准测试帧超分辨率和插值算法。
2相关工作
2.1 Pooling hand-crafted features
降采样广泛应用于手工编码特征提取。在Bag-of-Words图像被表示为一组局部图像patch,这些局部patch被合并,然后被编码为向量。基于此方法,空间金字塔匹配(Spatial Pyramid Matching,SPM)目标是尽可能保留空间信息。后来的工作扩展了这种方法,线性 SPM 选择最大的空间区域 SIFT 特征。特征池化的早期工作大多集中在基于生物皮层信号最大样本的最大池化上。最大池化和平均池化的信息保存研究表明,最大池化的结果在低特性激活状态下更具代表性。
2.2 Pooling in CNNs
随着各种计算机视觉任务中学习特征方法的普及,池化方法也适用于基于kernel早期的操作CNN在模型中,池化主要用于创建具有代表性的特征表示,从而减少模型的计算需求,并支持创建更深层次的架构。
近年来,相关特征的保存在降采样过程中发挥着越来越重要的作用。最初的方法包括随机池化,它使用kernel区域内概率加权采样。基于最大池化和平均池化的组合,其他池化方法(如混合池化)要么是概率性的,要么是每种方法某些部分的组合。Power Average()使用学习参数 确定平均池化和最大池化的相对重要性。当 时对应 sum pooling,而 对应max-pooling。
有些方法是基于网格采样的。S3Pool 采样是利用随机采样来创建原始特性映射网格的行和列。也有一些方法可以学习权重,比如 Detail Preserving Pooling (DPP),采用平均池化,同时采用高于平均值的增强激活。Local Importance Pooling (LIP)利用子网注意机制学习权重。图2中可以看到不同池化方法的可视化和数学概述。
上述池化工作大多不能倒置向上采样。Badrinarayanan等人提出了一种最大池化操作的反转,通过跟踪所选的最大像素的kernel上采样输出中的零值填充位置和其他位置。这将导致原始值的使用,但输出本质上是稀疏的。
最近,Zhao和Snoek提出了基于输入的四种可学习方法 sub-bands 的 LiftPool。发现产生的输出 sub-bands 混合成分。同时也提出了 LiftUpPool 向上采样反转。这两种方法都是基于 sub 网络结构也限制了它们作为高计算和内存效率的池化技术的可用性。
上述方法大多依赖于最大池化和平均池化的组合,或限制低计算和高效采样 sub-net。本文的工作不是结合现有的方法,而是基于自适应指数加权方法来提高信息的保留,更好地保留原始信号的细节。
本文提出的方法AdaPool是受到 启发。因此,根据它们的相关性kernel region不受相邻加权kernel item与平均池化和最大池化相比,影响。
AdaPool使用两组池化kernel。依赖于第一种方法 softmax 增强特征激活值的加权方法;第二种方法使用单个方法 kernel item 确定通道相似性及其平均值。相似性是基于的 Dice-S?rensen 获得系数。最后,两个kernel参数化地将操作输出集成到单个 volume 中。每个kernel位置参数是特定的,因此本文的方法具有区域适应性。
AdaPool 一个关键属性是在反向传播期间kernel item计算梯度也提高了网络的连通性。此外,下采样区不太可能表现出激活的消失趋势,就像Avg pool或sum pool等待观察到的贡献方法。
可在图3中看到 AdaPool 如何适应捕捉细节,放大区域显示签名。AdaPool 提高了字母和数字的清晰度和可识别性。
3本文方法 AdaPool
首先介绍池化方法中共享的操作。大小为C×H×W的激活映射 中定义了局部 kernel 区域 R,C通道高度为H,宽度为W。为了简化表示法,省略通道维数,假设R是尺寸k × k二维空间区域内激活的相对位置指标集合(即激活)。池化输出表示为 ,相应的梯度表示为 ,它是R区域内的坐标集。
A. Inverse Distance Weighting pooling
首先引入Inverse Distance Weighting pooling(IDW)池化。IDW 多变量插值广泛应用于加权平均法。假设近几何观察比近几何观察更相似。为了分配权重值,IDW 依赖于该区域测量的观测距离。图4显示了这个加权过程的可视化表示。
在前通道和后通道的梯度计算中,作者认为改进区域的平均计算可以限制异常输入值 pooled volumes 影响。目前池化的平均计算是一个kernel该区域的所有输入向量都使用相同的权重。这意味着所有的向量在其特征激活方面都同样重要。其结果是受离群值影响较大的输出区域的组成(离群值在几何上远远于该区域的平均值)。为了克服均匀加权区域平均的局限性,作者提出了一种基于IDW加权法称为加权法。
作者这里将 IDW 概念扩展到 kernel 加权,使用每个激活 相对像素坐标指数 距离,得到R的平均激活 ,得到的合并区域 公式为:
距离函数 可以用任何几何距离方法计算,如下距离形式:
与平均加权相比,IDWPool 生成的归一化结果在几何上更接近均值的特征激活向量具有更高的权重。这也适用于梯度的计算、减少了离群值的影响,为特征激活相关性提供了更好的代表性更新率。在这方面,IDWPool 的工作方式与平均所有激活的常见方法不同,在这种方法中,输出激活没有被规范化。
B. Smooth approximated average pooling
本文以两种方式扩展加权平均的使用。首先,在多维空间中,基于距离的加权平均虽然比统一方法有优势,但它是次优的。特征激活向量与区域内平均值之间的 L1 或 L2 距离是根据每个通道对的平均值、SUM或最大值计算的。结果距离是无界的,因为成对的距离也是无界的。
此外,计算的距离对每通道距离对离群值敏感。这一效果可以通过图5中池化的反向距离加权方法看到。当使用距离方法时,某些通道中的距离可能比其他通道中的距离大得多。这就产生了权值接近于零的问题。
或者,使用相似度度量可以绕过边界问题。但是,特别是对于广泛使用的余弦相似度面临的问题是,即使其中一个向量是无限大的两个向量之间的相似度也可以是1。幸运的是,其他向量点积方法可以解决这个问题,如Dice-Sørensen系数(DSC),通过考虑向量长度,克服了这一限制。
改进一:
作者还考虑了其他基于相似度的方法来寻找两个向量的相关性。除了余弦相似度外,还可以将Kumar和Hassebrook Peak-Correlation Energy (PCE) 应用于 vector volumes (如表II所示)。图5展示了不同相似度方法在池化质量上的差异。考虑到前面提到的余弦相似度的不足,作者在 PCE 上使用 DSC 主要是由于 PCE 的非单调性质和值分布。
改进二:
第二个扩展是使用激活向量和平均激活量之间相似度的指数(e)。对于式1的IDW方法,零值距离的权重为零。这导致池化方法在反向传播期间不可微分,因为不是每个位置的所有梯度都将被计算。这也增加了当权重接近于零时出现消失梯度问题的可能性。通过算术下溢,它们的梯度可以变为零。在引入相似系数指数的基础上将式1重新表述为:
下采样的关键目标之一是在保持信息特征的同时降低输入的空间分辨率。
创建不能完全捕获结构和特性外观的下采样可能会对性能产生负面影响。图3中可以看到这种丢失的详细示例。平均池化将统一地产生一个减少的激活。IDWpool可以通过权重值提高激活保持。但权值是无界的,可能导致渐变消失。相反,使用 Dice-Sørensen 系数(eDSCWPool)的指数提供了一种非零权重值的平衡方法,同时保持IDW的有益属性。
C. Smooth approximated maximum pooling
类似于创建一种在kernel区域内发现平滑近似平均值的方法,作者还讨论了基于平滑近似最大值的下采样公式,该公式最近被引入到了SoftPool之中。为了清晰,并且符合所使用的术语,将SoftPool称为指数最大值(eM)。
使用指数最大值背后的动机受到下采样手工编码特征的皮层神经模拟的影响。这种方法是以自然指数为基础的(e),以确保更大的激活将对最终产出产生更大的影响。该操作是可微的,类似于指数平均值,在反向传播过程中,kernel区域激活分配了相应比例的梯度。
指数最大池(eMPool)中的权值被用作基于相应激活值的非线性转换。高信息量的激活将比低信息量的更占主导地位。由于大多数池操作都是在高维特征空间上执行的,因此。在后一种情况下,丢弃大部分激活会带来丢失重要信息的风险。
eMPool的输出是通过对kernel区域R内所有加权激活的总和产生的:
与其他基于最大值的池化方法相比,激活区域softmax产生标准化结果,类似于eDSCWPool。但与平滑的指数平均值不同,归一化结果基于一个概率分布,该概率分布与kernel区域内每个激活相对于相邻激活的值成比例。完整的信息向前和向后传递的可视化如下图所示。
D. AdaPool: Adaptive exponential pooling
最后,提出了两种基于平滑近似的池化方法的自适应组合。基于它们的属性eMPool或eDSCWPool都演示了在有效保存特性细节方面的改进。
从图3中可以看出,这两种方法中没有一种通常优于另一种。基于这一观察结果,并使用可训练参数 β 来创建平滑近似平均值和平滑近似最大值的组合 volume 。在这里,β 是用来学习的比例,将使用从每两种方法。引入 β 作为网络训练过程的一部分,具有创建一个综合池化策略的优势,该策略依赖于eMPool和eDSCWPool属性的结合。
将该方法定义为下采样平滑逼近平均值()和平滑逼近最大值()的加权组合:
class CUDA_ADAPOOL2d(Function):
@staticmethod
@torch.cuda.amp.custom_fwd(cast_inputs=torch.float32)
def forward(ctx, input, beta, kernel=2, stride=None, return_mask=False):
assert input.dtype==beta.dtype, '`input` and `beta` are not of the same dtype.'
beta = torch.clamp(beta , 0., 1.)
no_batch = False
if len(input.size()) == 3:
no_batch = True
input.unsqueeze_(0)
B, C, H, W = input.shape
kernel = _pair(kernel)
if stride is None:
stride = kernel
else:
stride = _pair(stride)
oH = (H - kernel[0]) // stride[0] + 1
oW = (W - kernel[1]) // stride[1] + 1
output = input.new_zeros((B, C, oH, oW))
if return_mask:
mask = input.new_zeros((B, H, W))
else:
mask = input.new_zeros((1))
adapool_cuda.forward_2d(input.contiguous(), beta, kernel, stride, output, return_mask, mask)
ctx.save_for_backward(input, beta)
ctx.kernel = kernel
ctx.stride = stride
if return_mask:
mask_ = mask.detach().clone()
mask_.requires_grad = False
CUDA_ADAPOOL2d.mask = mask_
output = torch.nan_to_num(output)
if no_batch:
return output.squeeze_(0)
return output
@staticmethod
@torch.cuda.amp.custom_bwd
def backward(ctx, grad_output):
grad_input = torch.zeros_like(ctx.saved_tensors[0])
grad_beta = torch.zeros_like(ctx.saved_tensors[1])
saved = [grad_output] + list(ctx.saved_tensors) + [ctx.kernel, ctx.stride, grad_input, grad_beta]
adapool_cuda.backward_2d(*saved)
return torch.nan_to_num(saved[-2]), torch.nan_to_num(saved[-1]), None, None, None
def adapool2d(x, beta=None, kernel_size=2, stride=None, return_mask=False, native=False):
if stride is None:
stride = kernel_size
kernel_size = _pair(kernel_size)
stride = _pair(stride)
assert beta is not None, 'Function called with `None`/undefined `beta` parameter.'
shape = [(x.shape[-2] - kernel_size[-2]) // stride[-2] + 1 ,
(x.shape[-1] - kernel_size[-1]) // stride[-1] + 1]
beta_shape = list(beta.shape)
shape_d = [s*kernel_size[i] for i,s in enumerate(shape)]
assert shape == beta_shape or beta_shape==[1,1], 'Required `beta` shape {0} does not match given shape {1}'.format(shape, beta_shape)
assert x.is_cuda, 'Only CUDA implementation supported!'
if not native:
x = beta*CUDA_ADAPOOL2d_EDSCW.apply(x, kernel_size, stride, return_mask) + (1.-beta)*CUDA_ADAPOOL2d_EM.apply(x, kernel_size, stride, return_mask)
else:
x = CUDA_ADAPOOL2d.apply(x, beta, kernel_size, stride, return_mask)
# Replace `NaN's
if not return_mask:
return torch.nan_to_num(x)
else:
if not native:
return torch.nan_to_num(x), (CUDA_ADAPOOL2d_EDSCW.mask,CUDA_ADAPOOL2d_EM.mask,beta)
else:
return torch.nan_to_num(x), CUDA_ADAPOOL2d.mask
class AdaPool2d(torch.nn.Module):
def __init__(self, kernel_size=2, beta=None, stride=None,
beta_trainable=True,return_mask=False, device=None,
dtype=None, native=False):
factory_kwargs = {'device': device, 'dtype': dtype}
super(AdaPool2d, self).__init__()
if stride is None:
stride = kernel_size
self.kernel_size = _pair(kernel_size)
self.stride = _pair(stride)
assert isinstance(native, bool), 'Argument `native` should be boolean'
self.native = native
assert isinstance(beta, tuple) or torch.is_tensor(beta), 'Agument `beta` can only be initialized with Tuple or Tensor type objects and should correspond to size (oH, oW)'
if isinstance(beta, tuple):
beta = torch.randn(beta, **factory_kwargs)
else:
beta = beta.to(**factory_kwargs)
beta = torch.clamp(beta, 0., 1.)
self.return_mask = return_mask
if beta_trainable:
self.register_parameter(name='beta', param=torch.nn.Parameter(beta))
else:
self.register_buffer(name='beta', param=torch.nn.Parameter(beta))
def forward(self, x):
self.beta.data.clamp(0., 1.)
return adapool2d(x, beta=self.beta, kernel_size=self.kernel_size, stride=self.stride, return_mask=self.return_mask, native=self.native)
E. Upsampling using adaUnPool
在池化过程中,kernel 区域中的信息被压缩为一个输出。大多数子采样方法不建立从子采样到原始输入的映射。大多数任务都不需要这个链接,但其他任务,如语义分割,超分辨率或帧插值都受益于它。由于AdaPool是可微的,并且使用一个最小的权重值分配,发现的权重可以作为上行采样时的先验知识。将给定AdaPool权重的上采样操作称为AdaUnPool。
在 pooled volume ()的情况下,使用平滑的近似最大值()和平滑近似平均权值( )具有学习值β。第个kernel区域()的最终 unpooled 输出()计算如下:
其中,通过分配 pooled volume 进行插值()在kernel区域内每个位置I处的初始kernel区域。方法是用来放大 volume 。然后用相应的平滑近似平均 Mask 或最大 Mask 来确定 volume 。
def adaunpool(x, mask=None, interpolate=True):
assert mask is not None, 'Function called with `None`/undefined `mask` parameter.'
if interpolate:
if isinstance(mask, tuple):
mask_edscw = torch.clamp(mask[0], 0., 1.)
mask_em = torch.clamp(mask[1], 0., 1.)
x1 = F.interpolate(x*mask[2], size=mask[0].shape[1:], mode='area').transpose(0,1)
x2 = F.interpolate(x*(1.-mask[2]), size=mask[1].shape[1:], mode='area').transpose(0,1)
return (x1*mask_edscw.unsqueeze(0) + x2*mask_em.unsqueeze(0)).transpose(0,1)
else:
mask = torch.clamp(mask, 0., 1.)
x = F.interpolate(x, size=mask.shape[1:], mode='area').transpose(0,1)
return (x*mask.unsqueeze(0) + x*(1.- mask.unsqueeze(0))).transpose(0,1)
else:
if isinstance(mask, tuple):
mask_edscw = torch.clamp(mask[0], 0., 1.)
mask_em = torch.clamp(mask[1], 0., 1.)
return (x.transpose(0,1)*mask_edscw.unsqueeze(0) + x.transpose(0,1)*mask_em.unsqueeze(0)).transpose(0,1)
else:
mask = torch.clamp(mask, 0., 1.)
return (x.transpose(0,1)*mask.unsqueeze(0) + x.transpose(0,1)*(1.- mask.unsqueeze(0))).transpose(0,1)
class AdaUnpool2d(torch.nn.Module):
def __init__(self, mask=None):
super(AdaUnpool2d, self).__init__()
if stride is None:
stride = kernel_size
assert mask != None, '`mask` cannot be `None`!'
self.mask = mask
def forward(self, x):
return adaunpool(x, mask=self.mask)
4实验
分类
从上表可以看出,使用AdaPool后,在ImageNet数据集上,无论是ResNet、DenseNet还是ResNeXt都有不同程度的性能提升(+2.x%),可见AdaPool方法的有效性。
目标检测
从上表可以看出,使用AdaPool后,在COCO数据集上,无论是基于ResNet的目标检测还是实例分割都有不同程度的性能提升(+2.x% AP),可见AdaPool方法的有效性。
5参考
[1]. AdaPool: Exponential Adaptive Pooling for Information-Retaining Downsampling.
本文仅做学术分享,如有侵权,请联系删文。
1.面向自动驾驶领域的多传感器数据融合技术
2.面向自动驾驶领域的3D点云目标检测全栈学习路线!(单模态+多模态/数据+代码)3.彻底搞透视觉三维重建:原理剖析、代码讲解、及优化改进4.国内首个面向工业级实战的点云处理课程5.激光-视觉-IMU-GPS融合SLAM算法梳理和代码讲解6.彻底搞懂视觉-惯性SLAM:基于VINS-Fusion正式开课啦7.彻底搞懂基于LOAM框架的3D激光SLAM: 源码剖析到算法优化8.彻底剖析室内、室外激光SLAM关键算法原理、代码和实战(cartographer+LOAM +LIO-SAM)
9.从零搭建一套结构光3D重建系统[理论+源码+实践]
10.单目深度估计方法:算法梳理与代码实现
扫码添加小助手微信,可
也可申请加入我们的细分方向交流群,目前主要有、、、、、等微信群。
一定要备注:
▲长按加微信群或投稿
▲长按关注公众号
学习3D视觉核心技术,扫描查看介绍,3天内无条件退款
圈里有高质量教程资料、答疑解惑、助你高效解决问题