Pytorch: [Permintaan Fitur] Terapkan padding "sama" untuk operasi konvolusi?

Dibuat pada 25 Nov 2017  ·  59Komentar  ·  Sumber: pytorch/pytorch

Implementasinya akan mudah, tetapi dapat membantu banyak orang yang sakit kepala karena menghitung berapa banyak bantalan yang mereka butuhkan.

cc @ezyang @gchanan @zou3519 @albanD @mruberry

enhancement high priority convolution nn triaged

Komentar yang paling membantu

Apakah ada rencana untuk mengimplementasikan api serupa di pytorch dalam waktu dekat? Orang yang berasal dari latar belakang tensorflow / keras pasti akan menghargainya.

Semua 59 komentar

Ini sepertinya layak dilakukan. Apa antarmuka yang Anda usulkan? seperti nn.Conv2d(..., padding="same") ?

Perhatikan bahwa jika Anda mencari perilaku TensorFlow yang sama, implementasinya tidak akan semudah itu, karena jumlah piksel yang akan ditambahkan bergantung pada ukuran input. Lihat https://github.com/caffe2/caffe2/blob/master/caffe2/proto/caffe2_legacy.proto untuk referensi

Terima kasih telah menunjukkan masalah dan referensinya.
Untuk mengatasi masalah yang dinyatakan oleh @fmassa , saya mengusulkan dua antarmuka.
Pertama, seperti yang @soutmith sebutkan, antarmuka pertama akan seperti nn.Conv*d(..., padding="same") , menghitung padding setiap panggilan forward() .
Namun, itu akan menjadi cara yang tidak efisien ketika bentuk input diketahui pada fase inisialisasi. Oleh karena itu, saya menyarankan antarmuka seperti nn.CalcPadConv*d(<almost same parameters as Conv*d>) . Dengan menggunakannya, pengguna dapat menghitung padding menggunakan lebar dan tinggi yang diketahui dalam inisialisasi, dan meneruskan output (bentuk padding) ke parameter padding nn.Conv2d(...)
Saya tidak yakin apakah proposal kedua bisa menjadi optimasi prematur.
Bagaimana menurut Anda tentang ini? Apakah ada ide nama yang lebih baik?

Saya pikir sumber inefisiensi terbesar akan datang dari fakta bahwa kita perlu menambahkan lapisan F.pad sebelum setiap konvolusi lain yang memerlukan kasus padding=same (karena jumlah padding mungkin tidak sama di sisi kiri dan kanan), lihat misalnya bagaimana TensorFlow harus menanganinya dalam kasus cudnn . Jadi itu berarti nn.CalcPadConv*d biasanya akan semahal nn.Conv*d(..., padding="same") .

Ini dapat dibuat lebih efisien jika kami mendukung bantalan yang berbeda untuk setiap sisi konvolusi (seperti di Caffe2, jadi kiri, kanan, atas, bawah), tetapi cudnn masih tidak mendukungnya sehingga kami memerlukan bantalan ekstra dalam kasus tersebut .

Juga, saya pikir jika kita menambahkan padding="same" ke nn.Conv*d , kita mungkin harus melakukan hal yang sama untuk nn.*Pool*d , bukan?

Saya pikir apa yang sedikit mengganggu saya adalah bahwa pengguna mungkin mengharapkan perilaku padding=same setara dengan TF, tetapi mereka mungkin tidak mengharapkan penurunan kinerja.

Bagaimana menurutmu?

Mengapa itu tidak efisien? tidak bisakah kita menghitung padding di setiap langkah maju? biayanya harus kecil, jadi tidak perlu mengoptimalkannya. Mungkin saya tidak sepenuhnya memahami semantik, tetapi saya tidak dapat melihat mengapa F.pad diperlukan.

membuat padding tergantung pada ukuran input cukup buruk. Kami baru saja berdiskusi internal tentang ini, dengan @Yangqing menjelaskan mengapa ini adalah ide yang buruk untuk berbagai alasan serialisasi dan efisiensi.

@fmassa , yang saya maksudkan adalah menghitung bentuk padding "konstan" di __init__() menggunakan nn.CalcPadConv*d() . Seperti yang Anda katakan, cara ini tidak hanya berfungsi ketika padding yang dihitung ganjil. Oleh karena itu, lapisan F.pad perlu ditambahkan, atau, dukungan F.conv*d untuk bantalan ganjil akan membantu.

EDIT: Lalu apa yang saya sarankan harus berupa fungsi dan ditempatkan di, katakanlah, torch.nn.utils atau torch.utils.

Hasilnya, yang saya sarankan adalah fungsi utilitas sederhana, seperti (pseudocode):

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)

Juga, Fungsi ini dapat digunakan dengan F.pad untuk kepentingan pengguna.

@ qbx2 mungkin saya tidak sepenuhnya memahami proposal Anda, tetapi jika kami ingin meniru perilaku TensorFlow, saya rasa ini tidak cukup.

Berikut adalah cuplikan dari apa yang menurut saya meniru padding TensorFlow SAME (Saya menuliskannya ke antarmuka fungsional, sehingga nn.Conv2d dapat memanggil 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)

Itu sebagian besar copy-paste dari kode TensorFlow di sini dan di sini .

Seperti yang Anda lihat, ada banyak hal tersembunyi yang terjadi di sana, dan itulah mengapa saya pikir mungkin tidak ada gunanya menambahkan padding='same' . Dan saya pikir tidak mereplikasi perilaku SAME di TensorFlow juga tidak ideal.

Pikiran?

@fmassa Ya, Anda benar. Mungkin tidak efisien untuk menghitung padding pada setiap forward() .

Namun, proposal saya BUKAN untuk menghitung padding setiap panggilan forward() . Seorang peneliti (pengembang) mungkin mengharapkan ukuran gambar menjadi nn.Conv2d sebelum runtime. Dan jika dia menginginkan padding yang 'sama', dia dapat menggunakan fungsi untuk menghitung padding yang diperlukan untuk meniru 'SAMA'.

Misalnya, pikirkan kasus seorang peneliti memiliki gambar dengan ukuran 200x200, 300x300, 400x400. Kemudian dia dapat menghitung bantalan untuk tiga kasus dalam fase inisialisasi dan hanya meneruskan gambar ke F.pad() dengan bantalan yang sesuai. Atau dia hanya mengubah bidang pengisi nn.Conv2d sebelum panggilan forward() . Lihat ini:

>>> 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])

Ya, saya hanya ingin menambahkan "fungsi utilitas penghitungan padding" di inti pytorch.

Ketika peneliti menginginkan padding dependen pada setiap ukuran gambar masukan, ia dapat menggabungkan fungsi tersebut dengan F.pad() sebelum meneruskan gambar ke nn.Conv2d . Saya ingin membiarkan penulis kode memutuskan apakah akan memasukkan input pada setiap panggilan forward() atau tidak.

Apakah ada rencana untuk mengimplementasikan api serupa di pytorch dalam waktu dekat? Orang yang berasal dari latar belakang tensorflow / keras pasti akan menghargainya.

Jadi, strategi penghitungan padding dasar (yang tidak memberikan hasil yang sama seperti TensorFlow, tetapi bentuknya serupa) harus memiliki

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)

Apakah itu yang ada di pikiranmu @im9uri ?

Ini mirip dengan apa yang saya pikirkan, tetapi seperti yang Anda sebutkan sebelumnya, perhitungannya menjadi rumit dengan langkah dan pelebaran.

Juga memiliki api seperti itu dalam operasi konvolusi lain seperti ConvTranspose2d akan sangat bagus.

Saya pikir "operator jendela geser" semuanya harus mendukung bantalan asimetris.

Tentang argumen yang "sama"...
@soumith Bisakah Anda menjelaskan mengapa membuat padding tergantung pada ukuran input buruk?
Bagaimanapun, jika itu masalah, solusi pragmatis mungkin memerlukan stride == 1 saat menggunakan "sama". Untuk stride == 1 , padding tidak bergantung pada ukuran input dan dapat dihitung satu kali. Konstruktor harus menaikkan ValueError jika pengguna mencoba menggunakan padding='same' dengan stride > 1 .

Saya tahu, ini bukan solusi terbersih tetapi batasannya terdengar cukup masuk akal bagi saya mengingat:

  1. semantik asli dari label "sama" diperkenalkan untuk tidak berbelit-belit dan adalah: output memiliki ukuran input yang _sama_; tentu saja, ini tidak benar dalam tensorflow untuk stride > 1 dan itu membuat penggunaan kata "sama" sedikit menyesatkan IMO;
  2. itu akan mencakup 99% kasus yang ingin digunakan "sama"; Saya hampir tidak dapat membayangkan sebuah kasus ketika seseorang benar-benar membutuhkan perilaku tensorflow untuk stride > 1 , sementara jika kita memberikan "sama" semantik aslinya, yah, tentu saja tidak masuk akal untuk menggunakan konvolusi bertahap jika Anda ingin output memiliki ukuran input yang sama.

dokumentasi conv2d memberikan rumus eksplisit untuk ukuran keluaran. Menyamakan misalnya Hout dengan Hin, seseorang dapat memecahkan padding:

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

Karena padding yang sama berarti padding = (kernel_size - stride)//2, bagaimana jika padding = "sama" diperkenalkan sehingga ketika ditulis, secara otomatis membaca ukuran kernel dan stride (seperti yang juga disebutkan dalam nn.Conv2d) dan menerapkan padding secara otomatis sesuai

Berikut adalah lapisan Conv2d yang sangat sederhana dengan bantalan same untuk referensi. Ini hanya mendukung kernel persegi dan 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])

Jika ini masih dievaluasi untuk ditambahkan ke PyTorch, maka mengenai kompromi antara kompleksitas/inefisiensi vs. kemudahan penggunaan untuk pengembang:

Di jalan menuju posting blog 1.0 , ia menyatakan:

Tujuan utama PyTorch adalah untuk menyediakan platform yang bagus untuk penelitian dan kemampuan untuk diretas. Jadi, sementara kami menambahkan semua pengoptimalan [penggunaan produksi] ini, kami telah bekerja dengan batasan desain yang keras untuk tidak pernah menukarnya dengan kegunaan.

Secara anekdot, saya berasal dari latar belakang menggunakan Keras serta API tf.layers / estimator asli. Semua memiliki dukungan untuk padding same . Saat ini saya sedang mengimplementasikan kembali sebuah convnet yang awalnya saya tulis di TF dengan PyTorch, dan fakta bahwa saya harus membangun aritmatika untuk zero-padding sendiri telah menghabiskan waktu sekitar setengah hari.

Jika "tujuan utama" benar-benar difokuskan pada kegunaan, daripada saya berpendapat bahwa bahkan jika ada efisiensi yang tercapai untuk menghitung zero-padding pada setiap forward pass (seperti yang disebutkan di atas), waktu yang dihemat dalam hal efisiensi dan pemeliharaan pengembang ( misalnya tidak harus menulis kode khusus untuk menghitung zero padding) mungkin sepadan dengan pengorbanannya. Pikiran?

Saya akan menggunakan fitur ini

Tidak masuk akal bagi saya mengapa API opsional padding=SAME dapat ditawarkan? Jika seseorang bersedia mengeluarkan biaya tambahan untuk padding maka biarkan mereka melakukannya. Bagi banyak peneliti, pembuatan prototipe cepat adalah persyaratan.

Ya, jika seseorang dapat menambahkan dan menyetujui ini, itu akan sangat bagus.

Pasti menambahkan ini, conner menginginkannya.

Apakah pytorch mendukungnya sekarang? Bisakah menggunakan operasi yang sama seperti pertama di VGG, set padding = (kernel_size-1)/2 ?
Jaringan VGG dapat membuat ukuran output tidak berubah di grup pertama. Kemudian Anda dapat menggunakan stride untuk mengubah ukuran peta fitur, apakah kedengarannya baik-baik saja?

Berikut adalah salah satu contoh untuk memanggil padding conv2d yang sama dari deepfake:

# 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)

Hanya mampir untuk mengatakan bahwa saya juga sangat menghargai ini. Saat ini porting model sederhana dari tensorflow dan perhitungannya membutuhkan waktu yang sangat lama bagi saya untuk mengetahuinya ...

Sepertinya utas ini baru saja mati. Mengingat jumlah jempol di sini, akan sangat bagus untuk menambahkan fitur ini untuk pembuatan prototipe yang lebih cepat.

Saya akan menulis proposal untuk ini dan kita dapat menemukan seseorang untuk mengimplementasikannya.
Saya menempatkan ini terhadap tonggak v1.1.

Terima kasih, kalian luar biasa! Saya juga mengajukan permintaan fitur terpisah untuk membuat argumen padding menerima 4-Tuple. Ini akan memungkinkan padding asimetris serta simetris yang juga merupakan rute berbiaya rendah yang baik untuk sampai di tengah jalan.

@soumith Akan menyenangkan untuk memiliki mode padding SAMA di pytorch.

@soumith Bagaimana dengan menggunakan antarmuka tipe kompilasi?

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

Saya membuat Conv2D dengan padding yang sama yang mendukung pelebaran dan langkah, berdasarkan cara TensorFlow melakukannya. Yang ini menghitungnya secara real time, jika Anda ingin menghitung sebelumnya, cukup pindahkan padding ke init() dan miliki parameter ukuran input.

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

Contoh1:
Bentuk masukan: (1, 3, 96, 96)
Filter: 64
Ukuran: 9x9

Conv2dSame(3, 64, 9)

Bentuk empuk: (1, 3, 104, 104)
Bentuk keluaran: (1, 64, 96, 96)

Contoh2:
Sama seperti sebelumnya, tetapi dengan langkah = 2

Conv2dSame(3, 64, 9, 2)

Bentuk empuk = (1, 3, 103, 103)
Bentuk keluaran = (1, 64, 48, 48)

@jpatts Saya yakin perhitungan bentuk keluaran Anda salah, seharusnya ceil(input_dimension / stride). Pembagian bilangan bulat di python adalah pembagian lantai - kode Anda harus memiliki hasil yang berbeda dari tensorflow misalnya h=w=28, stride=3, kernel_size=1 .

Berikut adalah varian yang melakukan perhitungan sebelumnya:

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

Jika dimensi input diketahui dan tidak dihitung dengan cepat, ini dapat digunakan misalnya:

# 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)]

Namun, dalam hal ini beberapa pembukuan perlu dilakukan untuk dimensi input (ini adalah masalah inti), jadi jika Anda menggunakan cara di atas, Anda mungkin merasa berguna:

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 terima kasih telah menunjukkannya, saya membuatnya cepat dan kotor dan tidak pernah memeriksanya. Diperbarui untuk menggunakan langit-langit sebagai gantinya

@harritaylor Setuju, fitur ini pasti akan menyederhanakan port model Keras/TF ke PyTorch. Sesekali, saya masih menggunakan perhitungan "manual" dari ukuran padding untuk membangun lapisan yang sama.

@kylemcdonald

Berikut adalah lapisan Conv2d yang sangat sederhana dengan bantalan same untuk referensi. Ini hanya mendukung kernel persegi dan 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])

Haruskah kb = ka - 1 if kernel_size % 2 else ka atau tidak ?

Apakah ini juga berlaku untuk Conv1d?

Mungkin menambahkan metode padding baru ke kelas ConvND akan menjadi pilihan yang elegan, dan dengan membebani metode ini, jadwal padding dapat dengan mudah diperpanjang.

Saya mungkin bisa mengambil ini jika @soumith pernah menulis proposal itu atau jika seseorang merangkum apa yang perlu dilakukan. Ada banyak diskusi di atas dan saya tidak yakin apa yang telah kita selesaikan. Apakah kita menghitung padding tergantung pada data input atau tidak, apakah kita perlu mengimplementasikan padding="same" untuk pool juga, dll.?

Saya juga ingin menambahkan bantalan kausal. dan tolong juga tambahkan ini ke conv1d.
saya berhenti mengikuti komentar di beberapa titik tetapi saya pikir fitur ini dilakukan dengan sangat baik dengan keras. Anda harus mengikutinya dengan tepat.

@Chillee ini dia:

Cakupan

Kita harus menambahkan padding ke layer berikut:

  • Konv*d
  • MaxPool*d
  • Rata-RataKolam*d

Untuk PR pertama, mari kita tetap sederhana dan tetap berpegang pada Conv*d.

Kompleksitas dan Kelemahan

Kompleksitas yang dibahas di atas adalah seputar lapisan yang menjadi dinamis, setelah opsi padding same ditulis. Artinya, ia beralih dari parameter lapisan yang diketahui secara statis, yang bagus untuk ekspor model (misalnya ekspor ONNX), ke parameter lapisan yang dinamis. Dalam hal ini, parameter dinamis adalah padding .
Meskipun ini terlihat tidak berbahaya, non-statis menjadi sangat penting dalam runtime terbatas, seperti runtime seluler atau perangkat keras eksotis, di mana misalnya Anda ingin melakukan analisis dan pengoptimalan bentuk statis.

Kelemahan praktis lainnya adalah bahwa padding dihitung secara dinamis ini tidak selalu simetris lagi, karena tergantung pada ukuran / langkah kernel, faktor dilatasi, dan ukuran input, padding mungkin harus asimetris (yaitu berbeda jumlah padding di sisi kiri vs kanan). Ini berarti Anda tidak dapat menggunakan kernel CuDNN misalnya.

Desain

Saat ini, tanda tangan Conv2d adalah:

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

Di sini, kami mendukung padding menjadi int atau tuple int (yaitu untuk setiap dimensi tinggi / lebar).
Kita harus mendukung kelebihan tambahan untuk padding yang akan mengambil string, dengan nilai same .

Padding same harus melapisi input sedemikian rupa sebelum memberikannya ke konvolusi sehingga ukuran output sama dengan ukuran input .

Detail implementasi

Ketika 'same' diberikan ke padding , kita harus menghitung jumlah padding kiri dan kanan yang dibutuhkan di setiap dimensi.

Ada dua kasus yang perlu dipertimbangkan setelah padding L (kiri) dan R (kanan) yang diperlukan dihitung:

  • L == R: dalam hal ini padding simetris. Seseorang cukup memanggil F.conv2d dengan nilai padding sama dengan L
  • L != R: Dalam hal ini, padding tidak simetris, dan memiliki implikasi kinerja dan memori yang signifikan. Kami melakukan hal berikut:

    • kita memanggil input_padded = F.pad(input, ...) dan mengirim input_padded ke F.conv2d .

    • kami memberikan peringatan untuk kasus ini (setidaknya untuk rilis awal, dan kami dapat mengunjungi kembali jika peringatan diperlukan) tentang implikasi kinerja.

    • Saya tidak ingat detail formulasi dan di mana kita memasukkan kasus ini, tetapi jika saya ingat, mungkin sesederhana memiliki kernel berukuran genap. Jika itu masalahnya, peringatan dapat diperbaiki dengan mudah di sisi pengguna.

Tak perlu dikatakan, itu harus diuji untuk juga bekerja di jalur JIT

@Chilee untuk referensi, berikut adalah implementasi potensial untuk mendapatkan inspirasi dari https://github.com/mlperf/inference/blob/master/others/edge/object_detection/ssd_mobilenet/pytorch/utils.py#L40

Itu cocok dengan implementasi TF untuk konfigurasi yang diuji, tetapi pengujiannya tidak lengkap

@soumith Beberapa pertanyaan singkat:

  1. Apakah ada alasan mengapa kita tidak boleh mengimplementasikan ini melalui functional.conv2d ? Desain yang Anda tulis tampaknya menyiratkan bahwa tidak seharusnya demikian. Tidak ada apa-apa tentang padding = "sama" yang sepertinya harus khusus untuk lapisan. (EDIT: Nvm, tidak menyadari impl F.conv2d saya lihat adalah yang terkuantisasi).
  2. Saya pikir mode padding valid Tensorflow setara dengan kita dengan padding=0 , bukan?

Juga, tampaknya tidak akan ada perbaikan yang mudah bagi pengguna untuk menangani bantalan asimetris. Aturan lengkap untuk menentukan jumlah padding yang perlu terjadi adalah
(ceil(x/stride) -1)*stride + (filter-1)*dilation + 1 - x sepanjang dimensi. Secara khusus, kita perlu melakukan padding asimetris ketika ini bukan kelipatan 2. Sebagai contoh tandingan dari harapan Anda bahwa ini hanya terjadi dengan filter berukuran genap, ambil input = 10, stride=3, filter=3, dilation=1 . Saya tidak melihat aturan sederhana untuk menyelesaikan situasi di mana ini bisa terjadi.

Selanjutnya, kita tidak akan dapat menentukan padding secara statis kecuali dalam kasus ketika stride=1 , seperti ceil(x/stride) = x , dan kita memiliki padding yang sama dengan (filter-1)*dilation .

@Chillee tentang (1), tanpa alasan, saya belum memikirkan implikasinya -- perf atau sebaliknya.

(2) Ya.

Selanjutnya, kita tidak akan dapat menentukan padding secara statis kecuali dalam kasus ketika stride=1, karena ceil(x/stride) = x, dan kita memiliki padding yang sama dengan (filter-1)*dilatasi

Ya, tapi stride=1 sudah cukup umum dan manfaat dari static padding cukup baik sehingga kita harus menanganinya secara khusus.

Tentang bantalan asimetris, oh well.....

Tidak masuk akal bagi saya mengapa API opsional padding=SAME dapat ditawarkan? Jika seseorang bersedia mengeluarkan biaya tambahan untuk padding maka biarkan mereka melakukannya. Bagi banyak peneliti, pembuatan prototipe cepat adalah persyaratan.

Ya,

Tidak masuk akal bagi saya mengapa API opsional padding=SAME dapat ditawarkan? Jika seseorang bersedia mengeluarkan biaya tambahan untuk padding maka biarkan mereka melakukannya. Bagi banyak peneliti, pembuatan prototipe cepat adalah persyaratan.

Setuju! Saya terjebak dalam "padding" sialan ini selama 4 jam.

Apakah kami memiliki pembaruan tentang solusi untuk masalah ini?

Wow dan di sini saya pikir Pytorch akan lebih mudah daripada Keras/Tensorflow 2.0...

@zwep ada sedikit lebih banyak usaha untuk memulai. Anda harus menulis loop trianing Anda yang dapat mengganggu dan Anda harus menulis lapisan lebih eksplisit. Setelah Anda menyelesaikannya (sekali), Anda dapat maju lebih jauh pada peningkatan aktual di luar itu.

Aturan praktis saya adalah menggunakan Keras jika itu adalah sesuatu yang telah Anda lakukan jutaan kali/standar super.
gunakan pytorch setiap kali ada penelitian dan pengembangan yang terlibat.

di sini adalah kode saya untuk konvs 1d empuk

impor obor
dari impor obor nn
impor numpy sebagai np
impor obor.fungsional sebagai 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 He man, terima kasih untuk kodenya. Akan mengingat filosofi pytorch itu!

Ini tahun 2020. Masih tidak ada padding='same' di Pytorch?

Ini adalah salah satu cara untuk membuat padding yang sama berfungsi untuk semua ukuran kernel, langkah dan pelebaran (bahkan ukuran kernel juga berfungsi).

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)

Saya ingin fitur "padding yang sama" di nn.Conv2d juga.

BTW, selain masalah kinerja/seri yang dibahas di atas, ada alasan kebenaran/akurasi mengapa mode padding "sama" yang bergantung pada ukuran di TF bukanlah default yang baik. Saya telah membahas di https://github.com/tensorflow/tensorflow/issues/18213 dan menunjukkan bahwa sebenarnya banyak kode Google sendiri menggunakan mode padding "sama" yang independen ukuran.

Tampaknya tidak ada pekerjaan yang sedang berlangsung saat ini tentang masalah ini tetapi jika ada, saya harap ini adalah solusi ukuran-independen.

Hai, @ppwwyyxx Yuxin, terima kasih atas tanggapannya.
Saya pikir implementasi dari @McHughes288 bagus, dan saya ingin tahu pendapat Anda tentang implementasinya.

Inilah solusi saya untuk bantalan SAMA Conv1D (hanya berfungsi dengan benar ketika dilation==1 & groups==1 , lebih rumit ketika Anda mempertimbangkan pelebaran dan grup):

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 apakah Anda berniat untuk terus mengerjakan fitur ini? Saya akan membatalkan penugasan Anda untuk saat ini sehingga kami dapat melacak kemajuan masalah ini dengan lebih baik, jangan ragu untuk menetapkan ulang jika Anda masih mengerjakannya.

setelah membaca kode @wizcheu , saya membuat versi lain dari conv1d dengan padding='sama'

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

Apakah ada pembaruan tentang ini?

ada pembaruan??

@peterbell10 telah menautkan draf PR yang dapat Anda ikuti.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat