소개
이 게시물은 PyTorch를 사용해 scratch에서 가장 인기 있는 卷积 신경망을 만들고 있는 시리즈의 계속입니다. 이전 게시물 여기에서 LeNet5를 만들었습니다. 이 게시물에서는 컴퓨터 비전에서 가장 pivot적인 breakthrough algorithm인 AlexNet을 從頭부터 만듭니다.
우리는 まず AlexNet의 아키텍처를 조사하고 이해할 것입니다. 그 다음, 我们的 데이터셋, CIFAR-10을 로딩하여 데이터에 대한 一些 pre-processing을 적용하고, 그 다음에 PyTorch
를 사용하여 AlexNet을 만들고 이를 我们的 pre-processed data上에 训练할 것입니다. 결국, 이 trained model은 seen (test) data上에서 평가 目的로 시험 할 것입니다.
사전 요구
이 글을 이해하기 위해서는 신경망의 지식이 도움이 될 것입니다. 이는 다양한 신경망 层次 (입력 层次, 隱藏 层次, 출력 层次), 활성화 함수, 優化 算法 (gradient descent의 변형), 损失 函數 등을 熟悉하는 것을 포함합니다. 加えて, Python 문법과 PyTorch 库의 熟悉도 이 글에서 제시된 代码片段을 이해하기 必不可少的 합니다.
이러한 CNN에 대한 이해는 필수です. 이에는 입력 데이터에서 특징을 추출하는 데 사용되는 卷積 계층, 풀링 계층 그리고 그 역할에 대한 지식이 포함됩니다. stride, 패딩 및 커널/필터 크기의 영향 등의 개념을 이해하는 것도 도움이 됩니다.
AlexNet
AlexNet은 2012년 Alex Krizhevsky와 동료들이最初에 개발한 깊은 卷積 신경 네트워크입니다. ImageNet LSVRC-2010 경쟁에서 이미지를 분류하기 위해 디자인되었고, 현재 최고의 성능을 보여주었습니다. 모델에 대한 자세한 내용은 원래의 연구論文여기에서 확인할 수 있습니다.
AlexNet 논문에서 가져올 수 있는 주요 요점을 살펴보겠습니다. 우선, AlexNet은 (224x224x3)의 크기의 3채널 이미지로 작동했습니다. 서브 샘플링할 때 max pooling과 ReLU 활성화를 함께 사용했습니다. 卷積에 사용된 커널은 11×11, 5×5, 또는 3×3였고, max pooling에 사용된 커널 크기는 3×3였습니다. 그리고 이미지를 1000개의 클래스로 분류했으며, 다중 GPU를 활용했습니다.
데이터 세트
dataset을 로드하고 이전처리를 하겠습니다. 우리의 목적을 위해서는 CIFAR-10 데이터셋을 사용할 것입니다. 이 데이터셋은 10개의 클azz에 6000개의 32×32 크기의 색상이 들어가는 이미지로 구성되어 있으며, 각 클azz당 6000개의 이미지가 있습니다. 이것은 50000개의 훈련 이미지와 10000개의 테스트 이미지로 구성되어 있습니다.
이 dataset의 클azz들과 각 클azz에 대한 10개의 임의의 サンプル 이미지가 다음과 같습니다:
소스: source
dataset의 클azz는 完全に 상호 排他的합니다. 자동차와 트럭 사이에는 겹치는 것이 없습니다. “자동차”는 Sedan, SUVs 등과 같은 것을 포함합니다. “트럭”는 단지 큰 트럭을 포함합니다. 이들 중에는 펜시트 트럭은 포함되지 않습니다.
라이브러리 導入
まず, 필요한 라이브러리를 導入하고 device 변수를 정의하여 노트북이 GPU를 사용하여 모델 훈련을 할 것인지 알 수 있도록 합니다.
import numpy as np
import torch
import torch.nn as nn
from torchvision import datasets
from torchvision import transforms
from torch.utils.data.sampler import SubsetRandomSampler
# 디바이스 구성
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
dataset 로딩
torchvision
라이브러리를 사용하여 우리의 데이터셋을 로드할 것입니다. 이 方法的에는 입력 전처리를 간편하게 도울 수 있는 몇몇 도우미 함수가 포함되어 있습니다. get_train_valid_loader
과 get_test_loader
함수를 정의하고, 그 다음에 이를 호출하여 CIFAR-10 데이터를 로드하고 처리할 것입니다.:
def get_train_valid_loader(data_dir,
batch_size,
augment,
random_seed,
valid_size=0.1,
shuffle=True):
normalize = transforms.Normalize(
mean=[0.4914, 0.4822, 0.4465],
std=[0.2023, 0.1994, 0.2010],
)
# 변환 정의
valid_transform = transforms.Compose([
transforms.Resize((227,227)),
transforms.ToTensor(),
normalize,
])
if augment:
train_transform = transforms.Compose([
transforms.RandomCrop(32, padding=4),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
normalize,
])
else:
train_transform = transforms.Compose([
transforms.Resize((227,227)),
transforms.ToTensor(),
normalize,
])
# 데이터셋 로드
train_dataset = datasets.CIFAR10(
root=data_dir, train=True,
download=True, transform=train_transform,
)
valid_dataset = datasets.CIFAR10(
root=data_dir, train=True,
download=True, transform=valid_transform,
)
num_train = len(train_dataset)
indices = list(range(num_train))
split = int(np.floor(valid_size * num_train))
if shuffle:
np.random.seed(random_seed)
np.random.shuffle(indices)
train_idx, valid_idx = indices[split:], indices[:split]
train_sampler = SubsetRandomSampler(train_idx)
valid_sampler = SubsetRandomSampler(valid_idx)
train_loader = torch.utils.data.DataLoader(
train_dataset, batch_size=batch_size, sampler=train_sampler)
valid_loader = torch.utils.data.DataLoader(
valid_dataset, batch_size=batch_size, sampler=valid_sampler)
return (train_loader, valid_loader)
def get_test_loader(data_dir,
batch_size,
shuffle=True):
normalize = transforms.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225],
)
# 변환 정의
transform = transforms.Compose([
transforms.Resize((227,227)),
transforms.ToTensor(),
normalize,
])
dataset = datasets.CIFAR10(
root=data_dir, train=False,
download=True, transform=transform,
)
data_loader = torch.utils.data.DataLoader(
dataset, batch_size=batch_size, shuffle=shuffle
)
return data_loader
# CIFAR10 데이터셋
train_loader, valid_loader = get_train_valid_loader(data_dir = './data', batch_size = 64,
augment = False, random_seed = 1)
test_loader = get_test_loader(data_dir = './data',
batch_size = 64)
이 코드를 분석해 봅시다:
- 우리는 각각 트레이닝/검증 세트와 테스트 세트를 로드하기 위한 두 가지 함수
get_train_valid_loader
와get_test_loader
를 정의합니다. - 우리는 시작하기 전에 변수
normalize
를 정의하고 dataset의 각 채널(빨강, 녹색, 蓝色字体)에 대한 평균과 표준 편차를 담을 것입니다. 이러한 정보는 수동으로 계산할 수 있으며, CIFAR-10이 매우 인기 있기 때문에 线上에도 제공되는 것입니다. - 우리의 트레이닝 데이터셋에 대해, 데이터셋을 보다 鲁棒하게 만들고 이미지 수를 increase하기 위해 데이터셋을 进一步增强하는 옵션을 추가합니다. 참고: 증強은 전체 트레이닝 subset에만 적용되며, 검증과 테스트 subset은 평가 목적에 의해만 사용되므로 这些에 대해 적용되지 않습니다.
- 트레이닝 데이터셋을 트레이닝과 검증 세트로 분할하고 (90:10 비율), 전체 트레이닝 세트에서 임의의 subset으로 randomly 분할합니다.
- dataset을 로드할 때 batch size를 지정하고, 데이터를 shuffle하게 하여 각 batch에서 다양한 label 유형을 가진 것처럼하여 variance를 increase합니다. 이것은 우리가 얻는 모델의 정확도를 향상시킬 것입니다.
- 결국, 데이터 로더를 사용합니다. CIFAR-10과 같은 작은 데이터셋일 때 성능에 영향을 미치지 않지만, 대规模 데이터셋일 때 성능을 critical하게 제한할 수 있으며, 일반적으로 좋은 惯例입니다. 데이터 로더는 우리가 batch로 데이터를 이룰 수 있게 해줍니다. 이를 이용하여 데이터를 시작할 때에는 RAM에 모두 동시에 삽입되지 않고, 이룰 때 동안에 삽입합니다.
AlexNet from Scratch
이제 코드에 시작해봅시다:
class AlexNet(nn.Module):
def __init__(self, num_classes=10):
super(AlexNet, self).__init__()
self.layer1 = nn.Sequential(
nn.Conv2d(3, 96, kernel_size=11, stride=4, padding=0),
nn.BatchNorm2d(96),
nn.ReLU(),
nn.MaxPool2d(kernel_size = 3, stride = 2))
self.layer2 = nn.Sequential(
nn.Conv2d(96, 256, kernel_size=5, stride=1, padding=2),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.MaxPool2d(kernel_size = 3, stride = 2))
self.layer3 = nn.Sequential(
nn.Conv2d(256, 384, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(384),
nn.ReLU())
self.layer4 = nn.Sequential(
nn.Conv2d(384, 384, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(384),
nn.ReLU())
self.layer5 = nn.Sequential(
nn.Conv2d(384, 256, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.MaxPool2d(kernel_size = 3, stride = 2))
self.fc = nn.Sequential(
nn.Dropout(0.5),
nn.Linear(9216, 4096),
nn.ReLU())
self.fc1 = nn.Sequential(
nn.Dropout(0.5),
nn.Linear(4096, 4096),
nn.ReLU())
self.fc2= nn.Sequential(
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 = out.reshape(out.size(0), -1)
out = self.fc(out)
out = self.fc1(out)
out = self.fc2(out)
return out
AlexNet 모델 정의
위의 코드가 어떻게 작동하는지 들어봅시다:
- PyTorch에서 Neural Network(CNN 또는 그렇지 않은 것)을 정의하는 첫 단계는
nn.Module
를 상속 받는 클래스를 정의하는 것입니다. 이러한 클래스는 우리가 사용할 것인 많은 方法들을 포함합니다. - 그 다음 두 가지 주요 단계가 있습니다. 첫 번째는
__init__
내에서 우리가 CNN에서 사용할 레이어를 초기화하는 것입니다. 다음은 이러한 레이어들이 이미지를 처리할 순서를 정의하는 것입니다. 이것은forward
함수 내에서 정의합니다. - 자신의 구조로 대응하여 필요한 컨볼루 saration 层层을
nn.Conv2D
함수를 사용하여 적절한 内核 사이즈와 입력/출력 채널로 정의하고nn.MaxPool2D
함수를 사용하여 마이크 풀링을 적용합니다. PyTorch의 좋은 점은 컨볼루 saration 层层, 활성화 函數, 마이크 풀링을 하나의 基层(layer)로 결합할 수 있다는 것입니다.nn.Sequential
函數을 사용하여 여러 基层을 연속적으로 적용하는 것은 관리하기 도움이 됩니다. -
다음으로, 전체 연결 层层을 사용하여 linear (
nn.Linear
), dropout (nn.Dropout
), ReLU 활성화 函數 (nn.ReLU
)를 결합하고 이를nn.Sequential
函數과 함께 사용합니다. -
마지막으로, 마지막 层层은 10개의 神经元(neurons)를 출력합니다. 이것은 10개의 클래스의 物体的 마지막 예측이 됩니다.
超参数 设置
사용하기 전에 一些 超参数를 설정해야 합니다. 比如, 손실 函數, 사용할 오PTIMIZER, batch size, 학습률(learning rate), 에폭(epoch) 수 등입니다.
num_classes = 10
num_epochs = 20
batch_size = 64
learning_rate = 0.005
model = AlexNet(num_classes).to(device)
# 손실 函數과 오PTIMIZER
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate, weight_decay = 0.005, momentum = 0.9)
모델을 训练하는 것을 시작합니다.
total_step = len(train_loader)
우리는 간단한 하이퍼パラ미터(에poch, 배치 사이즈, leaned 률)을 정의하고 클래스 수를 인자로 모델을 초기화하는 것을 시작하고, 이 사례에서는 10과 모델을 적절한 장치(CPU 또는 GPU)로 이전시키는 것입니다. 그 다음, 우리는 지표函數으로 交叉熵 손실을 정의하고 최적화기를 아담스로 정의합니다. 이러한 지표函數과 최적화기에 대해 많은 선택이 있지만, 이들은 모델과 given 데이터로 좋은 result를 얻는 것이 유용합니다. 결국, total_step
를 정의하여 훈련 과정에서 스텝을 更好地 추적하는 것을 합니다
Training
이 时刻, 우리는 모델 训练을 시작할 준비가 되었습니다:
total_step = len(train_loader)
for epoch in range(num_epochs):
for i, (images, labels) in enumerate(train_loader):
# Tensor를 構성 device로 이동하는 것
images = images.to(device)
labels = labels.to(device)
# 전방향 통과
outputs = model(images)
loss = criterion(outputs, labels)
# 역방향과 최적화하는 것
optimizer.zero_grad()
loss.backward()
optimizer.step()
print ('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}'
.format(epoch+1, num_epochs, i+1, total_step, loss.item()))
# Validation
with torch.no_grad():
correct = 0
total = 0
for images, labels in valid_loader:
images = images.to(device)
labels = labels.to(device)
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
del images, labels, outputs
print('Accuracy of the network on the {} validation images: {} %'.format(5000, 100 * correct / total))
이 代码이 무엇을 하는지 보여 주세요:
- 우리는 에poch의 수를 이룰 것으로 시작하고, 그 다음 우리의 TCN 데이터에서 배치를 이룰 것입니다
- 이미지와 레이블을 우리가 사용하는 장치에 따라 변환하는 것, 즉, GPU 또는 CPU
- 전방향 통과를 통해 모델을 사용하여 예측을 하고, 실제 레이블과 那些 예측에 따라 손실을 계산하는 것
- 그 다음, 역방향 통과를 하는 것을 통해 우리의 가중치를 改善하기 위해 실제로 更新하는 것입니다
- 그 다음, Update하기 전에 가중치를 零으로 하는
optimizer.zero_grad()
함수를 사용하여 그 값을 정의합니다 - 그 다음,
loss.backward()
함수를 사용하여 新的 가중치를 계산하는 것을 합니다. - 마지막으로,
optimizer.step()
함수를 사용하여 가중치를 갱신합니다. - 또한, 모든 에폭의 끝에 validation set을 사용하여 모델의 정확도를 계산합니다. 이 경우 gradient를 사용하지 않으므로
with torch.no_grad()
를 사용하여 빠른 평가를 위해 사용합니다.
출력은 다음과 같습니다:
Train Loss and Validation Accuracy
正如我们所看到的,损失随着每一个epoch而减少,这表明我们的模型确实在学习。需要注意的是,这个损失是在训练集上计算的,如果损失过小,则可能表示过拟合。这就是我们也要使用validation set的原因。validation set上的准确度似乎在增加,这表明过拟合的可能性很小。让我们现在测试一下我们的模型,看看它的表现如何。
Testing
现在,我们来看看我们的模型在未见数据上的表现:
with torch.no_grad():
correct = 0
total = 0
for images, labels in test_loader:
images = images.to(device)
labels = labels.to(device)
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
del images, labels, outputs
print('Accuracy of the network on the {} test images: {} %'.format(10000, 100 * correct / total))
注意,代码与我们的validation目的完全相同。
只训练6个epoch,使用模型似乎在validation set上得到了大约78.8%的准确度。
Testing Accuracy
결론
이제 article에서 우리가 하였던 것을 결론ize해보겠습니다:
- 우리는 우선 AlexNet 모델의 아키텍тура와 다양한 层次들을 이해하였습니다.
- 그 다음,
torchvision
을 사용하여 CIFAR-10 데이터셋을 로드하고 preliminarily process 했습니다. - 그 다음,
PyTorch
를 사용하여 scratch에서 AlexNet 모델을 만들었습니다. - 결국, CIFAR-10 데이터셋에 대해 我们的 모델을 训练하고 테스트하였고, 모델은 训练 (6 epochs)이 minimal하게 끝나도 이 시험 데이터셋에 대해 좋은 성능을 보였습니다.
과정 향후 작업
이 글은 견고한 소개와 실제 경험을 제공하지만, 더 많은 지식을 얻을 수 있는 것을 발견하기 위해 더 deeply explore하는 것이 좋습니다.
- 다른 데이터셋을 시도할 수 있습니다. 하나의 데이터셋은 CIFAR-100입니다. 이것은 CIFAR-10 데이터셋을 100개의 클래스로 확장한 것입니다.
- 다양한 hyperparameters를 실험하여 이를 모델에 대한 가장 좋은 조합을 보기 위해 시도할 수 있습니다.
- 결국, 데이터셋에서 层次을 추가하거나 제거하여 그들이 모델의 능력에 대한 영향을 보기 위해 시도할 수 있습니다.
Source:
https://www.digitalocean.com/community/tutorials/alexnet-pytorch