안녕하세요 이번 포스트에서는 지난번에 이어 김성훈 교수님의 PytorchToAll 강의를 게시하도록 하겠습니다. 이번 포스트에서는 대표적인 인공신경망 구조인 CNN을 다루도록 하겠습니다. 본 포스트에서 사용된 코드는 유투브에 있는 Sung Kim님의 강의에서 가져왔음을 밝힙니다.
CNN은 대표적인 인공 신경망입니다. 가장 직관적으로 CNN을 접근한다면 높은 차원의 데이터를 특정 구간에 집중하여(patch) 작은 차원으로 줄이는 동시에 데이터의 특징을 잘 포착하는 인공신경망이라고 표현할 수 있겠습니다. Convolution, subsampling 단계를 거치면서 차원이 축소되고 특징을 잡아냅니다.(Feature extraction) 이후 Fully connected layer를 거쳐 task에 적합한 output을 산출합니다.(classification)
convolution layer에서는 stride만큼 움직이며 전체의 부분(patch)의 특징을 잡아냅니다. 가장자리에 0을 추가하여 filter를 적용하는 zero padding을 사용하기도 합니다.
Pooling은 filter가 stride만큼 이동하면서 부분의 대표적인 특징을 잡는 역할을 합니다. Max pooling, average pooling등을 사용합니다.
Fully connected neural net과 locally connected neural net의 가장 큰 차이점은 이름에서 나와 있듯이 전자는 주어진 모든 뉴런을 종합한 output을 계산하는 것이고 후자는 각 부분별로 뉴런의 아웃풋을 산출한다는 점입니다. 필연적으로 두 방법간에는 파라미터수가 크게 차이나게 됩니다.
아래의 코드는 두 개의 convolution layer, 두 개의 pooling layer를 사용한 CNN 구조입니다.
convolution layer인 Conv2d는 Conv2d(in_channels, out_channels, kernel_size)입니다. 여기서 2d란 말 그대로 convolution layer가 2차원임을 의미합니다.
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(1, 10, kernel_size=5)
self.conv2 = nn.Conv2d(10, 20, kernel_size=5)
self.mp = nn.MaxPool2d(2)
self.fc = nn.Linear(320, 10)
def forward(self, x):
in_size = x.size(0)
x = F.relu(self.mp(self.conv1(x)))
x = F.relu(self.mp(self.conv2(x)))
x = x.view(in_size, -1) # flatten the tensor
x = self.fc(x)
return F.log_softmax(x)
model = Net()
기본적인 CNN 구조에서 더 발전한 구조를 다루겠습니다. 강의에서는 Inception 모델을 다루고 있습니다. Inception 모델의 가장 큰 특징은 1by1 kernel size를 사용해서 계산을 용이하게 만들었다는 점입니다. 1by1 Conv를 사용해서 다양한 kernel size를 적용하여 모델의 feature extraction을 시도한 모델이라고 생각하면 되겠습니다.
효율적인 코드 작성을 위해서 다음과 같이 InceptionA라 명명한 class를 따로 설정하고 이를 네트워크를 구축하는데 사용합니다.
class InceptionA(nn.Module):
def __init__(self, in_channels):
super(InceptionA, self).__init__()
self.branch1x1 = nn.Conv2d(in_channels, 16, kernel_size=1)
self.branch5x5_1 = nn.Conv2d(in_channels, 16, kernel_size=1)
self.branch5x5_2 = nn.Conv2d(16, 24, kernel_size=5, padding=2)
self.branch3x3dbl_1 = nn.Conv2d(in_channels, 16, kernel_size=1)
self.branch3x3dbl_2 = nn.Conv2d(16, 24, kernel_size=3, padding=1)
self.branch3x3dbl_3 = nn.Conv2d(24, 24, kernel_size=3, padding=1)
self.branch_pool = nn.Conv2d(in_channels, 24, kernel_size=1)
def forward(self, x):
branch1x1 = self.branch1x1(x)
branch5x5 = self.branch5x5_1(x)
branch5x5 = self.branch5x5_2(branch5x5)
branch3x3dbl = self.branch3x3dbl_1(x)
branch3x3dbl = self.branch3x3dbl_2(branch3x3dbl)
branch3x3dbl = self.branch3x3dbl_3(branch3x3dbl)
branch_pool = F.avg_pool2d(x, kernel_size=3, stride=1, padding=1)
branch_pool = self.branch_pool(branch_pool)
outputs = [branch1x1, branch5x5, branch3x3dbl, branch_pool]
return torch.cat(outputs, 1)
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(1, 10, kernel_size=5)
self.conv2 = nn.Conv2d(88, 20, kernel_size=5)
self.incept1 = InceptionA(in_channels=10)
self.incept2 = InceptionA(in_channels=20)
self.mp = nn.MaxPool2d(2)
self.fc = nn.Linear(1408, 10)
def forward(self, x):
in_size = x.size(0)
x = F.relu(self.mp(self.conv1(x)))
x = self.incept1(x)
x = F.relu(self.mp(self.conv2(x)))
x = self.incept2(x)
x = x.view(in_size, -1) # flatten the tensor
x = self.fc(x)
return F.log_softmax(x)
model = Net()
CNN 구조를 더 깊게 쌓으면 학습이 더 좋아질까요? 안타깝게도 무조건 깊게 쌓는 것만이 좋은 성능을 담보하지는 않습니다. Gradient Vanishing problem, Overfitting, Degradation problem과 같이 여러 문제로 학습이 잘 진행되지 않게됩니다. 이를 보완하기 위해서 skip-connenction을 도입하여 back propagation 문제를 개선한 ResNet, 이전 층의 output을 활용한 DenseNet같은 구조가 제안되었습니다. 추후 설명할 기회를 갖도록 하겠습니다.