可变形卷积 deformable convolution 学习记录

Deformable ConvNets v1:
论文地址:https://github.com/chengdazhi/Deformable-Convolution-V2-PyTorch
工程地址:https://github.com/felixlaumon/deform-conv

论文地址: Deformable ConvNets v2: More Deformable, Better Results
工程地址:https://github.com/chengdazhi/Deformable-Convolution-V2-PyTorch

以下转摘于:https://blog.csdn.net/mykeylock/article/details/77746499
可变形卷积是指卷积核在每一个元素上额外增加了一个参数方向参数,这样卷积核就能在训练过程中扩展到很大的范围。
可变形卷积的论文为:Deformable Convolutional Networks【1】
而之前google一篇论文对这篇论文有指导意义:Spatial Transformer Networks【2】
论文【1】的github代码地址为https://github.com/felixlaumon/deform-conv
——————————————————————————————————————————
可变形卷积很好理解,但如何实现呢?实现方面需要关注两个限制:
1、如何将它变成单独的一个层,而不影响别的层;
2、在前向传播实现可变性卷积中,如何能有效地进行反向传播。
这两个问题的答案分别是:
1、在实际操作时,并不是真正地把卷积核进行扩展,而是对卷积前图片的像素重新整合,
变相地实现卷积核的扩张;
2、在图片像素整合时,需要对像素进行偏移操作,偏移量的生成会产生浮点数类型,
而偏移量又必须转换为整形,直接对偏移量取整的话无法进行反向传播,这时采用双线性差值的方式来得到对应的像素。
——————————————————————————————————————————
可变性卷积的流程为:
1、原始图片batch(大小为bhwc),记为U,经过一个普通卷积,卷积填充为same,即输出输入大小不变,
对应的输出结果为(b
hw2c),记为V,输出的结果是指原图片batch中每个像素的偏移量(x偏移与y偏移,因此为2c)。
2、将U中图片的像素索引值与V相加,得到偏移后的position(即在原始图片U中的坐标值),需要将position值限定为图片大小以内。
position的大小为(bhw*2c),但position只是一个坐标值,而且还是float类型的,我们需要这些float类型的坐标值获取像素。
3、例,取一个坐标值(a,b),将其转换为四个整数,floor(a), ceil(a), floor(b), ceil(b),将这四个整数进行整合,
得到四对坐标(floor(a),floor(b)), ((floor(a),ceil(b)), ((ceil(a),floor(b)), ((ceil(a),ceil(b))。这四对坐标每个坐标都对应U
中的一个像素值,而我们需要得到(a,b)的像素值,这里采用双线性差值的方式计算
(一方面得到的像素准确,另一方面可以进行反向传播)。
4、在得到position的所有像素后,即得到了一个新图片M,将这个新图片M作为输入数据输入到别的层中,如普通卷积。
——————————————————————————————————————————

以上是可变性卷积的实现流程,但实际代码实现起来涉及到比较多的tensor操作,比较繁琐。
代码实现主要的文件有
cnn.py:采用keras定义了所有训练需要的层,可变形卷积层为ConvOffset2D,
layer.py:定义了ConvOffset2D可变形卷积类,主要包括keras中需要的call函数与init函数,
call函数首先调用普通卷积,然后调用deform_conv.py中函数实际计算。
deform_conv.py:真正实现可变形卷积计算的文件。
layer.py主要代码:

def __init__(self, filters, init_normal_stddev=0.01, **kwargs):

    self.filters = filters
    super(ConvOffset2D, self).__init__(
        self.filters * 2, (3, 3), padding='same', use_bias=False,
        kernel_initializer=RandomNormal(0, init_normal_stddev),
        **kwargs
    )
def call(self, x):
    """Return the deformed featured map"""

    #获取x大小,x大小为(b,h,w,c),分别为batch_size,图片高度,图片宽度,特征图大小

    x_shape = x.get_shape()

    #调用普通卷积获得输出,输出结果为(b,h,w,2c)表示图片中每个像素需要偏移的量(x,y)
    offsets = super(ConvOffset2D, self).call(x)
     #reshape一下输出,方便后续操作,(b*c,h,w,2)表示共有b*c个图片,每个图片为h*w大小,每个像素对应2个方向
    # offsets: (b*c, h, w, 2)
    offsets = self._to_bc_h_w_2(offsets, x_shape)
    #将原始输入也重新reshape一下方便后续操作
    # x: (b*c, h, w)
    x = self._to_bc_h_w(x, x_shape)
    #调用deform_conv.py中的函数根据原始图片与偏移量生成新图片数据。
    # X_offset: (b*c, h, w)
    x_offset = tf_batch_map_offsets(x, offsets)
    # x_offset: (b, h, w, c)
    x_offset = self._to_b_h_w_c(x_offset, x_shape)
    return x_offset
def compute_output_shape(self, input_shape):
    """Output shape is the same as input shape
    Because this layer does only the deformation part
    """
    return input_shape
@staticmethod
def _to_bc_h_w_2(x, x_shape):
    """(b, h, w, 2c) -> (b*c, h, w, 2)"""
    x = tf.transpose(x, [0, 3, 1, 2])
    x = tf.reshape(x, (-1, int(x_shape[1]), int(x_shape[2]), 2))
    return x
@staticmethod
def _to_bc_h_w(x, x_shape):
    """(b, h, w, c) -> (b*c, h, w)"""
    x = tf.transpose(x, [0, 3, 1, 2])
    x = tf.reshape(x, (-1, int(x_shape[1]), int(x_shape[2])))
    return x
@staticmethod
def _to_b_h_w_c(x, x_shape):
    """(b*c, h, w) -> (b, h, w, c)"""
    x = tf.reshape(
        x, (-1, int(x_shape[3]), int(x_shape[1]), int(x_shape[2]))
    )
    x = tf.transpose(x, [0, 2, 3, 1])
    return x

deform_conv.py主要代码:

def tf_flatten(a):
    """Flatten tensor"""
    return tf.reshape(a, [-1])

def tf_repeat(a, repeats, axis=0):
    """TensorFlow version of np.repeat for 1D"""
    # https://github.com/tensorflow/tensorflow/issues/8521
    assert len(a.get_shape()) == 1
    a = tf.expand_dims(a, -1)
    a = tf.tile(a, [1, repeats])
    a = tf_flatten(a)
    return a
def tf_repeat_2d(a, repeats):
    """Tensorflow version of np.repeat for 2D"""
    assert len(a.get_shape()) == 2
    a = tf.expand_dims(a, 0)
    a = tf.tile(a, [repeats, 1, 1])
    return a
def tf_map_coordinates(input, coords, order=1):
    """Tensorflow verion of scipy.ndimage.map_coordinates
    Note that coords is transposed and only 2D is supported
    Parameters
    ----------
    input : tf.Tensor. shape = (s, s)
    coords : tf.Tensor. shape = (n_points, 2)
    """
    assert order == 1
    coords_lt = tf.cast(tf.floor(coords), 'int32')
    coords_rb = tf.cast(tf.ceil(coords), 'int32')
    coords_lb = tf.stack([coords_lt[:, 0], coords_rb[:, 1]], axis=1)
    coords_rt = tf.stack([coords_rb[:, 0], coords_lt[:, 1]], axis=1)

    vals_lt = tf.gather_nd(input, coords_lt)
    vals_rb = tf.gather_nd(input, coords_rb)
    vals_lb = tf.gather_nd(input, coords_lb)
    vals_rt = tf.gather_nd(input, coords_rt)

    coords_offset_lt = coords - tf.cast(coords_lt, 'float32')
    vals_t = vals_lt + (vals_rt - vals_lt) * coords_offset_lt[:, 0]
    vals_b = vals_lb + (vals_rb - vals_lb) * coords_offset_lt[:, 0]
    mapped_vals = vals_t + (vals_b - vals_t) * coords_offset_lt[:, 1]
    return mapped_vals
def sp_batch_map_coordinates(inputs, coords):
    """Reference implementation for batch_map_coordinates"""
    coords = coords.clip(0, inputs.shape[1] - 1)
    mapped_vals = np.array([
        sp_map_coordinates(input, coord.T, mode='nearest', order=1)
        for input, coord in zip(inputs, coords)
    ])
    return mapped_vals
def tf_batch_map_coordinates(input, coords, order=1):
    """Batch version of tf_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)
    """
    input_shape = tf.shape(input)
    batch_size = input_shape[0]
    input_size = input_shape[1]
    n_coords = tf.shape(coords)[1]
    coords = tf.clip_by_value(coords, 0, tf.cast(input_size, 'float32') - 1)

    #得到目标坐标左上角(left top)的整数坐标
    coords_lt = tf.cast(tf.floor(coords), 'int32')

    #得到又下角的整数坐标
    coords_rb = tf.cast(tf.ceil(coords), 'int32')

    #得到左下角的整数坐标
    coords_lb = tf.stack([coords_lt[..., 0], coords_rb[..., 1]], axis=-1)

    #得到右上角的整数坐标
    coords_rt = tf.stack([coords_rb[..., 0], coords_lt[..., 1]], axis=-1)

    #idx为索引展开,idx大小为(b*c*h*w),形如(0,0,0,0,0,1,1,1,1,1,2,2,2,2,2,3,3,3,3,3)

    #b*c为5,h*w为4,总数为所有图片所有坐标总数

    idx = tf_repeat(tf.range(batch_size), n_coords)
    def _get_vals_by_coords(input, coords):

        #stack完后,每一个点表示一个坐标

        #形如

        #(0,0,0,0,0,1,1,1,1,1,2,2,2,2,2,3,3,3,3,3)

        # (3,2,1,2,3,1,2,3,0,0,0,3,2,1,1,2,3,2,0,0,2)

            # (3,2,1,0,0,2,0,3,1,2,3,0,0,2,3,0,1,2,0,2,3)

        indices = tf.stack([
            idx, tf_flatten(coords[..., 0]), tf_flatten(coords[..., 1])
        ], axis=-1)
        vals = tf.gather_nd(input, indices)
        vals = tf.reshape(vals, (batch_size, n_coords))
        return vals
     #以下为分别得到左上,左下,右上,右下四个点的像素值。
    vals_lt = _get_vals_by_coords(input, coords_lt)
    vals_rb = _get_vals_by_coords(input, coords_rb)
    vals_lb = _get_vals_by_coords(input, coords_lb)
    vals_rt = _get_vals_by_coords(input, coords_rt)
     #用双线性插值得到像素值。
    coords_offset_lt = coords - tf.cast(coords_lt, 'float32')
    vals_t = vals_lt + (vals_rt - vals_lt) * coords_offset_lt[..., 0]
    vals_b = vals_lb + (vals_rb - vals_lb) * coords_offset_lt[..., 0]
    mapped_vals = vals_t + (vals_b - vals_t) * coords_offset_lt[..., 1]
    return mapped_vals
def sp_batch_map_offsets(input, offsets):
    """Reference implementation for tf_batch_map_offsets"""
    batch_size = input.shape[0]
    input_size = input.shape[1]
    #生成grid,grid表示将一个图片的所有坐标变成两列,每一行两个元素表示x,y

    (grid的最后大小为(b*c,h*w,2)
    offsets = offsets.reshape(batch_size, -1, 2)
    grid = np.stack(np.mgrid[:input_size, :input_size], -1).reshape(-1, 2)
    grid = np.repeat([grid], batch_size, axis=0)

    #将原始坐标与坐标偏移量相加,得到目标坐标,coords的大小为(b*c,h*w,2)
    coords = offsets + grid

    #目标坐标需要在图片最大坐标范围内,将目标坐标进行切割限制
    coords = coords.clip(0, input_size - 1)
    #根据原始输入与目标坐标得到像素。
    mapped_vals = sp_batch_map_coordinates(input, coords)
    return mapped_vals
def tf_batch_map_offsets(input, offsets, order=1):
    """Batch map offsets into input
    Parameters
    ---------
    input : tf.Tensor. shape = (b, s, s)
    offsets: tf.Tensor. shape = (b, s, s, 2)
    Returns
    -------
    tf.Tensor. shape = (b, s, s)
    """
    input_shape = tf.shape(input)
    batch_size = input_shape[0]
    input_size = input_shape[1]
    offsets = tf.reshape(offsets, (batch_size, -1, 2))
    grid = tf.meshgrid(
        tf.range(input_size), tf.range(input_size), indexing='ij'
    )
    grid = tf.stack(grid, axis=-1)
    grid = tf.cast(grid, 'float32')
    grid = tf.reshape(grid, (-1, 2))
    grid = tf_repeat_2d(grid, batch_size)
    coords = offsets + grid
    mapped_vals = tf_batch_map_coordinates(input, coords)
    return mapped_vals

原文地址:https://www.cnblogs.com/yanghailin/p/12321832.html

时间: 2024-08-29 21:18:17

可变形卷积 deformable convolution 学习记录的相关文章

CNN卷积神经网络 的学习记录一

1. 概述 卷积神经网络的特点:一方面它的神经元间的连接是非全连接的, 另一方面同一层中某些神经元之间的连接的权重是共享的(即相同的). 上图左:图像有1000*1000个像素,有10^6个隐层神经元,进行全连接,有1000*1000*100000=10^12个权值参数 上图右:同样有1000*1000个像素,感受野为10*10 ,每一个节点与上层节点同位置附件10x10的窗口相连接,则有10*10*10^6=10^8个权值参数 我们可以很容易计算网络节点的输出.例如,对于上图中被标注为红色节点

从图(Graph)到图卷积(Graph Convolution):漫谈图神经网络模型 (一)

本文属于图神经网络的系列文章,文章目录如下: 从图(Graph)到图卷积(Graph Convolution):漫谈图神经网络模型 (一) 从图(Graph)到图卷积(Graph Convolution):漫谈图神经网络模型 (二) 从图(Graph)到图卷积(Graph Convolution):漫谈图神经网络模型 (三) 笔者最近看了一些图与图卷积神经网络的论文,深感其强大,但一些Survey或教程默认了读者对图神经网络背景知识的了解,对未学过信号处理的读者不太友好.同时,很多教程只讲是什么

莫比乌斯反演·学习记录

莫比乌斯反演·学习记录 cyw在6.8左右学的莫比乌斯反演,记录一下 这个东西感觉不大好描述,我一开始也不知道这玩意能干嘛(其实现在也不知道) CYW认为,对关于一些因数/倍数关系进行操作的行为,可以用莫比乌斯反演来解决 莫比乌斯函数 这并不是什么高大上的东西,但是很有用 对于 莫比乌斯函数 的定义是 $d=1,\mu(d)=1 $ \(d=\prod_{k=1}^n p_k\;(k\in prime),\mu(d)=(-1)^k\) 即数\(d\)可以被表示为若干互异素数相乘的形式(指数不超过

卷积在深度学习中的作用(转自http://timdettmers.com/2015/03/26/convolution-deep-learning/)

卷积可能是现在深入学习中最重要的概念.卷积网络和卷积网络将深度学习推向了几乎所有机器学习任务的最前沿.但是,卷积如此强大呢?它是如何工作的?在这篇博客文章中,我将解释卷积并将其与其他概念联系起来,以帮助您彻底理解卷积. 已经有一些关于深度学习卷积的博客文章,但我发现他们都对不必要的数学细节高度混淆,这些细节没有以任何有意义的方式进一步理解.这篇博客文章也会有很多数学细节,但我会从概念的角度来看待他们,在这里我用每个人都应该能够理解的图像表示底层数学.这篇博文的第一部分是针对任何想要了解深度学习中

Python学习记录-2016-12-17

今日学习记录 模块: import os#导入os模块 import sys#导入sys模块 os.system("df -h")#执行df -h命令 cmd_res = os.popen("df -h").read()#将命令的返回结果赋值给cmd_res,如果不加入.read()会显示命令的返回加过在内存的位置 print(sys.path)#显示系统变量路径,一般个人模块位于site-packages下,系统模块位于lib下 print(sys.argu[2]

Objc基础学习记录5

NSMutableString类继承的NSString类. NSMutableString是动态的字符串. 1.appendingString 方式: 向字符串尾部添加一个字符串. 2.appendingFormat:可以添加多个类型的字符串. int,chat float,double等 3.stringWithString 创建字符串, 4.rangeOfString 返回str1在另一个字符串中的位置. 5.NSMakeRange(0,3) 字符串0位到3位. 6.deleteCharac

Windows API 编程学习记录<二>

恩,开始写Windows API编程第二节吧. 上次介绍了几个关于Windows API编程最基本的概念,但是如果只是看这些概念,估计还是对Windows API不是很了解.这节我们就使用Windows API 让大家来了解下Windows API的用法. 第一个介绍的Windows API 当然是最经典的MessageBox,这个API 的作用就是在电脑上显示一个对话框,我们先来看看这个API的定义吧: int WINAPI MessageBox(HWND hWnd, LPCTSTR lpTe

Windows API 编程学习记录<三>

恩,开始写API编程的第三节,其实马上要考试了,但是不把这节写完,心里总感觉不舒服啊.写完赶紧去复习啊       在前两节中,我们介绍了Windows API 编程的一些基本概念和一个最基本API函数 MessageBox的使用,在这节中,我们就来正式编写一个Windows的窗口程序. 在具体编写代码之前,我们必须先要了解一下API 编写窗口程序具体的三个基本步骤:             1. 注册窗口类:             2.创建窗口:             3.显示窗口: 恩,

Python学习记录day6

Python学习记录day6 学习 python Python学习记录day6 1.反射 2.常用模块 2.1 sys 2.2 os 2.3 hashlib 2.3 re 1.反射 反射:利用字符串的形式去对象(默认)中操作(寻找)成员 cat commons.py #!/usr/bin/env python#_*_coding:utf-8_*_''' * Created on 2016/12/3 21:54. * @author: Chinge_Yang.''' def login(): pr