سيكون التنفيذ سهلاً ، لكن يمكن أن يساعد العديد من الأشخاص الذين يعانون من صداع حساب عدد الحشو الذي يحتاجون إليه.
سم مكعبezyanggchanan @ zou3519albanDmruberry
يبدو أن هذا يستحق القيام به. ما هي الواجهة التي تقترحها؟ مثل 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
.
أعلم أنه ليس الحل الأنظف ولكن القيد يبدو معقولًا بدرجة كافية بالنسبة لي نظرًا لما يلي:
stride > 1
وهذا يجعل استخدام كلمة "نفسها" مضللاً قليلاً IMO ؛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 (يمين) المطلوبة:
F.conv2d
padding
تساوي L
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 بعض الأسئلة السريعة:
functional.conv2d
؟ يبدو أن التصميم الذي كتبته يعني أنه لا ينبغي ذلك. لا يوجد شيء بخصوص padding
= "نفسه" يبدو أنه يجب أن يكون خاصًا بالطبقات. (تحرير: Nvm ، لم يدرك أن الضمانة F.conv2d
كنت أبحث عنها كانت الكمية المحددة).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 مسودة علاقات عامة يمكنك متابعتها.
التعليق الأكثر فائدة
هل هناك أي خطة لتنفيذ API مماثل في pytorch في المستقبل القريب؟ الأشخاص القادمون من خلفية tensorflow / keras سيقدرونها بالتأكيد.