[Paper]LeNet5(CNN)
이미지 인식 분야에서 거의 모든 딥러닝 기술에 사용되는 CNN이 실질적인 등장을 하는 LeNet-5를 구현하면서 CNN에 대한 이해도를 높여보겠습니다. 1998년, “Gradient-Based Learning Applied to Document Recognition”이라는 논문에서 등장한 LeNet-5은 CNN의 핵심이 되는 합성곱 층(convolitionm layer), 풀링 층(pooling layer)를 소개하여 CNN에 대한 이해도를 높일 수 있을 것이라고 생각 합니다.
Ⅰ. LeNet-5
Figure 1 : image(32 x 32 x 3, width x height x depth) → 6 filter(5 x 5 x 3) → 6 activation map(28 x 28)
ⅰ. Motivation
- 기존의 신경망은 이미지에서 잘 조정되지 않음.
- parameters가 너무 많아, 너무 빨리 overfiting 되어 wasteful함.
- 모든 입력데이터를 동등하게 취급하여 입력값의 topology를 무시함. 즉, 완전연결계층은 공간정보를 손실하지만, 컨볼루션층은 공간정보를 유지. 따라서 2차원(흑백), 3차원(컬러)과 같은 공간정보를 유지하기 때문에 적은 수의 파라미터를 요구.
ⅱ. Model Architecture
Conv layer(Convolutional Layer, 합성곱 계층)
Input volume(3D) $\ast$ Filter(or kernel) = Output map(2D) ($\ast$ : convolution)
Convolution Operations
- feature(output) map인 conner, edge 와 같은 특징 도출.
즉,receptive field를 local로 제한.
- filter size = size of local region(or receptive field)
- feature(output) map인 conner, edge 와 같은 특징 도출.
- Parameters sharing
- feature map에 있는 unit은 동일한 weight, bias를 공유히먄 학습해야하는 parameter 수를 줄여 Overfitting을 방지하게 됨.
- 또한, 이미지가 변환되었으면 feature map의 결과값도 동일한만큼 변화하여 입력의 왜곡이나 변환에 대한 Robust를 지님.
- Spatial arrangement
- depth : color channel
- stride : 필터를 적용하는 간격을 의미
Zero-padding : output size를 결정할 수 있게 해주는 역할
- padding이란?
- 입력 데이터의 주변을 특정 값으로 채우는 기법.
ReLU layer
: 증가하는 비선형 특성을 지님.Pool Layer(Pooling, 풀링 계층)
- subsampling : 각 특징의 위치 정보는 패턴을 식별하는 것과 무관할 뿐만 아니라, 입력값에 따라 특징이 나타나는 위치가 다를 가능성이 높기 때문에 잠재적으로 유해하여 가장 간단한 방법으로 해상도를 감소시키켜 distortion과 shift에 대한 민감도를 감소시킬 수 있다고 함. 또한, 위치 정보를 소실 시키면서 생기는 손실은 feature map size를 줄여 더 많은 filter를 사용하여 다양한 feature를 추출하여 상호보완 할 수 있다고 함.
- 필터(kernel) 사이즈 내에서 특정 값을 추출하는 과정. Max, Min, Avg 등 다양한 방법이 있음.
- 일반적으로 conv layer 사이에 넣고, parameters 와 computation을 줄이는 역할을 함. depth dimension은 invariance 함.
- large stride를 갖거나, generative model에서는 제거해야 좋은 결과를 갖기도 함. 즉, 거의 등장하지 않음.
FC layer(Fully connected layer)
- softmax function을 사용하여 고차원의 데이터를 예측이나 회귀 등의 추론을 진행.
ⅲ. Model Implementation
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
import torch
import torch.nn as nn
import torch.nn.functional as F
class LeNet(nn.Module):
def __init__(self, output_classes):
super(LeNet, self).__init__()
self.feature_extractor = nn.Sequential(
nn.Conv2d(1, 6, kernel_size=5, stride=1), # C1 : feture maps 6@28x28
nn.ReLU(), # activation function
nn.AvgPool2d(kernel_size=2), # S2 : feature maps 6@14x14
nn.Conv2d(6, 16, kernel_size=5, stride=1), # C3 : feture maps 16@14x14
nn.ReLU(), # activation function
nn.AvgPool2d(kernel_size=2) # S4 : feature maps 6@10x10
)
self.classifier = nn.Sequential(
nn.Linear(16 * 4 * 4, 120), # C5 : layers 120
nn.ReLU(), # activation function
nn.Linear(120, 84), # C6 : layers 84
nn.ReLU(), # activation function
nn.Linear(84, output_classes) # output : layers 10
)
self.conv1 = nn.Conv2d(1, 6, kernel_size=5) # input_kernel : 1, output_kernel : 6
self.conv2 = nn.Conv2d(6, 16, kernel_size=5) # input_kernel : 6, output_kernel : 16
self.fc1 = nn.Linear(16 * 4 * 4, 120) # 256 to 120
self.fc2 = nn.Linear(120, 84) # 120 to 84
self.fc3 = nn.Linear(84, 10) # 84 to 10
def forward(self, input):
x = input # Input : 32 x 32
x = self.feature_extractor(x) # C1, S2, C3, S4
x = torch.flatten(x, 1) # Flatten
logits = self.classifier(x) # C5, C6, output
probs = F.softmax(logits, dim=1)
return logits, probs
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
def train(model, dataloader, criterion, optimizer, device):
model.train()
train_loss = 0.0
for inputs, labels in tqdm(dataloader, desc='Training'):
inputs, labels = inputs.to(device), labels.to(device)
optimizer.zero_grad()
# 순전파 및 손실 계산
outputs, _ = model(inputs)
loss = criterion(outputs, labels)
# 역전파 및 가중치 갱신
loss.backward()
optimizer.step()
train_loss += loss.item()
train_loss /= len(dataloader)
return model, optimizer, train_loss
def evaluate(model, test_dataloader, criterion, device):
model.eval() # 평가 모드로 설정
eval_loss = 0.0
with torch.no_grad():
for inputs, labels in tqdm(test_dataloader, desc='Testing'):
inputs, labels = inputs.to(device), labels.to(device)
# 순전파 및 손실 계산
outputs, _ = model(inputs)
loss = criterion(outputs, labels)
eval_loss += loss.item()
eval_loss /= len(test_dataloader)
return model, eval_loss
def train_loop(opt, model, train_dataloader, test_loader, optimizer, criterion, device):
os.makedirs(opt.checkpoint_dir, exist_ok=True)
os.makedirs(opt.log_loss_dir, exist_ok=True)
# Initialize an empty list to store the loss values
train_loss_history= []
eval_loss_history= []
gif_img = []
# Training loop
for epoch in tqdm(range(opt.n_epochs), desc='Epochs'):
# training
model, optimizer, train_loss = train(model, train_dataloader, criterion, optimizer, device)
# validation
with torch.no_grad():
model, eval_loss = evaluate(model, test_loader, criterion, device)
train_loss_history.append(train_loss)
eval_loss_history.append(eval_loss)
# logging
description = f'Epoch: [{epoch + 1}/{opt.n_epochs}], \
Train Loss: {train_loss:.6f}, \
Test Loss: {eval_loss:.6f}' \
tqdm.write(description)
# Update the loss graph
fig = plt.figure(figsize=(6,4), dpi=80)
update_plot(train_loss_history, eval_loss_history, opt.n_epochs)
# Read the temporary file as an image
image = imageio.imread('temp.png')
# Append the image to the GIF
gif_img.append(image)
# Display the updated graph
display.clear_output(wait=True)
display.display(plt.gcf())
# Clear the final graph after the loop
display.clear_output(wait=True)
plt.close(fig)
# Save the GIF file
log_loss = os.path.join(opt.log_loss_dir, 'log_loss.gif')
imageio.mimsave(log_loss , gif_img,'GIF', duration=500)
display.display(Image(filename=log_loss))
# checkpoint 저장
checkpoint_path = os.path.join(opt.checkpoint_dir, 'checkpoint.pt')
save_model(model, checkpoint_path)
def update_plot(x, y, xlim):
# Clear the current plot
plt.clf()
# Update the plot
plt.plot(x, color='blue', label=f'Train Loss : {x[-1]:.6f}')
plt.plot(y, color='red', label=f'Eval Loss : {y[-1]:.6f}')
plt.xlabel('Iterations')
plt.ylabel('Loss')
plt.xlim(0, xlim - 1)
plt.ylim(0, 1)
plt.title('Real-time Loss Graph')
plt.legend()
# Save the figure to a temporary file
plt.savefig('temp.png')
def save_model(model, path):
torch.save(model.state_dict(), path)
def load_model(model, path):
model.load_state_dict(torch.load(path))
Ⅱ. CONCLUSTION.
- CNN은 grid topology(ex. image, time series)에서 좋은 성능을 나타냄.
- FC(Fully-Connected Layer)는 데이터 형상을 무시하는데에 반해, conv는 이미지 픽셀 사이의 관계를 고려함.
- conv layer를 통해 equivariance 특성과 pool layer를 통해 invariance한 특성을 가짐.
Update. CNN 1x1 filter(24.04.05)
“CNN에서 1x1 filter를 왜 사용하는지 아는가?”라는 이야기를 들어서 관련해서 찾아본 블로그 글의 내용을 업데이트하려고 합니다.
결론적으로 1x1을 추가적으로 사용해 Channel 수 조절, 연산량 감소, 비선형성이라는 장점들을 가질 수 있어 효과적이라고 이야기합니다.
Ⅲ. REFERENCES
This post is licensed under CC BY 4.0 by the author.