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

这一篇文章,来讲解一下可变卷积的代码实现逻辑和可视化效果。全部基于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. 制作视频教程,用Camtasia你也可以

    一直以来,每当我在电脑使用过程中有不会的地方,往往我就会通过百度或者b站寻找教程,尤其是视频教程来学习,这样我往往就可以快速的学会相应的操作.当朋友在qq或者微信问我们一些操作时,我们却不能向他们提供 ...

  2. 【PYTEST】第二章编写测试函数

    知识点: assert 测试函数标记 跳过测试 标记预期失败的测试用例 1. asseet 返回的都是布尔值,等于False(F) 就是失败, assert 有很多 assert something ...

  3. 紧急发布用cherry-pick检出当前分支所有我的提交记录

    目录 背景 操作命令 cherry-pick git log Shell脚本 背景 公司接了个新项目,需在平台上增加几个新接口,问题是本来说是和平台一起迭代发布的时间提前了,但当前的代码都和其他开发人 ...

  4. 自定义JSTL Tag

    <?xml version="1.0" encoding="UTF-8" ?> <taglib xmlns="http://java ...

  5. js实现长按显示全部内容

    js实现文字超出省略号显示时长按显示全部 元素内容超出省略号显示时长按该元素,生成toast弹窗(id:toolkitContainer),以显示全部内容 #toolkitContainer { ma ...

  6. Java基础教程——类和对象

    视屏讲解:https://www.bilibili.com/video/av48272174 面向过程 VS 面向对象 面向过程:强调步骤. 面向对象:强调对象. 面向对象的特点就是:隐藏具体实现的细 ...

  7. Dotnet Core下的Channel, 你用了吗?

    今天给大家分享一个微软官方的好东西:Channel.   前言 今天给大家分享一个微软官方的生产者/消费者方案的特性解决:Channel. Channel在System.Threading.Chann ...

  8. kafka监听出现的问题,解决和剖析

    问题如下: kafka为什么监听不到数据 kafka为什么会有重复数据发送 kafka数据重复如何解决 为什么kafka会出现俩个消费端都可以消费问题 kafka监听配置文件 一. 解决问题一(kaf ...

  9. 使用KVM的API编写一个简易的AArch64虚拟机

    参考资料: Linux虚拟化KVM-Qemu分析(一) Linux虚拟化KVM-Qemu分析(二)之ARMv8虚拟化 Linux虚拟化KVM-Qemu分析(三)之KVM源码(1) Linux虚拟化KV ...

  10. 浅尝 Elastic Stack (四) Logstash + Beats 读取 Spring Boot 日志

    一.Spring Boot 日志配置 采用 Spring Boot 默认的 Logback: <?xml version="1.0" encoding="UTF-8 ...