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

这一篇文章,来讲解一下可变卷积的代码实现逻辑和可视化效果。全部基于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. MathType总结编辑括号的类型(中)

    MathType中的符号模板多种多样,能够满足各类用户的使用需求.即使是一些不常用的符号也有相应的模板,因为在工作学习中我们都需要他们,只是有的用的多有的用的少而已,下面我们就一起来看看公式编辑器编辑 ...

  2. 用Camtasia来快速地给视频添加水印

    在日常生活中,视频的流行度越来越高,各种短视频的软件蜂拥上市,所以越来越多的人走上了自媒体的道路,在这条路上,谁的视频更加的精致,谁才能获得更多的关注度,相应的也能增加自己的人气. 但是在制作视频的过 ...

  3. FLstudio 系列教程(七):FL Studio五大常用按钮介绍

    我们打开FL Studio编曲软件会发现界面中有好多的菜单和窗口,这些窗口每个都有其单独的功能.今天小编主要给大家详细讲解下FL Studio水果软件的五大常用按钮. 1.首先我,我们双击桌面的水果图 ...

  4. 如何清理MacBook电脑缓存?

    使用Mac电脑的用户都知道,Mac其空间硬盘大多数都是只有128G以及256G的规格,而用惯了win系统基本都是超过512G的硬盘的用户,转手来使用Mac电脑,就不得不接受这一硬伤问题. 用户往往再怎 ...

  5. SFTP 连接服务器下载文件方法采坑说明

    本篇博客主要记录请求SFTP服务器的一些方法采坑情况. 采坑的方法说明: 1. cd():这个方法用于进入某个目录下. 默认情况,当连接SFTP服务器成功后直接进入用户目录,比如我连接自己本机SFTP ...

  6. nginx学习sub_filter模块

    用户替换html中的字符 location / { root /opt/app/code/; random_index on; index index.html index.htm; sub_filt ...

  7. C语言讲义——二维数组

    二维数组,又称为矩形数组 可以不太准确地理解为"数组的数组" 也可以认为是一个表格 然而内存中并不是表格存储: 二维数组的初始化 第一维度可以省略 所有元素可以写在一个花括号中,计 ...

  8. Java基础教程——Stream

    Stream流 Java 8 API添加了一种新的机制--Stream(流).Stream和IO流不是一回事. 流式思想:像生产流水线一样,一个操作接一个操作. 使用Stream流的步骤:数据源→转换 ...

  9. 树莓派4b 安装最新wiringpi库

    树莓派4自带的wiringPi库默认是2.50,无法映射到gpio,所以需要更新到2.52才能与树莓派映射: 1. 安装自带的wiringPi库 $ Sudo apt-get install wiri ...

  10. fist-第一天随笔

    这个作业属于哪个课程 https://edu.cnblogs.com/campus/fzzcxy/2018SE1 这个作业要求在哪里 https://edu.cnblogs.com/campus/fz ...