[Model]PyTorch로 Deep Learning model 구현하기(base model: LeNet)
LeNet¶
paper: http://vision.stanford.edu/cs598_spring07/papers/Lecun98.pdf
refhow to make LeNet with PyTorch, youtuber: Aladdin Persson:
https://www.youtube.com/watch?v=fcOW-Zyb5Bo&list=PLhhyoLH6IjfxeoooqP9rhU3HJIAVAJ3Vz&index=16
만약 convolutional network, pooling 등 CNN의 기본적인 알고리즘에 대해 어떻게 작동하는지 알고 싶으시다면
https://cs231n.github.io/convolutional-networks/
Linear function에 대해 알고 싶으시다면
https://cs231n.github.io/linear-classify/
작성자 blog: https://self-deeplearning.blogspot.com/
작성자 github: https://github.com/withAnewWorld/models_from_scratch
from google.colab import drive
import sys
import os
drive.mount('/content/drive')
FOLDERNAME = 'models_from_scratch'
sys.path.append('/contente/drive/My Drive/{}'.format(FOLDERNAME))
%cd /content/drive/My Drive/$FOLDERNAME
Mounted at /content/drive /content/drive/My Drive/models_from_scratch
import torch
import torch.nn as nn
from IPython import display
# LeNet architecture from LeNet paper Fig. 2
display.Image(os.path.join(os.getcwd(), 'pic/LeNet.png'))
LeNet architecture¶
# psudo code
def LeNet():
'''
image classification task를 진행한다고 가정.
이미지 tensor를 LeNet에 feed할 경우 해당 이미지의 각 class의 logits(예측값)을 반환.
이 중 logit값이 가장 큰 값이 해당 network가 예측하는 class이다.
img의 크기(height, width)는 위의 예와 같이 32 x 32라고 가정.
inputs:
- x(Tensor[N, C, H, W])
- num_classes(int):
returns:
- LeNet(deep learning model)
'''
convolution network를 거친 후 image의 크기는 다음과 같이 변하게 된다.
W = 1 + (W - kernel_size + 2padding)/(stride) (no dilation conv)
H = 1 + (H - kernel_size + 2padding)/(stride) (no dilation conv)
논문의 저자는 모든 conv net의 kernel_size를 5x5로 고정
따라서, C1을 거친후 32x32의 image tensor가 28x28로 바뀌기 위해서 stride = 2
(모든 conv net padding = 0으로 고정)
이같은 방식으로 모든 conv net의 kernel_size, stride, padding을 설정할 수 있다.
Subsampling의 경우 average pool을 사용. conv net과 같이 Tensor의 height, width를 설정한다.
# psudo code
def LeNet():
nn.Conv2d(in_channels = 32,
out_channels = 6,
kernel_size = (5, 5),
stride = 2,
padding = 0)
nn.AvgPool2d(kernel_size = (2, 2),
stride = 2)
nn.Conv2d(in_channels = 6,
out_channels = 16,
kernel_size = (5, 5),
stride = 2,
padding = 0)
nn.AvgPool2d(kernel_size = (2, 2),
stride = 2)
nn.Conv2d(in_channels = 16,
out_channels = 120,
kernel_size = (5, 5),
stride = 2,
padding = 0)
nn.Flatten()
nn.Linear(120, 84)
nn.Linear(84, num_classes)
PyTorch model API
1) torch.nn
2) torch.nn.Module
3) torch.nn.Sequential
torch.nn¶
torch.nn에 존재하는 neural network function들을 하나씩 이용해서 model을 설계하는 방법.
def LeNet(x, num_classes):
'''
inputs:
- X(Tensor[N, C, H, W]): input image (N: num_images, C: num_channels, H: image Height, W: image Width)
- num_classes(int): how many expected labels
'''
C1 = nn.Conv2d(in_channels = 1,
out_channels = 6,
kernel_size = (5, 5),
stride= (1, 1),
padding = (0, 0))
S2 = nn.AvgPool2d(kernel_size = (2,2),
stride = (2,2))
C3 = nn.Conv2d(in_channels = 6,
out_channels = 16,
kernel_size = (5, 5),
stride= (1, 1),
padding = (0, 0))
S4 = nn.AvgPool2d(kernel_size = (2,2),
stride = (2,2))
C5 = nn.Conv2d(in_channels = 16,
out_channels = 120,
kernel_size = (5, 5),
stride= (1, 1),
padding = (0, 0))
F6 = nn.Linear(in_features = 120, out_features = 84)
flatten = nn.Flatten()
classifier = nn.Linear(in_features = 84, out_features = num_classes)
x = C1(x)
print(x.size())
x = S2(x)
print(x.size())
x = C3(x)
print(x.size())
x = S4(x)
print(x.size())
x = C5(x)
print(x.size())
x = flatten(x)
print(x.size())
x = F6(x)
print(x.size())
x = classifier(x)
print(x.size())
return x
torch.manual_seed(42) # deterministic
x = torch.randn(64, 1, 32, 32)
output = LeNet(x, 10)
torch.Size([64, 6, 28, 28]) torch.Size([64, 6, 14, 14]) torch.Size([64, 16, 10, 10]) torch.Size([64, 16, 5, 5]) torch.Size([64, 120, 1, 1]) torch.Size([64, 120]) torch.Size([64, 84]) torch.Size([64, 10])
output # in this case output[4] is the highest
tensor([[-0.0919, 0.0251, 0.0594, 0.0857, 0.0512, 0.1431, 0.0134, 0.0488,
0.1026, -0.0060]], grad_fn=<AddmmBackward0>)
nn.Module¶
위의 방법과 같이 torch.nn에 존재하는 network functions을 매번 일일이 설정하여 개발을 하기에 여러가지 문제가 존재
1) 모델의 크기가 커질수록 코드 가독성 현저하게 떨어짐
-> 이에 따라 code refactoring 불가능해짐
2) 현재 많은 deep learning model의 경우 module화 되어 있기 때문에 특정 layers(block)을 교체하거나 수정하는 경우가 빈번. 하지만 위의 방법대로는 매우 어려움.
3) torch에서 제공하는 많은 API를 사용하기 어려워짐 ex)GPU 사용, train / eval mode, model save / load 등
see other utils in nn.Module if you want
https://pytorch.org/docs/stable/generated/torch.nn.Module.html
PyTorch를 사용하는 많은 개발자는 nn.Module을 이용하여 DL model 개발 중
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__() <- nn.Module의 init함수 상속
def forward(self, x):
PyTorch는 model 설계에 필요한 까다로운 함수들을 모두 nn.Module Class에 구현해 놓았습니다.
이에 따라 개발자는 model을 설계할 때 nn.Module을 상속받음으로써 tricky한 part에 대해 신경쓰지 않고 model의 network를 초기화할 init 함수와 model에 feed할 input의 작동방식(forward)만을 고려하면 손쉽게 model을 만들 수 있습니다.
주의해야할 점은 model에 input을 feed할 때 forward함수를 직접 호출하는 것이 아닌 model을 초기화 하고 변수로 설정하셔야 합니다.
model = Net()
out = model.forward(x) # <- don't do ilke this!
out = model(x) # <- it's correct
class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
self.relu = nn.ReLU()
self.pool = nn.AvgPool2d(kernel_size = (2,2),
stride = (2,2))
self.conv1 = nn.Conv2d(in_channels = 1,
out_channels = 6,
kernel_size = (5, 5),
stride= (1, 1),
padding = (0, 0))
self.conv2 = nn.Conv2d(in_channels = 6,
out_channels = 16,
kernel_size = (5, 5),
stride= (1, 1),
padding = (0, 0))
self.conv3 = nn.Conv2d(in_channels = 16,
out_channels = 120,
kernel_size = (5, 5),
stride= (1, 1),
padding = (0, 0))
self.linear1 = nn.Linear(120, 84)
self.linear2 = nn.Linear(84, 10)
def forward(self, x):
x = self.relu(self.conv1(x))
x = self.pool(x)
x = self.relu(self.conv2(x))
x = self.pool(x)
x = self.relu(self.conv3(x)) # Nx120x1x1 -> Nx120
x = x.reshape(x.shape[0], -1)
x = self.relu(self.linear1(x))
return self.linear2(x)
x_train = torch.randn((64, 1, 32, 32))
model = LeNet()
with torch.no_grad():
out = model(x_train)
out.size()
torch.Size([64, 10])
nn.Sequential¶
model을 설계할 때 nn.Module로 작성하는 것도 적절하지 않을경우
(ex) class로 설계하기에 code가 길게 느껴지거나 가독성이 떨어진다고 느낄 경우 등)
nn.Seuqnetial을 사용하여 model을 설계할 수 있습니다.
위의 예시들에서 model 설계를 직접 보신 결과 순서대로 layer를 작성하여 이를 model로 만들면 편할 것 같다고 생각하실 것입니다.
nn.Seuqntial은 layer을 순서대로 설정하고 input을 feed하면 순서에 따라 layer를 적용합니다.
cf) nn.Seuqntial(block)을 nn.Module class 안에서 사용할 수도 있습니다.
이는 코드의 가독성을 높이고 유지, 보수에 용이하게 만들 수 있으니 model 설계를 할 때 고려해보세요.
model = nn.Sequential(
nn.Conv2d(in_channels = 1,
out_channels = 6,
kernel_size = (5, 5),
stride= (1, 1),
padding = (0, 0)),
nn.ReLU(),
nn.AvgPool2d(kernel_size = (2,2),
stride = (2,2)),
nn.Conv2d(in_channels = 6,
out_channels = 16,
kernel_size = (5, 5),
stride= (1, 1),
padding = (0, 0)),
nn.ReLU(),
nn.AvgPool2d(kernel_size = (2,2),
stride = (2,2)),
nn.Conv2d(in_channels = 16,
out_channels = 120,
kernel_size = (5, 5),
stride= (1, 1),
padding = (0, 0)),
nn.ReLU(),
nn.Flatten(),
nn.Linear(120, 84),
nn.ReLU(),
nn.Linear(84, 10)
)
x= torch.randn(64, 1, 32, 32)
output = model(x)
output.size()
torch.Size([64, 10])