2012년 AlexNet이 ImageNet에서 큰 성공을 거두면서, 연구자들은 "CNN을 어떻게 설계해야 성능이 더 좋아질까?"를 고민하기 시작했다.
당시에는 필터 크기, stride, 층 수 등을 이것저것 바꿔보는 식이어서, 어떤 요소가 성능을 결정하는지 명확하지 않았다. 특히 이런 질문이 남아 있었다.
2014년 옥스퍼드 대학 Visual Geometry Group(Simonyan & Zisserman)은 다른 조건은 최대한 단순하게 고정하고 깊이 하나만 바꿔가며 이 질문에 답하려 했다.
| 구분 | AlexNet (2012) | VGGNet (2014) | GoogLeNet (2014) |
|---|---|---|---|
| 필터 크기 | 11×11, 5×5, 3×3 혼용 | 3×3으로 통일 | 1×1, 3×3, 5×5 병렬 (Inception) |
| 깊이 | 8층 | 16~19층 | 22층 |
| 구조 | 층마다 설정이 제각각 | 단순하고 규칙적 | 복잡한 모듈 구조 |
| 파라미터 | 약 6천만 | 약 1억 3,800만 | 약 700만 |
3×3 합성곱을 연속으로 쌓으면 큰 필터와 같은 수용 영역(receptive field)을 얻는다.
| 구성 | 수용 영역 | 파라미터 수 (채널 C 기준) |
|---|---|---|
| 7×7 합성곱 1층 | 7×7 | 49C² |
| 3×3 합성곱 3층 | 7×7 | 27C² |
이렇게 하면 깊이를 늘리면서도 다음 이점을 얻는다.
1. 파라미터 감소: 같은 수용 영역을 더 적은 파라미터로 얻는다.
2. 비선형성 증가: 층마다 ReLU가 들어가므로 표현력이 풍부해진다.
입력 224×224 → 합성곱 블록 5개 → FC 3개 → Softmax
깊이가 깊어질수록 성능이 좋아진다는 것을 실험으로 보여주었다. 다만 한계도 분명했다.
블록 구조가 규칙적이라 설정 리스트만으로 네트워크를 만들 수 있다.
import torch.nn as nn
# 숫자 = 3×3 합성곱의 출력 채널, 'M' = Max Pooling
VGG16_CFG = [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M',
512, 512, 512, 'M', 512, 512, 512, 'M']
def make_features(cfg):
layers, in_ch = [], 3
for v in cfg:
if v == 'M':
layers.append(nn.MaxPool2d(kernel_size=2, stride=2))
else:
layers += [nn.Conv2d(in_ch, v, kernel_size=3, padding=1),
nn.ReLU(inplace=True)]
in_ch = v
return nn.Sequential(*layers)
class VGG16(nn.Module):
def __init__(self, num_classes=1000):
super().__init__()
self.features = make_features(VGG16_CFG)
self.classifier = nn.Sequential(
nn.Flatten(),
nn.Linear(512 * 7 * 7, 4096), nn.ReLU(True), nn.Dropout(0.5),
nn.Linear(4096, 4096), nn.ReLU(True), nn.Dropout(0.5),
nn.Linear(4096, num_classes),
)
def forward(self, x):
return self.classifier(self.features(x))
실무에서는 직접 학습하기보다 ImageNet으로 사전학습된 모델을 가져와 내 데이터에 맞게 마지막 층만 바꾼다.
import torch.nn as nn
from torchvision import models
model = models.vgg16(weights=models.VGG16_Weights.IMAGENET1K_V1)
# 특징 추출부는 고정
for p in model.features.parameters():
p.requires_grad = False
# 마지막 FC층을 내 클래스 수에 맞게 교체 (예: 5개 클래스)
model.classifier[6] = nn.Linear(4096, 5)
VGG의 중간층 출력은 이미지의 질감·형태를 잘 담고 있어서, 스타일 트랜스퍼나 초해상도 모델에서 "두 이미지가 사람 눈에 얼마나 비슷한가"를 측정하는 데 쓰인다.
import torch.nn as nn
from torchvision import models
vgg = models.vgg16(weights=models.VGG16_Weights.IMAGENET1K_V1).features[:16].eval()
for p in vgg.parameters():
p.requires_grad = False
def perceptual_loss(pred, target):
# 픽셀 값 대신 VGG 특징 맵끼리 비교
return nn.functional.mse_loss(vgg(pred), vgg(target))