0%

13. 인공신경망 최적화 - 드랍아웃(Dropout)

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


드랍아웃(Dropout)이란

드랍아웃은 인공신경망 훈련 과정을 최적화하기 위한 방법 중 하나이며, 오버피팅(overfitting)을 방지함으로써 모델의 정확도를 높여준다.

Note
오버피팅(overfitting)이란?
신경망을 학습하는 과정에서 훈련데이터에만 적합한 형태로 학습되는 현상을 오버피팅이라고 한다. 훈련데이터의 정확도는 거의 100%를 달성하는데 실제데이터에서는 일정 이상의 정확도에서 상승하지 않는 것이다. 이런 현상은 보통 훈련데이터를 너무 적게 사용한 경우 또는 모델 파라미터가 너무 많은 경우에 발생한다.

드랍아웃은 각각의 훈련데이터들이 결과값으로 연결되는 신호(엣지, Edge)를 일정한 퍼센트로 삭제함으로써 훈련데이터의 일부만 파라미터에 영향을 줄 수 있도록 조정하는 역할을 한다. 드랍아웃을 함수로 만들면 다음과 같다.

드랍아웃 구현

드랍아웃은 입력된 비율에 따라 몇몇의 신호값들을 0으로 반환한다. 이를 함수로 구현하면 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
using Random

function drop_out_single(input_size, rate)
function changing_T_or_F_with_percentage(number, input_size, rate)
temp_num = input_size * rate
if number > temp_num
return 0
else
return 1
end
end
temp = shuffle(reshape(1:input_size, 1, input_size))
return changing_T_or_F_with_percentage.(temp, input_size, rate)
end

위 함수는 신호값과 곱하는 마스크를 생성한다. 드랍아웃에서는 비율을 입력값으로 받아 입력된 신호값의 일부 위치를 무작위로 선정하고, 그 자리의 신호를 0으로 반환해야 하는데 이를 구현하기 위해 기술적으로 신호값과 곱해주는 마스크를 생성하는 것이다. 하지만 이는 데이터 하나의 형태(한 줄)에만 작동하는 함수이다. 우리는 지금까지 배치데이터(여러 줄)를 사용해왔기 때문에 각각의 모든 줄에 위의 드랍아웃을 적용하는 함수가 필요하다.

Note
배치데이터에서 드랍아웃의 작동원리
드랍아웃은 각각의 데이터들이 동일한 비율로 신호값이 제거되어야 한다. 다시 말해, 한 줄로 나열된 데이터를 여러 개 합쳐놓은 매트릭스 형태의 배치데이터에서는 한 줄마다 일정한 비율을 유지해주면서 신호값을 제거해야 한다. 그렇기에 한 줄씩 인덱스를 무작위로 선정하여 제거해주는 작업이 필수적이다. 만약 이를 고려하지 않고 배치데이터를 드랍아웃한다면, 이는 효과가 거의 없다.

1
2
3
4
5
6
7
8
9
function drop_out(input_size, hidden_size, rate)
temp = drop_out_single(hidden_size, rate)
temp_num = input_size - 1
for i = 1:temp_num
temp_1 = drop_out_single(hidden_size, rate)
temp = [temp; temp_1]
end
return temp
end

위 함수는 drop_out_single()을 배치데이터에 사용할 수 있도록 변환한 것이다. 위 함수에서 사용된 input_sizehidden_size는 배치데이터에서 행렬의 형상이다. 예를 들어 입력된 신호값이 $10 \times 784$의 행렬이라면 이는 $1 \times 784$ 데이터가 총 10개가 포함된 배치데이터이기에 drop_out_single()input_size만큼 반복하는 것이다.

신호값과 곱해줄 드랍아웃 마스크는 완성되었다. 이제는 신호값과 마스크를 곱해주는 함수를 생성해보자. 참고로 드랍아웃도 신호값을 제거하는 과정이기에 이후 역전파에서 같은 위치의 미분값이 제거되어야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mutable struct Dropout
mask
end

function dropout_forward(dropout, x, dropout_ratio)
if dropout_ratio < 1
dropout.mask = drop_out(size(x)[1],size(x)[2], dropout_ratio)
return x .* dropout.mask
else dropout_ratio = 1
return x .* (1.0 - dropout_ratio)
end
end

function dropout_backward(dropout, dout)
return dout .* dropout.mask
end

이제 드랍아웃을 위한 함수 구현은 모두 끝났다. 다음으로는 위 함수들을 이용하여 드랍아웃을 적용한 모델과 적용하지 않은 모델을 비교해보자.

신경망 모델 구현

신경망 모델은 지금까지 구현했던 MNIST데이터를 사용하는 2층 신경망 모델을 다시 사용할 것이다. 역전파 모델에 대한 정보는 해당 글에서 확인할 수 있다. 또한 모델 구성에 필요한 함수들은 깃허브에서 찾아볼 수 있다. 준비가 완료되었다면 본격적으로 구현해보자.

1
2
3
4
5
#훈련데이터 300개

train_x = train_x[1:300,:]
train_y = train_y[1:300,:]
t = t[1:300,:]

이번 구현에서는 오버피팅을 발생시키기 위해서 60000개인 train_x데이터 중에서 300개만을 사용하여 학습시킬 것이다.

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
params = Dict()
grads = Dict()

# 층에 들어갈 가중치와 편향 입력
W = ["W1", "W2"]
b = ["b1", "b2"]
hidden_size = [50]

making_network(W,b,784,hidden_size,10,"std")

# 계층마다 인스턴스를 만들어줘야 한다. (for 역전파)

result = SoftmaxwithLoss(0,0)
dense1 = dense_layer(0,0,0,0,0)
dense2 = dense_layer(0,0,0,0,0)
Sigmoid1 = Sigmoid(0)
Relu = ReLu(0)
optimizer = optimizers(0,0,0,0)


accuracy_test = Float64[]
accuracy_train = Float64[]
train_size = size(train_x)[1]
batch_size = 100
learning_rate = 0.01

모델 학습을 위한 변수들을 정의한다. 참고로 여기서 사용된 making_network()는 이전글인 가중치 초기값에서 사용했던 함수와 다르다. 역전파 알고리즘에서 사용했던 함수와 동일하다. 만약 새로운 making_network()를 사용하고 싶다면, 가중치와 편향 입력 부분을 아래와 같이 변경하면 된다.

1
2
3
4
5
6
7
# 층에 들어갈 가중치와 편향 입력
W = ["W1", "W2"]
b = ["b1", "b2"]
input_size = (1, 784)
hidden_size = [(784,50),(50,10)]

params = making_network(W, b, weight_size, input_size, "std")

이제 모델을 작동시킬 준비가 끝났다. 아래의 코드를 입력하여 학습을 시작하자.

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
@time begin
for i in 1:6000
batch_mask = rand(1:300, 100)
x_batch = train_x[batch_mask, :]
t_batch = t[batch_mask, :]

# 순전파
z1 = dense_layer_forward(dense1,x_batch,params["W1"],params["b1"])
a1 = relu_forward(Relu,z1)
z2 = dense_layer_forward(dense2,a1,params["W2"],params["b2"])
num = SoftmaxwithLoss_forward(z2,t_batch)

# 역전파
last_layer = SoftmaxwithLoss_backward(result)
z2_back = dense_layer_backward(dense2, last_layer)
grads["W2"] = dense2.dw
grads["b2"] = dense2.db
a1_back = relu_backward(Relu,z2_back)
z1_back = dense_layer_backward(dense1, a1_back)
grads["W1"] = dense1.dw
grads["b1"] = dense1.db

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

temp_loss = loss(x_batch, t_batch)
print("NO.$i: ")
println(temp_loss)
append!(train_loss_list, temp_loss)
append!(accuracy_test, evaluate(test_x, test_y))
append!(accuracy_train, evaluate(train_x, train_y))
end
end

위 모델은 300개의 데이터를 100개 배치데이터 단위로 2000에폭 학습한다. 이제 위의 결과를 그래프를 그려 확인해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
without_train = vcat(accuracy_train)
without_test = vcat(accuracy_test)

x = range(1,length(without_train),step=1)
data = [without_train without_test]
labels = ["accuracy_train" "accuracy_test"]
markercolors = [
:red :blue
]

pl_nodrop=plot(
x,
data,
label = labels,
color = markercolors,
markersize = 4,
title = "Accuracy without Dropout"
)

그 결과는 아래와 같다.

드랍아웃 없음

그래프를 확인해보면 훈련데이터는 정확도가 거의 100%에 가깝지만 실제데이터의 정확도는 80% 근방에서 멈춘 것을 확인할 수 있다. 따라서 이런 경우 드랍아웃을 추가하면 위의 현상을 완화시킬 수 있다. 이제 드랍아웃이 적용된 모델을 확인하자.

WARNING
모델을 다시 훈련시키기에 앞서 가중치와 편향을 다시 초기화해주어야 한다. 따라서 다시 아래의 코드를 작동시켜야 한다.

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
params = Dict()
grads = Dict()

# 층에 들어갈 가중치와 편향 입력
W = ["W1", "W2"]
b = ["b1", "b2"]
hidden_size = [50]

making_network(W,b,784,hidden_size,10,"std")

# 계층마다 인스턴스를 만들어줘야 한다. (for 역전파)

result = SoftmaxwithLoss(0,0)
dense1 = dense_layer(0,0,0,0,0)
dense2 = dense_layer(0,0,0,0,0)
Sigmoid1 = Sigmoid(0)
Relu = ReLu(0)
optimizer = optimizers(0,0,0,0)


accuracy_test = Float64[]
accuracy_train = Float64[]
train_size = size(train_x)[1]
batch_size = 100
learning_rate = 0.01

이제 모델을 학습해보자.

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
@time begin
for i in 1:6000
batch_mask = rand(1:300, 100)
x_batch = train_x[batch_mask, :]
t_batch = t[batch_mask, :]

# 신경망 계산
z1 = dense_layer_forward(dense1,x_batch,params["W1"],params["b1"])
a1 = relu_forward(Relu,z1)
dt = dropout_forward(dropout, a1, 0.3) # 드랍아웃 레이어
z2 = dense_layer_forward(dense2,dt,params["W2"],params["b2"])
num = SoftmaxwithLoss_forward(z2,t_batch)

# 역전파
last_layer = SoftmaxwithLoss_backward(result)
z2_back = dense_layer_backward(dense2, last_layer)
grads["W2"] = dense2.dw
grads["b2"] = dense2.db
dt_back = dropout_backward(dropout,z2_back) # 드랍아웃 레이어
a1_back = relu_backward(Relu, dt_back)
z1_back = dense_layer_backward(dense1, a1_back)
grads["W1"] = dense1.dw
grads["b1"] = dense1.db

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

temp_loss = loss(x_batch, t_batch)
print("NO.$i: ")
println(temp_loss)
append!(train_loss_list, temp_loss)
append!(accuracy_test, evaluate(test_x, test_y))
append!(accuracy_train, evaluate(train_x, train_y))
end
end

위 모델은 중간에 드랍아웃이 적용되어 있다. 드랍아웃 레이어를 확인해보면 비율 파라미터 자리에 0.3이 있다. 즉, 30%의 신호값을 제거하라는 의미이다. 이제 위의 결과를 그래프를 그려 확인해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
with_train = vcat(accuracy_train)
with_test = vcat(accuracy_test)

x = range(1,length(with_train),step=1)
data = [with_train with_test]
labels = ["accuracy_train" "accuracy_test"]
markercolors = [
:red :blue
]

pl_drop=plot(
x,
data,
label = labels,
color = markercolors,
markersize = 4,
title = "Accuracy with Dropout"
)

결과는 다음과 같다.

드랍아웃 있음

훈련데이터의 정확도와 실제데이터의 정확도 간격이 드랍아웃을 적용하지 않은 모델보다 훨씬 줄어든 것을 확인할 수 있다.