Pytorch: [طلب الميزة] تنفيذ "نفس" الحشو لعمليات الالتواء؟

تم إنشاؤها على ٢٥ نوفمبر ٢٠١٧  ·  59تعليقات  ·  مصدر: pytorch/pytorch

سيكون التنفيذ سهلاً ، لكن يمكن أن يساعد العديد من الأشخاص الذين يعانون من صداع حساب عدد الحشو الذي يحتاجون إليه.

سم مكعبezyanggchanan @ zou3519albanDmruberry

enhancement high priority convolution nn triaged

التعليق الأكثر فائدة

هل هناك أي خطة لتنفيذ API مماثل في pytorch في المستقبل القريب؟ الأشخاص القادمون من خلفية tensorflow / keras سيقدرونها بالتأكيد.

ال 59 كومينتر

يبدو أن هذا يستحق القيام به. ما هي الواجهة التي تقترحها؟ مثل nn.Conv2d(..., padding="same") ؟

لاحظ yhat إذا كنت تبحث عن نفس سلوك 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(...)
لست متأكدًا مما إذا كان الاقتراح الثاني يمكن أن يكون تحسينًا سابقًا لأوانه.
ما رأيك في هؤلاء؟ هل هناك أي فكرة عن اسم أفضل؟

أعتقد أن أكبر مصدر لعدم الكفاءة سيأتي من حقيقة أننا سنحتاج إلى إضافة طبقة F.pad قبل كل التفاف آخر يتطلب حالة padding=same (لأن مقدار الحشو قد لا يكون هو نفسه على الجانبين الأيسر والأيمن) ، راجع على سبيل المثال كيف يتعين على 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 ، ما قصدته هو حساب شكل الحشو "الثابت" في __init__() باستخدام nn.CalcPadConv*d() . كما قلت ، لن تعمل هذه الطريقة فقط عندما تكون المساحة المحسوبة غريبة. لذلك ، يلزم إضافة طبقة F.pad ، أو دعم F.conv*d للحشوات الفردية يجب أن يساعد.

تحرير: يجب أن يكون ما اقترحته وظيفة ووضعها ، على سبيل المثال ، torch.nn.utils أو torch.utils.

في النتيجة ، ما أقترحه هو وظيفة أداة مساعدة بسيطة ، مثل (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)

أيضًا ، يمكن استخدام الوظيفة مع F.pad لصالح المستخدم.

@ qbx2 ، ربما لا أفهم

إليك مقتطفًا لما أعتقد أنه يحاكي TensorFlow SAME padding (أنا أكتبه في الواجهة الوظيفية ، بحيث يمكن لـ 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' . وأعتقد أن عدم تكرار سلوك SAME في TensorFlow ليس مثاليًا أيضًا.

أفكار؟

@ fmassa نعم ، أنت على حق. قد يكون حساب المساحة المتروكة على كل forward() غير فعال.

ومع ذلك ، اقتراحي هو عدم حساب المساحة المتروكة لكل مكالمة forward() . قد يتوقع الباحث (المطور) أحجام الصور إلى nn.Conv2d قبل وقت التشغيل. وإذا أراد / تريد نفس المساحة المتروكة ، فيمكنه / يمكنها استخدام الوظيفة لحساب المساحة المتروكة المطلوبة لتقليد "SAME".

على سبيل المثال ، فكر في حالة أن الباحث لديه صور بحجم 200 × 200 ، 300 × 300 ، 400 × 400. ثم يمكنه حساب الحشوات للحالات الثلاث في مرحلة التهيئة وتمرير الصور إلى F.pad() مع المساحة المتروكة المقابلة. أو قام بتغيير حقل المساحة المتروكة لـ nn.Conv2d قبل المكالمة forward() أيضًا. الرجوع إلى هذا:

>>> 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 core.

عندما يريد الباحث حشوة تعتمد على حجم كل صورة إدخال ، يمكنه / يمكنها دمج الوظيفة مع F.pad() قبل تمرير الصورة إلى nn.Conv2d . أريد أن أترك كاتب الكود يقرر ما إذا كان سيحدد المدخلات في كل forward() مكالمة أم لا.

هل هناك أي خطة لتنفيذ API مماثل في pytorch في المستقبل القريب؟ الأشخاص القادمون من خلفية 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)

هل هذا ما يدور في ذهنك

إنه مشابه لما كان يدور في ذهني ، ولكن كما ذكرت سابقًا ، فإن الحساب يصبح معقدًا مع الخطوة والتمدد.

سيكون وجود مثل هذا api في عمليات التفاف أخرى مثل ConvTranspose2d أمرًا رائعًا.

أعتقد أن "مشغلي النوافذ المنزلقة" يجب أن يدعموا الحشو غير المتماثل.

حول حجة "نفس" ...
soumith هل يمكن أن تشرحوا لماذا جعل الحشو اعتمادًا على حجم الإدخال
إذا كانت هذه مشكلة ، على أي حال ، فقد يكون الحل العملي هو طلب stride == 1 عند استخدام "نفسه". بالنسبة إلى stride == 1 ، لا تعتمد المساحة المتروكة على حجم الإدخال ويمكن حسابها مرة واحدة. يجب على المُنشئ رفع ValueError إذا حاول المستخدم استخدام padding='same' مع stride > 1 .

أعلم أنه ليس الحل الأنظف ولكن القيد يبدو معقولًا بدرجة كافية بالنسبة لي نظرًا لما يلي:

  1. تم تقديم الدلالة الأصلية للتسمية "نفس" للتلافيفات غير المتدرجة وكان: الناتج له نفس حجم الإدخال ؛ بالطبع ، هذا ليس صحيحًا في Tensorflow مقابل stride > 1 وهذا يجعل استخدام كلمة "نفسها" مضللاً قليلاً IMO ؛
  2. سيغطي 99٪ من الحالات التي يريد المرء استخدام "نفس" ؛ بالكاد أستطيع أن أتخيل حالة يحتاج فيها شخص ما فعلاً إلى سلوك Tensorflow مقابل stride > 1 ، بينما إذا أعطينا نفس الدلالة الأصلية ، حسنًا ، بالطبع ، ليس من المنطقي استخدام التفاف خطي إذا كنت تريد أن يكون للإخراج نفس حجم الإدخال.

تعطي وثائق conv2d الصيغ الصريحة لأحجام المخرجات. معادلة Hout بـ Hin ، يمكن للمرء حل مشكلة الحشو:

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

نظرًا لأن الحشوة نفسها تعني الحشو = (kernel_size - stride) // 2 ، فماذا لو تم تقديم padding = "same" بحيث يقرأ حجم kernel وخطواته تلقائيًا عند كتابته (كما هو مذكور أيضًا في nn.Conv2d) ويطبق الحشو تلقائيًا وفقًا لذلك

هذه طبقة Conv2d بسيطة للغاية مع مساحة متروكة same كمرجع. إنها تدعم فقط الحبيبات المربعة والخطوة = 1 ، التمدد = 1 ، المجموعات = 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 / مقدر. كلها تدعم المساحة المتروكة same . أقوم حاليًا بإعادة تطبيق convnet الذي كتبته أصلاً في TF مع PyTorch ، وحقيقة أنني اضطررت إلى البناء في الحساب من أجل الحشو الصفري بنفسي قد كلفني حوالي نصف يوم من الوقت.

إذا كان "الهدف المركزي" يركز حقًا على قابلية الاستخدام ، فأنا أزعم أنه حتى لو كان هناك تأثير فعال على الحوسبة الصفرية في كل تمريرة للأمام (كما هو مذكور أعلاه) ، فإن الوقت الذي يتم توفيره من حيث كفاءة المطور وقابلية الصيانة ( على سبيل المثال ، عدم الاضطرار إلى كتابة رمز مخصص لحساب المساحة الصفرية) قد يكون أمرًا يستحق المقايضة. أفكار؟

سأستخدم هذه الميزة

ليس من المنطقي بالنسبة لي لماذا لا يمكن تقديم واجهة برمجة تطبيقات اختيارية padding=SAME ؟ إذا كان شخص ما على استعداد لتحمل التكلفة الإضافية للحشو ، فدعهم يفعلون ذلك. بالنسبة للعديد من الباحثين ، تعد النماذج الأولية السريعة مطلبًا.

نعم ، إذا كان بإمكان شخص ما إضافة هذا والموافقة عليه ، فسيكون ذلك رائعًا.

أضف هذا بالتأكيد ، يريده كونر.

هل يدعمها pytorch الآن؟ هل يمكن أن تستخدم نفس العملية مثل الأولى في VGG ، ضبط الحشوة = (kernel_size-1) / 2؟
يمكن لشبكة VGG أن تجعل حجم الإخراج لا يتغير في المجموعة الأولى. ثم يمكنك استخدام خطوة لتغيير حجم خريطة الميزات ، هل تبدو جيدة؟

فيما يلي مثال واحد لاستدعاء padding نفسه 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-tuple. هذا من شأنه أن يسمح بالحشو غير المتماثل وكذلك المتماثل والذي يعد أيضًا طريقًا جيدًا منخفض التكلفة للوصول إلى منتصف الطريق.

soumith سيكون من الجيد أن يكون لديك وضع حشو مماثل في pytorch.

soumith ماذا عن استخدام واجهة نوع الترجمة؟

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

لقد صنعت Conv2D بنفس الحشوة التي تدعم التمدد والخطوات ، بناءً على كيفية قيام TensorFlow بعملهم. يقوم هذا الشخص بحسابه في الوقت الفعلي ، على الرغم من ذلك ، إذا كنت تريد حسابه مسبقًا ، فما عليك سوى تحريك المساحة المتروكة إلى 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:
كما كان من قبل ، ولكن بخطوة = 2

Conv2dSame(3, 64, 9, 2)

شكل مبطن = (1، 3، 103، 103)
شكل الإخراج = (1 ، 64 ، 48 ، 48)

jpatts أعتقد أن حساب شكل الإخراج الخاص بك خاطئ ، يجب أن يكون بالسقف (input_dimension / stride). التقسيم الصحيح في بايثون هو تقسيم الأرضية - يجب أن يكون للكود الخاص بك نتيجة مختلفة عن Tensorflow على سبيل المثال 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. بين الحين والآخر ، ما زلت أستخدم الحسابات "اليدوية" لحجم الحشو لبناء طبقات ذات نفس البطانة.

تضمين التغريدة

هذه طبقة Conv2d بسيطة للغاية مع مساحة متروكة same كمرجع. إنها تدعم فقط الحبيبات المربعة والخطوة = 1 ، التمدد = 1 ، المجموعات = 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 هنا تذهب:

نطاق

يجب أن نضيف الحشو إلى الطبقات التالية:

  • تحويل * د
  • ماكسبول د
  • متوسط ​​* د

بالنسبة للعلاقات العامة الأولى ، دعنا نجعلها بسيطة ونلتزم فقط بـ 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 ليكون int أو tuple من ints (أي لكل بُعد من أبعاد الارتفاع / العرض).
يجب أن ندعم حملًا زائدًا إضافيًا لـ padding والذي قد يستغرق سلسلة ، بقيمة same .

يجب أن تغطي المساحة المتروكة same input بهذه الطريقة قبل إعطائها للالتفاف بأن الحجم output هو نفس الحجم input .

تفاصيل التنفيذ

عندما يتم منح 'same' لـ padding ، علينا حساب مقدار المساحة المتروكة اليمنى واليسرى المطلوبة في كل بُعد.

هناك حالتان يجب مراعاتهما بعد حساب حشوة L (يسار) و R (يمين) المطلوبة:

  • L == R: في هذه الحالة هو حشو متماثل. يمكن للمرء ببساطة استدعاء F.conv2d padding تساوي L
  • L! = R: في هذه الحالة ، الحشو غير متماثل ، وله تأثيرات كبيرة على الأداء والذاكرة. نقوم بما يلي:

    • ندعو input_padded = F.pad(input, ...) ونرسل input_padded إلى F.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 = "نفسه" يبدو أنه يجب أن يكون خاصًا بالطبقات. (تحرير: Nvm ، لم يدرك أن الضمانة F.conv2d كنت أبحث عنها كانت الكمية المحددة).
  2. أعتقد أن وضع الحشو valid لـ Tensorflow يعادل ببساطة وضعنا مع 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) ، بلا سبب ، لم أفكر في الآثار المترتبة - الكمال أو غير ذلك.

(2) نعم.

علاوة على ذلك ، لن نتمكن من تحديد الحشو بشكل ثابت إلا في الحالة التي تكون فيها الخطوة = 1 ، كما هو الحال بعد ذلك السقف (x / الخطوة) = x ، ولدينا حشوة تساوي (filter-1) * تمدد

نعم ، لكن الخطوة = 1 شائعة بما فيه الكفاية ومزايا الحشو الثابت جيدة بما يكفي بحيث يجب علينا بالتأكيد التعامل معها بشكل خاص.

حول الحشو غير المتماثل ، حسنًا .....

ليس من المنطقي بالنسبة لي لماذا لا يمكن تقديم واجهة برمجة تطبيقات اختيارية padding=SAME ؟ إذا كان شخص ما على استعداد لتحمل التكلفة الإضافية للحشو ، فدعهم يفعلون ذلك. بالنسبة للعديد من الباحثين ، تعد النماذج الأولية السريعة مطلبًا.

نعم،

ليس من المنطقي بالنسبة لي لماذا لا يمكن تقديم واجهة برمجة تطبيقات اختيارية padding=SAME ؟ إذا كان شخص ما على استعداد لتحمل التكلفة الإضافية للحشو ، فدعهم يفعلون ذلك. بالنسبة للعديد من الباحثين ، تعد النماذج الأولية السريعة مطلبًا.

يوافق على! لقد علقت في هذا "الحشو" اللعين لمدة 4 ساعات.

هل لدينا أي تحديث حول حل هذه المشكلة؟

واو وهنا اعتقدت أن Pytorch سيكون أسهل من Keras / Tensorflow 2.0 ...

zwep هناك مجهود أكبر قليلاً للبدء. عليك أن تكتب حلقة التدريب الخاصة بك والتي يمكن أن تكون مزعجة وعليك أن تكتب الطبقات بشكل أكثر وضوحًا. بمجرد الانتهاء من ذلك (مرة واحدة) ، يمكنك التقدم أكثر في التحسين الفعلي بعد ذلك.

قاعدتي الأساسية هي استخدام Keras إذا كان شيئًا قمت به مليون مرة / معيار فائق.
استخدم pytorch في أي وقت يكون هناك بحث وتطوير.

هذا هو الكود الخاص بي للتحويلات 1d المبطن

استيراد الشعلة
من استيراد الشعلة nn
استيراد numpy كـ np
استيراد الشعلة وظيفية مثل 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. لا يوجد حتى الآن padding='same' في Pytorch؟

هذه طريقة واحدة للحصول على نفس الحشو الذي يعمل مع أي حجم للنواة وخطوة وتمدد (حتى أحجام النواة تعمل أيضًا).

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 وأظهرت أن العديد من التعليمات البرمجية الخاصة بـ Google تستخدم في الواقع وضع الحشو "نفسه" المستقل عن الحجم بدلاً من ذلك.

يبدو أنه لا يوجد عمل مستمر في الوقت الحالي حول هذه المشكلة ، ولكن إذا كان هناك ، آمل أن يكون حلاً مستقلاً عن الحجم.

مرحبًا ، ppwwyyxx Yuxin ، شكرًا لك على الرد.
أعتقد أن التنفيذ من @ McHughes288 جيد ، وأتساءل عن رأيك في تنفيذه.

هذا هو الحل الخاص بي لحشو Conv1D SAME (يعمل بشكل صحيح فقط عندما يكون dilation==1 & groups==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 ، قمت بإنشاء نسخة أخرى من conv1d مع padding = 'same'

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 مسودة علاقات عامة يمكنك متابعتها.

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات