통계, IT, AI

딥러닝: 화풍을 모방하기 (10) - 연습: 자기부호화기(Autoencoder) 본문

머신러닝

딥러닝: 화풍을 모방하기 (10) - 연습: 자기부호화기(Autoencoder)

Harold_Finch 2017. 4. 30. 13:03

1. 개요


  Autoenoder를 실습하기 위하여 두가지 주제를 진행하였다. 첫번째는 <딥러닝 제대로 시작하기>의 그림 5-6을 재연하는 것이고 두번째는 Autoencoder로 사전학습을 하여 신경망의 학습이 잘 이루어지는지 확인하는 것이다. 즉, 신경망의 층이 많아지면 학습이 잘 이루어지지 않는 현상인 Gradient vanishing을 Autoencoder로 해결할 수 있는지 알아보고자 하였다. 재연을 위한 모든 코드는 깃허브의 deeplearning 폴더에 올려두었다. 실행을 위해서는 data/mnist 폴더의 mnist_loader.py를 실행하여 pkl 파일을 생성해야 한다.



2. <딥러닝 제대로 시작하기> 그림 5-6 재연


  <딥러닝 제대로 시작하기>의 그림 5-6은 자기부호화기의 파라미터가 시각적으로 어떠한 경향을 갖는지 표현한 것으로 아래 그림 1과 같다.

그림 1. <딥러닝 제대로 시작하기> 그림 5-6

  입력층의 노드 수가 \(D_x\), 은닉층의 노드의 수가 \(D_y\)이면 \(w^{(2)}\)의 \(D_y \times D_x\)이다. 즉, 열의 길이는 입력값의 수와 같으므로 입력과 같은 크기의 이미지 \(D_y\)개를 그릴 수 있다. 그리고 이 과정을 통하여 파라미터가 시각적으로 어떠한 특징을 갖는지 확인할 수 있다.


  정확한 재연을 위하여 저자가 사용한 조건을 사용하고자 하였지만 책에는 그것이 언급되어있지 않았다. 출판사에 문의해도 정확한 답변을 들을 수 없어서 그림 1에서 말하고자 하는 주된 내용 즉, \(\beta\)의 변화에 따른 \(w^{(2)}\)의 특성을 확인하는 촛점을 맞추기로 하였다. 그림을 재연하는 과정에서 크게 두가지의 시행착오를 겪었는데 이는 4. 생각해 볼 점에서 다루고자 한다. 내가 재연한 결과는 그림 2와 같다.

그림 2. <딥러닝 제대로 시작하기> 그림 5-6 재연

  저자가 사용한 조건과 \(w^{(2)}\)를 이미지로 나타내기 위한 처리 등이 다르기 때문에 세부 모습은 원본과 상당한 차이가 있다. 하지만 \(\beta\)가 커짐에 따라 \(w^{(2)}\)가 숫자의 경향성을 잡아내려고 하는 모습은 같다. \(\beta\)가 0일때 즉, 희소 규제화를 하지 않는 경우 중간층은 제멋대로 입력을 표현하려고 한다. \(\beta\)가 적당한 크기일 때는 중간층 유닛이 서로 협력하여 각각의 입력을 표현하게 된다. \(\beta\)가 너무 크다면 중간층은 단독으로 숫자를 표현하려고 하는 경향을 보인다.



3. 자기부호화기를 통한 사전학습


  일반적으로 신경망은 그 은닉층이 많아질 수록 역전파가 잘 일어나지 않는 Gradient Vanishing(기울기 소실)의 문제를 갖고 있다. 이를 해결하기 위한 여러가지 방법 중 사전학습이 있다. 사전학습은 \(w^{(l)}\)의 초기값을 미리 "잘" 결정하여 \(\Delta^{(l)}\)가 입력층에 가까워져도 0이 아닌 값을 갖도록 하는 방법으로 대표적으로 자기부호화기를 이용한다.


  이를 확인하기 위하여 4가지 조건을 주었다. A는 은닉층이 없는 상황이다. B는 2개의 은닉층을 갖는 상황으로 사전학습을 하지 않아도 기울기 소실이 일어나지 않는다. C는 15개의 은닉층을 갖는 상황으로 기울기 소실을 유도한 것이다. D는 C와 같이 15개의 은닉층을 갖게 하되 자기부호화기를 통한 사전학습 이후 학습을 진행하였다. 각 조건에서 epoch의 경과에 따른 정확도의 경향을 나타낸 결과가 그림 3과 같다.

그림 3. 자기부호화기를 통한 사전학습 결과

  은닉층이 없는 조건 A 아래에서는 빠른 속도로 학습이 이루어지지만 정확도는 그다지 높지 않다. 2개의 은닉층을 추가한 조건 B에서는 epoch을 거듭할수록 정확도가 97%까지 상승하였다. 15개의 은닉층을 갖고 있지만 사전학습을 하지 않은 조건 C에서는 의도한 대로 기울기 소실이 발생하여 학습이 이루어지지 않는 것을 볼 수 있다. 사전학습을 진행한 조건 D에서는 학습이 잘 이루어져 다른 모든 조건에 비하여 가장 빨리, 가장 높은 정확도를 보임을 알 수 있다.


  하지만 20회의 epoch이 수행되면 조건 B와 조건 D가 큰 차이를 보이지 않는다는 것을 알 수 있다. 시각적으로 조건 D가 가장 우수한 것은 명확하다. 하지만 조건 D에서의 신경망은 조건 B에서 학습한 신경망보다 훨씬 더 많은 파라미터를 갖고 있다. 또한 조건 D는 코드 구현 및 사전학습을 위한 하이퍼 파라미터 튜닝 등의 비용이 별도로 든다. 추가된 파라미터의 수와 사전학습을 위한 비용을 고려했을 때 조건 D가 갖는 매력은 빛을 바랜다고 할 수 있다. 어떤 것이 더 나은지는 데이터 셋과 기타 제약조건에 따라 달리 생각할 문제이다.



4. 생각해 볼 점


  내용에 비하여 자기부호화기를 학습하는데 상당한 시간이 소요되었다. 일이 바빠진 탓도 있지만 구현하는데 있어서 어려움이 있었기 때문이기도 하다. 구현을 위하여 크게 두가지 시행착오가 있었다.


  첫번째 시행착오는 python 코드에 관련된 구현 상의 오류였다. \(w^{(2)}\)를 이미지로 나타내기 위해서는 각 값들을 0부터 255사이의 값으로 나타내야 했다. 그 과정에서 그림을 그리기 위한 변수가 \(w^{(2)}\)에 영향을 주어 실행과정이 엉망이 되었다. 이에 대한 예시가 아래의 코드이다.


# -*- coding: utf-8 -*-

import numpy as np

# Bad 

w = np.asmatrix([[1,2,3]], dtype=float)

temp_w = w
temp_w -= np.min(temp_w)
temp_w /= np.max(temp_w)
temp_w *= 255
print('-- BAD --')
print('TEMP W: ', temp_w) # good
print('ORIGINAL W:', w) # not intended


# Good 

w = np.asmatrix([[1,2,3]], dtype=float)

temp_w = np.copy(w)
temp_w -= np.min(temp_w)
temp_w /= np.max(temp_w)
temp_w *= 255
print('\n-- GOOD --')
print('TEMP W: ', temp_w) # good
print('ORIGINAL W:', w) # intended


  두번째 시행착오는 신경망과 좀 더 직접적으로 관련이 있는 것이다. 위에서 발생한 문제를 해결한 후 그림 5-6을 재연하려고 하였다. 그런데 특정 \(j\)열의 파라미터의 학습이 이루어지지 않는 문제가 있었다. 즉, 그림 1의 \(\beta=0.0\)인 부분에서 6, 7행 10열은 학습이 이루어지지 않아 초기값에서 변화가 없는 것이데, 이와 같은 양상이 절반 가까운 행에서 있었다.


  이에 대한 원인은 \(w^{(2)}\)와 \(b^{(2)}\)의 초기값의 분포의 차이 때문에 발생한 문제였다. 나는 \(w^{(2)}\)의 초기값의 분포로 \(N(0, 1/D_x)\)를, \(b^{(2)}\)는 \(N(0, 1)\)를 사용하였다. 즉, \(b^{(2)}\)의 분산이 훨씬 더 컸다. 그리고 입력값은 \([0,1]\)의 값을 갖는다. 따라서 \(b^{(2)}\)은 \(u^{(2)}\)의 몇몇 행을 전부 음수로 만들기에 충분히 작은 값을 자주 갖게 되었다. 결국 활성화 함수(ramp)를 거친 \(z^{(2)}\)의 그 행은 모두 0이 되었으며 \(\Delta^{(2)}\)의 그 행도 0이 되었다. 이를 해결하기 위해 \(b^{(2)}\)의 분포를 \(N(0, 1/D_x/10)\)으로 하니 학습이 잘 진행되었다.


  또 이 문제의 원인 가운데 하나는 ramp 함수를 사용하는 것이다. 따라서 정의역이 음수인 경우 치역으로 무조건 0을 취하는 것이 아니라 적당히 작은 값을 선택하면 어떨지 생각해 보았는데 그것이 바로 PReLU였다. 하이퍼 파라미터를 결정하는 것이 학습의 성패를 가른다는 것을 다시 한번 깨닫게 되었다.

Comments