Pytorch: [Feature Request] 为卷积运算实现“相同”的填充?

创建于 2017-11-25  ·  59评论  ·  资料来源: pytorch/pytorch

实现会很容易,但可以帮助许多人为计算他们需要多少填充而感到头疼。

抄送@ezyang @gchanan @zou3519 @albanD @mruberry

enhancement high priority convolution nn triaged

最有用的评论

近期有没有计划在pytorch中实现类似的api? 来自 tensorflow / keras 背景的人肯定会欣赏它。

所有59条评论

这似乎值得做。 你提议的接口是什么? 像nn.Conv2d(..., padding="same")

请注意,如果您正在寻找与 TensorFlow 相同的行为,则实现将不会那么简单,因为要添加的像素数取决于输入大小。 请参阅https://github.com/caffe2/caffe2/blob/master/caffe2/proto/caffe2_legacy.proto以供参考

感谢您指出问题和参考。
为了解决@fmassa提出的问题,我提出了两个接口。
首先,正如@soutmith提到的,第一个接口就像nn.Conv*d(..., padding="same") ,计算每个forward()调用的填充。
但是,当在初始化阶段知道输入形状时,这将是一种低效的方法。 因此,我建议使用像nn.CalcPadConv*d(<almost same parameters as Conv*d>)这样的接口。 使用它,用户可以在初始化时使用已知的宽度和高度计算填充,并将输出(填充的形状)传递给nn.Conv2d(...)的填充参数
我不确定第二个建议是否可能是过早的优化。
你怎么看这些? 有没有更好的名字的想法?

我认为效率低下的最大来源将来自这样一个事实,即我们需要在需要padding=same情况的每个其他卷积之前添加一个F.pad层(因为填充量可能不相同在左侧和右侧),例如参见TensorFlow 如何在cudnn情况下处理它。 所以这意味着nn.CalcPadConv*d通常与nn.Conv*d(..., padding="same")一样昂贵。

如果我们为卷积的每一侧支持不同的填充(比如在 Caffe2 中,左、右、上、下),这可以提高效率,但是 cudnn 仍然不支持,所以在这些情况下我们需要额外的填充.

另外,我认为如果我们将padding="same"nn.Conv*d ,我们可能应该对nn.*Pool*d做同样的事情,对吧?

我认为让我有点困扰的是,用户可能期望padding=same的行为等同于 TF,但他们可能不期望性能下降。

你怎么认为?

为什么会效率低下? 我们不能在每一步都计算填充吗? 成本应该很小,所以没有必要优化它。 也许我不完全理解语义,但我不明白为什么需要F.pad

使填充依赖于输入大小是非常糟糕的。 我们刚刚对此进行了内部讨论, @Yangqing概述了为什么出于各种序列化和效率原因这是一个坏主意。

@fmassa ,我打算使用nn.CalcPadConv*d()计算__init__() “恒定”填充形状。 正如您所说,当计算出的填充很奇怪时,这种方式不仅有效。 因此,需要添加F.pad层,或者支持F.conv*d用于奇数填充应该会有所帮助。

编辑:然后我建议的应该是一个函数并放置在,比如,torch.nn.utils 或 torch.utils。

结果,我建议的是一个简单的实用程序函数,例如(伪代码):

def calc_pad_conv1d(width, padding='same', check_symmetric=True, ... <params that conv1d has>):
    shape = <calculate padding>

    assert not check_symmetric or <shape is symmetric>, \
        'Calculated padding shape is asymmetric, which is not supported by conv1d. ' \ 
        'If you just want to get the value, consider using check_symmetric=False.'

    return shape


width = 100  # for example
padding = calc_pad_conv1d(width, ...)
m = nn.Conv1d(..., padding=padding)

此外,该功能可以与F.pad一起使用,以利于用户。

@qbx2也许我不完全理解你的提议,但如果我们想复制 TensorFlow 行为,我认为这还不够。

这是我认为模仿 TensorFlow SAME填充的片段(我将它写到功能界面中,以便nn.Conv2d可以调用F.conv2d_same_padding ):

def conv2d_same_padding(input, weight, bias=None, stride=1, dilation=1, groups=1):
  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_needed =
          max(0, (out_rows - 1) * stride[0] + effective_filter_size_rows -
                  input_rows)
  padding_rows = max(0, (out_rows - 1) * stride[0] +
                        (filter_rows - 1) * dilation[0] + 1 - input_rows)
  rows_odd = (padding_rows % 2 != 0)
  # same for padding_cols

  if rows_odd or cols_odd:
    input = F.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)

它主要是从此处此处的TensorFlow 代码复制粘贴的。

正如你所看到的,那里有很多隐藏的东西,这就是为什么我认为添加padding='same'可能不值得。 而且我认为不在 TensorFlow 中复制SAME行为也不理想。

想法?

@fmassa是的,你说得对。 计算每个forward()的填充可能效率低下。

但是,我的建议不是每次调用forward()都计算填充。 研究人员(开发人员)可能期望在运行之前图像的大小为nn.Conv2d 。 如果他/她想要“相同”的填充,他/她可以使用该函数来计算所需的填充来模仿“相同”。

例如,假设研究人员拥有 200x200、300x300、400x400 的图像。 然后他/她可以在初始化阶段计算三种情况的填充,并将图像传递给带有相应填充的F.pad() 。 或者他/她只是在forward()调用之前更改nn.Conv2d的填充字段。 参考这个:

>>> import torch
>>> import torch.nn as nn
>>> from torch.autograd import Variable
>>> m = nn.Conv2d(1,1,1)
>>> m(Variable(torch.randn(1,1,2,2))).shape
torch.Size([1, 1, 2, 2])
>>> m.padding = (1, 1)
>>> m(Variable(torch.randn(1,1,2,2))).shape
torch.Size([1, 1, 4, 4])

是的,我只想在pytorch核心中添加“填充计算效用函数”。

当研究人员希望对每个输入图像大小进行相关填充时,他/她可以在将图像传递给nn.Conv2d之前将该函数与F.pad()结合使用。 我想让代码编写者决定是否在每次forward()调用时填充输入。

近期有没有计划在pytorch中实现类似的api? 来自 tensorflow / keras 背景的人肯定会欣赏它。

因此,基本填补计算战略(给出相同的结果作为TensorFlow,但形状相似)是有

def _get_padding(padding_type, kernel_size):
    assert padding_type in ['SAME', 'VALID']
    if padding_type == 'SAME':
        return tuple((k - 1) // 2 for k in kernel_size))
    return tuple(0 for _ in kernel_size)

这就是你的想法@im9uri吗?

这与我的想法相似,但正如您之前提到的,计算会因步幅和扩张而变得复杂。

在其他卷积操作(如 ConvTranspose2d)中也有这样的 api 会很棒。

我认为“滑动窗口运算符”应该都支持非对称填充。

关于“相同”的论点......
@soumith你能解释一下为什么根据输入大小制作填充不好吗?
如果这是一个问题,无论如何,实用的解决方案可能是在使用“相同”时需要stride == 1 。 对于stride == 1 ,填充不依赖于输入大小并且可以计算一次。 如果用户尝试将padding='same'stride > 1一起使用,构造函数应该引发ValueError stride > 1

我知道,这不是最干净的解决方案,但鉴于以下情况,限制对我来说听起来足够合理:

  1. 标签“相同”的原始语义是为非跨步卷积引入的,并且是:输出具有输入的 _same_ 大小; 当然,对于stride > 1 tensorflow 而言,情况并非如此,这使得“相同”一词的使用有点误导 IMO;
  2. 它将涵盖人们想要使用“相同”的 99% 的情况; 我几乎无法想象有人真的需要stride > 1量流行为的情况,而如果我们给“相同”它的原始语义,当然,使用跨步卷积没有任何意义如果您希望输出具有与输入相同的大小。

conv2d文档给出了输出大小的明确公式。 等同于例如 Hout 与 Hin one 可以解决填充:

def _get_padding(size, kernel_size, stride, dilation):
    padding = ((size - 1) * (stride - 1) + dilation * (kernel_size - 1)) //2
    return padding

由于相同的padding意味着padding = (kernel_size - stride)//2,如果引入padding = "same",当写入时,它会自动读取内核大小和步幅(这也在nn.Conv2d中提到)并应用padding自动相应地

这是一个非常简单的 Conv2d 层,带有same填充以供参考。 它只支持平方核和stride=1,dilation=1,groups=1。

class Conv2dSame(torch.nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size, bias=True, padding_layer=torch.nn.ReflectionPad2d):
        super().__init__()
        ka = kernel_size // 2
        kb = ka - 1 if kernel_size % 2 == 0 else ka
        self.net = torch.nn.Sequential(
            padding_layer((ka,kb,ka,kb)),
            torch.nn.Conv2d(in_channels, out_channels, kernel_size, bias=bias)
        )
    def forward(self, x):
        return self.net(x)

c = Conv2dSame(1,3,5)
print(c(torch.rand((16,1,10,10))).shape)

# torch.Size([16, 3, 10, 10])

如果仍在评估是否将其添加到 PyTorch,那么关于复杂性/低效率与开发人员易用性之间的权衡:

通往 1.0 的博客文章中,它指出:

PyTorch 的核心目标是为研究和可入侵性提供一个很好的平台。 因此,虽然我们添加了所有这些 [生产-使用] 优化,但我们一直在使用硬设计约束,永远不要在这些与可用性之间进行权衡。

有趣的是,我来自使用 Keras 以及原始tf.layers / estimator API 的背景。 所有都支持same填充。 我目前正在重新实现我最初用 PyTorch 用 TF 编写的卷积网络,而且我不得不自己构建零填充算法这一事实让我花费了大约半天的时间。

如果“中心目标”真的专注于可用性,那么我认为即使在每次前向传递时计算零填充都会降低效率(如上所述),但在开发人员效率和可维护性方面节省的时间(例如不必编写自定义代码来计算零填充)可能值得权衡。 想法?

我会使用这个功能

为什么不能提供padding=SAME的可选 API 对我来说没有意义? 如果有人愿意承担额外的填充费用,那么让他们这样做。 对于许多研究人员来说,快速原型制作是一项要求。

是的,如果有人可以添加并批准这一点,那就太好了。

一定要加上这个,康纳想要它。

pytorch 现在支持吗? 它可以使用与 VGG 中的 first 相同的操作,设置 padding = (kernel_size-1)/2 吗?
VGG 网络可以使输出大小在第一组中不变。 然后就可以使用stride来调整featuremap的大小了,听起来好吗?

这是一个从 deepfakes 调用填充相同 conv2d 的示例:

# modify con2d function to use same padding
# code referd to <strong i="6">@famssa</strong> in 'https://github.com/pytorch/pytorch/issues/3867'
# and tensorflow source code

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)


# custom con2d, because pytorch don't have "padding='same'" option.
def conv2d_same_padding(input, weight, bias=None, stride=1, padding=1, dilation=1, groups=1):

    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_needed = max(0, (out_rows - 1) * stride[0] + effective_filter_size_rows -
                  input_rows)
    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)

顺便顺便说一句,我也非常感谢这一点。 目前正在从 tensorflow 移植一个简单​​的模型,计算需要很长时间才能弄清楚......

看起来这个线程刚刚消失了。 鉴于此处竖起大拇指的数量,添加此功能以加快原型制作真的很棒。

我会为此写一份提案,我们可以找到人来实施它。
我把它放在 v1.1 里程碑上。

谢谢你,你真棒! 我还提交了单独的功能请求,以使填充参数接受 4 元组。 这将允许非对称和对称填充,这也是达到一半的良好低成本路线。

@soumith在 pytorch 中有一个相同的填充模式会很好。

@soumith使用编译类型接口怎么样?

model=torch.compile(model,input_shape=(3,224,224))

根据 TensorFlow 的工作方式,我制作了一个具有相同填充的 Conv2D,支持扩张和步幅。 这个是实时计算的,如果你想预先计算它,只需将填充移动到 init() 并有一个输入大小参数。

import torch as tr
import math

class Conv2dSame(tr.nn.Module):

    def __init__(self, in_channels, out_channels, kernel_size, stride=1, dilation=1):
        super(Conv2dSame, self).__init__()
        self.F = kernel_size
        self.S = stride
        self.D = dilation
        self.layer = tr.nn.Conv2d(in_channels, out_channels, kernel_size, stride, dilation=dilation)

    def forward(self, x_in):
        N, C, H, W = x_in.shape
        H2 = math.ceil(H / self.S)
        W2 = math.ceil(W / self.S)
        Pr = (H2 - 1) * self.S + (self.F - 1) * self.D + 1 - H
        Pc = (W2 - 1) * self.S + (self.F - 1) * self.D + 1 - W
        x_pad = tr.nn.ZeroPad2d((Pr//2, Pr - Pr//2, Pc//2, Pc - Pc//2))(x_in)
        x_out = self.layer(x_pad)
        return x_out

例1:
输入形状:(1, 3, 96, 96)
过滤器:64
尺寸:9x9

Conv2dSame(3, 64, 9)

填充形状:(1、3、104、104)
输出形状:(1, 64, 96, 96)

例2:
与之前相同,但 stride=2

Conv2dSame(3, 64, 9, 2)

填充形状 = (1, 3, 103, 103)
输出形状 = (1, 64, 48, 48)

@jpatts我相信你的输出形状计算是错误的,应该是 ceil(input_dimension / stride)。 python 中的整数除法是楼层除法 - 你的代码应该有与张量流不同的结果,例如h=w=28, stride=3, kernel_size=1

这是一个预先进行计算的变体:

def pad_same(in_dim, ks, stride, dilation=1):
    """
    Refernces:
          https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/framework/common_shape_fns.h
          https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/framework/common_shape_fns.cc#L21
    """
    assert stride > 0
    assert dilation >= 1
    effective_ks = (ks - 1) * dilation + 1
    out_dim = (in_dim + stride - 1) // stride
    p = max(0, (out_dim - 1) * stride + effective_ks - in_dim)

    padding_before = p // 2
    padding_after = p - padding_before
    return padding_before, padding_after

如果输入维度是已知的并且不是即时计算的,则可以使用它,例如:

# Pass this to nn.Sequential
def conv2d_samepad(in_dim, in_ch, out_ch, ks, stride, dilation=1, bias=True):
    pad_before, pad_after = pad_same(in_dim, ks, stride, dilation)
    if pad_before == pad_after:
        return [nn.Conv2d(in_ch, out_ch, ks, stride, pad_after, dilation, bias=bias)]
    else:
        return [nn.ZeroPad2d((pad_before, pad_after, pad_before, pad_after)),
                nn.Conv2d(in_ch, out_ch, ks, stride, 0, dilation, bias=bias)]

但是,在这种情况下,需要对输入维度进行一些簿记(这是核心问题),因此如果您使用上述内容,您可能会发现有用:

def conv_outdim(in_dim, padding, ks, stride, dilation):
    if isinstance(padding, int) or isinstance(padding, tuple):
        return conv_outdim_general(in_dim, padding, ks, stride, dilation)
    elif isinstance(padding, str):
        assert padding in ['same', 'valid']
        if padding == 'same':
            return conv_outdim_samepad(in_dim, stride)
        else:
            return conv_outdim_general(in_dim, 0, ks, stride, dilation)
    else:
        raise TypeError('Padding can be int/tuple or str=same/valid')


def conv_outdim_general(in_dim, padding, ks, stride, dilation=1):
    # See https://arxiv.org/pdf/1603.07285.pdf, eq (15)
    return ((in_dim + 2 * padding - ks - (ks - 1) * (dilation - 1)) // stride) + 1


def conv_outdim_samepad(in_dim, stride):
    return (in_dim + stride - 1) // stride

@mirceamironenco感谢您指出这一点,我做得又快又脏,从未检查过。 更新为使用天花板代替

@harritaylor同意,此功能肯定会简化将 Keras/TF 模型移植到 PyTorch 的过程。 每隔一段时间,我仍然使用填充大小的“手动”计算来构建我的相同填充层。

@kylemcdonald

这是一个非常简单的 Conv2d 层,带有same填充以供参考。 它只支持平方核和stride=1,dilation=1,groups=1。

class Conv2dSame(torch.nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size, bias=True, padding_layer=torch.nn.ReflectionPad2d):
        super().__init__()
        ka = kernel_size // 2
        kb = ka - 1 if kernel_size % 2 == 0 else ka
        self.net = torch.nn.Sequential(
            padding_layer((ka,kb,ka,kb)),
            torch.nn.Conv2d(in_channels, out_channels, kernel_size, bias=bias)
        )
    def forward(self, x):
        return self.net(x)

c = Conv2dSame(1,3,5)
print(c(torch.rand((16,1,10,10))).shape)

# torch.Size([16, 3, 10, 10])

应该是kb = ka - 1 if kernel_size % 2 else ka还是不是?

这也适用于 Conv1d 吗?

也许向 ConvND 类添加新的填充方法将是一个优雅的选择,并且通过重载该方法,可以轻松扩展填充计划。

如果@soumith曾经写过那个提案或者有人总结了需要做的事情,我可能会接受这个。 上面已经进行了很多讨论,我不确定我们已经解决了哪些问题。 我们是否根据输入数据计算填充,我们是否还需要为池实现padding="same"等?

我也想添加因果填充。 也请将此添加到conv1d。
我在某个时候停止关注评论,但我认为这个功能在 keras 中做得很好。 你应该完全遵循它。

@Chillee给你:

范围

我们应该为以下图层添加填充:

  • 转化率*d
  • 最大池*d
  • 平均池*d

对于第一个 PR,让我们保持简单并坚持使用 Conv*d。

复杂性和缺点

上面讨论的复杂性是在写入same填充选项后,层在本质上变得动态。 也就是说,它从静态已知的层参数(这对于模型导出(例如 ONNX 导出)非常有用)到动态的层参数。 在这种情况下,动态参数是padding
虽然这看起来很无害,但非静态性在有限的运行时变得非常重要,例如移动或外来硬件运行时,例如您想要进行静态形状分析和优化。

另一个实际的缺点是,这个动态计算的padding不再总是对称的,因为根据内核的大小/步长、扩张因子和输入大小,填充可能必须是不对称的(即不同的左侧与右侧的填充量)。 例如,这意味着您不能使用 CuDNN 内核。

设计

目前,Conv2d 的签名是:

torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros')

在这里,我们支持padding是一个inttuple的整数(即对于每个高度/宽度维度)。
我们应该支持padding的额外重载,它接受一个字符串,值为same

在将output大小与input大小相同的卷积之前, same填充应该以这样的方式填充input

实施细则

'same'被赋予padding ,我们必须计算每个维度所需的左右填充量。

在计算所需的 L(左)和 R(右)填充后,有两种情况需要考虑:

  • L == R:在这种情况下它是对称填充。 可以简单地调用F.conv2dpadding值等于L
  • L != R:在这种情况下,填充是不对称的,它具有显着的性能和内存影响。 我们执行以下操作:

    • 我们调用input_padded = F.pad(input, ...)并将input_paddedF.conv2d

    • 我们在这种情况下发出警告(至少在初始版本中,如果需要警告,我们可以重新审视)有关性能影响的警告。

    • 我不记得公式的细节以及我们在哪里进入这个案例,但如果我记得的话,它可能就像拥有一个偶数大小的内核一样简单。 如果是这种情况,警告可以在用户端轻松修复。

不用说,它必须经过测试才能在 JIT 路径上工作

@Chilee供参考,这是从https://github.com/mlperf/inference/blob/master/others/edge/object_detection/ssd_mobilenet/pytorch/utils.py#L40获得灵感的潜在实现

它与所测试配置的 TF 实现相匹配,但测试并非详尽无遗

@soumith一些简单的问题:

  1. 有什么理由我们不应该通过functional.conv2d吗? 你写的设计似乎暗示它不应该。 没有任何关于padding = "same" 的内容似乎应该特定于层。 (编辑:Nvm,没有意识到我正在查看的F.conv2d impl 是量化的)。
  2. 我认为 Tensorflow 的valid填充模式与我们的padding=0填充模式完全相同,对吗?

此外,用户处理非对称填充似乎没有一个简单的方法。 确定需要发生的填充量的完整规则是
(ceil(x/stride) -1)*stride + (filter-1)*dilation + 1 - x沿维度。 特别是,当它不是 2 的倍数时,我们将需要进行非对称填充。作为您希望这仅发生在偶数大小的过滤器中的反例,取input = 10, stride=3, filter=3, dilation=1 。 我没有看到任何解决可能发生这种情况的简单规则。

此外,我们将无法静态确定填充,除非在stride=1的情况下,然后ceil(x/stride) = x ,并且我们的填充等于(filter-1)*dilation

@Chillee关于 (1),没有理由,我没有考虑过其中的含义——perf 或其他。

(2) 是的。

此外,我们将无法静态确定填充,除非 stride=1,因为 ceil(x/stride) = x,并且填充等于 (filter-1)*dilation

是的,但是 stride=1 已经足够常见了,并且静态填充的好处足够好,我们绝对应该特别处理它。

关于不对称填充,哦,好吧.....

为什么不能提供padding=SAME的可选 API 对我来说没有意义? 如果有人愿意承担额外的填充费用,那么让他们这样做。 对于许多研究人员来说,快速原型制作是一项要求。

是的,

我不明白为什么不能提供padding=SAME的可选 API? 如果有人愿意承担额外的填充费用,那么让他们这样做。 对于许多研究人员来说,快速原型制作是一项要求。

同意! 我被这个该死的“填充”困了 4 个小时。

我们有关于此问题解决方案的任何更新吗?

哇,在这里我认为 Pytorch 会比 Keras/Tensorflow 2.0 更容易......

@zwep 入门需要更多的努力。 你必须编写你的 trianing 循环,这可能很烦人,你必须更明确地编写层。 一旦完成(一次),您就可以在实际改进方面取得更大进展。

我的经验法则是使用 Keras,如果你已经做了一百万次/超标准。
在涉及研究和开发的任何时候都可以使用 pytorch。

这是我的填充 1d 转换代码

进口火炬
从火炬进口nn
将 numpy 导入为 np
导入 torch.functional as F

class Conv1dSamePad(nn.Module):
    def __init__(self, in_channels, out_channels, filter_len, stride=1, **kwargs):
        super(Conv1dSamePad, self).__init__()
        self.filter_len = filter_len
        self.conv = nn.Conv1d(in_channels, out_channels, filter_len, padding=(self.filter_len // 2), stride=stride,
                              **kwargs)
        nn.init.xavier_uniform_(self.conv.weight)
        # nn.init.constant_(self.conv.bias, 1 / out_channels)

    def forward(self, x):
        if self.filter_len % 2 == 1:
            return self.conv(x)
        else:
            return self.conv(x)[:, :, :-1]


class Conv1dCausalPad(nn.Module):
    def __init__(self, in_channels, out_channels, filter_len, **kwargs):
        super(Conv1dCausalPad, self).__init__()
        self.filter_len = filter_len
        self.conv = nn.Conv1d(in_channels, out_channels, filter_len, **kwargs)
        nn.init.xavier_uniform_(self.conv.weight)

    def forward(self, x):
        padding = (self.filter_len - 1, 0)
        return self.conv(F.pad(x, padding))


class Conv1dPad(nn.Module):
    def __init__(self, in_channels, out_channels, filter_len, padding="same", groups=1):
        super(Conv1dPad, self).__init__()
        if padding not in ["same", "causal"]:
            raise Exception("invalid padding type %s" % padding)
        self.conv = Conv1dCausalPad(in_channels, out_channels, filter_len, groups=groups) \
            if padding == "causal" else Conv1dSamePad(in_channels, out_channels, filter_len, groups=groups)

    def forward(self, x):
        return self.conv(x)

@danFromTelAviv伙计,感谢您的代码。 将牢记 pytorch 哲学!

现在是 2020 年。 Pytorch 中仍然没有padding='same'吗?

这是使相同的填充适用于任何内核大小、步幅和膨胀(甚至内核大小也适用)的一种方法。

class Conv1dSame(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size, stride=1, dilation=1):
        super().__init__()
        self.cut_last_element = (kernel_size % 2 == 0 and stride == 1 and dilation % 2 == 1)
        self.padding = math.ceil((1 - stride + dilation * (kernel_size-1))/2)
        self.conv = nn.Conv1d(in_channels, out_channels, kernel_size, padding=self.padding, stride=stride, dilation=dilation)

    def forward(self, x):
        if self.cut_last_element:
            return self.conv(x)[:, :, :-1]
        else:
            return self.conv(x)

我也想要nn.Conv2d的“相同填充”功能。

顺便说一句,除了上面讨论的性能/序列化问题之外,还有正确性/准确性原因说明为什么 TF 中依赖于大小的“相同”填充模式不是一个好的默认值。 我在https://github.com/tensorflow/tensorflow/issues/18213 中讨论过,并表明实际上许多谷歌自己的代码使用与大小无关的“相同”填充模式。

目前似乎没有关于这个问题的正在进行的工作,但如果有,我希望这是一个与大小无关的解决方案。

嗨, @ppwwyyxx Yuxin ,感谢您的回复。
我认为@McHughes288的实现很好,我想知道你对他的实现的看法。

这是我对 Conv1D SAME填充的解决方案(仅在dilation==1groups==1时才能正常工作,当您考虑扩张和组时会更复杂):

import torch.nn.functional as F
from torch import nn

class Conv1dSamePadding(nn.Conv1d):
    """Represents the "Same" padding functionality from Tensorflow.
    NOTE: Only work correctly when dilation == 1, groups == 1 !!!
    """
    def forward(self, input):
        size, kernel, stride = input.size(-1), self.weight.size(
            2), self.stride[0]
        padding = kernel - stride - size % stride
        while padding < 0:
            padding += stride
        if padding != 0:
            # pad left by padding // 2, pad right by padding - padding // 2
            # in Tensorflow, one more padding value(default: 0) is on the right when needed
            input = F.pad(input, (padding // 2, padding - padding // 2))
        return F.conv1d(input=input,
                        weight=self.weight,
                        bias=self.bias,
                        stride=stride,
                        dilation=1,
                        groups=1)

@Chillee你打算继续研究这个功能吗? 我将暂时取消分配给您,以便我们可以更好地跟踪此问题的进度,如果您仍在处理此问题,请随时重新分配。

阅读@wizcheu的代码后,我使用 padding='same' 创建了另一个版本的 conv1d

class Conv1dPaddingSame(nn.Module):
    '''pytorch version of padding=='same'
    ============== ATTENTION ================
    Only work when dilation == 1, groups == 1
    =========================================
    '''
    def __init__(self, in_channels, out_channels, kernel_size, stride):
        super(Conv1dPaddingSame, self).__init__()
        self.kernel_size = kernel_size
        self.stride = stride
        self.weight = nn.Parameter(torch.rand((out_channels, 
                                                 in_channels, kernel_size)))
        # nn.Conv1d default set bias=True,so create this param
        self.bias = nn.Parameter(torch.rand(out_channels))

    def forward(self, x):
        batch_size, num_channels, length = x.shape
        if length % self.stride == 0:
            out_length = length // self.stride
        else:
            out_length = length // self.stride + 1

        pad = math.ceil((out_length * self.stride + 
                         self.kernel_size - length - self.stride) / 2)
        out = F.conv1d(input=x, 
                       weight = self.weight,
                       stride = self.stride, 
                       bias = self.bias,
                       padding=pad)
        return out

有任何更新吗?

任何更新??

@peterbell10链接了一个 PR 草案,您可以关注它。

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

eliabruni picture eliabruni  ·  3评论

bartolsthoorn picture bartolsthoorn  ·  3评论

a1363901216 picture a1363901216  ·  3评论

cdluminate picture cdluminate  ·  3评论

SeparateReality picture SeparateReality  ·  3评论