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

这一篇文章,来讲解一下可变卷积的代码实现逻辑和可视化效果。全部基于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. 需要登录才能下载的文件可以用Folx下载吗

    用苹果电脑的小伙伴有没有发现,有时候文件即时有下载链接也还是要先登录才能下载,那这样的文件用下载器Folx还能下载码?下面小编将在Mac系统平台上,通过一篇教程教大家利用Folx 5的密码管理来保存网 ...

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

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

  3. 浅谈代理模式与java中的动态代理

    代理模式的定义: 代理模式是一个使用律非常高的模式,定义如下: 为其他对象提供一种代理,以控制对这个对象的访问. 类图: 简单的静态代理: public interface IRunner{ //这是 ...

  4. 05_Content Provider

    Content Provider是内容提供器,与内容(数据)的存取(存储.获取)有关,是Android应用程序的四大组成部分之一,是Android中的跨应用访问数据机制. 数据库在Android当中是 ...

  5. 基于CFSSL工具创建CA证书,服务端证书,客户端证书

    背景描述 在局域网中部署组件时,想要通过证书来实现身份的认证,确保通信的安全性,可以通过cfssl工具来进行CA证书,服务端证书,客户端证书的创建. 目录 背景描述 部署cfssl工具 下载,上传cf ...

  6. Jmeter代理服务器录制脚本--浏览器拦截访问链接

    在 Jmeter性能测试的过程中您是否会遇到代理服务器无法打开浏览器,无法录制脚本的情况呢? 在测试过程中,我也遇到过这样的问题,希望能帮到正在找寻答案的你.... Jmeter录制脚本时,跟http ...

  7. 腾讯短信平台ASP接口范例

    疫情后一个小项目要用到腾讯短信平台,因为比较老,用ASP写的,平台没有相应的ASP接口,百度不到,无奈之下自己写了一个,也方便需要的朋友们. 主要代码如下: <!--#include file= ...

  8. jupyter notebook 将当前目录设置为工作目录

    生成配置文件首先打开你的CMD或者是终端(Linux),在你配置过环境变量的基础下,你直接输入以下命令: jupyter notebook --generate-config 然后打开生成的配置文件, ...

  9. Python中使用百分号占位符的字符串格式化方法中%s和%r的输出内容有何不同?

    Python中使用百分号占位符的字符串格式化方法中%s和%r表示需要显示的数据对应变量x会以str(x)还是repr(x)输出内容展示. 关于str和repr的关系请见: <Python中rep ...

  10. 第15.37节 PyQt(Python+Qt)入门学习:containers容器类部件QMdiArea多文档界面部件详解及编程开发案例

    专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 一.引言 老猿在前期学习PyQt相关知识时,对每个组件的属性及方法都研 ...