0%

18. 간단한 CNN 모델 구현

해당 시리즈는 프로그래밍 언어 중 하나인 줄리아(Julia)로 딥러닝(Deep learning)을 구현하면서 원리를 설명합니다.


지금까지 im2col(), col2im()을 적용한 새로운 합성곱과 풀링에 대해 알아보았다. 이번 글에서는 새로운 합성곱과 풀링을 사용하여 새로운 simpleNet을 구현할 것이다. im2col(), col2im()을 적용하지 않은 기존 simpleNet은 1에폭에 1시간이 걸린 반면 새로운 simpleNet은 1에폭에 5분이면 학습이 완료된다.

CNN 모델 구현

준비 단계

이제 설계한 모델을 직접 구현해보자. 먼저 모델을 생성하고 학습하는데 필요한 재료들을 가져와야 한다. 해당 깃허브 사이트에 가서 코드를 다운받으면 바로 학습을 진행할 수 있다. 만약 다운받기 싫다면 이 사이트에서 아래의 파일들의 코드를 모두 복사해서 사용해도 된다.

깃허브 데스크탑에 코드를 클론하거나 저장한 분들은 현재 사용하고 있는 커맨드의 경로를 CNN 파일로 변경해야 한다.

1
2
pwd # 현재 경로 확인
cd 코드가 있는 파일 경로 입력/Deep_Learning_in_Julia/CNN

다시 pwd를 입력했을 때 아래와 같이 변경되어 있으면 변경이 완료된 것이다.

1
/Users/코드가 있는 파일 경로/Deep_Learning_in_Julia/CNN

변경이 완료된 후 아래의 코드를 입력하자.

1
2
3
4
5
include("MNIST_data.jl")
include("functions.jl")
include("layers.jl")
include("making_network.jl")
include("optimizers.jl")

Note
만약 터미널 변경에는 성공했는데 위 코드가 작동하지 않는다면 해당 터미널이 줄리아 언어로 세팅되어 있는지를 확인해보자. 줄리아 언어로 변경하는 방법은 줄리아가 저장되어 있는 경로를 찾아서 입력하면 사용할 수 있다.

위 코드는 파일에 들어 있는 모든 코드들을 작동시킨다. 만약 코드를 복사하여 사용할 분들은 해당 페이지에서 위의 파일들의 코드를 복사하여 입력해주면 된다.

이제 간단한 CNN 모델을 만들 준비가 끝났다.

CNN 모델 설계

MNIST 데이터는 간단하게 손글씨로 쓴 숫자이기에 비교적 얕은 모델로도 높은 성능의 분류기를 만들 수 있다. 아래의 그림을 참고해보자.

model

위의 각 계층들에 대한 세부적인 정보는 아래와 같다.

  • input data: MNIST, batch = 100, data_shape = 28 x 28 x 1 x 100
  • 합성곱 계층: padding 없음, stride = 1, filter size = 5 x 5 x 1 x 30, output = 24 x 24 x 30 x 100
  • ReLU 계층: 음수 0으로 변경, output = 24 x 24 x 30 x 100
  • 풀링 계층: padding 없음, stride = 2, pooling size = 2 x 2, output = 12 x 12 x 30 x 100
  • flatten 계층: 4차원 데이터를 2차원으로 변경, output = 100 x 4320
  • Dense 계층: weight size = 4320 x 100, bias = 1 x 100, output = 100 x 100
  • ReLU 계층: 음수 0으로 변경, output = 100 x 100
  • Dense 계층: weight size = 100 x 10, bias = 1 x 10, output = 100 x 10
  • softmax with loss 계층: 손실 값 도출

각 계층들을 살펴보면 데이터가 어떻게 변화하면서 흐르는지 파악할 수 있다. 매우 간단한 모델이지만, MLP보다는 훨씬 좋은 성능을 보여준다.

학습 알고리즘은 아래와 같이 설계하여 진행할 것이다.

  • Algorithm: backward propagation
  • batch size: 100
  • optimizer: Adam
  • weight initialization: std

학습 구현

학습을 구현하기 위해서는 총 3가지의 과정을 거쳐야 한다.

  • predict() 및 저장소 설정
  • 가중치와 편향 초기값 설정
  • 모델 계층 쌓기

먼저 predict()를 위의 모델 설계에 따라 구성해보자.

1
2
3
4
5
6
7
8
9
10
11
function  predict(input)

pconv_1 = convolution2D_forward(pre_dense,input, params["W1"], params["b1"],1,0)
pconv_Re = relu.(pconv_1)
ppool_1 = Maxpooling_forward(pre_pool,pconv_Re, 2, 2, 2, 0)
flatten_1 = flatten_forward_batch(pre_flatten,ppool_1)
dense_1 = (flatten_1 * params["W2"]) .+ params["b2"]
dense_relu = relu.(dense_1)
dense_2 = (dense_relu * params["W3"]) .+ params["b3"]
result = softmax(dense_2)
end

predict()는 손실 값을 구할 때 사용되는 함수이다. 모델의 구성과 동일하며, 이때 사용되는 저장소들은 실제 필요한 데이터들이 아니라 함수의 작동을 위해 기술해둔 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# predict용 저장소(사용x)
pre_dense = dense_layer(0,0,0,0,0,0)
pre_pool= repository(0,0,0)
pre_flatten = repository(0,0,0)


# 실제 저장소
result = SoftmaxwithLoss(0,0)
dense1 = dense_layer(0,0,0,0,0,0)
dense2 = dense_layer(0,0,0,0,0,0)
dense3 = dense_layer(0,0,0,0,0,0)
Relu1 = repository(0,0,0)
Relu2 = repository(0,0,0)
optimizer = optimizers(0,0,0,0)
pool1= repository(0,0,0)
flatten1 = repository(0,0,0)

# 미분값, 손실값 저장
grads = Dict()
train_loss_list= []

그 다음 각 계층에 필요한 저장소들을 생성한다.

1
2
3
4
5
6
7
# 가중치, 편향 생성
W =["W1","W2","W3"]
b = ["b1","b2","b3"]
weight_size = [(5,5,1,30),(4320,100),(100,10)];
output_shape = [(28,28,1),(24,24,30),(1,100)]

params = making_network(W, b, weight_size,output_shape,"std");

두 번째 단계인 가중치와 편향 초기값을 생성한다. weight_size는 합성곱 계층과 Dense 계층에 사용되는 가중치의 형상을 나타내며, output_shape은 각 층마다 도출되는 결과값의 형상이다. 이를 사용하여 초기값인 params를 생성한다.

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
@time begin
for i in 1:600

batch_size = rand(1:size(train_x)[4],100)
train_x_batch = train_x[:,:,:,batch_size]
t_batch = reshape(t[batch_size,:],100,10)

#신경망 계산
conv_1 = convolution2D_forward(dense1,train_x_batch,params["W1"],params["b1"],1,0)
conv_Re = relu_forward(Relu1, conv_1)
pool_1 = Maxpooling_forward(pool1, conv_Re, 2, 2, 2, 0)
flatten_1 = flatten_forward_batch(flatten1, pool_1)
dense_1 = dense_layer_forward(dense2,flatten_1,params["W2"],params["b2"])
dense_relu = relu_forward(Relu2, dense_1)
dense_2 = dense_layer_forward(dense3,dense_relu,params["W3"],params["b3"])
num = SoftmaxwithLoss_forward(dense_2,t_batch)

#역전파 알고리즘
last_layer = SoftmaxwithLoss_backward(result)
dense_2_back = dense_layer_backward(dense3, last_layer)
grads["W3"] = dense3.dw
grads["b3"] = dense3.db
dense_relu_back = relu_backward(Relu2, dense_2_back)
dense_1_back = dense_layer_backward(dense2, dense_relu_back)
grads["W2"] = dense2.dw
grads["b2"] = dense2.db
flatten_1_back = flatten_backward_batch(flatten1,dense_1_back)
pool_1_back = Maxpooling_backward(pool1, flatten_1_back, 2, 2,2,0)
conv_Re_back = relu_backward(Relu1, pool_1_back)
conv_back = convolution2D_backward(dense1,conv_Re_back,1,0)
grads["W1"] = dense1.dw
grads["b1"] = dense1.db

#가중치 갱신
Adam(params,grads)

temp_loss = loss_CNN_batch(train_x_batch,t_batch)
print("NO.$i: ")
println(temp_loss)
append!(train_loss_list, temp_loss)
end
end

이제 마지막으로 모델 설계에 알맞게 계층을 쌓아 학습을 시작한다. 위 코드는 한번 학습할 때마다 입력 데이터 100개가 입력되며, 1에폭은 600번이다. 따라서 위 코드를 그래도 사용하면 1에폭을 학습한다. 학습 종료 후 정확도를 확인하고 싶다면 아래의 코드를 입력해보자.

1
2
# 테스트셋 정확도 계산
evaluate_CNN_batch(test_x, test_y)

결과

위 코드를 사용하여 총 5에폭(3000번)을 학습시켰으며, 그 결과 정확도를 아래와 같이 증가하였다.

1
2
3
4
5
# 1에폭: 96.37
# 2에폭: 97.22
# 3에폭: 98.1
# 4에폭: 98.46000000000001
# 5에폭: 98.57000000000001

im2col(), col2im()을 적용하지 않았던 모델과 비교해봤을 때 초기값의 랜덤 설정을 고려한다면 같은 결과를 얻었다고 볼 수 있다. 손실값의 그래프를 확인해도 잘 학습되는 것을 확인할 수 있다.

loss

하지만 5에폭을 진행하는데 걸린 시간은 고작 약 25분 정도이다. 기존의 simpleNet은 1에폭에 1시간이었다는 것을 떠올려보면 엄청난 발전이라고 볼 수 있다. 하지만 simpleNet은 테스트셋의 정확도 99%를 넘기지는 못한다. 따라서 다음 시간에는 tensorflow에서 MNIST의 예제로 제공하는 convNet을 살펴보고 이와 동일하게 모델을 설계하여 테스트셋의 정확도 99%를 달성해볼 것이다.