1. Introduction
입력이미지 → C1 → TANH → S2 → C3 → TANH → S4 → C5 → TANH → FC6 → SOFTMAX7
- C는 합성곱층, S는 풀링층(서브샘플링층), FC는 전결합층
- 1998년에는 ReLU가 발견되기 이전이었고, tanh와 시그모이드 함수가 주로 사용되는 것이 일반적이었음
LeNet-5는 미리 설정된 일정엔 맞춰 학습률을 감소시키는 학습률 감쇠를 사용
- 처음 2 에포크에는 0.005, 그 다음 3 에포크는 0.0002, 다시 그 다음 4 에포크에는 0.00005, 그 이후로는 0.00001의 학습률이 적용됨.
- 논문의 실험에서는 20 에포크까지 학습을 수행
2. Code
Hyperparameter
# 하이퍼파라미터
from torch.optim import Adam, lr_scheduler
epochs = 3
batch_size = 128
lr=1e-3
loss = nn.CrossEntropy().cuda()
optimizer = Adam(model.aprameters(),
lr=lr,
eps=1e-07)
Model
import torch.nn as nn
class LeNet5(nn.Module):
def __init__(self):
super(LeNet5, self).__init__()
self.conv1 = nn.Conv2d(1,6,kernel_size=5,stride=1,padding='same')
self.conv2 = nn.Conv2d(6,16,kernel_size=5,stride=1,padding='valid')
self.conv3 = nn.Conv2d(16,120,kernel_size=5,padding='valid')
self.pool = nn.AvgPool2d(kernel_size=2)
self.tanh = nn.Tanh()
self.flat = nn.Flatten()
self.fc1 = nn.Linear(120, 84)
self.fc2 = nn.Linear(84, 10)
def forward(self, image):
out = self.tanh(self.conv1(image))
out = self.pool(out)
out = self.tanh(self.conv2(out))
out = self.pool(out)
out = self.tanh(self.conv3(out))
out = self.flat(out)
out = self.tanh(self.fc1(out))
out = self.fc2(out)
return out
Train
import time
from tqdm import tqdm
# 모델 시각화를 위해 정확도를 저장할 리스트 생성
train_acc = list()
eval_acc = list()
for epoch in range(epochs):
start = time.time()
print(f'Epoch {epoch}/{epochs}')
train_loss = 0
correct = 0
# learning rate scheduling
# 0~2:0.0005, 3~5:0.0002, 6~9:0.00005, 10~20:0.00001
if epoch <= 2 :
optimizer.param_groups[0]['lr'] = 0.0005
elif epoch > 2 and epoch <= 5 :
optimizer.param_groups[0]['lr'] = 0.0002
elif epoch > 5 and epoch <= 9 :
optimizer.param_groups[0]['lr'] = 0.00005
else :
optimizer.param_groups[0]['lr'] = 0.00001
# train
for x,y in tqdm(train_loader):
optimizer.zero_grad()
y_pred = model(x.cuda())
cost = loss(y_pred, y.cuda())
cost.backward()
optimizer.step()
train_loss += cost.item()
pred = y_pred.data.max(1, keepdim=True)[1] # 각 클래스의 확률 값 중 가장 큰 값을 가지는 클래스의 인덱스를 pred 변수로 받음
# 맞은 개수를 구하는 과정
correct += pred.cpu().eq(y.data.view_as(pred)).sum() # view_as함수는 들어가는 인수의 모양으로 맞춰주고,
#.eq()를 통해 y_pred와 y의 값이 동일한지 판단하여 True 개수 구하기
train_loss /= len(trainloader)
train_accuracy = correct / len(trainloader.dataset)
train_accuracies.append(train_accuracy)
# eval
eval_loss = 0
correct = 0
with torch.no_grad(): # 학습하지 않음
model.eval()
for x,y in tqdm(test_loader):
y_pred = model(x.cuda())
cost = loss(y_pred,y.cuda())## loss 함수를 이용하여 test 데이터의 오차를 계산함
eval_loss += cost
pred = y_pred.data.max(1, keepdim=True)[1] # 각 클래스의 확률 값 중 가장 큰 값을 가지는 클래스의 인덱스를 pred 변수로 받음
correct += pred.cpu().eq(y.data.view_as(pred)).cpu().sum() # view_as함수는 들어가는 인수의 모양으로 맞춰주고,
#.eq()를 통해 y_pred와 y의 값이 동일한지 판단하여 True 개수 구하기
eval_loss /= len(testloader)
eval_accuracy = correct / len(testloader.dataset)
eval_accuracies.append(eval_accuracy)
## test 데이터의 loss를 기준으로 이전 loss 보다 작을 경우 체크포인트 저장
if eval_loss < best_loss:
torch.save({
'epoch': epoch,
'model': model,
'model_state_dict': model.state_dict(),
'optimizer_state_dict': optimizer.state_dict(),
'loss': cost.item,
}, './bestCheckPiont.pth')
print(f'Epoch {epoch:05d}: val_loss improved from {best_loss:.5f} to {eval_loss:.5f}, saving model to bestCheckPiont.pth')
best_loss = eval_loss
else:
print(f'Epoch {epoch:05d}: val_loss did not improve')
model.train()
print(f'{int(time.time() - start)}s - loss: {train_loss:.5f} - acc: {train_accuracy:.5f} - val_loss: {eval_loss:.5f} - val_acc: {eval_accuracy:.5f}')
Evaluation
# Load Checkpoint
best_model = torch.load('./bestCheckPiont.pth')['model'] # 전체 모델을 통째로 불러옴, 클래스 선언 필수
best_model.load_state_dict(torch.load('./bestCheckPiont.pth')['model_state_dict']) # state_dict를 불러 온 후, 모델에 저장
# Eval
correct = 0
with torch.no_grad(): # 학습하지 않기 위해
best_model.eval()
for x, target in tqdm(testloader):
y_pred = best_model(x.cuda())
pred = y_pred.data.max(1, keepdim=True)[1]
correct += pred.cpu().eq(target.data.view_as(pred)).cpu().sum()
eval_accuracy = correct / len(testloader.dataset)
print(f"Test accuracy: {100.* eval_accuracy:.4f}%")