activate pytorch_py38
in VS Code Command Prompt)main.py
with several arguments. (e.g. python main.py --mode test --download 1 --output_dir ./output --checkpoint ./output/model_epoch3.pt --dataset FashionMNIST
)
--mode
for choosing the mode of model - train, eval(evaluation), test. In ‘train’ mode, the model train the prepared dataset. ‘eval’ mode let the model calculate the accuracy of the prediction of the model by comparing the label of each image. In ‘test’ mode, the test image is shown with its predicted results.--download
: specify whether you download the dataset or not. (Bool)--output_dir
: specify where the output of the model(parameters, etc) is stored.--checkpoint
: specify which .pt
file you will use during evaluation or test mode--dataset
: specify the datasets you would use - MNIST or FashionMNISTlabel | description | label | description |
---|---|---|---|
0 | T-shirt/top | 1 | Trouser |
2 | Pullover | 3 | Dress |
4 | Coat | 5 | Sandal |
6 | Shirt | 7 | Sneaker |
8 | Bag | 9 | Ankle boot |
--dataset
argument
parser.add_argument('--dataset',
dest = 'dataset',
help = 'dataset to train model',
default = None,
type = str) # MNIST, FashionMNIST
self.dropout = nn.Dropout(p = 0.5)
self.bn0 = nn.BatchNorm2d(6)
self.bn1 = nn.BatchNorm2d(16)
self.bn2 = nn.BatchNorm2d(120)
tanh
function with LeakyReLU
. (기존에 사용했던 tanh 함수 대신 LeakyReLU 함수를 사용했다.)self.leakRelu = nn.LeakyReLU(0.1)
torch.nn.init.xavier_uniform_(self.conv0.weight)
torch.nn.init.xavier_uniform_(self.conv1.weight)
torch.nn.init.xavier_uniform_(self.conv2.weight)
torch.nn.init.xavier_uniform_(self.fc3.weight)
torch.nn.init.xavier_uniform_(self.fc4.weight)
기존에 연습에 사용한 MNIST 데이터는 32 x 32 사이즈였다. 그러나 FashionMNIST는 크기가 28 x 28이기 때문에 Transform
역시 같은 사이즈로 해야 할 것 같다고 생각했다.
단순히 Transform
크기만 조정하면 오류가 발생한다.
def get_data(name="MNIST"):
my_transform = transforms.Compose([
transforms.Resize([28, 28]), # 이 부분 수정
transforms.ToTensor(),
transforms.Normalize((0.5,), (1.0,))
])
#### MNIST 부분 생략 ####
elif name=="FashionMNIST":
download_root = "./fasion_mnist_dataset"
train_dataset = FashionMNIST(
root=download_root,
transform=my_transform,
train=True,
download=args.download)
eval_dataset = FashionMNIST(
root=download_root,
transform=my_transform,
train=False,
download=args.download
)
test_dataset = FashionMNIST(
root=download_root,
transform=my_transform,
train=False,
download=args.download
)
RuntimeError: Calculated padded input size per channel: (4 x 4). Kernel size: (5 x 5). Kernel size can't be greater than actual input size
이는 모델의 Layer을 고려하지 않아 발생한 문제이다. 에러 메시지를 보면 한 채널의 입력 크기는 (4x4)인데 커널 크기가 그보다 더 큰 (5x5)라 하고 있다.
더 자세한 문제 분석을 위해 기존 모델과 조정하고 싶은 모델을 비교해보았다.
먼저, 기존 모델의 경우이다.
합성곱층을 거치면 정사각 이미지 한 변(Width의 W라 하자)의 크기가 (W - K + 2P) / S} + 1
가 된다. LeNet-5에서 모든 합성곱층에서는 커널 크기(K)=5, 패딩 크기(P)=0, 스트라이드(S)=1로 동일하므로, 한 번의 합성곱층을 거치면 입력 크기보다 한 변이 4씩 줄어든다.
풀링층은 K=2, S=2이므로 한 번 거치면 변의 크기가 절반이 된다. 채널의 개수를 제외하고 이미지의 크기만 고려해보면 아래의 과정으로 크기가 변화한다.
32 --[ conv0 ]--> 28
--[ pool0 ]--> 14
--[ conv1 ]--> 10
--[ pool1 ]--> 5
--[ conv2 ]--> 1
따라서 conv2까지 거치면 채널은 120개, 크기는 1 X 1이 되고, 이를 완전연결계층으로 넘긴다.
120 --[ fc3 ]--> 84
--[ fc4 ]--> 10
반면 Fashion MNIST의 크기처럼 Transform을 하고 싶은 경우에는 단순히 입력 크기를 28 x 28로 변경했을 때 아래와 같은 흐름을 보인다.
28 --[ conv0 ]--> 24
--[ pool0 ]--> 12
--[ conv1 ]--> 8
--[ pool1 ]--> 4
--[ conv2 ]--> 0
출력 크기 계산식만으로 보면 conv2까지 거치면 크기가 0이 된다. 그도 그럴 것이, conv2 층 바로 직전 크기가 (4, 4)인데 커널은 (5, 5)에 패딩도 없다. 커널 크기보다 입력 크기가 작기 때문에 연산이 불가능하다. 에러 메시지도 그 내용을 담고 있었다.
따라서 레이어의 변경을 추가했다. 커널 사이즈를 5에서 3으로 줄이고 마지막 합성곱층 직후 풀링층 하나를 더 추가했다.(2 --[ pool2 ] --> 1
) 그러면 아래와 같은 흐름을 보이고, 에러가 발생하지 않는다.
28 --[ conv0 ]--> 26
--[ pool0 ]--> 13
--[ conv1 ]--> 10
--[ pool1 ]--> 5
--[ conv2 ]--> 2
--[ pool2 ]--> 1
또는 패딩을 2로 주어도 에러가 나지 않는다.
lr
=0.01, weight_decay
=0lr
=1e-3, weight_decay
=1e-5negative_slope
=0.1💻 optimizer = SGD일 때
no. | momentum | Dropout p | Activation Fun. | loss | eval. accuracy |
---|---|---|---|---|---|
1 | 0.9 | 0.1 | LeakyReLU | 1.721 | 시도 안함 |
2 | 0.9 | 0.3 | LeakyReLU | 1.628 | 시도 안함 |
3 | 0.9 | 0.5 | LeakyReLU | 1.664 | 시도 안함 |
4 | 0.9 | 0.5 | tanh | 1.668 | 시도 안함 |
5 | 0.9 | 0.3 | tanh | 1.613 | 85.53 % |
6 | 0.5 | 0.1 | tanh | 1.616 | 85.33 % |
💻 optimizer = Adam일 때
no. | Dropout p | Activation Fun. | loss | eval. accuracy |
---|---|---|---|---|
7 | 0.1 | tanh | 1.621 | 84.29 % |
8 | 0.5 | tanh | 1.632 | 84.60 % |
9 | 0.5 | LeakyReLU | 1.632 | 84.89 % |
지금은 grayscale의 작은 이미지이기 때문에 하이퍼 파라미터를 변경해도 큰 성능 변동이 없을 수 있다.
물론 32x32으로 Transform을 해도 충분히 실행은 된다. 이에 관한 팀원과 강사님의 코멘트는 몇 가지 Insight를 주었다.
💡입력 크기의 변화는 정확도나 손실의 차이에는 영향을 크게 미치지 않는다. 대신 연산 속도나 파라미터 크기에 영향을 준다
이를 확인하기 위해 같은 조건에서 모델의 파라미터를 확인해보았다. torchsummary
라이브러리를 활용했으며, 결과는 아래와 같다.
일단, 28 x 28 크기로 변환해 모델에 입력했을 때의 결과이다. 파라미터가 2만 9천개 정도 된다.
그럼 32 x 32 모델을 유지했을 때는 어떻게 되었을까. 결과는 파라미터 개수가 거의 6만 2천개에 가까웠다.
모델의 성능에 영향을 미치는 파라미터 개수가 급격히 줄어든 것을 볼 수 있었다. 따라서 작동 여부를 떠나 입력 크기를 줄이면 성능 저하가 일어날 위험이 있는 것이다. 실제로 Loss와 정확도를 비교해보면 성능 차가 있었는데, 아래 Loss 그래프에서 확연히 드러난다.
scale이 다른 것은 눈을 감아주시길 바란다. 😭 하도 시도를 많이 하다보니… 좌측은 입력 사이즈가 작았을 때(28x28) 우측은 컸을 때이다. 좌측에서 손실이 1.4 ~ 1.6 에서 떨어지지 않았다. 그러나 우측에서는 거의 0에 가까울 때도 많을 만큼 손실이 많이 줄고 있다.
사실 LeNet-5의 기본 입력 사이즈는 32 X 32이다. 입력 사이즈를 변경하면 weight params 수가 크게 줄고, 이는 곧 모델의 capacity가 작아지고 더 가벼운 모델이 된다. Lenet-5 자체가 가벼운 모델이므로 더 줄일 필요는 적다. 또한 대체적으로 입력 사이즈가 클 수록 성능이 향상될 수 있다.
Lenet-5의 forward()
부분에서 softmax 함수를 지우면 loss가 더 수렴한다. 계산에 사용되는 CrossEntrophyLoss 내에 포함되어 있어 중복 계산이 줄기 때문이다.
Matplotlib 라이브러리를 사용해 이미지를 출력하면 grayscale인 입력 이미지가 출력에서는 푸르스름하게 나온다. PIL을 이용하면 원래대로 Grayscale이 된다.