텐서플로 계층을 활용한 딥러닝 신경망 만들기


Samples from the MNIST test data set
source: Josef Steppan on Wikimedia Commons
원문 : Building deep learning neural networks using TensorFlow layers


Deep Learning은 컴퓨터 비전이나 자연언어처리(NLP), 구문 번역이나 음성을 구문으로 바꿔주는 경우와 같이 다양한 분야에서 그 효율성을 증명하고 있습니다. 이 이름은 머신러닝 동작을 수행하는 신경망을 구성하는데 쓰이는 계층의 수가 많은 것에서부터 나왔습니다. 전체적인 망 구조로 봤을 때 다양한 종류의 계층들이 존재하지만, 일반적으로 이런 망이 깊으면 깊어질수록 더 많은 복잡성이 동반됩니다. 이번 글에서는 신경망의 기초적인 개념과 Tensorflow를 사용해서 여러 종류를 만들어 나가는 과정에 대해서 설명할 것입니다. 



Barbara Fusinska 지음
Barbara는 10년간의 스타트업 및 글로벌 회사의 경력을 쌓은 소프트웨어 개발자이자, 아키텍트이며 팀 리더다. Barbara는 수많은 상식을 통한 실제 사례와 패턴을 사용해서 아름다운 시스템구조를 설계하는 것을 즐긴다. 이 열정은 팀 운용이나 사람들로 하여금 그들의 잠재력을 발휘할 수 있는 최적의 환경을 조성하는데 큰 신념으로 작용한다. 
·twitter @basiaFusinska  ·blog http://barbarafusinska.com


강찬석 옮김 | 한 전자회사에서 시스템 소프트웨어 엔지니어로 있으면서 통신 관련 소프트웨어를 개발중이다. 컴퓨터에 관해서 다양하고 광범위한 주제에 관심을 가지고 있으며, 배운 지식을 블로그를 통해 공유하는 것을 좋아한다.



텐서플로Tensorflow는 인공지능AI를 조금 더 대중적으로 다가가게끔 기여한 플랫폼입니다. 또한 폭넓은 커뮤니티와 훌륭한 지원을 받는 오픈소스 라이브러리입니다. Tensorflow는 신경망 구조를 만들고, 학습시키고 모델을 만드는데 필요한 여러 종류의 도구를 제공합니다. 이것은 다양한 단계의 추상화 과정을 제공함으로써 사용자가 고차원 상에서 머신러닝 과정을 그대로 활용할 수도 있고, 또는 좀더 깊게 파고들어 낮은 단계에서 직접 수학적 계산을 구현해볼 수도 있습니다.

Tensorflow는 tf.layers 패키지에서 다양한 종류의 계층을 제공합니다. 이 모듈은 딥러닝에 대해서 자세히 설정할 필요없이 쉽게 계층을 만들수 있게 해줍니다. Convolution 신경망에서 주로 쓰이는 계층을 지원하기도 하고 RNN과 같이 다른 종류의 신경망을 만들 때는 tf.contrib.rnn 이나 tf.nn을 살펴볼 필요가 있습니다. 가장 기본적으로 쓰이는 계층은 완전 연결Fully-Connected 계층입니다. 이걸 구현하기 위해서는 단지 입력과 크기를 Dense Class에 설정해주면 됩니다. 계층의 종류에 따라서는 다른 인자가 필요할 수 있겠지만, 대부분 그런 값들은 개발자의 시간을 절약하는 측면에서 기본값으로 구현되어 있습니다.

계층의 정의에 대해서 약간의 불일치한 부분이 있습니다. 그 중 한 의견은 “계층은 weight나 bias와 같이 학습된 인자를 가지고 있어야 한다.” 는 것입니다. 이 말은, 예를 들어, 활성 함수Activation function를 적용하는 것 자체가 계층이 아니라는 것을 표현하는 것입니다. 사실, tf.layers 는 활성 인자를 사용함으로써 함수를 구현합니다. 물론 모듈 내에서 소개되고 있는 계층들이 항상 이 규칙을 따르는 것은 아닙니다. 해당 모듈 내에서 다양한 형식의 계층을 확인할 수 있습니다. Fully connected, convolution, pooling, flatten, batch normalization, dropout, convolution transpose와 같은 계층들이 그런 것들입니다. 예를 들어 flattening과 max pooling과 같은 계층들은 학습 과정에서 학습과 관련된 어떠한 인자도 저장하지 않습니다. 그럼에도 불구하고 해당 계층들은 활성 함수보다 더 복잡한 동작들을 수행합니다. 그래서 모듈을 만든 사람은 그런 것들을 별도의 클래스로 설정하기로 결정합니다. 아마 이 글의 후반부에서 Deep convolutional Network을 만드는데 그것들을 어떻게 사용하게 되는지에 대해서 다루게 될 것입니다. 

일반적인 convolutional network은 convolution 계층과 pooling 계층이 연속된 모양에서 몇 개의 Fully connected layer가 따라오는 형태로 구성되어 있습니다. Convolution 계층이란 반복적으로 적용되는 작은 신경망과 같은 것인데, 각 위치에서 입력으로 작용합니다. 결과적으로 신경망은 점점 작아지지만, 깊이는 점점 깊어지게 됩니다. Pooling은 일반적으로 입력 이미지의 크기를 줄이는 동작을 말하는데, 많이 쓰이는 Max pooling이란 가장 흔하게 쓰이는 pooling 알고리즘이며, 수많은 컴퓨터 비전 작업에서 그 효율성을 입증해 왔습니다.

이번 글에서 저는 우리가 예제로 쓸 MNIST data set을 사용해서 이미지 처리를 위한 convolution 망에 tensorflow를 적용하는 방법에 대해 보여드릴 것입니다. 이 작업은 손으로 쓰여진 0에서 9까지의 숫자를 인식하는 것입니다.

우선 Tensorflow는 데이터를 저장하는 기능을 가지고 있습니다. 개발자가 해야 할 일은 input_data 모듈을 사용하는 것입니다.

from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets(folder_path, one_hot=True)


이제 다층 구조를 만들 것입니다. 학습 절차에 대해서 소개한 이후에 각각의 계층을 만들고 MNIST 분류 작업에 해당 계층들을 적용해보는 것을 해볼 것입니다. 

학습 과정은 신경망의 예측값과 실제 값간의 차이를 측정하는 손실함수loss function을 최적화하는 작업을 하는데, 딥러닝에서는 이 손실을 정의하는데 cross entropy라는 기법을 자주 사용합니다.

Tensorflow는 tf.losses.softmax_cross_entropy라는 함수를 제공하는데, 이 함수는 내부적으로 해당 모델의 평준화되지 않은 예측에 softmax 알고리즘을 적용하고 전체 클래스에 걸쳐서 결과를 더하는 동작을 합니다. 우리 예제에서는 tf.train API를 통해 제공되는 Adam optimizer를 사용합니다. labels는 학습과 테스트 과정에 제공될 것이며, 이를 이용해서 의미를 파악하는데 사용될 것입니다. output은 신경망의 예측값을 의미하며 신경망을 직접 만드는 다음 섹션에서 정의될 예정입니다.

loss = tf.losses.softmax_cross_entropy(labels, output)
train_step = tf.train.AdamOptimizer(1e-4).minimize(loss)

 

학습 과정에 대한 성능을 측정하기 위해서 우리는 예측값과 실제 결과를 비교하고 정확성을 계산할 겁니다.

correct_prediction = tf.equal(tf.argmax(output, 1), tf.argmax(labels, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))


이제 batch와 몇가지 스탭과 learning rate를 이용해서 간단한 학습 과정을 소개해 볼 것입니다. MNIST data set에서는 next_batch 함수는 단순히 mnist.train.next_batch를 호출하는 역할을 합니다. 신경망이 학습된 이후에 우리는 test data에 대해서도 성능을 확인해 볼 수 있습니다.

# 세션 열기
sess = tf.InteractiveSession()
sess.run(tf.global_variables_initializer())

for i in range(steps):
# 다음 batch를 얻어옴
   input_batch, labels_batch = next_batch(100)
   feed_dict = {x_input: input_batch, y_labels: labels_batch}

# 매 100 step 마다 현재 batch에 대한 정확성을 계산하고 출력함
   if i%100 == 0:
      train_accuracy = accuracy.eval(feed_dict=feed_dict)
      print("Step %d, training batch accuracy %g"%(i, train_accuracy))

   # 최적화 과정을 수행함
   train_step.run(feed_dict=feed_dict)

# 학습이 끝난 이후에 test data에 대한 정확성을 출력함
print("Test accuracy: %g"%accuracy.eval(feed_dict={x_input: test_images, y_labels: test_labels}))

 

실제 학습를 위해, 단순하게 하나의 출력 계층을 가지는 신경망을 만들어 봅시다. 우선 입력 데이터와 레이블 값을 담을 placeholder를 정의하는 것에서부터 시작해봅니다. 학습과정에서 앞에서 만든 placeholder에는 MNIST data set으로부터 나오는 데이터들이 채워질 것입니다. 원 데이터가 평평하기 때문에 입력 계층은 1차원으로 구성됩니다. 출력 계층의 크기는 레이블의 개수를 따라갑니다. 입력과 레이블은 추가적인 차원 집합을 none으로 가지게 되는데 이는 다양한 개수의 예제를 다루기 위해서입니다. 

input = tf.placeholder(tf.float32, [None, image_size*image_size])
labels = tf.placeholder(tf.float32, [None, labels_size])


이제 가장 흥미로운 파트인 출력 계층을 만들 시간입니다. 여기에 쓰이는 방법은 매우 직관적입니다. 여기에 포함되어 있는 모든 뉴런들은 각각 weight의 bias 인자를 가지고, 모든 입력으로부터 데이터를 얻어오며, 몇가지 연산을 수행하게 됩니다. 이게 Fully-connected 계층을 만드는 과정입니다.

Tensorflow의 tf.layers 패키지는 위의 모든 과정을 단 한줄의 코드로 공식화할 수 있게끔 되어 있습니다. 개발자에게 필요한 것은 단지 입력 data와 계층의 크기뿐입니다. 

output = tf.layers.dense(inputs=input, units=labels_size)


우리가 처음으로 만들어본 신경망은 정확성 측면에서 그렇게 인상적이지는 않지만, 간단하고 그만큼 빠르게 동작합니다.

이제 입력과 출력 사이에 계층을 추가함으로써 신경망을 개선해보겠습니다. 이것들을 hidden 계층들이라고 합니다. 우선 또다른 Fully connected 계층을 추가합니다.

이전 구조에서 약간의 변화가 필요합니다. 우선 hidden 계층내의 뉴런의 개수를 정의할 인자가 있습니다. 그리고 정의 그대로 입력 데이터를 받고 출력 계층에 연결시켜줍니다. 

hidden = tf.layers.dense(inputs=input, units=1024, activation=tf.nn.relu)
output = tf.layers.dense(inputs=hidden, units=labels_size)


이번에는 활성인자를 사용했습니다. 활성 인자는 뉴런의 활성 함수를 통해서 동작하는데, 이번 경우에는 이 활성 함수로써 ReLU를 사용했습니다. 이 알고리즘은 딥러닝 구조내에서 잘 동작하는 것으로 증명되어왔습니다.

성능 측면에서 봤을 때 약간 떨어지는 것을 확인할 수 있습니다. 우리가 만들고자 하는 신경망은 점점 깊어질텐데, 이 말은 조금 더 튜닝된 인자들이 필요할 것이고, 이 과정으로 인해 학습 과정이 점점 길어지게 됩니다. 반면 이 과정을 통해서 정확성을 94%까지 많이 높일 수 있게 됩니다. 

우리가 추가하고자 하는 다음 두 계층은 convolution 신경망의 적분 파트입니다. 이 것들은 앞에서 소개한 dense와 다르게 동작하고, 이미지와 같이 2차원 이상의 입력에 대해서 특히 잘 동작합니다. Convolutional 계층의 인자로는 Convolution의 window 크기와 필터의 수가 들어갑니다. Same의 패딩 집합은 결과 계층도 같은 크기라는 것을 나타냅니다. 이 과정을 거친 후에 우리는 max pooling을 적용합니다.

Convolution을 사용함으로써, 우리는 입력 데이터의 2차원적 특성을 활용할 수 있게 됩니다. 이건 우리가 이전 숫자 이미지를 평탄화하면서 잃었고, 이렇게 처리된 데이터가 dense 계층에 들어 갔었습니다. 다시 원래 구조로 돌아가기 위해 우리는 tf.reshape 함수를 쓸 수 있습니다. 

input2d = tf.reshape(input, [-1,image_size,image_size,1])


Convolution과 max pooling에 대한 코드는 다음과 같습니다. 참고로 dense 계층과의 접합을 위해서 결과값은 다시 평탄화되어야 합니다. 

conv1 = tf.layers.conv2d(inputs=input2d, filters=32, kernel_size=[5, 5], padding="same", activation=tf.nn.relu)
pool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=[2, 2], strides=2)
pool_flat = tf.reshape(pool1, [-1, 14 * 14 * 32])
hidden = tf.layers.dense(inputs= pool_flat, units=1024, activation=tf.nn.relu)
output = tf.layers.dense(inputs=hidden, units=labels_size)

 

사진에 대해서 convolution을 취함으로써 정확성이 97%까지 증가하였지만, 학습 과정이 매우 느려졌습니다. 해당 모델에 대한 모든 이점을 취하기 위해서, 우리는 다음 계층에 대한 작업을 해야 합니다. 다시 2차원 입력을 사용하지만 두번째 계층에서 나온 결과값만 평탄화하면 됩니다. 첫번째 출력은 평탄화하지 않아도 되는데, 그 이유는 convolution이 고차원에서 동작하기 때문입니다.

conv2 = tf.layers.conv2d(inputs=pool1, filters=64, kernel_size=[5, 5], padding="same", activation=tf.nn.relu)
pool2 = tf.layers.max_pooling2d(inputs=conv2, pool_size=[2, 2], strides=2)
pool_flat = tf.reshape(pool2, [-1, 7 * 7 * 64]) 


여기서 코드를 수행할 때 조금 인내를 가지고 기다려야 합니다. 지금 신경망의 복잡성이 매우 큰 부하로 작용하긴 하지만, 우리는 그만큼 더 나은 정확성을 갖게 됩니다.

이제 우리는 신경망의 성능을 높이고 overfitting 현상을 피할 수 있는 또다른 기법에 대해서 소개하고자 합니다. Dropout이라는 불리는데, 이걸 hidden dense 계층에 적용할 것입니다. Dropout은 각각의 node가 멈춰있거나, 뭔가 뚜렷한 확률을 가지고 있을 때 동작합니다. 이 방법은 학습 과정에서 쓰이기 때문에, 만약 지금 만든 신경망을 평가해보고자 한다면 해당 방법을 꺼야 합니다.

should_drop = tf.placeholder(tf.bool) 


두번째로 dropout을 정의하고 이것을 출력 계층에 연결해야 합니다. 그외 나머지 구조는 동일합니다.

hidden = tf.layers.dense(inputs=pool_flat, units=1024, activation=tf.nn.relu)
dropout = tf.layers.dropout(inputs=hidden, rate=0.5, training=should_drop)
output = tf.layers.dense(inputs=dropout, units=labels_size)


이 글을 통해서 우리는 딥러닝의 개념에 대해 소개해봤고, 다중 계층으로 구성된 convolutional 신경망을 만드는데 TensorFlow를 활용해보았습니다. 여기 쓰인 코드는 이미지 인식 작업을 할 때 다시 쓸수도 있고, 다른 데이터 집합에도 적용할 수 있습니다. 하지만 조금더 복잡한 이미지 같은 경우에는 inception이나 ResNet과 같이 조금더 꼬아진 형태와 같이 더 깊은 형태가 필요합니다. 

이번 예제의 핵심을 말하자면 AI 모델을 만들기 위해서 통계학적 지식을 통달하거나 복잡한 행렬 곱셈 코드를 작성할 필요가 없다는 것입니다. TensorFlow가 이런 작업들을 다 해줍니다. 다만 어떤 알고리즘이 당신의 데이터와 애플리케이션에 적합한 것인지, 또는 신경망 구조와 같은 환경에서 최적의 hyperparameter를 정의하거나, 계층의 깊이, batch의 크기, 학습률 같은 것에 대해서는 알 필요가 있습니다. TensorFlow와 같이 라이브러리 내에서 다양한 선택할 수 있는 경우에 대해서는 당신이 만드는 측면에서도 매우 큰 책임이 따른다는 것에도 유념해야 할 것입니다. 


텐서플로 첫걸음