
AlexNet은 LSVRC-2010 대회에서 Top-1 오류율과 Top-5 오류율에서 37.5%와 17.0%를, ILSVRC-2012 대회에서 Top-5 오류율 15.3%를 달성하였다.
이 신경망은 6천만개의 파라미터와 65만개의 뉴런을 가졌으며, 5개의 Convolution layer, Max-pooling layer 일부, 3개의 FC-layer, 마지막엔 1000-way softmax로 구성되어 있다.
학습을 빠르게하기 위해 non-saturating neuron(ReLU)과 GPU 기반 Convolution 연산을 사용하였다.
FC-layer에서의 과적합을 방지하기 위해 Dropout 기법을 사용하였다.
본 논문의 기여
최종 신경망 구조
모델 크기의 한계
데이터 셋 특징
ILSVRC
입력 전처리
신경망은 5개의 Convolution layer, 3개의 FC-layer로 구성되어 있으며, Section 3.1 ~ 3.4는 중요도 순으로 정렬되어 있다.
활성화 함수

위 그림은 4-layer CNN이 CIFAR-10 데이터셋에서 학습 오류 25%에 도달할 때까지 필요한 반복의 수를 보여준다.
이때 ReLU(실선)을 사용한 CNN이 tanh(점선)을 사용한 CNN보다 6배 빠르게 25% 학습 오류에 도달하는 것을 볼 수 있다.
이는 기존의 saturating neuron을 사용했다면 대규모 CNN 학습은 불가능했을 것이다.
기존 연구와 차별점
GPU 병렬화
병렬화 구조
이는 GPU간 통신 비용과 연산량 사이의 밸런스 (통신을 많이하면 느려지고, 너무 적으면 성능이 떨어짐.)를 맞추기 위함이고, 이는 cross-validation으로 최적 연결 구조를 찾을 수 있었다.
기존 연구와 차별점
하나의 GPU를 사용했을 때보다 Top-1, Top-5 오류율이 1.7%, 1.2% 감소되었으며, 학습 시간도 단축되었다.
ReLU 함수는 입력 정규화를 필요로 하지 않으며, 양의 입력 들어왔을때 해당 뉴런에서 학습이 이루어진다. 그러나 다음과 같은 로컬 정규화 방법은 일반화에 도움이 된다는 것을 알 수 있다.
로컬 정규화 방법
기존 연구와 차별점
이 정규화 방법을 사용했을 때 ImageNet에서 Top-1, Top-5 오류율 1.4%, 1.2% 감소, CIFAR-10에서 정규화하지 않았을 때 테스트 오류율 13%에서 11%로 감소

풀링(Pooling)은 근처 뉴런들을 요약하는 연산이다.
: stride
: 풀링 윈도우 크기
non-Overlapping Pooling
Overlapping Pooling
Overlapping Pooling이 적용되었을 때 Top-1, Top-5 오류율 0.4%, 0.3% 감소, 그리고 과적합에 더 강한 모습을 보임.

Conv Layer 5개, FC-Layer 3개, 1000-way softmax (출력), Multinomial Logistic Regression
Conv1 Layer
Conv2 Layer
Conv3 Layer
Conv4 Layer
Conv5 Layer
FC1 Layer
FC2 Layer
FC3 Layer
AlexNet은 6천만개의 파라미터를 가지며, 아래는 과적합을 방지하기 위한 방법을 소개한다.
데이터 증강에 있어서 필요한 연산은 GPU가 학습하는 동안 CPU에서 변형된 이미지가 생성되기 때문에 연산 비용 측면에서 자유롭다.
translation & hrizontal reflection
RGB color PCA
성능을 올리는 가장 좋은 방법은 앙상블이다. 그러나 AlexNet과 같이 큰 신경망에서는 시간과비용적인 측면에서 현실적으로 불가능하다. 그래서 사용한 방법이 Dropout.
Dropout
Dropout 없이는 매우 심한 과적합이 발생했으며, 여기선 처음 두 FC-Layer에서 적용하였다.
weight decay
Bias 초기화
Learing Rate
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from torchsummary import summary
from torch.utils import data
import torchvision.datasets as datasets
import torchvision.transforms as transforms
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# ReLU 활성화 함수 (Section 3.1) - 학습 시간 단축
# LRN (Section 3.3) - 과적합 방지
# Overlapping Pooling (Section 3.4) - 과적합 방지
# Dropout (Section 4.2) - 과적합 방지
# Bias Initialization (Section 5) - ReLU 함수에 양의 입력 -> 초기 단계 학습 가속
class AlexNet(nn.Module):
def __init__(self, num_classes=1000): # Section 3.5
# 입력 크기: (b x 3 x 227 x 227)
# 논문 상에선 입력 크기가 224라고 작성되어 있지만 실제론 227
super().__init__()
self.conv = nn.Sequential(
nn.Conv2d(in_channels=3, out_channels=96, kernel_size=11, stride=4), # (b x 96 x 55 x 55)
nn.ReLU(), # Section 3.1
nn.LocalResponseNorm(size=5, alpha=0.0001, beta=0.75, k=2), # Section 3.3
nn.MaxPool2d(kernel_size=3, stride=2), # Section 3.4 (b x 96 x 27 x 27)
nn.Conv2d(in_channels=96, out_channels=256, kernel_size=5, stride=1, padding=2), # (b x 256 x 27 x 27)
nn.ReLU(),
nn.LocalResponseNorm(size=5, alpha=0.0001, beta=0.75, k=2),
nn.MaxPool2d(kernel_size=3, stride=2), # (b x 256 x 13 x 13)
nn.Conv2d(in_channels=256, out_channels=384, kernel_size=3, stride=1, padding=1), # (b x 384 x 13 x 13)
nn.ReLU(),
nn.Conv2d(in_channels=384, out_channels=384, kernel_size=3, stride=1, padding=1), # (b x 384 x 13 x 13)
nn.ReLU(),
nn.Conv2d(in_channels=384, out_channels=256, kernel_size=3, stride=1, padding=1), # (b x 256 x 13 x 13)
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2) # (b x 256 x 6 x 6)
)
self.fc = nn.Sequential(
nn.Dropout(p=0.5), # Section 4.2
nn.Linear(in_features=(256 * 6 * 6), out_features=4096),
nn.ReLU(),
nn.Dropout(p=0.5),
nn.Linear(in_features=4096, out_features=4096),
nn.ReLU(),
nn.Linear(in_features=4096, out_features=num_classes)
)
self.init_wb()
def init_wb(self): # Section 5
for layer in self.conv:
if isinstance(layer, nn.Conv2d):
nn.init.normal_(layer.weight, mean=0, std=0.01)
nn.init.constant_(layer.bias, 0)
nn.init.constant_(self.conv[4].bias, 1)
nn.init.constant_(self.conv[10].bias, 1)
nn.init.constant_(self.conv[12].bias, 1)
for layer in self.fc:
if isinstance(layer, nn.Linear):
nn.init.normal_(layer.weight, mean=0, std=0.01)
nn.init.constant_(layer.bias, 1)
def forward(self, x):
x = self.conv(x)
x = torch.flatten(x, 1)
x = self.fc(x)
return x
# Details of Learning (Section 5)
# weight decay: 학습 오류 감소
model = AlexNet().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(
params=model.parameters(),
lr=0.01,
momentum=0.9,
weight_decay=0.0005
)
lr_scheduler = optim.lr_scheduler.StepLR(
optimizer=optimizer,
step_size=30,
gamma=0.1
)
summary(model, input_size=(3, 227, 227), batch_size=128)
----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Conv2d-1 [128, 96, 55, 55] 34,944
ReLU-2 [128, 96, 55, 55] 0
LocalResponseNorm-3 [128, 96, 55, 55] 0
MaxPool2d-4 [128, 96, 27, 27] 0
Conv2d-5 [128, 256, 27, 27] 614,656
ReLU-6 [128, 256, 27, 27] 0
LocalResponseNorm-7 [128, 256, 27, 27] 0
MaxPool2d-8 [128, 256, 13, 13] 0
Conv2d-9 [128, 384, 13, 13] 885,120
ReLU-10 [128, 384, 13, 13] 0
Conv2d-11 [128, 384, 13, 13] 1,327,488
ReLU-12 [128, 384, 13, 13] 0
Conv2d-13 [128, 256, 13, 13] 884,992
ReLU-14 [128, 256, 13, 13] 0
MaxPool2d-15 [128, 256, 6, 6] 0
Dropout-16 [128, 9216] 0
Linear-17 [128, 4096] 37,752,832
ReLU-18 [128, 4096] 0
Dropout-19 [128, 4096] 0
Linear-20 [128, 4096] 16,781,312
ReLU-21 [128, 4096] 0
Linear-22 [128, 1000] 4,097,000
================================================================
Total params: 62,378,344
Trainable params: 62,378,344
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 75.48
Forward/backward pass size (MB): 1885.10
Params size (MB): 237.95
Estimated Total Size (MB): 2198.54
----------------------------------------------------------------
# Data Augmentation (Section 4.1) - 과적합 방지
# mean substraction only, PCA color augmentation, 10 crop test 구현 X
TRAIN_DIR = ''
VAL_DIR = ''
TEST_DIR = ''
train_transform = transforms.Compose([
transforms.Resize(256),
transforms.RandomCrop(227),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]
)
])
train_dataset = datasets.ImageFolder(TRAIN_DIR, transform=train_transform)
train_loader = data.DataLoader(
train_dataset,
batch_size=128,
shuffle=True,
num_workers=8,
pin_memory=True
)
val_transform = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(227),
transforms.ToTensor(),
transforms.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]
)
])
val_dataset = datasets.ImageFolder(VAL_DIR, transform=val_transform)
val_loader = data.DataLoader(
val_dataset,
batch_size=128,
shuffle=False,
num_workers=8,
pin_memory=True
)
test_transform = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(227),
transforms.ToTensor(),
transforms.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]
)
])
test_dataset = datasets.ImageFolder(TEST_DIR, transform=test_transform)
test_loader = data.DataLoader(
test_dataset,
batch_size=128,
shuffle=False,
num_workers=8,
pin_memory=True
)
# Epoch (Section 5) - Roughly 90
best_acc = 0
for epoch in range(90):
model.train()
train_loss = 0
correct = 0
total = 0
for img, cls in train_loader:
img, cls = img.to(device), cls.to(device)
output = model(img)
loss = criterion(output, cls)
optimizer.zero_grad()
loss.backward()
optimizer.step()
train_loss += loss.item() * img.size(0)
_, preds = torch.max(output, 1)
correct += (preds == cls).sum().item()
total += cls.size(0)
train_loss /= total
train_acc = correct / total
model.eval()
val_loss = 0
correct = 0
total = 0
with torch.no_grad():
for img, cls in val_loader:
img, cls = img.to(device), cls.to(device)
output = model(img)
loss = criterion(output, cls)
val_loss += loss.item() * img.size(0)
_, preds = torch.max(output, 1)
correct += (preds == cls).sum().item()
total += cls.size(0)
val_loss /= total
val_acc = correct / total
if (epoch + 1) % 10 == 0:
print(f'Epoch: {epoch + 1}/90 - Train Loss: {train_loss:.4f} - Train Acc: {train_acc:.4f} - Val Loss {val_loss:.4f} - Val Acc {val_acc:.4f}')
if val_acc > best_acc:
best_acc = val_acc
torch.save(model.state_dict(), "best_alexnet.pth")
print("Best Model Updated")
torch.save({
'epoch': epoch,
'model_state_dict': model.state_dict(),
'optimizer_state_dict': optimizer.state_dict(),
'best_acc': best_acc
}, "alexnet_checkpoint.pth")
lr_scheduler.step()
model.load_state_dict(torch.load("best_alexnet.pth"))
model.to(device)
model.eval()
test_loss = 0
correct = 0
total = 0
with torch.no_grad():
for img, cls in test_loader:
img, cls = img.to(device), cls.to(device)
output = model(img)
loss = criterion(output, cls)
test_loss += loss.item() * img.size(0)
_, preds = torch.max(output, 1)
correct += (preds == cls).sum().item()
total += cls.size(0)
test_loss /= total
test_acc = correct / total
print(f'Test Loss {test_loss:.4f} - Test Acc {test_acc:.4f}')