文章转载自微信公众号:【机器学习炼丹术】,请支持原创。

这一篇文章,来讲解一下可变卷积的代码实现逻辑和可视化效果。全部基于python,没有C++。大部分代码来自:https://github.com/oeway/pytorch-deform-conv 但是我研究了挺久的,发现这个人的代码中存在一些问题,导致可变卷积并没有实现。之所以发现这个问题是在我可视化可变卷积的检测点的时候,发现一些端倪,然后经过修改之后,可以正常可视化,并且精度有所提升。

1 代码逻辑

# 为了可视化
class ConvOffset2D(nn.Conv2d):
"""ConvOffset2D Convolutional layer responsible for learning the 2D offsets and output the
deformed feature map using bilinear interpolation Note that this layer does not perform convolution on the deformed feature
map. See get_deform_cnn in cnn.py for usage
"""
def __init__(self, filters, init_normal_stddev=0.01, **kwargs):
"""Init Parameters
----------
filters : int
Number of channel of the input feature map
init_normal_stddev : float
Normal kernel initialization
**kwargs:
Pass to superclass. See Con2d layer in pytorch
"""
self.filters = filters
self._grid_param = None
super(ConvOffset2D, self).__init__(self.filters, self.filters*2, 3, padding=1, bias=False, **kwargs)
self.weight.data.copy_(self._init_weights(self.weight, init_normal_stddev)) def forward(self, x):
"""Return the deformed featured map"""
x_shape = x.size()
offsets_ = super(ConvOffset2D, self).forward(x) # offsets: (b*c, h, w, 2)
# 这个self._to_bc_h_w_2就是我修改的代码
offsets = self._to_bc_h_w_2(offsets_, x_shape) # x: (b*c, h, w)
x = self._to_bc_h_w(x, x_shape) # X_offset: (b*c, h, w)
x_offset = th_batch_map_offsets(x, offsets, grid=self._get_grid(self,x)) # x_offset: (b, h, w, c)
x_offset = self._to_b_c_h_w(x_offset, x_shape) return x_offset,offsets_

假设我们现在要对5通道的28x28的特征图进行可变卷积的offset的计算。

  1. offsets_ = super(ConvOffset2D, self).forward(x)

    现在offsets_是一个10通道的28x28的特征图。

  2. offsets = self._to_bc_h_w_2(offsets_, x_shape)

    调用这个函数特征图从(b,2c, h, w)变成(bxc, h, w, 2)的结构

  3. x = self._to_bc_h_w(x, x_shape)

    改变原来特征图的结构,变成(bxc,h,w)

  4. x_offset = th_batch_map_offsets(x, offsets, grid=self._get_grid(self,x))

    这个相当于把之前的偏移offsets施加到了特征图x上

  5. x_offset = self._to_b_c_h_w(x_offset, x_shape)

    把施加偏移之后的特征图恢复成(b,c,h,w)的结构

可以看到,关键就是如何把offset施加到x上这个步骤。

def th_batch_map_offsets(input, offsets, grid=None, order=1):
"""Batch map offsets into input
Parameters
---------
input : torch.Tensor. shape = (b, s, s)
offsets: torch.Tensor. shape = (b, s, s, 2)
Returns
-------
torch.Tensor. shape = (b, s, s)
"""
batch_size = input.size(0)
input_height = input.size(1)
input_width = input.size(2) offsets = offsets.view(batch_size, -1, 2)
if grid is None:
grid = th_generate_grid(batch_size, input_height, input_width, offsets.data.type(), offsets.data.is_cuda) coords = offsets + grid mapped_vals = th_batch_map_coordinates(input, coords)
return mapped_vals
  1. offsets = offsets.view(batch_size, -1, 2)

    offsets之前被改造成了(bxc,h,w,2)的样子,现在再改成(b,cxhxw,2)的样子

  2. coords = offsets + grid

    这个感觉是offsets+grid,grid类似于像素的xy轴,offsets是一个相对偏移,这样offset+grid就变成了偏移之后的绝对坐标,可以直接从特征图中定位到对应的元素。因为像素值的xy轴肯定为整数,因为这个偏移是小数,所以在特征图中定位到一个小数坐标的元素是通过双线性差值的方法获取到这个不存在位置的像素值的。

  3. mapped_vals = th_batch_map_coordinates(input, coords)

    这部分的内容是把offset施加到原特征图中

差不多逻辑就是这么个逻辑

2 结果展示

先看使用了不使用可变卷积的结果:

这种MNIST数字识别任务已经是幼儿园级别的了,所以成功率基本是非常高的:

在看使用了可变卷积的结果:

可以发现,最终的loss下降其实并比不过不用可变卷积的效果,至于原因我也不确定,也许是任务太简单了?我想到一点,也许是可变卷积的目的是对目标的纹理等更敏感,对于MNIST的分类问题反而起不到效果。

最后我也搞出来这样的一张图,我在费尽千辛万苦之后,终于实现的可变卷积的可视化效果:

可以看到,可变卷积对于数字部分的反应大一些,检测点在数字部分会有更大的偏移。不过可变卷积在我测试的过程中,这个偏移的大小不确定,这一次训练模型可能偏移很大,下一次训练可能偏移很小,似乎增加了网络训练的难度。大概就这么多把。(也不确定是不是自己代码的问题了。。)

3 完整代码

class ConvOffset2D(nn.Conv2d):
"""ConvOffset2D Convolutional layer responsible for learning the 2D offsets and output the
deformed feature map using bilinear interpolation Note that this layer does not perform convolution on the deformed feature
map. See get_deform_cnn in cnn.py for usage
"""
def __init__(self, filters, init_normal_stddev=0.01, **kwargs):
"""Init Parameters
----------
filters : int
Number of channel of the input feature map
init_normal_stddev : float
Normal kernel initialization
**kwargs:
Pass to superclass. See Con2d layer in pytorch
"""
self.filters = filters
self._grid_param = None
super(ConvOffset2D, self).__init__(self.filters, self.filters*2, 3, padding=1, bias=False, **kwargs)
self.weight.data.copy_(self._init_weights(self.weight, init_normal_stddev)) def forward(self, x):
"""Return the deformed featured map"""
x_shape = x.size()
offsets = super(ConvOffset2D, self).forward(x) # offsets: (b*c, h, w, 2)
offsets = self._to_bc_h_w_2(offsets, x_shape) # x: (b*c, h, w)
x = self._to_bc_h_w(x, x_shape) # X_offset: (b*c, h, w)
x_offset = th_batch_map_offsets(x, offsets, grid=self._get_grid(self,x)) # x_offset: (b, h, w, c)
x_offset = self._to_b_c_h_w(x_offset, x_shape) return x_offset @staticmethod
def _get_grid(self, x):
batch_size, input_height, input_width = x.size(0), x.size(1), x.size(2)
dtype, cuda = x.data.type(), x.data.is_cuda
if self._grid_param == (batch_size, input_height, input_width, dtype, cuda):
return self._grid
self._grid_param = (batch_size, input_height, input_width, dtype, cuda)
self._grid = th_generate_grid(batch_size, input_height, input_width, dtype, cuda)
return self._grid @staticmethod
def _init_weights(weights, std):
fan_out = weights.size(0)
fan_in = weights.size(1) * weights.size(2) * weights.size(3)
w = np.random.normal(0.0, std, (fan_out, fan_in))
return torch.from_numpy(w.reshape(weights.size())) @staticmethod
def _to_bc_h_w_2(x, x_shape):
"""(b, 2c, h, w) -> (b*c, h, w, 2)"""
x = x.contiguous().view(-1, int(x_shape[2]), int(x_shape[3]), 2)
return x @staticmethod
def _to_bc_h_w(x, x_shape):
"""(b, c, h, w) -> (b*c, h, w)"""
x = x.contiguous().view(-1, int(x_shape[2]), int(x_shape[3]))
return x @staticmethod
def _to_b_c_h_w(x, x_shape):
"""(b*c, h, w) -> (b, c, h, w)"""
x = x.contiguous().view(-1, int(x_shape[1]), int(x_shape[2]), int(x_shape[3]))
return x def th_generate_grid(batch_size, input_height, input_width, dtype, cuda):
grid = np.meshgrid(
range(input_height), range(input_width), indexing='ij'
)
grid = np.stack(grid, axis=-1)
grid = grid.reshape(-1, 2) grid = np_repeat_2d(grid, batch_size)
grid = torch.from_numpy(grid).type(dtype)
if cuda:
grid = grid.cuda()
return Variable(grid, requires_grad=False) def th_batch_map_offsets(input, offsets, grid=None, order=1):
"""Batch map offsets into input
Parameters
---------
input : torch.Tensor. shape = (b, s, s)
offsets: torch.Tensor. shape = (b, s, s, 2)
Returns
-------
torch.Tensor. shape = (b, s, s)
"""
batch_size = input.size(0)
input_height = input.size(1)
input_width = input.size(2) offsets = offsets.view(batch_size, -1, 2)
if grid is None:
grid = th_generate_grid(batch_size, input_height, input_width, offsets.data.type(), offsets.data.is_cuda) coords = offsets + grid mapped_vals = th_batch_map_coordinates(input, coords)
return mapped_vals def np_repeat_2d(a, repeats):
"""Tensorflow version of np.repeat for 2D""" assert len(a.shape) == 2
a = np.expand_dims(a, 0)
a = np.tile(a, [repeats, 1, 1])
return a def th_batch_map_coordinates(input, coords, order=1):
"""Batch version of th_map_coordinates
Only supports 2D feature maps
Parameters
----------
input : tf.Tensor. shape = (b, s, s)
coords : tf.Tensor. shape = (b, n_points, 2)
Returns
-------
tf.Tensor. shape = (b, s, s)
""" batch_size = input.size(0)
input_height = input.size(1)
input_width = input.size(2) n_coords = coords.size(1) # coords = torch.clamp(coords, 0, input_size - 1) coords = torch.cat((torch.clamp(coords.narrow(2, 0, 1), 0, input_height - 1), torch.clamp(coords.narrow(2, 1, 1), 0, input_width - 1)), 2) assert (coords.size(1) == n_coords) coords_lt = coords.floor().long()
coords_rb = coords.ceil().long()
coords_lb = torch.stack([coords_lt[..., 0], coords_rb[..., 1]], 2)
coords_rt = torch.stack([coords_rb[..., 0], coords_lt[..., 1]], 2)
idx = th_repeat(torch.arange(0, batch_size), n_coords).long()
idx = Variable(idx, requires_grad=False)
if input.is_cuda:
idx = idx.cuda() def _get_vals_by_coords(input, coords):
indices = torch.stack([
idx, th_flatten(coords[..., 0]), th_flatten(coords[..., 1])
], 1)
inds = indices[:, 0]*input.size(1)*input.size(2)+ indices[:, 1]*input.size(2) + indices[:, 2]
vals = th_flatten(input).index_select(0, inds)
vals = vals.view(batch_size, n_coords)
return vals vals_lt = _get_vals_by_coords(input, coords_lt.detach())
vals_rb = _get_vals_by_coords(input, coords_rb.detach())
vals_lb = _get_vals_by_coords(input, coords_lb.detach())
vals_rt = _get_vals_by_coords(input, coords_rt.detach()) coords_offset_lt = coords - coords_lt.type(coords.data.type())
vals_t = coords_offset_lt[..., 0]*(vals_rt - vals_lt) + vals_lt
vals_b = coords_offset_lt[..., 0]*(vals_rb - vals_lb) + vals_lb
mapped_vals = coords_offset_lt[..., 1]* (vals_b - vals_t) + vals_t
return mapped_vals def th_repeat(a, repeats, axis=0):
"""Torch version of np.repeat for 1D"""
assert len(a.size()) == 1
return th_flatten(torch.transpose(a.repeat(repeats, 1), 0, 1)) def th_flatten(a):
"""Flatten tensor"""
return a.contiguous().view(a.nelement())

pytorch实现 | Deformable Convolutional Networks | CVPR | 2017的更多相关文章

  1. 图像处理论文详解 | Deformable Convolutional Networks | CVPR | 2017

    文章转自同一作者的微信公众号:[机器学习炼丹术] 论文名称:"Deformable Convolutional Networks" 论文链接:https://arxiv.org/a ...

  2. 论文阅读笔记三十八:Deformable Convolutional Networks(ECCV2017)

    论文源址:https://arxiv.org/abs/1703.06211 开源项目:https://github.com/msracver/Deformable-ConvNets 摘要 卷积神经网络 ...

  3. 目标检测论文阅读:Deformable Convolutional Networks

    https://blog.csdn.net/qq_21949357/article/details/80538255 这篇论文其实读起来还是比较难懂的,主要是细节部分很需要推敲,尤其是deformab ...

  4. 深度学习方法(十三):卷积神经网络结构变化——可变形卷积网络deformable convolutional networks

    上一篇我们介绍了:深度学习方法(十二):卷积神经网络结构变化--Spatial Transformer Networks,STN创造性地在CNN结构中装入了一个可学习的仿射变换,目的是增加CNN的旋转 ...

  5. Deformable Convolutional Networks

    1 空洞卷积 1.1 理解空洞卷积 在图像分割领域,图像输入到CNN(典型的网络比如FCN)中,FCN先像传统的CNN那样对图像做卷积再pooling,降低图像尺寸的同时增大感受野,但是由于图像分割预 ...

  6. 论文讨论&&思考《Deformable Convolutional Networks》

    这篇论文真是让我又爱又恨,可以说是我看过的最认真也是最多次的几篇paper之一了,首先deformable conv的思想我觉得非常好,通过end-to-end的思想来做这件事也是极其的make se ...

  7. Deformable Convolutional Networks-v1-v2(可变形卷积网络)

    如何评价 MSRA 视觉组最新提出的 Deformable ConvNets V2? <Deformable Convolutional Networks>是一篇2017年Microsof ...

  8. 论文阅读笔记三十三:Feature Pyramid Networks for Object Detection(FPN CVPR 2017)

    论文源址:https://arxiv.org/abs/1612.03144 代码:https://github.com/jwyang/fpn.pytorch 摘要 特征金字塔是用于不同尺寸目标检测中的 ...

  9. Densely Connected Convolutional Networks 论文阅读

    毕设终于告一段落,传统方法的视觉做得我整个人都很奔溃,终于结束,可以看些搁置很久的一些论文了,嘤嘤嘤 Densely Connected Convolutional Networks 其实很早就出来了 ...

随机推荐

  1. Echo Delay:FL中好用的声音制作处理方法

    今天来一起研究FL Studio的Echo Delay的作用,Echo Delay可以从MIDI输入创建回声,并允许我们通过音量,声像,切除和共振,音高和时间来操纵延迟. 图1:Echo Dealy ...

  2. Ubuntu无法telnet

    1.Ubuntu无法telnet的原因 (1)/etc/hosts被修改过 (2)防火墙没有关闭 (3)没有安装相关服务 (4)/etc/inetd.conf文件没有telnet相关内容 2.解决办法 ...

  3. 精尽MyBatis源码分析 - SQL执行过程(三)之 ResultSetHandler

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  4. 早安打工人! 来把你的.NET程序模块化吧

    嗨朋友们,大家好! 还记得我是谁吗? 对了! 我就是 .NET 打工人 玩双截棍的熊猫 今天呐,我特别要向 写框架 的朋友们,想要写框架 ** 的朋友们,已经有框架** 的朋友问声好! 为什么呢?因为 ...

  5. LCCUP 2020 秋季编程大赛 补题

    果然是力扣杯,难度较于平时周赛提高了不少,个人感觉最后两题并不太容易QAQ LCP 18.早餐组合 #二分思想 题目链接 题意 你获得了每种主食的价格,及每种饮料的价格,你需要选择一份主食和一份饮料, ...

  6. moviepy音视频剪辑:与time时间线相关的变换函数freeze_region、make_loopable、speedx、time_mirror、time_symmetrize介绍

    ☞ ░ 前往老猿Python博文目录 ░ 一.引言 在<moviepy音视频剪辑:moviepy中的剪辑基类Clip详解>介绍了剪辑基类的fl.fl_time.fx方法,在<movi ...

  7. PyQt(Python+Qt)学习随笔:使用QFontDialog.getFont交互设置应用或部件的字体

    专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 PyQt中的部件只要是QWidget的派生类都可以在Designer或 ...

  8. PyQt(Python+Qt)学习随笔:Qt Designer中主窗口对象的iconSize属性

    主窗口对象的iconSize属性保存的是主窗口中工具栏的图标尺寸,在没有设置时缺省是GUI图形界面样式中定义的工具栏的缺省大小. 注意:这个大小是工具栏图标的最小尺寸. 可以使用iconSize()返 ...

  9. 在 GitHub 玩硬件——GitHub 热点速览 Vol.49

    作者:HelloGitHub-小鱼干 本周的 GitHub Trending 可以说是非常之硬核,天才少年稚晖君的 2 个硬件变装项目荣登热点榜,看完将充电宝改装为显示器的视频,搭配 HDMI-PI ...

  10. tensorflow 指定版本安装

    首先,建议在anaconda中创建虚拟环境,教程已写,参考上一篇 下载之前建议设置pip清华源(用以提速,可百度) 设置下载源 pip config set global.index-url http ...