배치 정규화

  • 데이터 셋을 사전에 정규화하는 것과는 달리 배치 정규화(batch normalization)은 각 미니 배치별로 학습 전에 정규화 처리를 하는 기법이다.
  • 데이터의 전처리로서 데이터 셋을 정규화하고 weight의 초기값을 정리함으로써 학습이 잘 진행되기는 하지만 학습시킬 대에는 신경망 모델 내부에서 분산이 편중되어 버리기 때문에 전처리를 한 효과가 한정적일 수 밖에 없다.
  • 배치 정규화는 학습에 사용하는 각 미니 배치별로 정규화를 하기 때문에 학습 과정 전체에서 효과가 나타난다.
  • 예를 들어 $m$개의 데이터로 구성된 미니 배치 $\mathbf{B}=\{x_1, x_2, \cdots, x_m\}$이 있다고 하면 미니 배치의 평균 $\mu_\mathbf{B}$과 분산 $\sigma^2_\mathbf{B}$은 각각 식 $(14.1)$과 식 $(14.2)$로 나타낼 수 있다.

\begin{align}\mu_\mathbf{B} &= \frac{1}{m} \sum_{i=1}^mx_i\tag{15.1}\\\\ \sigma^2_\mathbf{B} &= \frac{1}{m}\sum_{i=1}^m(x_i-\mu_\mathbf{B})^2\tag{15.2}\end{align}


  • 이와 달리 배치 정규화는 미니 배치에 포함된 각 데이터 $x_i$는 식 $(15.3)$와 식 $(15.4)$를 통해 변환된 $\{y_1,y_2,\cdots,y_m\}$을 배치 정규화된 출력 값으로 사용한다.
    • $\gamma$와 $\beta$가 이 모델의 매개변수이다.

\begin{align} \hat{x}_i &= \frac{x_i - \mu_\mathbf{B}}{\sqrt{\sigma^2_\mathbf{B} + \epsilon}}\tag{15.3}\\\\ y_i &= \gamma \hat{x}_i + \beta\tag{15.4}\end{align}


  • 배치 정규화를 사용한 딥러닝에서는 오차함수 $E$에 관해 해당 모델의 매개변수인 $\gamma$와 $\beta$가 이전 층에 전달할 $x_i$에 대한 경사를 각각 계산해야 하는데, 계산식은 다음과 같다.

\begin{align} \frac{\partial E}{\partial \gamma} &= \sum_{i=1}^m \frac{\partial E}{\partial y_i} \frac{\partial y_i}{\partial \gamma}\tag{15.5}\\&= \sum_{i=1}^m \frac{\partial E}{\partial y_i} \frac{\partial}{\partial \gamma}(\gamma \hat{x}_i + \beta)\tag{15.6}\\&= \sum_{i=1}^m \frac{\partial E}{\partial y_i} \hat{x}_i\tag{15.7}\\\\\frac{\partial E}{\partial \beta} &= \sum_{i=1}^m \frac{\partial E}{\partial y_i} \frac{\partial y_i}{\partial \beta}\tag{15.8}\\&= \sum_{i=1}^m \frac{\partial E}{\partial y_i} \frac{\partial}{\partial \beta}(\gamma \hat{x}_i + \beta)\tag{15.9}\\&= \sum_{i=1}^m \frac{\partial E}{\partial y_i} \tag{15.10}\\\\\frac{\partial E}{\partial x_i} &= \frac{\partial E}{\partial  \hat{x}_i} \frac{\partial \hat{x}_i}{\partial x_i} + \frac{\partial E}{\partial \mu_\mathbf{B}} \frac{\partial \mu_\mathbf{B}}{\partial x_i} + \frac{\partial E}{\partial \sigma^2_\mathbf{B}} \frac{\partial \sigma^2_\mathbf{B}}{\partial x_i} \tag{15.11}\\&= \frac{\partial E}{\partial  \hat{x}_i} \frac{\partial }{\partial x_i}\Bigg(\frac{x_i - \mu_\mathbf{B}}{\sqrt{\sigma^2_\mathbf{B} + \epsilon}}\Bigg) + \frac{\partial E}{\partial \mu^2_\mathbf{B}} \frac{\partial}{\partial x_i}\Bigg(\frac{1}{m} \sum_{i=1}^mx_i\Bigg) +\frac{\partial E}{\partial \sigma^2_\mathbf{B}} \frac{\partial}{\partial x_i} \Bigg(\frac{1}{m}\sum_{i=1}^m(x_i-\mu_\mathbf{B})^2\Bigg)\tag{15.12}\\&= \frac{\partial E}{\partial \hat{x}_i} \frac{1}{\sqrt{\sigma^2_\mathbf{B} + \epsilon}} + \frac{\partial E}{\partial \mu^2_\mathbf{B}} \frac{1}{m} +\frac{\partial E}{\partial \sigma^2_\mathbf{B}} \frac{2(x_i-\mu_\mathbf{B})}{m}\tag{15.13}\end{align}


  • 여기서 $\frac{\partial E}{\partial y_i}$는 역전파되어 온 오차로 우리가 알고 있는 값이다.  그러나 다른 경사값은 다음과 같이 구할 수 있기 때문에 모든 경사를 오차역전파법으로 최적화할 수 있다.

\begin{align} \frac{\partial E}{\partial \hat{x}_i} &= \frac{\partial E} {\partial y_i} \frac {\partial y_i} {\partial \hat{x}_i}\tag{15.14}\\&= \frac{\partial E} {\partial y_i} \frac {\partial } {\partial \hat{x}_i}\Bigg( \gamma \hat{x}_i + \beta\Bigg)\tag{15.15}\\&= \frac{\partial E} {\partial y_i} \gamma \tag{15.16}\\\\ \frac{\partial E}{\partial \sigma^2_\mathbf{B}} &= \sum_{i=1}^m \frac{\partial E} {\partial \hat{x}_i} \frac {\partial \hat{x}_i} {\partial \sigma^2_\mathbf{B}} \tag{15.17}\\&= \sum_{i=1}^m \frac{\partial E} {\partial \hat{x}_i} \frac {\partial } {\partial \sigma^2_\mathbf{B}} \Bigg( \frac{x_i - \mu_\mathbf{B}}{\sqrt{\sigma^2_\mathbf{B} + \epsilon}} \Bigg)\tag{15.18}\\&= \sum_{i=1}^m \frac{\partial E} {\partial \hat{x}_i} \frac {\partial } {\partial \sigma^2_\mathbf{B}} \Bigg( \bigg(x_i - \mu_\mathbf{B}\bigg)\bigg(\sigma^2_\mathbf{B} + \epsilon\bigg)^{-\frac{1}{2}}\tag{15.19}\\&= \sum_{i=1}^m \frac{\partial E} {\partial \hat{x}_i} \bigg(-\frac{1} {2}\bigg) \bigg(x_i - \mu_\mathbf{B}\bigg)\bigg(\sigma^2_\mathbf{B} + \epsilon\bigg)^{-\frac{1}{2}-1}\cdot \frac{\partial}{\partial \sigma^2_\mathbf{B}}\Big(\sigma^2_\mathbf{B}+\epsilon\Big)\tag{15.20}\\&= -\sum_{i=1}^m \frac{\partial E} {\partial \hat{x}_i}  \frac{x_i - \mu_\mathbf{B}} {2\sqrt{\big(\sigma^2_\mathbf{B} + \epsilon\big)^3 }}\tag{15.21}\\\\\end{align}


\begin{align}\frac{\partial E}{\partial \mu_\mathbf{B}}  &= \sum_{i=1}^m \frac{\partial E} {\partial \hat{x}_i} \frac {\partial \hat{x}_i} {\partial \mu_\mathbf{B}} + \frac {\partial E} {\partial \sigma^2_\mathbf{B} } \frac {\partial \sigma^2_\mathbf{B}} {\partial \mu_\mathbf{B}}\tag{15.22}\\&= \sum_{i=1}^m \frac{\partial E} {\partial \hat{x}_i} \frac {\partial } {\partial \mu_\mathbf{B}} \Bigg( \frac{x_i - \mu_\mathbf{B}}{\sqrt{\sigma^2_\mathbf{B} + \epsilon}} \Bigg)+ \frac {\partial E} {\partial \sigma^2_\mathbf{B} } \frac {\partial } {\partial \mu_\mathbf{B}} \Bigg( \frac{1}{m}\sum_{i=1}^m(x_i-\mu_\mathbf{B})^2 \Bigg) \tag{15.23}\\ &= \sum_{i=1}^m \frac{\partial E} {\partial \hat{x}_i} \Bigg( \frac{- 1}{\sqrt{\sigma^2_\mathbf{B} + \epsilon}} \Bigg)+ \frac {\partial E} {\partial \sigma^2_\mathbf{B} } \Bigg( \frac{2}{m}\sum_{i=1}^m(x_i-\mu_\mathbf{B})^{2-1} \Bigg) \frac{\partial } {\partial \mu_\mathbf{B}}\Big( x_i-\mu_\mathbf{B} \Big) \tag{15.24}\\&= \sum_{i=1}^m \frac{\partial E} {\partial \hat{x}_i} \Bigg( \frac{- 1}{\sqrt{\sigma^2_\mathbf{B} + \epsilon}} \Bigg)+ \frac {\partial E} {\partial \sigma^2_\mathbf{B} } \Bigg( \frac{2}{m}\sum_{i=1}^m(x_i-\mu_\mathbf{B})^{2-1} \Bigg) (-1) \tag{15.25}\\&= \sum_{i=1}^m \frac{\partial E} {\partial \hat{x}_i} \Bigg( \frac{- 1}{\sqrt{\sigma^2_\mathbf{B} + \epsilon}} \Bigg) - \frac {\partial E} {\partial \sigma^2_\mathbf{B} } \Bigg( \frac{2}{m}\sum_{i=1}^m(x_i-\mu_\mathbf{B}) \Bigg) \tag{15.26}\end{align}


  • 배치 정규화는 미니 배치에 포함된 데이터를 정규화하기 때문에 지금까지는 층의 활성화 함수를 식 $(12.127)$과 같이 표현했다.

\begin{align} \mathbb{h} = f(W\mathbb{x}+\mathbb{b})\tag{15.27}\end{align}


  • 미니 배치에 해당하는 식 $(15.4)$의 처리과정을 $BN_{\gamma,\beta}(\mathbb{x}_i)$라고 한다면 층의 활성화를 나타내는 식 $(15.27)$은 다음과 같이 쓸 수 있다.

\begin{align} \mathbb{h} = f(BN_{\gamma,\beta}(W\mathbb{x}_i) )\tag{15.28}\end{align}


  • 식 $(15.28)$에서 바이어스는 신경쓰지 않아도 된다는데 왜 그렇지?
  • 위 논문에서 주장하는 미니 배치의 장점으로는 다음과 같다.
    • 학습률을 크게 설정해도 학습이 잘 진행됐다.
    • 드롭아웃을 사용하지 않아도 일반화 성능이 높다.


  • TensorFlow에서는 tf.nn.batch_normalization()를 사용하면 되지만 해당 API를 사용하지 않고 코드로 구현하면 다음과 같다.
    • 식 $(15.3)$과 식 $(15.4)$를 구현한 것이 batch_normalization()이다.
    • tf.nn.moments() 함수는 평균과 분산을 계산해 반환한다.
    • '은닉층-출력층'은 지금까지 해왔던 것처럼 softmax() 함수를 사용한다.
def inference(x, keep_prob, n_in, n_hiddens, n_out):
    # 미니 배치
    def weight_variable(shape):
        initial = np.sqrt(2.0 / shape[0]) * tf.truncated_normal(shape)
        return tf.Variable(initial)

    def bias_variable(shape):
        initial = tf.zeros(shape)
        return tf.Variable(initial)

    def batch_normalization(shape, x):
        # 배치 정규화 처리
        eps = 1e-8
        beta = tf.Variable(tf.zeros(shape))
        gamma = tf.Variable(tf.ones(shape))
        mean, var = tf.nn.moments(x, [0])
        return gamma * (x - mean) / tf.sqrt(var + eps) + beta

    # 미니 배치  입력층-은닉층, 은닉층-은닉층
    for i, n_hidden in enumerate(n_hiddens):
        if i == 0:
            input = x
            input_dim = n_in

        else:
            input = output
            input_dim = n_hiddens[i-1]

        W = weight_variable([input_dim, n_hidden])
        u = tf.matmul(input, W)
        h = batch_normalization([n_hidden], u)
        output = tf.nn.relu(h)

    # 은닉층-출력층
    W_out = weight_variable([n_hiddens[-1], n_out])
    b_out = bias_variable([n_out])
    y = tf.nn.softmax(tf.matmul(output, W_out) + b_out)

    return y

그림 15.1 TensorFlow에서 미니 배치 정규화 알고리즘에서 Adam Optimizer를 사용했을 때의 정확도와 오차 변화


  • Keras에서는 from keras.layers.normalization import BatchNormalization을 선언하고 학습과정에서 BatchNormalization()을 사용하면 된다.
model = Sequential()

for i, input_dim in enumerate(([n_in] + n_hiddens)[:-1]):
    model.add(Dense(input_dim=input_dim, units=n_hiddens[i], init=weight_variable))
    model.add(BatchNormalization())
    model.add(LeakyReLU(alpha=alpha))

model.add(Dense(units=n_out, kernel_initializer=weight_variable))
model.add(Activation('softmax'))

그림 15.2 Keras에서 미니 배치 정규화 알고리즘에서 Adam Optimizer를 사용했을 때의 정확도와 오차 변화


출처 : 정식으로 배우는 딥러닝

학습 조기 종료(Early Stopping)

  • 학습 횟수(=epoch 수)가 많을수록 학습 데이터에 관한 오차는 작아지지만 이것이 오버피팅을 초래해서 모델의 일반화 성능이 떨어지게 된다.
  • 실제 epoch = 300으로 설정하고 학습시킨 결과 처음에는 오차가 순조롭게 줄어들다가 어느 시점부터 오차가 커져가는 것(즉, 오버피팅이 되는 것)을 알 수 있다.

그림 14.1  epoch=300을 했을 때 정확도와 오차의 변화

 

  • 이 문제를 해결하기 위해 사용하는 기법이 조기 종료(early stopping)로 '이전 epoch 때와 비교해서 오차가 증가했다면 학습을 중단한다'라는 방법이다.
  • 개념을 설명하자면 각 epoch의 마지막 부분에 조기 종료를 시킬 것인지 판단하는 부분을 추가하는 것으로 유사 코드는 다음과 같다.
for epoch in range(epochs):     loss = model.train()['loss']      if early_stopping(loss):         break

 

  • 조기 종료를 구현할 때, 주의할 사항은 바로 직전의 epoch 오차를 비교하기만 해서는 안된다. 그림 14.1의 그래프가 보여주는 것처럼 오차값은 각 epoch마다 올라가기도 하고 내려가기도 하며, 드롭아웃을 적용한 경우에는 아직 학습이 되지 않은 뉴런이 존재할 가능성이 있기 때문에 오차값이 상하로 움직이게 된다.
  • 따라서 '어떤 일정한 epoch 수를 거듭하면서 계속해서 오차가 증가하면 학습을 중단한다'는 방식으로 구현해야 한다.
  • TensorFlow에서는 tf.contrib.learntf.contrib.learn.monitors.ValidationMonitor()를 사용하면 되지만 다음과 같은 코드를 사용하면 조기 종료 기능을 구현할 수 있다.
class EarlyStopping():     def __init__(self, patience=0, verbose=0):         self._step = 0         self._loss = float('inf')         self.patience  = patience         self.verbose = verbose      def validate(self, loss):         if self._loss < loss:             self._step += 1             if self._step > self.patience:                 if self.verbose:                     print(f'Training process is stopped early....')                 return True         else:             self._step = 0             self._loss = loss          return False

 

  • 위 코드에서 patience는 '오차를 보기 위해 과거 몇 epoch까지 거슬러 올라갈 것인가'를 설정하는 값이다.
early_stopping = EarlyStopping(patience=10, verbose=1)

 

  • 학습을 시키기 전에 EarlyStopping의 인스턴스를 위와 같은 방법으로 생성하고 아래와 같이 반복 학습 과정의 마지막에 early_stopping.validate()을 추가하면 조기 학습 기능을 구현할 수 있게 된다.
if __name__ == '__main__':          early_stopping = EarlyStopping(patience=10, verbose=1)      for epoch in range(epochs):         X_, Y_ = shuffle(X_train, Y_train)          for i in range(n_batches):              start = i * batch_size             end = start + batch_size              sess.run(train, feed_dict={x: X_[start: end], y: Y_[start: end], keep_prob: p_keep})          # 검증 데이터를 사용해 정확도 측정         val_loss = loss.eval(session=sess, feed_dict={x: X_val,                                                       y: Y_val,                                                       keep_prob: 1.0})          val_accuracy = accuracy.eval(session=sess, feed_dict={x: X_val,                                                               y: Y_val,                                                               keep_prob: 1.0})          # 검증 데이터에 대한 학습 진행 상황을 저장         history['val_loss'].append(val_loss)         history['val_acc'].append(val_accuracy * 100)          if early_stopping.validate(val_loss):             break

그림 14.1 TensorFlow에서 조기 종료 기능을 사용했을 때 정확도와 오차 변화

 

  • Keras에서는 from keras.callbacks import EarlyStopping을 선언하면 조기 종료 기능을 사용할 수 있다.
    • keras.callbacks는 각 epoch마다 모델을 학습시킨 후에 호출하는 콜백 함수이다
    • 모델을 학습시키기 전에 다음과 같이 선언하고
early_stopping = EarlyStopping(monitor='val_loss', patient=10, verbose=1)

 

  • 아래와 같이 학습 모델 model.fit()callbacks=early_stopping을 사용하면 된다.
hist = model.fit(X_train, Y_train,                  epochs=epochs,                  batch_size=batch_size,                  validation_data=(X_val, Y_val),                  callbacks=[early_stopping])

그림 14.2 Keras에서 조기 종료 기능을 사용했을 때 정확도와 오차 변화

 

출처 : 정석으로 배우는 딥러닝

 

'신경망(Neural Network) 스터디' 카테고리의 다른 글

15. 배치 정규화  (0) 2018.02.01
13. 학습률 설정  (0) 2018.01.29
12. 데이터 정규화 및 매개변수 초기화  (0) 2018.01.23
11. 학습 과정 시각화  (3) 2018.01.19
10. 신경망 모델 구현 방법  (0) 2018.01.19

학습률을 설정하는 방법

  • 신경망 모델을 학습시킬 때 일반적으로 경사하강법(GDA)을 사용하는데, 이 때 학습률은 \(0.1\)이나 \(0.01\)과 같은 값을 사용하였다.
  • 학습률의 값을 어떻게 설정하는지에 따라 최적해가 구해지는지 또는 구해지지 않는지가 정해진다고 하면 이 학습률도 적절히 설정하는 방법들이 개발되어 있다.


모멘텀(Momentum)

  • 국소최적해에 빠지지 않고 효율적으로 해를 구하려면 학습률을 '처음에는 크게, 그리고 점점 작게'하는 것이 바람직하다고 알려져 있다.
  • 모멘텀은 학습률의 값 자체는 같지만 매개변수를 변경해갈 때 모멘텀 항이라는 조정항을 사용해 유사적으로 '처음에는 크게 그리고 점점 작게'라는 개념을 표현한다.
  • 오차함수 \(E\)에 대한 신경망 모델의 매개변수를 \(\theta\)라 하고 \(\theta\)에 대한 \(E\)의 경사를 \(\nabla_\theta E\), 매개변수의 차 \(\Delta\theta^{(t)}\)를 식 \((13.1)\)라고 하면 스텝 \(t\)에서 모멘텀을 사용해  매개변수를 변경해 가는 식은 식 \((13.2)\)와 같다.
    • \(\gamma\Delta\theta^{(t-1)}\) : 모멘텀항
    • 계수 \(\gamma(<1)\)는 일반적으로 \(0.5\)나 \(0.9\)와 같은 값을 설정

\begin{align}\Delta\theta^{(t)} &= \theta^{(t-1)} - \theta^{(t)} \tag{13.1}\\&=-\eta\nabla_\theta E(\theta) + \gamma \Delta\theta^{(t-1)} \tag{13.2}\\\\ \Delta\theta^{(t)} - \gamma \Delta\theta^{(t-1)}&= -\eta\nabla_\theta E(\theta)\tag{13.3}\\\\\frac{1}{\eta}\Delta\theta^{(t)} - \frac{\gamma}{\eta} \Delta\theta^{(t-1)}&= -\nabla_\theta E(\theta)\tag{13.4}\end{align}

  • 식 \((13.2)\)는 물리학에 비유하면 식 \((13.5)\)와 같은 소위 '공기 저항식'과 같은 모양으로 단계가 높아질 수록 경사가 점점 낮아진다는 것을 알 수 있다.

\begin{align} m\frac{d^2\theta}{dt^2} + \mu\frac{d\theta}{dt} = -\Delta_\theta E(\theta)\tag{13.5}\end{align}


  • TensorFlow에서는 tf.train.momentumOptimizer()를 사용하면 모멘텀을 구현할 수 있다.
def train(loss):
    optimizer = tf.train.MomentumOptimizer(learning_rate=0.01, momentum=0.9)
    train = optimizer.minimize(loss)
    return train

그림 13.1 TensorFlow에서 Momentum Optimizer를 사용했을 때의 정확도와 오차 변화


  • Keras에서는 SGD()의  매개변수로 momentum=을 지정하면 된다.
model.compile(loss='categorical_crossentropy', 
              optimizer=SGD(lr=0.01, momentum=0.9), 
              metrics=['accuracy'])

그림 13.2 Keras에서 Momentum Optimizer를 사용했을 때의 정확도와 오차 변화


Nesterov Momentum

  • Yurii Nesterov, A method for unconstrained convex minimization problem with the rate of convergence \(O(1/k^2)\) . Doklady AN SSSR , 269(3):543–547, 1983
    • 해당 논문의 원문을 찾을 수가 없어 링크를 걸지 못함
  • Nesterov는 식 \((13.2)\)로 표현되는 순수한 모멘텀을 약간 변형해서 매개변수가 '어느 방향을 향해야 하는지'를 식으로 만든 것으로 식 \((13.2)\)에서 \(\Delta\theta^{(t)}=v^{(t)}\)라 놓으면 식 \((13.2)\)는 식 \((13.5)\)와 식 \((13.6)\)으로 표현할 수 있다.
\begin{align} v^{(t)} &= -\eta \nabla _\theta E(\theta) + \gamma \Delta \theta ^{(t-1)} \tag{13.5}\\\\ \theta^{(t)} &= \theta^{(t-1)} - v^{(t)}\tag{13.6}\end{align}
  • \((13.5)\)에 식 \((13.6)\)을 적용하여 모멘텀을 다시 표현하면 식 \((13.7)\)과 식 \((13.8)\)로 나타낼 수 있다.

\begin{align} v^{(t)} &= -\eta \nabla _\theta E(\theta + \gamma v^{(t-1)}) + \gamma \Delta \theta ^{(t-1)} \tag{13.7}\\ &= -\eta \nabla_\theta E(\theta + \gamma \Delta\theta^{(t-1)})+ \gamma \Delta \theta ^{(t-1)}\tag{13.8}\\\\ \theta^{(t)} &= \theta^{(t-1)} - v^{(t)}\tag{13.9}\end{align}

  • \((13.5)\)와 식 \((13.7)\)의 차이는 \(E(\theta + \gamma v^{(t-1)})\)에 있는데, 이 차이로 인해 다음 단계에서 매개변수의 근사값이 구해지므로 학습률을 효율적으로 설정해 해를 구할 수 있다.


  • TensorFlow에서는 tf.train.momentumOptimizer()의 매개변수로  nesterov=True를 지정하면 된다.
def train(loss):
    optimizer = tf.train.MomentumOptimizer(learning_rate=0.01, momentum=0.9, use_nesterov=True)
    train = optimizer.minimize(loss)
    return train

그림 13.3 TensorFlow에서 Nestrov Momentum Optimizer를 사용했을 때의 정확도와 오차 변화


  • Keras에서는 SGD()의 매개변수로 nesterov=True를 지정하면 된다.
model.compile(loss='categorical_crossentropy', 
              optimizer=SGD(lr=0.01, momentum=0.9, nesterov=True), 
              metrics=['accuracy'])

그림 13.4 Keras에서 Nestrov Momentum Optimizer를 사용했을 때의 정확도와 오차 변화


Adaptive Gradient(Adagrad) 알고리즘

  • 모멘텀은 학습률 값을 고정하고 모멘텀 항으로 매개변수의 변경값을 조정했지만 Adagrad 방법은 학습률 값 자체를 변경한다.
  • 다음과 같이 정의하고 식을 전개해보자.

\begin{align} g_i = \nabla_\theta E(\theta_i)\tag{13.10}\end{align}

  • Adagrad 방법은 식 \((13.11)\)처럼 표현된다.

\begin{align} \theta_i^{(t+1)} = \theta_i^{(t)} - \frac{\eta}{\sqrt{G_{ii}^{(t)} +\epsilon}}\odot g^{(t)}\tag{13.11}\end{align}

  • 행렬 \(G^{(t)}\)는 대각행렬이며, \((i,i)\) 성분은 \(t\)번째 단계까지 \(\theta_i\)에 관한 경사를 제곱해서 총합을 낸 것으로 식 \((13.12)\)로 표현할 수 있다.

\begin{align} G_{ii}^{(t)} = \sum_{\tau=0}^t g_i^{(\tau)} \cdot g_i^{(\tau)}\tag{13.12}\end{align}

  • \(\epsilon\)은 분모가 \(0\)이 되지 않도록 하기 위한 작은 값으로 일반적으로 \(\epsilon=1.0\times 10^{-6}\sim1.0\times 10^{-8}\)정도로 설정한다.
  • 식 \((13.11)\)은 다음과 같이 정리할 수 있다.

\begin{align} \theta^{(t+1)} = \theta^{(t)} - \frac{\eta}{\sqrt{G^{(t)} + \epsilon}}\odot G^{(t)}\tag{13.13}\end{align}

  • Adagrad 알고리즘은 모멘텀과 비교하면 하이퍼  매개변수가 적고 이제까지의 경사를 바탕으로 해서 자동으로 학습률 \(\eta\)를 수정하므로 더욱 다루기 쉬운 기법이라고 할 수 있다.
  • 식 \((13.11)\)이나 식 \((13.13)\)의 유사 코드는 다음과 같지만 TensorFlow와 Keras에서 Adagrad 알고리즘 관련 API를 제공하므로 라이브러리를 사용하면 된다.
G[i][i] += g[i] * g[i]
theta[i] -= (learning_rate / sqrt(G[i][i] + epsilon)) * g[i]


  • TensorFlow에서는 tf.train.AdagradOptimizer()를 사용하면 된다.
def train(loss):
    optimizer = tf.train.AdagradOptimizer(learning_rate=0.01)
    train = optimizer.minimize(loss)
    return train

그림 13.5 TensorFlow에서 Adagrad Optimizer를 사용했을 때의 정확도와 오차 변화


  • Keras에서는 from keras.optimizers import Adagrad를 선언하고 optimizer=Adagrad()를 사용하면 된다.
model.compile(loss='categorical_crossentropy', 
              optimizer=SGD(lr=0.01, momentum=0.9, nesterov=True), 
              metrics=['accuracy'])

그림 13.6 Keras에서 Adagrad Optimizer를 사용했을 때의 정확도와 오차 변화


Adadelta 알고리즘

  • Adagrad 알고리즘을 통해 학습률을 자동으로 조정할 수 있게 되었지만 대각행렬 \(G^{(t)}\)는 경사의 제곱의 누적합이기 때문에 단조증가한다.
  • 따라서 식 \((13.13)\)에서도 알 수 있듯이 학습의 단계가 진행될 수록 곱해지는 계수의 값이 급격하게 작아져버려 학습을 진행할 수 없게되는 문제가 발생하는데 이 문제를 해결한 것이 Adadelta 알고리즘이다.
  • Adadelta의 기본적인 개념은 \(0\)번째 단계부터 제곱합을 누적해가는 것이 아니라 누적해가는 단계 수를 정수 \(w\)로 제한하는 것이다.
  • 단순히 \(w\)만큼 제곱합을 동시에 유지하는 것은 구현의 관점에서 보면 비효율적인 방법이므로 Adadelta는 직전의 단계까지의 모든 경사의 제곱합을 감쇠평균시켜서 재귀식으로 계산한다.
  • 식을 간단하게 정리하기 위해 이제까지 \(g^{(t)}\)라고 한 것을 \(g_t\)라고 쓰면 \(t\)단계에서의 경사의 제곱(\(g_t\odot g_t\))의 이동평균 \(E[g^2]_t\)는 다음과 같이 쓸 수 있다.

\begin{align} E[g^2]_t = \rho E[g^2]_{t-1} + (1-\rho)g^2_t\tag{13.14}\end{align}

  • 식 \((13.14)\)를 보면 단계가 거듭될 수록 경사의 합이 지수함수적으로 작아져간다는 것을 알 수 있다.
  • 그런데 Adagrad를 나타내는 식은 식 \((13.15)\)인데

\begin{align} \theta_{t+1} = \theta_{t} - \frac{\eta}{\sqrt{G^{t} + \epsilon}}\odot G_t\tag{13.15}\end{align}

  • Adadelta는 \(G_t\)를 이제까지의 경사를 제곱한 것의 감쇠평균인 \(E[g^2]_t\)로 치환되므로 식 \((13.16)\)과 같이 나타낼 수 있다.

\begin{align} \theta^{(t+1)} = \theta^{(t)} - \frac{\eta}{\sqrt{E[g^2]_t} + \epsilon}g^t\tag{13.16}\end{align}

  • 이 때 \(\sqrt{E[g^2]_t + \epsilon}\)에 주목하면 (\(\epsilon\)을 무시하면) 제곱평균제곱근(root mean square) 형태이기 때문에 \(\text{RMS}[\cdot]\)로 나타내면 식 \((13.16)\)은 식 \((13.17)\)과 같이 정리된다.

\begin{align} \theta^{(t+1)} = \theta^{(t)} - \frac{\eta}{\textrm{RMS}[g]_t}g^t\tag{13.17}\end{align}

  • 해당 논문에서는 식 \((13.17)\)을 한 단계 더 변형해서 학습률 \(\eta\)를 설정하지 않아도 되게 한다. 다음과 같이 정의하면

\begin{align} \Delta\theta_t = \theta^{(t)} - \frac{\eta}{\textrm{RMS}[g]_t}g^t\tag{13.17}\end{align}

  • 식 \((13.14)\)에 대해 \(\nabla \theta^2_t\)의 감쇠평균은 식 \((13.18)\)과 같이 나타낼 수 있으며

\begin{align} E[\Delta \theta^2]_t = \rho E[\Delta \theta^2]_{t-1} + (1-\rho)\Delta \theta^2_t\tag{13.18}\end{align}

  • \(\Delta\theta_t\)는 알 수 없으므로

\begin{align} \text{RMS}[\Delta\theta]_t=\sqrt{E[\Delta\theta^2]_t+\epsilon}\tag{13.19}\end{align}

  • 식 \((13.19)\)를 \(t-1\)에서의 \(\text{RMS}\)로 근사하면 식 \((13.20)\)으로 표현되는 Adadelta 식이 구해진다.

\begin{align} \Delta\theta_t = - \frac{\text{RMS}[\Delta\theta]_{t-1}}{\text{RMS}[g]_t}g_t\tag{13.20}\end{align}

  • 결국 Adadelta에서는 \(\rho\)(또는 \(epsilon\))만 설정하면 된다는 것이며 이것은 일반적으로 \(\rho=0.95\)로 설정한다.


  • TensorFlow에서는 tf.train.AdadeltaOptimizer()를 사용하면 된다.
    • 수식에서는 식 \((13.18)\)처럼 학습률이 필요없지만 구현할 때에는 learning_rate=1.0이라고 해야 한다. 이는 \(\theta_{t+1}=\theta_t+\alpha\Delta\theta_t\)의 \(\alpha\)를 설정하기 위한 것이다.
    • TensorFlow에서는 기본적으로 이 값을 \(0.001\)로 정하는데 앞서 나온 식의 흐름을 따라가보면 이 값을 \(1.0\)으로 정해도 문제없으므로 learning_rate=1.0을 매개변수로 넘겨준 것이다.
def train(loss):
    optimizer = tf.train.AdadeltaOptimizer(learning_rate=1.0, rho=0.95)
    train = optimizer.minimize(loss)
    return train

그림 13.7 TensorFlow에서 Adadelta Optimizer를 사용했을 때의 정확도와 오차 변화


  • Keras에서는 from keras.optimizers import Adadelta를 선언하고 optimizer=Adadelta(rho=0.95)를 사용하면 된다.
model.compile(loss='categorical_crossentropy', 
              optimizer=Adadelta(rho=0.95), 
              metrics=['accuracy'])

그림 13.8 Keras에서 Adadelta Optimizer를 사용했을 때의 정확도와 오차 변화


RMSprop 알고리즘

  • RMSprop 알고리즘은 Adadelta와 마찬가지로 Adagrad에서 학습률이 급격하게 감소하는 문제를 해결하기 위한 기법이다.
  • RMSprop 알고리즘은 Adadelta와 비슷한 시기에 고안된 기법이지만 논문의 형태로 존재하지 않고 Coursera 온라인 강의에 사용되는 강의 자료로 정리되어 있는 기법이다.
  • RMSprop은 Adadelta의 간이 버전이라고 할 수 있으며 식 \((13.14)\)에서 \(\rho=0.9\)로 설정해서

\begin{align} E[g^2]_t &= \rho E[g^2]_{t-1} + (1-\rho)g^2_t\tag{13.21}\\ &=0.9 E[g^2]_{t-1} + (1-0.9)g^2_t\tag{13.22}\\ &=0.9 E[g^2]_{t-1} + 0.1g^2_t\tag{13.23}\end{align}

  • 식을 위와 같이 두고

\begin{align} \theta_{t+1} = \theta_t - \frac{\eta}{\sqrt{E[g^2]_t+\epsilon}}g^t\tag{13.24}\end{align}

  • 식 \((13.24)\)로 매개변수를 변경해가는 기법이다. 학습률 \(\eta\)는 일반적으로 \(0.001\)과 같은 작은 값으로 설정한다.


  • TensorFlow에서는 tf.train.RMSpropOptimizer()를 사용하면 된다.
def train(loss):
    optimizer = tf.train.RMSpopOptimizer(learning_rate=0.01)
    train = optimizer.minimize(loss)
    return train

그림 13.9 TensorFlow에서 RMSprop Optimizer를 사용했을 때의 정확도와 오차 변화


  • Keras에서는 from keras.optimizers import RMSprop을 선언하고 optimizer=RMSprop(lr=0.001)를 사용하면 된다.
model.compile(loss='categorical_crossentropy', 
              optimizer=RMSprop(lr=0.001), 
              metrics=['accuracy'])

그림 13.10 Keras에서 RMSprop Optimizer를 사용했을 때의 정확도와 오차 변화


Adam 알고리즘

  • Adadelta와 RMSprop이 직전의 단계인 \(t-1\)까지 경사의 제곱의 이동평균 \(v_t=E[g^2]_t\)를 지수함수적으로 감쇠평균한 항을 저장해가며 유지하고 매개변수의 변경식에 이 값을 사용했던 것과는 달리 Adam(Adaptive moment estimation)에서는 여기에 추가로 단순한 경사의 이동평균인 \(m_t=E[g]_t\)를 지수함수적으로 감쇠시킨 항도 사용한다.
  • \(m_t\)와 \(v_t\)는 각각 식 \((13.25)\)와 식 \((13.26)\)으로 나타낼 수 있다.
    • \(\beta_1,\beta_2\in [0,1]\)은 하이퍼 매개변수이며 이동평균의 (지수함수적인) 감쇠율을 조정한다.
    • \(m_t\)와 \(v_t\)는 각각 경사의 1차 모멘트 (평균), 2차 모멘트 (분산)의 추정값에 해당한다.

\begin{align} m_t &= \beta_1m_{t-1} + (1-\beta_1)g_t\tag{13.25}\\\\ v_t &= \beta_2v_{t-1} + (1-\beta_2)g^2_t\tag{13.26}\end{align}

  • 두 개의 이동평균 \(m_t\)와 \(v_t\)는 모두 편중된 모멘트이므로 이 편중은 보정한(편중을 \(0\)으로 만든) 추정값을 구하는 것을 생각해보자.
  • 이 때, \(v_0=\mathbf{0}\)으로 초기화했다고 가정하면 식 \((13.26)\)에 의해 식 \((13.27)\)을 얻을 수 있다.

\begin{align} v_t=(1-\beta_2)\sum_{i=1}^t\beta_2^{t-i}\cdot g_i^2\tag{13.27}\end{align}

  • 이제 우리가 알고싶은 것은 2차 모멘트 \(v_t\)의 이동평균 \(E[v_t]\)와 2차 모멘트 \(E[g_t^2]\)의 관련성이므로 이것을 식 \((13.27)\)을 통해 구하면 다음과 같은 식을 얻을 수 있다.

\begin{align} E[v_t] &= E\Bigg[(1-\beta_2)\sum_{i=1}^t\beta_2^{t-i}\cdot g_i^2\Bigg]\tag{13.28}\\&= E[g_i^2]\cdot(1-\beta_2)\sum_{i=1}^t\beta_2^{t-i}+\zeta\tag{13.29}\\&= E[g_t^2]\cdot(1-\beta_2^t) + \zeta\tag{13.30}\end{align}

  • 이 때 \(\zeta=0\)으로 근사할 수 있도록 하이퍼 매개변수의 값을 설정하면 식 \((13.31)\)을 얻을 수 있다.
    • 2차 모멘트 \(E[g_t^2]\)가 불변이면 \(\zeta=0\)이고, 그렇지 않으면 각 감쇠율 \(1-\beta_1\)과 \(1-\beta_2\)를 축소시켜 \(zeta=0\)으로 근사시킬 수 있다.
    • 따라서 일반적으로 \(\beta_1=0.9\), \(\beta_2=0.999\)로 설정한다.

\begin{align} \hat{v}_t = \frac{v_t}{1-\beta_2^t}\tag{13.31}\end{align}

  • 식 \((13.31)\)과 같이 (편중되지 않은) 추정값을 얻을 수 있으며, \(m_t\)도 같은 방식으로 계산하면 식 \((13.32)\)을 얻을 수 있다.

\begin{align} \hat{m}_t = \frac{m_t}{1-\beta_1^t}\tag{13.32}\end{align}

  • 따라서 매개변수 변경식은 다음과 같이 식 \((13.33)\)으로 정리된다.

\begin{align}\theta_t = \theta_{t-1} - \frac{\alpha}{\sqrt{\hat{v}_t}+\epsilon}\hat{m}_t\tag{13.33}\end{align}

  • Adam도 식은 복잡하지만 최종적인 알고리즘은 단순하게 정리되는데 다음과 같은 유사코드를 보며 생각해보자.
# 초기화
m = 0
v = 0

# 반복
m = beta1 * m + (1 - beta1) * g
v = beta2 * v + (1 - beta2) * g * g
m_hat = m / (1 - beta1 ** t)
v_hat = v / (1 - beta2 ** t)
theta -= learning_rate * m_hat / (sqrt(v_hat) + epsilon)


  • 구체적은 계산은 생략하겠지만 학습률 \(\alpha\)는 식 \((13.34)\)와 같다.

\begin{align}\alpha_t = \alpha \cdot \frac{\sqrt{1-\beta_2^t}}{1-\beta_1^t} \tag{13.34}\end{align}

  • 식 \((13.34)\)를 만족시키는 \(\alpha\)를 사용하면 더욱 효율적으로 탐색할 수 있다. 이 경우의 유사코드는 다음과 같다.
# 초기화
m = 0
v = 0

# 반복
learning_rate_t = learning_rate * sqrt(1 - beta2 ** t) / (1 - beta1 ** t)
m = beta1 * m + (1 - beta1) * g
v = beta2 * v + (1 - beta2) * g * g
theta -= learning_rate * m / (sqrt(v) + epsilon)


  • TensorFlow에서는 tf.train.AdamOptimizer()를 사용하면 된다.
def train(loss):
    optimizer = tf.train.AdamOptimizer(learning_rate=0.001,
                                       beta1=0.9,
                                       beta2=0.999)
    train = optimizer.minimize(loss)
    return train

그림 13.11 TensorFlow에서 Adam Optimizer를 사용했을 때의 정확도와 오차 변화


  • Keras에서는 from keras.optimizers import Adam을 선언하고 optimizer=Adam(lr=0.001, beta_1=0.9, beta_2=0.999)를 사용하면 된다.
model.compile(loss='categorical_crossentropy', 
              optimizer=Adam(lr=0.001, beta_1=0.9, beta_2=0.999), 
              metrics=['accuracy'])

그림 13.12 Keras에서 Adam Optimizer를 사용했을 때의 정확도와 오차 변화



출처 : 정석으로 배우는 딥러닝

데이터 정규화 및 웨이트 초기화

  • weight의 초깃값이 신경망 모델의 학습에 영향을 미치는데, 초깃값을 어떻게 설정해야 좋을까?
  • weight에 관해 생각해보기 위해 입력 데이터를 '깔끔하게' 정리를 해보자.
  • 다양한 입력 데이터가 일정한 범위에 들어가도록 전처리를 해보자. 가장 좋은 방법은 이 범위를 \(0~1\) 사이의 값을 갖도록 하는 것이다.
  • 예를 들어 MNIST 데이터의 경우 데이터가 \(0\)에서 \(255\)까지의 gray scale 값을 갖기 때문에 다음과 같이 코드를 작성하면 입력 데이터의 값이 모두 \(0\)부터 \(1\) 사이의 값을 갖게 된다.
X = X / 255


  • 위의 코드를 다른 모든 데이터에도 적용이 가능하도록 일반화하면 다음과 같다.
X = X / X.max()


  • 위의 과정처럼 데이터가 일정한 범위 안에 들어가게 해서 그 다음 처리를 원할하게 이루어지도록 하는 것을 정규화(normalization)이라고 한다.
  • 데이터의 분포를 생각하면 데이터의 평균이 \(0\)이 되도록 정규화하는 것이 좋다. 이 과정을 코드로 바꾸면 다음과 같다.
X = X / X.max()
X = X - X.mean(axis=1).reshape(len(X), 1)


  • 데이터를 정규화했을 때 데이터의 분포가 한쪽으로 치우지지 않는다면 weight의 성분은 양수값과 음수값을 모두 갖게 될 것이다. 이 때 weight의 값이 한쪽으로 치우지지 않는다면 양수와 음수가 거의 반반으로 비슷하게 분포할 것이다. 따라서 weight의 성분을 모두 $0$으로 초기화하는 것을 가장 먼저 생각해볼 수 있다.
    • weight의 성분을 모두 같도록 초기화하면 오차역전파식을 사용했을 때 경사값도 같아져버리므로 weight 값이 제대로 갱신되지 못하기 때문에 \(0\)에 가까운 난수로 초기화를 해야한다.
    • 지금까지 weight의 값을 정규분포를 사용해 초기화한 이유는 평균이 \(\mu=0\)에 가까운 난수를 얻기 위함이다. 또한 표준편차 \(\sigma\)가 작을수록 weight의 성분들은 \(0\)에 가까워져 평균도 \(0\)에 가깝기 때문에 좋다. 따라서 이를 위하여 np.random.normal(scale=0.01, size=shape) 함수를 사용하는 것이다.
  • 단순히 표준편차 \(\sigma\)를 작게 한다고 좋아지는 것은 아니다. 초깃값이 너무 작으면 weight가 계수로 곱해지는 경사값도 너무 작아져 학습이 제대로 진행되지 않는 문제가 발생한다.
    • ReLU는 \(\mathbb{x}=0\) 부근에서도 경사가 소실되지 않는 성질을 지난 활성화 함수이기 때문에 표준편차를 \(\sigma=0.01\)과 같이 작은 값으로 잡아야 학습이 잘 진행되는 경향이 있다.
  • 문제 해결을 위해 표준편차가 \(\sigma=1.0\)인 표준정규분포를 전제로 여기에 적절한 계수를 곱해서 좋은 초깃값을 생성하는 방법을 사용하는데, a*np.random.normal(scale=0.01, size=shape)에서 a에 어떤 값을 줘야 하는 것인가이다.
    • 주의할 점은 입력 데이터의 차원 수가 높을 수록 (표준편차의 값이 \(1.0\)이기 때문에) 생성되는 weight의 성분 값들이 서로 흩어지기 쉬워진다는 것이다. 따라서 weight를 초기화할 때 이 흩어짐을 억제할 수 있는 방법이 필요하다.


웨이트 성분값들의 흩어짐을 막기 위한 방법

  • 입력 데이터 \(\mathbb{x}\)가 \(n\)차원이고, weight가 \(W\)일 때, 활성화되기 전의 값을 \(\mathbb{p}\)라고 하면 \(\mathbb{p}\)의 각 성분 \(p_j\)는 식 \((12.1)\)과 같이 나타낼 수 있다.

\begin{align}p_j = \sum_{i=1}^nw_{j_i}x_i \tag{12.1}\end{align}

  • 이 때 \(E[\cdot]\)가 기대값(평균)이라 하고, \(Var[\cdot]\)를 분산이라고 하면 식 \((12.2)\)가 성립한다.

\begin{align} Var[X]=E[X^2]-(E[X])^2 \tag{12.2} \end{align}

  • \(p_j\)의 분산은 다음과 같다.

\begin{align}Var\left[p_j\right] &= Var\left[\sum_{i=1}^nw_{j_i}x_i\right]\tag{12.3}\\&= \sum_{i=1}^n Var\left[w_{j_i}x_i\right]\tag{12.4}\\&= \sum_{i=1}^n \left\{E \left[ \left(w_{j_i}x_i\right)^2 \right] - \left( E \left[ w_{j_i}x_i \right] \right)^2 \right\}\quad(\because\,\,(12.2))\tag{12.5}\\&= \sum_{i=1}^n \left\{E \left[ w_{j_i}^2x_i^2 \right] - \left( E \left[ w_{j_i} \right] \cdot E \left[x_i \right]\right)^2 \right\}\tag{12.6}\\&= \sum_{i=1}^n \left\{E \left[ w_{j_i}^2\right] \cdot E \left[ x_i^2 \right] - \left( E \left[ w_{j_i} \right] \right)^2 \cdot \left(E \left[x_i \right]\right)^2 \right\}\tag{12.7}\end{align}

  • 이제 \(E [ w_{j_i}^2]\)와 \(E [ x_i^2 ]\)를 따로 계산하면 다음과 같다.

\begin{align}E \Big[ w_{j_i}^2\Big] &= Var\Big[w_{j_i}\Big] + \Big(E \Big[ w_{j_i}\Big]\Big)^2 \tag{12.8}\end{align}

\begin{align}E \Big[ x_i^2 \Big] &= Var\Big[x_i\Big] + \Big(E \Big[ x_i\Big]\Big)^2\tag{12.9}\end{align}


  • 식 \((12.8)\)과 식 \((12.9)\)의 곱을 계산하면 식 \((12.10)\)과 같다.

\begin{align}E \Big[ w_{j_i}^2\Big] \cdot E \Big[ x_i^2 \Big] &= \bigg(Var\Big[w_{j_i}\Big] + \Big(E \Big[ w_{j_i}\Big]\Big)^2\bigg) \cdot \bigg(Var\Big[x_i\Big] + \Big(E \Big[ x_i\Big]\Big)^2\bigg) \tag{12.10}\\&=Var\Big[w_{j_i}\Big]Var\Big[x_i\Big] + Var\Big[w_{j_i}\Big]\Big(E \Big[ x_i\Big]\Big)^2 + \Big(E \Big[ w_{j_i}\Big]\Big)^2 Var\Big[x_i\Big] +  \Big(E \Big[ w_{j_i}\Big]\Big)^2\Big( E \Big[ x_i \Big] \Big)^2 \tag{12.11}\end{align}


  • 식 \((12.11)\)을 식 \((12.9)\)에 대입하면 식 \((12.11)\)과 식 \((12.9)\)의 마지막 항들이 소거되어 식 \((12.12)\)가 된다.

\begin{align}Var\big[p_j\big] &= \sum_{i=1}^n \Bigg\{ Var\Big[w_{j_i}\Big]Var\Big[x_i\Big] + Var\Big[w_{j_i}\Big]\Big(E \Big[ x_i\Big]\Big)^2 + \Big(E \Big[ w_{j_i}\Big]\Big)^2 Var\Big[x_i\Big] \Bigg\} \tag{12.12}\end{align}


  • 이제 입력 데이터가 정규화가 되어 있다면 \(E[x_i]=0\)이며, 또한 weight가 이상적으로 정규분포를 따른다고 가정하면 \(E[w_{j_i}]=0\)이기 때문에 식 \((12.12)\)는 식 \((12.15)\)가 된다.

\begin{align}Var\big[p_j\big] &= \sum_{i=1}^n \Bigg\{ Var\Big[w_{j_i}\Big]Var\Big[x_i\Big] + Var\Big[w_{j_i}\Big]\Big(\not{E} \Big[ x_i\Big]\Big)^2 + \Big(\not{E} \Big[ w_{j_i}\Big]\Big)^2 Var\Big[x_i\Big]\Bigg\}\tag{12.13}\\ &= \sum_{i=1}^n Var\Big[w_{j_i}\Big]Var\Big[x_i\Big] \tag{12.14}\\ &= n\cdot Var\Big[w_{j_i}\Big]Var\Big[x_i\Big]\tag{12.15}\end{align}


  • 식 \((12.15)\)에 따라 \(\mathbb{p}\)의 분산을 \(\mathbb{x}\)의 분산과 같게 하려면 weight \(W\)의 각 성분의 분산은 \(\frac{1}{n}\)이어야 한다. 즉 \(Var(w_j)=\frac{1}{n}\)이어야 \(Var(W)=\frac{1}{n}\)이기 때문에 식 \((12.15)\)에 의해 \(Var\big[\mathbb{p}\big] = Var\big[\mathbb{x}\big]\)이 된다.


  • \(a\)가 상수이고 \(X\)를 확률변수라고 하면 \(Var[aX]=a^2Var(X)\)이기 때문에 앞에서 언급한 것처럼 a*np.random.normal(scale=0.01, size=shape)에서 a에 어떤 값을 줘야 하는 것인가 하는 문제로 돌아가면 \(a\)는 식 \((12.16)\)이 되어야 한다.

\begin{align} a = \sqrt{\frac{1}{n}} \tag{12.16}\end{align}


  • 따라서 a*np.random.normal(scale=0.01, size=shape)는  다음과 같이 구현해야 한다.
np.sqrt(1.0 / n) * np.random.normal(size=shape)


  • 식 \((12.15)\)를 구하기 위해 몇 가지 가정을 한 상태에서 식을 전개하였다. 이 가정을 바꾸면 초기화하는 방법들을 만들어 낼 수 있다.


LeCun 등 1998년 논문에서 제시하는 초기화 방법

  • Yann LeCun, Leon Bottou, Genevieve B. Orr and Klaus-Robert Müller, Efficient BackProp, Neural Networks: Tricks of the Trade, pp. 9-50, Springer, 1998

  • 이 방법은 정규분포 또는 균등분포를 적용해 초기화하며 균등분포를 적용할 경우의 코드는 다음과 같다.
np.random.uniform(low=np.sqrt(1.0 / n),
                  high=np.sqrt(1.0 / n),
                  size=shape)


  • Keras에서는 kernel_initializer='lecun_uniform'이라는 별칭을 사용할 수 있다.


Glorot and Bengio 2010년 논문에서 제시하는 초기화 방법

  • 이 방법은 정규분포나 균등분포를 사용할 경우 각각의 웨이트 초깃값에 관해 연구한 것으로 코드로 나타내면 균등분포일 경우에는 다음과 같다.
np.random.uniform(low=np.sqrt(6.0 / (n_in + n_out)),
                  high=nq.sqrt(6.0 / (n_in + n_out)),
                  size=shape)


  • 정규분포일 경우에는 다음과 같이 구현하는 것으 좋다고 알려져있다.
np.sqrt(3.0 / (n_in + n_out)) * np.random.normal(size=shape)


  • TensorFlow에서 이 초기화 방법을 사용하려면 tf.contrib.layers.xavier_initializer(uniform=True)를 호출하면 된다.
  • Keras에서는 각각 init='glorot_uniform'init='glorot_normal'을 호출하면 된다.


He 등 2015년 논문에서 제시하는 초기화 방법

  • ReLU를 사용할 경우 어떻게 초기화하는지에 관해 설명하는데 다음과 같이 구현하는 좋다고 주장한다.
np.sqrt(2.0 / n) * np.random.normal(size=shape)


  • Keras에서는 init='he_normal' 사용할 수 있다.



출처 : 정석으로 배우는 딥러닝





'신경망(Neural Network) 스터디' 카테고리의 다른 글

14. 학습 조기 종료  (2) 2018.02.01
13. 학습률 설정  (0) 2018.01.29
11. 학습 과정 시각화  (3) 2018.01.19
10. 신경망 모델 구현 방법  (0) 2018.01.19
9. 오버피팅 문제 해결  (0) 2018.01.19

학습 과정을 시각화

  • 지금까지 수행한 결과는 정량적인 평가로 테스트 데이터에 대한 예측 정확도를 측정하였다.
  • 학습 데이터에 대해서도 오차 함수의 값이 예측 정확도을 출력하기는 했어도 이는 학습이 진행되는지만을 확인하기 위한 것이었고 어떤 학습이 진행되고 있는지 대략적인 파악만 가능했다.
  • 데이터의 규모가 커지면 검증 데이터를 사용해서 학습을 평가하는 작업도 적절한 선에서 실시해야 한다.
  • 테스트 데이터에 대한 예측 정확도는 (데이터가 1개일 경우) 결과가 하나의 수치로 표현되지만 학습 데이터 또는 검증 데이터에 관한 예측 정확도는 각 epoch마다 평가해야 하므로 여러 개의 수치를 한 눈에 볼 수 있어야 하며, 이는 숫자들을 보는 것보다는 그래프 형태로 볼 수 있으면 직관적으로 파악이 가능하다.
  • 지금까지 구현한 코드에 다음 처리 과정을 추가해보도록 한다.
    • 검증 데이터를 사용해서 학습시키고 예측한다.
    • 학습시킬 때의 예측 정확도를 시각화한다.


TensorFlow로 구현

  • 학습 데이터와 검증 데이터, 테스트 데이터를 준비하는 부분 구현
    • 전체 데이터를 학습 데이터 테스트 데이터로 나누고, 다시 학습 데이터를 학습 데이터와 학습한 모델을 평가하기 위한 검증 데이터로 나눈다.
if __name__ == '__main__':
    MNIST = datasets.fetch_mldata('MNIST original', data_home='.')

    n = len(MNIST.data)
    indices = np.random.permutation(range(n))[:N]
    X = MNIST.data[indices]
    Y = MNIST.target[indices]
    Y = np.eye(10)[Y.astype(int)]  # 1-of-K hot coding 표현으로 변환

    test_size = 0.2
    # 학습 데이터와 테스트 데이터를 80:20으로 나눈다
    X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=test_size)

    # 학습 데이터의 20%를 검증 데이터로 사용한다
    X_train, X_val, Y_train, Y_val = train_test_split(X_train, Y_train, test_size=test_size)


  • 모든 학습 데이터를 학습 데이터와 검증 데이터로 완전히 분리하고 같은 검증 데이터를 모델 평가에 사용하는 기법을 홀드아웃 검증(hold-out validation)이라고 한다.
  • 학습 데이터를 $K$개의 데이터 셋으로 나누고 그 중 하나를 검증 데이터로 사용하고 나머지 $K-1$개의 데이터를 학습에 사용하는 기법을 $K$-분할교차 검증($K$-cross validation)이라고 한다.
    • $K$-분할교차 검증에서는 $K$개 나눈 데이터 셋을 조합해서 총 $K$번의 학습과 검증을 수행한다.
    • 이 과정에서 얻어진 예측 정확도의 평균을 모델의 성능으로 간주한다.
    • $K$-분할교차 검증이 일반화 성능을 매우 엄밀하게 평가할 수 있지만 딥러닝에서는 모델을 학습시키는데 방대한 시간이 걸릴 경우가 많기 때문에 $K$-분할교차 검증은 잘 사용되지 않는다.


  • 검증 데이터에 대한 오차나 예측 정확도는 각 epoch마다 평가해야 하기 때문에 모델을 학습시키는 부분은 다음과 같이 구현한다.
for epoch in range(epoch):
    X_, Y_ = shuffle(x_train, y_train)
    
    for i in range(n_batches):
        start = i * batch_size
        end = start + batch_size
        
        sess.run(train, feed_dict={
            x: X_[start:end],
            y: Y_[start:end],
            keeP_prob: p_keep
        })
        
    # 검증 데이터를 사용해 평가한다
    val_loss = loss.eval(session=sess, feed_dict={
        x: x_val,
        y: v_val,
        keep_prob: 1.0
    })
    
    val_acc = accuracy.eval(session=sess, feed_dict={
        x: x_val,
        y: y_val,
        keep_prob: 1.0
    })


  • 검증 데이터에 대한 손실을 나타내는 val_loss와 예측 정확도를 나타내는 val_acc를 각 epoch마다 출력해도 학습이 진행되는 상황을 확인할 수 있지만 시각화를 위해 저장해둘 변수를 설정한다. 프로그램 선언부에 명시한다.
history = {
    'val_loss' = [],
    'val_acc' = []
}


  • 이제 각 epoch마다 나오는 val_lossval_acc를 추가해 저장한다.
    # 학습 진행 상황 데이터를 저장할 변수 설정
    history = {'val_loss': [],
               'val_acc': []}
    
    for epoch in range(epochs):
        X_, Y_ = shuffle(X_train, Y_train)

        for i in range(n_batches):

            start = i * batch_size
            end = start + batch_size

            sess.run(train, feed_dict={x: X_[start: end], y: Y_[start: end], keep_prob: p_keep})

        # 검증 데이터를 사용해 정확도 측정
        val_loss = loss.eval(session=sess, feed_dict={x: X_val
                                                      y: Y_val,
                                                      keep_prob: 1.0})

        val_accuracy = accuracy.eval(session=sess, feed_dict={x: X_val,
                                                              y: Y_val,
                                                              keep_prob: 1.0})

        # 검증 데이터에 대한 학습 진행 상황을 저장
        history['val_loss'].append(val_loss)
        history['val_acc'].append(val_accuracy)


matplotlib 패키지를 이용해 그래프 그리기

  • 검증 데이터의 정확도를 그래프로 그리자
import matplotlib.pyplot as plt
import matplotlib

    # 폰트 설정
    font_name = matplotlib.font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name()
    matplotlib.rc('font', family=font_name)
    # 도화지 생성
    fig = plt.figure()
    # 정확도 그래프 그리기
    plt.plot(range(epochs), history['val_acc'], label='Accuracy', color='darkred')
    # 축 이름
    plt.xlabel('epochs')
    plt.ylabel('검증 정확도(%)')
    plt.title('epoch에 따른 검증 데이터에 대한 정확도 그래프')
    plt.grid(linestyle='--', color='lavender')
    # 그래프 표시
    plt.show()
    # 그래프 저장
    plt.savefig('mnist_tensorflow_acc.png')


  • 전체 코드
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.utils import shuffle
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
import matplotlib

def inference(x, keep_prob, n_in, n_hiddens, n_out):
    def weight_variable(shape):
        initial = tf.truncated_normal(shape, stddev=0.01)
        return tf.Variable(initial)

    def bias_variable(shape):
        initial = tf.zeros(shape)
        return tf.Variable(initial)

    # 입력층-은닉층과 은닉층-은닉층 설계
    for i, n_hidden in enumerate(n_hiddens):
        if i == 0:  # 입력층-은닉층
            input = x
            input_dim = n_in
        else:   # 은닉층-은닉층
            input = output  # 출력층이 다음 입력층이 된다
            input_dim = n_hiddens[i-1]

        W = weight_variable([input_dim, n_hidden])  # weight의 shape은 입력층 차원(784) x 뉴런의 개수
        b = bias_variable([n_hidden])               # bias의 shape은 뉴런의 개수

        hidden_layer = tf.nn.relu(tf.matmul(input, W) + b)  # 은닉층의 활성화 함수로 ReLU를 사용
        output = tf.nn.dropout(hidden_layer, keep_prob=keep_prob)   # 출력은 드롭아웃을 적용

    # 은닉층-출력층 설계
    W_out = weight_variable([n_hiddens[-1], n_out]) # 출력층의 weight shape은 뉴런의 개수 x 출력층의 차원(10개)
    b_out = bias_variable([n_out])                  # bias의 shape은 출력층의 차원

    hypothesis = tf.nn.softmax(tf.matmul(output, W_out) + b_out)    # 출력 활성화 함수는 softmax를 사용

    return hypothesis

def loss(hypothesis, y):
    # cross entropy
    cross_entropy = tf.reduce_mean(-tf.reduce_sum(y * tf.log(hypothesis), reduction_indices=[1]))

    return cross_entropy

def train(loss):
    optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01)
    train = optimizer.minimize(loss)

    return train

def accuracy(hypothesis, y):
    correct_prediction = tf.equal(tf.argmax(hypothesis, 1), tf.argmax(y, 1))
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

    return accuracy


if __name__ == '__main__':
    MNIST = datasets.fetch_mldata('MNIST original', data_home='.')

    n = len(MNIST.data)
    N = int(n * 1.0)
    indices = np.random.permutation(range(n))[:N]
    X = MNIST.data[indices]
    Y = MNIST.target[indices]
    Y = np.eye(10)[Y.astype(int)]  # 1-of-K hot coding 표현으로 변환

    test_size = 0.2
    # 학습 데이터와 테스트 데이터를 80:20으로 나눈다
    X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=test_size)

    # 학습 데이터의 20%를 검증 데이터로 사용한다
    X_train, X_val, Y_train, Y_val = train_test_split(X_train, Y_train, test_size=test_size)

    # 2. 모델을 설정한다
    # 입출력 데이터와 은닉층의 뉴런 개수 설정
    n_in = len(X[0])  # 입력 데이터 이미지의 크기 28x28(=784)
    n_hiddens = [200, 200, 200]  # 각 은닉층의 뉴런 개수
    n_out = len(Y[0])  # 출력 데이터의 개수 (0~9)
    p_keep = 0.5        # 드롭아웃 확률의 비율

    x = tf.placeholder(tf.float32, shape=[None, n_in])
    y = tf.placeholder(tf.float32, shape=[None, n_out])

    keep_prob = tf.placeholder(tf.float32)

    # 가설식을 그래프에 추가
    hypothesis = inference(x, keep_prob, n_in=n_in, n_hiddens=n_hiddens, n_out=n_out)

    # 오차 함수를 그래프에 추가
    loss = loss(hypothesis, y)

    # 학습 함수를 그래프에 추가
    train = train(loss)

    # 정확도 예측 함수 그래프에 추가
    accuracy = accuracy(hypothesis, y)

    # 3. 모델을 학습시키면서 그래프를 실행
    epochs = 50
    batch_size = 200

    init = tf.global_variables_initializer()
    sess = tf.Session()
    sess.run(init)

    n_batches = len(X_train) // batch_size

    # 학습 진행 상황 데이터를 저장할 변수 설정
    history = {'val_loss': [],
               'val_acc': []}

    for epoch in range(epochs):
        X_, Y_ = shuffle(X_train, Y_train)

        for i in range(n_batches):

            start = i * batch_size
            end = start + batch_size

            sess.run(train, feed_dict={x: X_[start: end], y: Y_[start: end], keep_prob: p_keep})

        # 검증 데이터를 사용해 정확도 측정
        val_loss = loss.eval(session=sess, feed_dict={x: X_val,
                                                      y: Y_val,
                                                      keep_prob: 1.0})

        val_accuracy = accuracy.eval(session=sess, feed_dict={x: X_val,
                                                              y: Y_val,
                                                              keep_prob: 1.0})

        # 검증 데이터에 대한 학습 진행 상황을 저장
        history['val_loss'].append(val_loss)
        history['val_acc'].append(val_accuracy * 100)

        print(f'epoch : {epoch:03}, Validation loss : {val_loss:.7}, Validation accuracy : {val_accuracy*100:.6} %')

    # 4. 모델을 평가한다
    accuracy_rate = accuracy.eval(session=sess, feed_dict={x: X_test, y: Y_test, keep_prob: 1.0})
    print(f'Test Accuracy : {accuracy_rate*100:.6} %')

    # 폰트 설정
    font_name = matplotlib.font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name()
    matplotlib.rc('font', family=font_name)
    # 도화지 생성
    fig = plt.figure()
    # 정확도 그래프 그리기
    plt.plot(range(epochs), history['val_acc'], label='Accuracy', color='darkred')
    # 축 이름
    plt.xlabel('epochs')
    plt.ylabel('검증 정확도(%)')
    plt.title('epoch에 따른 검증 데이터에 대한 정확도 그래프')
    plt.grid(linestyle='--', color='lavender')
    # 그래프 표시
    plt.show()
    plt.savefig('mnist_tensorflow_acc.png')


  • 코드를 실행하면 그림 $11.1$과 같은 그래프를 볼 수 있다

그림 11.1 epoch에 따른 검증 정확도 변화


  • 그림 $11.1$을 보면 검증 정확도가 점점 올라갔다가 오느 시점부터는 전혀 학습이 이루어지지 않는다는 것을 알 수 있다.
  • 이는 경사 소실 문제와 마찬가지로 softmax 함수(sigmoid 함수)를 통해 전달되는 경사값이 너무 작아져서 $0$처럼 취급되어 계산되는 것이 원인이다.
  • 이 문제 해결을을 위해서 오차 함수 식을 다음과 같이 수정해야만 한다.
    • 계산에 사용되는 하한값(또는 상한값)을 새롭게 추가된 tf.clip_by_value()가 결정한다.
    • 이 하한값을 1e-10$(=10^{-10})$과 같이 작은 값으로 설정해두면 학습을 계산하는데  악영향을 주지 않으면서 $0$으로 나누는 문제를 방지할 수 있다.
def loss(hypothesis, y):
    # cross entropy
    cross_entropy = tf.reduce_mean(-tf.reduce_sum(y * tf.log(tf.clip_by_value(hypothesis, 1e-10, 1.0)),
                                                  reduction_indices=[1]))


  • 오차 함수 식을 수정한 결과 출력된 그래프는 그림 $11.2$와 같다.

그림 11.2 epoch에 따른 검증 정확도 변화(오차 함수 수정 후)


  • 오차도 함께 그래프에 표시해보자

# 폰트 설정 font_name = matplotlib.font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name() matplotlib.rc('font', family=font_name) # 도화지 생성 fig = plt.figure() # 큰 도화지(fig)를 1x1로 나눈 도화지에 첫 번째 칸에 ax_acc 그래프를 추가한다 ax_acc = fig.add_subplot(111) # 정확도 그래프 그리기 ax_acc.plot(range(epochs), history['val_acc'], label='정확도(%)', color='darkred') # 축 이름 plt.text(3, 14.7, "<----------------정확도(%)", verticalalignment='top', horizontalalignment='right') plt.xlabel('epochs') plt.ylabel('정확도(%)') ax_acc.grid(linestyle='--', color='lavender') # ax_loos 그래프를 x축을 동일하게 사용하여 겹쳐 그리기 선언 ax_loss = ax_acc.twinx() ax_loss.plot(range(epochs), history['val_loss'], label='오차', color='darkblue') plt.text(3, 2.2, "<----------------오차", verticalalignment='top', horizontalalignment='left') plt.ylabel('오차') ax_loss.yaxis.tick_right() ax_loss.grid(linestyle='--', color='lavender') # 그래프 표시 plt.show() plt.legend() plt.savefig('mnist_tensorflow_acc.png')


  • 정확도와 오차를 함께 그린 그래프는 $11.3$과 같다.

그림 11.2 epoch에 따른 검증 정확도와 오차를 함께 그린 그래프



Keras로 구현

  • Keras에서는 model.fit()의 반환값에 학습이 진행된 정도를 나타내는 값이 저장된 결과가 들어있다.
  • 검증 데이터를 사용해서 학습시키는 경우에는 학습시킬 때 다음과 같이 validation_data=를 사용하면 hist 변수에 오차와 정확도가 저장된다.
hist = model.fit(X_train, Y_train, epochs=epochs, batch_size=batch_size, validation_data=(X_val, Y_val))

val_loss = hist.history['val_loss']
val_acc = hist.history['val_acc']


  • 정확도를 그래프로 그리면 그림 $11.3$과 같다.
epochs = 50
batch_size = 200

hist = model.fit(X_train, Y_train, epochs=epochs, batch_size=batch_size, validation_data=(X_val, Y_val))
val_loss = hist.history['val_loss']
val_acc = hist.history['val_acc']

font_name = matplotlib.font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name()
matplotlib.rc('font', family=font_name)

fig = plt.figure()
ax_acc = fig.add_subplot(111)

ax_acc.plot(range(epochs), val_acc, label='정확도(%)', color='darkred')
plt.text(3, 14.7, "<----------------정확도(%)", verticalalignment='top', horizontalalignment='right')
plt.xlabel('epochs')
ax_acc.grid(linestyle='--', color='lavender')

plt.show()


그림 11.3 Keras로 그린 epoch에 따른 검증 정확도 그래프


  • 그림 $11.3$도 어느 순간부터 학습이 제대로 진행되지 않는 것을 알 수 있다. 그 이유는 매개변수를 초기화하는 부분에 있다.
  • TensorFlow에서는 weight를 초기화하기 위하여 tf.truncated_normal(shape, stddev=0.01) 함수를 사용하였지만 Keras에서는 아무 것도 사용하지 않아 학습이 제대로 진행되지 않는 것이다.
  • weight의 초기값을 어떻게 결정하는지가 학습이 제대로 이루어지는지 아니면 실패하는지에 영향을 준다. 초기값을 설정하는 몇 가지 기법이 연구가 된 상태이다.
  • Keras에서는 Dense() 함수에 kernel_initializer=라는 매개변수가 초기값을 결정하는데 영향을 준다. 매개변수를 사용하는 방법은 다음과 같다.
    • Keras에서 미리 alias로 정의되어 있는 것 이외의 것을 사용할 때에는 keras.backend를 통해 처리를 수행한다.
    • weight_variable() 함수 내부에서 K.truncated_normal(shape, stddev=0.01)이 TensorFlow에서 구현했을 때처럼 표준편차가 $0.01$인 절단 정규분포를 따르는 난수를 반환한다.
    • Dense(kernel_initializer=weight_variable) 함수가 실행되면 TensorFlow에서 실행되었던 것처럼 초기값을 가진 weight를 생성한다.
from keras import backend as K

def weight_variable(shape):
    return K.truncated_normal(shape, stddev=0.01)
model = Sequential()

for i, input_dim in enumerate(([n_in] + n_hiddens)[:-1]):
    model.add(Dense(input_dim=input_dim, units=n_hiddens[i], kernel_initializer=weight_variable))
    model.add(LeakyReLU(alpha=alpha))
    model.add(Dropout(p_keep))

model.add(Dense(units=n_out, kernel_initializer=weight_variable))
model.add(Activation('softmax'))


  • 절단정규분포는 keras.initializersTruncatedNormal() 함수를 통해 사용할 수도 있다.
    • 이 때 weight_variable()은 정의하지 않아도 된다.
from keras.initializers import TruncatedNormal

model.add(Dense(units=n_out, kernel_initializer=TruncatedNormal(stddev=0.01)))


  • Keras에서 NumPy로 생성한 난수를 그대로 사용해도 된다.
    • 이 때 weight_variable()은 다음과 같이 정의한다.
def weight_variable(shape):
return np.random.normal(scale=0.01, size=shape)


  • 전체 코드를 실행하면 그림 $11.4$와 같은 그래프를 얻을 수 있다.
from sklearn import datasets
from sklearn.model_selection import train_test_split
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Activation, Dropout
from keras.optimizers import SGD
from keras import backend as K
from keras.layers.advanced_activations import LeakyReLU
import matplotlib.pyplot as plt
import matplotlib

MNIST = datasets.fetch_mldata('MNIST original', data_home='.')

n = len(MNIST.data)
N = int(n * 1.00)  # 70,000개의 데이터를 모두 사용
test_size = 0.2

# 학습 데이터와 테스트 데이터를 나누기 전에 입력 데이터를 무작위로 섞는다
indices = np.random.permutation(range(n))[:N]
X = MNIST.data[indices]
Y = MNIST.target[indices]
Y = np.eye(10)[Y.astype(int)]  # 1-of-K hot coding 표현으로 변환

# 학습 데이터와 테스트 데이터를 80:20으로 나눈다
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=test_size)
X_train, X_val, Y_train, Y_val = train_test_split(X_train, Y_train, test_size=test_size)

n_in = len(X[0])  # 입력 데이터 이미지의 크기 28x28(=784)
n_hiddens = [200, 200, 200]  # 각 은닉층의 뉴런 개수
n_out = len(Y[0])  # 출력 데이터의 개수 (0~9)
alpha = 0.01
p_keep = 0.5        # 드롭아웃 확률의 비율

def weight_variable(shape):
    return K.truncated_normal(shape, stddev=0.01)

model = Sequential()

for i, input_dim in enumerate(([n_in] + n_hiddens)[:-1]):
    model.add(Dense(input_dim=input_dim, units=n_hiddens[i], kernel_initializer=weight_variable))
    model.add(LeakyReLU(alpha=alpha))
    model.add(Dropout(p_keep))

model.add(Dense(units=n_out, kernel_initializer=weight_variable))
model.add(Activation('softmax'))

model.compile(loss='categorical_crossentropy', optimizer=SGD(lr=0.01), metrics=['accuracy'])

epochs = 50
batch_size = 200

hist = model.fit(X_train, Y_train, epochs=epochs, batch_size=batch_size, validation_data=(X_val, Y_val))
val_loss = hist.history['val_loss']
val_acc = hist.history['val_acc']

font_name = matplotlib.font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name()
matplotlib.rc('font', family=font_name)

fig = plt.figure()
ax_acc = fig.add_subplot(111)

ax_acc.plot(range(epochs), val_acc, label='정확도(%)', color='darkred')
plt.text(3, 14.7, "<----------------정확도(%)", verticalalignment='top', horizontalalignment='right')
plt.xlabel('epochs')
plt.ylabel('정확도(%)')
ax_acc.grid(linestyle='--', color='lavender')

ax_loss = ax_acc.twinx()
ax_loss.plot(range(epochs), val_loss, label='오차', color='darkblue')
plt.text(3, 2.2, "<----------------오차", verticalalignment='top', horizontalalignment='left')
plt.ylabel('오차')
ax_loss.yaxis.tick_right()
ax_loss.grid(linestyle='--', color='lavender')

plt.legend()
plt.show()
ax_acc.legend()
plt.savefig('mnist_keras_plot.png')

evaluation = model.evaluate(X_test, Y_test)
print(f'\nloss : {evaluation[0]}, accuracy : {evaluation[1]*100:.6} %')


그림 11.4 weight를 초기화시켜 학습시킨 모델의 그래프



출처 : 정석으로 배우는 딥러닝

신경망 모델 코드로 구현하기

  • 딥러닝 모델 구현 시, 1개의 층을 코드로 작성하면 다음과 같다.
    • TensorFlow
W = tf.Variable(tf.truncated_normal([n_in, n_hidden], stddev=0.01))
b = tf.Variable(tf.zeros([n_hidden]))
h = tf.nn.relu(tf.matmul(x, W) + b)
h_drop = tf.nn.dropout(h, keep_prob)
    • Keras
model = Sequential()
model.add(Dense(input_dim=n_in, units=n_hidden))
model.add(Activation('relu'))
model.add(Dropout(0.5))
  • 딥러닝 모델에서 층을 늘려갈 떄에는 위의 코드를 반복적으로 사용하여 구현하였다.


효율적으로 신경망 모델 구현하기

  • 층의 개수를 더 늘리고 싶거나 모델의 일부분을 변경(예를 들어 활성화 함수를 다른 것으로 변경)하고 싶을 때 유연하게 적용할 수 있도록 신경망 모델을 설계해보자

TensorFlow 설계 방법

  • 신경망 모델을 정의하는 전체적인 과정은 그림 10.1과 같다.

그림 10.1 드롭아웃이 적용된 신경망의 예

  • TensorFlow에서는 위의 과정을 단계별로 함수화할 것을 권장하고 있으며 각각 inference(), loss(), training() 함수로 정의해 사용해야 한다.
    • inference() : 모델 전체를 설정하고 모델의 출력(예측 결과)를 반환하는 함수
    • loss() : 모델의 오차 함수를 정의하고 오차를 반환하는 함수
    • training() : 모델을 학습시키고 학습시킨 결과(진행 상황)을 반환하는 함수
  • 전체적인 코드의 구성은 다음과 같다.
def inference(x):
    # 모델을 정의

def loss(hypothesis, y):
    # 오차함수를 정의

def training(loss):
    # 학습 알고리즘을 정의

if __name__ == '__main__':
    # 1. 데이터를 준비한다
    # 2. 모델을 설정한다
        hypothesis = inference(x)
        loss = loss(hypothesis, y)

    # 3. 모델을 학습시킨다
        train = training(loss)

    # 4. 모델을 평가한다


  • hypohesis = inference(x) 함수 설계
    • 지금까지 각 층을 h0, h1 등으로 함수를 정의했지만 이제는 함수들을 한꺼번에 정의하기 위해 각 층에 있는 뉴런의 개수를 인수로 받게 만들어야 한다.
    • 드롭아웃을 지원하기 위해 x 이외에 keep_prob 변수를 인수로 받아야 하기 때문에 다음과 같이 코드를 작성한다.
def inference(x, keep_prob, n_in, n_hiddens, n_out):
    # 모델을 정의

if __name__ == '__main__':
    # 1. 데이터를 준비한다
    MNIST = datasets.fetch_mldata('MNIST original', data_home='.')

    n = len(MNIST.data)
    N = int(n * 1.00)  # 70,000개의 데이터를 모두 사용
    N_train = int(n * 0.2)
    N_val = int(N_train * 0.2)
    # 학습 데이터와 테스트 데이터를 나누기 전에 입력 데이터를 무작위로 섞는다
    indices = np.random.permutation(range(n))[:N]
    X = MNIST.data[indices]
    Y = MNIST.target[indices]
    Y = np.eye(10)[Y.astype(int)]  # 1-of-K hot coding 표현으로 변환

    # 학습 데이터와 테스트 데이터를 80:20으로 나눈다
    X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=N_train)

    X_train, X_val, Y_train, Y_val = train_test_split(X_train, Y_train, test_size=N_val)

    # 2. 모델을 설정한다
    # 입출력 데이터와 은닉층의 뉴런 개수 설정
    n_in = len(X[0])  # 입력 데이터 이미지의 크기 28x28(=784)
    n_hiddens = [200, 200, 200]  # 각 은닉층의 뉴런 개수
    n_out = len(Y[0])  # 출력 데이터의 개수 (0~9)
    p_keep = 0.5        # 드롭아웃 확률의 비율

    x = tf.placeholder(tf.float32, shape=[None, n_in])
    y = tf.placeholder(tf.float32, shape=[None, n_out])

    keep_prob = tf.placeholder(tf.float32)

    # 가설식을 그래프에 추가
    hypothesis = inference(x, keep_prob, n_in=n_in, n_hiddens=n_hiddens, n_out=n_out)
    • 입력층에서 출력층의 바로 앞 층까지는 모든 출력을 같은 식으로 나타낼 수 있지만 출력층만은 활성화 함수가 소프트맥스 함수(또는 시그모이드 함수)로 지정돼 있어 드음과 같이 코드를 작성한다.
      • weight_variable()bias_variable()을 정의해, 웨이트와 바이어스를 초기화 처리를 한다.
      • for 문의 내부에서 '입력층 - 은닉층'과 '은닉층-은닉층'은 각각 처리가 다르지만 기본적으로 이전 층의 output이 다음 층의 input이 되는 구조를 구현
def inference(x, keep_prob, n_in, n_hiddens, n_out):
    def weight_variable(shape):
        initial = tf.truncated_normal(shape, stddev=0.01)
        return tf.Variable(initial)

    def bias_variable(shape):
        initial = tf.zeros(shape)
        return tf.Variable(initial)

    # 입력층-은닉층과 은닉층-은닉층 설계
    for i, n_hidden in enumerate(n_hiddens):
        if i == 0:  # 입력층-은닉층
            input = x
            input_dim = n_in
        else:   # 은닉층-은닉층
            input = output  # 출력층이 다음 입력층이 된다
            input_dim = n_hiddens[i-1]

        W = weight_variable([input_dim, n_hidden])  # weight의 shape은 입력층 차원(784) x 뉴런의 개수
        b = bias_variable([n_hidden])               # bias의 shape은 뉴런의 개수

        hidden_layer = tf.nn.relu(tf.matmul(input, W) + b)  # 은닉층의 활성화 함수로 ReLU를 사용
        output = tf.nn.dropout(hidden_layer, keep_prob=keep_prob)   # 출력은 드롭아웃을 적용

    # 은닉층-출력층 설계
    W_out = weight_variable([n_hiddens[-1], n_out]) # 출력층의 weight shape은 뉴런의 개수 x 출력층의 차원(10개)
    b_out = bias_variable([n_out])                  # bias의 shape은 출력층의 차원

    hypothesis = tf.nn.softmax(tf.matmul(output, W_out) + b_out)    # 출력 활성화 함수는 softmax를 사용
    
    return hypothesis


  • loss(hypothesis, y) 함수와 training(loss) 함수 설계
def loss(hypothesis, y):
    cross_entropy = tf.reduce_sum(-tf.reduce_sum(y * tf.log(hypothesis), reduction_indices=[1]))

    return cross_entropy

def training(loss):
    optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01)
    train = optimizer.minimize(loss)

    return train


  • 모델 평가를 위한 accuracy(hypothesis, y) 함수 설계
def accuracy(hypothesis, y):
    correct_prediction = tf.equal(tf.argmax(hypothesis, 1), tf.argmax(y, 1))
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
    
    return accuracy


  • 전체 코드
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.utils import shuffle
import numpy as np
import tensorflow as tf

def inference(x, keep_prob, n_in, n_hiddens, n_out):
    def weight_variable(shape):
        initial = tf.truncated_normal(shape, stddev=0.01)
        return tf.Variable(initial)

    def bias_variable(shape):
        initial = tf.zeros(shape)
        return tf.Variable(initial)

    # 입력층-은닉층과 은닉층-은닉층 설계
    for i, n_hidden in enumerate(n_hiddens):
        if i == 0:  # 입력층-은닉층
            input = x
            input_dim = n_in
        else:   # 은닉층-은닉층
            input = output  # 출력층이 다음 입력층이 된다
            input_dim = n_hiddens[i-1]

        W = weight_variable([input_dim, n_hidden])  # weight의 shape은 입력층 차원(784) x 뉴런의 개수
        b = bias_variable([n_hidden])               # bias의 shape은 뉴런의 개수

        hidden_layer = tf.nn.relu(tf.matmul(input, W) + b)  # 은닉층의 활성화 함수로 ReLU를 사용
        output = tf.nn.dropout(hidden_layer, keep_prob=keep_prob)   # 출력은 드롭아웃을 적용

    # 은닉층-출력층 설계
    W_out = weight_variable([n_hiddens[-1], n_out]) # 출력층의 weight shape은 뉴런의 개수 x 출력층의 차원(10개)
    b_out = bias_variable([n_out])                  # bias의 shape은 출력층의 차원

    hypothesis = tf.nn.softmax(tf.matmul(output, W_out) + b_out)    # 출력 활성화 함수는 softmax를 사용

    return hypothesis

def loss(hypothesis, y):
    # cross entropy
    cross_entropy = tf.reduce_mean(-tf.reduce_sum(y * tf.log(hypothesis), reduction_indices=[1]))

    return cross_entropy

def train(loss):
    optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01)
    train = optimizer.minimize(loss)

    return train

def accuracy(hypothesis, y):
    correct_prediction = tf.equal(tf.argmax(hypothesis, 1), tf.argmax(y, 1))
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

    return accuracy

if __name__ == '__main__':
    # 1. 데이터를 준비한다
    MNIST = datasets.fetch_mldata('MNIST original', data_home='.')

    n = len(MNIST.data)
    N = int(n * 1.00)  # 70,000개의 데이터를 모두 사용
    N_train = int(n * 0.2)
    N_val = int(N_train * 0.2)
    # 학습 데이터와 테스트 데이터를 나누기 전에 입력 데이터를 무작위로 섞는다
    indices = np.random.permutation(range(n))[:N]
    X = MNIST.data[indices]
    Y = MNIST.target[indices]
    Y = np.eye(10)[Y.astype(int)]  # 1-of-K hot coding 표현으로 변환

    # 학습 데이터와 테스트 데이터를 80:20으로 나눈다
    X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=N_train)

    X_train, X_val, Y_train, Y_val = train_test_split(X_train, Y_train, test_size=N_val)

    # 2. 모델을 설정한다
    # 입출력 데이터와 은닉층의 뉴런 개수 설정
    n_in = len(X[0])  # 입력 데이터 이미지의 크기 28x28(=784)
    n_hiddens = [200, 200, 200]  # 각 은닉층의 뉴런 개수
    n_out = len(Y[0])  # 출력 데이터의 개수 (0~9)
    p_keep = 0.5        # 드롭아웃 확률의 비율

    x = tf.placeholder(tf.float32, shape=[None, n_in])
    y = tf.placeholder(tf.float32, shape=[None, n_out])

    keep_prob = tf.placeholder(tf.float32)

    # 가설식을 그래프에 추가
    hypothesis = inference(x, keep_prob, n_in=n_in, n_hiddens=n_hiddens, n_out=n_out)

    # 오차 함수를 그래프에 추가
    loss = loss(hypothesis, y)

    # 학습 함수를 그래프에 추가
    train = train(loss)

    # 정확도 예측 함수 그래프에 추가
    accuracy = accuracy(hypothesis, y)

    # 3. 모델을 학습시키면서 그래프를 실행
    epochs = 50
    batch_size = 200

    init = tf.global_variables_initializer()
    sess = tf.Session()
    sess.run(init)

    n_batches = N_train // batch_size

    for epoch in range(epochs):
        X_, Y_ = shuffle(X_train, Y_train)

        for i in range(n_batches):

            start = i * batch_size
            end = start + batch_size

            sess.run(train, feed_dict={x: X_[start: end], y: Y_[start: end], keep_prob: p_keep})

        accuracy_rate = accuracy.eval(session=sess, feed_dict={x: X_val, y: Y_val, keep_prob: p_keep})
        print(f'Validation accuracy rate per epoch: {accuracy_rate}')

    # 4. 모델을 평가한다
    accuracy_rate = accuracy.eval(session=sess, feed_dict={x: X_test, y: Y_test, keep_prob: 1.0})
    print(f'Test Accuracy : {accuracy_rate}')


Keras 설계 방법

  • Keras는 단순하게 구현하려고 사용하는 것이기 때문에 TensorFlow에서 했던 것처럼 구현 방침같은 것이 정해져 있지 않다.
  • Keras에서는 각 층을 한꺼번에 정의할 수 있기 때문에 모델을 설정하는 전체적인 코드는 다음과 같다.
    • for 문의 내부에서 ([n_in] + n_hiddens)[:-1]을 반복하여 출력층 바로 앞까지의 각 층의 입력과 출력의 차원을 Dense()에 넘겨준다.
    • '입력층 - 은닉층'과 '은닉층-은닉층'은 각각 처리가 다르지만 기본적으로 이전 층의 output이 다음 층의 input이 되는 구조를 구현
from sklearn import datasets
from sklearn.model_selection import train_test_split
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Activation, Dropout
from keras.optimizers import SGD

MNIST = datasets.fetch_mldata('MNIST original', data_home='.')

n = len(MNIST.data)
N = int(n * 1.00)  # 70,000개의 데이터를 모두 사용
N_train = int(n * 0.2)
N_val = int(N_train * 0.2)

# 학습 데이터와 테스트 데이터를 나누기 전에 입력 데이터를 무작위로 섞는다
indices = np.random.permutation(range(n))[:N]
X = MNIST.data[indices]
Y = MNIST.target[indices]
Y = np.eye(10)[Y.astype(int)]  # 1-of-K hot coding 표현으로 변환

# 학습 데이터와 테스트 데이터를 80:20으로 나눈다
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=N_train)


n_in = len(X[0])  # 입력 데이터 이미지의 크기 28x28(=784)
n_hiddens = [200, 200]  # 각 은닉층의 뉴런 개수
n_out = len(Y[0])  # 출력 데이터의 개수 (0~9)
activation = 'relu'
p_keep = 0.5        # 드롭아웃 확률의 비율

model = Sequential()

for i, input_dim in enumerate(([n_in] + n_hiddens)[:-1]):
    model.add(Dense(input_dim=input_dim, units=n_hiddens[i]))
    model.add(Activation(activation))
    model.add(Dropout(p_keep))

model.add(Dense(units=n_out))
model.add(Activation(activation))

model.compile(loss='categorical_crossentropy', optimizer=SGD(lr=0.01), metrics=['accuracy'])

epochs = 50
batch_size = 200

model.fit(X_train, Y_train, epochs=epochs, batch_size=batch_size)
loss_and_metrics = model.evaluate(X_test, Y_test)
print(f'\nLoss : {loss_and_metrics[0]:6}')
print(f'Accuracy : {loss_and_metrics[1]*100:.7}%')


TensorFlow에서 신경망 모델을 클래스로 설계하기

  • TensorFlow에서는 각각의 과정을 inference(), loss(), training()이라는 함수 3개로 분리했기 떄문에 구현하기가 쉬워졌다.
  • 모델을 실제로 학습을 시키는 부분은 이 3개의 함수에 포함되어 있지 않기 때문에 main 함수 부분에 코드로 작성할 내용이 많아질 수 있기 때문에 학습시키는 부분도 포함해 모든 것을 하나의 클래스로 정리해놓으면 아래 코드처럼 Keras와 비슷하게 코딩을 할 수 있다.
model = DNN()
model.fit(x_train, y_train)
model.evaluate(x_test, y_test)


  • 클래스를 정의하는 전체적인 코드는 다음과 같다.
class DNN(objec):
    def __init__(self):
        # 초기화 처리

    def weight_variable(self, shape):
        initial = tf.truncated_normal(shape, stddev=0.01)
        return tf.Variable(initial)

    def bias_variable(self, shape):
        initial = tf.zeros(shape)
        return tf.Variable(initial)

    def inference(self, x, keep_prob):
        # 모델을 정의한다
        return hypothesis

    def loss(self, hypothesis, y):
        cross_entropy = tf.reduce_mean(-tf.reduce_sum(y * tf.log(hypothesis), reduction_indices=[1]))
        return cross_entropy
    
    def training(self, loss):
        optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01)
        train = optimizer.minimize(loss)
        return train
    
    def accuracy(self, hypothesis, y):
        correct_prediction = tf.equal(tf.argmax(hypothesis, 1), tf.argmax(y, 1))
        accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
        return accuracy
    
    def fit(self, x_train, y_train):
        # 학습 처리

    def evaluate(self, x_test, y_test):
        # 예측 결과 처리


신경망 모델 초기화

  • 신경망 모델 초기화 부분에서는 모델의 구성을 결정하는 것이 바람직하므로 각 층의 차원을 매개변수로 받아야 한다.
  • 모델의 구성도 결정해야 하기 때문에 각 층에 관련된 weight와 bias로 정의한다.
class DNN(objec):
    def __init__(self, n_in, n_hiddens, n_out):
        self.n_in = n_in
        self.n_hiddens = n_hiddens
        self.n_out = n_out
        self.weight = []
        self.bias = []


inference() 함수 정의

  • 초기화 부분에 따라 inference() 함수는 다음과 같이 구현할 수 있다. 기존 코드에서는 각 층의 차원 수를 매개변수를 통해 받아들였지만 self.n_in 등을 통해 대체할 수 있게 되었다.
class DNN(objec):
    def inference(self, x, keep_prob):
        # 입력층 - 은닉층, 은닉층 - 입력층
        for i, n_hidden in enumerate(self.n_hiddens):
            if i == 0:
                input = x
                input_dim = self.n_in
            else:
                input = output
                input_dim = self.n_hiddens[i-1]

        self.weight.append(self.weight_variable([input_dim, n_hidden]))
        self.bias.append(self.bias_variable([n_hidden]))

        h = tf.nn.relu(tf.matmul(input, self.weight[-1]) + self.bias[-1])
        output = tf.nn.dropout(hypothesis, keep_prob)

        # 은닉층 - 출력층
        self.weight.append(self.weight_variable([self.n_hiddens[-1], self.n_out]))
        self.bias.append(self.bias_variable([self.n_out]))

        hypothesis = tf.nn.softmax(tf.matmul(output, self.weight[-1]) + self.bias[-1])

        return hypothesis


fit() 함수 정의

  • Keras처럼 학습 데이터와 epoch 수, batch size 등 매개변수를 받아들일 수 있어야 한다. 또한 dropout 변수도 사용할 수 있어야 한다.
class DNN(objec):
    def fit(self, X_train, Y_train, epochs=100, batch_size=100, p_keep=0.5, verbose=1):
        # 학습처리
        x = tf.placeholder(tf.float32, shape=[None, self.n_in])
        y = tf.placeholder(tf.float32, shape=[None, self.n_out])
        keep_prob = tf.placeholder(tf.float32)

        # 예측을 위한 evauate() 함수용으로 작성해둔다
        self._x = x
        self._y = y
        self._keep_prob = keep_prob

        # 학습을 위한 오차 함수 및 예측 정확도 함수 그래프 생성
        hypothesis = self.inference(x, keep_prob)
        loss = self.loss(hypothesis, y)
        train = self.train(loss)
        # evaluate() 함수에서 참조할 수 있도록 함
        self.accuracy = self.accuracy(hypothesis, y)

        # 그래프 초기화
        init = tf.global_variables_initializer()
        sess = tf.Session()
        sess.run(init)

        # 예측을 위한 evaluate() 함수용으로 작성해둔다
        self._sess = sess

        N_train = len(X_train)
        n_batches = N_train // batch_size

        for epoch in range(epochs):
            X_, Y_ = shuffle(X_train, Y_train)

            for i in range(n_batches):
                start = i * batch_size
                end = start + batch_size

                sess.run(train, feed_dict={x: X_[start:end],
                                           y: Y_[start: end],
                                           keep_prob: p_keep})

            loss_ = loss.eval(session=sess, feed_dict={x: X_train,
                                                       y: Y_train,
                                                       keep_prob: p_keep})
            
            # evaluate() 함수에서 참조할 수 있도록 함
            accuracy_ = self.accuracy.eval(session=sess, feed_dict={x: X_train,
                                                               y: Y_train,
                                                               keep_prob: p_keep})

            # 오차와 정확도를 그래프로 그리기 위해 값을 기록한다
            self._history['loss'].append(loss_)
            self._history['accuracy'].append(accuracy_)

            if verbose:
                print(f'epoch : {epoch:03}, loss : {loss_:.7}, accuracy : {accuracy_*100:.6} %')

        return self._history


  • 초기화 부분 다시 정리
    • 테스트할 때 사용되는 변수는 evaluate() 과정에도 필요하기 때문에 클래스 내부에 둬야 한다.
    • 학습 진행 상황을 클래서에서 파악하고 있으면 학습 종료 후에 데이터를 처리하기 쉬워지무로 self.history를 정의해 사용한다.
class DNN(objec):
    def __init__(self, n_in, n_hiddens, n_out):
        self.n_in = n_in
        self.n_hiddens = n_hiddens
        self.n_out = n_out
        self.weight = []
        self.bias = []

        # 학습 진행 상황을 확인하기 위하여 오차와 정확도를 _history 변수에 저장하고 한다
        self._x = None
        self._y = None
        self._keep_prob = None
        self._sess = None
        self._history = {'accuracy': [],
                         'loss': []}


evaluate() 함수 정의

  • 앞에서 정의한 self._sess 등을 사용해 다음과 같이 구현한다.
class DNN(objec):
    def evaluate(self, X_test, Y_test):
        # 모델 예측 결과 처리
        return self.accuracy(session=self._sess, feed_dict={self._x: X_test,
                                                            self._y: Y_test,
                                                            self._keep_prob: 1.0})


정의한 class를 사용하여 모델 설계하기앞서

  • 앞서 정의한 class를 사용한다면 main 함수에서 다음과 같이 코딩하여 사용할 수 있다.
if __name__ == '__main__':

    MNIST = datasets.fetch_mldata('MNIST original', data_home='.')

    n = len(MNIST.data)
    N = int(n * 1.00)
    N_train = int(n * 0.2)
    N_val = int(N_train * 0.2)
    indices = np.random.permutation(range(n))[:N]
    X = MNIST.data[indices]
    Y = MNIST.target[indices]
    Y = np.eye(10)[Y.astype(int)]

    X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=N_train)
    X_train, X_val, Y_train, Y_val = train_test_split(X_train, Y_train, test_size=N_val)

    n_in = len(X[0])
    n_hiddens = [200, 200, 200]
    n_out = len(Y[0])

    # 신경망 모델링 정의
    model = DNN(n_in=n_in, n_hiddens=n_hiddens, n_out=n_out)

    # 신경망 모델 학습
    model.fit(X_train, Y_train, epochs=50, batch_size=200, p_keep=0.5)

    accuracy = model.evaluate(X_test, Y_test)
    print(f'accuracy : {accuracy*100:.6} %')


  • 위의 예제에서는 fit() 함수가 수행하는 처리 내용을 단순한 형태로 코딩을 했는데 내부에 있는 처리 내용을 다시 분할해서 코딩하면 더욱 범용으로 편리하게 사용할 수 있는 API가 된다.
  • TensorFlow에서는 이러한 높은 수준의 API도 제공하고 있는데, tf.contrib.learn을 통해 이용할 수 있다.


출처 : 정석으로 배우는 딥러닝



오버피팅 문제 해결 방안

  • 학습 데이터에만 최적화되지 않고 테스트 데이터에 대해서도 정확도가 높아지도록 패턴 분류를 실행하는 것을 일반화(generalization)이라고 하는데, 오버피팅을 방지하기 위해서는 신경망 모델의 일반화 성능을 향상시켜야만 한다.
  • 드롭아웃(dropout) : 학습을 시킬 때 무작위로 뉴런을 '드롭아웃(=제외)'시키는 방법으로 드롭아웃이 적용된 신경망 모델의 예가 그림 9.1이다. 은영처리된 뉴런이 드롭아웃된 뉴런으로 이 뉴런은 마치 '신경망에 존재하지 않는 것'으로 취급된다.
    • 학습을 시킬 때마다 드롭아웃시킬 뉴런을 무작위로 선택해서 학습 전체에서 매개변수 값이 조정되도록 한다.
    • 뉴런이 드롭아웃될 확률 $p$는 일반적으로 $p=0.5$의 값을 사용한다.
    • 학습이 끝난 후에 테스트와 예측을 실행할 때에는 드롭아웃을 시행하지 않지만 신경망 모델의 전체 웨이트가 $W$일 때 학습 전체의 평균을 나타내는 $(1-p)W$의 출력을 사용하게 된다.

그림 9.1 드롭아웃이 적용된 신경망의 예

 

드롭아웃과 일반화 성능의 관계

  • 하나의 모델로 학습을 시키면 어쩔 수 없이 오버피팅이 발생할 수 밖에 없다.
  • 드롭아웃을 적용하면 학습할 때마다 새로운 신경망 모델이 만들어지기 때문에 다수의 신경망 모델로 학습을 하고 각 모델로 예측을 한다면 '집단지성'을 통해 예측을 하기 때문에 오버피팅을 방지할 수 있다.
  • 하나의 신경망 모델에서 여러 개의 신경망 모델을 생성해 학습을 시키는 것을 앙상블 학습(ensemble learning)이라고 한다.
  • 드롭아웃 학습법은 유사적으로 앙상블 학습을 하는 것이라고 볼 수 있다.

 

드롭아웃 학습법 해석

  • 드롭아웃 학습법을 수식을 통해 해석한다고 하면 뉴런을 무작위로 선택해 드롭아웃을 시켜야 하는 상황을 고려해야 한다.
  • 신경망 모델의 뉴런을 무작위로 선택해서 $0$(드롭아웃)이나 $1$이라는 값을 취하는 마스크를 씌우면 된다.
  • $\mathbb=(m_1, \ldots, m_j, \ldots, m_J)$라는 벡터에서 $m_j$는 확률 $p$로 $0$을, 확률 $(1-p)$로 $1$을 취하는 값이라고 가정해보자.
  • 드롭아웃이 없는 경우 1개의 은닉층을 가진 신경망 모델의 순전파 식은 식 $(9.1)$과 같다.

$$\begin{align} \mathbb{h}_1 = f(W\mathbb{x}+\mathbb{b}) \tag{9.1}\end{align}$$

  • 드롭아웃이 있는 경우에는 식 $(9.1)$에 마스크를 추가하면 되기 때문에 식 $(9.2)$와 같다.

$$\begin{align} \mathbb{h}_1 = f(W\mathbb{x}+\mathbb{b})\odot\mathbb{m} \tag{9.2}\end{align}$$

  • 순전파에서 마스크를 씌운다는 것은 순전파를 진행할 때도 마스크 항이 추가되기 때문에 $\mathbb{h}_1$ 다음의 2번째 은닉층 식 $(9.3)$라고 하자.

$$\begin{align} \mathbb{h}_2 = g(V\mathbb{h}_1+\mathbb{c}) \tag{9.3}\end{align}$$

  • 또 활성화되기 전의 상태를 다음과 같이 정의하자.

$$\begin{align} \mathbb{p} &= W\mathbb{x}+\mathbb{b} \tag{9.4}\\\mathbb{q} &= V\mathbb{h}_1+\mathbb{c}\\ &= Vf(\mathbb{p})\odot\mathbb{m}+\mathbb{c}\tag{9.5}\end{align}$$

  • 이 신경망 모델에서 식 $(9.4)$와 식 $(9.5)$에 대한 오차항 $\mathbb{e}_{h_1}, \mathbb{e}_{h_2}$은 식 $(9.6)$과 식 $(9.7)$로 표현할 수 있다.

$$\begin{align} \mathbb{e}_{h_1} &= \frac{\partial E_n}{\partial \mathbb{p}} \tag{9.6}\\ \mathbb{e}_{h_2} &= \frac{\partial E_n}{\partial \mathbb{q}}\tag{9.7}\end{align}$$

  • $\mathbb{e}_{h_1}$은 식 $(9.8)$과 같이 계산되기 때문에 오차역전파에서 마스크 $\mathbb{m}$이 필요하다는 것을 알 수 있다.

$$\begin{align} \mathbb{e}_{h_1} &= \frac{\partial E_n}{\partial \mathbb{q}}\frac{\partial \mathbb{q}}{\partial \mathbb{p}}\\ &= \mathbb{e}_{h_1}\frac{\partial }{\partial \mathbb{p}} \Big(Vf(\mathbb{p})\odot\mathbb{m}+\mathbb{c}\Big) \\&= f'(\mathbb{p})\odot\mathbb{m}\odot V\mathbb{e}_{h_2}\tag{9.8}\end{align}$$

 

MNIST 드롭아웃 모델 구현하기

  • TensorFlow
    • 수식과 마찬가지로 순전파의 기본식을 정의하고 마스크를 씌우는 순서대로 모델을 정의한다.
    • 전체 코드
      • 드롭아웃 확률은 학습 시에는 $p=0.5$이지만 예측을 할 때에는 $p=1.0$인 것에 유의
import numpy as np 
import tensorflow as tf 
from sklearn import datasets 
from sklearn.model_selection import train_test_split 
from sklearn.utils import shuffle 

MNIST = datasets.fetch_mldata('MNIST original', data_home='.') 

n = len(MNIST.data) 
N = 10000 
train_size = 0.8 
indices = np.random.permutation(range(n))[:N] 

X = MNIST.data[indices] 
Y = MNIST.target[indices] 
Y = np.eye(10)[Y.astype(int)] 

X_train, X_test, Y_train, Y_test = train_test_split(X, Y, train_size=train_size) 

# 모델 설정 
n_in = len(X[0]) 
n_hidden = 200 
n_out = len(Y[0]) 

x = tf.placeholder(tf.float32, shape=[None, n_in]) 
y = tf.placeholder(tf.float32, shape=[None, n_out]) 
keep_prob = tf.placeholder(tf.float32) # 드롭아웃하지 않을 확률 정의 

# 입력층 - 은닉층 
W0 = tf.Variable(tf.truncated_normal([n_in, n_hidden], stddev=0.01)) 
b0 = tf.Variable(tf.zeros([n_hidden])) 
h0 = tf.nn.relu(tf.matmul(x, W0) + b0) 
h0_drop = tf.nn.dropout(h0, keep_prob) 

# 은닉층 - 은닉층 
W1 = tf.Variable(tf.truncated_normal([n_hidden, n_hidden], stddev=0.01)) 
b1 = tf.Variable(tf.zeros([n_hidden])) 
h1 = tf.nn.relu(tf.matmul(h0_drop, W1) + b1) 
h1_drop = tf.nn.dropout(h1, keep_prob) 

W2 = tf.Variable(tf.truncated_normal([n_hidden, n_hidden], stddev=0.01)) 
b2 = tf.Variable(tf.zeros([n_hidden])) h2 = tf.nn.relu(tf.matmul(h1_drop, W2) + b2) 
h2_drop = tf.nn.dropout(h2, keep_prob) 

# 은닉층 - 출력층 
W3 = tf.Variable(tf.truncated_normal([n_hidden, n_out], stddev=0.01)) 
b3 = tf.Variable(tf.zeros([n_out])) 
hypothesis = tf.nn.softmax(tf.matmul(h2_drop, W3) + b3) 

cross_entropy = tf.reduce_mean(-tf.reduce_sum(y * tf.log(hypothesis), axis=1)) 
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy) 
correct_prediction = tf.equal(tf.argmax(hypothesis, 1), tf.argmax(y, 1)) 

accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) 

# 모델 학습 과정 
epochs = 30 
batch_size = 200 

init = tf.global_variables_initializer() 
sess = tf.Session() 
sess.run(init) 

n_batches = (int)(N * train_size) // batch_size 

for epoch in range(epochs): 
    X_, Y_ = shuffle(X_train, Y_train) 
    
    for i in range(n_batches): 
        start = i * batch_size 
        end = start + batch_size 
        
        sess.run(train_step, feed_dict={x: X_[start:end], y: Y_[start:end], keep_prob: 0.5}) 
        
    # 학습 데이터에 대응하는 학습 진행 상황을 출력 
    loss = cross_entropy.eval(session=sess, feed_dict=) 
    acc = accuracy.eval(session=sess, feed_dict=)
    print(f'epoch = , loss: , accuracy: %') 
    
# 예측 결과 평가 
accuracy_rate = accuracy.eval(session=sess, feed_dict=) 
print(f'accuracy: %')
    • 실행 결과
epoch = 027, loss: 0.079016678, accuracy: 97.6875 % 
epoch = 028, loss: 0.077762701, accuracy: 97.8875 % 
epoch = 029, loss: 0.06995143, accuracy: 97.95 % 

accuracy: 93.95 %
  • Keras
    • 전체 코드
    • TensorFlow에서는 dropout이 드롭아웃하지 않을 뉴런의 비율을 의미했지만, Kera에서는 드롭아웃시킬 뉴런의 비율을 의미함
import numpy as np 
from keras.models import Sequential 
from keras.layers.core import Dense, Activation, Dropout 
from keras.optimizers import SGD 
from sklearn import datasets 
from sklearn.model_selection import train_test_split 
from time import time 

# 데이터 생성 
MNIST = datasets.fetch_mldata('MNIST original', data_home='.') 
n = len(MNIST.data) 
N = 10000 
indices = np.random.permutation(range(n))[:N] 

X = MNIST.data[indices] 
Y = MNIST.target[indices] 
Y = np.eye(10)[Y.astype(int)] 

X_train, X_test, Y_train, Y_test = train_test_split(X, Y, train_size=0.8) 

# 모델 설정 
n_in = len(X[0])
n_hidden = 200 
n_out = len(Y[0]) 

model = Sequential() 
model.add(Dense(n_hidden, input_dim=n_in)) 
model.add(Activation('tanh')) 
model.add(Dropout(0.5)) 
model.add(Dense(n_hidden)) 
model.add(Activation('tanh')) 
model.add(Dropout(0.5)) 
model.add(Dense(n_hidden)) 
model.add(Activation('tanh')) 
model.add(Dropout(0.5)) 
model.add(Dense(n_out)) 
model.add(Activation('softmax')) 
model.compile(loss='categorical_crossentropy', optimizer=SGD(lr=0.01), metrics=['accuracy']) 

start = time() 

# 모델 학습시키기 
epochs = 150 
batch_size = 200 

model.fit(X_train, Y_train, epochs=epochs, batch_size=batch_size) 
print(f'\nElapse training time : {(time() - start)//60}분 {(time() - start) % 60:.6}초\n') 

# 정확도 측정 
start = time() 
loss_and_metrics = model.evaluate(X_test, Y_test) 
print(f'\nLoss : ') 
print(f'Accuracy : %') 
print(f'Elapse test time : 초') 
    • 실행 결과
Elapse training time : 0.0분 45.3053초 

  32/2000 [..............................] - ETA: 1s 
2000/2000 [==============================] - 0s 33us/step 

Loss : 0.329999 
Accuracy : 90.85% 
Elapse test time : 0.06604743003845215초

 

출처 : 정석으로 배우는 딥러닝

 

'신경망(Neural Network) 스터디' 카테고리의 다른 글

11. 학습 과정 시각화  (3) 2018.01.19
10. 신경망 모델 구현 방법  (0) 2018.01.19
8. 경사 소실 문제 해결하기  (0) 2018.01.18
7. 심층 신경망  (0) 2018.01.10
6. 다층 퍼셉트론 모델링  (0) 2017.12.28

경사 소실 문제 해결 방안

  • 출력층 활성화 함수 : 신경망 모델에서는 반드시 확률을 출력하는 함수여야 하기 때문에 일반적으로 시그모이드 함수나 소프트맥스 함수를 사용한다.
  • 은닉층 활성화 함수 : 입력값이 작으면 작은 값을 출력하고, 입력값이 크면 큰 값을 출력하는 함수를 사용
  • 은닉층에서 활성화 함수로 시그모이드 함수를 사용하면 경사 소실 문제가 발생하기 때문에 다른 활성화 함수를 사용해 경사 소실 문제를 해결할 수 있다.
  • 시그모이드 함수를 대체할 수 있는 활성화 함수를 사용하려면  시그모이드 함수와 모양이 비슷하고 경사가 소실되지 않는 함수를 찾아봐야 한다

 

쌍곡탄젠트 함수(Hyperbolic Tangent Function) $\tanh(x)$

  • $\tanh(x)$ 함수는 식 $(8.1)$과 같이 정의하며, 그림 $8.1$은 $\tanh(x)$ 함수의 그래프이다.

$$\begin{align} \tanh(x) = \frac{e^x-e^{-x}}{e^x+e^{-x}}\tag{8.1}\end{align}$$

그림 8.1 $\tanh(x)$의 그래프

  • 그림 $8.1$의 그래프를 보면 시그모이드 함수 $\sigma(x)$의 그래프와 비슷하지만 함수값의 범위가 다르다.
    • 시그모이드 함수 : 입력값 $-\infty<x<\infty$에 대하여 함수값의 범위는 $0<\sigma(x)<1$
    • 쌍곡탄젠트 함수 : 입력값 $-\infty<x<\infty$에 대하여 함수값의 범위는 $-1<\tanh(x)<1$
  • 활성화 함수로 쌍곡탄젠트 함수를 사용할 경우 경사를 구하려면 $\tanh(x)$의 도함수를 알아야 하는데 도함수를 구해보자. 먼저 $\tanh(x)$는 식 $(8.2)$와 같이 변형할 수 있다.

$$\begin{align} \tanh(x) &= \frac{e^x-e^{-x}}{e^x+e^{-x}} \\ &= \frac{e^x-e^{-x}}{e^x+e^{-x}}\times 1\\&= \frac{e^x-e^{-x}}{e^x+e^{-x}}\times \frac{e^{-x}}{e^{-x}} \\&= \frac{(e^x-e^{-x})e^{-x}}{(e^x+e^{-x})e^{-x}}\\&= \frac{e^xe^{-x}-e^{-x}e^{-x}}{e^xe^{-x}+e^{-x}e^{-x}}\\&= \frac{e^{x-x}-e^{-x-x}}{e^{x-x}+e^{-x-x}}\\&= \frac{e^{0}-e^{-2x}}{e^{0}+e^{-2x}}\\&= \frac{1-e^{-2x}}{1+e^{-2x}}\tag{8.2}\end{align}$$

  • 이제 $\tanh'(x)$는 식 $(8.3)$과 같이 구할 수 있다.

\begin{align} \tanh'(x) &= \frac{(1-e^{-2x})'(1+e^{-2x}) + (1-e^{-2x})(1+e^{-2x})'}{(1+e^{-2x})^2} \\&= \frac{2e^{-2x}(1+e^{-2x}) + (1-e^{-2x})(-2e^{-2x})}{(1+e^{-2x})^2}\\&= \frac{2e^{-2x}(1+\not{e^{-2x}} + 1 - \not{e^{-2x}})}{(1+e^{-2x})^2}\\&= \frac{4e^{-2x}}{(1+e^{-2x})^2}\\&= \frac{(1+2e^{-2x}+e^{-4x})-(1-2e^{-2x}+e^{-4x})}{(1+e^{-2x})^2}\\&= \frac{(1+e^{-2x})^2-(1-e^{-2x})^2}{(1+e^{-2x})^2}\\&= \frac{\not{(1+e^{-2x})^2}}{\not{(1+e^{-2x})^2}}-\frac{(1-e^{-2x})^2}{(1+e^{-2x})^2}\\&= 1-\Bigg(\frac{1-e^{-2x}}{1+e^{-2x}}\Bigg)^2\\&= 1-\tanh^2(x)\\&= (1+\tanh(x))(1-\tanh(x))\tag{8.3}\end{align}

  • 그림 8.2는 $\tanh'(x)$와 $\sigma'(x)$의 그래프로 $\sigma'(x)$의 최대값은 $\sigma'(0)=0.25$였지만 $\tanh'(x)$의 최대값은 $\tanh'(0)=1$이기 때문에 시그모이드 함수와 비교했을 때 경사가 소실되기 힘들다는 것을 알 수 있다.

그림 8.2 $\tanh'(x)$와 $\sigma'(x)$의 그래프

  • 쌍곡탄젠트 함수 코드 구현
    • TensorFlow
tf.nn.tanh()
    • Keras
Activation('tanh')

 

은닉층 4개로 MNIST 모델링하기

  • Keras 코드로 구현하면 정확도가 $93.2%$까지 올라가는 것을 알 수 있다.
from time import time 
import numpy as np 
from keras.models import Sequential 
from keras.layers import Dense, Activation 
from keras.optimizers import SGD 
from sklearn import datasets 
from sklearn.model_selection import train_test_split 

start = time() 
MNIST = datasets.fetch_mldata('MNIST original', data_home='.') 

n = len(MNIST.data) 
N = 10000 
indices = np.random.permutation(range(n))[:N] 

X = MNIST.data[indices] 
Y = MNIST.target[indices] 
Y = np.eye(10)[Y.astype(int)] 

x_train, x_test, y_train, y_test = train_test_split(X, Y, train_size=0.8) 

# 모델 설정 
n_in = len(X[0]) 
n_hidden = 200 
n_out = len(Y[0]) 

model = Sequential() 
model.add(Dense(input_dim=n_in, units=n_hidden)) 
model.add(Activation('tanh')) 
model.add(Dense(units=n_hidden)) 
model.add(Activation('tanh')) 
model.add(Dense(units=n_hidden)) 
model.add(Activation('tanh')) 
model.add(Dense(units=n_hidden)) 
model.add(Activation('tanh')) 
model.add(Dense(units=n_out)) 
model.add(Activation('softmax')) 
model.compile(loss='categorical_crossentropy', optimizer=SGD(lr=0.01), metrics=['accuracy']) 

# 모델 학습 
epochs = 1000 
batch_size = 100 

model.fit(x_train, y_train, epochs=epochs, batch_size=batch_size) 

print(f'\nElapse training time : {(time() - start)//60}분 {(time() - start) % 60:.6}초\n') 

# 정확도 측정 
start = time() 
loss_and_metrics = model.evaluate(x_test, y_test) 

print(f'\nLoss : ') 
print(f'Accuracy : %') 
print(f'Elapse test time : 초')
Elapse training time : 4.0분 37.9497초 

  32/2000 [..............................] - ETA: 0s 
2000/2000 [==============================] - 0s 32us/step 

Loss : 0.396467 
Accuracy : 93.2% 
Elapse test time : 0.06404519081115723초

 

ReLU(Rectified Linear Unit) 함수

  • $\tanh(x)$ 함수를 사용하면 경사가 소실되기 어렵기 때문에 좋기는 하지만 고차원 데이터를 다룰 경우에는 값이 커질 수 있어 다시 경사가 소실될 수 있는 문제점이 발생한다
  • 복잡한 데이터일 수록 고차원일 경우가 많은 이를 회피할 수 있는 활성화 함수가 ReLU 함수로 램프 함수 또는 정규화 선형 함수라고도 하는데 식 $(8.4)$와 같이 정의하며, 그래프는 그림 $8.3$과 같다.

$$\begin{align}f(x) = \max(0,x) \tag{8.4}\end{align}$$

그림 8.3 $f(x)=\max(0,x)$ 함수의 그래프

import numpy as np 
import matplotlib.pyplot as plt 

def relu(x): 
    if x >= 0: 
        return x 
    else: 
        return 0 
        
fig = plt.figure() 
ax = fig.add_subplot(111) 
major_y_ticks = np.arange(-1, 2, 0.2) 
ax.set_yticks(major_y_ticks) 
major_x_ticks = np.arange(-1, 1.3, 0.5) 
ax.set_xticks(major_x_ticks) 
ax.grid(which='major', linestyle='--') 

x = np.linspace(-1, 1, 100) 
y = np.array([relu(x) for x in x]) 
ax.plot(x, y, label='ReLU') 
ax.legend() 
plt.show()
  • ReLU 함수는 시그모이드나 쌍곡탄젠트 함수와 달리 곡선 부분이 없으며, ReLU 함수를 미분하면 식 $(8.5)$와 같이 계단 함수가 되는 것을 알 수 있다.

\begin{align}f'(x) =\left\{ \begin{array}{lc} 1 & \quad x > 0\\ 0 & \quad x \leqq 0\end{array}\right. \tag{8.5}\end{align}

  • ReLU 함수의 도함수는 $x$가 아무리 커져도 1을 반환하므로 경사가 소실되지 않기 때문에 시그모이드 함수나 쌍곡탄젠트 함수에 비교해 학습 속도가 빠르다.
  • 또한 ReLU와 ReLU의 도함수는 지수 함수가 포함되지 않은 단순한 식으로 표현되기 때문에 빠르게 계산이 가능하다.
  • 단점으로는 $x\leqq 0$일 때는 함수값도 경사도 $0$이기 때문에 ReLU를 활성화 함수로 사용한 신경망 모델의 뉴런 중 활성화되지 못한 뉴런은 학습동안 활성화가 되지 않는 문제가 있다.
  • 학습률을 큰 값으로 설정하면 첫 오차역전파에서 뉴런의 값이 너무 작아져 해당 뉴런은 신경망 모델에서 존재하지 않는 것이나 다름없는 상태가 되기 때문에 주의해야 한다.
  • 쌍곡탄젠트 함수 코드 구현
    • TensorFlow
tf.nn.relu()
    • Keras
Activation('relu')

 

은닉층 4개로 MNIST 모델링하기

  • Keras 코드로 구현하면 정확도가 $94.1\%$까지 올라가는 것을 알 수 있다.
from time import time 
import numpy as np 
from keras.models import Sequential 
from keras.layers import Dense, Activation 
from keras.optimizers import SGD 
from sklearn import datasets 
from sklearn.model_selection import train_test_split 

start = time() 

MNIST = datasets.fetch_mldata('MNIST original', data_home='.') 

n = len(MNIST.data) 
N = 10000 
indices = np.random.permutation(range(n))[:N] 

X = MNIST.data[indices] 
Y = MNIST.target[indices] 
Y = np.eye(10)[Y.astype(int)] 

x_train, x_test, y_train, y_test = train_test_split(X, Y, train_size=0.8) 

# 모델 설정 
n_in = len(X[0]) 
n_hidden = 200 
n_out = len(Y[0]) 

model = Sequential() 
model.add(Dense(input_dim=n_in, units=n_hidden)) 
model.add(Activation('relu')) 
model.add(Dense(units=n_hidden)) 
model.add(Activation('relu')) 
model.add(Dense(units=n_hidden)) 
model.add(Activation('relu')) 
model.add(Dense(units=n_hidden)) 
model.add(Activation('relu')) 
model.add(Dense(units=n_out)) 
model.add(Activation('softmax')) 
model.compile(loss='categorical_crossentropy', optimizer=SGD(lr=0.01), metrics=['accuracy']) 

# 모델 학습 
epochs = 1000 
batch_size = 100 
model.fit(x_train, y_train, epochs=epochs, batch_size=batch_size) 

print(f'\nElapse training time : {(time() - start)//60}분 {(time() - start) % 60:.6}초\n') 

# 정확도 측정 
start = time() 
loss_and_metrics = model.evaluate(x_test, y_test) 

print(f'\nLoss : ') 
print(f'Accuracy : %') 
print(f'Elapse test time : 초')
Elapse training time : 4.0분 32.813초 

  32/2000 [..............................] - ETA: 0s 
2000/2000 [==============================] - 0s 31us/step 

Loss : 0.534027 
Accuracy : 94.1% 
Elapse test time : 0.06304502487182617초

 

LeakyReLU 함수

  • LeakyReLU 함수는 LReLU라고도 하며 ReLU 함수를 개량시킨 것으로 식 $(8.6)$과 같이 정의되며 $\alpha$는 $0.01$과 같은 작은 상수값이다.

$$}\begin{align} f(x)=\max(\alpha x, x)\tag{8.6}\end{align}$$

  • LReLU의 그래프는 그림 $8.4$와 같으며, ReLU 함수와의 차이점은 $\alpha$에 의해 $x<0$일 때도 작은 경사(\alpha)를 갖는다. LReLU 함수를 미분하면 식 $(8.7)$과 같다.

$$\begin{align}f'(x) =\left\{ \begin{array}{lc} 1 & x > 0\\ \alpha & x \leqq 0\end{array}\right. \tag{8.7}\end{align}$$

그림 8.4 $f(x)=\max(\alpha x,x)$ 함수의 그래프

 

  • ReLU 함수는 $x\leqq 0$일 때 경사가 사라져버려 학습 과정이 불안해질 수 있는 문제가 있었지만 LReLU는 $x\leqq 0$일 때도 학습이 진행되기 때문에 ReLU 함수보다 효과적인 활성화 함수라고 생각할 수 있지만 실제로 사용하게 되면 효과가 있는 경우도 있고, 업는 경우도 있어 언제 효과가 나타나는지에 관해 아직 밝혀진 바가 없다.
  • LReLU 함수 코드 구현
    • TensorFlow에서는 아직 API가 제공되지 않아 직접 함수를 정의해 사용해야 한다.
def lrelu(x, alpha=0.01): 
    if x >= 0: 
        return x 
    else: 
        return alpha * x
def lrelu(x, alpha=0.01): 
    return tf.maximum(alpha * x, x)

 

    • Keras에서는 다음 코드를 추가한다.
from keras.layers.advanced_activations import LeakyReLU

 

은닉층 4개로 MNIST 모델링하기

  • TensorFlow로 구현하면 정확도 $93.7\%$가 나온다
import numpy as np 
import tensorflow as tf 
from sklearn import datasets 
from sklearn.model_selection import train_test_split 
from sklearn.utils import shuffle 

mnist = datasets.fetch_mldata('MNIST original', data_home='.') 

n = len(mnist.data) 
N = 10000 
train_size = 0.8 
indices = np.random.permutation(range(n))[:N] 

X = mnist.data[indices] 
y = mnist.target[indices] 
Y = np.eye(10)[y.astype(int)] 
X_train, X_test, Y_train, Y_test =\ train_test_split(X, Y, train_size=train_size) 

n_in = len(X[0]) 
n_hidden = 200 
n_out = len(Y[0]) 

def lrelu(x, alpha=0.01): 
    return tf.maximum(alpha * x, x) 
    
x = tf.placeholder(tf.float32, shape=[None, n_in]) 
t = tf.placeholder(tf.float32, shape=[None, n_out]) 

W0 = tf.Variable(tf.truncated_normal([n_in, n_hidden], stddev=0.01)) 
b0 = tf.Variable(tf.zeros([n_hidden])) 
h0 = lrelu(tf.matmul(x, W0) + b0) 

W1 = tf.Variable(tf.truncated_normal([n_hidden, n_hidden], stddev=0.01)) 
b1 = tf.Variable(tf.zeros([n_hidden]))
h1 = lrelu(tf.matmul(h0, W1) + b1) 

W2 = tf.Variable(tf.truncated_normal([n_hidden, n_hidden], stddev=0.01)) 
b2 = tf.Variable(tf.zeros([n_hidden])) 
h2 = lrelu(tf.matmul(h1, W2) + b2) 

W3 = tf.Variable(tf.truncated_normal([n_hidden, n_hidden], stddev=0.01)) 
b3 = tf.Variable(tf.zeros([n_hidden])) 
h3 = lrelu(tf.matmul(h2, W3) + b3)

W4 = tf.Variable(tf.truncated_normal([n_hidden, n_out], stddev=0.01)) 
b4 = tf.Variable(tf.zeros([n_out])) 

y = tf.nn.softmax(tf.matmul(h3, W4) + b4) 

cross_entropy = tf.reduce_mean(-tf.reduce_sum(t * tf.log(y), axis=1)) 

train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy) 
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(t, 1)) 
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) 

epochs = 50 
batch_size = 200 
init = tf.global_variables_initializer() 
sess = tf.Session() 
sess.run(init) 
n_batches = (int)(N * train_size) // batch_size 

for epoch in range(epochs): 
    X_, Y_ = shuffle(X_train, Y_train) 
    
    for i in range(n_batches): 
        start = i * batch_size 
        end = start + batch_size 
        sess.run(train_step, feed_dict={ x: X_[start:end], t: Y_[start:end] }) 
        
    loss = cross_entropy.eval(session=sess, feed_dict={ x: X_, t: Y_ }) 
    acc = accuracy.eval(session=sess, feed_dict={ x: X_, t: Y_ }) 
    print(f'epoch: , loss: , accuracy: ') 


accuracy_rate = accuracy.eval(session=sess, feed_dict={ x: X_test, t: Y_test }) 
print(f'accuracy: %')
epoch: 47, loss: 0.03343997895717621, accuracy: 0.9942499995231628 
epoch: 48, loss: 0.02821164019405842, accuracy: 0.9956250190734863 
epoch: 49, loss: 0.036636706441640854, accuracy: 0.9912499785423279 

accuracy: 93.7 % 
  • Keras 코드로 구현하면 정확도 $92.9\%$가 나온다.
from time import time 
import numpy as np 
from keras.models import Sequential 
from keras.layers import Dense, Activation 
from keras.optimizers import SGD 
from sklearn import datasets 
from sklearn.model_selection import train_test_split 
from keras.layers.advanced_activations import LeakyReLU 

start = time() 
MNIST = datasets.fetch_mldata('MNIST original', data_home='.') 

n = len(MNIST.data) 
N = 10000 
indices = np.random.permutation(range(n))[:N] 

X = MNIST.data[indices] 
Y = MNIST.target[indices] 
Y = np.eye(10)[Y.astype(int)] 

x_train, x_test, y_train, y_test = train_test_split(X, Y, train_size=0.8) 

# 모델 설정 
n_in = len(X[0]) 
n_hidden = 200 
n_out = len(Y[0]) 

alpha = 0.01 

model = Sequential() 
model.add(Dense(input_dim=n_in, units=n_hidden)) 
model.add(LeakyReLU(alpha=alpha)) 
model.add(Dense(units=n_hidden)) 
model.add(LeakyReLU(alpha=alpha)) 
model.add(Dense(units=n_hidden)) 
model.add(LeakyReLU(alpha=alpha)) 
model.add(Dense(units=n_hidden)) 
model.add(LeakyReLU(alpha=alpha)) 
model.add(Dense(units=n_out)) 
model.add(Activation('softmax')) 
model.compile(loss='categorical_crossentropy', optimizer=SGD(lr=0.01), metrics=['accuracy']) 

# 모델 학습 
epochs = 20 
batch_size = 200 

model.fit(x_train, y_train, epochs=epochs, batch_size=batch_size) 

print(f'\nElapse training time : {(time() - start)//60}분 {(time() - start) % 60:.6}초\n') 

# 정확도 측정 
start = time() 
loss_and_metrics = model.evaluate(x_test, y_test) 

print(f'\nLoss : ') print(f'Accuracy : %') 
print(f'Elapse test time : 초')
   32/2000 [..............................] - ETA: 0s 
 1984/2000 [============================>.] - ETA: 0s 
 2000/2000 [==============================] - 0s 33us/step 
 
 Elapse training time : 0.0분 0.0660467초 
 
 Loss : 0.473356 Accuracy : 92.9% 
 Elapse test time : 0.06604671478271484초

 

Parametric ReLU 함수

  • LeakyReLU 함수는 $x<0$일 때, 경사 $\alpha$가 고정되었지만, 경사 $\alpha$도 학습으로 최적화하는 것이 Parametric ReLU 함수로 PReLU라고도 한다.
  • PReLU 함수는 활성화 이전의 값(벡터) $\mathbb=(p_1, p_2, \ldots, p_J)$에 대하여 식 $(8.7)$과 같이 정의하는데, 이 때 상수(스칼라) $\alpha$가 아닌 벡터 $\mathbb{\alpha}=(\alpha_1, \alpha_2, \ldots, \alpha_j, \ldots, \alpha_J)$가 주어진다.

\begin{align} f(p_j) = \left\{\begin{array}{lc} p_j & \quad p_j > 0 \\ \alpha_jp_j & \quad p_j \leqq 0 \end{array}\right.\tag{8.8}\end{align}

  • 이 벡터가 최적화해야할 매개변수(중 하나)이기 때문에 weight와 bias를 최적화할 때와 마찬가지로 오차 함수 $E$에 포함된 $\alpha_j$에 대한 경사를 구해야 하며, 경사는 식 $(8.8)$과 같이 나타낼 수 있다.

\begin{align} \frac{\partial E}{\partial \alpha_j} = \sum_{p_j}\frac{\partial E}{\partial f(p_j)} \frac{\partial f(p_j)}{\partial \alpha_j}\tag{8.9}\end{align}

  • 우변에 있는 두 항 중에  $\frac{\partial E}{\partial f(p_j)}$는 앞쪽에 있는 층(순전파에서 다음 층)에서 역전파해오는 오차항이기 때문에 $\frac{\partial f(p_j)}{\partial \alpha_j}$는 식 $(8.8)$에 의해 다음과 같이 구할 수 있다.

\begin{align} \frac{\partial f(p_j)}{\partial \alpha_j} = \left\{ \begin{array}{ll}0 & \quad p_j > 0 \\ p_j &\quad p_j \leqq 0 \end{array}\right.\tag{8.10}\end{align}

  • 식 $(8.10)$과 같이 경사를 계산할 수 있기 때문에 경사하강법으로 매개변수를 최적화시킬 수 있다.
  • PReLU 함수 코드 구현
    • TensorFlow에서는 아직 API가 제공되지 않아 직접 함수를 정의해 사용해야 한다.
    • 식 $(8.8)$은 식 $(8.11)$로 변형할 수 있기 때문에 식 $(8.11)$을 사용해 구현한다.

\begin{align} f(p_j) = \max(0, p_j) + \alpha_j\min(0, p_j)\tag{8.11}\end{align}

def prelu(x, alpha): 
    return tf.maximum(tf.zeros(tf.shape(x)), x) + alpha * tf.minimum(tf.zeros(tf.shape(x)), x)

 

    • Keras에서는 다음 코드를 추가한다.
from keras.layers.advanced_activations import PReLU

 

은닉층 4개로 MNIST 모델링하기

  • TensorFlow로 구현하면 정확도 $92.55\%$가 나온다
import numpy as np 
import tensorflow as tf 
from sklearn import datasets 
from sklearn.model_selection import train_test_split 
from sklearn.utils import shuffle 

mnist = datasets.fetch_mldata('MNIST original', data_home='.') 

n = len(mnist.data) 
N = 10000 train_size = 0.8 
indices = np.random.permutation(range(n))[:N] 

X = mnist.data[indices] 
y = mnist.target[indices] 
Y = np.eye(10)[y.astype(int)] 

X_train, X_test, Y_train, Y_test = train_test_split(X, Y, train_size=train_size) 

n_in = len(X[0]) 
n_hidden = 200 
n_out = len(Y[0]) 

def prelu(x, alpha): 
    return tf.maximum(tf.zeros(tf.shape(x)), x) + alpha * tf.minimum(tf.zeros(tf.shape(x)), x) 
    
x = tf.placeholder(tf.float32, shape=[None, n_in]) 
t = tf.placeholder(tf.float32, shape=[None, n_out])
    
W0 = tf.Variable(tf.truncated_normal([n_in, n_hidden], stddev=0.01)) 
b0 = tf.Variable(tf.zeros([n_hidden])) alpha0 = tf.Variable(tf.zeros([n_hidden])) 
h0 = prelu(tf.matmul(x, W0) + b0, alpha0) 
    
W1 = tf.Variable(tf.truncated_normal([n_hidden, n_hidden], stddev=0.01)) 
b1 = tf.Variable(tf.zeros([n_hidden])) 
alpha1 = tf.Variable(tf.zeros([n_hidden])) 
h1 = prelu(tf.matmul(h0, W1) + b1, alpha1) 
   
W2 = tf.Variable(tf.truncated_normal([n_hidden, n_hidden], stddev=0.01)) 
b2 = tf.Variable(tf.zeros([n_hidden])) 
alpha2 = tf.Variable(tf.zeros([n_hidden])) 
h2 = prelu(tf.matmul(h1, W2) + b2, alpha2) 
    
W3 = tf.Variable(tf.truncated_normal([n_hidden, n_hidden], stddev=0.01)) 
b3 = tf.Variable(tf.zeros([n_hidden])) 
alpha3 = tf.Variable(tf.zeros([n_hidden])) 
h3 = prelu(tf.matmul(h2, W3) + b3, alpha3) 
    
W4 = tf.Variable(tf.truncated_normal([n_hidden, n_out], stddev=0.01)) 
b4 = tf.Variable(tf.zeros([n_out])) 
y = tf.nn.softmax(tf.matmul(h3, W4) + b4) 
   
cross_entropy = tf.reduce_mean(-tf.reduce_sum(t * tf.log(y), axis=1)) 
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy) 
    
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(t, 1)) 
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) 
    
epochs = 50 
batch_size = 200 
init = tf.global_variables_initializer() 
    
sess = tf.Session() 
sess.run(init) 

n_batches = (int)(N * train_size) // batch_size 
    
for epoch in range(epochs): 
    X_, Y_ = shuffle(X_train, Y_train) 
    
    for i in range(n_batches): 
        start = i * batch_size 
        end = start + batch_size sess.run(train_step, feed_dict={ x: X_[start:end], t: Y_[start:end] }) 
            
    loss = cross_entropy.eval(session=sess, feed_dict={ x: X_, t: Y_ }) 
    acc = accuracy.eval(session=sess, feed_dict={ x: X_, t: Y_ }) 
    print(f'epoch: , loss: , accuracy: ') 
            
accuracy_rate = accuracy.eval(session=sess, feed_dict={ x: X_test, t: Y_test }) 
print(f'accuracy: %')
epoch: 47, loss: 0.027591068297624588, accuracy: 0.9975000023841858 
epoch: 48, loss: 0.326477587223053, accuracy: 0.9257500171661377 
epoch: 49, loss: 0.048840634524822235, accuracy: 0.9894999861717224 

accuracy: 92.55 %

 

  • Keras로 구현하면 정확도 $90.6\%$가 나온다.
from time import time 
import numpy as np 
from keras.models import Sequential 
from keras.layers import Dense, Activation 
from keras.optimizers import SGD 
from sklearn import datasets 
from sklearn.model_selection import train_test_split 
from keras.layers.advanced_activations import PReLU 

start = time() 
MNIST = datasets.fetch_mldata('MNIST original', data_home='.') 

n = len(MNIST.data)
N = 10000 
indices = np.random.permutation(range(n))[:N] 

X = MNIST.data[indices] 
Y = MNIST.target[indices] 
Y = np.eye(10)[Y.astype(int)] 

x_train, x_test, y_train, y_test = train_test_split(X, Y, train_size=0.8) # 모델 설정 

n_in = len(X[0]) 
n_hidden = 200 
n_out = len(Y[0]) 
alpha = 0.01 

model = Sequential() 
model.add(Dense(n_hidden, input_dim=n_in)) 
model.add(PReLU()) model.add(Dense(n_hidden)) 
model.add(PReLU()) model.add(Dense(n_hidden)) 
model.add(PReLU()) model.add(Dense(n_hidden)) 
model.add(PReLU()) model.add(Dense(n_out)) 
model.add(Activation('softmax'))
model.compile(loss='categorical_crossentropy', optimizer=SGD(lr=0.01), metrics=['accuracy']) # 모델 학습 

epochs = 20 
batch_size = 200 

model.fit(x_train, y_train, epochs=epochs, batch_size=batch_size) 

print(f'\nElapse training time : {(time() - start)//60}분 {(time() - start) % 60:.6}초\n') # 정확도 측정 

start = time() 
loss_and_metrics = model.evaluate(x_test, y_test) 
print(f'\nLoss : ') 
print(f'Accuracy : %') 
print(f'Elapse test time : 초')
Elapse training time : 0.0분 7.06802초 

  32/2000 [..............................] - ETA: 1s 
1376/2000 [===================>..........] - ETA: 0s 
2000/2000 [==============================] - 0s 48us/step

Loss : 0.607181 
Accuracy : 90.6% 
Elapse test time : 0.09506750106811523초

 

기타 활성화 함수

  • Randomized ReLU(RReLU) 함수는 학습시킬 때 경사를 모두 난수로 선택하고 테스트할 때에는 그 평균을 사용하는 함수
  • Exponential Linear  Units(ELU) 함수는 식 $(8.12)$로 정의하는 함수이다.

\begin{align}f(x) = \left\{ \begin{array}{ll} x & \quad x>0\\ e^x-1 & \quad x \leqq 0 \end{array}\right. \tag{8.12}\end{align}

 

마무리

  • 활성화 함수로 어떤 함수를 사용해야할지 고민이 될 때에는 일단 ReLU 또는 LReLU 함수를 사용하면 충분히 만족스러운 결과가 나올 때가 많다.

 

 

출처 : 정석으로 배우는 딥러닝

'신경망(Neural Network) 스터디' 카테고리의 다른 글

10. 신경망 모델 구현 방법  (0) 2018.01.19
9. 오버피팅 문제 해결  (0) 2018.01.19
7. 심층 신경망  (0) 2018.01.10
6. 다층 퍼셉트론 모델링  (0) 2017.12.28
5. 다중 클래스 로지스틱 회귀  (0) 2017.12.27

+ Recent posts