ResNet (code)
λ³Έ κΈμ 2020-2νκΈ° βμ»΄ν¨ν° λΉμ β μμ μ λ£κ³ , μ€μ€λ‘ νμ΅νλ©΄μ κ°μΈμ μΈ μ©λλ‘ μ 리ν κ²μ λλ€. μ§μ μ μΈμ λ νμμ λλ€ :)
pyTorch
λ₯Ό μ΄μ©ν΄ ResNet
μ ꡬνν github/KellerJordanμ μ½λμ λν κ°μΈμ μΈ λΆμκΈμ
λλ€.
ResNet32
μ ꡬνν κ²μΌλ‘ μΆμ λ©λλ€.
picture from Pablo Ruiz's blog
ResNet20 μ½λ λΆμ
ResNet20
class ResNet(nn.Module):
...
self.layers1 = self._make_layer(n, 16, 16, 1)
...
def _make_layer(self, layer_count, channels, channels_in, stride):
return nn.Sequential(
ResBlock(channels, channels_in, stride, ...),
*[ResBlock(channels) for _ in range(layer_count-1)])
class ResBlock(nn.Module):
...
ResNet
λͺ¨λ νλλ§ λ§λ€μ΄ λͺ¨λΈμ ꡬμΆν κ²μ΄ μλλΌ ResBlock
λͺ¨λμ λ§λ€μ΄ μ¬μ©ν μ μ΄ λμ λλ€.
μ¦, nn.Module
μ μμ λ°μ λͺ¨λ λ΄λΆμ λ λ€λ₯Έ λͺ¨λμ μ¬μ΄μ λͺ¨λΈ ꡬ쑰λ₯Ό λμμΈν μ μμμ 보μ¬μ€λ€! (κ΅³μ΄ λ°μ§μλ©΄, dependencyλ₯Ό λΆμ¬νλ€λ λ§)
ResBlock
λͺ¨λμ nn.Sequential
λ₯Ό μ΄μ©ν΄ μ΄μ΄λΆμλ€.
def _make_layer(self, layer_count, channels, channels_in, stride):
return nn.Sequential(
ResBlock(channels, channels_in, stride, ...),
*[ResBlock(channels) for _ in range(layer_count-1)])
[ResBlock(channels) for _ in range(layer_count-1)]
μ΄ λΆλΆμ 보면 μ μ μλ― λ΄λΆμ μμΉν ResBlock
μμ μ±λμκ° μ μ§λλ€.
μ½λμμλ layer_count
λ‘ λ³μκ°μΌλ‘ μ§μ λμ΄ μλλ°, default κ°μ 5
λΌκ³ νλ€.
κ·Έλμ _make_layer
ν¨μλ μ±λμλ₯Ό λ λ°°λ‘ λ리λ ResBlock
κ³Ό μ±λμκ° μ μ§λλ 4
κ°μ ResBlock
μ μμ±νλ€.
κ° ResBlock
μ 2κ°μ conv layerλ₯Ό κ°λλ°, λ°λΌμ _make_layer
κ° 10κ°μ conv layerλ₯Ό μμ±ν¨μ μ μ μλ€.
λ, [ResBlock(channels) for _ in range(layer_count-1)]
λ inline forλ¬Έμ μ±μ©ν΄ μ½λλ₯Ό κ²½λν νλ€.
κ·Έλ¦¬κ³ nn.Sequential()
λ΄λΆμ *[]
λ₯Ό μ¬μ©νλλ°, μ€μ λ‘ list νμ
μ *
λ₯Ό λΆμ¬μ nn.Sequential()
μ μ λ¬ν μ μλ€κ³ νλ€. μλλ μμ μ½λ
import torch.nn as nn
net = nn
layers = [nn.Linear(2, 2), nn.Linear(2, 2)]
net = nn.Sequential(*layers)
print(net)
μ΄ ResNet μ½λλ _make_layer()
ν¨μλ₯Ό μΈλ² μ λ νΈμΆνλ€.
class ResNet(nn.Module):
def __init__(self, ...):
...
self.layers1 = self._make_layer(n, 16, 16, 1)
self.layers2 = self._make_layer(n, 32, 16, 2)
self.layers3 = self._make_layer(n, 64, 32, 2)
...
λ€μ΄μ΄κ·Έλ¨μΌλ‘ ννν ꡬ쑰μ μ½λλ₯Ό λΉκ΅ν΄λ³΄μ.
picture from Pablo Ruiz's blog
class ResNet(nn.Module):
def forward(self, x):
out = self.conv1(x)
out = self.norm1(out)
out = self.relu1(out)
out = self.layers1(out) # in: 16, out: 16
out = self.layers2(out) # in: 16, out: 32
out = self.layers3(out) # in: 32, out: 64
out = self.avgpool(out)
out = out.view(out.size(0), -1)
out = self.linear(out) # in: 64, out: 10
return out
ResNet
μ μ΄ conv layer μλ₯Ό λ°μ§λ©΄,
1 + (10 + 10 + 10) + 1 = 32
κ·Έλμ μ΄ μ½λλ ResNet32
λ₯Ό ꡬνν κ²μ΄λ€!
ResBlock
ResNet
μ κ½μ skip connectionμ΄ κ΅¬νλ ResBlock
λΆλΆμ΄λ€.
class ResBlock(nn.Module):
...
def forward(self, x):
residual = x # store residual
out = self.conv1(x)
out = self.bn1(out)
out = self.relu1(out)
out = self.conv2(out)
out = self.bn2(out)
out += residual # skip connection!
out = self.relu2(out)
return out
λ³ΈμΈμ μ΄ μ½λλ₯Ό λ³΄κ³ λμμΌ λΉλ‘μ ResNetμ΄ μμ ν μ΄ν΄κ° λμλ€ γ γ
μ°Έκ³ λ‘ ResBlock
μμ μ¬μ©λ layer μλ 2κ°μ΄λ€.
residual projection options
μ΄ κ΅¬νμμ residual
μ λ°λ‘ λνλ κ² μλλΌ self.projection
μ νλ² κ±°μΉκ² νλ μ΅μ
λ ꡬνμ νλ€.
class ResBlock(nn.Module):
def __init__(self, num_filters, ...):
...
if res_option == 'A':
self.projection = IdentityPadding(num_filters, channels_in, stride)
elif res_option == 'B':
self.projection = ConvProjection(num_filters, channels_in, stride)
elif res_option == 'C':
self.projection = AvgPoolPadding(num_filters, channels_in, stride)
...
def forward(self, x):
...
if self.projection: # residual projection!
residual = self.projection(x)
...
κ°κ° 2μ°¨μμ residual
μ΄λ―Έμ§λ₯Ό μ²λ¦¬νλ μ΅μ
λ€λ‘
residual
μ΄λ―Έμ§λ₯Ό κ·Έλλ‘ λ³΄λ΄κΈ°λ νκ³ ;IdentityPadding()
residual
μ΄λ―Έμ§λ₯Ό Convolution νκΈ°λ νκ³ ;ConvProjection()
residual
μ΄λ―Έμ§λ₯Ό Average Pooling νκΈ°λ νλ€;AvgPoolPadding()
residual projection μ΅μ λ€μ λν λ μμΈν λ΄μ©μ μ΄ λ§ν¬λ₯Ό ν΅ν΄ νμΈν μ μλ€!
KellerJordanμ ResNetμ λͺ¨λΈ ꡬνμ κΉλνκ² μ ν΄λμ΄μ μ λ§ μ’μ μ½λλΌκ³ μκ°νλ€ γ γ
ResNet
μ ꡬνν λλ€λ₯Έ μ½λλ μλ€.
μ΄ μ½λμμ ResNet18
, ResNet34
, ResNet50
, ResNet101
, ResNet152
κΉμ§ λͺ¨λ ꡬνλμ΄ μλ€.
μ΄ μ½λλ λͺ¨λ λΆλ¦¬λ₯Ό μ ν΄λμ΄ κΉλν νΈμ΄μ§λ§, μ£Όμμ΄ λΆμ‘±ν μ μ΄ μμ½λ€.