3.2 Sample of CNN - fashion_mnist
3.2.1 fasion_mnist Dataset
fashion_mnist 데이터셋은 torchvision에 내장된 예제 데이터. 28×28 픽셀의 이미지 7만 장으로 이루어져 있으며 10 가지 label로 분류가 가능하다.
train_images는 0~255 사이의 정수값을 갖는 28×28 NumPy 배열이며 train_labels는 0~9까지의 정수값을 갖는다.
Label | Class |
---|---|
0 | T-Shirt |
1 | Trouser |
2 | Pullover |
3 | Dress |
4 | Coat |
5 | Sandal |
6 | Shirt |
7 | Sneaker |
8 | Bag |
9 | Ankle Boot |
3.2.2 Download Dataset
먼저 torchvision을 사용해 fashion_mnist 데이터셋을 다운받자.
Line 13 : GPU 사용 설정이 되어 있으면 pytorch는 GPU를 인식. 설정이 안 되어 있으면 CPU 사용.
Line 15 :
- "../DL/Ch 3/3.1/data" : FasionMNIST를 내려받을 위치 지정
- download : True 로 변경 시 첫 번째 parameter의 위치에 해당 dataset이 있는지 확인한 후 다운로드
- transform : 이미지를 tensor(0 ~ 1)로 변경
DataLoader
는 내려받은 fashion_mnist 데이터를 메모리로 불러온다. 이때 원하는 크기의 batch, 단위로 데이터를 불러오거나 순서를 무작위로 섞을 수 있다.
Dataset이 잘 불러와졌는지 확인해보자. 20개의 이미지를 label 정보와 함께 출력한다.
3.2.2 DNN
CNN과 비교하기 위해 먼저 Deep neural network를 만들어보자.
Line 2 : class 형태의 모델은 항상 torch.nn.Module을 상속받는다. __init__()은 객체가 갖는 속성값을 초기화하며 객체가 생성될 때 자동으로 호출된다.
Line 3 : super(FashionDNN, self).__init__()은 FashionDNN이라는 부모 클래스를 상속받겠다는 의미이다.
Line 4 : nn은 deep learning model 구성에 필요한 module들이 모인 package. Linear는 단순 선형회귀 모델.
- in_features : input size.
- out_features : output size
실제 연산이 진행되는 forward()에는 첫 param.만 넘겨주고 두 번째 param.에서 정의된 크기가 forward() 연산의 결과
Line 5 : torch.nn.Dropout(p)는 p의 비율만큼 tensor값이 0이 되고 0이 되지 않는 값들은 기존 값에 1/(1-p)만큼 곱해져 커진다.
Line 9 : forward() 함수는 model이 train data를 받아 forward propagation 학습을 진행한다. 반드시 이름은 forward()여야 한다!
Line 10 : pytorch의 view는 numpy의 resize와 같이 tensor의 크기를 바꿔 준다. input_data.view(-1, 784)는 input_data를 크기 (?, 784)로 변경하라는 뜻. 첫 번째 차원 -1은 pytorch에게 알아서 맡겠다는 뜻이다.
Line 11 : activation fcn의 지정은 다음 두 방법을 쓴다.
- F.relu() : forward() 함수 내에서 정의
- nn.ReLU() : __init__() 함수 내에서 정의
근본적으로 nn.functional.xx()
와 nn.xx()
의 차이는 사용 방법에 있다.
nn.Conv2d
에서는 input_channel과 output_channel을 사용해 연산하는 반면, functional.conv2d
는 직접 input과 weight를 넣어 준다. 이 말인즉슨, weight를 전달해야 할 때마다 weight 값을 새로 정의해야 한다는 뜻이다.
구분 | nn.xx() |
nn.functional.xx() |
---|---|---|
형태 | nn.Conv2d: 클래스 nn.Module 클래스를 상속받아 사용 |
nn.functional.conv2d: 함수 def function (input)으로 정의된 순수한 함수 |
호출 방법 | 먼저 hyperparameter를 전달하고 함수 호출을 통해 data 전달 | 함수를 호출할 때 hyperparameter, data 전달 |
위치 | nn.Sequential 내에 위치 | nn.Sequential 내에 위치할 수 없음 |
Parameter | 새로 정의할 필요 없음 | weight를 수동으로 전달해야 할 때마다 자체 weight를 정의 |
학습하기 전에 loss fcn., learning rate, optimizer를 정의.
Result :
FashionDNN(
(fc1): Linear(in_features=784, out_features=256, bias=True)
(drop): Dropout2d(p=0.25, inplace=False)
(fc2): Linear(in_features=256, out_features=128, bias=True)
(fc3): Linear(in_features=128, out_features=10, bias=True)
)
이제 DNN을 사용한 학습이다. 주의해야 할 점은, model과 data는 항상 동일한 장치(CPU, GPU)에서 처리되어야 한다는 것이다.
Result :
Iteration: 500, Loss: 0.5451766848564148, Accuracy: 83.2699966430664%
Iteration: 1000, Loss: 0.4473138749599457, Accuracy: 84.57999420166016%
Iteration: 1500, Loss: 0.33242782950401306, Accuracy: 84.3499984741211%
Iteration: 2000, Loss: 0.36623263359069824, Accuracy: 85.47000122070312%
Iteration: 2500, Loss: 0.26425182819366455, Accuracy: 86.25%
Iteration: 3000, Loss: 0.34857454895973206, Accuracy: 86.32999420166016%
최종 정확도는 86.3%. 그럼 CNN은 어떨까?
3.2.2 CNN
CNN network를 생성하자.
Line 4 : nn.Sequential을 사용하면 __init__()에서 사용할 network model을 정의해주는데다가 forward()에서 구현될 forward propagation을 layer 형태로 보기 좋게 작성한다.
즉, layer를 차례로 쌓을 수 있도록 Wx + b와 같은 수식과 activation fcn.을 연결해 준다. 여러 개 layer를 하나의 container에 구현하기 딱 좋다.
Line 5 : conv. layer는 convolution 연산을 통해 image의 feature를 추출. Sec 3.1을 다시 보자. kernel이라는 n × m 행렬이 (높이) × (너비) 크기의 image를 훑으면서 원소끼리 곱해 모두 더한 값을 출력.
- in_channels : 입력 channel의 수. 흑백 image는 1, RGB image는 3
3D로 생각해 보면, channel은 결국 depth를 의미하기도 한다.
- out_channels : 출력 channel의 수
- kernel_size : kernel 또는 filter 사이즈. CNN의 학습 대상은 바로 filter parameter.
만일 직사각형 kernel을 쓰고 싶다면 (3, 5)처럼 지정하자.
- padding : padding 값이 클수록 output 크기도 커진다.
Line 6 : BatchNorm2d는 학습 과정에서 각 batch 단위별로 data가 다양한 분포를 갖더라도 정규화시키겠다는 의미이다. 평균은 0, 표준편차는 1로 조정된다.
Line 8 : MaxPool2d는 image 크기를 축소시킨다.
- kernel_size : m × n 행렬로 구성된 weight
- stride : stride 값이 클수록 output은 작아진다.
Line 16 : class 분류를 위해서는 image 형태의 data를 array 형태로 변환해야 한다. output size는 Conv2d의 hyperparamet들에 의해 정의된다. padding, stride가 중요하다는 뜻.
이렇게 줄어든 output 크기가 최종 분류를 담당하는 fully connected layer로 전달된다.
- in_features : input data 크기. output 결과를 fully connected layer로 전달하기 위해서는 1D로 변경해야 한다.
- Conv2d Layer
output_volume_size = (input_volume_size - kernel_size + 2 * padding_size) / strides + 1
fashion_mnist의 input_volume_size는 784이며, stride의 기본값은 (1,1)이므로 계산하면 output_volume_size는 784이다.
그러므로 output 형태는 [32, 784, 784]가 된다.
- MaxPool2d layer
output_volume_size = input_filter_size / kernel_size
kernel_size가 2이므로 첫 번째 MaxPool2d layer의 output 크기는 [32, 392, 392]가 된다. 여기서 첫 번째 성분 32는 앞 Conv2d layer의 out_channels.
Line 24 : conv.layer에서 fully connected layer로 변경되므로 data를 1D로 변경. 이때 out.size(0)은 결국 100을 의미한다. -1은 column의 수를 알지 못하기 때문.
---
Result :
FashionCNN(
(layer1): Sequential(
(0): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
(3): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(layer2): Sequential(
(0): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1))
(1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
(3): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(fc1): Linear(in_features=2304, out_features=600, bias=True)
(drop): Dropout2d(p=0.25, inplace=False)
(fc2): Linear(in_features=600, out_features=120, bias=True)
(fc3): Linear(in_features=120, out_features=10, bias=True)
)
학습해 보자. 코드 자체는 앞 DNN의 경우와 똑같다.
Result :
Iteration: 500, Loss: 0.5134249925613403, Accuracy: 87.9000015258789%
Iteration: 1000, Loss: 0.3914521336555481, Accuracy: 87.06999969482422%
Iteration: 1500, Loss: 0.3010081648826599, Accuracy: 88.31999969482422%
Iteration: 2000, Loss: 0.21716825664043427, Accuracy: 88.69999694824219%
Iteration: 2500, Loss: 0.1348704844713211, Accuracy: 89.58999633789062%
Iteration: 3000, Loss: 0.20500893890857697, Accuracy: 90.5%
정확도는 90.5%. DNN과 비교해서 높아졌다!