MXNet 시작하기 (3) – Module API

2편에서는 Symbols를 사용하여 NDArrays에 저장된 데이터를 처리하는 연산 그래프를 정의하는 방법을 설명했습니다. 이 글에서는 Symbol 및 NDArrays에서 배운 것을 사용하여 일부 데이터를 준비하고 신경망을 구성합니다. 그런 다음 Module API를 사용하여 신경망 기반 데이터를 학습하고 결과를 예측해보겠습니다.

데이터셋 정의
(가상) 데이터 세트는 1000개의 데이터 샘플로 구성합니다.

  • 각 샘플에는 100개의 기능(Feature)이 있습니다.
  • 각 기능은 0에서 1 사이의 float 값으로 표현됩니다.
  • 샘플은 10개의 카테고리로 나뉩니다. 네트워크의 목적은 주어진 샘플에 대한 올바른 카테고리를 예측하는 것입니다.
  • 학습(Training)을 위해 800개의 샘플을 사용하고 검증(Validation)을 위해 200개의 샘플을 사용할 것입니다.
  • 학습 및 검증을 위해 배치(Batch) 크기 10을 사용합니다.

아래는 데이터 정의를 위한 간단한 MXNet 코드입니다.

import mxnet as mx
import numpy as np
import logging

logging.basicConfig(level=logging.INFO)

sample_count = 1000
train_count = 800
valid_count = sample_count - train_count

feature_count = 100
category_count = 10
batch=10

데이터셋 생성
균등 분포를 사용하여 1000 개의 샘플을 생성합시다. 이를 ‘X’라는 이름의 NDArray에 저장합니다. (즉, 1000라인, 100 컬럼)

X = mx.nd.uniform(low=0, high=1, shape=(sample_count,feature_count))

>>> X.shape
(1000L, 100L)
>>> X.asnumpy()
array([[ 0.70029777,  0.28444085,  0.46263582, ...,  0.73365158,
         0.99670047,  0.5961988 ],
       [ 0.34659418,  0.82824177,  0.72929877, ...,  0.56012964,
         0.32261589,  0.35627609],
       [ 0.10939316,  0.02995235,  0.97597599, ...,  0.20194994,
         0.9266268 ,  0.25102937],
       ...,
       [ 0.69691515,  0.52568913,  0.21130568, ...,  0.42498392,
         0.80869114,  0.23635457],
       [ 0.3562004 ,  0.5794751 ,  0.38135922, ...,  0.6336484 ,
         0.26392782,  0.30010447],
       [ 0.40369365,  0.89351988,  0.88817406, ...,  0.13799617,
         0.40905532,  0.05180593]], dtype=float32)

각 1000개의 샘플에 대한 카테고리는 0-9 범위의 정수로 표시됩니다. 랜덤하게 생성되어 ‘Y’라는 NDArray에 저장됩니다.

Y = mx.nd.empty((sample_count,))
for i in range(0,sample_count-1):
  Y[i] = np.random.randint(0,category_count)

>>> Y.shape
(1000L,)
>>> Y[0:10].asnumpy()
array([ 3.,  3.,  1.,  9.,  4.,  7.,  3.,  5.,  2.,  2.], dtype=float32)

데이터셋 나누기
다음 단계로 학습과 검증을 위해 데이터셋을 80/20으로 나눠야합니다. 이를 위해 NDArray.crop 함수를 사용합니다. 여기서 데이터 세트는 완전 무작위이므로 학습을 위해 상위 80%를 사용하고, 유효성을 검사하기 위해 하위 20%를 사용합니다. 실제 정식으로 할 때는 순차적으로 생성 된 데이터에 잠재적인 편향을 피하기 위해 데이터셋을 먼저 뒤섞어 놓아야 할 것입니다.

X_train = mx.nd.crop(X, begin=(0,0), end=(train_count,feature_count-1))

X_valid = mx.nd.crop(X, begin=(train_count,0), end=(sample_count,feature_count-1))

Y_train = Y[0:train_count]

Y_valid = Y[train_count:sample_count]

자, 이제 데이터 준비가 끝났습니다. 다음 단계로 넘어가 볼까요?

신경망 바인딩
우리가 만든 네트워크는 매우 간단합니다. 각 레이어를 살펴 보겠습니다.

  • 입력 레이어는 ‘data’라는 심볼로 표현됩니다. 나중에 실제 입력 데이터에 바인딩 할 것입니다.
data = mx.sym.Variable('data')
  • fc1에서 첫 번째 숨겨진 레이어는 64개 연결된 신경(Neurons)으로 구성됩니다. 즉, 입력 레이어의 각 기능은 64개의 모든 신경에 연결됩니다. 이를 위해 Symbol.FullyConnected 함수를 사용합니다.이 함수는 수동으로 각 연결을 만드는 것보다 훨씬 편리합니다!
fc1 = mx.sym.FullyConnected(data, name='fc1', num_hidden=64)
  • fc1의 각 결과 출력은 활성화 함수를 통해 수행합니다. 활성화 함수란 Sigmod처럼 어디에 속하는지 분류하기 위해 일정 값을 두고 그 값을 넘어야 성공 혹은 참으로 분류하는 함수 입니다. 여기서 우리는 Rectified Linear Unit , 일명 ‘ReLU’를 사용합니다.  (역자주: ReLU는 신경망 학습에서 매우 중요한 성능을 높이는 판단 기준을 제안한 것으로, 출력이 0보다 작을 때는 0을 사용하고, 0보다 큰 값에 대해서는 해당 값을 그대로 사용하는 방법입니다. 음수에 대해서는 값이 바뀌지만, 양수에 대해서는 값을 바꾸지 않습니다.)
relu1 = mx.sym.Activation(fc1, name='relu1', act_type="relu")
  • fc2에서 두 번째 숨겨진 레이어는 연결된 10개의 뉴런으로 구성되며 10개의 카테고리에 매핑됩니다. 각 뉴런은 임의의 부동 소수점 값을 출력합니다. 10개의 값 중 가장 큰 값은 데이터 샘플의 가장 가능성 있는 카테고리를 나타냅니다.
fc2 = mx.sym.FullyConnected(relu1, name='fc2', num_hidden=category_count)
  • 출력 레이어는 fc2 레이어에서 10개의 값에 Softmax 함수를 적용합니다.이 값은 0과 1사이의 10 개의 값으로 변환되어 1을 가산합니다. 각 값은 각 카테고리의 예상 확률을 나타내며 가장 가능성 있는 큰 카테고리를 가리키게 됩니다. (역자주: Softmax 함수는 자연수 N개의 값에서, n 번째 값의 중요도를 찾는 함수로서 각각의 값의 편차를 확대시켜 큰 값은 상대적으로 더 크게, 작은 값은 상대적으로 더 작게 만든 다음 정규화 시키는 함수이다.)
out = mx.sym.SoftmaxOutput(fc2, name='softmax')
mod = mx.mod.Module(out)

데이터 반복자(Iterator) 만들기
Part 1에서는 한 번에 학습되지 않은 하나의 샘플을 가진 신경망를 살펴 보았습니다. 성능 관점에서 볼 때 매우 비효율적이죠. 대신에 고정 된 수의 샘플인 배치(Batch)를 사용합니다.

이러한 배치를 네트워크에 전달하려면 NDArrayIter 함수를 사용하여 반복기(Interna)를 만들어야합니다. 그 파라미터는 학습 데이터, 카테고리 (MXNet에서는 라벨(Label)이라고 부름) 및 배치 크기입니다.

아래 처럼, 한 번에 10개의 샘플과 10개의 레이블로 데이터셋을 반복 할 수 있습니다. 그런 다음 reset() 함수를 호출하여 반복기 상태를 원래로 복원합니다.

train_iter = mx.io.NDArrayIter(data=X_train,label=Y_train,batch_size=batch)
>>> for batch in train_iter:
...   print batch.data
...   print batch.label
...
[<NDArray 10x99 @cpu(0)>]
[<NDArray 10 @cpu(0)>]
[<NDArray 10x99 @cpu(0)>]
[<NDArray 10 @cpu(0)>]
[<NDArray 10x99 @cpu(0)>]
[<NDArray 10 @cpu(0)>]
<edited for brevity>
>>> train_iter.reset()

우리가 만든 데이터셋을 통한 네트워크는 이제 학습 준비가 끝났습니다!

모델 학습하기
먼저 입력 심볼을 실제 데이터셋 (샘플 및 레이블)에 바인딩합니다. 반복기를 통해 쉽게 할 수 있겠죠?

mod.bind(data_shapes=train_iter.provide_data, label_shapes=train_iter.provide_label)

다음으로, 네트워크에서 뉴런 가중치를 초기화 해 봅시다. 이것은 실제로 매우 중요한 단계입니다. “올바른”기술로 초기화하면 네트워크가 훨씬 빨리 학습하는 데 도움이 됩니다. Xavier 초기화(이것을 만든 Xavier Glorot ( PDF)의 이름을 따서 명명)는 이러한 기술 중 하나입니다.

# Allowed, but not efficient
mod.init_params()
# Much better
mod.init_params(initializer=mx.init.Xavier(magnitude=2.))

다음으로 최적화 매개 변수를 정의해야 합니다.

  • 우리는 기계 학습 및 딥러닝 애플리케이션에 오랫동안 사용해온 Stochastic Gradient Descent 알고리즘 (일명 SGD)을 사용하고 있습니다.
  • 우리는 학습 속도를 SGD의 일반적 값인 0.1로 설정하고 있습니다.
mod.init_optimizer(optimizer='sgd', optimizer_params=(('learning_rate', 0.1), ))

이제 신경망에 대한 학습을 시작합니다. 50번의 학습 단계(Epochs)를 반복합니다. 즉, 전체 데이터셋이 네트워크를 통해 50회 (10개 샘플 배치 처리)로 진행합니다.

mod.fit(train_iter, num_epoch=50)
INFO:root:Epoch[0] Train-accuracy=0.097500
INFO:root:Epoch[0] Time cost=0.085
INFO:root:Epoch[1] Train-accuracy=0.122500
INFO:root:Epoch[1] Time cost=0.074
INFO:root:Epoch[2] Train-accuracy=0.153750
INFO:root:Epoch[2] Time cost=0.087
INFO:root:Epoch[3] Train-accuracy=0.162500
INFO:root:Epoch[3] Time cost=0.082
INFO:root:Epoch[4] Train-accuracy=0.192500
INFO:root:Epoch[4] Time cost=0.094
INFO:root:Epoch[5] Train-accuracy=0.210000
INFO:root:Epoch[5] Time cost=0.108
INFO:root:Epoch[6] Train-accuracy=0.222500
INFO:root:Epoch[6] Time cost=0.104
INFO:root:Epoch[7] Train-accuracy=0.243750
INFO:root:Epoch[7] Time cost=0.110
INFO:root:Epoch[8] Train-accuracy=0.263750
INFO:root:Epoch[8] Time cost=0.101
INFO:root:Epoch[9] Train-accuracy=0.286250
INFO:root:Epoch[9] Time cost=0.097
INFO:root:Epoch[10] Train-accuracy=0.306250
INFO:root:Epoch[10] Time cost=0.100
...
INFO:root:Epoch[20] Train-accuracy=0.507500
...
INFO:root:Epoch[30] Train-accuracy=0.718750
...
INFO:root:Epoch[40] Train-accuracy=0.923750
...
INFO:root:Epoch[50] Train-accuracy=0.998750
INFO:root:Epoch[50] Time cost=0.077

훈련의 정확도는 빠르게 상승하여 50 단계 후에 99+%에 도달합니다. 우리가 만든 네트워크에서 학습 세트를 잘 수행을 했으며, 아주 좋은 결과를 보입니다. 이제 유효성 검사를 수행해 보겠습니다.

모델 유효성 검사하기
이제 우리는 네트워크에 새로운 데이터 샘플, 즉 학습에 사용하지 않은 데이터 20%를 통해 유효성 검사를 해보겠습니다. 유효성 검사 샘플과 라벨을 사용하여 반복기를 먼저 만듭니다.

pred_iter = mx.io.NDArrayIter(data=X_valid,label=Y_valid, batch_size=batch)

그 다음으로 Module.iter_predict() 함수를 사용하여 네트워크를 통해 20% 샘플을 실행합니다. 이제 예상 라벨과 실제 라벨을 비교합니다. 유효성 점수와 검증 정확도를 추적하는데, 이는 네트워크가 유효성 검증셋의 수행 결과가 잘 되었는지 알아 볼 수 있습니다.

pred_count = valid_count
correct_preds = total_correct_preds = 0

for preds, i_batch, batch in mod.iter_predict(pred_iter):
    label = batch.label[0].asnumpy().astype(int)
    pred_label = preds[0].asnumpy().argmax(axis=1)
    correct_preds = np.sum(pred_label==label)
    total_correct_preds = total_correct_preds + correct_preds

print('Validation accuracy: %2.2f' % (1.0*total_correct_preds/pred_count))

뭔가 막 잘되고 있는 것 같죠? 🙂

iter_predict()는 다음 값을 반환합니다.

  • i_batch : Batch 번호
  • batch : NDArrays 배열로 현재 배치를 저장하는 단일 NDArray가 있습니다. 현재 배치에서 10개의 데이터 샘플의 레이블을 찾는 데 사용합니다. 레이블을 numpy 배열 (10개 요소)에 저장합니다.
  • preds : NDArrays의 배열로 현재 배치에 대해 예측된 레이블을 저장하는 단일 NDArray가 있습니다. 각 샘플에 대해 10개의 범주 (10×10 매트릭스)에 대한 확률을 예측합니다. 따라서, argmax() 함수를 사용하여 가장 높은 값, 즉 가장 가능성이 큰 카테고리의 인덱스를 찾습니다. 특히, pred_label은 현재 배치의 각 데이터 샘플에 대한 예측 카테고리를 보유하는 10 요소 배열입니다.

그런 다음 Numpy.sum()을 사용하여 label과 pred_label의 동일한 값의 수를 비교합니다.

마지막으로 검증 정확도를 계산하여 표시합니다.

Validation accuracy: 0.09

음… 9%라니? 이것은 정말로 나쁜 결과네요! 하지만, 데이터 세트가 더 무작위였다면 결과는 더 좋을 것입니다.

결론은 실제로 신경망 학습으로 많은 것을 배울 수 있지만, 우리의 결과처럼 데이터가 무의미하다면 아무 것도 예측할 수 없게됩니다. 쓰레기 입력, 쓰레기 출력(Garbage in, garbage out)이 있을 뿐이죠!

하지만, MXNet으로 신경망 학습을 하는 아주 기본적인 소스 코드를 배울 수 있었습니다. 자신의 실제 데이터에 사용하려면 시간을 투자하십시오. 이것이 배우는 가장 빠른 방법입니다. 다음 글 부터는 실제 데이터를 활용하여 학습을 해보겠습니다.

다음 글: MXNet 시작하기 (4) – 이미지 분류를 위한 학습 모델 사용하기 (Inception v3)

연재 순서

코드 전체 보기

- ;

Disclaimer- 본 글은 개인적인 의견일 뿐 제가 재직했거나 하고 있는 기업의 공식 입장을 대변하거나 그 의견을 반영하는 것이 아닙니다. 사실 확인 및 개인 투자의 판단에 대해서는 독자 개인의 책임에 있으며, 상업적 활용 및 뉴스 매체의 인용 역시 금지함을 양해해 주시기 바랍니다. (The opinions expressed here are my own and do not necessarily represent those of current or past employers. Please note that you are solely responsible for your judgment on chcking facts for your investiments and prohibit your citations as commercial content or news sources.)


여러분의 생각

Comments are closed.