๊ตฌํ์ ์ฝ์ง๋ง ํ์ํ ํจ๋ฉ ์๋ฅผ ๊ณ์ฐํ๋ ๋ฐ ์ด๋ ค์์ ๊ฒช๋ ๋ง์ ์ฌ๋๋ค์ ๋์ธ ์ ์์ต๋๋ค.
cc @ezyang @gchanan @zou3519 @albanD @mruberry
์ด๊ฒ์ ํ ๊ฐ์น๊ฐ ์๋ ๊ฒ ๊ฐ์ต๋๋ค. ๋น์ ์ด ์ ์ํ๋ ์ธํฐํ์ด์ค๋ ๋ฌด์์
๋๊น? nn.Conv2d(..., padding="same")
์ฒ๋ผ?
TensorFlow์ ๋์ผํ ๋์์ ์ฐพ๊ณ ์๋ ๊ฒฝ์ฐ ์ถ๊ฐํ ํฝ์ ์๊ฐ ์ ๋ ฅ ํฌ๊ธฐ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง๊ธฐ ๋๋ฌธ์ ๊ตฌํ์ด ๊ฐ๋จํ์ง ์์ต๋๋ค. ์ฐธ์กฐ๋ https://github.com/caffe2/caffe2/blob/master/caffe2/proto/caffe2_legacy.proto ๋ฅผ ์ฐธ์กฐํ์ญ์์ค.
๋ฌธ์ ์ ์ฐธ์กฐ๋ฅผ ์ง์ ํด ์ฃผ์
์ ๊ฐ์ฌํฉ๋๋ค.
@fmassa ๊ฐ ์ธ๊ธํ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ ๊ฐ์ง ์ธํฐํ์ด์ค๋ฅผ ์ ์ํฉ๋๋ค.
๋จผ์ @soutmith๊ฐ ์ธ๊ธํ๋ฏ์ด ์ฒซ ๋ฒ์งธ ์ธํฐํ์ด์ค๋ nn.Conv*d(..., padding="same")
์ ๊ฐ์ผ๋ฉฐ forward()
ํธ์ถ๋ง๋ค ํจ๋ฉ์ ๊ณ์ฐํฉ๋๋ค.
๊ทธ๋ฌ๋ ์ด๊ธฐํ ๋จ๊ณ์์ ์
๋ ฅ ํํ๋ฅผ ์๋ฉด ๋นํจ์จ์ ์ธ ๋ฐฉ๋ฒ์ด ๋ฉ๋๋ค. ๋ฐ๋ผ์ nn.CalcPadConv*d(<almost same parameters as Conv*d>)
์ ๊ฐ์ ์ธํฐํ์ด์ค๋ฅผ ์ ์ํฉ๋๋ค. ์ด๋ฅผ ์ฌ์ฉํ์ฌ ์ฌ์ฉ์๋ ์ด๊ธฐํ ์ ์๋ ค์ง ๋๋น์ ๋์ด๋ฅผ ์ฌ์ฉํ์ฌ ํจ๋ฉ์ ๊ณ์ฐํ๊ณ nn.Conv2d(...)
์ ํจ๋ฉ ๋งค๊ฐ๋ณ์์ ์ถ๋ ฅ(ํจ๋ฉ ๋ชจ์)์ ์ ๋ฌํ ์ ์์ต๋๋ค.
๋ ๋ฒ์งธ ์ ์์ด ์กฐ๊ธฐ ์ต์ ํ๊ฐ ๋ ์ ์๋์ง ์ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค.
์ด๊ฒ๋ค์ ๋ํด ์ด๋ป๊ฒ ์๊ฐํ์ธ์? ๋ ๋์ ์ด๋ฆ์ ๋ํ ์์ด๋์ด๊ฐ ์์ต๋๊น?
๋นํจ์จ์ ๊ฐ์ฅ ํฐ ์์ธ์ padding=same
์ผ์ด์ค๋ฅผ ํ์๋ก ํ๋ ๋ค๋ฅธ ๋ชจ๋ ์ปจ๋ณผ๋ฃจ์
์ ์ F.pad
๋ ์ด์ด๋ฅผ ์ถ๊ฐํด์ผ ํ๋ค๋ ์ฌ์ค์์ ๋น๋กฏ๋ ๊ฒ์ด๋ผ๊ณ ์๊ฐํฉ๋๋ค TensorFlow๊ฐ cudnn
์ผ์ด์ค์์ ์ด๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์ฐธ์กฐํ์ญ์์ค. ๋ฐ๋ผ์ nn.CalcPadConv*d
๋ ์ผ๋ฐ์ ์ผ๋ก nn.Conv*d(..., padding="same")
๋งํผ ๋น์๋๋ค.
์ด๊ฒ์ ์ปจ๋ณผ๋ฃจ์ ์ ๊ฐ ๋ฉด์ ๋ํด ๋ค๋ฅธ ํจ๋ฉ์ ์ง์ํ๋ฉด ๋ ํจ์จ์ ์ผ ์ ์์ง๋ง(Caffe2์์์ ๊ฐ์ด ์ผ์ชฝ, ์ค๋ฅธ์ชฝ, ์์ชฝ, ์๋์ชฝ) cudnn์ ์ฌ์ ํ โโ์ด๋ฅผ ์ง์ํ์ง ์์ผ๋ฏ๋ก ์ด๋ฌํ ๊ฒฝ์ฐ์ ์ถ๊ฐ ํจ๋ฉ์ด ํ์ํฉ๋๋ค. .
์ฐ๋ฆฌ๋ ์ถ๊ฐํ๋ ๊ฒฝ์ฐ ๋ํ, ๋๋ ์๊ฐ padding="same"
์ nn.Conv*d
, ์ฐ๋ฆฌ๋ ์๋ง์ ๋ํด ๋์ผํด์ผ nn.*Pool*d
์ค๋ฅธ์ชฝ?
์ ์๊ฐ์๋ ์ฌ์ฉ์๊ฐ padding=same
์ ๋์์ด TF์ ๋์ผํ ๊ฒ์ผ๋ก ์์ํ ์ ์์ง๋ง ์ฑ๋ฅ ์ ํ๋ฅผ ๊ธฐ๋ํ์ง ์์ ์๋ ์๋ค๋ ์ ์ด ์ ๋ฅผ ์ฝ๊ฐ ๊ดด๋กญํ๋ ๊ฒ ๊ฐ์ต๋๋ค.
์ด๋ป๊ฒ ์๊ฐํ๋์?
์ ๊ทธ๊ฒ์ด ๋นํจ์จ์ ์
๋๊น? ๋ชจ๋ ์ ์ง ๋จ๊ณ์์ ํจ๋ฉ์ ๊ณ์ฐํ ์ ์์ต๋๊น? ๋น์ฉ์ด ์์์ผ ํ๋ฏ๋ก ์ต์ ํํ ํ์๊ฐ ์์ต๋๋ค. ์๋ฏธ๋ฅผ ์์ ํ ์ดํดํ์ง ๋ชปํ๋ ๊ฒ์ผ ์๋ ์์ง๋ง F.pad
์ด ํ์ํ ์ด์ ๋ฅผ ์ ์ ์์ต๋๋ค.
์ ๋ ฅ ํฌ๊ธฐ์ ๋ฐ๋ผ ํจ๋ฉ์ ๋ง๋๋ ๊ฒ์ ๋งค์ฐ ๋์ฉ๋๋ค. ๋ค์ํ ์ง๋ ฌํ ๋ฐ ํจ์จ์ฑ ์ด์ ๋ก ์ด๊ฒ์ด ์ ๋์ ์๊ฐ์ธ์ง ์ค๋ช ํ๋ @Yangqing ๊ณผ ํจ๊ป
@fmassa , ๋ด๊ฐ ์๋ํ ๊ฒ์ __init__()
์ฌ์ฉํ์ฌ nn.CalcPadConv*d()
__init__()
์์ "์ผ์ ํ" ํจ๋ฉ ๋ชจ์์ ๊ณ์ฐํ๋ ๊ฒ์ด์์ต๋๋ค. ๋งํ๋ฏ์ด ์ด ๋ฐฉ๋ฒ์ ๊ณ์ฐ๋ ํจ๋ฉ์ด ํ์์ผ ๋๋ง ์๋ํ์ง ์์ต๋๋ค. ๋ฐ๋ผ์ F.pad
๋ ์ด์ด๋ฅผ ์ถ๊ฐํ๊ฑฐ๋ ํ์ ํจ๋ฉ์ ๋ํ F.conv*d
์ง์์ด ๋์์ด ๋ ๊ฒ์
๋๋ค.
ํธ์ง: ๊ทธ๋ฐ ๋ค์ ๋ด๊ฐ ์ ์ํ ๊ฒ์ ๊ธฐ๋ฅ์ด์ด์ผ ํ๋ฉฐ, ์๋ฅผ ๋ค์ด torch.nn.utils ๋๋ torch.utils์ ๋ฐฐ์นํด์ผ ํฉ๋๋ค.
๊ฒฐ๊ณผ์ ์ผ๋ก ๋ด๊ฐ ์ ์ํ๋ ๊ฒ์ (์์ฌ ์ฝ๋)์ ๊ฐ์ ๊ฐ๋จํ ์ ํธ๋ฆฌํฐ ๊ธฐ๋ฅ์ ๋๋ค.
def calc_pad_conv1d(width, padding='same', check_symmetric=True, ... <params that conv1d has>):
shape = <calculate padding>
assert not check_symmetric or <shape is symmetric>, \
'Calculated padding shape is asymmetric, which is not supported by conv1d. ' \
'If you just want to get the value, consider using check_symmetric=False.'
return shape
width = 100 # for example
padding = calc_pad_conv1d(width, ...)
m = nn.Conv1d(..., padding=padding)
๋ํ ์ด ๊ธฐ๋ฅ์ ์ฌ์ฉ์์๊ฒ ์ ๋ฆฌํ๊ฒ F.pad
์ ํจ๊ป ์ฌ์ฉํ ์ ์์ต๋๋ค.
@qbx2 ์๋ง๋ ๊ทํ์ ์ ์์ ์์ ํ ์ดํดํ์ง ๋ชปํ ์๋ ์์ง๋ง TensorFlow ๋์์ ๋ณต์ ํ๋ ค๋ ๊ฒฝ์ฐ ์ด๊ฒ์ผ๋ก ์ถฉ๋ถํ์ง ์๋ค๊ณ ์๊ฐํฉ๋๋ค.
๋ค์์ TensorFlow SAME
ํจ๋ฉ์ ๋ชจ๋ฐฉํ ๊ฒ์ผ๋ก ์๊ฐ๋๋ ์ค๋ํซ์
๋๋ค( nn.Conv2d
๊ฐ F.conv2d_same_padding
ํธ์ถํ ์ ์๋๋ก ๊ธฐ๋ฅ ์ธํฐํ์ด์ค์ ์ ์ด ๋ก๋๋ค).
def conv2d_same_padding(input, weight, bias=None, stride=1, dilation=1, groups=1):
input_rows = input.size(2)
filter_rows = weight.size(2)
effective_filter_size_rows = (filter_rows - 1) * dilation[0] + 1
out_rows = (input_rows + stride[0] - 1) // stride[0]
padding_needed =
max(0, (out_rows - 1) * stride[0] + effective_filter_size_rows -
input_rows)
padding_rows = max(0, (out_rows - 1) * stride[0] +
(filter_rows - 1) * dilation[0] + 1 - input_rows)
rows_odd = (padding_rows % 2 != 0)
# same for padding_cols
if rows_odd or cols_odd:
input = F.pad(input, [0, int(cols_odd), 0, int(rows_odd)])
return F.conv2d(input, weight, bias, stride,
padding=(padding_rows // 2, padding_cols // 2),
dilation=dilation, groups=groups)
์ฌ๊ธฐ ์ ์ฌ๊ธฐ ์ TensorFlow ์ฝ๋์์ ๋๋ถ๋ถ ๋ณต์ฌํ์ฌ ๋ถ์ฌ๋ฃ์
๋ณด์๋ค์ํผ, ๊ฑฐ๊ธฐ์๋ ๋ง์ ์จ๊ฒจ์ง ์ผ๋ค์ด ์ผ์ด๋๊ณ ์๊ณ , ๊ทธ๋์ padding='same'
์ถ๊ฐํ ๊ฐ์น๊ฐ ์์ ์๋ ์๋ค๊ณ ์๊ฐํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ TensorFlow์์ SAME
๋์์ ๋ณต์ ํ์ง ์๋ ๊ฒ๋ ์ด์์ ์ด์ง ์๋ค๊ณ ์๊ฐํฉ๋๋ค.
์๊ฐ?
@fmassa ๋ค, ๋ง์ต๋๋ค. forward()
๋ง๋ค ํจ๋ฉ์ ๊ณ์ฐํ๋ ๊ฒ์ ๋นํจ์จ์ ์ผ ์ ์์ต๋๋ค.
๊ทธ๋ฌ๋ ๋ด ์ ์์ forward()
ํธ์ถ๋ง๋ค ํจ๋ฉ์ ๊ณ์ฐํ์ง ์๋ ๊ฒ์
๋๋ค. ์ฐ๊ตฌ์(๊ฐ๋ฐ์)์ ๋ฐํ์ ์ ์ ์ด๋ฏธ์ง์ ํฌ๊ธฐ๋ฅผ nn.Conv2d
์์ํ ์ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ๊ทธ/๊ทธ๋
๊ฐ '๋์ผํ' ํจ๋ฉ์ ์ํ๋ฉด 'SAME'์ ๋ชจ๋ฐฉํ๊ธฐ ์ํด ํ์ํ ํจ๋ฉ์ ๊ณ์ฐํ๋ ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด ์ฐ๊ตฌ์์ด 200x200, 300x300, 400x400 ํฌ๊ธฐ์ ์ด๋ฏธ์ง๋ฅผ ๊ฐ์ง๊ณ ์๋ ๊ฒฝ์ฐ๋ฅผ ์๊ฐํด ๋ณด์ญ์์ค. ๊ทธ๋ฐ ๋ค์ ์ด๊ธฐํ ๋จ๊ณ์์ ์ธ ๊ฐ์ง ๊ฒฝ์ฐ์ ๋ํ ํจ๋ฉ์ ๊ณ์ฐํ๊ณ ํด๋น ํจ๋ฉ๊ณผ ํจ๊ป ์ด๋ฏธ์ง๋ฅผ F.pad()
์ ๋ฌํ ์ ์์ต๋๋ค. ๋๋ forward()
ํธ์ถ ์ ์ nn.Conv2d
์ ํจ๋ฉ ํ๋๋ฅผ ๋ณ๊ฒฝํฉ๋๋ค. ๋ค์์ ์ฐธ์กฐํ์ญ์์ค.
>>> import torch
>>> import torch.nn as nn
>>> from torch.autograd import Variable
>>> m = nn.Conv2d(1,1,1)
>>> m(Variable(torch.randn(1,1,2,2))).shape
torch.Size([1, 1, 2, 2])
>>> m.padding = (1, 1)
>>> m(Variable(torch.randn(1,1,2,2))).shape
torch.Size([1, 1, 4, 4])
์, pytorch ์ฝ์ด์ "ํจ๋ฉ ๊ณ์ฐ ์ ํธ๋ฆฌํฐ ๊ธฐ๋ฅ"์ ์ถ๊ฐํ๊ณ ์ถ์ต๋๋ค.
์ฐ๊ตฌ์๊ฐ ๊ฐ ์
๋ ฅ ์ด๋ฏธ์ง ํฌ๊ธฐ์ ๋ํ ์ข
์ ํจ๋ฉ์ ์ํ ๋ nn.Conv2d
์ด๋ฏธ์ง๋ฅผ ์ ๋ฌํ๊ธฐ ์ ์ F.pad()
์ ํจ์๋ฅผ ๊ฒฐํฉํ ์ ์์ต๋๋ค. ์ฝ๋ ์์ฑ์๊ฐ ๋ชจ๋ forward()
ํธ์ถ์์ ์
๋ ฅ์ ์ฑ์ธ์ง ์ฌ๋ถ๋ฅผ ๊ฒฐ์ ํ๋๋ก ํ๊ณ ์ถ์ต๋๋ค.
๊ฐ๊น์ด ์ฅ๋์ pytorch์์ ์ ์ฌํ API๋ฅผ ๊ตฌํํ ๊ณํ์ด ์์ต๋๊น? tensorflow / keras ๋ฐฐ๊ฒฝ์์ ์จ ์ฌ๋๋ค์ ํ์คํ ๊ฐ์ฌํ ๊ฒ์ ๋๋ค.
๋ฐ๋ผ์ ๊ธฐ๋ณธ ํจ๋ฉ ๊ณ์ฐ ์ ๋ต(TensorFlow์ ๋์ผํ ๊ฒฐ๊ณผ๋ฅผ ์ ๊ณตํ์ง ์์ง๋ง ๋ชจ์์ ์ ์ฌํจ)์
def _get_padding(padding_type, kernel_size):
assert padding_type in ['SAME', 'VALID']
if padding_type == 'SAME':
return tuple((k - 1) // 2 for k in kernel_size))
return tuple(0 for _ in kernel_size)
@im9uri ๋ฅผ ์ผ๋์ ๋๊ณ
๋ด๊ฐ ์ผ๋์ ๋์๋ ๊ฒ๊ณผ ๋น์ทํ์ง๋ง ์์ ์ธ๊ธํ๋ฏ์ด ๋ณดํญ๊ณผ ํฝ์ฐฝ์ผ๋ก ๊ณ์ฐ์ด ๋ณต์กํด์ง๋๋ค.
๋ํ ConvTranspose2d์ ๊ฐ์ ๋ค๋ฅธ ์ปจ๋ณผ๋ฃจ์ ์์ ์์ ์ด๋ฌํ API๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
"์ฌ๋ผ์ด๋ฉ ์ฐฝ ์ฐ์ฐ์"๋ ๋ชจ๋ ๋น๋์นญ ํจ๋ฉ์ ์ง์ํด์ผ ํ๋ค๊ณ ์๊ฐํฉ๋๋ค.
"๊ฐ์" ์ฃผ์ฅ์ ๋ํด...
@sumith ์
๋ ฅ ํฌ๊ธฐ์ ๋ฐ๋ผ ํจ๋ฉ์ ๋ง๋๋ ๊ฒ์ด ์
์ด์จ๋ ๊ทธ๊ฒ์ด ๋ฌธ์ ๋ผ๋ฉด ์ค์ฉ์ ์ธ ํด๊ฒฐ์ฑ
์ "๋์ผ"์ ์ฌ์ฉํ ๋ stride == 1
๋ฅผ ์๊ตฌํ๋ ๊ฒ์ผ ์ ์์ต๋๋ค. stride == 1
์ ๊ฒฝ์ฐ ํจ๋ฉ์ ์
๋ ฅ ํฌ๊ธฐ์ ์์กดํ์ง ์์ผ๋ฉฐ ํ ๋ฒ์ ๊ณ์ฐํ ์ ์์ต๋๋ค. ์ฌ์ฉ์๊ฐ padding='same'
์ ํจ๊ป stride > 1
padding='same'
๋ฅผ ์ฌ์ฉํ๋ ค๊ณ ํ๋ฉด ์์ฑ์๋ ValueError
๋ฐ์์์ผ์ผ ํฉ๋๋ค.
๋๋ ๊ทธ๊ฒ์ด ๊ฐ์ฅ ๊นจ๋ํ ํด๊ฒฐ์ฑ ์ ์๋์ง๋ง ์ ์ฝ ์กฐ๊ฑด์ด ๋ค์๊ณผ ๊ฐ์ด ๋์๊ฒ ์ถฉ๋ถํ ํฉ๋ฆฌ์ ์ผ๋ก ๋ค๋ฆฐ๋ค๋ ๊ฒ์ ์๋๋ค.
stride > 1
๋ํ tensorflow์์ ์ฌ์ค์ด ์๋๋ฉฐ "๋์ผํ"์ด๋ผ๋ ๋จ์ด๋ฅผ ์ฌ์ฉํ์ฌ IMO๋ฅผ ์ฝ๊ฐ ์คํดํ๊ฒ ๋ง๋ญ๋๋ค.stride > 1
๋ํ tensorflow์ ๋์์ ์ ๋ง๋ก ํ์๋ก ํ๋ ๊ฒฝ์ฐ๋ฅผ ๊ฑฐ์ ์์ํ ์ ์์ง๋ง, ์๋ ์๋ฏธ๋ฅผ "๋์ผํ๊ฒ" ์ ๊ณตํ๋ฉด ์, ๋ฌผ๋ก strided convolution์ ์ฌ์ฉํ๋ ๊ฒ์ ์๋ฏธ๊ฐ ์์ต๋๋ค. ์ถ๋ ฅ์ ์ํ๋ ๊ฒฝ์ฐ ์
๋ ฅ์ ํฌ๊ธฐ๊ฐ ๋์ผํฉ๋๋ค.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๋ฅผ ์๋ฏธํ๊ธฐ ๋๋ฌธ์ ํจ๋ฉ = "๋์ผ"์ด ๋์ ๋์ด ์์ฑ๋ ๋ ์ปค๋ ํฌ๊ธฐ์ ๋ณดํญ(nn.Conv2d์์๋ ์ธ๊ธ๋จ)์ ์๋์ผ๋ก ์ฝ๊ณ ํจ๋ฉ์ ์ ์ฉํฉ๋๋ค. ๊ทธ์ ๋ฐ๋ผ ์๋์ผ๋ก
๋ค์์ ์ฐธ์กฐ์ฉ์ผ๋ก same
ํจ๋ฉ์ด ์๋ ๋งค์ฐ ๊ฐ๋จํ Conv2d ๋ ์ด์ด์
๋๋ค. ์ ์ฌ๊ฐํ ์ปค๋๊ณผ stride=1, dilation=1, groups=1๋ง ์ง์ํฉ๋๋ค.
class Conv2dSame(torch.nn.Module):
def __init__(self, in_channels, out_channels, kernel_size, bias=True, padding_layer=torch.nn.ReflectionPad2d):
super().__init__()
ka = kernel_size // 2
kb = ka - 1 if kernel_size % 2 == 0 else ka
self.net = torch.nn.Sequential(
padding_layer((ka,kb,ka,kb)),
torch.nn.Conv2d(in_channels, out_channels, kernel_size, bias=bias)
)
def forward(self, x):
return self.net(x)
c = Conv2dSame(1,3,5)
print(c(torch.rand((16,1,10,10))).shape)
# torch.Size([16, 3, 10, 10])
์ด๊ฒ์ด ์ฌ์ ํ PyTorch์ ์ถ๊ฐ๋๋ ๊ฒ์ผ๋ก ํ๊ฐ๋๊ณ ์๋ค๋ฉด ๊ฐ๋ฐ์๋ฅผ ์ํ ๋ณต์ก์ฑ/๋นํจ์จ์ฑ ๋ ์ฌ์ฉ ์ฉ์ด์ฑ ๊ฐ์ ์ ์ถฉ์ ์ ๋ํด:
1.0 ๋ธ๋ก๊ทธ ๊ฒ์๋ฌผ๋ก ๊ฐ๋ ๊ธธ์ ๋ค์ ๊ณผ ๊ฐ์ด ๋ช ์๋์ด ์์ต๋๋ค.
PyTorch์ ์ค์ฌ ๋ชฉํ๋ ์ฐ๊ตฌ ๋ฐ ํดํน ๊ฐ๋ฅ์ฑ์ ์ํ ํ๋ฅญํ ํ๋ซํผ์ ์ ๊ณตํ๋ ๊ฒ์ ๋๋ค. ๋ฐ๋ผ์ ์ด๋ฌํ ๋ชจ๋ [ํ๋ก๋์ ์ฌ์ฉ] ์ต์ ํ๋ฅผ ์ถ๊ฐํ๋ ๋์ ์ฐ๋ฆฌ๋ ์ฌ์ฉ์ฑ๊ณผ ์ด๋ฅผ ์ ์ถฉํ์ง ์๋๋ก ์๊ฒฉํ ์ค๊ณ ์ ์ฝ ์กฐ๊ฑด์ผ๋ก ์์ ํด ์์ต๋๋ค.
์ผํ์ ์ผ๋ก ์ ๋ Keras์ ์๋ tf.layers
/ estimator API๋ฅผ ์ฌ์ฉํ ๊ฒฝํ์ด ์์ต๋๋ค. ๋ชจ๋ same
ํจ๋ฉ์ ์ง์ํฉ๋๋ค. ์ ๋ ํ์ฌ PyTorch๋ฅผ ์ฌ์ฉํ์ฌ TF์์ ์๋ ์์ฑํ๋ convnet์ ๋ค์ ๊ตฌํํ๊ณ ์์ผ๋ฉฐ, ์ ๋ก ํจ๋ฉ์ ์ํด ์ฐ์ ์ ์ง์ ๊ตฌ์ถํด์ผ ํ๊ธฐ ๋๋ฌธ์ ์ฝ ๋ฐ๋์ ์ ์๊ฐ์ด ์์๋์์ต๋๋ค.
"์ค์ฌ ๋ชฉํ"๊ฐ ์ค์ ๋ก ์ฌ์ฉ์ฑ์ ์ค์ ์ ๋๋ค๋ฉด ๋ชจ๋ ์ ์ง ํจ์ค(์์์ ์ธ๊ธํ ๋ฐ์ ๊ฐ์ด)์์ ์ ๋ก ํจ๋ฉ์ ๊ณ์ฐํ๋ ๋ฐ ํจ์จ์ฑ์ด ๋จ์ด์ง๋๋ผ๋ ๊ฐ๋ฐ์ ํจ์จ์ฑ ๋ฐ ์ ์ง ๊ด๋ฆฌ ์ธก๋ฉด์์ ์๊ฐ์ด ์ ์ฝ๋๋ค๊ณ ์ฃผ์ฅํ๋ ๊ฒ๋ณด๋ค ์๋ฅผ ๋ค์ด ์ ๋ก ํจ๋ฉ์ ๊ณ์ฐํ๊ธฐ ์ํด ์ฌ์ฉ์ ์ ์ ์ฝ๋๋ฅผ ์์ฑํ ํ์๊ฐ ์์์ ์ ์ถฉ์ ๊ฐ์น๊ฐ ์์ ์ ์์ต๋๋ค. ์๊ฐ?
์ด ๊ธฐ๋ฅ์ ์ฌ์ฉํ๊ฒ ์ต๋๋ค
padding=SAME
์ ์ ํ์ API๋ฅผ ์ ๊ณตํ ์ ์๋ ์ด์ ๋ ๋ฌด์์
๋๊น? ๋๊ตฐ๊ฐ๊ฐ ํจ๋ฉ์ ๋ํ ์ถ๊ฐ ๋น์ฉ์ ๊ธฐ๊บผ์ด ์ง๋ถํ ์์ฌ๊ฐ ์๋ค๋ฉด ๊ทธ๋ ๊ฒ ํ๋๋ก ํ์ญ์์ค. ๋ง์ ์ฐ๊ตฌ์์๊ฒ ๋น ๋ฅธ ํ๋กํ ํ์ดํ์ ์๊ตฌ ์ฌํญ์
๋๋ค.
์, ๋๊ตฐ๊ฐ ์ด๊ฒ์ ์ถ๊ฐํ๊ณ ์น์ธํ ์ ์๋ค๋ฉด ์ข์ ๊ฒ์ ๋๋ค.
ํ์คํ ์ด๊ฒ์ ์ถ๊ฐํ์ญ์์ค. ์ฝ๋๋ ๊ทธ๊ฒ์ ์ํฉ๋๋ค.
์ด์ pytorch๊ฐ ์ง์ํฉ๋๊น? VGG์์ ์ฒ์๊ณผ ๊ฐ์ ์์
์ ์ฌ์ฉํ์ฌ ํจ๋ฉ = (kernel_size-1)/2๋ฅผ ์ค์ ํ ์ ์์ต๋๊น?
VGG ๋คํธ์ํฌ๋ ์ฒซ ๋ฒ์งธ ๊ทธ๋ฃน์์ ์ถ๋ ฅ ํฌ๊ธฐ๊ฐ ๋ณ๊ฒฝ๋์ง ์๋๋ก ํ ์ ์์ต๋๋ค. ๊ทธ๋ฐ ๋ค์ stride๋ฅผ ์ฌ์ฉํ์ฌ featuremap์ ํฌ๊ธฐ๋ฅผ ์กฐ์ ํ ์ ์์ต๋๋ค. ๊ด์ฐฎ์ต๋๊น?
๋ค์์ deepfakes์์ ๋์ผํ conv2d ํจ๋ฉ์ ํธ์ถํ๋ ํ ๊ฐ์ง ์์ ๋๋ค.
# modify con2d function to use same padding
# code referd to <strong i="6">@famssa</strong> in 'https://github.com/pytorch/pytorch/issues/3867'
# and tensorflow source code
import torch.utils.data
from torch.nn import functional as F
import math
import torch
from torch.nn.parameter import Parameter
from torch.nn.functional import pad
from torch.nn.modules import Module
from torch.nn.modules.utils import _single, _pair, _triple
class _ConvNd(Module):
def __init__(self, in_channels, out_channels, kernel_size, stride,
padding, dilation, transposed, output_padding, groups, bias):
super(_ConvNd, self).__init__()
if in_channels % groups != 0:
raise ValueError('in_channels must be divisible by groups')
if out_channels % groups != 0:
raise ValueError('out_channels must be divisible by groups')
self.in_channels = in_channels
self.out_channels = out_channels
self.kernel_size = kernel_size
self.stride = stride
self.padding = padding
self.dilation = dilation
self.transposed = transposed
self.output_padding = output_padding
self.groups = groups
if transposed:
self.weight = Parameter(torch.Tensor(
in_channels, out_channels // groups, *kernel_size))
else:
self.weight = Parameter(torch.Tensor(
out_channels, in_channels // groups, *kernel_size))
if bias:
self.bias = Parameter(torch.Tensor(out_channels))
else:
self.register_parameter('bias', None)
self.reset_parameters()
def reset_parameters(self):
n = self.in_channels
for k in self.kernel_size:
n *= k
stdv = 1. / math.sqrt(n)
self.weight.data.uniform_(-stdv, stdv)
if self.bias is not None:
self.bias.data.uniform_(-stdv, stdv)
def __repr__(self):
s = ('{name}({in_channels}, {out_channels}, kernel_size={kernel_size}'
', stride={stride}')
if self.padding != (0,) * len(self.padding):
s += ', padding={padding}'
if self.dilation != (1,) * len(self.dilation):
s += ', dilation={dilation}'
if self.output_padding != (0,) * len(self.output_padding):
s += ', output_padding={output_padding}'
if self.groups != 1:
s += ', groups={groups}'
if self.bias is None:
s += ', bias=False'
s += ')'
return s.format(name=self.__class__.__name__, **self.__dict__)
class Conv2d(_ConvNd):
def __init__(self, in_channels, out_channels, kernel_size, stride=1,
padding=0, dilation=1, groups=1, bias=True):
kernel_size = _pair(kernel_size)
stride = _pair(stride)
padding = _pair(padding)
dilation = _pair(dilation)
super(Conv2d, self).__init__(
in_channels, out_channels, kernel_size, stride, padding, dilation,
False, _pair(0), groups, bias)
def forward(self, input):
return conv2d_same_padding(input, self.weight, self.bias, self.stride,
self.padding, self.dilation, self.groups)
# custom con2d, because pytorch don't have "padding='same'" option.
def conv2d_same_padding(input, weight, bias=None, stride=1, padding=1, dilation=1, groups=1):
input_rows = input.size(2)
filter_rows = weight.size(2)
effective_filter_size_rows = (filter_rows - 1) * dilation[0] + 1
out_rows = (input_rows + stride[0] - 1) // stride[0]
padding_needed = max(0, (out_rows - 1) * stride[0] + effective_filter_size_rows -
input_rows)
padding_rows = max(0, (out_rows - 1) * stride[0] +
(filter_rows - 1) * dilation[0] + 1 - input_rows)
rows_odd = (padding_rows % 2 != 0)
padding_cols = max(0, (out_rows - 1) * stride[0] +
(filter_rows - 1) * dilation[0] + 1 - input_rows)
cols_odd = (padding_rows % 2 != 0)
if rows_odd or cols_odd:
input = pad(input, [0, int(cols_odd), 0, int(rows_odd)])
return F.conv2d(input, weight, bias, stride,
padding=(padding_rows // 2, padding_cols // 2),
dilation=dilation, groups=groups)
์ด ์ ์ ๋ํด ๋งค์ฐ ๊ฐ์ฌํ๊ฒ ์๊ฐํฉ๋๋ค. ํ์ฌ tensorflow์์ ๊ฐ๋จํ ๋ชจ๋ธ์ ์ด์ํ๊ณ ์์ผ๋ฉฐ ๊ณ์ฐ์ ์ดํดํ๋ ๋ฐ ๋งค์ฐ ์ค๋ ์๊ฐ์ด ๊ฑธ๋ฆฝ๋๋ค...
์ด ์ค๋ ๋๊ฐ ๋ฐฉ๊ธ ์ฃฝ์ ๊ฒ ๊ฐ์ต๋๋ค. ์ฌ๊ธฐ์์ ์์ง ์๊ฐ๋ฝ์ ์๋ฅผ ๊ฐ์ํ ๋ ๋ ๋น ๋ฅธ ํ๋กํ ํ์ดํ์ ์ํด ์ด ๊ธฐ๋ฅ์ ์ถ๊ฐํ๋ฉด ์ ๋ง ์ข์ ๊ฒ์ ๋๋ค.
์ด์ ๋ํ ์ ์์๋ฅผ ์์ฑํ๊ณ ์ด๋ฅผ ๊ตฌํํ ์ฌ๋์ ์ฐพ์ ์ ์์ต๋๋ค.
๋๋ ์ด๊ฒ์ v1.1 ์ด์ ํ์ ๋ํด ๋๊ณ ์์ต๋๋ค.
๊ฐ์ฌํฉ๋๋ค, ๋น์ ์ ๊ต์ฅํฉ๋๋ค! ๋ํ ํจ๋ฉ ์ธ์๊ฐ 4-ํํ์ ํ์ฉํ๋๋ก ๋ณ๋์ ๊ธฐ๋ฅ ์์ฒญ ์ ์ ์ถํ์ต๋๋ค. ์ด๊ฒ์ ๋์นญ ํจ๋ฉ๋ฟ๋ง ์๋๋ผ ๋์นญ ํจ๋ฉ๋ ํ์ฉํ๋ฉฐ ์ด๋ ์ค๊ฐ์ ๋๋ฌํ๊ธฐ ์ํ ์ข์ ์ ๋น์ฉ ๊ฒฝ๋ก์ด๊ธฐ๋ ํฉ๋๋ค.
@soumith pytorch ์ ํจ๋ฉ ๋ชจ๋๊ฐ ๋์ผํ๋ฉด ์ข์ ๊ฒ์ ๋๋ค.
@soumith ์ปดํ์ผ ์ ํ ์ธํฐํ์ด์ค๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ์ด๋ป์ต๋๊น?
model=torch.compile(model,input_shape=(3,224,224))
TensorFlow๊ฐ ์ํํ๋ ๋ฐฉ์์ ๋ฐ๋ผ ํฝ์ฐฝ๊ณผ ๋ณดํญ์ ์ง์ํ๋ ๋์ผํ ํจ๋ฉ์ผ๋ก Conv2D๋ฅผ ๋ง๋ค์์ต๋๋ค. ์ด๊ฒ์ ์ค์๊ฐ์ผ๋ก ๊ณ์ฐํฉ๋๋ค. ๋ฏธ๋ฆฌ ๊ณ์ฐํ๋ ค๋ฉด ํจ๋ฉ์ init()์ผ๋ก ์ด๋ํ๊ณ ์ ๋ ฅ ํฌ๊ธฐ ๋งค๊ฐ๋ณ์๋ฅผ ๊ฐ์ง๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค.
import torch as tr
import math
class Conv2dSame(tr.nn.Module):
def __init__(self, in_channels, out_channels, kernel_size, stride=1, dilation=1):
super(Conv2dSame, self).__init__()
self.F = kernel_size
self.S = stride
self.D = dilation
self.layer = tr.nn.Conv2d(in_channels, out_channels, kernel_size, stride, dilation=dilation)
def forward(self, x_in):
N, C, H, W = x_in.shape
H2 = math.ceil(H / self.S)
W2 = math.ceil(W / self.S)
Pr = (H2 - 1) * self.S + (self.F - 1) * self.D + 1 - H
Pc = (W2 - 1) * self.S + (self.F - 1) * self.D + 1 - W
x_pad = tr.nn.ZeroPad2d((Pr//2, Pr - Pr//2, Pc//2, Pc - Pc//2))(x_in)
x_out = self.layer(x_pad)
return x_out
์ 1:
์
๋ ฅ ๋ชจ์: (1, 3, 96, 96)
ํํฐ: 64
ํฌ๊ธฐ: 9x9
Conv2dSame(3, 64, 9)
ํจ๋ฉ ๋ชจ์: (1, 3, 104, 104)
์ถ๋ ฅ ํํ: (1, 64, 96, 96)
์ 2:
์ด์ ๊ณผ ๋์ผํ์ง๋ง stride=2
Conv2dSame(3, 64, 9, 2)
ํจ๋ฉ ๋ชจ์ = (1, 3, 103, 103)
์ถ๋ ฅ ํํ = (1, 64, 48, 48)
@jpatts ์ถ๋ ฅ ๋ชจ์ ๊ณ์ฐ์ด ์๋ชป๋์๋ค๊ณ ์๊ฐํฉ๋๋ค. ceil(input_dimension / stride)์ด์ด์ผ ํฉ๋๋ค. ํ์ด์ฌ์ ์ ์ ๋๋๊ธฐ๋ ๋ฐ๋ฅ ๋๋๊ธฐ์
๋๋ค. ์ฝ๋๋ h=w=28, stride=3, kernel_size=1
๋ํด tensorflow์ ๋ค๋ฅธ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ์ ธ์ผ ํฉ๋๋ค.
๋ค์์ ๋ฏธ๋ฆฌ ๊ณ์ฐ์ ์ํํ๋ ๋ณํ์ ๋๋ค.
def pad_same(in_dim, ks, stride, dilation=1):
"""
Refernces:
https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/framework/common_shape_fns.h
https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/framework/common_shape_fns.cc#L21
"""
assert stride > 0
assert dilation >= 1
effective_ks = (ks - 1) * dilation + 1
out_dim = (in_dim + stride - 1) // stride
p = max(0, (out_dim - 1) * stride + effective_ks - in_dim)
padding_before = p // 2
padding_after = p - padding_before
return padding_before, padding_after
์ ๋ ฅ ์ฐจ์์ด ์๋ ค์ ธ ์๊ณ ์ฆ์ ๊ณ์ฐ๋์ง ์๋ ๊ฒฝ์ฐ ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉํ ์ ์์ต๋๋ค.
# Pass this to nn.Sequential
def conv2d_samepad(in_dim, in_ch, out_ch, ks, stride, dilation=1, bias=True):
pad_before, pad_after = pad_same(in_dim, ks, stride, dilation)
if pad_before == pad_after:
return [nn.Conv2d(in_ch, out_ch, ks, stride, pad_after, dilation, bias=bias)]
else:
return [nn.ZeroPad2d((pad_before, pad_after, pad_before, pad_after)),
nn.Conv2d(in_ch, out_ch, ks, stride, 0, dilation, bias=bias)]
๊ทธ๋ฌ๋ ์ด ๊ฒฝ์ฐ ์ ๋ ฅ ์ฐจ์์ ๋ํด ์ผ๋ถ ๋ถ๊ธฐ ๊ด๋ฆฌ๋ฅผ ์ํํด์ผ ํ๋ฏ๋ก(์ด๊ฒ์ด ํต์ฌ ๋ฌธ์ ์) ์์ ๋ด์ฉ์ ์ฌ์ฉํ๋ฉด ์ ์ฉํ ์ ์์ต๋๋ค.
def conv_outdim(in_dim, padding, ks, stride, dilation):
if isinstance(padding, int) or isinstance(padding, tuple):
return conv_outdim_general(in_dim, padding, ks, stride, dilation)
elif isinstance(padding, str):
assert padding in ['same', 'valid']
if padding == 'same':
return conv_outdim_samepad(in_dim, stride)
else:
return conv_outdim_general(in_dim, 0, ks, stride, dilation)
else:
raise TypeError('Padding can be int/tuple or str=same/valid')
def conv_outdim_general(in_dim, padding, ks, stride, dilation=1):
# See https://arxiv.org/pdf/1603.07285.pdf, eq (15)
return ((in_dim + 2 * padding - ks - (ks - 1) * (dilation - 1)) // stride) + 1
def conv_outdim_samepad(in_dim, stride):
return (in_dim + stride - 1) // stride
@mirceamironenco ์ง์ ํด ์ฃผ์ ์ ๊ฐ์ฌํฉ๋๋ค. ๋๋ ์ด๊ฒ์ ๋น ๋ฅด๊ณ ๋๋ฝ๊ฒ ๋ง๋ค์๊ณ ๊ฒฐ์ฝ ํ์ธํ์ง ์์์ต๋๋ค. ๋์ ์ฒ์ฅ์ ์ฌ์ฉํ๋๋ก ์ ๋ฐ์ดํธ๋จ
@harritaylor ๋์ํฉ๋๋ค. ์ด ๊ธฐ๋ฅ์ Keras/TF ๋ชจ๋ธ์ PyTorch๋ก ์ด์ํ๋ ์์ ์ ํ์คํ ๋จ์ํํฉ๋๋ค. ๋๋๋ก ๋๋ ์ฌ์ ํ ํจ๋ฉ ํฌ๊ธฐ์ "์๋" ๊ณ์ฐ์ ์ฌ์ฉํ์ฌ ๋์ผํ ํจ๋ฉ ๋ ์ด์ด๋ฅผ ๋ง๋ญ๋๋ค.
@kylemcdonald
๋ค์์ ์ฐธ์กฐ์ฉ์ผ๋ก
same
ํจ๋ฉ์ด ์๋ ๋งค์ฐ ๊ฐ๋จํ Conv2d ๋ ์ด์ด์ ๋๋ค. ์ ์ฌ๊ฐํ ์ปค๋๊ณผ stride=1, dilation=1, groups=1๋ง ์ง์ํฉ๋๋ค.class Conv2dSame(torch.nn.Module): def __init__(self, in_channels, out_channels, kernel_size, bias=True, padding_layer=torch.nn.ReflectionPad2d): super().__init__() ka = kernel_size // 2 kb = ka - 1 if kernel_size % 2 == 0 else ka self.net = torch.nn.Sequential( padding_layer((ka,kb,ka,kb)), torch.nn.Conv2d(in_channels, out_channels, kernel_size, bias=bias) ) def forward(self, x): return self.net(x) c = Conv2dSame(1,3,5) print(c(torch.rand((16,1,10,10))).shape) # torch.Size([16, 3, 10, 10])
kb = ka - 1 if kernel_size % 2 else ka
์ด์ด์ผ ํ ๊น์?
์ด๊ฒ์ Conv1d์๋ ์ ์ฉ๋ฉ๋๊น?
ConvND ํด๋์ค์ ์๋ก์ด ํจ๋ฉ ๋ฐฉ๋ฒ์ ์ถ๊ฐํ๋ ๊ฒ์ ์ฐ์ํ ์ ํ์ผ ์ ์์ผ๋ฉฐ, ๋ฉ์๋๋ฅผ ์ค๋ฒ๋ก๋ํ์ฌ ํจ๋ฉ ์ผ์ ์ ์ฝ๊ฒ ์ฐ์ฅํ ์ ์์ต๋๋ค.
@sumith ๊ฐ ๊ทธ ์ ์์ ์์ฑํ ์ ์ด ์๊ฑฐ๋ ๋๊ตฐ๊ฐ๊ฐ ์ํํด์ผ ํ ์์
์ ์์ฝํ๋ฉด ์๋ง๋ ์ด๊ฒ์ ๋ฐ์๋ค์ผ ์ ์์ต๋๋ค. ์์์ ๋ง์ ๋
ผ์๊ฐ ์์๊ณ ์ฐ๋ฆฌ๊ฐ ๋ฌด์์ ๊ฒฐ์ ํ๋์ง ์ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค. ์
๋ ฅ ๋ฐ์ดํฐ์ ๋ฐ๋ผ ํจ๋ฉ์ ๊ณ์ฐํ๋์ง ์ฌ๋ถ, ํ์๋ padding="same"
๋ฅผ ๊ตฌํํด์ผ ํฉ๋๊น?
์ธ๊ณผ๊ด๊ณ ํจ๋ฉ๋ ์ถ๊ฐํ๊ณ ์ถ์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ด๊ฒ์ conv1d์๋ ์ถ๊ฐํด์ฃผ์ธ์.
๋๋ ์ด๋ ์์ ์์ ์ฃผ์์ ๋ฐ๋ฅด๋ ๊ฒ์ ์ค๋จํ์ง๋ง ์ด ๊ธฐ๋ฅ์ keras์์ ๋งค์ฐ ์ ์ํ๋์๋ค๊ณ ์๊ฐํฉ๋๋ค. ์ ํํ ๋ฐ๋ผ์ผ ํฉ๋๋ค.
@์น ๋ฆฌ ์ฌ๊ธฐ ์์ต๋๋ค.
๋ค์ ๋ ์ด์ด์ ํจ๋ฉ์ ์ถ๊ฐํด์ผ ํฉ๋๋ค.
์ฒซ ๋ฒ์งธ PR์ ๊ฒฝ์ฐ ๋จ์ํ๊ฒ ์ ์งํ๊ณ Conv*d๋ฅผ ๊ณ ์ํฉ์๋ค.
์์์ ๋
ผ์ํ ๋ณต์ก์ฑ์ same
ํจ๋ฉ ์ต์
์ด ์์ฑ๋ ํ ๋ ์ด์ด๊ฐ ๋ณธ์ง์ ์ผ๋ก ๋์ ์ผ๋ก ๋ณํ๋ ๊ฒ์
๋๋ค. ์ฆ, ๋ชจ๋ธ ๋ด๋ณด๋ด๊ธฐ(์: ONNX ๋ด๋ณด๋ด๊ธฐ)์ ์ข์ ์ ์ ์ผ๋ก ์๋ ค์ง ๋ ์ด์ด์ ๋งค๊ฐ๋ณ์์์ ๋์ ์ธ ๋ ์ด์ด์ ๋งค๊ฐ๋ณ์๋ก ์ด๋ํฉ๋๋ค. ์ด ๊ฒฝ์ฐ ๋์ ๋งค๊ฐ๋ณ์๋ padding
์
๋๋ค.
์ด๊ฒ์ ๋งค์ฐ ๋ฌดํดํด ๋ณด์ด์ง๋ง, ์๋ฅผ ๋ค์ด ์ ์ ํํ ๋ถ์ ๋ฐ ์ต์ ํ๋ฅผ ์ํํ๋ ค๋ ๋ชจ๋ฐ์ผ ๋๋ ์ด๊ตญ์ ์ธ ํ๋์จ์ด ๋ฐํ์๊ณผ ๊ฐ์ ์ ํ๋ ๋ฐํ์์์ ๋น์ ์ ์ฑ์ ๋งค์ฐ ์ค์ํฉ๋๋ค.
๋ค๋ฅธ ์ค์ฉ์ ์ธ ๋จ์ ์ ๋์ ์ผ๋ก ๊ณ์ฐ๋ padding
๊ฐ ๋ ์ด์ ํญ์ ๋์นญ์ ์ด์ง ์๋ค๋ ๊ฒ์
๋๋ค. ์ปค๋์ ํฌ๊ธฐ/๋ณดํญ, ํฝ์ฐฝ ๊ณ์ ๋ฐ ์
๋ ฅ ํฌ๊ธฐ์ ๋ฐ๋ผ ํจ๋ฉ์ด ๋น๋์นญ(์ฆ, ๋ค๋ฅธ ์ผ์ชฝ ๋ ์ค๋ฅธ์ชฝ์ ํจ๋ฉ ์). ์๋ฅผ ๋ค์ด CuDNN ์ปค๋์ ์ฌ์ฉํ ์ ์๋ค๋ ์๋ฏธ์
๋๋ค.
ํ์ฌ Conv2d์ ์๋ช ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros')
์ฌ๊ธฐ์ ์ฐ๋ฆฌ๋ padding
๊ฐ int
๋๋ tuple
์ ์ ์๊ฐ ๋๋๋ก ์ง์ํฉ๋๋ค(์ฆ, ๋์ด / ๋๋น์ ๊ฐ ์ฐจ์์ ๋ํด).
๊ฐ์ด same
๋ฌธ์์ด์ ์ฌ์ฉํ๋ padding
๋ํ ์ถ๊ฐ ์ค๋ฒ๋ก๋๋ฅผ ์ง์ํด์ผ ํฉ๋๋ค.
same
ํจ๋ฉ์ output
ํฌ๊ธฐ๊ฐ input
ํฌ๊ธฐ์ ๋์ผํ๋๋ก ์ปจ๋ณผ๋ฃจ์
์ ์ ๊ณตํ๊ธฐ ์ ์ input
๋ฅผ ํจ๋ฉํด์ผ ํฉ๋๋ค.
'same'
๊ฐ padding
์ฃผ์ด์ก์ ๋, ์ฐ๋ฆฌ๋ ๊ฐ ์ฐจ์์์ ํ์ํ ์ผ์ชฝ๊ณผ ์ค๋ฅธ์ชฝ ํจ๋ฉ์ ์์ ๊ณ์ฐํด์ผ ํฉ๋๋ค.
ํ์ํ L(์ผ์ชฝ) ๋ฐ R(์ค๋ฅธ์ชฝ) ํจ๋ฉ์ด ๊ณ์ฐ๋ ํ ๊ณ ๋ คํด์ผ ํ ๋ ๊ฐ์ง ๊ฒฝ์ฐ๊ฐ ์์ต๋๋ค.
F.conv2d
A๋ฅผ 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
= "same"์ ๋ํด์๋ ๋ ์ด์ด์ ํน์ ํด์ผ ํ๋ ๊ฒ์ฒ๋ผ ๋ณด์ด๋ ๊ฒ์ด ์์ต๋๋ค. (ํธ์ง: Nvm, ๋ด๊ฐ ๋ณด๊ณ ์๋ F.conv2d
impl์ด ์์ํ๋ ๊ฒ์์ ๊นจ๋ซ์ง ๋ชปํ์ต๋๋ค).valid
ํจ๋ฉ ๋ชจ๋๋ ๋จ์ํ padding=0
๊ฐ ์๋ ๊ฒ๊ณผ ๋์ผํ๋ค๊ณ ์๊ฐํฉ๋๋ค. ๋ง์ต๋๊น?๋ํ ์ฌ์ฉ์๊ฐ ๋น๋์นญ ํจ๋ฉ์ ์ฝ๊ฒ ํด๊ฒฐํ ์ ์์ ๊ฒ ๊ฐ์ง ์์ต๋๋ค. ๋ฐ์ํด์ผ ํ๋ ํจ๋ฉ์ ์์ ๊ฒฐ์ ํ๋ ์ ์ฒด ๊ท์น์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
์ฐจ์์ ๋ฐ๋ผ (ceil(x/stride) -1)*stride + (filter-1)*dilation + 1 - x
์
๋๋ค. ํนํ, ์ด๊ฒ์ด 2์ ๋ฐฐ์๊ฐ ์๋ ๊ฒฝ์ฐ ๋น๋์นญ ํจ๋ฉ์ ์ํํด์ผ ํฉ๋๋ค. ์ด๊ฒ์ด ์ง์ ํฌ๊ธฐ์ ํํฐ์์๋ง ๋ฐ์ํ๊ธฐ๋ฅผ ๋ฐ๋ผ๋ ๊ฒ์ ๋ํ ๋ฐ๋ก๋ก input = 10, stride=3, filter=3, dilation=1
์ทจํ์ญ์์ค. ๋๋ ์ด๊ฒ์ด ์ผ์ด๋ ์ ์๋ ์ํฉ์ ํด๊ฒฐํ๊ธฐ ์ํ ์ด๋ค ๊ฐ๋จํ ๊ท์น๋ ๋ณด์ง ๋ชปํ๋ค.
๋ํ stride=1
, ceil(x/stride) = x
์ธ ๊ฒฝ์ฐ๋ฅผ ์ ์ธํ๊ณ ํจ๋ฉ์ ์ ์ ์ผ๋ก ๊ฒฐ์ ํ ์ ์์ผ๋ฉฐ ํจ๋ฉ์ด (filter-1)*dilation
.
@Chillee (1)์ ๋ํด ์ด์ ๊ฐ ์์ต๋๋ค. ์ฑ๋ฅ ๋๋ ๊ธฐํ ์๋ฏธ์ ๋ํด ์๊ฐํ์ง ์์์ต๋๋ค.
(2) ๋ค.
๋ํ stride=1์ธ ๊ฒฝ์ฐ๋ฅผ ์ ์ธํ๊ณ ๋ ํจ๋ฉ์ ์ ์ ์ผ๋ก ๊ฒฐ์ ํ ์ ์์ต๋๋ค. ceil(x/stride) = x์ด๊ณ ํจ๋ฉ์ (filter-1)*dilation๊ณผ ๊ฐ์ต๋๋ค.
์, ํ์ง๋ง stride=1์ ์ถฉ๋ถํ ์ผ๋ฐ์ ์ด๋ฉฐ ์ ์ ํจ๋ฉ์ ์ด์ ์ ํ์คํ ํน๋ณํ ์ฒ๋ฆฌํด์ผ ํ ๋งํผ ์ข์ต๋๋ค.
๋น๋์นญ ํจ๋ฉ์ ๋ํด, ์.....
padding=SAME
์ ์ ํ์ API๋ฅผ ์ ๊ณตํ ์ ์๋ ์ด์ ๋ ๋ฌด์์ ๋๊น? ๋๊ตฐ๊ฐ๊ฐ ํจ๋ฉ์ ๋ํ ์ถ๊ฐ ๋น์ฉ์ ๊ธฐ๊บผ์ด ์ง๋ถํ ์์ฌ๊ฐ ์๋ค๋ฉด ๊ทธ๋ ๊ฒ ํ๋๋ก ํ์ญ์์ค. ๋ง์ ์ฐ๊ตฌ์์๊ฒ ๋น ๋ฅธ ํ๋กํ ํ์ดํ์ ์๊ตฌ ์ฌํญ์ ๋๋ค.
์,
padding=SAME
์ ์ ํ์ API๋ฅผ ์ ๊ณตํ ์ ์๋ ์ด์ ๋ ๋ฌด์์ ๋๊น? ๋๊ตฐ๊ฐ๊ฐ ํจ๋ฉ์ ๋ํ ์ถ๊ฐ ๋น์ฉ์ ๊ธฐ๊บผ์ด ์ง๋ถํ ์์ฌ๊ฐ ์๋ค๋ฉด ๊ทธ๋ ๊ฒ ํ๋๋ก ํ์ญ์์ค. ๋ง์ ์ฐ๊ตฌ์์๊ฒ ๋น ๋ฅธ ํ๋กํ ํ์ดํ์ ์๊ตฌ ์ฌํญ์ ๋๋ค.
๋์ํ๋ค! ๋๋ ์ด ๋น์ด๋จน์ "ํจ๋ฉ"์ 4์๊ฐ ๋์ ๊ฐํ ์์๋ค.
์ด ๋ฌธ์ ์ ๋ํ ์๋ฃจ์ ์ ๋ํ ์ ๋ฐ์ดํธ๊ฐ ์์ต๋๊น?
์์ฐ ๊ทธ๋ฆฌ๊ณ ์ฌ๊ธฐ์์ ์ ๋ Pytorch๊ฐ Keras/Tensorflow 2.0๋ณด๋ค ์ฌ์ธ ๊ฒ์ด๋ผ๊ณ ์๊ฐํ์ต๋๋ค...
@zwep ์์ํ๋ ๋ฐ ์กฐ๊ธ ๋ ๋ง์ ๋ ธ๋ ฅ์ด ํ์ํฉ๋๋ค. ์ฑ๊ฐ์ค ์ ์๋ trianing ๋ฃจํ๋ฅผ ์์ฑํด์ผ ํ๊ณ ๋ ์ด์ด๋ฅผ ๋ ๋ช ์์ ์ผ๋ก ์์ฑํด์ผ ํฉ๋๋ค. ์ผ๋จ ๋น์ ์ด ๊ทธ๊ฒ์ ๋๋ด๋ฉด(ํ ๋ฒ) ๋น์ ์ ๊ทธ ์ด์์ผ๋ก ์ค์ ๊ฐ์ ์ ๋ํด ํจ์ฌ ๋ ๋ฐ์ ํ ์ ์์ต๋๋ค.
๋ด๊ฐ ๊ฒฝํํ ๊ท์น์ ๋ฐฑ๋ง ๋ฒ/์ต๊ณ ์์ค์ ์์
์ ์ํํ ๊ฒฝ์ฐ Keras๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์
๋๋ค.
์ฐ๊ตฌ ๊ฐ๋ฐ์ด ํ์ํ ๋๋ง๋ค pytorch๋ฅผ ์ฌ์ฉํ์ญ์์ค.
ํจ๋ฉ 1d ์ ํ์ ๋ํ ๋ด ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
์์
ํ ์น
ํ ์น ์์
nn์์
numpy๋ฅผ np๋ก ๊ฐ์ ธ์ค๊ธฐ
ํ ์น.๊ธฐ๋ฅ์ F๋ก ๊ฐ์ ธ์ค๊ธฐ
class Conv1dSamePad(nn.Module):
def __init__(self, in_channels, out_channels, filter_len, stride=1, **kwargs):
super(Conv1dSamePad, self).__init__()
self.filter_len = filter_len
self.conv = nn.Conv1d(in_channels, out_channels, filter_len, padding=(self.filter_len // 2), stride=stride,
**kwargs)
nn.init.xavier_uniform_(self.conv.weight)
# nn.init.constant_(self.conv.bias, 1 / out_channels)
def forward(self, x):
if self.filter_len % 2 == 1:
return self.conv(x)
else:
return self.conv(x)[:, :, :-1]
class Conv1dCausalPad(nn.Module):
def __init__(self, in_channels, out_channels, filter_len, **kwargs):
super(Conv1dCausalPad, self).__init__()
self.filter_len = filter_len
self.conv = nn.Conv1d(in_channels, out_channels, filter_len, **kwargs)
nn.init.xavier_uniform_(self.conv.weight)
def forward(self, x):
padding = (self.filter_len - 1, 0)
return self.conv(F.pad(x, padding))
class Conv1dPad(nn.Module):
def __init__(self, in_channels, out_channels, filter_len, padding="same", groups=1):
super(Conv1dPad, self).__init__()
if padding not in ["same", "causal"]:
raise Exception("invalid padding type %s" % padding)
self.conv = Conv1dCausalPad(in_channels, out_channels, filter_len, groups=groups) \
if padding == "causal" else Conv1dSamePad(in_channels, out_channels, filter_len, groups=groups)
def forward(self, x):
return self.conv(x)
@danFromTelAviv ์ฝ๋ ๊ฐ์ฌํฉ๋๋ค. ๊ทธ pytorch ์ฒ ํ์ ์ผ๋์ ๋ ๊ฒ์ ๋๋ค!
2020๋
์
๋๋ค. ์์ง Pytorch์ padding='same'
๊ฐ ์์ต๋๊น?
์ด๊ฒ์ ๋ชจ๋ ์ปค๋ ํฌ๊ธฐ, ๋ณดํญ ๋ฐ ํฝ์ฐฝ์ ๋ํด ๋์ผํ ํจ๋ฉ์ด ์๋ํ๋๋ก ํ๋ ํ ๊ฐ์ง ๋ฐฉ๋ฒ์ ๋๋ค(์ปค๋ ํฌ๊ธฐ๋ ์๋ํจ).
class Conv1dSame(nn.Module):
def __init__(self, in_channels, out_channels, kernel_size, stride=1, dilation=1):
super().__init__()
self.cut_last_element = (kernel_size % 2 == 0 and stride == 1 and dilation % 2 == 1)
self.padding = math.ceil((1 - stride + dilation * (kernel_size-1))/2)
self.conv = nn.Conv1d(in_channels, out_channels, kernel_size, padding=self.padding, stride=stride, dilation=dilation)
def forward(self, x):
if self.cut_last_element:
return self.conv(x)[:, :, :-1]
else:
return self.conv(x)
nn.Conv2d
์๋ "๋์ผํ ํจ๋ฉ" ๊ธฐ๋ฅ์ด ํ์ํฉ๋๋ค.
BTW, ์์์ ๋ ผ์ํ ์ฑ๋ฅ/์ง๋ ฌํ ๋ฌธ์ ์ธ์๋ 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 ์ ์ฝ๋๋ฅผ ์ฝ์ ํ padding='same'์ผ๋ก ๋ค๋ฅธ ๋ฒ์ ์ conv1d๋ฅผ ๋ง๋ญ๋๋ค.
class Conv1dPaddingSame(nn.Module):
'''pytorch version of padding=='same'
============== ATTENTION ================
Only work when dilation == 1, groups == 1
=========================================
'''
def __init__(self, in_channels, out_channels, kernel_size, stride):
super(Conv1dPaddingSame, self).__init__()
self.kernel_size = kernel_size
self.stride = stride
self.weight = nn.Parameter(torch.rand((out_channels,
in_channels, kernel_size)))
# nn.Conv1d default set bias=True๏ผso create this param
self.bias = nn.Parameter(torch.rand(out_channels))
def forward(self, x):
batch_size, num_channels, length = x.shape
if length % self.stride == 0:
out_length = length // self.stride
else:
out_length = length // self.stride + 1
pad = math.ceil((out_length * self.stride +
self.kernel_size - length - self.stride) / 2)
out = F.conv1d(input=x,
weight = self.weight,
stride = self.stride,
bias = self.bias,
padding=pad)
return out
์ด์ ๋ํ ์ ๋ฐ์ดํธ๊ฐ ์์ต๋๊น?
์ด๋ค ์ ๋ฐ์ดํธ??
@peterbell10 ์ด ํ๋ก์ฐํ ์ ์๋ PR ์ด์์ ์ฐ๊ฒฐํ์ต๋๋ค.
๊ฐ์ฅ ์ ์ฉํ ๋๊ธ
๊ฐ๊น์ด ์ฅ๋์ pytorch์์ ์ ์ฌํ API๋ฅผ ๊ตฌํํ ๊ณํ์ด ์์ต๋๊น? tensorflow / keras ๋ฐฐ๊ฒฝ์์ ์จ ์ฌ๋๋ค์ ํ์คํ ๊ฐ์ฌํ ๊ฒ์ ๋๋ค.