딥러닝

Weakly-Supervised localization: 뉴럴넷은 어디를 바라보고 있는가

ConvNet이 이미지를 다루는 데 강력한 성능을 보이기는 하지만, layer가 깊어지면 깊어질수록 이미지의 어떤 특징을 보고 판단을 내리는지를 파악하기란 쉽지 않다.

이미지 하나를 인풋으로 넣고 layer를 차례차례 통과하면서 activation이 어떻게 변하는지를 살펴볼 수도 있겠지만 VGG와 같은 모형처럼 레이어가 매우 깊어지거나 GoogLeNet, ResNet처럼 특수한 모듈 구조를 사용, 또는 residual connection을 사용하는 네트워크에서는 뉴럴넷이 이미지의 어느 부분을 중요하게 생각하는지의 정보를 얻기가 힘들어진다.

블랙박스인 듯 보이는 뉴럴넷(특히 ConvNet)의 내부를 조금이나마 투명하게 드러내려는 시도는 꾸준히 있어왔다. 구글에서도 최근 “Inceptionism”이라는 이름으로 재미난 연구 결과를 블로그에 공개한 바 있다.

여기서는 이보다 뉴럴넷이 “어디를 집중해서 보고 있는지”를 제시한 아주 단순하고도 유용한 방법을 살펴보고자 한다. 바로 weakly-supervised localization의 시초격 되는 방법인데, localizer 모형에게 이미지에 어떤 사물이 있는지만을 학습시키면 이미지에 어떤 사물이 있는지는 물론, 이미지의 어느 위치에 그 사물이 있는지 까지 꽤나 정확하게 예측할 수 있다.

지도학습으로 학습시키긴 했지만 학습 과정에서 알려주지 않은 사물의 위치까지 학습한다는 점에서 이러한 방식을 weakly-supervised라고 이름 붙였다. 해당 논문은 재작년에 ArXiv에 올라온 것으로 발표 당시 큰 주목을 받았었다.

 

Architecture

이 논문에서 제시한 Weakly-supervised localizer의 구조는 매우 단순하다. 어떻게 localizer를 만드는지 그 과정을 하나하나 짚어보자.

스크린샷 2018-06-17 오후 10.12.16.png

0. (Pre-trained) CNN model

CNN classifier를 가져온다. 이 classifier는 이미지를 보고 이미지에 상응하는 class label을 예측해야한다. 밑바닥부터 학습시켜도 되지만 pre-trained 모형을 사용하면 좀 더 빠르게 좋은 성능을 낼 수 있다.

가져온 CNN classifier에서 말단의 fully connected layer를 모두 제거한다. 즉 가장 깊이 있는 CONV layer가 최말단 레이어가 되도록 한다.

1. CONV layer

0.의 CNN classifier의 최말단 CONV layer 뒤에 CONV layer를 하나 추가한다. 논문에서는 1024개의 filter를 가지는 CONV layer를 추가했다. 여기서도 filter 갯수는 1024라고 가정하자.

이 레이어의 출력은 1024개의 feature map (depth=1024)이 된다.

2. Global Average Pooling (GAP)

1024개의 feature map을 크기가 1024인 벡터로 변환시킨다. 다시 말해 feature map 하나당 수치 하나로 변환시킨다.

이 과정에서 global average pooling (GAP)을 사용한다. GAP layer는 pool size가 input feature map의 크기와 같은, 즉 이미지 전체에서 딱 하나의 값(평균값)만을 pooling하는 레이어이다.

이 논문 이전에 global max pooling (GMP)를 사용한 weakly-supervised localization 방법이 공개되었었는데 GMP는 가장 activation이 강한 region의 정보만을 가져오기 때문에 사물의 경계선을 파악하는 데에 그쳤다고 한다. 사물의 위치 전체를 localization하는 데에는 모든 activation의 정보를 평균내는 GAP가 더 적합하다.

GAP layer의 출력은 각 feature map(\text{width} \times \text{height} \times 1024)의 평균값의 벡터(1 \times 1 \times 1024)가 된다.

3. Fully connected layer

fully connected layer를 GAP layer 뒤에 하나 추가한다.

이 dense layer의 입력은 GAP layer의 출력, 즉 크기 1 \times 1 \times 1024의 벡터이고 출력은 예측하려는 class label의 갯수이다. Weight matrix W의 모양은 1024 \times \text{(\#classes)}가 된다.

4. Class Activation Map (CAM) 계산

1.의 CONV layer에서의 feature map과 3.의 dense layer에서의 weight W를 사용해서 class activation map (CAM)을 계산한다.

Label=c인 class의 activation map CAM_c는 다음과 같이 계산한다.

CAM_c = \sum_{k=1}^{1024}{w_{k, c}f_k}

여기서 w_{k, c}k번째 GAP 벡터에서 class c로 이어지는 가중치값(Wkc열 원소)이고, f_k는 1.에서 추가한 CONV layer의 k번째 feature map이다.

이렇게 구한 CAM_c는 해당 이미지가 클래스 c에 속한다고 판단내릴 때 ConvNet이 이미지의 어떤 부분을 보고있었는지의 정보를 가지고 있다.

CAM_c의 값이 큰 부분이 클래스 c에 속하는 사물의 위치라고 ConvNet이 판단내린 것이다.

 

응용

Weakly-supervised localization은 여러 방면에 응용될 수 있다. Semantic image segmentation을 위한 데이터를 만드는 데에도 사용될 수 있고, ConvNet이 보고있는 지점을 파악할 수 있다는 점에서 ConvNet이 이미지로부터 학습한 feature를 사람이 직관적으로 이해할 수 있도록 시각화하는 데에도 사용될 수 있다.

이미 학습된 모형을 사용해서 간단히 만들 수 있다는 큰 장점이 있어 여러 분야에 매우 쉽게 응용할 수 있다. 비록 localizer가 아닌 classifier에 비해서는 classification 성능이 떨어지지만 그 차이가 크지 않으며 localization 기능까지 덤으로 추가할 수 있다.

여기서는 직접 간단한 ConvNet을 만들어 두 가지 작업에 응용해보았다.

1) Facial emotion recognition

학습과 예측에 사용한 데이터는 다음과 같다. [Kaggle]

  • Feature: 48 \times 48 크기의 흑백 얼굴 사진.
  • Target: 사진 속 인물의 표정(감정). 7가지 중 하나이다(neutral, fear, happy, sad, surprise, angry, disgust).
  • train set: 25120 imgs / test set: 5382 imgs

이 데이터를 학습해서 사람 표정의 어떤 부분을 보고 감정을 유추하는지를 알아보려 했다.

모형 구조와 학습에 사용한 하이퍼 파라미터는 이 노트북[1, 2]을 참고하시길 바란다.

결과를 살펴보면 꽤 그럴듯하다.

fer-1

주로 행복한 표정은 입 모양을 보고 판단하는 듯하다. 입모양이 크고 반달 모양이면 happy라고 판단할 확률이 크게 올라간다. 놀란 표정은 눈동자의 크기를 보고 판단하고 무표정은 이목구비의 전체적 형태를 보고 판단하는 듯하다.

fer-2

이 예시에서는 True class는 FEAR로 예측에는 실패했지만  SURPRISE라고 판단하는 것도 틀린 판단은 아닐 수 있다는 것을 보여준다. ConvNet이 무엇을 보고있는지를 주의 깊게 봐야하는 이유다.

2) Chest X-Ray (Pneumonia)

의료 이미지를 분석하는 경우엔 올바르게 질병 또는 정상상태를 분류하는 것도 중요하지만, 이미지의 어떤 부분(환부)을 보고 그런 판단을 내렸는지를 살펴보는 것이 매우 중요하다. 어디까지나 최종 판단은 의료 분야의 전문 종사자가 내리는 것인 만큼 전문가가 판단을 내리는 데에 도움이 되도록 뉴럴넷이 판단한 환부의 위치까지 표시해 준다면 더욱 유용할게다.

학습과 예측에 사용한 데이터는 다음과 같다. [Kaggle]

  • Feature: 다양한 크기의 흉부 엑스레이 사진.
  • Target: 폐렴인지 정상인지. [PNEUMONIA / NORMAL]
  • train set: 5216 imgs / test set: 624 imgs

데이터 학습이 용이하도록 모든 사진을 가로 256 픽셀, 세로 200 픽셀로 resize한 후 모형에 입력시켰다. 모형 구조와 학습에 사용한 하이퍼 파라미터는 이 노트북을 참고하시길 바란다 (실수로 zero-padding을 하지 않아서 이미지의 가장자리 부분의 정보 손실이 일어났다).

81%의 꽤 높은 정확도로 test set을 올바르게 진단할 수 있었다.

 

xray-1

폐렴 환자의 흉부 엑스레이 사진에서는 주로 좌폐와 우폐 사이의 공간을 유심히 살펴본 것으로 보인다.

xray-2.png

 

반면 정상 환자의 사진에서는 옆구리 부분의 흉골, 양쪽 폐 아래의 공간을 중요하게 본 듯 하다.

임상 전문가라면 뉴럴넷이 살펴본 위치를 보고 더 많은 아이디어를 얻을 수 있지 않을까?

 

참고

 

 

 

 

 

 

Batch Normalization 이해하기

현대적인 딥러닝 모델을 디자인할 거의 항상 빠지지 않고 쓰이는 테크닉들이 있다. 하나는 recurrent 구조 (LSTM, Attention)이고 다른 하나는 batch normalization (BatchNorm)이다. LSTM과 attention 대해서는 recurrent neural net 다루면서 자세히 살펴보도록 하고 이번 글에서는 학습 과정에서 뉴럴넷을 안정시켜주는 표준화 기법 하나인 batch normalization 대해 다뤄보겠다.

 

  • 기존 방법의 문제점
  • BatchNorm
    • 알고리즘
    • 테스트할 때
    • BN layer
  • TensorFlow 구현

 

기존 방법의 문제점

BatchNorm이 어떤 의미를 가지는지를 알기 위해서는 BatchNorm이 고안되기 이전의 딥러닝 모형 초기화 및 학습 과정 표준화 과정을 둘러볼 필요가 있다.

뉴럴넷이 안정적으로 잘 학습되기 위해서는 입력층에 넣을 인풋과 각 층의 weight를 표준화할 필요가 있다. BatchNorm이 고안되기 전에는 두 가지 방법을 주로 사용했는데, 이전 포스트[1, 2]에서 각각의 방법을 간단히 다룬 바 있다. 간단히 복기하자면 이렇다: (1) 인풋은 centering scaling하고 (2) 인풋 뉴런 n개인 층의 weight \div \sqrt{n/2}로 표준화한다. 단순한 방법이지만 표준화하지 않은 입력, 가중치값을 사용했을 때에 비해 더 빨리, 더 좋은 성능으로 수렴하는 것을 경험적으로 확인할 수 있다.

여기서 중요한 문제가 발생한다. 입력층에 넣는 인풋은 표준화할 수 있다. 뉴럴넷에 넣기 전에 우리가 원하는 방식으로 원하는 만큼 preprocessing을 하면 된다. 그 결과 입력층의 input distribution은 항상 비슷한 형태로 유지가 되고 안정적으로 가중치 학습을 진행할 수 있다.

e18489e185b3e1848fe185a6e1848ee185b53.png

그러나 은닉층은 인풋의 분포가 학습이 진행됨에 따라 계속 변한다. 은닉층은 이전 레이어의 activation f(XW)을 입력으로 받는다. 학습 과정에서 가중치 W의 값이 W^\prime로 업데이트되면 이전 레이어의 activation 또한 f(XW^\prime)로 바뀌게 된다. 은닉층의 입장에서는 인풋 값의 분포가 계속 널뛰는 것이나 마찬가지이다. 입력 분포의 형태가 유지되지 않으므로 학습도 잘 진행되지 않는다. 그라디언트 값이 큰 학습 초기일수록 문제가 더 심각해진다.

스케치

 

Batch Normalization

알고리즘

바로 위에서 언급한 문제를 internal covariate shift라고 한다. 그대로 입력층보다 깊은, 내부에 있는(internal) 층의 입력값, 공변량(covariate) 고정된 분포를 갖지 않고 이리저리 움직인다(shift) 의미이다. BatchNorm 바로 internal covariate shift 해결하는 테크닉이다.

[1]

은닉층의 입력도 표준화한다면 안정적으로 깊은 레이어의 가중치도 학습시킬 수 있을 것이다. “은닉층의 입력을 표준화한다는 것은 곧이전 층의 출력(raw activation)을 표준화한다는 의미와 같다.

딥러닝은 거의 항상 전체 샘플을 mini batch로 나누어 학습하고 가중치를 업데이트하므로 이전 층의 raw activation을 표준화할때도 각 batch마다 따로 표준화하면 된다.

스케치

이와 같이 각각의 minibatch 평균 \mu_{\mathcal{B}} = \frac{1}{m} \sum_i {x_iw_i} 표준편차 \sigma_{\mathcal{B}} = \frac{1}{m} \sum_i {(x_iw_i - \mu_{\mathcal{B}})^2} 표준화한 activation a_s = f(\frac{XW_1 - \mu_{\mathcal{B}}}{\sigma_{\mathcal{B}}}) 은닉층 B 입력으로 사용하면 은닉층 B 입력은 고정된 분포를 따른다.

쉬워도 너무 쉽다. 이렇게만 하면 될 것 같지만..

[1 문제점]

문제가 가지 있다. 이렇게 은닉층의 입력을 표준화하면 gradient update 과정에서 bias(편향)값이 무시된다. [1]만을 사용해서 표준화한다고 그라디언트 업데이트 과정을 자세히 살펴보자. Raw activation a_r = wx + b라고 E(a_r) = \frac{1}{n} \sum_i a_{r_{i}}이므로

  1. 그라디언트를 계산한다.
    • \Delta b \propto - {\partial L}/{\partial b},  where L is a loss function.
  2. 편향(과 가중치)을 업데이트한다.
    • b \gets b + \Delta b
  3. 편향을 업데이트한 이후의 raw activation:
    • a_r ^\prime = wx + (b + \Delta b)
  4. [1] 이용해서 센터링만 raw activation:
    • \begin{array}{lcl} a_{r_{centered}} ^\prime &=& a_r ^\prime - E(a_r ^\prime) \\ &=& \{(wx + b) + \Delta b\} - \{ E[wx + b] + \Delta b \} \\ &=& (wx + b) - E[wx + b] \end{array}

Bias b 업데이트 \Delta b 완벽하게 캔슬되었다. 초기 편향값에서 이상 업데이트가 되지 않는 것이다. 종류의 파라미터 w, b 사용했는데 파라미터 w 가지만 사용하는 단순한 모형으로 irreversible하게 변환된 것이다.

이 때문에 b 대신 편향의 역할을 할 파라미터를 추가해야한다. 이 파라미터는 그라디언트 업데이트 과정에서 무시되어서는 안된다.

다른 문제도 있다. raw activation 분포를 고정시키는 것은 좋지만 항상 N(0, 1) 고정시킬 필요는 없다. 적절하게 scaling, shifting activation \gamma \cdot \frac{a_r - \mu_{\mathcal{B}}}{\sigma_{\mathcal{B}}} + \beta 사용하는 것이 학습에 도움될 수도 있다.

형태의 activation 사용할 경우 필요하다면 표준화를 되돌릴 수도 있다. \gamma = \sigma_{\mathcal{B}}, \beta = \mu_{\mathcal{B}} \gamma \cdot \frac{a_r - \mu_{\mathcal{B}}}{\sigma_{\mathcal{B}}} + \beta = a_r이기 때문이다.

[2]

위의 문제를 극복하기 위해 표준화한 scaling shifting raw activation, 즉

a_{BN} = \gamma \cdot \frac{XW_1 - \mu_{\mathcal{B}}}{\sigma_{\mathcal{B}}} + \beta

activation function f 입력으로 사용한다. 은닉층 B 입력으로는 f(a_{BN}) 사용한다. 방법을 BatchNorm이라고 한다. \gamma, \beta 파라미터로 학습 과정에서 업데이트되는 값이다.

BatchNorm 장점이 꽤나 많은데

  • bias 업데이트를 무시하지 않는다. \beta bias처럼 행동한다. \beta 업데이트는 표준화해도 캔슬되지 않는다.
  • 은닉층마다 적절한 input distribution 가질 있다. scaling factor \gamma shifting factor \beta 사용해서 적절한 모양으로 입력분포를 조정할 있다.
  • 필요한 경우 표준화를 하지 않을 수도 있다. 위에서 언급한 \gamma = \sigma_{\mathcal{B}}, \beta = \mu_{\mathcal{B}} 경우이다.
  • Activation 값을 적당한 크기로 유지하기 때문에 vanishing gradient 현상을 어느정도 막아준다. 덕분에 tanh, softmax같은 saturating nonlinearity 사용해도 문제가 생긴다.
  • batch-wise로 계산하기 때문에 컴퓨팅하기 용이하다.
  • 위의 장점들을 모두 가지면서, 동시에 층마다 입력 분포를 특정 형태로 안정시켜서 internal covariate shift 방지할 있다.
  • 입력 분포가 안정되므로 학습시 손실함수가 더 빨리, 더 좋은 값으로 수렴한다.
  • 초기 learning rate를 크게 설정해도 안정적으로 수렴한다고 한다.
  • Weak regularizer로도 작용한다고 한다.

이쯤 되면 거의 만능이다.

테스트

지금까지 다룬 내용은 모두 학습 과정에서 일어나는 일들이다. 학습 과정에서는 raw activation minibatch mean, stdev 표준화하면 됐었다. 그런데 학습을 마치고 테스트(또는 evaluation, inference) 때에는 minibatch mean, stdev 존재하지 않는다.

테스트 과정에서는 대신 전체 training data mean, stdev 사용해서 BatchNorm 한다. 전체 training data mean, stdev 번에 계산하기에는 메모리의 제약이 있으므로, minibatch statistic 평균낸 값을 대신 사용한다.

, n개의 minibatch 있을 ,

\hat{\mu} = \frac{1}{n} \sum_i {\mu_{\mathcal{B}}^{(i)}}
\hat{\sigma} = \frac{1}{n} \sum_i {\sigma_{\mathcal{B}}^{(i)}}

Minibatch statistic 따로 저장할 필요 없이 학습 과정에서 moving average \hat{\mu}, \hat{\sigma} 계산하면 된다. Exponential moving average 사용해도 좋다.

i번째 minibatch statistic 각각 \mu_{\mathcal{B}}^{(i)}, \sigma_{\mathcal{B}}^{(i)}라고 ,

\hat{\mu} \gets \alpha \hat{\mu} + (1-\alpha) \mu_{\mathcal{B}}^{(i)}
\hat{\sigma} \gets \alpha \hat{\sigma} + (1-\alpha) \sigma_{\mathcal{B}}^{(i)}

BatchNorm layer

ReLU activation 뉴럴넷의 레이어로 나타낼 있듯 BatchNorm 또한 레이어로 표현할 있다. BN layer raw activation activation function 사이에 위치한다. Convolutional layer에 BatchNorm을 적용하고 싶을 때에도 동일하게 raw feature map과 ReLU layer 사이에 BN layer를 추가하면 된다.

e18489e185b3e1848fe185a6e1848ee185b57.png

BN layer mini batch raw activations a_r 입력받아 아래와 같은 연산을 수행하여 다음 레이어(activation function f) 전달한다.

BN_{\gamma, \beta}(a_r) = \gamma \cdot \frac{a_r - \mu_{\mathcal{B}}}{\sigma_{\mathcal{B}}} + \beta

또한 테스트 사용하기 위해 학습 과정에서 minibatch statistic exponential moving average(또는 그냥 MA) minibatch마다 업데이트한다.

 

TensorFlow 구현

구글에서 고안한 방법답게 TensorFlow에 이 내용들이 친절히 함수로 구현되어 있다. tf.nn.batch_normalization, tf.contrib.slim.batch_norm를 쓰면 간단히 위 알고리즘을 모형 구축에 사용할 수 있다.

tf.nn.batch_normalization을 사용할 경우, minibatch statistic의 EMA를 계산하는 코드를 따로 작성해야 한다.

tf.contrib.slim.batch_norm를 사용할 경우 is_training 옵션을 True로 주면 자동으로 EMA를 계산해서 저장하고, False로 주면 저장된 EMA 값으로 activation을 표준화한다.

TF-Slim 레이어에도 쉽게 적용시킬 수 있다.

import tensorflow as tf
import tensorflow.contrib.slim as slim

bn_params = {"decay": .9,
             "updates_collections": None,
             "is_training": tf.placeholder(tf.bool)}
net = slim.fully_connected(input, 1024,
                           normalizer_fn=slim.batch_norm,
                           normalizer_params=bn_params)

Convolutional layer에도 마찬가지다.

net = slim.conv2d(input, 64, [5,5], padding="SAME",
                  normalizer_fn=slim.batch_norm,
                  normalizer_params=bn_params)

 

 

참고

ConvNet (CNN) 이해하기

지난 시리즈에서는 뉴럴넷 중 가장 단순한 형태인 MLP, dense (fully connected) layer만을 사용하는 뉴럴넷과 이를 학습시키는 방법에 대해서 살펴보았다. MLP를 사용하면 효과적으로 비선형 함수를 모사할 수 있었고 이 덕분에 기존의 선형함수 기반 모형에 비해 훌륭한 성능을 낼 수 있었다.

(1) 선형 분류기
(2) 퍼셉트론과 신경망
(3) 학습에는 왕도가 없다
(4) 디테일

그러나 실제 데이터를 올바르게 분류하거나 회귀할 때에는 이보다는 복잡한 구조가 필요한 경우가 있다. 다음의 몇 가지 상황을 생각해보자.

1) MLP에서는 별도의 feature engineering 없이 raw feature 곧바로 입력층에 넣었었다. 붓꽃 데이터, 또는 데이터의 차원이 크지 않은 경우에는 raw feature를 전부 사용해도 모형의 파라미터 수가 크지 않았기 때문에 학습 과정에서 문제가 생기지 않았고 성능도 좋았지만, 이미지와 같은 데이터에서는 문제가 달라진다.

이미지 데이터를 예시로 들어보자. 가로 48픽셀, 세로 48픽셀의 컬러(RGB) 이미지 데이터를 생각해보자. 이미지의 각각의 픽셀을 하나의 feature 사용해서 MLP 입력층에 넣을 있을 것이다. 이렇게 feature engineering 없이 픽셀의 raw value 전부 입력으로 넣는다면, 뉴럴넷이 감당해야 하는 feature 차원수는 48 \times 48 \times 3 = 6912 된다. 겨우 가로세로 48픽셀밖에 안되는 저화질 이미지였는데도 feature 차원이 7000 달한다. Feature 갯수가 이렇게 많아지게 되면 컴퓨팅 속도가 저하되고 오버피팅의 위험이 생길 있다.

2) MLP에서는 샘플이 등장한 맥락을 고려하지 않았다. , 샘플들이 정렬되어 있는 순서가 중요하지 않았다. 그러나 주식 일별 종가, 문장 속 단어와 같은 데이터에서는 각 샘플의 앞 뒤로 어떤 샘플들이 있었는지의 정보(‘문맥’)도 중요하다. 이 경우엔 MLP만으로는 충분히 좋은 성능을 발휘할 수 없다.

이번 시리즈에서는 전통적인 dense layer 외에 이미지 등의 데이터를 처리하기 위해 새로이 고안된 뉴럴넷의 레이어에 대해서 살펴보고자 한다. 시작은 convolutional neural network (CNN; ConvNet) 구성하는 레이어들이다.

 

  • 2-D CONV layer
    • Convolution 연산
    • 수학/신호처리학적 의미
  • RELU layer
  • POOL layer
  • 일반적인 ConvNet 구조
  • ConvNet 개선하기
    • 1×1 convolution (InceptionNet)
    • Dense CONV 표현하기
    • 변형된 convolution

 

Convolutional (CONV) layer

앞의 예시에서 이미지의 픽셀을 그대로 feature 사용하는 것은 좋지 않은 방법이라는 것을 언급했었다. 대신 이미지로부터 독특한 특성만을 뽑아내어 feature 사용한다면 좋은 성능을 있을 것이다. 과정을 feature extraction이라고 하고, 이미지에서 feature extractor 동작하는 레이어가 바로 convolutional layer이다.

Convolutional layer 가지 가정을 전제로 한다.

  1. Local connectivity: 이전 레이어의 모든 뉴런과 연결되는 MLP에서와는 달리 CONV layer는 이전 레이어의 뉴런 중 일부와만 연결된다. 즉 이미지의 모든 픽셀과 연결되는 대신 이미지 중 일부분의 픽셀과만 연결된다.
  2. Spatial invariance: 이미지의 한 부분의 데이터 분포는 다른 부분에서의 데이터 분포와 다르지 않다. , 이미지의 분포적인 특성은 어느 부위에서든 동일하다고 가정하는 것이다.

Dense layer에서 입력 샘플을 1차원의 벡터 데이터로 보았다면, Convolutional layer 입력 샘플을 3차원의 텐서(Tensor) 데이터로 인식한다. Convolutional layer 입력 3차원 데이터를 적절히 변형해서 또다른 3차원 데이터를 출력한다.

스케치

Convolutional layer 하는 일을 자세히 살펴보자.

  1. 먼저 이미지에서 특징적인 부분을 추출하는 데에 사용될 필터(또는 커널)가 정의되어야 한다. 필터 크기는 이미지 크기보다 작거나 같다. 필터는 인풋 이미지를 시작부터 끝까지 차례차례 훑으면서(마치 sliding window처럼) 이미지 픽셀 값에 필터 값을 곱해 더한 값을 출력한다.
  2. 필터가 이미지를 얼마나 자세히 훑으면서 지나갈지를 결정해야한다. 이것을 stride라고 한다. Stride 1로 정의된 경우, 필터는 이미지 위를 한 번에 한 픽셀씩 이동하면서 자세히 훑는다. Stride 10으로 정의된 경우, 필터는 이미지 위를 한 번에 10픽셀씩 이동하면서 대충 훑는다. (가로방향 stride와 세로방향 stride를 다르게 설정할 수도 있다)

스케치

f_{ij}는 필터의 ij열 값, x_{ij}는 인풋 이미지의 ij열 값이다.

convolutional layer는 필터를 일정 간격씩 이미지 위에서 이동시키면서 필터 값을 이미지 픽셀값에 곱하여 더한 값을 출력한다. 출력된 feature map의 픽셀 하나는 인풋 이미지 중 3×3만큼의 정보를 바탕으로 결정된 값이다. feature map receptive field 크기는 3×3이 된다는 뜻이다. 인풋 이미지 중 3×3만큼을보고‘ Feature map의 픽셀 하나의 값을 계산했다는 의미다.

애니메이션으로 살펴보면 이해가 쉽다. 파란색은 인풋 이미지, 초록색은 필터이다.

1*VVvdh-BUKFh2pwDD0kPeRA@2x

초록색 필터가 이미지를 칸씩(stride=1) 돌아다니면서 곱의 합을 출력하는 것을 있다. 이렇게 출력된 (convolved feature 또는 feature map; 붉은색) 추출된 이미지의 특징이라고 생각하여 feature 사용할 있다.

필터 하나 하나의 feature map 만들어지므로, 필터 n개를 사용하는 CONV layer 출력은 depth=n 갖는 텐서가 된다. 만약 32×32짜리 컬러(depth=3) 이미지 하나가 필터 10개를 사용하는 CONV layer 통과하면 출력 depth 10 된다.

1*BSLjlJf31gj98ABJMCt3-g@2x

수학/신호처리학에서의 cross-correlation

주의해야할 것은 수학에서 정의하는 convolution 연산과는 다른 연산이라는 것이다. 오히려 뉴럴넷에서의 convolution 연산은 수학/신호처리에서의 cross-correlation 같다.

신호처리에서 cross-correlation은 두 신호간의 유사성을 잴 때에 사용되는 연산이라고 한다. 때문에 뉴럴넷에서의 convolution convolution filter input image 사이의 유사성을 측정해서 출력하는 연산이라고 생각할 수 있다. 필터와 이미지의 패턴이 유사하다면 절대값이 큰 값을, 그렇지 않다면 작은 값을 반환하는 것이다.

예를 들어, 아래처럼 우상에서 좌하로 향하는 사선 모양의 convolution filter는 이미지 내에서 사선 모양이 있는 부분(주로 이미지의 윤곽선)을 보면 큰 값을 출력한다. 아래 돌고래 이미지에서 우상좌하 사선 모양의 윤곽선이 도드라지게 추출된 것을 확인할 수 있다.

실제 ConvNet에서는 필터를 우리가 직접 정해주지 않고 알고리즘이 데이터를 바탕으로 문제 해결에 적합한 형태가 되도록 학습해나간다.

Zero padding

이미지에 필터를 적용할 때, 아무리 촘촘히(stride=1) convolution을 해도 출력된 feature map의 가로세로 크기는 원본 이미지에 비해 반드시 작아질 수 밖에 없다. 원본 이미지와 가로세로 크기가 같은 feature map을 얻기 위해서는 필터를 적용하기 전에 입력 이미지에 처리를 해주어야 한다.

1*W2D564Gkad9lj3_6t9I2PA@2x

단순하고도 널리 사용되는 방법이 바로 zero padding, 즉 원본 이미지의 상하좌우를 0값으로 둘러싸는(padding) 방법이다. 아무 정보가 없는 0값으로 이미지를 패딩함으로써 feature map의 가로세로 크기를 인풋과 같게 유지하면서 convolution layer를 깊게 쌓을 수 있다.

 

ReLU layer

CONV layer 통해서 이미지로부터 추출한 feature map에서 양의 값만을 활성화시키는 레이어다. ReLU layer에서는 feature map에서 특히나 두드러지는 특징을 다시 추출한다.

사실 별도의 layer라고 칭하기는 뭣하고, CONV layer activation function으로 ReLU 사용한 것이라 이해하면 쉽다.

output_19_0

ReLU activation 적용 후 우상좌하 사선 모양의 윤곽선이 매우 잘 추출되었다.

 

POOL layer

수많은 이미지를 처리하는 과정에서 필연적으로 맞딱뜨릴 수 밖에 없는 것이 바로 메모리 문제다. 특히나 CONV layer는 원본 이미지의 가로세로 크기를 유지하면서 depth를 수백~수천 수준으로 키우기 때문에 메모리를 많이 먹는 레이어다. 이때문에 이미지와 feature map의 가로세로 크기를, 특징은 유지하되 적절한 수준으로 다운샘플링하여 메모리를 절약할 필요가 있다. 다운샘플링 방법 중 ConvNet에서는 pooling을 사용한다.

가장 빈번히 사용되는 풀링은 max pooling이다. Max pooling pool region 내에서 가장 수치가 위치의 값만을 가져온다.

1*GksqN5XY8HPpIddm5wzm7A

2×2 pool size, stride 2 max pooling 적용한 모습이다. 2×2 pool region 내에서 가장 수치가 값만을 샘플링한 것을 있다.

Weakly-supervised localization 등의 문제를 해결할 때에는 average pooling 사용하기도 한다. Average pooling 해당 pool region 내의 모든 값을 평균내어 가져온다.

돌고래 이미지의 예시에 2×2 max pooling (stride 2) 적용한 결과, 아래와 같이 feature map 특징은 유지하면서 이미지 크기를 1/4 축소할 있었다.

output_20_0

 

ConvNet 일반적인 구조

일반적으로 ConvNet의 구조는 다음과 같이 표현할 수 있다.

\{(CONV + RELU) \times n + POOL \} \times m + (Dense \times k)

여기서 CONV + RELU + POOL feature extractor 역할을 하고 Dense layer classifier 또는 regressor 역할을 한다. 이미지 분류 문제를 예시로 들자면, CONV + RELU + POOL 인풋 이미지로부터 분류에 사용할만한 특징적인 feature 추출하고 Dense layer 추출된 feature 입력받아 이미지를 적당한 카테고리로 분류한다.

이 때 얕은(입력층에 가까운) CONV layer일수록 단순한 특성윤곽선, 음영 등을 추출하고 깊은(출력층에 가까운) CONV layer일수록 복잡한 특성동물 모양, 특정 색상 조합 등을 추출한다.

2012 ILSVRC에서 우승한 AlexNet이 유행시킨 구조라 할 수 있겠다. 딥러닝을 사용해서 효과적으로 이미지를 분석할 수 있다는 것을 보여준 첫 사례다.

1*qyc21qM0oxWEuRaj-XJKcw

그림에서 있듯, 5개의 CONV layer 3개의 dense layer 사용하여 최종적으로 1000 카테고리에 대한 softmax 값을 출력한다. AlexNet 대해서는 이후 기회가 자세히 다루도록 한다.

 

ConvNet 개선하기

1×1 convolution

어느 모형에서나 파라미터 수가 지나치게 많아지는 것은 overfitting 컴퓨팅 속도 저하를 의미하기에 좋지 않다. convolution 연산에 사용되는 파라미터 (필터값 ) 줄이기 위해서 1×1, 1xn 또는 nx1 convolution 사용할 있다.

n \times n \times 3 형태의 인풋 이미지에 receptive field 3×3 되도록, 128개의 필터를 사용해서 convolution 한다고 가정해보자. 단순히 3×3 filter 사용할 수도 있다. 경우 필요한 파라미터 수는 3 \times 3 \times 3 \times 128 = 3456개이다.

반면 1×1 > 1×3 > 3×1 순서로 convolution 한다면? 필요한 파라미터 수는 (3 \times 1 \times 1 \times 128) + (1 \times 1 \times 3 \times 128) + (1 \times 3 \times 1 \times 128) = 384 + 384 + 384 = 1152 뿐이다. 같은 크기의 receptive field 유지하면서 파라미터 수를 거의 1/3 수준으로 줄였다!

inception-v4-fig-5

구글에서 개발한 GoogLeNet에서 바로 이 방법을 활용한 Inception 구조를 사용한다.

Dense layer CONV layer 표현하기

Dense layer CONV layer 차이점은 local connection 뿐이다. Dense layer에서는 이전 레이어의 모든 뉴런이 다음 레이어의 모든 뉴런과 연결되어 있는 반면(그래서 fully connected layer라고도 불린다), CONV layer에서는 이전 레이어의 일부분만이 다음 레이어의 뉴런과 연결되어 있다.

그럼 CONV layer에서 이전 레이어의 모든 뉴런을 다음 레이어와 연결시킨다면? 즉 인풋 이미지의 일부분에만 필터를 적용시키지 않고 이미지 전체에 이미지와 동일한 크기의 필터를 적용시킨다면? Dense layer와 동일한 연산을 하는 CONV layer를 만들 수 있다. 계산해보면 파라미터 수도 일치한다.

스케치

Dense layer CONV layer로 대체함으로써 인풋 이미지 크기를 고정하지 않아도 된다는 이익을 얻을 수 있다. 사전에 정의된 크기의 이미지를 인풋하지 않으면 오류를 뿜는 Dense layer와는 달리, CONV layer는 사전 정의된 것보다 큰 이미지를 인풋해도 오류 없이 연산을 해낸다. (다만 3차원 텐서 형태로 출력하기는 한다)

1*wRkj6lsQ5ckExB5BoYkrZg

Fully Convolutional Network (FCN)이 바로 이 구조를 처음 제안한 모형이다.

특수한 Convolution

외에도 위에서 정의한 것과는 다른, 서로다른 문제에 특화된 특수한 convolution 사용할 수도 있다. 예를 들어 atrous convolution 구멍이 숭숭 뚫린 듯한 모양의 필터를 사용해서 receptive field 더욱 향상시킨다. Deconvolution Convolution 연산을 역으로 수행해 추출된 feature map 원본 이미지에 가까운 형태로 되돌린다.

특수한 convolution 연산들에 대해서는  페이지를 참고하면 좋다.

 

참고

Google Duplex: 인공지능-인간간 대화를 꿈꾸다

3일쯤 전에 있었던 구글 IO 2018 키노트에서는 구글 포토의 신기능, 어시스턴트의 새로운 목소리, 새로 디자인된 TPU 3.0 등등 많은 업데이트가 있었다.

아니나 다를까 AI와 딥러닝은 이 모든 업데이트들을 관통하는 핵심 제재였다. 안드로이드 신기능 중 상당수는 어시스턴트, 렌즈와 같이 AI에 뿌리를 두는 기술들이었고 새로 발표한 TPU 역시 딥러닝에 특화된 하드웨어라는 점에서 그러했다.

이처럼 AI 관련 업데이트가 많을거라 예상은 했지만, 개 중에서 특히나 눈을 휘둥그레지게 한 기능이 있어 간단히 다뤄보고자 한다. 바로 Google Duplex라 명명된 기능인데, 상용화된다면/혹은 상용화되기 전부터 상당히 큰 파장을 일으킬 것으로 생각된다.

 

Google Duplex?

OpenTable 등 온라인 예약 서비스 시장이 꽤나 활발한 북미 시장에서도 소규모 영업장과 같이 온라인 예약을 지원하지 않는 경우에는 사람이 직접 전화를 걸어서 예약을 해야했다. 그런데 Google Duplex는 사람에게 명령을 받은 구글 어시스턴트가 직접 영업장에 전화를 걸어서 예약을 한다.

백문이 불여일견이다.

 

영상에서 소개된 실제 Duplex-사람간 통화에서 주목할만한 점은

  1. 액센트, 인토네이션이 완벽에 가깝다. 타사의 음성 기반 어시스턴트를 사용할 때 느껴지는 약간의 어색함이 없다.
  2. hmm, um.. uh, 같은, 사람이 생각할 때에 사용할만한, 의미 전달과는 관계가 없는 표현을 사용할 수 있다. (자연어처리, 음성생성 분야에서는 이런 단어들을 speech disfluency라 한다)
  3. 상대의 발화에 따라 대답까지의 딜레이 시간을 조절한다. “여보세요?”같은 짧은 발화에는 빠르게 대답하는 한편, “정오에는 예약이 불가능해요”같은 긴 발화에 대해서는 대답하기까지의 딜레이가 조금 더 길다.
  4. 뜻 밖의 시나리오에도 대응할 수 있다. “5명 이상인 경우에만 예약이 가능해요. 그냥 매장에 오세요”라는 말에 “그냥 가면 오래 기다리나요?”라고 물어볼 수 있다.

사실 대화 내용 전체가 주목할 만 하다. 그저 놀랍다.

 

Duplex의 아키텍처

개인적으로는 알파고 이상의 충격을 받았던 터라 그 아키텍처가 심히 궁금해서 찾아보았는데, 아직까지 공식적으로 공개된 디테일은 거의 없었다.

구글 공식 AI 블로그에 공개된 매우 단순화된 아키텍처는 다음과 같다.

뉴럴넷에 인풋으로 들어가는 Feature는 세 가지 정도로 분류할 수 있다. (1) 대화 상대방의 음성 신호. (2) 1의 음성 신호로부터 추출한 발화 텍스트. 텍스트 추출에는 구글이 직접 개발한 ASR(automatic speech recognition)이 사용된다. (3) 발화가 이루어진 환경, 문맥과 관련된 정보들. 이전까지 나눈 대화라던가, 어시스턴트가 사람에게 하달받은 명령, 대화 중인 영업장의 업종, 대화 중인 시간 등의 정보가 사용되는 듯하다.

이 Feature들로 RNN(recurrent neural network)을 돌린다고 한다. (아직 개발 중이니 당연하겠지만 디테일이라곤 찾아볼 수 없는 설명이다. 나중에라도 세부 내용을 좀 더 알려줬으면 좋겠다) RNN은 인풋으로 들어온 상대방의 발화에 대해 어시스턴트가 대답할 내용을 텍스트 형태로 출력한다.

RNN에서 출력된 텍스트를 구글의 TTS(text-to-speech) 시스템이 음성 신호로 변환한다, 즉 “읽는다”. 사람만큼이나 자연스럽게 말하게 하기 위해 딥마인드의 WaveNet과 구글브레인의 Tacotron을 사용하는 음성합성 엔진을 사용한다고 한다.

요약하자면 그냥 [음성신호와 발화내용과 문맥 -> RNN -> 대답할 텍스트 -> TTS -> 대답 음성]이 전부다. Duplex의 실제 통화 음성을 들었을 때의 충격에 비하면, 현재까지 발표된 내용에서는 기술적으로 대단한 부분은 딱히 없다. 이후의 발표가 더욱 기대되는 이유다.

추가적으로 실제 통화가 이루어지는 중에 사람이 개입해서 올바른 대화를 지시할 수 있도록 real-time supervised learning을 사용했다고 한다.

 

한계

Duplex도 한계는 명확하다. Duplex가 이렇게 자연스러운 대화를 할 수 있는 도메인은 오직 “특정 영업장에 서비스를 예약하고자 할 때”, “영업시간 등의 영업장 정보를 묻고자 할 때 뿐이다. 상기된 것과 다른 목적의 대화 또는 목적이 없이 시작되는 일상 대화(small talk)는 할 수 없다.

사실 이건 Duplex의 문제라기보다는 현 시대 인공지능의 한계, 또는 자연어처리 문제의 내재적인 복잡성이라고 생각할 수 있다. 작년 11월에 아마존이 주최한 Alexa Prize 2017에서는 음성 어시스턴트 알렉사가 가장 오랫동안 일상적인 대화를 이어나가게 하는 팀에 총 백만달러의 상금을 걸기도 했었다 (1등 팀이 10분 22초라는 성적을 냈다). 50만 달러를 가져갈 수 있었던 이유가 고작 10분간의 대화라니, 이 문제가 얼마나 어려운 문제인지 알려주는 예시라 할 수 있겠다. 누군가는 사람과 20분 자유대화를 할 수 있는 프로그램을 개발하는 것은 “달 착륙 수준이 아니라, 화성 여행을 하는 것이나 마찬가지”라고 평가했었다.

 

의의와 영향

Duplex가 발표됨에 따라 기술과 규제의 측면과 사회윤리적 측면에서 다양한 논의를 해볼 수 있는 주제가 던져졌다고 본다. 여기서는 짧은 시간동안 생각해본 몇 가지만 다뤄보았다.

첫 번째는 인공지능이 할 수 있는 것의 영역에 대한 이야기다.

인공지능과 딥러닝이 일궈온 놀라운 성과가 있었음에도, 지금까지 혁신적인 성과가 있었던 것은 주로 이미지/영상 인식 및 생성 분야였다 (CNN, DCGAN). 물론 문맥이 있는 데이터, 특히 자연어 처리에서도 이에 버금가는 성과가 있었다고 알고는 있었지만, 사람이 대화 상대가 기계일지도 모른다는 생각조차 하지 못할 정도로 발전했는지는 미처 알지 못했다.

인공지능이 뭔가를 할 때마다 나오는 말 같아서 식상하지만, Duplex는 이런 점에서 다시 한 번 ‘인공지능이 할 수 있는 것’의 영역을 크게 넓혔다고 생각된다. 어쩌면 이번에 넓힌 영역의 크기는 알파고가 했던 것 그 이상일지도 모른다.

두 번째는 사회적, 윤리적 영향에 대한 이야기다.

1)

Duplex가 발표되고 나서, 트위터를 비롯한 몇몇 포럼에서는 많은 의견이 오갔다. 기술 발전에 놀라워 하는 의견이 대부분이었지만 몇몇은 끔찍한 아이디어라고 평가하기도 했다. Duplex를 끔찍해하는 사람들의 의견에는 인간 특유의 것이라 믿었던, 즉 인간성에 대한 배신감이 서려있다. 더 이상 실시간 대화는 (특정 분야의 대화에 한해서는) 사람만의 것이 아니다.

바둑에서도 한 차례 이런 일이 있었지만 그 때는 이처럼 거부감이 크지는 않았다. Google Duplex에 대한 거부감이 큰 것은 Duplex가 정복한(것으로 보이는) 분야가 하필 대화이기 때문일 것이다.

인간의 언어를 사용한 대화는 실로 인간만 하는 행위이(었)다. 지능이 높은 동물들도 대화를 한다고는 알려져 있지만, 우리의 언어를 사용하지는 않았다. 사람의 말로 자연스럽게 대화할 수 있는 능력은 곧 인간성을 구성하는 핵심 중 하나였다. 곧, 대화에 자연스럽게 참여하는 모든 대상은 사람으로 간주할 수 있다는 뜻이었다.

물론 Google Duplex 이전의 어시스턴트도 자연스러운 대화가 가능했다. 하지만 이때는 목적을 가지고 대화를 시작하는 주체가 항상 사람이었다. 어시스턴트는 인간의 명령을 대기하고 있을 뿐이었다. 초기발화주체가 항상 사람이라는 일방성이 존재했기 때문에 우리는 발화 상대가 인간이 아님을 알 수 있었고, 따라서 거부감이 느껴지지 않았다.

Google Duplex를 탑재한 어시스턴트는 “인간성”을 획득함과 동시에, 기존 어시스턴트에 존재했던 발화의 일방성을 깨버렸기 때문에 문제가 된다. 이제는 인공지능이 먼저 목적을 가지고 발화를 시작한다. 경우에 따라서는 사람을 수동적인 존재로 만드는 듯한 느낌까지 준다. 사람(영업장의 직원)이 인공지능(구글 어시스턴트)의 발화를 기다리는 것 같은 상황이 연출된다.

2)

요즘들어 젊은 세대의 통화공포증에 대해 연구가 진행 중이라고 한다. ‘공포증’이라 이름붙일만한 수준은 아니지만 본인도 통화를 꺼리고 메신저를 선호하는데, 비슷한 생각을 하는 이들의 의견을 종합해보면 그 이유는 이렇다.

  • 발화는 수정이 불가능하다.
  • 물음과 답변 사이의 긴 딜레이가 용납되지 않는다. 준비할 수 없는 느낌이다.
  • 주로 메신저를 사용하는 등, 안하다보니 통화가 필요한 상황에서도 하기가 겁난다.

Duplex는 마치 이 문제를 해결하기 위해서 개발된 듯하다. 대부분의 “통화가 필요한 상황”은 예약이나 영업시간 질문 등을 위한 것이니, 어시스턴트가 전화를 대신 해주면 전부 해결된다. 유일한 문제는 이게 근본적인 해결책이 아니라는 데에 있다.

 

혼란 속에서 커뮤니티엔 이런 토의주제가 등장했었다:
“당신이 대화하는 대상이 사람이 아니라는 것을 의무적으로 밝히도록 해야 하는가?”

 

맺으며

구글에 의하면 Duplex 기능은 지난 몇 년간 개발되어 왔고, 앞으로도 상용화까지는 상당한 시간이 더 필요할 것이라고 한다.

당장의 문제는 아니지만 자율주행차 논의와 함께 대화봇 논의도 지금보다 활발히 진행될 필요가 있다고 본다. 충분히 대비해서 해로울 것은 없다.

Duplex가 끼칠 영향이 마음 한 켠에선 걱정되는 한편, 실은 이 기술이 가져다줄 미래가 기대되는 마음이 훨씬 더 크다. 당장 오늘 해야하는 전화도 Google Duplex가 있었다면 (그리고 한국어를 지원한다면) 난 주저않고 어시스턴트에게 부탁했을 것이다.

아키텍쳐가 더 자세하게 공개가 된다면 이 내용도 기회가 될 때 다루고싶다.

 

인공신경망 이해하기 (4) 디테일

지금까지의 글에서는 머신러닝의 기초와 신경망의 구조 및 학습에 대해서 다루어보았다. 그러나 중간중간추후에 기회가 된다면 자세히 다루겠다고 미뤄둔 내용들이 있다.

(1) 선형 분류기
(2) 퍼셉트론과 신경망
(3) 학습에는 왕도가 없다

이번 글에서는 지금까지 다룬 뼈대에 살점을 붙여보려고 한다. 자주 등장하는 개념을 위주로 정리해보려 했다. 여기서부터는 신경망을 포함한 머신러닝 학습의 디테일이다.

 

  1. 정규화
    1. Ridge regularization
    2. Lasso regularization
    3. Elastic Net regularization
    4. Dropout
    5. DropConnect
  2. 수치적 최적화
    1. 일반 경사하강법
    2. 모멘텀 경사하강법
    3. 적응형 학습률 방법
  3. 데이터 전처리
    1. 센터링과 스케일링
    2. minmax 표준화
    3. 주성분 분석 (PCA)

 

정규화

정규화의 개념과 학습에서의 역할에 대해서는 이 글에서 언급했었다 ((1) 선형 분류기정규화 손실 추가하기). 간단히 다시 정리하자면, 분류기가 데이터에서 분류문제와 상관이 없는 노이즈까지 학습하지 않도록 제약을 두는 것을 말한다.

정규화 방법들의 디테일에 대해서 살펴보기 전에 가중치 행렬과 편향 벡터를 다시 들여다보자. 다음은 신경망의 한 층만을 식으로 나타낸 것이다. 선형 분류기의 판별함수와도 같은 식이다.

스케치

스케치 재활용 장난아니다..는 생각은 접어두고, 이 식 XW + b은 다음과 같이 더욱 간단히 줄일 수 있다. 두 식의 계산 결과는 완전 동일하다.

스케치

 

무슨 짓을 한걸까? feature 행렬 X의 열에 1열벡터를 추가해주었고 가중치 행렬 W와 편향 벡터 b를 합쳐서 하나의 큰 가중치 행렬로 만들었다. 이처럼 feature 행렬의 마지막 열에 1벡터를 추가해서 가중치와 편향을 하나의 벡터로 나타내는 기술을 “feature 행렬을 증강시킨다 (feature matrix augmentation; design matrix augmentation)”고 한다.

표기 및 구현상의 편의성을 이유로 실제 알고리즘을 코딩할 때는 증강된 버전을 많이 사용한다. 이제부터 말하는 가중치 행렬 W는 모두 증강된 가중치 행렬이다. , 가중치 w_{ij} 뿐만 아니라 편향 정보 b_k도 포함하고 있는 행렬이다. 편의상 편향 정보도 $w_{ij}$라고 호칭하겠다.

스케치

이제 우리의 가중치 행렬은 이렇게 생겼다.

 

Ridge 정규화 (L2 정규화)

이미 선형 분류기를 구현하는 과정에서 차례 다룬적이 있는 Ridge 정규화법 가중치 행렬 W 모든 성분 w_{ij} 제곱합 R(W) = \sum_{\forall (i, j)} w_{ij}^2 제약하는 정규화법이다. 제곱값(2차항) 제약하기 때문에 L2 정규화라고도 부른다.

스케치

Ridge 정규화된 가중치행렬은 전체적으로 고르고 작은 값을 선호하게 된다. 튀는 값이 있거나 전체적으로 가중치의 크기가 커지면 정규화 손실 R(W)의 값이 커진다.

 

Lasso 정규화 (L1 정규화)

가중치의 제곱합 \sum_{\forall (i,j)} w_{ij}^2 제약하는 Ridge 정규화와 달리 Lasso 정규화 가중치의 절대값의 R(W) = \sum_{\forall (i,j)} |w_{ij}| 제약한다. 1차항인 절대값항을 제약하기 때문에 L1 정규화라고도 널리 불린다.

Lasso 정규화된 가중치행렬은 중요한 feature에 연결된 가중치값을 제외하고는 작은 가중치값을 가지도록 제약하는 경향이 있다. 그래서 Lasso 정규화 강도를 점차 늘리면 가중치값이 분류 문제 해결에 중요한 feature 순서대로 0 되는 을 확인할 수 있다. 이를 그래프로 나타내면 다음과 같다.

output_7_0

feature에 대응하는 가중치값이 0으로 수렴하는 속도를 보여주는 이 플롯을 Lasso path라고 한다. 선형 분류기나 회귀분석에서는 Lasso path를 사용해서 feature 중요도를 판단하기도 한다. 더 천천히 0이 되는 가중치의 feature일수록 더 중요한 feature인 것이다. 그림의 경우는 X_4가 가장 중요한 feature라고 판단할 수 있다.

 

Elastic Net 정규화

1차항(Lasso) 2차항(Ridge) 모두 사용해서 정규화를 수도 있다.

R(W) = \alpha \sum_{\forall (i,j)} w_{ij}^2 + (1-\alpha) \sum_{\forall{(i,j)}} |w_{ij}|

여기서 \alpha [0, 1] 구간 내의 값으로 1차항 제약과 2차항 제약 사이의 밸런스를 조정하는 역할을 한다. \alpha=0 때는 Lasso 정규화와 동일해진다 (2차항 정규화항이 0 되어 사라진다). 반대로 \alpha=1 때는 Ridge 정규화와 동일해진다 (1차항 정규화항이 0 된다).

 

그래서 써야하나요

셋 중 무슨 정규화를 사용할지 고민이라면 그냥 Ridge를 쓰면 된다. 튀는 값을 불호하는 Ridge의 특성상 덜 공격적인 가중치값을 얻을 수 있다.

신경망 모형의 경우엔 주로 Ridge 정규화와 함께 아래에서 설명할 Dropout 기법을 사용해서 정규화한다.

 

Dropout

지금껏 설명한 다른 정규화법과 달리 Dropout은 신경망 기반 모형에 특화된 정규화 방법이다. Dropout의 직관은 이렇다: 각각의 뉴런은 매 학습에서 p의 확률로 활성화될 수도 있고 그렇지 않을 수도 있다.

스케치

이렇게 학습시킨 신경망의 분류결과는 마치 비슷하지만 서로다른 여러개의 신경망 모형들의 분류 결과를 평균내는 과 유사한 형태가 된다. 신경망 여러개의 결과가 평균내졌기 때문에 일반적이지 않은 분류 결과를 제거하는 정규화의 효과를 가진다.

Dropout을 사용하면 학습 과정에서 각 뉴런의 활성화 확률이 p이므로 활성화 기댓값은 E(a) = p \times f(XW) + (1-p)\times0 = p \times f(XW)이다. 즉, Dropout을 사용하지 않았을 때의 활성화함수값 f(XW)에 비해 크기가 p의 비율로 작아진다.

Test set에서 성능평가를 할 때에는 모든 뉴런이 활성화된 상태에서 분류를 하므로 활성화함수값을 학습할 때와 같은 크기로 조정해줘야 한다. 성능평가시의 활성화함수값에 \times p를 해주면 학습시의 활성화함수값과 같은 크기로 조정된다.

Test set에서 성능평가시에 활성화함수값의 크기를 조정해줘야하는 귀찮음때문에 새로운 방법이 고안되었다. 새로운 방법에서는 활성화함수값의 크기를 학습시에 \div p로 조정해준다. 이렇게 하면 학습시 활성화함수의 기댓값은 E(a) = \{p \times f(XW) + (1-p)\times0\} \div p = f(XW)가 되므로, 성능평가시에는 별도의 활성화함수값 조정을 해주지 않아도 된다.

이 방식을 inverted Dropout이라고 하며, 일반적으로 dropout을 사용했다고 하면 열에 아홉은 inverted dropout을 사용했다는 말이다. inverted가 아닌 dropout은 잘 사용하지 않는다.

 

DropConnect

Dropout 유사한 방법으로 DropConnect라는 방법이 있다. epoch마다 랜덤하게 뉴런의 출력 죽여버리는 Dropout 달리 DropConnect epoch마다 뉴런의 입력 랜덤하게 죽인다.

스케치

DropConnect 사용하면 특정 뉴런을 완전히 죽이지는 않고 일부 연결만 죽일 수도 있다. 따라서 DropConnect Dropout 포함하는, 더 일반적인 개념이라 있겠다.

 

 

수치적 최적화

수치적 최적화 역시 처음 선형 분류기를 학습시킬 때 언급했었다. 수치적 최적화는 손실함수값 L을 최소로 만드는 가중치값 W를 찾기 위해 사용됐었다. 지금까지는 경사하강법(또는 SGD)을 사용해서 수치적 최적화를 진행했었다.

 

일반 경사하강법

일반 경사하강법은 그라디언트의 반대 방향으로 가중치를 움직이며 최적의 가중치값을 찾는 방법이다.

W_{new} = W_{old} - \eta \frac{\partial L}{\partial W}

계산 시간과 메모리를 절약하기 위해서 확률적 경사하강법(SGD)를 사용할 수도 있었다. 매 학습에서 가중치 하나만으로 그라디언트를 계산하는 SGD는 아래와 같이 표현된다.

W_{new} = W_{old} - \eta \frac{\partial L}{\partial w_{ij}}

일반 경사하강법은 손실함수의 최저점 주변에서 가중치 값이 진동하는 특징이 있다.

 

모멘텀 경사하강법

모멘텀 경사하강법은 물리학적인 관점에서 경사하강법을 재해석한 방법이다.

손실함수 모양의 곡선에 공을 굴러 떨어뜨린다고 생각해보자. 공의 위치에너지와 운동에너지를 생각해볼 수 있다. 모멘텀 경사하강법에서는 그라디언트를 공의 위치에너지라고 생각한다. 최초의 운동에너지는 0이라고 본다.

  1. 위치에너지(= \frac{\partial L}{\partial W_{old}}) 운동에너지 v 변환된다.
    • v_{new} = \mu \times v_{old} - \eta \frac{\partial L}{\partial W_{old}}
    • 여기서 새로운 하이퍼 파라미터 \mu이 등장한다. m모멘텀이라고 부른다. 실제로는 마찰계수와 비슷한 역할을 한다 (업데이트 전의 속도 v_{old}를 감소시킨다).
  2. 1.에서 업데이트된 운동에너지만큼 가중치가 움직인다.
    • W_{new} = W_{old} + v_{new}
  3. 손실 함수가 수렴할 때까지 반복한다.

 

Nesterov 모멘텀 경사하강법

Nesterov는 모멘텀 경사하강법에 대한 좀 특이한(?) 개선법을 내놓았다.

을 이미 관측한 시점에서 가속도와 속도를 계산하면, 과거의 속도를 이용해서 미래의 공의 위치를 결정하는 것이므로 진짜 현실세계에서 떨어지고 있는 공의 물리적 성질과는 약간의 차이가 있을 것이다. 따라서 굴러가는을 더 현실에 가깝게 물리학적으로 모델링하려면 앞으로 공이 움직일 위치(W_{ahead}) 속도(\frac{\partial L}{\partial w_{ahead}})를 예측해서 공의 위치(가중치값 업데이트 W_{new})를 결정해야 한다.

Nesterov의 모멘텀 경사하강법은 이렇게 진행된다.

  1. 가까운 미래의 공의 위치 W_{ahead} 예측한다.
    • W_{ahead} = W_{old} + (m \times v)
  2. 가까운 미래의 위치 W_{ahead}에서의 그라디언트( 위치에너지; \frac{\partial L}{\partial W_{ahead}}) 계산한다.
  3. 위치에너지 \frac{\partial L}{\partial W_{ahead}} 운동에너지 v 변환시킨다.
    • v_{new} = \mu \times v_{old} - \eta \frac{\partial L}{\partial W_{ahead}}
  4. 운동에너지만큼 가중치를 움직인다.
    • W_{new} = W_{old} + v_{new}

모멘텀 경사하강법들은 일반 경사하강법에 비해 훨씬 빠른 속도로 손실함수의 최저점에 도달한다. 하지만관성때문에 손실함수의 최저점이 되는 값을 지나쳐버렸다가 되돌아오는(“overthrow”되는) 성질이 있다.

 

적응형 학습률(adaptive learning rate) 방법

모멘텀 경사하강법이 경사를 타고 내려오는 속도를 변형시켜 개선시켰다면, 적응형 학습률 방법은 학습 도중에 학습률 \eta를 변형시켜서 개선시킨다 – 그래서 이름이 적응형 학습률(adaptive learning rate)이다.

적응형 학습률 방법은 매 학습에서의 그라디언트 정보를 캐시(cache)라는 변수에 차곡차곡 “기억”해둔다. 그리고 캐시를 이용해서 학습률 \eta를 조정해 나간다. 학습이 진행되어 그라디언트가 작아져도 학습 속도가 느려지지 않도록 조정해주며 가중치를 fine-tuning하는 데에 도움이 된다.

Adagrad라고 이름붙은 방법은 다음과 같이 학습률 \eta를 조정한다.

  1. 그라디언트를 이용해서 캐시를 업데이트한다.
    • cache_{new} = cache_{old} + (\frac{\partial L}{\partial w_{old}})^2
  2. 학습률 \eta 조정(업데이트)한다.
    • \eta_{new} = \frac{\eta_{old}}{\sqrt{cache_{new}}}
  3. 가중치 W 업데이트한다.
    • W_{new} = W_{old} - \eta_{new} \frac{\partial L}{\partial W}

제프리 힌튼이 제시한 RMSProp이라는 방법에서는 캐시가 시간이 지남에 따라 정보를 조금씩잊어버린다”.

  1. 그라디언트를 이용해서 캐시를 업데이트한다. , 캐시는 이전 정보의 일부를 잊어버린다. decay rate \gamma만큼의 정보만 기억한다.
    • cache_{new} = \gamma \times cache_{old} + (1 - \gamma) \times (\frac{\partial L}{\partial w_{old}})^2
  2. 학습률 \eta 조정(업데이트)한다.
    • \eta_{new} = \frac{\eta_{old}}{\sqrt{cache_{new}}}
  3. 가중치 W 업데이트한다.
    • W_{new} = W_{old} - \eta_{new} \frac{\partial L}{\partial W}

 

실제 학습 시 각 방법의 움직임 특징을 보이기 위해서 움짤을 만들어봤다.

ezgif-5-72ec1ba217

이 애니메이션도 유명하다 (출처).

 

 

데이터 전처리

분류기에 학습을 시키기 전에 입력 데이터를 학습에 알맞게 변형시키는 과정을 일컬어 전처리(preprocessing)라 부른다. 전처리는 주로 학습 데이터의 본질만을 남겨두고, 학습에 방해가 되는 요소를 제거하는 역할을 한다.

예를 들어 붓꽃 분류 문제에서, 꽃받침 길이는 cm 단위로 측정되어 있는데 꽃받침 너비는 mm 단위로 측정되었다면 어땠을까? mm 단위로 측정된 값들이 대체로 cm 단위로 측정된 값보다 큰 수치로 기록되어 있을 것이고 이는 가중치의 업데이트에 좋지 않은 영향을 미칠 것이다.

전처리 과정에서는 이처럼 단위 차이에 의한 영향, 최대최소값의 차이에 의한 영향, 평균값의 차이에 의한 영향, 분포도 차이에 의한 영향 등 데이터의 본질과는 무관한 영향을 제거한다.

전처리시 주의해야할 점은 전처리 통계량(데이터 평균, 데이터 분산, eigenvalue, eigenvector, …)은 모두 training set에서만 계산되어야 한다는 것이다. 이에 대해서는 ‘(3) 학습에는 왕도가 없다‘ 에서 다루었다.

 

센터링(centering) 스케일링(scaling)

가장 간편하고 보편적인 전처리는 데이터를 평균 0, 분산 1을 따르도록 변형시키는 것이다. 이 중 평균을 0으로 만드는 과정을 센터링(centering)이라고 하고 분산을 1로 만드는 과정을 스케일링(scaling)이라고 한다.

굳이 하나를 고른다면 스케일링보다는 센터링이 훨씬 중요하다고 한다.

 

Minmax 표준화

데이터의 분포를 유지한 채로 모든 관측값이 일정 구간 (주로 [0, 1]) 사이에 존재하도록 변형하고 싶다면 minmax 표준화를 사용할 수 있다.

X_{normalized} = \frac{X - \min(X)}{\max(X) - \min(X)}

변형한 데이터는 모두 [0, 1] 사이의 값을 갖게 된다. 이 방법은 데이터를 센터링하지는 않기 때문에 신경망에 사용하기엔 부적합할 수 있다.

 

주성분 분석 (Principal Component Analysis; PCA)

주성분 분석은 상관관계가 존재할 수도 있는 데이터셋을 서로 독립인 성분(주성분)의 데이터셋으로 분리해주는 테크닉이다.

PCA 데이터의 공분산 행렬(covariance matrix) \Sigma 특이값 분해(singular-value decomposition; SVD)시키는 과정과 동일하다. 혹은 데이터의 상관 행렬(correlation matrix) R 고유값 분해(eigen-value decomposition)시키는 과정과 동일하다.

상관관계가 존재하는 데이터 X PCA를 통해서 세 행렬의 곱으로 분해할 수 있다.

X = Z_s D^{1/2} U^T, 따라서 Z_s = XUD^{-1/2}

여기서 Z_s 평균이 0이고 분산이 1이며 모든 차원이 서로 독립(full-rank) 데이터이다. D Z 차원의 분산의 행렬이다. 동시에 X eigenvalue 대각성분으로 가지는 대각행렬이기도 하다. U 회전행렬이다. 동시에 X eigenvector matrix이기도 하다.

스케치

이를 기하적으로 해석해보면 이렇다: 모든 차원이 서로 독립이고 표준화된 데이터 Z_s 차원간 상관관계가 존재하는 데이터 X U만큼 회전시키고 D^{-1/2} 표준화시켜서 얻을 있다. 과정에서 얻은 Z_1, Z_2 또는 Z_{s_1}, Z_{s_2} 주성분(principal component; PC)라고 부른다.

데이터를 센터링, 스케일링하는 대신 PCA PC를 사용해도 된다. 한 차원의 PC만으로도 대부분의 데이터 분산을 설명할 수 있다면 아예 데이터의 차원을 축소해도 된다. 그림의 경우 Z_1만으로도 대부분의 데이터 분산을 설명할 수 있으므로 Z_2 차원을 버리고 1차원으로 데이터를 축소시켜도 된다.

주저리 주저리 설명하긴 했지만 신경망 모형에서는 주로 PCA보다 센터링 + 스케일링을 선호한다고 한다.

 

여기까지 Batch Normalization 하나만 빼고 대강의 살은 붙였다.

다음 글에서는 신경망에 적용시킬 수 있는 특수한 층(layer) 구조에 대해서 다뤄보겠다. 이 내용을 다루면서 Batch Normalization도 다룰 생각이다.

 

 

참고

  • 이 글은 CS231n 강의록을 매우 많이 참고했다. 원 강의록을 읽어보시기를 강하게 추천드린다.
  • Adaptive learning rate method 내용은 이 페이지도 참고했다.
  • 첫 번째 수치적 최적화 비교 GIF를 만드는 과정에서 최적화할 함수 모양을 설정할 때 이 페이지를 참고했다.
  • 두 번째 수치적 최적화 비교 GIF는 CS231n에서 가져왔으나 원 출처가 따로 있다.
  • PCA 내용은 “Analyzing Multivariate Data” (Lattin et al., 2002)를 참고했다.

인공신경망 이해하기 (3) 학습에는 왕도가 없다

지금까지 선형 분류기를 통해 머신러닝의 기초를 짚어보았다. , 일부 특수한 비선형 문제를 해결하기 위해 사용되는 커널 트릭을 아주 간단히 다루었으며 신경망 기초 이론을 이야기하면서 2층 신경망을 직접 구현해서 대부분의 비선형 문제를 해결할 수 있다는 것을 보였다.

(1) 선형 분류기
(2) 퍼셉트론과 신경망

이번 글에서는 신경망(을 포함한 머신러닝 모델)을 학습시키고 성능을 평가할 때 주의해야 할 점들에 대해 포괄적으로 다뤄보고자 한다.

 

  1. Train set / test set
    1. Label skew 주의하기
  2. 하이퍼 파라미터 결정하기
    1. 결정해야 하는 하이퍼 파라미터
      1. 학습률 (learning rate)
      2. 정규화 강도 (regularization strength)
      3. ..
    2.  좋은 하이퍼파라미터 값 찾기
  3. 모형을 학습시키기 전에
    1. 초기 가중치, 편향 설정
    2. 데이터 전처리
    3. 초기 그라디언트 체크
    4. 초기 손실함수 체크
    5. 의도적 과적화
  4. 모형을 학습시키는 중에
    1. 손실함수 모니터링
    2. training set acc. vs. test set acc.
  5. 구현

 

Training set / Test set

앞서 과적화(overfitting) 현상에 대해 짧게 언급한 적이 있다. 모형이 데이터를 지나치게 학습한 나머지, 실제 카테고리 분류와는 관련이 없는 노이즈까지 학습해버리는 현상을 말한다.

사람 사진을 보고 남자인지, 여자인지, 혹은 둘 다 아닌지 성별을 구별해주는 모형을 학습시킨다고 해보자. 그런데 하필, 학습에 사용한 데이터에서 남자 사진은 모두 배경에 의자가 함께 찍혀있었다고 가정해보자.

제대로 학습된 모형이라면 이목구비의 생김새, 배치, 머리카락의 모양, 길이 등을 학습해서 성별을 구별할 것이다. 그러나 과적화된 모형은 배경에 의자가 있는지(카테고리 구분과 관련 없는 노이즈)를 보고 성별을 구별한다.

이러한 오버피팅 문제를 해결하기 위해서 정규화 손실항 R(W) 손실함수 L 추가한다는 것을 이야기했었다. , 정규화의 세기는 정규화 강도 (regularization strength) \lambda 사용해서 조절한다는 것도 이미 이야기한 있다.

여기서 의문이 생긴다. 정규화를 했어도 오버피팅이 일어날 있다. 오버피팅이 일어나지 않았는지 어떻게 확신할 있을까? 다른 말로 풀어보면 이렇다: 학습시킨 분류기가 학습에 사용한 데이터 외에 다른 데이터에도 일반적으로 동작한다는 것을 어떻게 확인할 있을까?

답은 간단하다. 직접 테스트를 해보는 수밖에 없다. 그리고 테스트를 하기 위해 우리는 가지고 있는 모든 데이터 중 일부만을 학습에 사용해야 한다. 전체 데이터를 두 조각으로 쪼개어 한 조각은 테스트 용으로 남겨둬야 한다는 말이다.

e18489e185b3e1848fe185a6e1848ee185b512.png

쪼갠 데이터 중 학습에 사용하는 데이터셋을 training set, 학습에는 사용하지 않고 테스트시에만 사용하는 데이터셋을 test set이라고 한다. 데이터를 쪼개는 비율은 정해져 있지는 않지만 일반적으로 train:test = 4:1, 7:3, 9:1 등을 많이 사용한다.

 

카테고리가 치우치지 않도록 쪼개야 한다

데이터를 쪼개는 과정에서 카테고리(라벨) 데이터셋의 카테고리가 종류로 치우칠 있다. 예를 들어,  붓꽃 데이터를 training set test set으로 쪼개는 과정에서 training set에는 setosa 종과 versicolor 종의 데이터만, test set에는 verginica 종의 데이터만 포함될 있다. 이런 경우 training set, test set 모두 데이터가 불완전하므로 제대로 학습 테스트를 수가 없다.

따라서 항상 train/test 데이터셋 쪼개기를 수행할 때에는 카테고리가 한 쪽으로 치우지지 않도록 조심을 기해야 한다.

가장 간편하면서도 많이 사용하는 방법은 전체 데이터를 랜덤 셔플한 다음 train/test set 쪼개는 이다.

e18489e185b3e1848fe185a6e1848ee185b513.png

(그럴 일은 많지 않겠지만) 극히 낮은 확률로 랜덤 셔플한 후 쪼개었음에도 카테고리가 치우칠 수 있다. 이런 상황을 방지하기 위해 아예 층화 추출(stratified sampling)을 해도 된다. 층화추출법은 전체 데이터에서 각 카테고리의 비율을 유지하며 샘플링하는 기법이다. 그러나 오히려 층화추출에 의해 test set의 카테고리가 치우치는 경우가 있어서 그냥 랜덤 셔플 후 샘플링 방법을 사용하는 것이 다수이다.

e18489e185b3e1848fe185a6e1848ee185b514.png

Train set은 최종 테스트(모형 성능평가)를 위해 가만히 남겨두고, training set만 가지고 놀도록 하자. 이제 학습에 사용할 데이터셋은 준비가 됐다.

 

하이퍼 파라미터 결정

결정해야 하는 하이퍼 파라미터

하이퍼 파라미터(hyper-parameter)는 머신러닝에서 분류기 등 모형의 학습 양상을 결정하는 중요한 수치이다. 분류기를 학습하는 과정에서 업데이트되는 파라미터(parameter; 우리가 다루었던 가중치 W와 편향b이 여기에 속한다)와 달리, 하이퍼 파라미터는 분류기를 학습하기 전에 결정되고, 학습 과정에서는 일반적으로 업데이트되지 않는다그래서 hyper라고 부른다.

본격적으로 모형을 학습하기 전 우리가 정해야 하는 하이퍼 파라미터는 이런 것들이 있다.

학습률 (learning rate; \eta)

가장 먼저 살펴볼 하이퍼 파라미터는 학습률 \eta이다. 이녀석은 가중치를 업데이트할 때 등장했었다.

W_{new} = W_{old} - \eta \frac{\partial L}{\partial W}

b_{new} = b_{old} - \eta \frac{\partial L}{\partial b}

학습률 \eta의 값을 크게 설정할수록 한 번의 업데이트에서 가중치값이 변화하는 폭이 커진다 (학습 속도가 빨라진다). 반대로 \eta의 값을 작게 설정할수록 가중치값의 변화폭이 작아진다.

빨리 학습한다는 것이 듣기에는 좋아보이지만 경사하강법의 원리를 생각한다면 마냥 좋은건 아니다. \eta가 너무 크면 경사를 올바르게 타고 내려가지 못해서 이상한 값으로 가중치 W가 수렴해버릴 수 있기 때문이다.

그럼 아예 학습률을 아주 작게 설정해버리면? 학습에 너무 많은 시간이 걸려서 효율이 떨어진다. 가중치 W가 덜 좋은 값(non-optimum)으로 수렴해버리는 경우도 있다.

e18489e185b3e1848fe185a6e1848ee185b515.png

따라서 적절한 학습률을 설정하는 것이 모형 가중치의 수렴과 학습 효율에 중요하다고 하겠다.

신경망의 경우엔 처음에는 큰 학습률을 사용했다가, 일정 정도 이상 학습을 한 이후에는 학습률을 작게 조정하는 경우도 있다. 이러한 기술을 학습률 분해(learning rate decay)라고 한다. 가장 많이 사용하는 학습률 분해 테크닉은 다음과 같다.

\eta = \gamma^t \eta_0

여기서 \eta_0 초기에 설정한 학습률이고, \gamma [0, 1] 사이의 , t 학습 기간이다. 방법을 사용하면 일정 학습 기간 t마다 \gamma 비율로 초기 학습률이 점점분해되어작아진다. 일정 학습 기간마다 같은 비율로 학습률을 분해시킨다고 해서 방법을 step decay라고 하고 \gamma 분해율(decay rate)이라 부른다.

학습률을 분해시킬 때는 분해율 \gamma 분해기간 t 추가로 결정해줘야 한다. 다시말해 \gamma, t 하이퍼파라미터이다.

정규화 강도 (regularization strength; \lambda)

정규화의 세기를 정해주는 정규화 강도 \lambda도 하이퍼 파라미터다.

정규화 강도를 너무 세게 하면 데이터로부터 오는 손실이 무시될 수 있다 (데이터로부터 거의 학습을 하지 않는 underfitting 현상이 발생한다). 반대로 정규화 강도를 너무 약하게 하면 데이터의 노이즈까지 학습하는 오버피팅이 발생할 수 있다.

Dropout이라는, 신경망 모형에서 자주 사용하는 정규화 방법에선 뉴런이 특정 확률(p)로 활성화될 수도 있고 안 될 수도 있다. 이 때 뉴런이 활성화될 확률 p도 학습 전 결정해야하는 하이퍼 파라미터이다. Dropout에 대해서는 다른 글에서 자세히 살펴보기로 한다.

외의 하이퍼 파라미터

  • RNN에서 뉴런의 unfold 횟수
  • CNN에서 filter 크기 stride
  • 경사하강법 외의 다른 최적화 방법에 필요한 하이퍼파라미터

등등이 많지만 주로 초기 학습률 \eta_0, 학습률 분해율 \gamma 분해기간 t, 정규화 강도 \lambda 모형의 성능과 수렴에 중요한 영향을 미치기에 글에서는 자세히 다루지 않겠다.

 

좋은 하이퍼파라미터 찾기

validation set 만들기

결정해야하는 하이퍼 파라미터의 종류를 알았으니 이젠 그 값을 결정할 차례다. 어떤 값이 좋은 값인지는 어떻게 알 수 있을까? 다른 방법이 없다. 테스트를 해봐야 한다.

이 때 주의해야 할 것은 test set으로는 하이퍼 파라미터 테스트를 하면 안된다는 것이다. Test set으로 테스트한 하이퍼 파라미터를 사용하겠다는 것은 모형의 학습에 test set을 사용하겠다는 것과 같은 말이다. (Test set은 최종 모형의 성능 평가를 할 때, 딱 한 번만 사용해야 한다!)

e18489e185b3e1848fe185a6e1848ee185b516.png

대신 training set을 다시 한번 쪼개어 하이퍼 파라미터 테스트용 데이터셋을 만들기로 한다. training set을 쪼개어 만든 하이퍼 파라미터 테스트용 데이터셋을 우리는 validation set이라고 부른다. 만든 validation set에 다양한 하이퍼 파라미터 세팅을 시험해보면서 우수한 성능을 내는 값을 선택하면 된다.

Cross-validation (CV)

데이터를 여러개의 fold 나눈 다음 validation set 만드는 방법도 있다.

e18489e185b3e1848fe185a6e1848ee185b517.png

방법을 k-fold cross-validation이라고 한다. 주로 3 또는 5-fold CV 많이 사용한다.

Random search

Validation set에 시험해볼 하이퍼 파라미터 값은 어떻게 정할까? 정답: 무작위로 정한다. 그렇다고 완전 무작위로 하지는 않고 보통 다음과 같은 가이드라인을 따른다.

  1. 처음에는 큰 범위에서 무작위로 선택한다.
  2. 짧게 (1~3회 정도) 학습시킨다. 여기서 학습한다함은 가중치를 업데이트시킨다는 것이다.
  3. 좋은 성능을 내는 범위로 범위의 크기를 좀 줄여서 무작위로 선택한다.
  4. 길게 (5~10 정도) 학습시킨다.
  5. 범위를 조금 더 줄여서 무작위로 선택한다.
  6. 더욱 길게 학습시킨다.

이렇게 큰 범위에서 점점 좁은 범위로 하이퍼 파라미터 값을 선택해 나가면서 좋은 값을 fine-tuning할 수 있다. 최종적인 후보 값을 선택한 후에는 손실함수값이 수렴할 때까지 끝까지 학습을 시킨다.

 

모형을 학습시키기 전에

모형을 학습시키는 코드를 짜기 전에 고려해야 하는 문제가 몇 가지 있다. 첫째는 학습을 시작하기 전 모형을 구성하는 파라미터(W, b ) 등의 초기 값들을 어떻게 설정할 것인지에 대한 문제이고, 둘째는 모형이 올바르게 구현되었는지를 확인하는 문제이다..

 

초기 가중치 W 설정

학습 이전 초기 가중치는 평균이 0인 임의의 작은 수로 설정하는 것이 보편적이다.

W = np.random.randn((D, K)) * .01

그런데 뉴런에 입력되는 데이터의 feature 차원에 따라 판별함수값 s = XW + b = \sum w_ix_i + b_i 크기가 제각기 달라지므로, 판별함수값의 크기가 표준편차 1 갖도록 feature 차원수 n으로 표준화를 해준다.

W = np.random.randn(n) * .01 / np.sqrt(n)

ReLU 활성화 함수로 갖는 신경망층의 경우 \div \sqrt{n/2} 표준화해주면 된다고 증명돼있다.

W = np.random.randn(n) * .01 / np.sqrt(n/2.)

요즘은 batch normalization(BatchNorm; BN)이라는 방식을 사용하는 것이 거의 필수가 되었다. BatchNorm 대해서는 나중에 기회가 되면 다루기로 한다. 간단하게 정리해보자면

  • BN layer라는 표준화층을 모든 뉴런의 판별함수와 활성화함수 사이에 추가한다.
  • 표준화 과정에 사용되는 파라미터도 수치적 최적화(경사하강법 등) 과정에서 업데이트되도록 한다.
  • 시간이 지남에 따라 은닉층의 출력이 특정 방향으로 shift되는 것을 막을 수 있다.

 

초기 편향 b 설정

초기 편향은 간단히 영벡터로 설정해준다.

b = np.random.zeros((1, K))

 

입력 데이터 표준화

분류기에 입력시킬 데이터(train set, test set, validation set 모두) 또한 전처리(preprocessing) 해주어야 한다. 평균이 0, 표준편차가 1 되도록 모든 관찰값을 정규화해준다.

x_mean = np.sum(x, axis=0) / x.shape[0]   # 표본평균

x_std = np.std(x, axis=0)              # 표본표준편차

x_normalized = (x - x_mean) / x_std

주의할 점은 표본평균과 표본표준편차를 계산할 test set, validation set 사용하면 안된다 것이다. 표본평균/표준편차는 training set으로만 계산해야한다. Training set에서 계산한 값들로 validation set, test set표준화해주면 된다.

 

초기 손실함수값 체크

손실함수에서는 두 가지 사항을 확인해야한다.

  • nan이 아닌 값을 반환하는가
  • 예상되는 값과 비슷한 값을 반환하는가

구현한 손실함수가 학습 이전에 nan을 반환하거나 예상되는 값과 다른 값을 반환한다면 구현한 코드에 문제가 있을 가능성이 크다.

 

초기 그라디언트 체크

분석적으로 계산한(역전파로 계산한) 그라디언트가 수치적으로 계산한 그라디언트와 일치하는지 체크한다. 수치적으로 그라디언트는 미분의 정의(f^\prime (x) = \lim_{h \to 0} \frac{f(x+h) - f(x-h)}{2h}) 사용해서 계산할 있다.

분석적 그라디언트가 수치적 그라디언트와 일치하는지의 여부는 아래 에러함수의 값으로 판단한다.

\frac{|f^\prime_N - f^\prime_A|}{\max(|f^\prime_N|, |f^\prime_A|)}

여기서 f^\prime_N, f^\prime_A는 각각 수치적 그라디언트, 분석적 그라디언트이다. 그라디언트의 크기에 대한 상대적인 오류 비율이라는 의미에서 이 에러함수를 relative error라고 칭한다.

  • Relative error 값이 1e-7 이하이면 아무 문제 없다.
  • 1e-4 정도의 에러는 문제가 있을 수도 있다. ReLU처럼 미분불가능한 구간이 있는 경우엔 이정도 오류는 괜찮다. 미분불가능한 구간이 없는 경우엔 문제가 있는 에러 크기이다.
  • 1e-4 이상의 에러는 문제가 있다. 분석적 그라디언트를 잘못 계산했을 수 있다.

 

의도적 과적화

Training set에서 작은 크기의 샘플을 추출한 다음 이걸 모형에 학습시켜서 의도적으로 과적화를 유도한다. 의도적으로 과적화시켰음에도 100%에 가까운 accuracy를 얻을 수 없다면 이 모형으로는 해당 분류 문제를 해결할 수 없다. 즉 분류기 설계가 잘못됐거나 분리가 불가능한 데이터이다.

모든 사항을 확인했다면 이제 모형을 학습시키면 된다.

 

모형을 학습시키는 도중에

모형을 학습시키는 중에도 모형이 데이터를 잘 학습하고 있는지 체크할 필요가 있다. 머신러닝 모형의 학습양상은 손실함수값의 수렴과 test set에서의 accuracy를 확인함으로써 파악할 수 있다.

 

손실함수 모니터링

가중치 W와 손실함수 L는 머신러닝 모형의 내부를 보여주는 창이다. 특히나 손실함수는 은닉층 학습양상이 직관적이지 않은(블랙박스) 신경망 기반 모형의 학습양상을 파악하는 데에 큰 도움이 된다.

스케치

매 학습단계(epoch)에서의 손실함수값을 통해서 우리는

  • 학습률이 적절히 잘 설정되었는지
  • 학습이 잘 되었는지, 되다 만 것은 아닌지

등을 판단할 수 있다.

(1) 학습률이 너무 경우 손실함수 L 최적의 값이 아닌 값으로 수렴할 있다. 경우 학습률 \eta 작게 설정해야 한다.

(2) 학습률이 너무 작은 경우 손실함수 $\latex L$이 수렴하는 데에 훨씬 오랜 epoch가 필요하다. 컴퓨팅 파워와 시간이 충분하다면 epoch를 늘려보자. 그렇지 않다면 \eta 값을 조금 더 크게 설정해보는 것도 방법이다.

 

Test set accuracy 모니터링

학습단계에서 training set에서의 accuracy test set에서의 accuracy 비교하면

  • 오버피팅이 되었는지 (정규화 강도를 조절해야 하는지)
  • 언더피팅이 되었는지 (학습이 덜 되었는지, 분류기가 너무 단순하지는 않은지)

등을 판단할 수 있다.

e18489e185b3e1848fe185a6e1848ee185b519-e1524062685380.png

(1) test acc train acc 비해 너무 낮다면 오버피팅의 가능성이 크다. 경우에는 정규화 강도 \lambda 값을 키워볼 필요가 있다.

(2) test acc train acc와 거의 같다면 언더피팅을 의심해볼 수 있다. 이 경우 손실함수가 잘 수렴했는지, sub-optimal 값으로 수렴한 것은 아닌지 살펴보아야 한다. 손실함수에 문제가 없음에도 이런 현상이 나타났다면 모형이 해당 분류문제를 해결하기엔 너무 단순한 것일 수 있다. 파라미터 갯수를 늘려서 새로 모형을 만들어보자. 신경망의 경우 뉴런 수를 늘리거나 은닉층을 하나 추가해서 파라미터 갯수를 늘릴 수 있다. 물론, 그냥 문제가 쉬워서 test set에서도 성능이 좋게 나오는 걸수도 있다. 이 경우엔 두 accuracy 모두 1에 가깝게 나올 것이다.

 

구현

사실 이전 글에서 구현했던 분류기에는 지금까지 다뤘던 내용이 상당히 이미 코드에 포함되어 있다. 여기서는 나선형 데이터를 분류하는 2-layer NN을 구현하는 과정에서 초기 가중치 설정과 train/test set 분리, test set accuracy 모니터링을 추가로 코드로 구현해보았다.

왼쪽열 그래프들은 붓꽃 분류문제에서의 accuracy, 손실함수값이고 오른쪽 그래프들은 나선형 데이터 분류문제에서의 acc와 손실함수값이다. training set만으로 학습을 마친 뒤 decision hyperplane을 training set과 test set에 각각 덧씌워 그려보았다.

 

여기까지 살펴 보았듯이 머신러닝 모형을 학습시키는 데에는 왕도가 없다. 파라미터 초기화를 잘 해주고 세심히 하이퍼 파라미터를 조정한 다음 학습 과정을 유심히 지켜보며 문제가 생기지는 않는지 계속 모형을 관찰하며 어루만져 주어야 한다.

이제야 제대로 된 학습과 테스트까지 다루었다다음 글에서는 지금껏 자세히 다루지 않았던 내용들(경사하강법 이회의 수치적 최적화 방법, Ridge 외의 정규화 방법 등)을 차례차례 다뤄보고자 한다.

 

참고

  • 이 글은 CS231n 강의록을 매우 많이 참고했다. 원 강의록을 읽어보시기를 강하게 추천드린다.
  • 코드는 이 노트북에 있다.

인공신경망 이해하기 (2) 퍼셉트론과 신경망

이전 글에서는 분류 문제를 해결하기 위해 간단한 선형 분류기를 만드는 데에 필요한 요소들을 다루었다. 이제 우리는 분류기(모형)를 학습시키기 위해서는 판별함수 s, 정규화 손실항이 포함된 손실함수 L 필요함을 알고 있다. 또한 학습 과정에서 최적화 과정(SGD )을 통해 손실이 작아지는 방향으로 가중치 W와 편향 b의 값을 반복적으로 업데이트할 수 있다.

직선(또는 hyperplane)으로 서로 다른 카테고리의 영역을 구획짓는 가장 단순한 형태의 선형 분류기를 만들어 학습시킨 결과, 붓꽃 분류 문제를 92.7% accuracy로 성공적으로 분류해낼 수 있었다. 그러나 직선으로 구획지을 수 없는 분류 문제에서는 매우 낮은 성능을 보였다.

이번 글에서는 직선으로 구획지을 수 없는 분류 문제를 해결하는 분류기를 다루면서 신경망 이론에 (드디어!) 발을 들이고자 한다.

 

  1. 계속 선형 분류기를 고집하기
  2. 선형 분류기를 넘어서
    1. 퍼셉트론과 활성화 함수
    2. 다중층 퍼셉트론
  3. 신경망 학습시키기
  4. 2층 신경망 구현

 

선형 분류기를 고집하기 (커널 트릭)

선형 분류기(linear classifier)라는 이름은 판별함수값이 가중치 w_i feature x_i 선형 조합으로 표현될 있었기 때문에 붙은 이름이었다. 약간의 꼼수를 쓰면, 선형 분류기가 곡선적으로 판별하도록 변형시킬 있다.

붓꽃 분류 문제로 돌아가보자. 붓꽃 데이터에는 4차원의 feature(x_1: 꽃받침 길이, x_2: 꽃받침 너비, x_3: 꽃잎 길이, x_4: 꽃잎 너비) 존재했다. 여기에 추가로 다섯 번째 feature x_5 추가해보자. 다섯 번째 feature 꽃잎 길이의 제곱, x_5 = (x_3)^2이다.

이제 우리의 feature 데이터는 5차원이 되었다. 데이터의 차원이 바뀌었을 뿐, 여전히 판별함수의 식은 s = XW + b로 동일하다. 판별함수 식이 여전히 선형조합의 형태이므로 이 분류기 또한 여전히 선형 분류기라고 할 수 있다.

스케치

 

그러나 내부를 들여다보면 이 선형 분류기는 직선적으로 카테고리를 구획짓지는 않는다.

번째 관찰의 번째 카테고리의 판별함수값을 보면 s_{i1} = \sum_{j} w_{ji} x_{ij} + b_i = w_{1i} x_{i1} + w_{2i} x_{i2} + w_{3i} x_{i3} + w_{4i} x_{i4} + w_{5i} x_{i5} + b_i이고 x_5 = (x_3)^2이므로, 판별함수는 다음과 같은 이차식(, 곡선 또는 곡면) 형태 띄게 된다.

s_{i1} = w_{5i} (x_{i3})^2 + w_{1i} x_{i1} + w_{2i} x_{i2} + w_{3i} x_{i3} + w_{4i} x_{i4} + b_i

단순히 판별함수에 학습시키는 인풋 데이터를 변형함으로써 곡선적으로 분류하는 선형 분류기를 만들 수 있는 것이다. 인풋 데이터를 일정 규칙에 따라 변형시켜주는 함수를 커널 함수(kernel function)라고 하며 커널 함수를 사용해서 곡선적으로 분류하는 선형분류기를 만드는 것을 ‘커널 트릭(kernel trick)을 사용한다’고 한다.

XOR 문제

‘exclusive OR (XOR)’라고 불리는 다음과 같은 문제가 있다. 이런 형태의 데이터는 하나의 함수만으로는 분류가 불가능하다.

스케치

하지만 다항(polynomial) 커널을 사용해서 feature를 변형하면 직선적으로 분류가 가능해진다. 다항 커널을 사용해서 변형한 feature 값에 softmax 선형 분류기를 구현한 다음, 변형하기 이전 원래 feature 값에 판별 hyperplane을 나타내보면 아래처럼 곡선적으로 분류가 되었음을 확인할 수 있다.

poly_softmax

 

 

선형 분류기를 넘어서

똑같은 선형 분류기를 사용해도 단순히 인풋 데이터를 변형하기만 해도 몇몇 형태의 곡선적 데이터는 성공적으로 분류할 수 있다. 그러나 커널 트릭은 근본적인 해결책은 되지 못한다. 분류기의 형태는 여전히 단순 선형조합만으로 표현되므로, 커널 트릭으로 해결할 수 있는 곡선적 분류 문제는 극히 일부이다. 따라서 우리는 모든 경우에도 높은 성능을 내는 비선형 분류기(non-linear classifier)를 구현할 필요가 있다.

어떤 형태의 곡선적 또는 비선형 문제든지 모두 학습이 가능하도록 고안된 분류 모형 인공신경망(Artifical Neural Network; ANN)이다.

퍼셉트론

인공신경망에 다이빙하기 전에 퍼셉트론의 개념을 다룰 필요가 있다. 퍼셉트론(perceptron; 또는 그냥 neuron이라고도 )은 시신경세포를 수학적으로 비유/모델링하는 과정에서 탄생한 분류기이다. 실제 시신경세포의 작용과는 (당연히) 큰 차이가 있다. 입력 정보의 강도에 따라 출력 여부가 결정된다는 점(, 출력이 thresholding된다는 점)에 착안해서 만들어진 분류기라고 이해하면 좋다.

스케치

퍼셉트론 하나의 판별함수는 다음과 같이 식으로 나타낼 수 있다.

s = f(XW + b)

가중치 W feature X의 선형조합 XW + b에 함수 f를 씌운 형태이다. 여기서 함수 f는 출력의 여부 및 세기를 결정(뉴런으로 비유하자면 활성화 여부와 정도를 결정)한다고 해서 활성화 함수(activation function)라고 부른다. 활성화 함수로는 항상 비선형 함수를 사용한다.

활성화 함수를 softmax 함수로 설정하고 손실함수를 cross-category loss(L_i = - \sum_{j} p_{y_i} log{p_j}) 설정한 퍼셉트론은 앞서 구현한 softmax 분류기와 동일한 분류기가 된다. 비슷한 방법으로 손실함수를 max-margin loss 설정하면 퍼셉트론은 서포트 벡터 머신이 된다. 결국 퍼셉트론은 선형 분류기에 약간의 비선형 트윅(활성화 함수) 가한, 선형 분류기의 연장이다.

로지스틱 함수 외에도 쌍곡탄젠트 함수 등 몇 가지 활성화 함수가 제시되어 있지만 일반적으로 가장 많이 사용하는건 ReLU(Rectified Linear Unit)라는 녀석이다. ReLU activation을 식으로 나타내면 다음과 같고, 이는 0 이상 강도의 신호만을 출력하게 하는 0-역치값(zero-threshold) 역할을 한다.

스케치

다중층 퍼셉트론: 신경망

, 다시 XOR 문제로 돌아가보자. 지금까지의 내용을 정리해보면 이렇다.

  1. XOR 문제는 하나의 판별함수로는 분류할 수 없는 문제다 (두 개 이상의 함수가 필요하다).
  2. 퍼셉트론은 시신경세포에 대한 매우 단순한 수학적 모형이다.
  3. 퍼셉트론은 선형 분류기에 비선형 변형을 가한 출력값을 반환한다 (비선형 함수값을 준다).

이 내용을 보고 이렇게 생각해볼 수 있다:

  1. 그럼 함수 두 개 이상을 만들면 되겠네.
  2. 신경뭉치(더 나아가서, )처럼 퍼셉트론을 여러개 연결시키면 어떨까?
  3. 퍼셉트론은 곧 비선형 함수 한 개니까, 퍼셉트론 뭉치는 두 개 이상의 함수의 조합이겠네.
  4. 그럼 퍼셉트론 뭉치로 XOR 포함한 비선형 분류 문제를 해결할 있겠다!

이렇게 해서 퍼셉트론을 여러 쌓아 하나의 거대한 비선형 분류기를 만들 있다. 이를 다중층 퍼셉트론(multi-layer perceptron; MLP) 또는 신경망(Neural Network; NeuralNet; NN)이라고 부른다.

신경망은 다음과 같은 구성으로 이루어져 있다.

스케치

  • 입력층(input layer): 학습 데이터 feature를 입력받는 층이다. Feature의 차원 수(붓꽃 데이터의 경우 4)만큼의 뉴런 갯수를 가진다. 입력 층은 MLP 당 하나 뿐이다.
  • 은닉층(hidden layer): 입력층과 출력층 사이의 모든 층을 뜻한다. 입력층에서 입력받은 raw data에 대해 다수의 비선형 판별함수가 학습되는 층이다. 그림의 신경망에는 각각 6개 뉴런을 가지는 2개의 은닉층이 있다.
  • 출력층(output layer): 데이터에 대해 각 카테고리에 대응하는 판별함수값을 출력하는 층이다. 카테고리 수(붓꽃 데이터의 경우 3)만큼의 뉴런 갯수를 가진다. 출력층도 입력층과 같이 MLP 당 하나 뿐이다. 출력층에 있는 뉴런은 활성화함수를 가지지 않는 경우가 일반적이다.
  • 신경망은 “n-층 신경망”과 같이 층 갯수로 이름을 붙인다. 이 때 입력층은 갯수를 셀 때 포함하지 않는다. 그림의 신경망은 3 신경망이다.
  • 같은 층에 있는 뉴런끼리는 연결되어 있지 않다.
  • 한 층에 있는 뉴런은 다음 층에 있는 모든 뉴런과 연결된다.
  • 뉴런의 출력은 방향(출력층 -> 은닉층1 -> 은닉층2 -> … -> 출력층)으로만 전파된다.

은닉층이 하나 있는 2-layer neural net 식으로 다음과 같이 표현할 있다.

s = hW_2 + b_2 = f(XW_1 + b_1)W_2 + b_2

일반적인 n-layer neural net 다음과 같이 표현 가능하다.

s = h_{n-1}W_n + b_n = f_{n-1}(h_{n-2}W_{n-1} + b_{n-1})W_n + b_n = ...

활성화함수의 중요성이 여기서 드러난다. 비선형 활성화함수가 매 층 사이에 존재하지 않는다면 위의 식은 결국 가중치와 feature의 선형조합으로 정리된다. 다시 말해 활성화함수가 없다면 신경망은 선형 분류기와 동일해진다. 활성화함수가 신경망 분류모형에 비선형성(non-linearity)를 제공한다고 이해할 수 있는 대목이다. 이론적으로 하나 이상의 은닉층을 갖는 신경망으로 존재하는 모든 비선형 함수를 근사할 있음이 증명되어 있다.

Feature 데이터는 입력층 -> 은닉층 -> 출력층의 순서로 전파되어 판별함수값 s로 변환되며, 이 과정을 일컬어 ‘feed-forward’라고 한다.

 

신경망 학습시키기

신경망을 학습시키는 데에도 선형 분류기를 학습시킬 때와 마찬가지로 손실함수값 L를 계산하고, 손실함수값 L의 가중치 W에 대한 그라디언트를 계산해야한다. 계산한 그라디언트의 반대 방향으로 반복적으로 가중치를 업데이트하면 우수한 성능을 내는 신경망 분류기를 얻을 수 있을 것이다.

그런데 신경망에서의 그라디언트 계산은 선형 분류기에 비해 복잡하다. 한 차례 편미분으로 쉽게 그라디언트를 구할 수 있었던 선형 분류기에서와 달리, 신경망의 손실함수는 가중치 W로 직접 편미분하기에는 식이 복잡하다.

그래서 신경망의 그라디언트는 미분의 연쇄법칙(chain rule)을 사용해서 단계적으로 계산한다.

선형 분류기의 예시

비교적 단순한 예시부터 살펴보자.

앞서 구현한 선형 Softmax 분류기는 은닉층이 없는 신경망이라고 생각할 있다. 손실함수로 softmax 손실 + Ridge 정규화 손실(L = \sum_i L_i + \alpha R(W), L_i = - \log{\frac{e^{f_{y_i}}}{\sum_j e^{f_j}}}, R(W) = \sum_i \sum_j w_{ij}^2 + \sum_k b_k^2) 둔다고 , 선형 분류기의 feed-forward 아래와 같이 왼쪽(입력측)에서 오른쪽(출력층) 방향으로 일어난다.

Image

그라디언트의 계산은 feed-forward반대 방향으로 일어난다: 출력층에서 입력층의 방향으로 단계적으로 계산된다.

Image

연쇄법칙에 의해 \frac{\partial L}{\partial W} = \frac{1}{N} \sum_i \frac{\partial L_i}{\partial W} + \frac{\partial R(W)}{\partial W} = \frac{1}{N} \sum_i \frac{\partial L_i}{\partial f} \frac{\partial f}{\partial W} + \frac{\partial R(W)}{\partial W} 그라디언트를 계산할 있다.

2-layer Neural Net 예시

신경망에서도 똑같은 방법으로 역전파할 수 있다. 그림은 좀 더 복잡하다.

Image

이처럼 연쇄법칙을 사용해서 단계적으로, 출력층에서 입력층 방향으로 그라디언트 값을 전파하는 계산법을 역전파(back-propagation; backprop)이라고 한다.

 

2-layer Neural Net 구현

붓꽃 분류 문제

판별함수(신경망 구조)와 손실함수도 정의했고 그라디언트 계산법도 알았으니 이제 직접 붓꽃 종을 분류하는 신경망을 구현해볼 시간이다. 선형 분류기를 구현했을 때와 마찬가지로 붓꽃 측정치 중에서 2개의 차원(꽃받침 길이, 꽃잎 길이)만을 사용했다.

  • 데이터 손실로 softmax loss 가지고
  • 정규화 손실로 Ridge 정규화항을 가지며,
  • 2개 뉴런이 있는 입력층 하나와
  • 100개 뉴런이 있는 은닉층 하나와
  • 3개 뉴런이 있는 출력층을 가지는,
  • back-propagation으로 그라디언트를 계산하여 가중치를 업데이트하는

2층 신경망을 구현한 코드는 아래와 같다.

def train_nn(x, y, D, K, reg=.001, eta=.05, epochs=5000):
    # initialize weights
    W1 = np.random.randn(D, 100) * 0.01
    b1 = np.zeros((1, 100))
    W2 = np.random.randn(100, K) * 0.01
    b2 = np.zeros((1, K))

    # training process
    losses = []

    for epoch in range(epochs):
        h = np.maximum(0, x.dot(W1) + b1) # hidden layer
        f = h.dot(W2) + b2 # final layer

        # backprop to hidden layer
        p = np.exp(f) / np.sum(np.exp(f), axis=1, keepdims=True)
        dhidden = p.copy()
        dhidden[range(x.shape[0]), y] -= 1
        dhidden /= x.shape[0]
        dW2 = (h.T).dot(dhidden) + reg * W2
        db2 = np.sum(dhidden, axis=0, keepdims=True)

        # backprop the activation (relu)
        drelu = (dhidden).dot(W2.T)
        drelu[h <= 0] = 0

        # backprop to input layer
        dW1 = (x.T).dot(drelu) + reg * W1
        db1 = np.sum(drelu, axis=0, keepdims=True)

        # update weights
        W1 -= eta * dW1; b1 -= eta * db1
        W2 -= eta * dW2; b2 -= eta * db2

        # compute loss
        data_loss = -np.sum(np.log(p[range(x.shape[0]), y])) / x.shape[0]
        reg_loss = 0.5 * reg * (np.sum(W2**2) + np.sum(W1**2))
        loss = data_loss + reg_loss
        losses.append(loss)
        if epoch % 1000 == 0:
            print("{}: loss={}".format(epoch, loss))
 
    return W1, b1, W2, b2, losses

output_19_1

학습시킨 신경망으로 붓꽃 종을 분류한 결과 96% accuracy를 얻을 수 있었다 (선형 분류기에서는 92.7%였다).

자동으로 그라디언트를 계산해주는 Tensorflow등의 프레임워크를 사용하면 더 간단히 구현할 수 있다. 거기에 Keras와 같은 high-level 프레임워크를 사용하면 훨씬 더 간단히 구현할 수 있다. 그냥 구현은 남에게 맡기고 간단히 체험만 해보고싶다면 구글에서 제공하는 웹페이지에서 클릭 몇 번으로 간단한 신경망을 학습시킬 수 있다.

비선형 데이터 분류 문제

붓꽃 분류 문제는 선형 분류기로도 쉽게 해결할 수 있었다. 신경망은 비선형 분류 문제에서 더욱 빛난다. 선형 분류기를 사용했을 때는 엉망진창으로 분류되던 데이터가 신경망을 사용한 분류에서는 매우 높은 accuracy (~99%)로 해결되는 것을 확인할 수 있다.

지금까지 가장 단순한 신경망 구조인 MLP를 이용해서 비선형 분류 문제를 해결해 보았다. 다음 글에서는 신경망 구조를 학습시킬 때 주의깊게 살펴야 하는 점들에 대해 다루어보겠다.

 

참고

  • 이 글은 CS231n 강의록을 매우 많이 참고했다. 원 강의록을 읽어보시기를 강하게 추천드린다.
  • polynomial kernel을 사용한 softmax classifier 및 2-layer neural net를 구현한 전체 코드는 이 노트북에서 확인할 수 있다.
  • 애니메이션을 만들어보면 decision hyperplane이 학습되고 있는 상황을 확인할 수 있다. 꽤 재미지고 신기하다.

Animated GIF-downsized_large