class Conv_Block(nn.Module):
def __init__(self, input_ch, output_ch, **kwargs) -> None:
super(Conv_Block, self).__init__()
self.conv = nn.Conv2d(input_ch, output_ch, **kwargs)
self.batchnorm = nn.BatchNorm2d(output_ch)
self.relu = nn.ReLU()
def forward(self, x: Tensor) -> Tensor:
x = self.conv(x) # Convolutional Layer
x = self.batchnorm(x) # BatchNorm Layer
x = self.relu(x) # ReLU
return x
<aside> 💡 Inception module 구현
</aside>
class Inception(nn.Module):
# 클래스 생성자
def __init__(self, input_ch, n1x1, nReduced_3x3, n3x3, nReduced_5x5, n5x5, pool_proj) -> None:
super(Inception, self).__init__()
self.branch1 = Conv_Block(input_ch, n1x1, kernel_size=1, stride=1, padding=0)
self.branch2 = nn.Sequential(
Conv_Block(input_ch, nReduced_3x3, kernel_size=1, stride=1, padding=0),
Conv_Block(nReduced_3x3, n3x3, kernel_size=3, stride=1, padding=1)
)
self.branch3 = nn.Sequential(
Conv_Block(input_ch, nReduced_5x5, kernel_size=1, stride=1, padding=0),
Conv_Block(nReduced_5x5, n5x5, kernel_size=5, stride=1, padding=2)
)
self.branch4 = nn.Sequential(
nn.MaxPool2d(kernel_size=3, stride=1, padding=1),
Conv_Block(input_ch, pool_proj, kernel_size=1, stride=1, padding=0)
)
def forward(self, x: Tensor) -> Tensor: # 순방향 전파 함수
x1 = self.branch1(x)
x2 = self.branch2(x)
x3 = self.branch3(x)
x4 = self.branch4(x)
return torch.cat([x1, x2, x3, x4], dim=1)
<aside> 💡 Auxiliary Classification 과정
</aside>
class InceptionAux(nn.Module):
def __init__(self, input_ch, num_classes) -> None:
super(InceptionAux, self).__init__()
self.avgpool = nn.AvgPool2d(kernel_size=5, stride=3) # 5x5 크기의 avgpool layer
self.conv = Conv_Block(input_ch, 128, kernel_size=1, stride=1, padding=0) # 입력데이터의 채널을 128로
self.fc1 = nn.Linear(2048, 1024)
self.fc2 = nn.Linear(1024, num_classes) # 선형 변환을 수행하는 fc 레이어
self.dropout = nn.Dropout(p=0.7) # 논문 상 Dropout rate인 0.7로
self.relu = nn.ReLU() # ReLU 활성화 함수
def forward(self, x: Tensor) -> Tensor:
x = self.avgpool(x)
x = self.conv(x)
x = x.view(x.size()[0], -1)
x = self.fc1(x)
x = self.relu(x)
x = self.dropout(x)
x = self.fc2(x)
return x
<aside> 💡 위의 클래스들을 이용하여 GoogLeNet 구현
</aside>
class GoogLeNet(nn.Module):
def __init__(self, aux_logits=True, num_classes=1000) -> None:
super(GoogLeNet, self).__init__()
assert aux_logits == True or aux_logits == False
self.aux_logits = aux_logits
self.conv1 = Conv_Block(input_ch=3, output_ch=64, kernel_size=7, stride=2, padding=3)
self.maxpool1 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1, ceil_mode=True)
self.conv2 = Conv_Block(input_ch=64, output_ch=64, kernel_size=1, stride=1, padding=0)
self.conv3 = Conv_Block(input_ch=64, output_ch=192, kernel_size=3, stride=1, padding=1)
self.maxpool2 = nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True)
self.a3 = Inception(192, 64, 96, 128, 16, 32, 32)
self.b3 = Inception(256, 128, 128, 192, 32, 96, 64)
self.maxpool3 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1, ceil_mode=True)
self.a4 = Inception(480, 192, 96, 208, 16, 48, 64)
self.b4 = Inception(512, 160, 112, 224, 24, 64, 64)
self.c4 = Inception(512, 128, 128, 256, 24, 64, 64)
self.d4 = Inception(512, 112, 144, 288, 32, 64, 64)
self.e4 = Inception(528, 256, 160, 320, 32, 128, 128)
self.maxpool4 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
self.a5 = Inception(832, 256, 160, 320, 32, 128, 128)
self.b5 = Inception(832, 384, 192, 384, 48, 128, 128)
self.avgpool = nn.AvgPool2d(kernel_size=7, stride=1)
self.dropout = nn.Dropout(p=0.4)
self.linear = nn.Linear(1024, num_classes)
if self.aux_logits:
self.aux1 = InceptionAux(512, num_classes)
self.aux2 = InceptionAux(528, num_classes)
else:
self.aux1 = None
self.aux2 = None
def transform_input(self, x: Tensor) -> Tensor:
x_R = torch.unsqueeze(x[:, 0], 1) * (0.229 / 0.5) + (0.485 - 0.5) / 0.5
x_G = torch.unsqueeze(x[:, 1], 1) * (0.224 / 0.5) + (0.456 - 0.5) / 0.5
x_B = torch.unsqueeze(x[:, 2], 1) * (0.225 / 0.5) + (0.406 - 0.5) / 0.5
x = torch.cat([x_R, x_G, x_B], 1)
return x
def forward(self, x: Tensor) -> Tensor: # 순방향 전파
x = self.transform_input(x)
x = self.conv1(x) # conv filter 1
x = self.maxpool1(x)
x = self.conv2(x) # conv filter 2
x = self.conv3(x) # conv filter 3
x = self.maxpool2(x)
x = self.a3(x)
x = self.b3(x)
x = self.maxpool3(x)
x = self.a4(x)
aux1: Optional[Tensor] = None
if self.aux_logits and self.training:
aux1 = self.aux1(x)
x = self.b4(x)
x = self.c4(x)
x = self.d4(x)
aux2: Optional[Tensor] = None
if self.aux_logits and self.training:
aux2 = self.aux2(x)
x = self.e4(x)
x = self.maxpool4(x)
x = self.a5(x)
x = self.b5(x)
x = self.avgpool(x)
x = x.view(x.shape[0], -1) # x = x.reshape(x.shape[0], -1)
x = self.linear(x)
x = self.dropout(x)
if self.aux_logits and self.training:
return aux1, aux2
else:
return x
import os
import numpy as np
import torch
import torchvision.datasets as datasets
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
import argparse
def load_dataset():
# preprocess
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
# load train, test data
train = datasets.CIFAR10(root="../data", train=True, transform=transform, download=True)
test = datasets.CIFAR10(root="../data", train=False, transform=transform, download=True)
train_loader = DataLoader(train, batch_size=args.batch_size, shuffle=True)
test_loader = DataLoader(test, batch_size=args.batch_size, shuffle=False)
return train_loader, test_loader
if __name__ == "__main__":
# set hyperparameter
parser = argparse.ArgumentParser()
parser.add_argument('--batch_size', action='store', type=int, default=100)
parser.add_argument('--learning_rate', action='store', type=float, default='0.0002')
parser.add_argument('--n_epochs', action='store', type=int, default=100)
parser.add_argument('--plot', action='store', type=bool, default=True)
args = parser.parse_args(args=[]) # Jupyter 환경이므로 다음과 같이 설정
np.random.seed(1)
seed = torch.manual_seed(1)
# load dataset
train_loader, test_loader = load_dataset()
# model, loss, optimizer
losses = []
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = GoogLeNet(aux_logits=False, num_classes=10).to(device)
criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=args.learning_rate)
# train
for epoch in range(args.n_epochs):
model.train()
train_loss = 0
correct, count = 0, 0
for batch_idx, (images, labels) in enumerate(train_loader, start=1):
images, labels = images.to(device), labels.to(device)
optimizer.zero_grad()
output = model.forward(images)
loss = criterion(output, labels)
loss.backward()
optimizer.step()
train_loss += loss.item()
_, preds = torch.max(output, 1)
count += labels.size(0)
correct += preds.eq(labels).sum().item() # torch.sum(preds == labels)
if batch_idx % 100 == 0:
print (f"[*] Epoch: {epoch} \\tStep: {batch_idx}/{len(train_loader)}\\tTrain accuracy: {round((correct/count), 4)} \\tTrain Loss: {round((train_loss/count)*100, 4)}")
# valid
model.eval()
correct, count = 0, 0
valid_loss = 0
with torch.no_grad():
for batch_idx, (images, labels) in enumerate(test_loader, start=1):
images, labels = images.to(device), labels.to(device)
output = model.forward(images)
loss = criterion(output, labels)
valid_loss += loss.item()
_, preds = torch.max(output, 1)
count += labels.size(0)
correct += preds.eq(labels).sum().item() # torch.sum(preds == labels)
if batch_idx % 100 == 0:
print (f"[*] Step: {batch_idx}/{len(test_loader)}\\tValid accuracy: {round((correct/count), 4)} \\tValid Loss: {round((valid_loss/count)*100, 4)}")