λ³Έ 글은 2020-2ν•™κΈ° β€œμ»΄ν“¨ν„° 비전” μˆ˜μ—…μ„ λ“£κ³ , 슀슀둜 ν•™μŠ΅ν•˜λ©΄μ„œ 개인적인 μš©λ„λ‘œ μ •λ¦¬ν•œ κ²ƒμž…λ‹ˆλ‹€. 지적은 μ–Έμ œλ‚˜ ν™˜μ˜μž…λ‹ˆλ‹€ :)

5 minute read

λ³Έ 글은 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을 κ΅¬ν˜„ν•œ λ˜λ‹€λ₯Έ μ½”λ“œλ„ μžˆλ‹€.

github/kuangliu

이 μ½”λ“œμ—μ„  ResNet18, ResNet34, ResNet50, ResNet101, ResNet152κΉŒμ§€ λͺ¨λ‘ κ΅¬ν˜„λ˜μ–΄ μžˆλ‹€.

이 μ½”λ“œλ„ λͺ¨λ“ˆ 뢄리λ₯Ό 잘 해두어 κΉ”λ”ν•œ νŽΈμ΄μ§€λ§Œ, 주석이 λΆ€μ‘±ν•œ 점이 아쉽닀.