1. Introduction
VGGNet은 2014년 옥스퍼드 대학교의 VGG 연구 그룹에서 제안한 신경망 구조
VGGNet의 구성 요소는 새로 고안된 요소 없이 LeNet이나 AlexNet과 동일하지만 신경망의 층수가 더 많음
VGG16 이라고 알려진 VGGNet은 가중치를 가진 층 16개로 구성 (합성곱층 13개, 전결합층 3개)
모든 층의 하이퍼파라미터가 동일하게 설정
구성 D와 E가 가장 일반적으로 사용되며 가중치를 포함하는 층수를 붙여, 이들을 각각 VGG16, VGG19라고 부름
VGGNet의 개선점은 동일하게 설정된 층(합성곱층과 전결합층)을 사용해서 신경망 구조를 단순화 시켰다는 점임
전체적인 신경망 구조는 일련의 합성곱층 뒤에 역시 풀링층이 배치되는 구조로 모든 합성곱층은 3x3 크기의 필터와 스트라이드 1, 패딩 1이 적용되었고, 모든 풀링층은 2x2크기의 풀링 영역과 스트라이드 2가 적용되었음
VGGNet에서 합성곱층의 필터 크기를 줄인(3x3) 이유는 AlexNet(11x11, 5x5)보다 더 세밀한 특징을 추출하기 위해서임
수용영역의 크기가 같을 때 크기가 큰 하나의 커널보다 크기가 작은 커널을 여러 개 쌓은쪽이 더 성능이 좋음
→ 커널을 여러 개 쌓으며 비선형층을 늘리는 것이 신경망의 층수를 늘리는 것과 동일한 효과가 있기 때문
→ 파라미터 수를 억제하므로 더 낮은 비용으로 더 복잡한 특징을 학습할 수 있음
커널을 여러 개 쌓으며 비선형층을 늘리는 것이 신경망의 층수를 늘리는 것과 동일
- 커널의 크기가 3x3인 합성곱층을 두 층 쌓은 구조(합성곱층 사이에 풀링층은 두지 않음)는 5x5 크기의 수용 영역을 가진 합성곱층과 효과가 동등함
- 커널의 크기가 3x3인 합성곱층을 세 층 쌓은 구조(합성곱층 사이에 풀링층은 두지 않음)는 7x7 크기의 수용 영역을 가진 합성곱층과 효과가 동등함
- 따라서 수용영역 크기가 3x3인 커널을 여러 개 쌓으면 비선형함수 ReLU를 여러 개 포함시키는 것과 같아 결정 함수의 변별력을 향상시키는 효과가 있음
파라미터 수를 억제하는 효과
- C개 채널을 가진 7x7 크기의 커널을 가진 단일 합성곱층의 파라미터 수는 $7^2 C^2 = 49 C^2$
- 3x3 크기의 커널을 3개 쌓은 구조의 파라미터수는 $3*3^2 C^2 = 27 C^2$개로 절반 가까이로 억제하는 효과가 있음
VGGNet은 이렇게 합성곱 연산과 풀링 연산이 일어나는 구조를 통합하는 방법으로 전체 신경망의 구조를 단순하게 유지해서 신경망에 대한 이해 및 구현 편의성을 개선함
VGGNet은 3x3 크기의 커널을 여러 층 쌓은 합성곱층 사이에 간간이 2x2 크기의 풀링 연산을 하는 풀링층을 끼워 넣는 식으로 구성됨
이 구조 뒤에 다시 일반적인 전결합층과 소프트맥스 함수가 배치되는 전형적인 분류기 구조가 배치됨
하이퍼파라미터
VGGNet의 학습 과정은 AlexNet의 학습 과정을 많이 참고했음
미니배치 경사 하강법을 사용하되 모멘텀 0.9를 적용했음
학습률은 초깃값 0.01로 시작해서 검증 데이터의 정확도가 정체될 때 마다 1/10 로 감소시킴
2. Code
VGG16
class VGG16(nn.Module):
def __init__(self, num_classes=1000):
super(VGG16, self).__init__()
self.layer1 = nn.Sequential(
nn.Conv2d(3, 64, kernel_size=3, stride=1, padding='same'),
nn.ReLU(),
nn.Conv2d(64, 64, kernel_size=3, stride=1, padding='same'),
nn.ReLU(),
nn.MaxPool2d(kernel_size = 2, stride = 2))
self.layer2 = nn.Sequential(
nn.Conv2d(64, 128, kernel_size=3, stride=1, padding='same'),
nn.ReLU(),
nn.Conv2d(128, 128, kernel_size=3, stride=1, padding='same'),
nn.ReLU(),
nn.MaxPool2d(kernel_size = 2, stride = 2))
self.layer3 = nn.Sequential(
nn.Conv2d(128, 256, kernel_size=3, stride=1, padding='same'),
nn.ReLU(),
nn.Conv2d(256, 256, kernel_size=3, stride=1, padding='same'),
nn.ReLU(),
nn.Conv2d(256, 256, kernel_size=3, stride=1, padding='same'),
nn.ReLU(),
nn.MaxPool2d(kernel_size = 2, stride = 2))
self.layer4 = nn.Sequential(
nn.Conv2d(256, 512, kernel_size=3, stride=1, padding='same'),
nn.ReLU(),
nn.Conv2d(512, 512, kernel_size=3, stride=1, padding='same'),
nn.ReLU(),
nn.Conv2d(512, 512, kernel_size=3, stride=1, padding='same'),
nn.ReLU(),
nn.MaxPool2d(kernel_size = 2, stride = 2))
self.layer5 = nn.Sequential(
nn.Conv2d(512, 512, kernel_size=3, stride=1, padding='same'),
nn.ReLU(),
nn.Conv2d(512, 512, kernel_size=3, stride=1, padding='same'),
nn.ReLU(),
nn.Conv2d(512, 512, kernel_size=3, stride=1, padding='same'),
nn.ReLU(),
nn.MaxPool2d(kernel_size = 2, stride = 2))
self.fc = nn.Sequential(
nn.Flatten(),
nn.Linear(7*7*512, 4096),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(4096, 4096),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(4096, num_classes))
def forward(self, x):
out = self.layer1(x)
out = self.layer2(out)
out = self.layer3(out)
out = self.layer4(out)
out = self.layer5(out)
out = self.fc(out)
return out
VGG19
class VGG19(nn.Module):
def __init__(self, num_classes=1000):
super(VGG19, self).__init__()
self.layer1 = nn.Sequential(
nn.Conv2d(3, 64, kernel_size=3, stride=1, padding='same'),
nn.ReLU(),
nn.Conv2d(64, 64, kernel_size=3, stride=1, padding='same'),
nn.ReLU(),
nn.MaxPool2d(kernel_size = 2, stride = 2))
self.layer2 = nn.Sequential(
nn.Conv2d(64, 128, kernel_size=3, stride=1, padding='same'),
nn.ReLU(),
nn.Conv2d(128, 128, kernel_size=3, stride=1, padding='same'),
nn.ReLU(),
nn.MaxPool2d(kernel_size = 2, stride = 2))
self.layer3 = nn.Sequential(
nn.Conv2d(128, 256, kernel_size=3, stride=1, padding='same'),
nn.ReLU(),
nn.Conv2d(256, 256, kernel_size=3, stride=1, padding='same'),
nn.ReLU(),
nn.Conv2d(256, 256, kernel_size=3, stride=1, padding='same'),
nn.ReLU(),
nn.Conv2d(256, 256, kernel_size=3, stride=1, padding='same'),
nn.ReLU(),
nn.MaxPool2d(kernel_size = 2, stride = 2))
self.layer4 = nn.Sequential(
nn.Conv2d(256, 512, kernel_size=3, stride=1, padding='same'),
nn.ReLU(),
nn.Conv2d(512, 512, kernel_size=3, stride=1, padding='same'),
nn.ReLU(),
nn.Conv2d(512, 512, kernel_size=3, stride=1, padding='same'),
nn.ReLU(),
nn.Conv2d(512, 512, kernel_size=3, stride=1, padding='same'),
nn.ReLU(),
nn.MaxPool2d(kernel_size = 2, stride = 2))
self.layer5 = nn.Sequential(
nn.Conv2d(512, 512, kernel_size=3, stride=1, padding='same'),
nn.ReLU(),
nn.Conv2d(512, 512, kernel_size=3, stride=1, padding='same'),
nn.ReLU(),
nn.Conv2d(512, 512, kernel_size=3, stride=1, padding='same'),
nn.ReLU(),
nn.Conv2d(512, 512, kernel_size=3, stride=1, padding='same'),
nn.ReLU(),
nn.MaxPool2d(kernel_size = 2, stride = 2))
self.fc = nn.Sequential(
nn.Flatten(),
nn.Linear(7*7*512, 4096),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(4096, 4096),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(4096, num_classes))
def forward(self, x):
out = self.layer1(x)
out = self.layer2(out)
out = self.layer3(out)
out = self.layer4(out)
out = self.layer5(out)
out = self.fc(out)
return out