前言
TensorFlow中在使用卷积层函数的时候有一个参数padding可以选择same或者vaild,具体可以看之前的这篇文章:https://oldpan.me/archives/tf-keras-padding-vaild-same.而在pytorch中,现在的版本(0.3.1)中还是没有这个功能的,现在我们要在pytorch中实现与TensorFlow相同功能的padding=’same’的操作。
pytorch中padding-Vaild
首先需要说明一点,在pytorch中,如果你不指定padding的大小,在pytorch中默认的padding方式就是vaild。
我们用一段程序来演示一下pytorch中的vaild操作:
根据上图中的描述,我们首先定义一个长度为13的一维向量,然后用核大小为6,步长为5的一维卷积核对其进行卷积操作,由上图很容易看出输出为长度为2的数据(因为只进行了两次卷积操作,12和13被弃用了)。
>>> input = torch.FloatTensor([[[1,2,3,4,5,6,7,8,9,10,11,12,13]]]) >>> input (0 ,.,.) = 1 2 3 4 5 6 7 8 9 10 11 12 13 [torch.FloatTensor of size 1x1x13] # 输入长度为13 conv = torch.nn.Conv1d(1,1,6,5) # 定义一维卷积核 >>> input.size() >>> torch.Size([1, 1, 13]) >>> input = torch.autograd.Variable(input) >>> input Variable containing: (0 ,.,.) = 1 2 3 4 5 6 7 8 9 10 11 12 13 [torch.FloatTensor of size 1x1x13] >>> output = conv(input) >>> output.size() >>> torch.Size([1, 1, 2]) # 输出长度为2
由程序结果可以看到pytorch中的默认padding模式是vaild。
pytorch中padding-same
这里我们借用TensorFlow中的核心函数来模仿实现padding=same的效果。
def conv2d_same_padding(input, weight, bias=None, stride=1, padding=1, dilation=1, groups=1): # 函数中padding参数可以无视,实际实现的是padding=same的效果 input_rows = input.size(2) filter_rows = weight.size(2) effective_filter_size_rows = (filter_rows - 1) * dilation[0] + 1 out_rows = (input_rows + stride[0] - 1) // stride[0] padding_rows = max(0, (out_rows - 1) * stride[0] + (filter_rows - 1) * dilation[0] + 1 - input_rows) rows_odd = (padding_rows % 2 != 0) padding_cols = max(0, (out_rows - 1) * stride[0] + (filter_rows - 1) * dilation[0] + 1 - input_rows) cols_odd = (padding_rows % 2 != 0) if rows_odd or cols_odd: input = pad(input, [0, int(cols_odd), 0, int(rows_odd)]) return F.conv2d(input, weight, bias, stride, padding=(padding_rows // 2, padding_cols // 2), dilation=dilation, groups=groups)
自定义这个函数后我们移植pytorch中的Conv2d函数,在其forward中将默认的conv2d函数改为我们的padding-same函数:
import torch.utils.data from torch.nn import functional as F import math import torch from torch.nn.parameter import Parameter from torch.nn.functional import pad from torch.nn.modules import Module from torch.nn.modules.utils import _single, _pair, _triple class _ConvNd(Module): def __init__(self, in_channels, out_channels, kernel_size, stride, padding, dilation, transposed, output_padding, groups, bias): super(_ConvNd, self).__init__() if in_channels % groups != 0: raise ValueError('in_channels must be divisible by groups') if out_channels % groups != 0: raise ValueError('out_channels must be divisible by groups') self.in_channels = in_channels self.out_channels = out_channels self.kernel_size = kernel_size self.stride = stride self.padding = padding self.dilation = dilation self.transposed = transposed self.output_padding = output_padding self.groups = groups if transposed: self.weight = Parameter(torch.Tensor( in_channels, out_channels // groups, *kernel_size)) else: self.weight = Parameter(torch.Tensor( out_channels, in_channels // groups, *kernel_size)) if bias: self.bias = Parameter(torch.Tensor(out_channels)) else: self.register_parameter('bias', None) self.reset_parameters() def reset_parameters(self): n = self.in_channels for k in self.kernel_size: n *= k stdv = 1. / math.sqrt(n) self.weight.data.uniform_(-stdv, stdv) if self.bias is not None: self.bias.data.uniform_(-stdv, stdv) def __repr__(self): s = ('{name}({in_channels}, {out_channels}, kernel_size={kernel_size}' ', stride={stride}') if self.padding != (0,) * len(self.padding): s += ', padding={padding}' if self.dilation != (1,) * len(self.dilation): s += ', dilation={dilation}' if self.output_padding != (0,) * len(self.output_padding): s += ', output_padding={output_padding}' if self.groups != 1: s += ', groups={groups}' if self.bias is None: s += ', bias=False' s += ')' return s.format(name=self.__class__.__name__, **self.__dict__) class Conv2d(_ConvNd): def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True): kernel_size = _pair(kernel_size) stride = _pair(stride) padding = _pair(padding) dilation = _pair(dilation) super(Conv2d, self).__init__( in_channels, out_channels, kernel_size, stride, padding, dilation, False, _pair(0), groups, bias) # 修改这里的实现函数 def forward(self, input): return conv2d_same_padding(input, self.weight, self.bias, self.stride, self.padding, self.dilation, self.groups)
然后在实际使用中,调用我们移植过来修改完的函数即可。
亲测可以实现,具体可以到我这个项目源码中查看:https://github.com/Oldpan/faceswap-pytorch
参考资料:
https://github.com/pytorch/pytorch/issues/3867
https://github.com/tensorflow/tensorflow/blob/3c3c0481ec087aca4fa875d6d936f19b31191fc1/tensorflow/core/framework/common_shape_fns.cc#L40-L48
https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/kernels/conv_ops.cc#L568-L605
你好,我觉得你的函数是有一些问题的。
在conv2d_same_padding中,首先你只对row方向做了padding,col方向的padding就是直接复制row的padding模式,这显然只在stride[0] = stride[1]的时候是正确的,而两者不同的时候是不对的。
其次,你对row方向做的padding也是有问题的。你在计算row方向的padding时所用的都是stride[0], dilation[0],即col方向的参数,这样计算出来的结果显然是不一定正对的。
最后,stride等值的默认type和后面code中的type也不一样。后面code中明显的stride等应该是类list 的type,可是默认值却是int
第二点我理解错了。
多谢指正!很久之前的事儿了,第一点我稍后细看下,第二点Pytorch中有一个函数可以将int转化为n长度的tuple。
很感谢你的work!!我基于你原来的code稍微改了下放到了我的github:https://github.com/Gasoonjia/Tensorflow-type-padding-in-pytorch-conv2d. 感觉这样应该就没有问题了!
如果我这边还有什么问题还请你多指正!