머신러닝을 통한 음식 분류를 하기 전에, 어떤 라이브러리를 사용할 것인지에 대해 고민해보기로 했다.
Keras는 TensorFlow위에서 동작이 가능하다고 하니
PyTorch와 TensorFlow로 가장 기본적인 예제인 MNIST를 구현해보고
두 라이브러리를 비교한 뒤, 어떤 라이브러리를 사용해 음식 분류를 구현할 것인지 결정한다.
구조
2개의 Convolution layer와 2개의 FC레이어로 구성했다.
각 layer의 중간에 오버피팅을 줄이고 연산량을 감소시키기 위해 Max pooling 하였고,
Convolution layer의 끝에는 Dropout을 적용했다.
소스코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
|
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import torchvision
import torchvision.transforms as transfroms
device = 'cuda' if torch.cuda.is_available() else 'cpu'
torch.manual_seed(777)
if device == 'cuda':
torch.cuda.manual_seed_all(777)
print(device + " is available")
learning_rate = 0.001
batch_size = 100
num_classes = 10
epochs = 5
# MNIST 데이터셋 로드
train_set = torchvision.datasets.MNIST(
root = './data/MNIST',
train = True,
download = True,
transform = transfroms.Compose([
transfroms.ToTensor() # 데이터를 0에서 255까지 있는 값을 0에서 1사이 값으로 변환
])
)
test_set = torchvision.datasets.MNIST(
root = './data/MNIST',
train = False,
download = True,
transform = transfroms.Compose([
transfroms.ToTensor() # 데이터를 0에서 255까지 있는 값을 0에서 1사이 값으로 변환
])
)
# train_loader, test_loader 생성
train_loader = torch.utils.data.DataLoader(train_set, batch_size=batch_size)
test_loader = torch.utils.data.DataLoader(test_set, batch_size=batch_size)
# input size를 알기 위해서
examples = enumerate(train_set)
batch_idx, (example_data, example_targets) = next(examples)
example_data.shape
class ConvNet(nn.Module):
def __init__(self): # layer 정의
super(ConvNet, self).__init__()
# input size = 28x28
self.conv1 = nn.Conv2d(1, 10, kernel_size=5) # input channel = 1, filter = 10, kernel size = 5, zero padding = 0, stribe = 1
# ((W-K+2P)/S)+1 공식으로 인해 ((28-5+0)/1)+1=24 -> 24x24로 변환
# maxpooling하면 12x12
self.conv2 = nn.Conv2d(10, 20, kernel_size=5) # input channel = 1, filter = 10, kernel size = 5, zero padding = 0, stribe = 1
# ((12-5+0)/1)+1=8 -> 8x8로 변환
# maxpooling하면 4x4
self.drop2D = nn.Dropout2d(p=0.25, inplace=False) # 랜덤하게 뉴런을 종료해서 학습을 방해해 학습이 학습용 데이터에 치우치는 현상을 막기 위해 사용
self.mp = nn.MaxPool2d(2) # 오버피팅을 방지하고, 연산에 들어가는 자원을 줄이기 위해 maxpolling
self.fc1 = nn.Linear(320,100) # 4x4x20 vector로 flat한 것을 100개의 출력으로 변경
self.fc2 = nn.Linear(100,10) # 100개의 출력을 10개의 출력으로 변경
def forward(self, x):
x = F.relu(self.mp(self.conv1(x))) # convolution layer 1번에 relu를 씌우고 maxpool, 결과값은 12x12x10
x = F.relu(self.mp(self.conv2(x))) # convolution layer 2번에 relu를 씌우고 maxpool, 결과값은 4x4x20
x = self.drop2D(x)
x = x.view(x.size(0), -1) # flat
x = self.fc1(x) # fc1 레이어에 삽입
x = self.fc2(x) # fc2 레이어에 삽입
return F.log_softmax(x) # fully-connected layer에 넣고 logsoftmax 적용
model = ConvNet().to(device) # CNN instance 생성
# Cost Function과 Optimizer 선택
criterion = nn.CrossEntropyLoss().to(device)
optimizer = torch.optim.Adam(model.parameters(), lr = learning_rate)
for epoch in range(epochs): # epochs수만큼 반복
avg_cost = 0
for data, target in train_loader:
data = data.to(device)
target = target.to(device)
optimizer.zero_grad() # 모든 model의 gradient 값을 0으로 설정
hypothesis = model(data) # 모델을 forward pass해 결과값 저장
cost = criterion(hypothesis, target) # output과 target의 loss 계산
cost.backward() # backward 함수를 호출해 gradient 계산
optimizer.step() # 모델의 학습 파라미터 갱신
avg_cost += cost / len(train_loader) # loss 값을 변수에 누적하고 train_loader의 개수로 나눔 = 평균
print('[Epoch: {:>4}] cost = {:>.9}'.format(epoch + 1, avg_cost))
# test
model.eval() # evaluate mode로 전환 dropout 이나 batch_normalization 해제
with torch.no_grad(): # grad 해제
correct = 0
total = 0
for data, target in test_loader:
data = data.to(device)
target = target.to(device)
out = model(data)
preds = torch.max(out.data, 1)[1] # 출력이 분류 각각에 대한 값으로 나타나기 때문에, 가장 높은 값을 갖는 인덱스를 추출
total += len(target) # 전체 클래스 개수
correct += (preds==target).sum().item() # 예측값과 실제값이 같은지 비교
print('Test Accuracy: ', 100.*correct/total, '%')
|
cs |
각 layer를 지날때마다 바뀌는 크기를 계산하는 부분이 어려웠다.
kernel = 3, zero padding = 1을 주는 경우 이미지의 크기가 변하지 않게 할 수 있는데
다른 예제에서 kernel만 5로 설정해서 특별한 이유가 있을까 하고 따라해봤다.
결과
'개발 > 파이썬' 카테고리의 다른 글
[Python] YOLOv5 Custom dataset 으로 학습하기 (26) | 2020.09.17 |
---|---|
[Python] OpenCV로 이미지 배경 제거하기 (0) | 2020.09.16 |
[Python] 파이토치(PyTorch) 학습한 모델 저장 & 불러오기 (0) | 2020.09.08 |
[Python] MNIST 예제를 통한 Keras, PyTorch 비교 (2) | 2020.08.25 |
[Python] 케라스(Keras)를 사용한 MNIST 문자인식 구현 예제 (0) | 2020.08.25 |