본 논문에서 저자들은 global average pooling layer에 대해서 다시 논의하고, 어떻게 이것에 의해서 convolutional neural network (CNN)가 image-level label에 학습되었음에도 불구하고 놀라운 localization ability를 가지도록 만들어주는지에 대해서 논의합니다.
Global average pooling은 학습을 규제하기 위한 수단으로써 이전에 제안되었지만, 본 논문의 저자들은 이것이 이미지에 대해서 CNN의 암시된 attention을 드러내는 포괄적인 localizable deep representation을 만들어낸다고 생각하였습니다.
Global average pooling의 단순함에도 불구하고, 본 논문의 저자들은 어떠한 bounding box annotation을 이용한 학습을 하지 않고도 ILSVRC 2014 object localization에 대해서 37.1% top-5 error를 달성할 수 있었습니다.
저자들은 본 논문에서 network가 classification task를 해결하기 위해서 학습되었음에도 불구하고, discriminative image region의 위치를 알아낼 수 있다는 것을 다양한 실험을 통해서 검증하였습니다.
1. Introduction
Zhou et al에 의한 최근 연구는 object의 위치에 대한 어떠한 supervision이 제공되지 않았음에도 불구하고 convolutional neural network (CNNs)의 여러 가지 layers의 convolutional units이 실제로 object detector로써 행동한다는 것을 보여주었습니다.
Convolutional layers가 object의 위치를 알아낼 수 있는 놀라운 능력을 가지고 있음에도 불구하고, 이러한 능력은 classification을 위해 사용되는 fully-connected layer가 존재할 때 잃게 됩니다.
최근에, Network in Network (NIN)와 GoogLeNet과 같은 여러 인기 있는 fully-convolutional neural networks는 좋은 성능을 유지하면서도 parameter의 수를 최소화하기 위해 fully-connected layers의 사용을 피하기 위해 제안되었습니다. (convolution layer에 비해서 fully-connected layer의 weight 수가 훨씬 많기 때문이죠.)
이를 달성하고자, Zhou et al는 structural regularizer의 역할을 수행하는 global average pooling을 사용하였으며, 이는 학습이 진행되는 도중에 overfitting을 방지합니다.
본 논문의 저자들은 실험을 통해 global average pooling의 이점이 단순히 regularizer의 역할을 수행하는 것 이상으로 확장될 수 있다는 사실을 발견하였습니다.
즉, network의 구조를 약간 수정하면, network가 놀라운 localization ability를 마지막 layer까지 보유할 수 있다는 것입니다.
이러한 수정은 네트워크가 매우 다양한 task를 위한 discriminative image region을 single forward-pass에서 쉽게 식별하도록 만들어줍니다.
Figure 1(a)에서 보이는 것처럼, object categorization에 대해 학습된 CNN은 action classification을 위한 discriminative regions을 사람보다는 사람이 상호작용하고 있는 개체의 위치로 잘 찾고 있는 것을 볼 수 있습니다.
본 논문의 저자들의 접근법이 분명하게 간단함에도 불구하고, ILSVRC benchmark에 대한 weakly supervised object localization에 대해서 저자들의 최고 성능은 top-5 test error 기준 37.1%를 달성하였고, 이는 fully supervised AlexNet이 달성한 top-5 error 34.2%에 근접합니다.
추가적으로, 본 논문의 저자들은 제안하는 접근법을 통해 얻어지는 deep feature의 localizability가 generic classification, localization, 그리고 concept discovery를 위한 다른 recognition dataset에도 쉽게 사용될 수 있다는 것을 여러 가지 실험을 통해 검증합니다.
1.1 Related Work
선행 연구 부분은 생략합니다.
2. Class Activation Mapping
이번 section에서, 저자들은 CNNs에 있는 global average pooling을 사용해 class activation maps (CAM)를 만들어내는 절차에 대해서 설명합니다.
특정한 category에 대한 class activation map은 해당 이미지를 특정한 category로 식별하기 위해 CNN에 의해서 사용된 discriminative image region을 나타냅니다.
Figure 3는 이에 대한 예시를 보여줍니다.
이러한 map을 만들어내는 절차는 Figure 2에 설명되어 있습니다.
저자들은 Network in Network와 GoogLeNet과 유사한 network architecture를 사용하였습니다.
즉, network는 주로 convolutional layer로 구성되고, 마지막 output layer의 바로 직전 convolutional feature map에 대해서 global average pooling을 수행하여 이를 desired output을 만들어내는 fully-connected layer를 위한 feature로써 사용합니다.
이 간단한 connectivity structure가 주어졌을 때, 저자들은 output layer의 weight를 convolutional feature maps에 대해서 다시 project 함으로써 image regions에 대한 importance를 확인할 수 있으며 저자들은 이 기법을 class activation mapping이라고 부릅니다.
Figure 2에서 볼 수 있듯이, global average pooling은 마지막 convolutional layer의 각 unit의 feature map의 spatial average를 만들어냅니다.
이러한 값들의 weighted sum은 final output을 만들어내는 데 사용됩니다.
유사하게, 저자들은 class activation map을 얻기 위해서 last convolutional layer의 feature map의 weighted sum을 계산합니다.
저자들은 이를 softmax의 케이스에 대해 formally 하게 설명합니다. 이 기법은 다른 loss나 regression에 대해서도 적용할 수 있습니다.
(이 파트부터는 제가 직접 만든 그림과 함께 설명을 진행하겠습니다.)
주어진 이미지에 대해서, $f_k(x, y)$는 spatial location $(x, y)$에서의 last convolutional layer의 unit $k$의 activation을 나타냅니다.
last convolutional layer의 크기를 (Batch, 3, 4, 4)라고 가정하겠습니다. (channel이 3, 가로 세로가 각각 4라고 가정)
여기서 channel이 3 이므로, $k$는 3까지 존재하게 됩니다.
그러고 3번째 channel의 (1, 1)의 위치는 $f_3(1,1)$로 표현될 수 있습니다.
그러고 나서, unit $k$에 대해, global average pooling을 수행한 결과인 $F^k$는 $\sum_{x, y}f_k(x, y)$입니다.
global average pooling을 하게 되면, 이전 $f_k$에서 4x4 짜리였던 데이터를 평균 내서 하나의 값으로 만들게 됩니다.
따라서 그림에서 $F^k$가 하나의 데이터로 표현이 되는 것이죠.
각 데이터들은 $F^1$, $F^2$, $F^3$가 됩니다.
따라서, 주어진 class $c$에 대해서, softmax의 input $S_c$는 $\sum_k w^c_kF_k$이며, $w^c_k$는 unit $k$가 class $c$에 대응되는 weight를 나타냅니다.
문제를 단순하게 하기 위해서, 하나의 이미지를 개와 고양이를 분류하는 이진 분류 문제로 가정하였습니다.
Global Average Pooling을 통해서 얻은 $F^k$를 weight와 곱해서 최종적으로 softmax의 input인 $S_c$를 얻게 됩니다.
이때, $S_1$ = $F^1 \times 0.3(w^1_1) + F^2 \times 0.5(w^1_2) + F^3 \times 0.1(w^1_3)$로 구할 수 있습니다.
동일한 방법을 적용하면, $S_2$ = $F^1 \times 0.4(w^2_1) + F^2 \times 0.6(w^2_2) + F^3 \times 0.8(w^2_3)$로 구할 수 있게 됩니다.
본질적으로, $w^c_k$는 class $c$에 대한 $F_k$의 importance를 나타냅니다.
최종적으로 class $c$에 대한 softmax output $P_c$는 ${exp(S_c)}/{\sum_c exp(S_c)}$로 나타내질 수 있습니다.
여기서 저자들은 bias term를 무시하고자 명시적으로 softmax의 input bias를 0으로 지정하는데, 이는 classification performance에 영향을 거의 주지 않기 때문이라고 합니다.
$F_k = \sum_{x, y}f_k(x, y)$를 class score $S_c$를 나타내는 식에 적용하면, 이렇게 표현할 수 있습니다.
저자들은 $M_c$를 class $c$에 대한 class activation map으로 정의하고, 각 spatial element는 다음과 같이 나타낼 수 있습니다.
위의 예시 그림을 통해서 표현해보면 다음과 같습니다.
따라서, $S_c = \sum_{x, y}M_c(x, y)$이며, $M_c(x, y)$는 직접적으로 image의 분류가 class $c$가 되도록 이끄는 spatial grid $(x, y)$에서의 activation의 importance를 나타냅니다.
직관적으로는, 이전 연구에 기반해서 본 논문의 저자들은 각 unit이 receptive field 내에서 어떤 visual pattern에 의해 활성화되는 것을 기대합니다.
따라서, $f_k$는 이러한 visual pattern의 존재에 대한 map이 됩니다.
Class activation map은 간단하게 다른 spatial locations에서 이러한 visual pattern의 존재에 대한 weighted linear sum이 됩니다.
Class activation map을 input image의 사이즈로 upsampling 해줌으로써, 특정한 category와 가장 관련이 있는 image regions을 식별할 수 있게 됩니다.
Figure 3에서는 위의 접근법을 사용해서 나온 CAM output의 여러 예시들을 보여줍니다.
다양한 클래스의 이미지들의 discriminative region이 강조된 것을 볼 수 있습니다.
Figure 4에서는, 하나의 이미지에 대해서 map을 만들 때 다른 classes $c$를 사용했을 때 CAM에서의 차이를 보여줍니다.
이를 통해서 다른 category에 대한 discriminative region이 주어진 이미지에 대해서 다르다는 것을 확인할 수 있습니다.
이는 저자들의 접근법이 기대한 대로 작동함을 나타냅니다.
Global average pooling (GAP) vs Global max pooling (GMP)
Weakly supervised object localization에서 GMP을 사용한 이전 연구들을 고려해 볼 때, 저자들은 GAP와 GMP 사이의 직관적인 차이를 강조하는 것이 중요하다고 생각했다고 합니다.
저자들이 생각하기에는 하나의 discriminative part만을 식별하도록 만드는 GMP와 비교했을 때 GAP loss는 network가 object의 면적을 식별할 수 있도록 만든다고 합니다.
이는 맵의 평균을 낼 때, 낮은 activation이 맵의 output을 감소시키므로 object의 모든 discriminative part를 찾아 최대화될 수 있기 때문이라고 합니다.
반면에 GMP의 경우는 가장 discriminative 한 image region을 제외하고는 모든 image region의 낮은 score가 max를 수행할 때 score에 영향을 주지 않습니다.
저자들은 이를 Section 3에서 ILSVRC dataset에 대해서 실험적으로 검증합니다.
GMP는 classification performance에 있어서는 GAP와 유사한 성능을 내지만, GAP는 localization에 있어서는 GMP보다 더 좋은 성능을 냅니다.
3. Weakly-supervised Object Localization
이번 section에서는 ILSVRC 2014 benchmark dataset에 대해서 CAM의 localization ability를 평가합니다.
모든 내용을 자세히는 다루지 않을 것이고, 간략하게 다루려고 합니다.
3.1 Setup
해당 실험에서는, AlexNet, VGGnet, GoogLeNet을 사용하여 실험을 진행합니다.
이 3개의 network는 원래 fully-connected layer가 있지만, 이를 제거하고 GAP로 대체하여 실험을 진행합니다.
fully-connected layer가 network parameter의 수를 상당히 많이 감소시키므로, classification performance의 drop을 가져오게 됩니다.
그리고 저자들은 network의 localization ability가 GAP 이전의 last convolutional layer가 더 큰 spatial resolution을 가질 때 향상된다는 사실을 발견하였으며, 이를 mapping resolution이라고 부른다고 합니다.
Classification에 대해서는 original AlexNet, VGGnet, GoogLeNet, Network in Network에 대한 결과를 제공합니다.
Localization에 대해서는 original GoogLeNet, NIN, 그리고 backpropagation을 이용한 결과를 비교합니다.
추가적으로, max pooling과 average pooling을 비교하기 위해, global max pooling으로 학습된 GoogLeNet의 결과도 제공합니다. (GoogLeNet-GMP)
3.2 Results
Classification
Table 1에서 classification performance의 비교를 보여줍니다.
다양한 network에서 layer를 제거하는 것에 의해 약간의 성능 하락이 보이고 있습니다.
AlexNet이 fully-connected layer의 제거에 의해 가장 크게 영향을 받았는데, 이를 보상해주기 위해 두 개의 convolutional layer를 추가하여 AlexNet*-GAP network로 실험했다고 합니다.
그리고 GoogLeNet-GMP와 GoogLeNet-GAP는 유사한 성능을 보이고 있는데, 이는 저자들이 예측한 대로 나오고 있음을 알 수 있는 부분입니다.
Localization
Localization을 수행하려면, bounding box를 만들어야 하는데요.
저자들은 CAM에서 bounding box를 만들기 위해서, thresholding 하는 방법을 사용했습니다.
CAM의 max value의 20%보다 높은 값들을 가지는 구역만 잘라낸 뒤에, 이를 포함할 수 있는 가장 큰 bounding box를 만드는 방법입니다.
Figure 6(a)에는 이 방법을 사용하여 만들어낸 bounding box의 예시들을 볼 수 있습니다.
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from tqdm.auto import tqdm
import torchvision
import torchvision.transforms as transforms
import datetime
import os
from torch.utils.tensorboard import SummaryWriter
import shutil
class BasicBlock(nn.Module):
def __init__(self, in_planes, out_planes, stride, dropRate=0.0):
super(BasicBlock, self).__init__()
self.bn1 = nn.BatchNorm2d(in_planes)
self.relu1 = nn.ReLU(inplace=True)
self.conv1 = nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(out_planes)
self.relu2 = nn.ReLU(inplace=True)
self.conv2 = nn.Conv2d(out_planes, out_planes, kernel_size=3, stride=1,
padding=1, bias=False)
self.droprate = dropRate
self.equalInOut = (in_planes == out_planes)
self.convShortcut = (not self.equalInOut) and nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride,
padding=0, bias=False) or None
def forward(self, x):
if not self.equalInOut:
x = self.relu1(self.bn1(x))
else:
out = self.relu1(self.bn1(x))
out = self.relu2(self.bn2(self.conv1(out if self.equalInOut else x)))
if self.droprate > 0:
out = F.dropout(out, p=self.droprate, training=self.training)
out = self.conv2(out)
return torch.add(x if self.equalInOut else self.convShortcut(x), out)
class NetworkBlock(nn.Module):
def __init__(self, nb_layers, in_planes, out_planes, block, stride, dropRate=0.0):
super(NetworkBlock, self).__init__()
self.layer = self._make_layer(block, in_planes, out_planes, nb_layers, stride, dropRate)
def _make_layer(self, block, in_planes, out_planes, nb_layers, stride, dropRate):
layers = []
for i in range(int(nb_layers)):
layers.append(block(i == 0 and in_planes or out_planes, out_planes, i == 0 and stride or 1, dropRate))
return nn.Sequential(*layers)
def forward(self, x):
return self.layer(x)
class WideResNet(nn.Module):
def __init__(self, depth, num_classes, widen_factor=1, dropRate=0.0):
super(WideResNet, self).__init__()
nChannels = [16, 16*widen_factor, 32*widen_factor, 64*widen_factor]
assert((depth - 4) % 6 == 0) # depth must be 6n + 4
n = (depth - 4) / 6
# 1st conv before any network block
self.conv1 = nn.Conv2d(3, nChannels[0], kernel_size=3, stride=1,
padding=1, bias=False)
# 1st block
self.block1 = NetworkBlock(n, nChannels[0], nChannels[1], BasicBlock, 1, dropRate)
# 2nd block
self.block2 = NetworkBlock(n, nChannels[1], nChannels[2], BasicBlock, 2, dropRate)
# 3rd block
self.block3 = NetworkBlock(n, nChannels[2], nChannels[3], BasicBlock, 2, dropRate)
# global average pooling and classifier
self.bn1 = nn.BatchNorm2d(nChannels[3])
self.relu = nn.ReLU(inplace=True)
self.fc = nn.Linear(nChannels[3], num_classes)
self.nChannels = nChannels[3]
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
elif isinstance(m, nn.BatchNorm2d):
m.weight.data.fill_(1)
m.bias.data.zero_()
elif isinstance(m, nn.Linear):
m.bias.data.zero_()
def forward(self, x):
out = self.conv1(x)
out = self.block1(out)
out = self.block2(out)
out = self.block3(out)
out = self.relu(self.bn1(out))
out = F.avg_pool2d(out, 8, 1, 0)
out = out.view(-1, self.nChannels)
return self.fc(out)
WideResNet이라는 이름으로 WRN를 나타내는 class를 만들어줍니다.
input으로는 depth, class 개수, widen_factor, dropout rate를 받게 됩니다.
Channel의 개수는 16, 16*k, 32*k, 64*k로 구성되며, k는 이전 논문 review에서도 언급되었듯이 widening factor를 나타냅니다.
이를 통해 기존 original residual network보다 더 넓은 형태의 residual network를 구성할 수 있습니다.
depth의 경우는 반드시 6n + 4의 값이 되어야 하는데, 이는 다음 표를 보고 이해해볼 수 있습니다.
먼저, conv1에서 1개, 그리고 conv2, conv3, conv4에서 shortcut connection 형태로 layer가 들어가게 되어 기본적으로 4개의 layer가 있어야 합니다.
그리고 6n이 되는 이유는, conv2에서 3x3 짜리 2개, conv3에서 3x3짜리 2개, conv4에서 3x3짜리 2개로 총 6개가 기본적인 setting이므로, 이 setting보다 몇 배 더 많은 layer를 구성할 것인지를 결정하기 위해 6n가 되게 됩니다.
만약 layer의 총개수가 16이라면, 6*2 + 4가 되며, 이는 conv2 block 내에서의 layer가 4개, conv3 block 내에서의 layer가 4개, conv4 block 내에서의 layer가 4개가 됩니다.
즉, n이라는 것은 conv block 내에서 3x3 conv layer의 개수에 곱해지는 계수라고 생각할 수 있습니다.
위의 Table 1의 structure가 코드로 구현되어 있음을 확인할 수 있는데요.
WRN의 전체적인 흐름부터 살펴보겠습니다.
def forward(self, x):
out = self.conv1(x)
out = self.block1(out)
out = self.block2(out)
out = self.block3(out)
out = self.relu(self.bn1(out))
out = F.avg_pool2d(out, 8, 1, 0)
out = out.view(-1, self.nChannels)
return self.fc(out)
(WideResNet에서 이쪽 부분을 확인해주시면 됩니다!)
먼저 self.conv1은 3x3 conv이며, 16 channel이 output이 되도록 연산이 되게 됩니다.
self.block1, self.block2, self.block3는 위 Table 1에서 conv2, conv3, conv4에 대응됩니다.
self.block에 대해서는 조금 있다가 자세히 살펴보도록 할 예정입니다.
self.block3까지 지난 결과를 BN과 ReLU를 통과시키고, average pooling을 적용해줍니다.
그리고 이를 [batch_size, 64*widen_factor]의 shape로 만들어주고, 이를 마지막 fully connected layer에 연결하여
dataset의 class 각각에 대한 probability를 정답으로 내게 됩니다.
이전에 paper review를 진행할 때, WRN의 contribution 중 하나가 바로 batch normalization, activation, convolutional layer의 순서에 대한 연구를 진행한 것이었고, 따라서 어떤 순서로 연산할지 변경되었다는 얘기를 했었습니다.
그렇다면, 여기서 ResNet과 Wide Residual Network는 어떻게 다른지 한번 살펴보겠습니다.
class ResNet(nn.Module):
def __init__(self, block, num_blocks, num_classes=10):
super(ResNet, self).__init__()
self.in_planes = 64
self.conv1 = nn.Conv2d(3, 64, kernel_size=3,
stride=1, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(64)
self.layer1 = self._make_layer(block, 64, num_blocks[0], stride=1)
self.layer2 = self._make_layer(block, 128, num_blocks[1], stride=2)
self.layer3 = self._make_layer(block, 256, num_blocks[2], stride=2)
self.layer4 = self._make_layer(block, 512, num_blocks[3], stride=2)
self.linear = nn.Linear(512*block.expansion, num_classes)
def _make_layer(self, block, planes, num_blocks, stride):
strides = [stride] + [1]*(num_blocks-1)
layers = []
for stride in strides:
layers.append(block(self.in_planes, planes, stride))
self.in_planes = planes * block.expansion
return nn.Sequential(*layers)
def forward(self, x):
out = F.relu(self.bn1(self.conv1(x)))
out = self.layer1(out)
out = self.layer2(out)
out = self.layer3(out)
out = self.layer4(out)
out = F.avg_pool2d(out, 4)
out = out.view(out.size(0), -1)
out = self.linear(out)
return out
이전 ResNet code review를 할 때 사용했던 코드입니다.
ResNet에서는 먼저 input 데이터를 받아 conv1 -> BN -> relu를 거치고 conv block을 거친 후, average pooling을 하고, 이를 fully connected layer에 연결하는 순서로 이루어집니다.
하지만, Wide residual network는 input 데이터를 받아 conv1을 거치고, conv block을 거친 후, BN -> relu를 거치고 average pooling을 한 후 이를 fully connected layer에 연결하는 순서로 이루어집니다.
전체적인 흐름상 batch normalization과 ReLU의 적용 위치가 바뀌었네요.
그리고 conv block이 ResNet에서는 4개였으나, Wide residual network에서는 3개라는 점도 하나의 차이가 될 수 있겠네요.
conv block 1개당 stride = 2가 적용되므로, 따라서 ResNet은 conv block이 모두 진행되었을 때 resolution이 4x4지만, wide residual network는 resolution이 8x8이 됩니다.
간단한 예시를 들기 위해서, n = 2 / nChannels[0] = 16 / nChannels[1] = 64 / dropRate = 0.3으로 지정한다고 가정하겠습니다.
class NetworkBlock(nn.Module):
def __init__(self, nb_layers, in_planes, out_planes, block, stride, dropRate=0.0):
super(NetworkBlock, self).__init__()
self.layer = self._make_layer(block, in_planes, out_planes, nb_layers, stride, dropRate)
def _make_layer(self, block, in_planes, out_planes, nb_layers, stride, dropRate):
layers = []
for i in range(int(nb_layers)):
layers.append(block(i == 0 and in_planes or out_planes, out_planes, i == 0 and stride or 1, dropRate))
return nn.Sequential(*layers)
def forward(self, x):
return self.layer(x)
따라서, 우리가 만들어낸 것은 NetworkBlock(nb_layers = 2, in_planes = 16, out_planes = 64, block = BasicBlock, stride = 1, dropRate= 0.3)이 됩니다.
이를 통해서 만들어지는 것은 다음과 같습니다.
def _make_layer(self, block = BasicBlock, in_planes=16, out_planes=64, nb_layers=2, stride=1, dropRate=0.3):
layers = []
for i in range(int(nb_layers)):
layers.append(block(i == 0 and in_planes or out_planes, out_planes, i == 0 and stride or 1, dropRate))
return nn.Sequential(*layers)
layer의 개수만큼 for문을 돌게 되고, 이를 통해서 layers = []에 block이 append 되는 구조입니다.
nb_layers = 2이므로, block이 2개 쌓이게 되겠죠?
여기서 조금 생소한 코드가 등장하는데요, A and B or C 형태의 문법이 사용되었습니다.
이는 B if A else C와 똑같은 의미입니다.
즉, BasicBlock(in_planes if i == 0 else out_planes, out_planes, stride if i == 0 else 1, dropRate)가 만들어집니다.
Deep Residual networks는 수천 개의 layer까지 scale up 할 수 있음을 보여주었고, 향상된 성능을 보여준 모델입니다.
하지만, 정확도를 향상하기 위해서는 layer의 수를 많이 늘려야 했고, 매우 깊은 residual network를 학습시키는 것은 감소된 feature를 재사용하는 문제를 가지고 있으며, 이는 학습을 매우 느리게 만든다고 합니다.
이 문제를 해결하기 위해, 본 논문에서 저자들은 residual network의 width는 증가시키고 depth는 감소시키는 새로운 architecture를 기반으로 ResNet block의 architecture에 대한 상세한 실험 연구를 수행합니다.
저자들은 그 결과로 나온 network structure를 wide residual network (WRNs)라고 부르고, 흔하게 사용되는 얇고 매우 깊은 네트워크에 비해서 훨씬 더 우월한 성능을 내는 것을 보여줍니다.
1. Introduction
Convolutional neural network는 지난 몇 년간 층의 수를 점차적으로 늘려왔습니다.
AlexNet에서부터 시작해서, VGG, Inception, Residual network까지 층의 수를 늘리는 것을 통해 image recognition task에서 많은 성능 향상을 가져왔습니다.
하지만, deep neural network를 학습시키는 것은 exploding/vanishing gradient와 degradation을 포함해 여러 가지 어려움을 가지고 있습니다.
Well-designed initialization strategies, better optimizers, skip connections, knowledge transfer and layer-wise training과 같은 다양한 기법들이 deeper neural network를 학습시킬 수 있도록 하기 위해서 제안되어 왔습니다.
가장 최근의 residual network는 다양한 benchmark에서 SOTA를 달성하였으며, 이와 관련된 후속 연구에서는 residual network에서의 activation의 순서에 대해서 연구가 진행되었습니다.
지금까지, residual network의 연구는 주로 ResNet block 내에서의 activation의 순서와 residual network의 depth에 대해서만 이루어졌습니다.
본 논문에서, 저자들은 이 point를 넘어서는 실험적인 연구를 수행하려고 시도했습니다.
본 논문에서의 저자들의 목표는 ResNet blocks의 훨씬 더 풍부한 network architecture를 탐색하고, activations의 순서 외에 다른 몇 가지 측면들이 성능에 어떻게 영향을 미치는지 철저히 검토하는 것입니다.
Width vs depth in residual networks
Residual network의 저자들은 residual network의 depth를 증가시키고 더 적은 parameter를 가지게 하기 위해서 network를 가능한 얇게 만드려고 시도했으며, 심지어 ResNet block을 더욱 얇게 만들기 위해서 bottleneck block을 도입하였습니다.
그러나, 본 논문의 저자들은 매우 깊은 네트워크를 훈련시킬 수 있는 identity mapping을 가진 residual block이 동시에 residual network의 약점임을 주목했다고 합니다.
Gradient가 네트워크를 통해서 흐르기 때문에 residual block weight를 거치도록 강제할 수 있는 것이 없으며, 훈련 중에 어떤 것도 학습하지 않을 수 있으므로 오직 몇 개의 블록만 유용한 표현을 학습하거나, 혹은 많은 블록들은 최종 목표에 적은 기여를 하는 매우 작은 정보만 공유할 수 있다고 합니다.
이 문제는 diminishing feature reuse로 이전 선행연구에서 표현되었다고 하네요.
(제가 생각하기에는, layer가 많다 보면 backpropagation을 통해서 gradient가 layer 각각에 전달되는 과정에서 모든 layer가 모두 유용한 representation을 얻지 못할 수도 있다 라는 얘기를 하는 것 같습니다. 우리 몸으로 따지자면 심장에 가까운 부위들은 산소를 많이 가지고 있는 혈액을 공급받고, 심장에서 먼 부위들은 산소가 적은 혈액을 공급받는 느낌..?)
본 논문의 저자들은 network의 depth를 증가시키는 것과 비교했을 때 ResNet block의 widening이 적절하게 이루어진다면 성능을 향상하는 더욱 효과적인 방법을 제공하는 것임을 증명합니다.
특히, 저자들은 이전 연구에 비해서 상당히 향상되고, 50배 더 적은 layer를 가지며 2배 더 빠른 wider deep residual network를 제시하였는데, 이를 wide residual networks라고 부릅니다.
예를 들어, 본 논문의 저자들이 제시한 wide 16-layer deep network는 1000-layer thin deep network와 유사한 accuracy를 가지며, 몇 배 더 빠르게 학습되면서 비슷한 수의 parameter를 가집니다.
따라서, 이러한 유형의 실험들은 deep residual network의 주요한 힘이 residual block에 있으며, depth의 효과는 추가적임을 나타냅니다.
Use of dropout in ResNet blocks
Dropout은 'Dropout: A simple way to prevent neural networks from overfitting'이라는 논문에서 처음으로 도입되었고, 많은 성공적인 architecture에 채택되었습니다.
이는 대부분 많은 수의 parameter를 가지고 있는 top layer에 적용되었고, 이는 feature coadaptation과 overfitting을 방지하기 위함입니다.
Dropout은 주로 batch normalization에 의해서 대체되었는데, 이는 batch normalization도 regularizer의 역할을 수행하기 때문이며, 실험적으로 batch normalization을 가지는 network가 dropout을 가진 network에 비해서 더 나은 accuracy를 가진다는 것이 보였기 때문입니다.
본 논문에서는, residual block의 widening이 parameter의 수를 증가시키는 결과를 가져오기 때문에, 학습을 규제하고 overfitting을 막는 dropout의 효과를 연구하였다고 합니다.
이전에는, residual network에서의 dropout은 block의 identity part에 투입되었으며 부정적인 효과를 보였습니다.
그 대신에, 본 논문에서는 dropout을 convolutional layers들 사이에 투입시킵니다.
Wide residual network에 대한 실험적인 결과는 dropout이 일관성 있게 성능을 향상한다는 것을 보여주며, 심지어 새로운 SOTA result를 만들어내는 것을 보였습니다.
지금까지의 내용을 요약하자면, 본 논문의 contribution은 다음과 같습니다.
ResNet block structure에 대한 여러가지 중요한 측면을 철저히 조사하는 residual network architecture의 상세한 실험적인 연구를 제시합니다.
Residual network가 상당히 향상된 성능을 낼 수 있도록 만들어주는 ResNet block에 대한 새로운 widened architecture를 제안합니다.
학습이 이루어지는 동안에 network를 적절하게 규제하고 overfitting을 회피하기 위해 deep residual network 내에서 dropout을 활용하는 새로운 방법을 제안합니다.
마지막으로, 새롭게 제안된 ResNet architecture가 여러 가지 dataset에서 향상된 accuracy와 속도를 보여주며 SOTA result를 달성하는 것을 보입니다.
2. Wide residual networks
Identity mapping을 가지는 residual block은 다음과 같은 식으로 표현될 수 있습니다.
Residual network는 연속적으로 쌓여진 residual block으로 구성됩니다.
Residual network는 두 가지 형태의 block으로 구성되는데요.
Basic - 연속된 3x3 convolution으로 구성되며, batch normalization과 ReLU가 앞선 convolution에 적용되는 구조
Bottleneck - 하나의 3x3 convolution은 차원을 감소하고 증가시키는 1x1 convolution layer에 둘러싸여 있는 구조
Original architecture와 비교해서, residual block에서의 batch normalization, activation and convolution의 순서는 conv-BN-ReLU에서 BN-ReLU-conv로 변경합니다.
후자가 더 빠르게 학습되고 더 나은 결과를 가져오기 때문에, 본 논문에서는 original version을 고려하지 않습니다.
Bottleneck block은 layer의 수를 늘리기 위해서 block의 연산량을 감소시키고자 사용되었는데, 본 논문에서 저자들은 widening의 효과를 연구하길 원하는 것이므로 networks를 얇게 만드는 데 사용되는 bottleneck 구조는 고려하지 않습니다.
Residual block의 representational power를 증가시키는 3가지 근본적인 간단한 방법이 있습니다.
Block 당 더 많은 convolutional layer를 추가하는 방법
더 많은 feature planes를 추가하여 convolutional layer를 넓히는 방법
Convolutional layer의 filter size를 증가시키는 방법
여러 연구에서 작은 filter가 매우 효과적이라는 것이 검증되었기 때문에, 3x3보다 더 큰 filter는 고려하지 않습니다.
또한 저자들은 추가적으로 deepening factor $l$와 widening factor $k$라는 2개의 요소를 도입하는데, $l$는 block에서의 convolution의 개수이고 $k$는 convolutional layer에서의 feature의 수에 곱해지는 값입니다.
따라서, baseline basic block은 $l = 2, k = 1$에 대응됩니다.
Figure 1(a)와 1(c)는 각각 basic block과 basic-wide block을 나타냅니다.
본 논문에서 사용하는 residual network의 일반적인 구조는 table 1에 나와있습니다.
처음에 convolutional layer conv1 다음에 3개의 group (각 사이즈는 $N$)의 residual blocks conv2, conv3, conv4 다음에 average pooling, final classification layer가 뒤따르는 구조입니다.
conv1의 사이즈는 모든 실험에서 고정이며, 반면에 새롭게 도입된 widening factor $k$는 3개의 그룹 conv2-conv4에서의 residual block의 width를 변경합니다.
본 논문의 저자들은 residual block의 representational power의 효과를 연구하길 원하며, 이를 위해 다음 subsection에서 자세히 설명되어 있는 기본 architecture에 대한 몇 가지 수정을 수행하고 테스트를 진행합니다.
2.1 Type of convolutions in residual block
$B(M)$은 residual block structure를 나타내며, $M$은 block 내부에 있는 convolutional layers의 kernel size list를 나타냅니다.
예를 들어, $B(3, 1)$은 3x3 convolutional layer와 1x1 convolutional layer를 가지고 있는 residual block을 나타냅니다.
이전에 언급했듯이, 본 논문에서는 bottleneck block을 고려하지 않으므로 block마다의 feature planes의 수는 항상 똑같이 유지됩니다.
본 논문의 저자들은 basic residual architecture에서 3x3 convolutional layer 각각이 얼마나 중요한지에 대한 질문에 답을 하고 싶었고, 이것이 1x1 layer나 1x1과 3x3 convolutional layer의 조합으로 대체될 수 있는지가 궁금했습니다.
따라서, 다음과 같은 조합들을 실험하였습니다.
2.2 Number of convolutional layers per residual block
본 논문의 저자들은 또한 block deepening factor $l$이 성능에 어떻게 영향을 미치는지 보기 위해 실험을 진행하였습니다.
비교는 같은 수의 파라미터를 가지는 네트워크들 사이에 이루어졌으며, 따라서 이 경우에 저자들은 network complexity를 거의 동일하게 유지하면서 동시에 다른 수의 $l$과 $d$를 가지는 네트워크를 만들었습니다. ($d$는 block의 전체 수)
예를 들어서, $l$이 증가할 때, $d$는 감소해야 함을 의미합니다.
2.3 Width of residual blocks
저자들은 block의 widening factor $k$에 대해서도 실험을 진행했습니다.
파라미터의 수는 $l$ (deepening factor)와 $d$(ResNet block의 수)에 선형적으로 증가하지만, 파라미터의 수와 computational complexity는 $k$에 대해서는 2차로 증가합니다.
하지만 GPU가 large tensor의 병렬 계산에서 훨씬 더 효율적이기 때문에 수천 개의 small kernel을 가지는 것보다 layer를 넓히는 것이 계산적으로 더 효과적이므로, 저자들은 최적의 $d$ to $k$ ratio에 관심을 가졌습니다.
더 넓은 residual network에 대한 하나의 주장은, 가장 성공적이었던 Inception, VGG를 포함한 residual network 이전의 모든 architecture들은 residual network에 비해서 훨씬 더 넓었다는 것입니다.
예를 들어, residual network WRN-22-8과 WRN-16-10(해당 notation에 대한 설명은 다음 paragraph에서 나옵니다.)은 VGG architecture와 parameter의 수, width, depth에서 매우 유사합니다.
저자들은 더 나아가 $k=1$을 가지는 original residual network를 <<thin>>으로 지칭하고, $k>1$인 네트워크는 <<wide>>로 지칭합니다.
본 논문의 나머지 부분에서는, 다음과 같은 notation을 사용합니다.
WRN-$n$-$k$는 convolutional layers의 총개수를 $n$개 가지고 있으며 widening factor $k$를 가지고 있는 residual network를 나타냅니다. (예를 들어, 40개의 layer를 가지고 있고 original보다 2배 더 넓은 경우는 WRN-40-2로 표기)
또한, 이 표기에 block type을 덧붙여서 표현할 수 있습니다. 예를 들어, WRN-40-2-B(3, 3)과 같이 표현이 가능합니다.
2.4 Dropout in residual blocks
layer를 넓히는 것이 parameter의 수를 증가시키기 때문에, 본 논문의 저자들은 regularization의 방법을 연구하고자 하였습니다.
Residual network는 이미 regularization effect를 제공하는 batch normalization을 가지고 있으나, 이는 heavy data augmentation을 요구하고 저자들은 이를 피하고자 하였습니다.
따라서 저자들은 fig. 1(d)에 보여지는 것처럼 dropout layer를 residual block 안에 있는 convolution 사이에 추가하였습니다.
3. Experimental results
Type of convolutions in a block
다른 block types $B$를 가지고 학습된 네트워크에 대한 실험 결과를 보고하는 것부터 시작합니다.
최적의 비율을 찾기 위해서, 저자들은 $k$를 2부터 12까지, depth는 16부터 40까지 실험하였으며, 결과는 Table 4에서 확인할 수 있습니다.
Table 4에서 볼 수 있는 것처럼, 40, 22, 16 layer를 가지는 모든 네트워크는 width가 1부터 12배까지 증가함에 따라서 일관적인 증가를 보여주고 있습니다. (논문에는 이렇게 쓰여있었는데, 보니까 22 layer는 CIFAR-10에서는 감소했고, CIFAR-100에서는 증가했습니다.)
반면에, 동일한 고정된 widening factor $k = 8$ or $k = 10$을 유지할 때, 그리고 depth를 16에서 28까지 다양하게 했을 때 일관적으로 성능 향상이 있었습니다.
하지만, depth가 40까지 증가했을 때 accuracy는 오히려 감소했습니다. (즉, WRN-40-8은 WRN-22-8보다 더 낮은 accuracy를 가집니다.)
본 논문의 저자들은 얇은 residual network와 넓은 residual network를 비교한 추가적인 결과를 Table 5에서 제공합니다.
wide WRN-40-4와 thin ResNet-1001을 비교했을 때, wide WRN이 CIFAR-10과 CIFAR-100에서 모두 더 나은 accuracy를 보여줌을 확인할 수 있습니다.
또한, wide WRN-28-10은 thin ResNet-1001보다 36배 더 적은 layer를 가지고 CIFAR-10에서 0.92%, CIFAR-100에서 3.46% 더 좋은 성능을 냈습니다.
Figure 2에서는 CIFAR-10과 CIFAR-100에서의 training curve를 나타내고 있습니다.
Depth가 regularization effect를 주고, width는 network가 overfitting 되도록 야기한다는 이전의 주장에도 불구하고, 본 논문의 저자들은 ResNet-1001보다 몇 배 더 많은 수의 parameter를 가진 network를 성공적으로 학습했습니다.
일반적으로, 저자들은 CIFAR mean/std preprocessing이 wider and deeper network를 더 좋은 정확도를 가지고 학습하도록 만들어준다는 사실을 발견하였다고 합니다.
지금까지의 실험 내용을 요약하자면,
Widening은 다른 depth를 가지는 residual network 사이에서 일관적으로 성능을 향상한다.
Depth와 width를 모두 증가시키는 것은 parameter의 수가 너무 높아지거나 혹은 더 강력한 regularization이 요구될 때까지는 도움이 된다.
매우 깊은 깊이를 가지는 residual network에서 어떠한 regularization effect가 보이지 않는데, 이는 같은 수의 parameter를 가지는 thin network와 비교했을 때 wide network가 동일하거나 혹은 더 나은 representation을 학습할 수 있기 때문이다. 또한, wide network는 thin network보다 2배 또는 그 이상의 parameter를 성공적으로 학습할 수 있다.
Dropout in residual blocks
본 논문의 저자들은 모든 데이터셋에 대해서 residual block 안에 convolution 사이에 dropout을 삽입한 network를 학습했다.
저자들은 dropout probability value를 결정하기 위해 cross-validation을 사용하였으며, CIFAR에서는 0.3이었다.
Dropout은 WRN-28-10을 가지고 CIFAR-10과 CIFAR-100에 대해서 test error를 감소시켰으며, 다른 ResNet에서도 동일한 성능 향상을 가져왔다.
저자들은 thin network와 wide network 모두에게서 dropout을 사용하였을 때 상당한 성능 향상을 관측할 수 있었다.
전체적으로, batch normalization과 결합한다는 주장에도 불구하고, dropout은 그 자체로 thin network와 wide network에서의 효과적인 regularization 기술이다.
Computational efficiency
Width를 증가시키는 것은 효과적으로 computation을 균형 맞추는 데 더욱 최적의 방식으로 도움을 주며, 따라서 thin network보다 wide netowrk가 몇 배는 더 효율적이다.
저자들은 cudnn v5와 Titan X를 사용해 여러 가지 네트워크에 대해 minibatch size 32를 가지고 forward + backward update time을 측정했으며, 결과는 figure 4와 같다.
CIFAR에서 최고의 성능을 낸 WRN-28-10가 thin ResNet-1001보다 1.6배 더 빠르다.
게다가, wide WRN-40-4는 ResNet-1001와 거의 유사한 accuracy를 가지지만, 8배 더 빠르다.
Implementation details
본 논문의 모든 실험에서 Nesterov momentum을 활용하는 SGD와 cross-entropy loss를 사용하였다.
본 논문에서는 residual network의 width에 대한 연구와 residual architecture에서 dropout의 사용에 대한 연구를 제시하였습니다.
이 연구를 기반으로, 저자들은 여러가지 benchmark dataset에 대해서 SOTA results를 가져오는 wide residual network architecture를 제안합니다.
저자들은 16 layer를 가지는 wide network가 1000-layer deep network에 비해서 상당히 더 좋은 성능을 낸다는 것을 검증하였으며, 따라서 residual network의 주요한 힘은 극단적인 depth에 있는 것이 아니라 residual blocks에 있음을 검증하였습니다.
또한, wide residual network는 학습하는데 몇 배 더 빠르게 학습됩니다.
여기까지 Wide Residual network 논문에 대해서 살펴보았습니다.
기존 ResNet이 depth에 초점을 두었다면, 본 논문은 width에 초점을 두고 연구를 진행했으며 dropout을 추가했고 BN와 ReLU, conv의 architecture 순서에 대한 연구를 통해 순서를 바꾸었다는 점이 가장 핵심이 되는 연구라고 보입니다.
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import torch.backends.cudnn as cudnn
from tqdm.auto import tqdm
import torchvision
import torchvision.transforms as transforms
import datetime
import os
from torch.utils.tensorboard import SummaryWriter
import shutil
self.shortcut = nn.Sequential()은 연결만 해주는 코드로, input을 그대로 연결해주는 역할을 합니다.
if문은 stride가 1이 아니거나 in_planes != self.expansion*planes인 경우(즉, input으로 들어온 차원과 output에 해당하는 차원이 다른 경우)에 해당하게 되는데, 이 때는 1x1 conv를 진행하도록 해서 차원이 일치하도록 만들어줍니다.
만약 input이 32x32x64이고 stride가 2로 설정되어 있는 경우의 shape를 나타낸 그림입니다.
input인 32x32x64이었으나, layer가 거치면서 16x16x128가 되었으므로 input을 그대로 더해줄 수 없게 됩니다.
따라서, 1x1 conv를 이용해서 channel 수를 맞춰주면서 동시에 stride = 2를 적용하여 가로, 세로 길이도 /2 해서 16x16x128를 맞춰주게 되면 shortcut을 더해줄 수 있게 되는 것입니다.
input의 shape와 layer를 통과하고 나서의 shape가 동일한 경우는, 다음과 같이 살펴볼 수 있습니다.
앞에서도 언급했지만, nn.Sequential()은 말 그대로 input 값을 그대로 가지고 있다고 생각하시면 됩니다.
BasicBlock에 대한 설명은 이쯤이면 충분한 것 같아 다음 block으로 넘어가겠습니다.
class Bottleneck(nn.Module):
expansion = 4
def __init__(self, in_planes, planes, stride=1):
super(Bottleneck, self).__init__()
self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=1, bias=False)
self.bn1 = nn.BatchNorm2d(planes)
self.conv2 = nn.Conv2d(planes, planes, kernel_size=3,
stride=stride, padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(planes)
self.conv3 = nn.Conv2d(planes, self.expansion *
planes, kernel_size=1, bias=False)
self.bn3 = nn.BatchNorm2d(self.expansion*planes)
self.shortcut = nn.Sequential()
if stride != 1 or in_planes != self.expansion*planes:
self.shortcut = nn.Sequential(
nn.Conv2d(in_planes, self.expansion*planes,
kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(self.expansion*planes)
)
def forward(self, x):
out = F.relu(self.bn1(self.conv1(x)))
out = F.relu(self.bn2(self.conv2(out)))
out = self.bn3(self.conv3(out))
out += self.shortcut(x)
out = F.relu(out)
return out
다음에 설명할 코드는 Bottleneck 코드입니다.
ResNet의 layer가 50층 이상 넘어갈 경우, 연산량을 줄여주기 위해 기존의 3x3 conv를 두 개 쌓아서 만든 layer를 사용하는 것이 아닌, 1x1 conv - 3x3 conv - 1x1 conv를 이용하는 방식을 사용합니다.
이번에도 if문에 해당하는 경우 어떻게 흘러가는지를 설명드리도록 하겠습니다.
input이 32x32x256인 경우의 상황을 보여주고 있는 그림입니다.
먼저, 1x1 conv을 이용해서 차원을 128차원으로 줄여줍니다.
그러고 나서, 3x3 conv 연산을 진행하고, 1x1 conv 연산을 통해서 다시 512차원으로 늘려주게 됩니다.
input이 32x32x256이었으므로, shortcut은 input을 그대로 전달해주면 안 됩니다.
따라서 Basicblock과 마찬가지로 1x1 conv을 통해서 차원을 맞춰주면서 동시에 stride = 2를 적용해 가로, 세로 사이즈를 /2 해줍니다.
다음으로는 stride가 1인 경우에 대해서 보여드리겠습니다.
stride가 2로 되어서 가로, 세로가 /2이 되는 경우가 아니라면, channel의 수만 중간에 한번 변경하게 됩니다.
이는 3x3 conv를 적용할 때 더 적은 차원의 데이터만 적용하도록 만들어서 연산량을 줄이기 위함입니다.
이 경우에는 input과 layer를 통과한 output의 shape가 같으므로, nn.Sequential()을 이용해서 shortcut을 진행해주면 됩니다.
class ResNet(nn.Module):
def __init__(self, block, num_blocks, num_classes=10):
super(ResNet, self).__init__()
self.in_planes = 64
self.conv1 = nn.Conv2d(3, 64, kernel_size=3,
stride=1, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(64)
self.layer1 = self._make_layer(block, 64, num_blocks[0], stride=1)
self.layer2 = self._make_layer(block, 128, num_blocks[1], stride=2)
self.layer3 = self._make_layer(block, 256, num_blocks[2], stride=2)
self.layer4 = self._make_layer(block, 512, num_blocks[3], stride=2)
self.linear = nn.Linear(512*block.expansion, num_classes)
def _make_layer(self, block, planes, num_blocks, stride):
strides = [stride] + [1]*(num_blocks-1)
layers = []
for stride in strides:
layers.append(block(self.in_planes, planes, stride))
self.in_planes = planes * block.expansion
return nn.Sequential(*layers)
def forward(self, x):
out = F.relu(self.bn1(self.conv1(x)))
out = self.layer1(out)
out = self.layer2(out)
out = self.layer3(out)
out = self.layer4(out)
out = F.avg_pool2d(out, 4)
out = out.view(out.size(0), -1)
out = self.linear(out)
return out
다음으로는 ResNet class입니다.
논문 리뷰할 때 ResNet 전체 architecture 표에서는 맨 처음 conv가 7x7로 되어 있었는데, 논문의 CIFAR-10 실험한 부분을 보면 CIFAR-10의 데이터가 32x32x3 임을 감안해서 architecture가 살짝 변경되었음을 확인할 수 있습니다.
따라서, 해당 코드는 CIFAR-10에 대해 실험을 진행했기 때문에 구조가 살짝 다릅니다. (핵심적인 ResNet 구조는 동일).
ResNet을 만들 때는 block과 num_blocks를 입력으로 받습니다. (def __init__의 parameter를 보시면 됩니다.)
맨 처음에 3x3 conv - BN을 적용해주고, 그 이후에는 _make_layer 함수를 이용해서 block이 포함된 layer들을 만들어줍니다.
여기서 block이란, 앞에서 제가 설명드린 BasicBlock이나 Bottleneck block을 의미합니다. (앞에서 input으로 block을 받는다고 얘기했었죠?)
함수가 생각보다는 복잡하기 때문에, 간단하게 하나 예시를 들어서 하나하나 따라가 보겠습니다.
가장 간단한 형태였던 ResNet18를 예로 들어보도록 하겠습니다.
ResNet18은 Basicblock을 사용하며, 각 layer 마다 block을 2개씩 사용합니다.
따라서, ResNet18을 만든다고 한다면 net = ResNet(BasicBlock, [2, 2, 2, 2])로 만들 수 있습니다.
먼저 input은 32x32x3이 될 것이고요, 첫 번째로 self.conv1 = nn.Conv2d(3, 64, k = 3, s = 1, p = 1)를 통과해줍니다. output은 32x32x64이 됩니다. 여기에 self.bn1 = nn.BatchNorm2d(64)를 통과해줍니다. BN은 shape에 영향을 안 주니 shape는 그대로입니다.
stride가 모두 1이므로, 가로와 세로의 사이즈는 변동되지 않으며, channel 수는 여전히 64가 됩니다.
BasicBlock이 두 개였으므로, 위의 과정을 동일하게 두 번 거쳐서 나오면 ResNet18 기준에서 self.layer1을 통과한 것이라고 보시면 되겠습니다.
다음으로는 self.layer2인데요, 이는 self._make_layer(BasicBlock, 128, 2, stride = 2)를 적용합니다.
_make_layer 함수로 들어가게 되면, strides가 이번에는 [2, 1]가 됩니다. 아까는 [1, 1]이었죠.
그리고 똑같이 for stride in strides:의 반복문을 이용해서 layers.append를 해주는데, BasicBlock(64, 128, 2)와 BasicBlock(128, 128, 1)을 append 해줍니다. 그리고 이 때는 self.in_planes = planes * block.expansion인데 planes가 128이었으므로, self.in_planes가 64에서 128로 값이 변경됩니다.
앞과 동일하게 def _make_layer는 nn.Sequential(*layers)를 return 해주므로, ResNet 기준에서 self.layer2는 nn.Sequential(BasicBlock(64, 128, 2), BasicBlock(128, 128, 1))이 될 것입니다.
다시 BasicBlock 쪽으로 가봐야겠죠?
이번에는 in_planes가 64, planes가 128, stride = 2입니다.
이번에는 BasicBlock의 input이 아까 ResNet 기준 self.layer1에서의 output인 32x32x64가 됩니다.
self.conv1 = nn.Conv2d(64, 128, k=3, s = 2, p= 1) 이므로, 이를 통과해주면 16x16x128이 되고, self.bn1 = nn.BatchNorm2d(128)을 통과해줍니다.
다음으로는 self.conv2 = nn.Conv2d(128, 128, k=3, s= 1, p= 1)를 통과해주고, 이 때는 shape의 변동이 없습니다. self.bn2 = nn.BatchNorm2d(128)도 마찬가지죠.
그리고 아까와 달라지는 점은, stride가 1이 아니므로, BasicBlock의 if문에 들어갑니다. 이 때는 self.shortcut이 달라지게 되는데요.
따라서, 4x4x512에 average pooling을 적용해서 이를 1x1x512로 만들어줍니다.
그리고. view를 이용해서 이를 Batch sizex512로 바꿔주게 되고, 이를 self.linear와 연결합니다. self.linear는 nn.Linear(512, 10)이므로, 이를 통과하면 Batch size x 10가 되겠죠? 이를 최종 예측값으로 사용해줍니다.
이전에는 논문을 한 줄 한 줄 세세하게 정리했었는데, 작업하는데 시간이 매우 오래 걸리고 어떤 것이 핵심인지 저 조차 파악하기가 힘들어져서 분량을 줄이고, 핵심적인 부분을 정리하는 방향으로 바꾸려고 합니다.
시작하겠습니다.
Abstract
깊은 신경망은 학습시키기가 더욱 어렵습니다.
본 논문에서는, 이전에 사용했던 것보다 상당히 더 깊은 네트워크의 학습을 쉽게 만들기 위해서 residual learning framework를 제시합니다.
Layer들이 unreferenced function을 학습하는 것 대신에, layer의 input에 대해서 residual function을 학습하는 것으로 다르게 표현합니다.
본 논문에서는, 이러한 residual network가 최적화하기 쉽고, 증가된 depth로부터 더 좋은 accuracy를 얻을 수 있다는 실증적인 증거를 보였습니다.
ImageNet dataset에 대해서는 residual nets을 152 layers까지 평가하였고, 이는 VGGnet보다 8배 더 깊지만 낮은 complexity를 가집니다.
Residual net의 앙상블을 이용해서 ImageNet test set에서 3.57% error를 달성하였으며, 이는 ILSVRC 2015 classification task에서 1등을 수상하였습니다.
1. Introduction
Deep convolutional neural network는 image classification에서 일련의 돌파구를 이끌었습니다.
Deep network는 자연스럽게 low/mid/high-level feature와 classifier를 end-to-end multi-layer 형식으로 통합하며, feature의 level은 쌓인 layer의 수 (깊이)에 의해 풍부해질 수 있습니다.
최근, network 깊이가 중요하다는 사실이 드러났으며, ImageNet dataset에 대해서 좋은 결과를 낸 경우는 모두 매우 깊은 모델을 사용하였습니다.
깊이와 관련해서, 다음과 같은 질문이 존재합니다.
"더 많은 레이어를 쌓는 것만큼 더 나은 네트워크를 학습하는 것이 쉬운가?"
이와 관련된 문제는 vanishing/exploding gradient의 문제이며, 이는 초기부터 모델의 수렴을 방해합니다.
하지만, 이 문제는 주로 normalized initialization이나, 중간 normalization layer에 의해서 해결되어 왔으며 이를 통해 수십 개의 layer로 구성된 network가 backpropagation을 가지고 SGD를 이용해 수렴할 수 있도록 할 수 있었습니다.
깊은 network가 수렴하기 시작할 수 있을 때, degradation 문제가 제기되었습니다.
Degradation이란, network의 깊이가 증가할수록 accuracy가 saturate 되며, 빠르게 저하되는 것을 의미합니다.
이러한 degradation은 overfitting에 의해서 일어나는 것이 아니며, 더 많은 레이어를 추가했을 때 더 높은 training error를 야기합니다.
Fig.1는 degradation의 전형적인 예시를 보여주는 그래프입니다.
본 논문에서는 deep residual learning framework를 도입하여 degradation problem을 해결합니다.
각 layer들이 직접적으로 바람직한 underlying mapping에 fitting 되도록 바라는 것 대신에, residual mapping에 fitting 되도록 하는 것입니다.
원래의 mapping을 표현하자면 $\mathcal{F}(x) + x$로 나타낼 수 있습니다.
본 논문에서는 원래 unreferenced mapping을 최적화하는 것보다 residual mapping을 최적화하는 것이 쉬울 것이라는 가설을 세웠습니다.
극단적으로, 만약 identity mapping이 최적이라면, layer들이 identity mapping을 fitting 하는 것보다 residual을 0으로 만드는 것이 쉬울 것입니다.
$\mathcal{F}(x) + x$의 formulation은 "shortcut connections"를 가진 feedforward neural network에 의해서 나타내질 수 있습니다.
Shortcut connections은 1개 혹은 더 많은 수의 layer를 skipping 하는 것입니다.
본 논문의 경우에서는, shortcut connection은 단순히 identity mapping을 수행하게 됩니다.
따라서, 별도의 parameter나 computational complexity를 추가하지 않습니다.
전체 네트워크는 여전히 SGD를 가지고 backpropagation에 의해서 end-to-end로 학습될 수 있습니다.
본 논문에서는 degradation problem을 제시하고 residual network를 평가하기 위해서 ImageNet에서 광범위한 실험을 제시합니다.
1) 극단적으로 깊은 residual net이 최적화하기 쉬우며, 반면에 counterpart인 "plain" net (단순히 레이어를 쌓은 경우)는 깊이가 증가했을 때 더 높은 training error를 나타냄을 보입니다.
2) Deep residual network는 깊이를 증가할수록 더 높은 accuracy를 얻을 수 있음을 보입니다.
유사한 현상이 CIFAR-10 dataset에서도 나타나며, 최적화의 어려움과 residual net의 효과가 단순히 특정한 데이터셋에만 적용되는 것이 아님을 추가적으로 보입니다.
2. Related Work
생략
3. Deep Residual Learning
3.1 Residual Learning
$\mathcal{H}(x)$를 몇 개의 stacked layers (필수적으로 전체 네트워크여야 하는 것은 아닙니다.)에 의해 fitting 되어야 하는 underlying mapping이라고 하고, $x$는 이러한 layer의 input을 나타냅니다.
만약 multiple nonlinear layers가 점근적으로 복잡한 함수들을 근사할 수 있다고 가설을 세운다면, 이는 점근적으로 $\mathcal{H}(x) -x$ (이때, input과 output은 동일한 차원을 가짐을 가정합니다.)와 같은 residual function도 근사할 수 있다는 것과 동일합니다.
본 논문에서 제시하는 모델에서는, 모든 stacked layer에 residual learning을 채택합니다.
Building block은 Fig.2에서 제시되었으며, 공식적으로 다음과 같이 정의할 수 있습니다.
$x$와 $y$는 고려된 layer의 input과 output vector입니다. 함수 $\mathcal{F}(x, {W_i})$는 학습되어야 할 residual mapping을 나타냅니다.
예를 들어, two layer를 가지는 Fig. 2에서 $\mathcal{F} = W_2\sigma(W_1x)$이고, $\sigma$는 ReLU를 나타내며 notation의 단순화를 위해서 bias는 생략하였습니다.
Operation $\mathcal{F} + x$는 shortcut connection과 element-wise addition에 의해서 수행되며, addition 후에 두 번째 nonlinear function을 적용합니다.
Eqn. (1)에 있는 shortcut connections은 추가적인 parameter나 computation complexity를 도입하지 않습니다.
이는 실제로 구현할 때 매력적일 뿐 아니라, plain과 residual network 사이의 비교에 있어서도 중요합니다.
$x$와 $\mathcal{F}$의 차원은 Eqn. (1)에서 동일해야 하며, 그렇지 않은 경우에는 차원을 맞추기 위해 shortcut connection에 의한 linear project $W_s$를 수행할 수 있습니다.
물론, Eqn.(1)에서도 square matrix $W_s$를 사용할 수 있지만, 실험을 통해서 identity mapping이 degradation problem을 해결하는데 충분하고 경제적임을 확인하여서 $W_s$는 오직 차원을 맞출 때만 사용됩니다.
Residual function $\mathcal{F}$의 형태는 유연합니다.
본 논문에서 실험을 할 때는 two or three layer를 포함하는 function $\mathcal{F}$를 사용했지만, 더 많은 layer도 가능합니다.
하지만, 만약 $\mathcal{F}$가 single layer라면, Eqn.(1)는 linear layer와 유사해집니다. ($y = W_1x + x$). 이런 경우, 어떤 이점도 가지지 못하게 됩니다.
notation을 단순하게 하기 위해서 fully-connected로 나타냈지만, 이는 convolutional layer에도 적용이 가능합니다.
함수 $\mathcal{F}(x, {W_i})$는 multiple convolutional layer를 나타낼 수 있으며, element-wise addition은 channel by channel로 두 feature map에 수행됩니다.
3.3 Network Architectures
저자들은 다양한 plain / residual net을 테스트했고, 일관적인 현상을 발견했다고 합니다. 논의를 위한 예시를 제공하고자, ImageNet에 대해 두 모델을 나타냅니다.
Plain Network. plain baselines (Fig. 3, middle)은 주로 VGGnet의 철학에 의해 영감을 받아서 만들어졌습니다. (Fig. 3, left)
convolutional layer는 대부분 3x3 filter를 가지고 있으며 두 가지 간단한 규칙을 따릅니다.
(i) 같은 output feature map size에 대해서, layer는 같은 수의 filter를 가진다.
(ii) 만약 feature map size가 절반이 되면, filter의 사이즈는 두 배로 해서 layer 당 time complexity를 보존합니다.
해당 network에서는 stride를 2로 설정한 convolutional layer에 의해서 직접적으로 downsampling이 수행됩니다.
네트워크는 global average pooling layer로 끝나고, 1000-way fully-connected layer with softmax로 끝납니다.
전체 가중치가 있는 layer는 34개이며, 해당 모델은 VGGnet에 비해서 더 적은 filter와 더 낮은 복잡도를 가집니다.
Residual Network. 위의 Plain network에 기반해서, 이 network를 residual version으로 바꿔주는 shortcut connection을 추가합니다.
Identity shortcuts은 input과 output이 같은 차원을 가질 때 직접적으로 사용될 수 있습니다. (Fig. 3에서 실선)
차원이 증가할 때, (Fig. 3에 있는 점선) 두 가지 선택을 고려해볼 수 있습니다.
(A) shortcut은 여전히 identity mapping을 수행하나, 차원을 증가시키기 위해 zero padding을 해준다.
이 선택은 추가적인 parameter를 도입하지 않습니다.
(B) Eqn.(2)에 나타난 projection shortcut을 사용하여 차원을 맞춘다. (1x1 conv을 이용하게 됩니다.)
3.4 Implementation
해당 파트는 실험 관련된 부분이므로, 필요하다면 논문을 직접 읽어보시면 됩니다.
ResNet model architectures
위의 그림은, ResNet에서 사용한 구조를 모두 정리한 표입니다.
4. Experiments
(실험 파트에서는, 논문을 읽으면서 읽어볼 필요가 있다고 판단되는 검증 내용들을 위주로 정리하였습니다.)
4.1 ImageNet Classification
Plain Networks. 첫 번째로, 저자들은 18-layer와 34-layer plain nets을 평가하였습니다.
34-layer plain net은 Fig. 3 (middle)에 나와있으며, 18-layer plain net도 유사한 형태를 가진다고 합니다.
상세한 구조는 위에 올려놓은 Table 1을 참고해주시면 됩니다.
Table 2에 나온 결과를 살펴보면, 34-layer plain net이 18-layer plain net에 비해서 더 높은 validation error를 가진다는 것을 확인할 수 있습니다.
원인을 파악하기 위해, Fig. 4 (left)에서 저자들은 학습 동안의 training/validation error를 비교하였습니다.
여기서, 앞에서 언급한 "degradation problem"을 관측할 수 있었습니다.
즉, 18-layer plain network의 solution space가 34-layer plain network의 solution space의 부분집합임에도 불구하고 34-layer plain network가 더 높은 training error를 나타낸다는 것이죠.
저자들은 이러한 최적화의 어려움이 vanishing gradient에 의해서 야기되었다고 생각하지 않습니다.
plain network는 Batch Normalization에 의해서 학습되었으며, backward propagated gradient가 healthy norm을 가진다는 것을 검증하였기 때문이죠.
baselines architectures는 plain net과 동일하고, Fig. 3 (right)에 나온 것처럼 각 3x3 filter 쌍에 shortcut connection만 추가하였습니다.
첫 번째 비교에서 (Table 2, Fig 4 right), 모든 shortcut에 identity mapping을 사용하였고, 차원을 증가할 때는 zero-padding을 사용하였습니다. (option A).
따라서, plain network와 비교했을 때 추가적인 parameter가 필요하지 않습니다.
Table 2와 Fig. 4를 보고, 세 가지 주요한 관측을 해볼 수 있습니다.
첫 번째, 34-layer ResNet이 18-layer ResNet보다 더 낫습니다.
34-layer ResNet은 상당히 더 낮은 trainig error를 나타내고 있고, validation data에 더 일반화 가능합니다.
이는 해당 setting에서 degradation problem이 잘 해결되었음을 나타내고, 이를 통해 증가된 depth로부터 accuracy의 증가를 얻을 수 있게 됩니다.
두 번째, plain network와 비교했을 때, 34-layer가 top-1 error에서 3.5% 정도 감소하였으며 (Table 2), 성공적으로 training error를 감소시켰습니다. (Fig.4 right vs left)
이를 통해 매우 깊은 system에서의 residual learning의 효과성을 검증하였습니다.
마지막으로, 18-layer plain/residual net이 상당히 정확하지만, 18-layer ResNet이 더 빠르게 수렴합니다. (Fig. 4 right vs left)
ResNet은 초기 단계에서 더 빠르게 수렴하도록 만들어 최적화를 더 쉽게 만듭니다.
Identity vs Projection Shortcuts. 저자들은 parameter-free인 identity shortcuts이 학습을 도와준다는 사실을 보였습니다.
다음으로는, projection shortcuts(Eqn.(2))에 대해서 알아봅니다.
Table 3에서 3가지 option을 비교합니다.
(A) 차원을 늘릴 때, zero-padding shortcut을 사용하고, 모든 shortcut은 parameter-free인 경우
(B) 차원을 늘릴 때, projection shortcuts을 사용하고, 다른 shortcut은 identity인 경우
(C) 모든 shortcut이 projection인 경우
Table 3은 모든 3가지 option이 plain인 경우보다 상당히 더 좋음을 나타내고 있습니다.
A < B < C인데, A/B/C 간의 차이가 크지 않기 때문에, 이는 degradation problem을 해결하는 데 있어서 projection shortcut이 필수적이지 않음을 나타냅니다.
따라서, 저자들은 memory/time complexity와 model size를 줄이기 위해서 논문의 나머지 부분에서 option C는 사용하지 않았다고 합니다.
Deeper Bottleneck Architectures. 다음으로는 ImageNet에서 사용한 deeper net에 대해서 알아봅니다.
저자들이 사용할 수 있는 training time에 대한 우려 때문에, building block을 bottleneck design으로 수정했다고 합니다.
각 residual function $\mathcal{F}$에 대해서, 2개의 layer 대신 3개의 layer를 쌓은 형태를 사용하였습니다.
3개의 layer는 1x1, 3x3, 1x1 convolution이며, 1x1 layer는 차원을 줄이거나 늘리는 역할을 하고, 3x3 layer bottleneck은 줄어든 dimension에 대해 연산을 진행하게 됩니다.
Fig. 5는 하나의 예시를 보여주고 있고, 두 디자인은 동일한 time complexity를 가진다고 합니다.
왼쪽은 64차원인 경우이고, 오른쪽은 256차원인 경우 이므로, bottleneck design을 통해서 complexity를 줄인 것임을 확인할 수 있습니다.
parameter-free identity shortcut은 bottleneck architecture에서 특히 중요한데, 만약 Fig. 5 (right)에 있는 identity shortcut을 projection으로 교체한다면, time complexity와 model size가 두배가 되기 때문입니다. 이는 shortcut이 두 개의 고차원끼리 연결되어 있기 때문입니다.
따라서, identity shortcut은 bottleneck design을 위해서 더욱 효율적인 모델을 만들어줍니다.
50-layer ResNet: 저자들은 34-layer에 있던 2-layer block을 3-layer bottleneck block으로 교체하였고, 이를 통해 50-layer ResNet가 만들어지게 되었습니다.
저자들은 차원을 증가시킬 때 option B을 사용하였습니다.
101-layer and 152-layer ResNets: 저자들은 101-layer와 152-layer인 ResNet을 3-layer block을 사용해서 만들었습니다.
비록 depth는 상당히 증가했지만, 152-layer ResNet은 여전히 Vgg-16/19보다 더 낮은 complexity를 가집니다.
여기까지 ResNet 논문을 정리해 보았습니다.
아주 간단한 아이디어를 통해, degradation problem을 해결한 논문으로 매우 유명한 논문이죠.
이는 종종 밀도의 parametric family $(P_\theta)_{\theta \in R^d}$을 정의함으로써 이루어지며, 우리 데이터에 대한 likelihood를 최대화하는 parameter를 찾게 된다.
(제 생각에는 이 $P_\theta$라는 것은 어떤 분포의 parameter 일 수도 있고, 아니면 neural network의 가중치가 될 수도 있는 것 같습니다. 정규분포라고 한다면 평균과 표준편차가 parameter가 되겠죠. 그래서 parametric family라는 용어를 사용한 것이 아닐까라는 생각이 듭니다.)
즉, 만약 우리가 실제 데이터 $\left\{x^{(i)}\right\}^m_{i=1}$를 가지고 있을 때, 우리는 다음 문제를 해결하는 것이다.
만약 실제 데이터 분포 $\mathcal{P}_r$가 밀도를 나타내고, $\mathcal{P}_\theta$가 parametrized density $P_\theta$의 분포라면, 점근적으로, 이 양은 KL divergence $KL(\mathcal{P}_r \parallel \mathcal{P}_\theta)$를 최소화한다. (즉, 실제 데이터 분포와 parameter로 나타내지는 밀도 사이의 거리를 최소화해서 parameter로 실제 데이터 분포에 가깝게 만들어보자는 의미입니다.)
이것이 타당하기 위해서, 우리는 model density $P_\theta$가 존재할 필요가 있다.
하지만, 이는 저차원 manifold에 의해서 지지를 받는 분포를 다루는 일반적인 상황에서는 그렇지 않다.
model manifold와 실제 분포의 support가 무시할 수 없는 교차점을 가질 가능성은 거의 없으며, 이는 KL distance가 정의되지 않는다는 것을 의미한다. (혹은 단순히 무한하다.)
(support라는 개념은 한국어로는 지지집합이라는 개념인데, 어떤 함수가 존재할 때 함숫값이 0이 되는 정의역의 집합을 의미합니다. 예를 들어서, 함수가 f(x) = x + 1이라면, 지지 집합은 x = -1이 될 것입니다.)
전형적인 해결책은 model distribution에 noise term을 추가하는 것이다.
이것은 고전적인 머신러닝 문헌에서 묘사되는 사실상 모든 생성 모델들이 noise component를 포함하는 이유이다.
가장 간단한 경우에, 모든 예시들을 커버하기 위해 상대적으로 높은 대역폭을 가지는 Gaussian noise를 가정한다.
예를 들어, 이미지 생성 모델의 경우에, 이 noise는 샘플의 품질을 저하시키고 뿌옇게 만든다는 사실은 잘 알려져 있다.
예를 들어, 최근 논문 [23]에서 likelihood를 최대화할 때 모델에 더해지는 noise의 최적의 standard deviation은 생성된 이미지에서 각 픽셀에 약 0.1이며, 이때 픽셀은 이미 정규화되어 [0, 1] 범위 안에 들어간 상태이다.
이는 매우 높은 양의 noise이며, 따라서 논문에서 모델의 샘플을 나타낼 때, 그들은 likelihood number를 보고하는 noise term을 추가하지 않는다.
즉, 추가된 noise term은 문제에 대해서 명백하게 부정확하지만, maximum likelihood approach가 작동하도록 하기 위해 필요하다.
아마도 존재하지 않는 $\mathcal{P}_r$의 밀도를 추정하는 것 대신에, fixed distribution $p(z)$를 가지는 random variable $Z$를 정의할 수 있으며 이를 어떤 분포 $\mathcal{P}_\theta$를 따르는 샘플을 직접적으로 만드는 parametric function $g_\theta : Z \rightarrow X$ (전형적으로 어떤 종류의 신경망)에 통과시킨다.
$\theta$를 다르게 하면서, 우리는 이 분포를 변화시킬 수 있고 실제 데이터 분포 $\mathcal{P}_r$에 가깝게 만들 수 있다.
이는 두 가지 측면에서 유용하다.
첫 번째로, 밀도와는 다르게, 이 접근법은 저차원의 manifold에 국한된 분포를 표현할 수 있다.
두 번째로, 쉽게 샘플을 생성할 수 있는 능력은 밀도의 값을 아는 것보다 더욱 유용하다. (예를 들어, 이미지 super-resolution이나 semantic segmentation에서 input image가 주어졌을 때의 output image의 조건부 분포를 고려할 때)
일반적으로, 임의의 고차원 밀도가 주어졌을 때 샘플을 생성하는 것은 연산적으로 어렵다.
Variational Auto-Encoders (VAEs)와 Generative Adversarial Networks (GANs)는 이러한 접근법으로 잘 알려져 있다.
왜냐하면 VAE는 examples의 approximate likelihood에 초점을 두기 때문에, 표준 모델의 한계점을 공유하며 추가적인 noise terms을 조작할 필요가 있다.
GANs는 목적 함수의 정의에서 훨씬 더 많은 융통성을 제공하며, Jensen-Shannon과 모든 f-divergence, exotic combinations를 포함한다.
반면에, GANs를 학습시키는 것은 [1]에서 이론적으로 연구된 이유로 인해 까다롭고 불안정한 것으로 잘 알려져 있다.
본 논문에서는 거리나 분산 $\rho(\mathcal{P}_\theta, \mathcal{P}_r)$를 정의하는 다양한 방법에 대해 모델 분포와 실제 분포가 얼마나 가까운지를 측정하는 다양한 방법에 관심을 가진다.
이러한 distance 사이의 가장 근본적인 차이는 확률 분포의 sequence의 convergence에 미치는 영향이다.
분포의 sequence $(\mathcal{P}_t)_{t \in N}$는 $\rho(\mathcal{P}_t, \mathcal{P}_\infty)$가 0이 되는 경향이 있는 분포 $\mathcal{P}_\infty$가 존재할 때 수렴하게 되며 이는 distance $\rho$가 얼마나 정확히 정의되는지에 달려있다.
비공식적으로, distance $\rho$는 이것이 distribution의 sequence가 수렴하기 더 쉽도록 만들 때 weaker topology를 유발한다.
Section 2는 인기있는 확률 거리가 이 점에서 얼마나 다른지 명확히 한다.
Parameter $\theta$를 최적화하기 위해서, 물론 mapping $\theta \mapsto \mathcal{P}_\theta$를 연속적으로 만드는 방식으로 우리의 모델 분포 $\mathcal{P}_\theta$를 정의하는 것이 바람직하다.
연속성은 parameter의 sequence인 $\theta_t$가 $\theta$에 수렴할 때, distributions $\mathcal{P}_{\theta_t}$ 또한 $\mathcal{P}_\theta$에 수렴한다는 것을 의미한다.
하지만, distributions $\mathcal{P}_{\theta_t}$의 수렴이라는 개념은 distribution 간 거리를 계산하는 방법에 의존한다는 사실을 기억하는 것이 필수적이다.
이 거리가 약할수록, $\theta$-space에서 $\mathcal{P}_\theta$-space로의 continuous mapping을 정의하는 것이 쉬워지게 되는데, 이는 distribution이 수렴하기가 쉽기 때문이다.
Mapping $\theta \mapsto \mathcal{P}_\theta$를 continuous가 되도록 신경을 쓰는 주요한 이유는 다음과 같다.
만약 $\rho$가 두 분포 간 우리의 거리 개념이라면, 우리는 continuous loss function $\theta \mapsto \rho(\mathcal{P}_\theta, \mathcal{P}_r)$를 가지고 싶을 것이며, 이는 분포 간 거리인 $\rho$를 사용했을 때 mapping $\theta \mapsto \mathcal{P}_\theta$가 continuous가 되도록 하는 것과 동일하다.
본 논문의 contribution은 다음과 같다.
Section 2에서는, 분포를 학습하는 관점에서 주로 사용되는 probability distance와 divergence를 비교하여 Earth Mover (EM) distance가 어떻게 작용하는지에 대한 포괄적인 이론적 분석을 제공한다.
Section 3에서는, EM distance의 효율적이고 합리적인 approximation을 최소화하는 Wasserstein-GAN이라고 불리는 형태의 GAN을 정의하고, 대응되는 최적화 문제가 타당하다는 것을 이론적으로 보인다.
Section 4에서는, GAN의 주요한 학습 문제를 WGANs이 경험적으로 해결한다는 것을 보인다. 실제로 WGANs을 학습하는 것은 discriminator와 generator 사이의 조심스러운 균형을 유지하는 것이 요구되지 않으며 network architecture의 조심스러운 설계 또한 요구되지 않는다. GANs에서 주로 발생하는 mode dropping phenomenon 또한 매우 줄어든다. WGANs의 가장 주목할만한 실질적 이득은 discriminator를 학습시킴으로써 EM distance를 최적성까지 끊임없이 추정할 수 있는 능력이다. 이러한 학습 curve를 그리는 것은 hyperparameter search와 디버깅에 유용할 뿐만 아니라 관측되는 샘플 품질과 현저하게 상관관계가 있다.
2. Different Distances
우리는 이제 우리의 notation을 도입한다.
$\mathcal{X}$를 compact metric set (이미지 $[0, 1]^d$의 공간과 같은)이라 하고, $\sum$은 $\mathcal{x}$의 모든 Borel subset의 집합을 나타낸다.
(수학적인 용어들이라 내용이 좀 어려운데... 일단 compact라는 용어부터 정리해보겠습니다.
compact라는 의미는 경계가 있고 (bounded) 동시에 경계를 포함 (closed)하는 집합을 의미합니다. 적절한 비유를 하자면, '서울시' 라고 하면 서울시의 시 영토가 있을 것이고, 지도상에서 어떤 경계가 존재하는 것을 생각해보시면 될 것 같습니다.
metric은 distance라는 용어로도 불리는 개념으로, 거리함수가 존재하는 것을 의미합니다. 예를 들면 유클리디안 거리가 여기에 해당합니다.
Borel 집합은 X 내에서 측정 가능한 집합들을 말합니다. 즉, 확률분포로 확률 값이 계산될 수 있는 집합을 의미합니다. 연속함수의 기댓값을 계산하기 위한 수학적인 최소 조건이라고 합니다.)
$Prob(\mathcal{X})$은 $\mathcal{X}$에 정의된 probability measures의 공간을 의미한다. (probability measures는 확률 분포랑 동일한 용어입니다.)
우리는 이제 두 분포 $\mathcal{P}_r, \mathcal{P}_\theta \in Prob(\mathcal{X})$ 사이의 거리와 divergence에 대해서 정의할 수 있다.
Total Variation (TV) distance
Kullback-Leibler (KL) divergence
$\mathcal{P}_r$와 $\mathcal{P}_g$는 완전히 연속이라고 가정되며, 그러므로 $\mathcal{X}$에 정의된 동일한 measure $\mu$에 대해서 밀도를 인정한다.
KL divergence는 유명하게 비대칭이며 $P_g(x) =0, P_r(x) > 0$인 point가 존재할 때 무한이다.
Jensen-Shannon (JS) divergence
$\mathcal{P}_m$은 $(\mathcal{P}_r + \mathcal{P}_g)/2$이다. 이 divergence는 대칭이며 우리가 $\mu = P_m$으로 선택할 수 있기 때문에 항상 정의된다.
Earth-Mover (EM) distance or Wasserstein-1
$\prod(\mathcal{P}_r, \mathcal{P}_g)$는 주변 확률 분포가 각각 $\mathcal{P}_r$와 $\mathcal{P}_g$인 모든 결합 분포 $\gamma(x, y)$의 집합을 나타낸다.
직관적으로, $\gamma(x, y)$는 분포 $\mathcal{P}_r$를 분포 $\mathcal{P}_g$로 변형시키기 위해서 얼마나 많은 "질량"을 $x$에서 $y$로 운송해야 하는지를 나타낸다. EM distance는 최적 운송 계획의 cost이다.
다음 예시는 확률 분포의 간단한 sequence가 EM distance 하에서는 얼마나 명백하게 수렴하고 다른 distance와 divergence 하에서는 수렴하지 않는지를 나타낸다.
Example 1 (수평선을 학습하는 경우). $Z \sim U[0, 1]$는 unit interval 하에서의 균일 분포이다. $P_0$는 $(0, Z) \in R^2$ (0은 x 축이고 random variable $Z$는 y축)의 분포라고 가정하고, 원점을 통과하는 직선의 수직선이다.
$g_\theta(z) = (\theta, z)$라고 하고 $\theta$는 single real parameter이다.
$\theta_t -> 0$일 때, sequence ($\mathcal{P}_{\theta_t})_{t \in N}$ 는 EM distance 하에서 $\mathcal{P}_0$으로 수렴하지만, JS, KL, reverse KL, TV divergence 하에서는 수렴하지 않는다. Figure 1는 EM과 JS distance의 경우에 대해 이를 나타낸다.
Figure 1을 살펴보면, JS divergence의 경우 대부분의 케이스에서 값이 같으므로, 적절한 gradient 값을 얻기가 어렵습니다. 반면에, EM distance의 경우 대부분의 케이스에서 유의미한 gradient 값을 얻을 수 있게 되죠.
Example 1은 EM distance에 대해서 gradient descent를 진행함으로써 저차원의 manifold에 대한 확률 분포를 학습할 수 있는 케이스를 우리에게 제공한다. 이는 다른 distance나 divergence를 가지고는 될 수 없는데, 이는 resulting loss function이 연속적이지 않기 때문이다.
Wasserstein distance가 JS distance에 비해서 훨씬 약하기 때문에, 가벼운 가정 하에서도 $W(\mathcal{P}_r, \mathcal{P}_\theta)$가 $\theta$에 대한 연속형 loss function인지 아닌지를 물어볼 수 있다. 이는 진실이며, 이제 이를 증명한다.
2번에 나와있는 regularity assumption 1도 가져와 보겠습니다.
사실 모든 내용을 정확하게 이해하기란 어렵지만, 일단 제가 이해했던 내용을 조금 설명해보자면...
먼저 Assumption 1.에서 'locally Lipschitz'라고 하는 내용이 있습니다. 이것에 대해서 먼저 알아보겠습니다.
Intuitively, a Lipschitzcontinuous functionis limited in how fast it can change: there exists a real number such that, for every pair of points on the graph of this function, theabsolute valueof the slope of the line connecting them is not greater than this real number; the smallest such bound is called theLipschitz constantof the function.
Lipschitz function이라는 것은 얼마나 빨리 변화할지가 제한된 함수라는 얘깁니다. 2차원에서 생각해보면, 임의의 두 점을 이었을 때 이 직선의 기울기가 바로 변화율이 되죠? 이것이 제한된다는 얘기죠.
그리고 그 bound의 가장 작은 값이 Lipschitz constant라는 것이고요.
즉 어떤 함수가 Lipschitz constant라는 값보다는 항상 변화율이 작아야 한다는 그런 말이 됩니다.
Assumption 1은 이 Lipschitz constant의 기댓값이 무한대보다 작은 경우 g라는 함수가 다음 가정을 만족한다고 적혀있네요.
변화율을 어느 특정 값보다 작게 한다는 것은... 아무래도 Gradient가 과하게 커진다거나 그런 상황을 방지하는 거겠죠?
쓸모 있는 gradient를 얻을 수 있게 한다고 생각하면 될까요... 아무튼 이 정도로 생각해볼 수 있을 것 같습니다.
$z$에 대한 prior $p(z)$에 대해서 이에 대한 distance의 기댓값이 무한대보다 작으면, 아까 얘기했던 locally lipschitz 조건을 만족시키게 되고, Wasserstein distance가 모든 곳에서 연속이고 거의 모든 곳에서 미분 가능하다... 이런 얘기입니다.
이 모든 것들은 EM이 적어도 JS divergence에 비해서 우리 문제에 대해 훨씬 더 민감한 cost function이라는 것을 보인다.
다음 theorem은 이러한 distance와 divergence에 의해서 유도되는 위상의 상대적 강함을 묘사하며, KL이 가장 강력하고, 그다음으로는 JS, TV, EM이 가장 약하다.
이는 KL, JS, TV distance가 저차원의 manifold에 의해서 지지를 받는 분포를 학습할 때 민감한 cost function이 아니라는 사실을 강조한다. 하지만 EM distance는 이러한 환경에서 민감하다.
3. Wasserstein GAN
Theorem 2는 최적화가 이루어질 때 Wasserstein distance가 JS divergence에 비해서 더 좋은 특성을 가진다는 사실을 가리킨다.
하지만, EM distance의 하한 중 가장 큰 값은 매우 계산하기가 어렵다. 반면에, Kantorovich-Rubinstein duality는 다음을 설명한다.
그리고 이 식을 만약 우리가 어떤 $K$에 대해서 $K$-Lipschitz를 만족하는 parameterized family of functions을 가지고 있다면 다음과 같이 변경할 수 있다고 합니다.
그리고 이 문제에 대한 솔루션 $f : \mathcal{X} -> R$을 다음과 같이 구할 수 있다.
이제 equation (2)에 있던 최대화 문제를 해결하는 함수 $f$를 찾는 문제에 도달한다.
이를 대략 근사하기 위해서, 우리가 할 수 있는 것은 compact space $\mathcal{W}$에 놓여있는 가중치 $w$를 파라미터로 가지는 neural network를 학습시키는 것으로, 그러고 나서 $E_{z \sim p(z)} [\triangledown_\theta f_w (g_\theta(z)) ]$을 통해서 backprop을 한다. 이 과정은 일반적인 GAN을 가지고 수행한다.
$\mathcal{W}$이 compact라는 사실은 모든 functions $f_w$가 개별 가중치가 아닌 $\mathcal{W}$에만 의존하는 일부 $K$에 대한 K-Lipschitz일 것이라고 암시하므로 (2)를 관련이 없는 scaling factor와 critic $f_w$의 용량까지 근사한다.
Parameters $w$가 compact space에 놓이도록 하기 위해서(어떤 경계 안에 들어오게 하기 위해서), 우리가 할 수 있는 간단한 어떤 것은 각 gradient update 이후에 가중치들을 fixed box(예를 들어 $\mathcal{W} = [-0.01, 0.01]^l$)에 고정시키는 것이다.
Weight clipping은 Lipschitz constraint를 강제하기 위해서 명백하게 끔찍한 방법이다.
만약 clipping parameter가 크면, 어떤 가중치는 이들의 limit까지 도달하는데 오랜 시간이 걸릴 수 있으며, critic을 최적성까지 학습시키기 어렵게 만든다.
만약 clipping이 작으면, 이는 layer의 수가 적을 때 혹은 batch normalization이 사용되지 않았을 때 vanishing gradient로 이끌기 쉬울 수 있다.
우리는 거의 차이가 없는 간단한 변형을 가지고 실험했으며, 단순성과 이미 좋은 성능을 내는 것 때문에 weight clipping을 고수하기로 했다.
하지만, 우리는 신경망 환경에서 Lipschitz constraints를 강제하는 주제를 추가적인 연구 주제로 남겨둔다.
해당 표가 WGAN의 전체적인 절차를 담고 있습니다. 추후 code review에서 이 내용이 어떻게 코드로 구현되는지도 알아볼 예정입니다.
EM distance가 연속적이고 미분 가능하다는 사실은 우리가 critic을 최적성까지 훈련시킬 수 있다는 사실을 의미한다.
논거는 간단한데, 우리가 더 많이 critic을 학습시킬수록 더 신뢰할 만한 Wasserstein의 gradient를 우리가 얻게 되며, 이는 Wasserstein의 거의 모든 곳에서 미분 가능하다는 사실에 의해 실제로 유용하다.
JS의 경우에, discriminator가 더 나은 gradients을 얻을수록 더 신뢰할 만한 gradient을 얻게 되지만, JS가 locally saturated이기 때문에 true gradient는 0이고 우리가 figure 1과 [1]의 Theorem 2.4에서 볼 수 있듯이 vanishing gradient을 얻게 된다.
Figure 2에서 이 개념의 증명을 볼 수 있으며, 여기서 우리는 GAN discriminator와 WGAN critic을 최적성까지 학습하였다.
Discriminator는 fake와 real 사이를 구별하기 위해서 매우 빠르게 학습하며, 예상되듯이 신뢰할 만한 gradient information을 제공하지 못한다.
하지만 critic은 saturate 되지 않으며 모든 곳에서 현저하게 명백한 gradient를 제공하는 linear function으로 수렴한다.
우리가 가중치를 제한한다는 사실은 공간의 서로 다른 부분에서 함수의 가능한 성장을 최대 선형으로 제한하며, 이는 최적의 critic이 이러한 행동을 하도록 강제한다.
더 중요한 점은, 우리가 critic을 최적성까지 학습할 수 있다는 사실은 우리가 학습할 때 modes가 붕괴되는 것을 불가능하게 만든다.
4. Empirical Results
우리는 Wasserstein-GAN algorithm을 사용하여 image generation에 대해서 실험을 수행하였으며 표준적인 GAN에서 사용되는 formulation에 비해서 이를 사용하는 것에 대한 상당한 실제적 이점이 있음을 보인다.
우리는 두 가지 주요한 이점을 주장한다.
Generator의 수렴과 샘플 품질과 상관관계가 있는 의미 있는 loss metric
최적화 프로세스의 향상된 안정성
4.1 Experimental Procedure
실험은 Image generation에 대해서 수행되었고, 학습하려고 하는 target distribution은 LSUN-Bedrooms dataset을 사용했다. baseline 비교는 DCGAN이 사용되었고, 이는 표준적인 GAN procedure를 사용하여 학습되었다.
생성된 샘플은 64x64 pixel의 3 채널 이미지이며, 실험에 사용된 hyperparameter는 Algorithm 1에서 나타난 그대로 사용하였다.
4.2 Meaningful loss metric
WGAN algorithm은 각 generator update (Algorithm 1에서의 10번째 줄) 이전에 critic $f$를 상대적으로 잘 학습하도록 시도하기 때문에, 이 지점에서의 loss function은 우리가 $f$의 Lipschitz constant를 제약하는 방법과 관련되어 있는 constant factor까지의 EM distance의 추정치이다.
우리의 첫 번째 실험은 얼마나 이 추정치가 생성된 샘플의 품질과 상관관계가 있는지를 나타낸다.
Convolutional DCGAN architecture 외에, generator 혹은 generator와 critic 모두를 4-layer ReLU-MLP with 512 hidden units로 교체하여 실험을 진행하였다.
Figure 3는 모든 3개의 architecture의 WGAN training 동안의 EM distance의 WGAN estimate의 변화를 나타낸다. 이 그림들은 생성된 샘플의 시각적 품질과 이들의 그래프가 상관관계가 있음을 명확하게 보여준다.
대조적으로, Figure 4는 GAN training 동안의 JS distance의 GAN estimate의 변화를 보여준다.
GAN training 동안에, discriminator는 이 값을 최대화하도록 학습된다.
이 양은 명백하게 샘플 품질과 상관관계가 없다. 이는 Figure 4에서 확인할 수 있다.
마지막으로, 높은 learning rate를 사용하거나 critic에 Adam과 같은 momentum based optimizer를 사용할 때 WGAN 학습이 불안정해질 수 있다.
Critic에 사용되는 loss가 nonstationary 이므로, momentum 기반의 방법론들은 더 안 좋게 perform 하는 것으로 보인다.
우리는 momentum이 잠재적인 원인이라고 파악하였는데, 이는 loss가 상승하고 샘플이 악화될 때 Adam step과 gradient 간의 cosine 값이 일반적으로 마이너스로 변하기 때문이다.
이 cosine 값이 마이너스인 유일한 곳은 이러한 불안정한 상황들이었다.
그러므로 우리는 nonstationary problem에서도 잘 작동하는 것으로 알려져 있는 RMSProp을 사용하였다.
4.3 Improved stability
WGAN의 이점 중 하나는 critic을 최적성까지 학습하도록 해준다는 것이다.
critic이 완전히 학습되었을 때, 이는 다른 신경망인 generator가 학습될 수 있도록 하는 loss를 제공한다.
이는 우리가 더 이상 generator와 discriminator의 capacity를 적절하게 균형 맞출 필요가 없다는 것을 의미한다.
critic이 더 잘할수록, generator이 학습하는 데 사용되는 더 높은 gradient를 제공한다.
우리는 generator에 사용되는 구조를 변화시켰을 때 GAN보다도 WGAN이 더 강건하다는 것을 확인했다.
우리는 이를 3개의 generator 구조를 실험해서 확인하였다.
(1) convolutional DCGAN generator / (2) convolutional DCGAN generator without batch normalization with 512 hidden units / (3) 4-layer ReLU-MLP with 512 hidden units.
마지막 두 개는 GAN을 이용했을 때 매우 안 좋은 성능을 내는 것으로 알려져 있다.
우리는 WGAN critic이나 GAN discriminator로 convolutional DCGAN 구조를 사용했다.
Figure 5, 6, 7는 3가지 구조를 이용해서 WGAN과 GAN을 사용하였을 때 생성된 샘플을 보여준다.
5. Related works
생략
6. Conclusion
우리는 전통적인 GAN training을 대체하는 WGAN이라는 알고리즘을 도입했다.
새로운 모델에서, 학습의 안정성이 향상될 수 있고 mode collapse와 같은 문제들이 제거되며, debugging이나 hyperparameter search에 유용한 유의미한 학습 곡선을 제공한다는 것을 보였다.
추가로, 우리는 최적화 문제가 건전함을 보였으며, 분포 간의 다른 거리와의 깊은 연관성을 강조하는 광범위한 이론적인 논의도 제공되었다.
수학적인 내용이 많이 들어가 있는 논문이라, 완벽하게 이해하는 것이 불가능했습니다.
모델을 이용하는 입장에서 기억해야 할 점이라고 한다면, 먼저 loss가 이미지의 품질과 상관관계를 가지고 있기 때문에 loss 값을 보고서 학습이 잘 진행되고 있는지 파악하기가 쉽다는 점이 있을 것 같습니다.
논문의 그림에서 나온 대로, 점점 loss가 줄어들고 이에 따라서 이미지의 품질이 좋아지는 것을 확인하면 학습이 잘 이루어지고 있다는 것을 파악할 수 있겠죠.
그리고 기존 GAN의 경우 Generator와 Discriminator가 서로 균형을 이루면서 학습이 진행되어야만 하지만, WGAN의 경우는 critic을 최대한으로 최적 상태까지 학습시키는 것을 통해서 generator에게 더 좋은 gradient를 제공한다는 점에서 학습의 메커니즘 차이가 있다 정도를 기억해두면 좋을 것 같습니다.