0~9까지의 손글씨 숫자 데이터인 MNIST을 대상으로 99% 이상의 분류 정확도에 대한 모델 테스트에 대한 글입니다. 이와 유사한 글이 2개 있었는데, 첫번째는 퍼셉트론 방식이고 두번째는 DNN 방식입니다. 아래는 MNIST 데이터셋을 DNN 방식으로 분류한 글입니다.
위의 글과는 다르게 이 글은 CNN 방식으로 분류 정확도를 개선했습니다. MNIST 분류에 대한 정확도 순위를 보면 약 40개여의 순위가 존재합니다. 다행인건 이 글의 모델이 이 순위의 상단에 위치한다는 것입니다.
파이토치를 사용했으며, 해당 코드를 대략적으로 살펴보면 다음과 같습니다. 늘 그렇듯이 필요한 패키지를 포함합니다.
import torch import torch.nn as nn import torch.optim as optim import torchvision.datasets as dset import torchvision.transforms as transforms from torch.utils.data import DataLoader from torch.optim import lr_scheduler
배치 크기와 학습률, 에폭단위의 반복할 학습수를 먼저 지정합니다. 손실값에 큰 영향을 주는 하이퍼 파라메터입니다.
batch_size = 100 learning_rate = 0.001 num_epoch = 20
학습과 테스트를 위한 MNSIT 데이터를 로딩합니다.
mnist_train = dset.MNIST("./", train=True, transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize(mean=(0.1307,), std=(0.3081,)) ]), target_transform=None, download=True) mnist_test = dset.MNIST("./", train=False, transform=transforms.Compose([ transforms.ToTensor(), transforms.Normalize(mean=(0.1307,), std=(0.3081,)) ]), target_transform=None, download=True) train_loader = DataLoader(mnist_train, batch_size=batch_size, shuffle=True, drop_last=True) test_loader = DataLoader(mnist_test, batch_size=batch_size, shuffle=False, drop_last=True)
중요한 점은 데이터에 대한 정규화가 적용됩니다. 정규화 방식은 전체 데이터에 대한 평균과 표준편차를 사용했습니다. 이 표준과 표준편차를 구하는 방식은 아래글을 참고하기 바랍니다.
다음은 신경망입니다.
class CNN(nn.Module): def __init__(self): super(CNN,self).__init__() self.layer = nn.Sequential( nn.Conv2d(1, 16, 3, padding=1), nn.BatchNorm2d(16), nn.ReLU(), nn.Conv2d(16, 32, 3,padding=1), nn.BatchNorm2d(32), nn.ReLU(), nn.MaxPool2d(2, 2), nn.Conv2d(32, 64, 3,padding=1), nn.BatchNorm2d(64), nn.ReLU(), nn.MaxPool2d(2, 2) ) self.fc_layer = nn.Sequential( nn.Linear(64*7*7, 128), nn.BatchNorm1d(128), nn.ReLU(), nn.Linear(128, 64), nn.BatchNorm1d(64), nn.ReLU(), nn.Linear(64, 10), ) def forward(self,x): out = self.layer(x) out = out.view(batch_size, -1) out = self.fc_layer(out) return out
중요한 부분은 Convolution Layer와 Affine Layer에 배치정규화가 반영되어 있다는 점입니다. 물론 신경망 구성 레이어와 각 레이어의 뉴런수도 중요한 하이퍼파라메터입니다.
학습 코드입니다. 미분값 계산을 통해 얻어진 경사도 방향으로의 하강 방식은 Adam 방식을 사용했고, 학습률을 고정하지 않고 ReduceLROnPlateau 클래스를 사용하여 손실값의 감소 경향에 따라 변경하도록 하였습니다.
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") model = CNN().to(device) loss_func = nn.CrossEntropyLoss().to(device) optimizer = optim.Adam(model.parameters(), lr=learning_rate) scheduler = lr_scheduler.ReduceLROnPlateau(optimizer,threshold=0.1, patience=1, mode='min') for i in range(1, num_epoch+1): for _,[image,label] in enumerate(train_loader): x = image.to(device) y_= label.to(device) optimizer.zero_grad() output = model.forward(x) loss = loss_func(output, y_) loss.backward() optimizer.step() scheduler.step(loss) print('Epoch: {}, Loss: {}, LR: {}'.format(i, loss.item(), scheduler.optimizer.state_dict()['param_groups'][0]['lr']))
학습 데이터를 통해 학습된 모델을 테스트 데이터를 통해 정확도를 얻는 코드는 다음과 같습니다.
correct = 0 total = 0 model.eval() with torch.no_grad(): for image,label in test_loader: x = image.to(device) y_= label.to(device) output = model.forward(x) _,output_index = torch.max(output, 1) total += label.size(0) correct += (output_index == y_).sum().float() print("Accuracy of Test Data: {}%".format(100.0*correct/total))
실제 실행해 보면, 정확도가 99.5을 넘기는 것을 볼 수 있었습니다. 아래는 그 중 하나의 출력 결과입니다.
Epoch:2, Loss: 0.00971189048141241, LR: 0.001
Epoch:3, Loss: 0.030652683228254318, LR: 0.001
Epoch:4, Loss: 0.011247940361499786, LR: 0.0001
Epoch:5, Loss: 0.001827826490625739, LR: 0.0001
Epoch:6, Loss: 0.0049532032571733, LR: 0.0001
Epoch:7, Loss: 0.0009714126354083419, LR: 0.0001
Epoch:8, Loss: 0.001510353060439229, LR: 0.0001
Epoch:9, Loss: 0.00044545173295773566, LR: 0.0001
Epoch:10, Loss: 0.0010514688910916448, LR: 0.0001
Epoch:11, Loss: 0.0006617116741836071, LR: 1e-05
Epoch:12, Loss: 0.0009317684452980757, LR: 1e-05
Epoch:13, Loss: 0.00043862819438800216, LR: 1.0000000000000002e-06
Epoch:14, Loss: 0.011570921167731285, LR: 1.0000000000000002e-06
Epoch:15, Loss: 0.0028407489880919456, LR: 1.0000000000000002e-07
Epoch:16, Loss: 0.00031417846912518144, LR: 1.0000000000000002e-07
Epoch:17, Loss: 0.0014804458478465676, LR: 1.0000000000000002e-07
Epoch:18, Loss: 0.012818637304008007, LR: 1.0000000000000004e-08
Epoch:19, Loss: 0.0010410761460661888, LR: 1.0000000000000004e-08
Epoch:20, Loss: 0.00025289534823969007, LR: 1.0000000000000004e-08
Accuracy of Test Data: 99.52999877929688%