论文地址:https://arxiv.org/abs/2103.09950
论文中指出,你的模型效果不好可能是因为你的resize方法不行,因而提出了带有学习参数的resizer模块。
本人基于cifar-10数据集做实验,后发现当输入为极小分辨率的图像(32*32)时,采用resizer模块进行上采样可以有较好效果(相比于cv2的resize根据相邻值添加冗余特征,resizer可以获得更多有效的特征)。当图像较大时,效果并不明显。感兴趣的可以自行实验。
个人理解:
由于cifar-10数据集的图像尺寸都是32x32,而ImageNet预训练模型大多都是基于224x224尺寸训练的结果。因此,实验中如果图像尺寸选择32x进行训练,即使训练了很长的迭代数,最终效果还是不如224x的。尽管32x到224x的缩放是通过双线性插值实现的,理论上扩大尺寸的同时添加了很多的冗余特征,但仍然可以获得更高的实验精度。于是开始探索,能否通过包含可学习参数的网络层来实现上采样?效果会不会更好呢?
而谷歌恰好针对resize在视觉任务上的作用发表了一篇论文:Learning to Resize Images for Computer Vision Tasks。里面的做法是把较大的原图先resize到448x448,然后再通过论文提出的resizer模块缩放到224x224,最终的实验效果会比直接把原图resize到224x224好很多。
于是在backbone网络前也加入了论文中的resizer模块,不同的是,论文中用来进行下采样,此处用来进行上采样。实验中测试了多种上采样尺寸,但更大的尺寸意味着只能使用更小的batchsize以及更大的训练开销,因此最终选择了上采样到224x224。
实验精度大概提高了0.5%(97.72%->98.17%)。感兴趣的读者可以将此模块简单地加入到自己的网络中进行测试,resizer的输出结果接上原始模型的backbone即可~
import torch
import torch.nn as nn
import torch.nn.functional as F
from functools import partial
class ResBlock(nn.Module):
def __init__(self, channel_size: int, negative_slope: float = 0.2):
super().__init__()
self.block = nn.Sequential(
nn.Conv2d(channel_size, channel_size, kernel_size=3, padding=1,
bias=False),
nn.BatchNorm2d(channel_size),
nn.LeakyReLU(negative_slope, inplace=True),
nn.Conv2d(channel_size, channel_size, kernel_size=3, padding=1,
bias=False),
nn.BatchNorm2d(channel_size)
)
def forward(self, x):
return x + self.block(x)
class Resizer(nn.Module):
"""
scale_factor:缩放因子,output_shape = input_shape * scale_factor
"""
def __init__(self, scale_factor=7):
super().__init__()
self.interpolate_mode = "bilinear"
self.scale_factor = scale_factor
n = 16 # 卷积通道数
r = 2 # ResBlock数量,网络深度
slope = 0.2
self.module1 = nn.Sequential(
nn.Conv2d(3, n, kernel_size=7, padding=3),
nn.LeakyReLU(slope, inplace=True),
nn.Conv2d(n, n, kernel_size=1),
nn.LeakyReLU(slope, inplace=True),
nn.BatchNorm2d(n)
)
resblocks = []
for i in range(r):
resblocks.append(ResBlock(n, slope))
self.resblocks = nn.Sequential(*resblocks)
self.module3 = nn.Sequential(
nn.Conv2d(n, n, kernel_size=3, padding=1, bias=False),
nn.BatchNorm2d(n)
)
self.module4 = nn.Conv2d(n, 3, kernel_size=7,
padding=3)
self.interpolate = partial(F.interpolate,
scale_factor=self.scale_factor,
mode=self.interpolate_mode,
align_corners=False,
recompute_scale_factor=False)
def forward(self, x):
residual = self.interpolate(x)
out = self.module1(x)
out_residual = self.interpolate(out)
out = self.resblocks(out_residual)
out = self.module3(out)
out = out + out_residual
out = self.module4(out)
out = out + residual
return out
下面是一个使用demo~
class ResizerEfficientnet(torch.nn.Module):
def __init__(self):
super().__init__()
self.resizer_model = Resizer()
self.backbone = efficientnet.efficientnetb0()
def forward(self, x):
x = self.resizer_model(x)
x = self.backbone(x)
return x