블로그 이미지
평범하게 살고 싶은 월급쟁이 기술적인 토론 환영합니다.같이 이야기 하고 싶으시면 부담 말고 연락주세요:이메일-bwcho75골뱅이지메일 닷컴. 조대협


Archive»


 
 


텐서플로우로 모델을 만들어보자

Softmax를 이용한 숫자 인식

조대협 (http://bcho.tistory.com)


텐서플로우와 머신러닝에 대한 개념에 대해서 대략적으로 이해 했으면 간단한 코드를 한번 짜보자.

MNIST

그러면 이제 실제로 텐서플로우로 모델을 만들어서 학습을 시켜보자. 예제에 사용할 시나리오는 MNIST (Mixed National Institute of Standards and Technology database) 라는 데이타로, 손으로 쓴 숫자이다. 이 손으로 쓴 숫자 이미지를 0~9 사이의 숫자로 인식하는 예제이다.



이 예제는 텐서플로우 MNIST 튜토리얼 (https://www.tensorflow.org/tutorials/mnist/beginners/) 을 기반으로 작성하였는데, 설명이 빠진 부분과 소스코드 일부분이 수정되었으니 내용이 약간 다르다는 것을 인지해주기를 바란다.


MNIST 숫자 이미지를 인식하는 모델을 softmax 알고리즘을 이용하여 만든 후에, 트레이닝을 시키고, 정확도를 체크해보도록 하겠다.

데이타셋

MNIST 데이타는 텐서플로우 내에 라이브러리 형태로 내장이 되어 있어서 쉽게 사용이 가능하다. tensorflow.examples.tutorials.mnist 패키지에 데이타가 들어 있는데, read_data_sets 명령어를 이용하면 쉽게 데이타를 로딩할 수 있다.


데이타 로딩 코드

import tensorflow as tf

from tensorflow.examples.tutorials.mnist import input_data


mnist = input_data.read_data_sets('/tmp/tensorflow/mnist/input_data', one_hot=True)


Mnist 데이타셋에는 총 60,000개의 데이타가 있는데, 이 데이타는  크게 아래와 같이 세종류의 데이타 셋으로 나눠 진다. 모델 학습을 위한 학습용 데이타인 mnist.train 그리고, 학습된 모델을 테스트하기 위한 테스트 데이타 셋은 minst.test, 그리고 모델을 확인하기 위한 mnist.validation 데이타셋으로 구별된다.

각 데이타는 아래와 같이 학습용 데이타 55000개, 테스트용 10,000개, 그리고, 확인용 데이타 5000개로 구성되어 있다.


데이타셋 명

행렬 차원

데이타 종류

노트

mnist.train.images

55000 x 784

학습 이미지 데이타


mnist.train.labels

55000 x 10

학습 라벨 데이타


mnist.test.images

10000 x 784

테스트용 이미지 데이타


mnist.test.labels

10000 x 10

테스트용 라벨 데이타


mnist.validation.images

5000 x 784

확인용 이미지 데이타


mnist.validation.labels

5000 x 10

확인용 라벨 데이타



각 데이타셋은 학습을 위한 글자 이미지를 저장한 데이타 image 와, 그 이미지가 어떤 숫자인지를 나타낸 라벨 데이타인 label로 두개의 데이타 셋으로 구성되어 있다.

이미지

먼저 이미지 데이타를 보면 아래 그림과 같이 28x28 로 구성되어 있는데,


이를 2차원 행렬에서 1차원으로 쭈욱 핀 형태로 784개의 열을 가진 1차원 행렬로 변환되어 저장이 되어 있다.

mnist.train.image는 이러한 784개의 열로 구성된 이미지가 55000개가 저장이 되어 있다.


텐서플로우의 행렬을 나타내는 shape의 형태로는 shape=[55000,784] 이 된다.


마찬가지로, mnist.train.image 도 784개의 열로 구성된 숫자 이미지 데이타를 10000개를 가지고 있고 텐서플로우의 shape으로는 shape=[10000,784] 로 표현될 수 있다.


라벨

Label 은 이미지가 나타내는 숫자가 어떤 숫자인지를 나타내는 라벨 데이타로 10개의 숫자로 이루어진 1행 행렬이다. 0~9 순서로, 그 숫자이면 1 아니면 0으로 표현된다. 예를 들어 1인경우는 [0,1,0,0,0,0,0,0,0,0,0]  9인 경우는 [0,0,0,0,0,0,0,0,0,1] 로 표현된다.

이미지 데이타에 대한 라벨이기 때문에, 당연히 이미지 데이타 수만큼의 라벨을 가지게 된다.



Train 데이타 셋은 이미지가 55000개 였기 때문에, Train의 label의 수 역시도 55000개가 된다.


소프트맥스 회귀(Softmax regression)

숫자 이미지를 인식하는 모델은 많지만, 여기서는 간단한 알고리즘 중 하나인 소프트 맥스 회귀 모델을 사용하겠다.

소프트맥스 회귀에 대한 알고리즘 자체는 자세히 설명하지 않는다. 소프트맥스 회귀는 classification 알고리즘중의 하나로, 들어온 값이 어떤 분류인지 구분해주는 알고리즘이다.

예를 들어 A,B,C 3개의 결과로 분류해주는 소프트맥스의 경우 결과값은 [0.7,0.2,0.1] 와 같이 각각 A,B,C일 확률을 리턴해준다. (결과값의 합은 1.0이 된다.)


(cf. 로지스틱 회귀는 두 가지로만 분류가 가능하지만, 소프트맥스 회귀는 n 개의 분류로 구분이 가능하다.)


모델 정의

소프트맥스로 분류를 할때, x라는 값이 들어 왔을때, 분류를 한다고 가정했을때, 모델에서 사용하는 가설은 다음과 같다.  

y = softmax (W*x + b)

W는 weight, 그리고 b는 bias 값이다.

y는 최종적으로 10개의 숫자를 감별하는 결과가 나와야 하기 때문에, 크기가 10인 행렬이 되고,

10개의 결과를 만들기 위해서 W역시 10개가 되어야 하며, 이미지 하나는 784개의 숫자로 되어 있기 때문에, 10개의 값을 각각 784개의 숫자에 적용해야 하기 때문에, W는 784x10 행렬이 된다. 그리고, b 는 10개의 값에 각각 더하는 값이기 때문에, 크기가 10인 행렬이 된다.


이를 표현해보면 다음과 같은 그림이 된다.


이를 텐서플로우 코드로 표현하면 다음과 같다.

x = tf.placeholder(tf.float32, [None, 784])

W = tf.Variable(tf.zeros([784, 10]))

b = tf.Variable(tf.zeros([10]))

k = tf.matmul(x, W) + b

y = tf.nn.softmax(k)


우리가 구하고자 하는 값은 x 값으로 학습을 시켜서 0~9를 가장 잘 구별해내는 W와 b의 값을 찾는 일이다.


여기서 코드를 주의깊게 봤다면 하나의 의문이 생길것이다.

x의 데이타는 총 55000개로, 55000x784 행렬이 되고, W는 784x10 행렬이다. 이 둘을 곱하면, 55000x10 행렬이 되는데, b는 1x10 행렬로 차원이 달라서 합이 되지 않는다.

텐서플로우와 파이썬에서는 이렇게 차원이 다른 행렬을 큰 행렬의 크기로 늘려주는 기능이 있는데, 이를 브로드 캐스팅이라고 한다. (브로드 캐스팅 개념 참고 - http://bcho.tistory.com/1153)

브로드 캐스팅에 의해서 b는 55000x10 사이즈로 자동으로 늘어나고 각 행에는 첫행과 같은 데이타들로 채워지게 된다.


소프트맥스 알고리즘을 이해하고 사용해도 좋지만, 텐서플로우에는 이미 tf.nn.softmax 라는 함수로 만들어져 있고, 대부분 많이 알려진 머신러닝 모델들은 샘플들이 많이 있기 때문에, 대략적인 원리만 이해하고 가져다 쓰는 것을 권장한다. 보통 모델을 다 이해하려고 하다가 수학에서 부딪혀서 포기하는 경우가 많은데, 디테일한 모델을 이해하기 힘들면, 그냥 함수나 예제코드를 가져다 쓰는 방법으로 접근하자. 우리가 일반적인 프로그래밍에서도 해쉬테이블이나 트리와 같은 자료구조에 대해서 대략적인 개념만 이해하고 미리 정의된 라이브러리를 사용하지 직접 해쉬 테이블등을 구현하는 경우는 드물다.

코스트(비용) 함수

이 소프트맥스 함수에 대한 코스트 함수는 크로스엔트로피 (Cross entropy) 함수의 평균을 이용하는데, 복잡한 산식 없이 그냥 외워서 쓰자. 다행이도 크로스엔트로피 함수역시 함수로 구현이 되어있다.


Cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(tf.matmul(x, W) + b, y_))


가설에 의해 계산된 값 y를 넣지 않고 tf.matmul(x, W) + b 를 넣은 이유는 tf.nn.softmax_cross_entropy_with_logits 함수 자체가 softmax를 포함하기 때문이다.

y_은 학습을 위해서 입력된 값이다.


텐서플로우로 구현

자 그럼 학습을 위한 전체 코드를 보자


샘플코드

# Import data

from tensorflow.examples.tutorials.mnist import input_data

import tensorflow as tf

 

mnist = input_data.read_data_sets('/tmp/tensorflow/mnist/input_data', one_hot=True)


# Create the model

x = tf.placeholder(tf.float32, [None, 784])

W = tf.Variable(tf.zeros([784, 10]))

b = tf.Variable(tf.zeros([10]))

k = tf.matmul(x, W) + b

y = tf.nn.softmax(k)


# Define loss and optimizer

y_ = tf.placeholder(tf.float32, [None, 10])                                                                               

learning_rate = 0.5

cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(k, y_))

train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost)


print ("Training")

sess = tf.Session()

init = tf.global_variables_initializer() #.run()

sess.run(init)

for _ in range(1000):

   # 1000번씩, 전체 데이타에서 100개씩 뽑아서 트레이닝을 함.  

   batch_xs, batch_ys = mnist.train.next_batch(100)

   sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})


print ('b is ',sess.run(b))

print('W is',sess.run(W))

데이타 로딩

# Import data

from tensorflow.examples.tutorials.mnist import input_data

import tensorflow as tf

 

mnist = input_data.read_data_sets('/tmp/tensorflow/mnist/input_data', one_hot=True)


앞에서 데이타에 대해서 설명한것과 같이 데이타를 로딩하는 부분이다. read_data_sets에 들어가 있는 디렉토리는 샘플데이타를 온라인에서 다운 받는데, 그 데이타를 임시로 저장해놓을 위치이다.

모델 정의

다음은 소프트맥스를 이용하여 모델을 정의한다.

# Create the model

x = tf.placeholder(tf.float32, [None, 784])

W = tf.Variable(tf.zeros([784, 10]))

b = tf.Variable(tf.zeros([10]))

k = tf.matmul(x, W) + b

y = tf.nn.softmax(k)


x는 트레이닝 데이타를 저장하는 스테이크홀더, W는 Weight, b는 bias 값이고, 모델은 y = tf.nn.softmax(tf.matmul(x, W) + b) 이 된다.

코스트함수와 옵티마이저 정의

모델을 정의했으면 학습을 위해서, 코스트 함수를 정의한다.

# Define loss and optimizer

y_ = tf.placeholder(tf.float32, [None, 10])                                                                               

learning_rate = 0.5

cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(k, y_))

train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost)


코스트 함수는 크로스 엔트로피 함수의 평균값을 사용한다. 크로스엔트로피 함수는 아래와 같은 모양인데, 이 값을 전체 트레이닝 데이타셋의 수로 나눠 준다.  


그래서 최종적으로 cost 함수는 cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(k, y_)) 이 된다.

이 때 주의할점은 y가 아니라 k를 넣어야 한다. softmax_cross_entropy_with_logits 함수는 softmax를 같이 하기 때문에, 위의 y값은 이미 softmax를 해버린 함수이기 때문에 softmax가 중복될 수 있다.



이 코스트 함수를 가지고 코스트가 최소화가 되는 W와 b를 구해야 하는데, 옵티마이져를 사용한다. 여기서는 경사 하강법(Gradient Descent Optimizer)를 사용하였고 경사하강법에 대한 개념은 http://bcho.tistory.com/1141 를 참고하기 바란다.

GradientDescent에서 learning rate는 학습속도 인데, 학습 속도에 대한 개념은 http://bcho.tistory.com/1141 글을 참고하기 바란다.

세션 초기화  

print ("Training")

sess = tf.Session()

init = tf.global_variables_initializer() #.run()

sess.run(init)


tf.Session() 을 이용해서 세션을 만들고, global_variable_initializer()를 이용하여, 변수들을 모두 초기화한후, 초기화 값을 sess.run에 넘겨서 세션을 초기화 한다.

트레이닝 시작

세션이 생성되었으면 이제 트레이닝을 시작한다.

for _ in range(1000):

   # 1000번씩, 전체 데이타에서 100개씩 뽑아서 트레이닝을 함.  

   batch_xs, batch_ys = mnist.train.next_batch(100)

   sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})


여기서 주목할점은 Batch training 과 Stochastic training 인데, Batch training이란, 학습을 할때 전체 데이타를 가지고 한번에 학습을 하는게 아니라 전체 데이타셋을 몇 개로 쪼갠후 나눠서 트레이닝을 하는 방법을 배치 트레이닝이라고 한다. 그중에서 여기에 사용된 배치 방법은 Stochastic training 이라는 방법인데, 원칙대로라면 전체 55000개 의 학습데이타가 있기 때문에 배치 사이즈를 100으로 했다면, 100개씩 550번 순차적으로 데이타를 읽어서 학습을 해야겠지만, Stochastic training은 전체 데이타중 일부를 샘플링해서 학습하는 방법으로, 여기서는 배치 한번에 100개씩의 데이타를 뽑아서 1000번 배치로 학습을 하였다.

(텐서플로우 문서에 따르면, 전체 데이타를 순차적으로 학습 시키기에는 연산 비용이 비싸기 때문에, 샘플링을 해도 비슷한 정확도를 낼 수 있기 때문에, 예제 차원에서 간단하게, Stochastic training을 사용한것으로 보인다.)


결과값 출력

print ('b is ',sess.run(b))

print('W is',sess.run(W))


마지막으로 학습에서 구해진 W와 b를 출력해보자

다음은 실행 결과 스크린 샷이다.




먼저 앞에서 데이타를 로딩하도록 지정한 디렉토리에, 학습용 데이타를 다운 받아서 압축 받는 것을 확인할 수 있다. (Extracting.. 부분)

그 다음 학습이 끝난후에, b와 W 값이 출력되었다. W는 784 라인이기 때문에, 중간을 생략하고 출력되었으나, 각 행을 모두 찍어보면 아래와 같이 W 값이 들어가 있는 것을 볼 수 있다.


모델 검증

이제 모델을 만들고 학습을 시켰으니, 이 모델이 얼마나 정확하게 작동하는지를 테스트 해보자.  mnist.test.image 와 mnist.test.labels 데이타셋을 이용하여 테스트를 진행하는데, 앞에서 나온 모델에 mnist.test.image 데이타를 넣어서 예측을 한 후에, 그 결과를 mnist.test.labels (정답)과 비교해서 정답률이 얼마나 되는지를 비교한다.


다음은 모델 테스팅 코드이다. 이 코드를 위의 코드 뒤에 붙여서 실행하면 된다.


모델 검증 코드

print ("Testing model")

# Test trained model

correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))

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

print('accuracy ',sess.run(accuracy, feed_dict={x: mnist.test.images,

                                    y_: mnist.test.labels}))

print ("done")

   

correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))

코드를 보자, tf.argmax 함수를 이해해야 하는데, argmax(y,1)은 행렬 y에서 몇번째에 가장 큰 값이 들어가 있는지를 리턴해주는 함수이다. 아래 예제 코드를 보면


session = tf.InteractiveSession()


data = tf.constant([9,2,11,4])

idx = tf.argmax(data,0)

print idx.eval()


session.close()


[9,2,11,4] 에서 최대수는 11이고, 이 위치는 두번째 (0 부터 시작한다)이기 때문에 0을 리턴한다.

두번째 변수는 어느축으로 카운트를 할것인지를 선택한다. , 1차원 배열의 경우에는 0을 사용한다.

여기서 y는 2차원 행렬인데, 0이면 같은 열에서 최대값인 순서, 1이면 같은 행에서 최대값인 순서를 리턴한다.

그럼 원래 코드로 돌아오면 tf.argmax(y,1)은 y의 각행에서 가장 큰 값의 순서를 찾는다. y의 각행을 0~9으로 인식한 이미지의 확률을 가지고 있다.

아래는 4를 인식한 y 값인데, 4의 값이 0.7로 가장높기 (4일 확률이 70%, 3일 확률이 10%, 1일 확률이 20%로 이해하면 된다.) 때문에, 4로 인식된다.

여기서 tf.argmax(y,1)을 사용하면, 행별로 가장 큰 값을 리턴하기 때문에, 위의 값에서는 4가 리턴이된다.

테스트용 데이타에서 원래 정답이 4로 되어 있다면, argmax(y_,1)도 4를 리턴하기 때문에, tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))는 tf.equals(4,4)로 True를 리턴하게 된다.


모든 테스트 셋에 대해서 검증을 하고 나서 그 결과에서 True만 더해서, 전체 트레이닝 데이타의 수로 나눠 주면 결국 정확도가 나오는데, tf.cast(boolean, tf.float32)를 하면 텐서플로우의 bool 값을 float32 (실수)로 변환해준다. True는 1.0으로 False는 0.0으로 변환해준다. 이렇게 변환된 값들의 전체 평균을 구하면 되기 때문에, tf.reduce_mean을 사용한다.


이렇게 정확도를 구하는 함수가 정의되었으면 이제 정확도를 구하기 위해 데이타를 넣어보자

sess.run(accuracy, feed_dict={x: mnist.test.images,y_: mnist.test.labels})

x에 mnist.test.images 데이타셋으로 이미지 데이타를 입력받아서  y (예측 결과)를 계산하고, y_에는 mnist.test.labels 정답을 입력 받아서, y와 y_로 정확도 accuracy를 구해서 출력한다.


최종 출력된 accuracy 정확도는 0.9 로 대략 90% 정도가 나온다.


Testing model
('accuracy ', 0.90719998)
done


다른 알고리즘의 정확도는 http://rodrigob.github.io/are_we_there_yet/build/classification_datasets_results.html 를 참고하면 된다.


다음글에서는 소프트맥스 모델 대신 CNN (Convolutional Neural Network)를 이용하여, 조금 더 정확도가 높은  MNIST를 구현하고 테스트해보도록 하겠다.


참고 자료

  • 텐서플로우 MNIST https://www.tensorflow.org/tutorials/mnist/beginners/


2017년 1월 6일 추가

위의 코드 부분에 잘못된 부분이 있어서 수정합니다.


k = tf.matmul(x, W) + b

y = tf.nn.softmax(k)


# Define loss and optimizer

y_ = tf.placeholder(tf.float32, [None, 10])                                                                               

learning_rate = 0.5

cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(k, y_))


https://github.com/tensorflow/tensorflow/blob/master/tensorflow/g3doc/api_docs/python/functions_and_classes/shard7/tf.nn.softmax_cross_entropy_with_logits.md 레퍼런스에 따르면


WARNING: This op expects unscaled logits, since it performs a softmax on logits internally for efficiency. Do not call this op with the output of softmax, as it will produce incorrect results.


tf.nn.softmax_cross_entropy_with_logits 함수는 softmax를 포함하고 있다. 그래서 softmax를 적용한 y를 넣으면 안되고 softmax 적용전인 k를 넣어야 한다.



딥러닝 - 컨볼루셔널 네트워크를 이용한 이미지 인식의 개념


조대협 (http://bcho.tistory.com)


이번 글에서는 딥러닝 중에서 이미지 인식에 많이 사용되는 컨볼루셔널 뉴럴 네트워크 (Convolutional neural network) 이하 CNN에 대해서 알아보도록 하자.


이 글을 읽기에 앞서서 머신러닝에 대한 기본 개념이 없는 경우는 다음 글들을 참고하기 바란다.



CNN은 전통적인 뉴럴 네트워크 앞에 여러 계층의 컨볼루셔널 계층을 붙인 모양이 되는데, 그 이유는 다음과 같다. CNN은 앞의 컨볼루셔널 계층을 통해서 입력 받은 이미지에 대한 특징(Feature)를 추출하게 되고, 이렇게 추출된 특징을 기반으로 기존의 뉴럴 네트워크를 이용하여 분류를 해내게 된다.




컨볼루셔널 레이어  (Convolutional Layer)

컨볼루셔널 레이어는 앞에서 설명 했듯이 입력 데이타로 부터 특징을 추출하는 역할을 한다.

컨볼루셔널 레이어는 특징을 추출하는 기능을 하는 필터(Filter)와, 이 필터의 값을 비선형 값으로 바꾸어 주는 액티베이션 함수(Activiation 함수)로 이루어진다.

그럼 각 부분의 개념과 원리에 대해서 살펴보도록 하자.


<그림 Filter와 Activation 함수로 이루어진 Convolutional 계층>

필터 (Filter)

필터 개념 이해

필터는 그 특징이 데이타에 있는지 없는지를 검출해주는 함수이다. 예를 들어 아래와 같이 곡선을 검출해주는 필터가 있다고 하자.



필터는 구현에서는 위의 그림 좌측 처럼 행렬로 정의가 된다.

입력 받은 이미지 역시 행렬로 변환이 되는데, 아래 그림을 보자.


쥐 그림에서 좌측 상단의 이미지 부분을 잘라내서 필터를 적용하는 결과이다.

잘라낸 이미지와, 필터를 곱하면

과 같이 결과 값이 매우 큰 값이 나온다.

만약에 아래 그림처럼 쥐 그림에서 곡선이 없는 부분에 같은 필터를 적용해보면


결과 값이 0에 수렴하게 나온다.


즉 필터는 입력받은 데이타에서 그 특성을 가지고 있으면 결과 값이 큰값이 나오고, 특성을 가지고 있지 않으면 결과 값이 0에 가까운 값이 나오게 되서 데이타가 그 특성을 가지고 있는지 없는지 여부를 알 수 있게 해준다.

다중 필터의 적용

입력값에는 여러가지 특징이 있기 때문에 하나의 필터가 아닌 여러개의 다중 필터를 같이 적용하게 된다.

다음과 같이 |,+,- 모양을 가지고 있는 데이타가 있다고 하자


각 데이타가 |와 - 의 패턴(특징을) 가지고 있는지를 파악하기 위해서 먼저 | (세로) 필터를 적용해보면 다음과 같은 결과가 나온다.


(맨앞의 상자는 필터이다.) 두번째 상자부터 원본 이미지에 세로선(|) 이 없는 경우 결과 이미지에 출력이 없고, 세로선이 있는 경우에는 결과 이미지에 세로 선이 있는 것을 확인할 수 있다.

마찬가지로 가로선(-) 특징이 있는지 가로 선을 추출하는 필터를 적용해보면 다음과 같은 결과를 얻을 수 있다.



이렇게 각기 다른 특징을 추출하는 필터를 조합하여 네트워크에 적용하면, 원본 데이타가 어떤 형태의 특징을 가지고 있는지 없는지를 판단해 낼 수 있다. 다음은 하나의 입력 데이타에 앞서 적용한 세로와 가로선에 대한 필터를 동시에 적용한 네트워크의 모양이다.



Stride

그러면 이 필터를 어떻게 원본 이미지에 적용할까? 큰 사진 전체에 하나의 큰 필터 하나만을 적용할까?

아래 그림을 보자, 5x5 원본 이미지가 있을때, 3x3인 필터를 좌측 상단에서 부터 왼쪽으로 한칸씩 그 다음 한줄을 내려서 또 왼쪽으로 한칸씩 적용해서 특징을 추출해낸다.

오른쪽 Convolved Feature 행렬이 바로 원본 이미지에 3x3 필터를 적용하여 얻어낸 결과 이다.



이렇게 필터를 적용 하는 간격 (여기서는 우측으로 한칸씩 그리고 아래로 한칸씩 적용하였다.) 값을 Stride라고 하고, 필터를 적용해서 얻어낸 결과를 Feature map 또는 activation map 이라고 한다.

Padding

앞에서 원본 데이타에 필터를 적용한 내용을 보면 필터를 적용한 후의 결과값은 필터 적용전 보다 작아졌다. 5x5 원본 이미지가 3x3의 1 stride 값을 가지고 적용되었을때, 결과 값은 3x3으로 크기가 작아졌다.

그런데, CNN 네트워크는 하나의 필터 레이어가 아니라 여러 단계에 걸쳐서 계속 필터를 연속적으로 적용하여 특징을 추출하는 것을 최적화 해나가는데, 필터 적용 후 결과 값이 작아지게 되면 처음에 비해서 특징이 많이 유실 될 수 가 있다. 필터를 거쳐감에 따라서 특징이 유실되는 것을 기대했다면 문제가 없겠지만, 아직까지 충분히 특징이 추출되기 전에, 결과 값이 작아지면 특징이 유실된다. 이를 방지 하기 위한 방법으로 padding 이라는 기법이 있는데, padding은 결과 값이 작아지는 것을 방지하기 위해서 입력값 주위로 0 값을 넣어서 입력 값의 크기를 인위적으로 키워서, 결과값이 작아지는 것을 방지 하는 기법이다.


다음 그림을 보자, 32x32x3 입력값이 있을때, 5x5x3 필터를 적용 시키면 결과값 (feature map)의 크기는 28x28x3 이 된다. 이렇게 사이즈가 작아지는 것을 원하지 않았다면 padding을 적용하는데, input 계층 주위로 0을 둘러 싸서, 결과 값이 작아지고 (피쳐가 소실 되는것)을 막는다

32x32x3 입력값 주위로 2 두깨로 0을 둘러싸주면 36x36x3 이 되고 5x5x3 필터 적용하더라도, 결과값 은 32x32x3으로 유지된다.


< 그림, 32x32x3 데이타에 폭이 2인 padding을 적용한 예 >


패딩은 결과 값을 작아지는 것을 막아서 특징이 유실되는 것을 막는 것 뿐 아니라, 오버피팅도 방지하게 되는데, 원본 데이타에 0 값을 넣어서 원래의 특징을 희석 시켜 버리고, 이것을 기반으로 머신러닝 모델이 트레이닝 값에만 정확하게 맞아 들어가는 오버피팅 현상을 방지한다.


오버 피팅에 대해서는 별도의 다른 글을 통해서 설명한다.

필터는 어떻게 만드는 것일까?

그렇다면 CNN에서 사용되는 이런 필터는 어떻게 만드는 것일까? CNN의 신박한 기능이 바로 여기에 있는데, 이 필터는 데이타를 넣고 학습을 시키면, 자동으로 학습 데이타에서 학습을 통해서 특징을 인식하고 필터를 만들어 낸다.

Activation function

필터들을 통해서 Feature map이 추출되었으면, 이 Feature map에 Activation function을 적용하게 된다.

Activation function의 개념을 설명하면, 위의 쥐 그림에서 곡선값의 특징이 들어가 있는지 안들어가 있는지의 필터를 통해서 추출한 값이 들어가 있는 예에서는 6000, 안 들어가 있는 예에서는 0 으로 나왔다.

이 값이 정량적인 값으로 나오기 때문에, 그 특징이 “있다 없다”의 비선형 값으로 바꿔 주는 과정이 필요한데, 이 것이 바로 Activation 함수이다.


예전에 로지스틱 회귀 ( http://bcho.tistory.com/1142 )에서 설명하였던 시그모이드(sigmoid) 함수가 이 Activation 함수에 해당한다.

간단하게 짚고 넘어가면, 결과 값을 참/거짓 으로 나타내는 것이 아니라, 참에 가까워면 0.5~1사이에서 1에 가까운 값을 거짓에 가까우면 0~0.5 사이의 값으로 리턴하는 것이다.


<그림. Sigmoid 함수>

뉴럴 네트워크나 CNN (CNN도 뉴럴 네트워크이다.) 이 Activation 함수로 이 sigmoid 함수는 잘 사용하지 않고, 아래 그림과 같은 ReLu 함수를 주요 사용한다.




<그림. ReLu 함수>

이 함수를 이용하는 이유는 뉴럴 네트워크에서 신경망이 깊어질 수 록 학습이 어렵기 때문에, 전체 레이어를 한번 계산한 후, 그 계산 값을 재 활용하여 다시 계산하는 Back propagation이라는 방법을 사용하는데, sigmoid 함수를 activation 함수로 사용할 경우, 레이어가 깊어지면 이 Back propagation이 제대로 작동을 하지 않기 때문에,(값을 뒤에서 앞으로 전달할때 희석이 되는 현상. 이를 Gradient Vanishing 이라고 한다.) ReLu라는 함수를 사용한다.

풀링 (Sub sampling or Pooling)

이렇게 컨볼루셔날 레이어를 거쳐서 추출된 특징들은 필요에 따라서 서브 샘플링 (sub sampling)이라는 과정을 거친다.


컨볼루셔널 계층을 통해서 어느정도 특징이 추출 되었으면, 이 모든 특징을 가지고 판단을 할 필요가 없다.

쉽게 예를 들면, 우리가 고해상도 사진을 보고 물체를 판별할 수 있지만, 작은 사진을 가지고도 그 사진의 내용이 어떤 사진인지 판단할 수 있는 원리이다.


그래서, 추출된 Activation map을 인위로 줄이는 작업을 하는데, 이 작업을 sub sampling 도는 pooling 이라고 한다. Sub sampling은 여러가지 방법이 있는데, max pooling, average pooling, L2-norm pooling 등이 있고, 그중에서 max pooling 이라는 기법이 많이 사용된다.


Max pooling (맥스 풀링)

맥스 풀링은 Activation map을 MxN의 크기로 잘라낸 후, 그 안에서 가장 큰 값을 뽑아내는 방법이다.

아래 그림을 보면 4x4 Activation map에서 2x2 맥스 풀링 필터를 stride를 2로 하여 2칸씩 이동하면서 맥스 풀링을 한 예인데, 좌측 상단에서는 6이 가장 큰 값이기 때문에 6을 뽑아내고, 우측 상단에는 2,4,7,8 중 8 이 가장 크기 때문에 8을 뽑아 내었다.


맥스 풀링은 특징의 값이 큰 값이 다른 특징들을 대표한다는 개념을 기반으로 하고 있다.

(주의 풀링은 액티베이션 함수 마다 매번 적용하는 것이 아니라, 데이타의 크기를 줄이고 싶을때 선택적으로 사용하는 것이다.)


이런 sampling 을 통해서 얻을 수 있는 장점은 다음과 같다.

  • 전체 데이타의 사이즈가 줄어들기 때문에 연산에 들어가는 컴퓨팅 리소스가 적어지고

  • 데이타의 크기를 줄이면서 소실이 발생하기 때문에, 오버피팅을 방지할 수 있다.


컨볼루셔널 레이어

이렇게 컨볼루셔널 필터와 액티베이션 함수 (ReLU) 그리고 풀링 레이어를 반복적으로 조합하여 특징을 추출한다.

아래 그림을 보면 여러개의 컨볼루셔널 필터(CONV)와 액티베이션 함수 (RELU)와 풀링 (POOL) 사용된것을 볼 수 있다.


Fully connected Layer

컨볼루셔널 계층에서 특징이 추출이 되었으면 이 추출된 특징 값을 기존의 뉴럴 네트워크 (인공 신경 지능망)에 넣어서 분류를 한다.

그래서 CNN의 최종 네트워크 모양은 다음과 같이 된다.



<그림. CNN 네트워크의 모양>

Softmax 함수

Fully connected network (일반적인 뉴럴 네트워크)에 대해서는 이미 알고 있겠지만, 위의 그림에서 Softmax 함수가 가장 마지막에 표현되었기 때문에, 다시 한번 짚고 넘어가자.

Softmax도 앞에서 언급한 sigmoid나 ReLu와 같은 액티베이션 함수의 일종이다.


Sigmoid 함수가 이산 분류 (결과값에 따라 참 또는 거짓을 나타내는) 함수라면, Softmax 는 여러개의 분류를 가질 수 있는 함수이다. 아래 그림이 Softmax 함수의 그림이다.




이것이 의미하는 바는 다음과 같다. P3(x)는 특징(feature) x에 대해서 P3일 확률, P1(x)는 특징 x 에 대해서 P1인 확률이다.

Pn 값은 항상 0~1.0의 범위를 가지며,  P1+P2+...+Pn = 1이 된다.


예를 들어서 사람을 넣었을때, 설현일 확률 0.9, 지현인 확율 0.1 식으로 표시가 되는 것이다.

Dropout 계층

위 CNN 그래프에서 특이한 점중 하나는 Fully connected 네트워크와 Softmax 함수 중간에 Dropout layer (드롭아웃) 라는 계층이 있는 것을 볼 수 있다.


드롭 아웃은 오버피팅(over-fit)을 막기 위한 방법으로 뉴럴 네트워크가 학습중일때, 랜덤하게 뉴런을 꺼서 학습을 방해함으로써, 학습이 학습용 데이타에 치우치는 현상을 막아준다.



<그림. 드롭 아웃을 적용한 네트워크 >

그림 출처 : https://leonardoaraujosantos.gitbooks.io/artificial-inteligence/content/dropout_layer.html


일반적으로 CNN에서는 이 드롭아웃 레이어를 Fully connected network 뒤에 놓지만, 상황에 따라서는 max pooling 계층 뒤에 놓기도 한다.


다음은 드롭아웃을 적용하고 학습시킨 모델과 드롭 아웃을 적용하지 않은 모델 사이의 예측 정확도를 비교한 결과 이다.



<그림. 드룹아웃을 적용한 경우와 적용하지 않고 학습한 경우, 에러율의 차이 >

이렇게 복잡한데 어떻게 구현을 하나요?

대략적인 개념은 이해를 했다. 그렇다면 구현을 어떻게 해야 할까? 앞에서 설명을 할때, softmax 나 뉴런에 대한 세부 알고리즘 ReLu 등과 같은 알고리즘에 대한 수학적인 공식을 설명하지 않았다. 그렇다면 이걸 하나하나 공부해야 할까?


아니다. 작년에 구글에서 머신러닝용 프로그래밍 프레임워크로 텐서 플로우라는 것을 발표했다.

이 텐서 플로우는 (http://www.tensorflow.org)는 이런 머신 러닝에 특화된 프레임웍으로, 머신러닝에 필요한 대부분의 함수들을 이미 구현하여 제공한다.

실제로 CNN을 구현한 코드를 보자. 이 코드는 홍콩 과학기술 대학교의 김성훈 교수님의 강의를 김성훈님이란 분이 텐서 플로우 코드로 구현하여 공유해놓은 코드중 CNN 구현 예제이다. https://github.com/FuZer/Study_TensorFlow/blob/master/08%20-%20CNN/CNN.py




첫번째 줄을 보면, tf.nn.conv2d 라는 함수를 사용하였는데, 이 함수는 컨볼루셔널 필터를 적용한 함수 이다. 처음 X는 입력값이며, 두번째 w 값은 필터 값을 각각 행렬로 정의 한다. 그 다음 strides 값을 정의해주고, 마지막으로 padding 인자를 통해서  padding 사이즈를 정한다.

컨볼루셔널 필터를 적용한 후 액티베이션 함수로 tf.nn.relu를 이용하여 ReLu 함수를 적용한 것을 볼 수 있다.

다음으로는 tf.nn.max_pool 함수를 이용하여, max pooling을 적용하고 마지막으로 tf.nn.dropout 함수를 이용하여 dropout을 적용하였다.


전문적인 수학 지식이 없이도, 이미 잘 추상화된 텐서플로우 함수를 이용하면, 기본적인 개념만 가지고도 머신러닝 알고리즘 구현이 가능하다.


텐서 플로우를 공부하는 방법은 여러가지가 있겠지만, 유투브에서 이찬우님이 강의 하고 계신 텐서 플로우 강의를 듣는 것을 추천한다. 한글이고 설명이 매우 쉽다. 그리고 매주 일요일에 생방송을 하는데, 궁금한것도 물어볼 수 있다.

https://www.youtube.com/channel/UCRyIQSBvSybbaNY_JCyg_vA


그리고 텐서플로우 사이트의 튜토리얼도 상당히 잘되어 있는데, https://www.tensorflow.org/versions/r0.12/tutorials/index.html 를 보면 되고 한글화도 잘 진행되고 있다. 한글화된 문서는 https://tensorflowkorea.gitbooks.io/tensorflow-kr/content/ 에서 찾을 수 있다.

구현은 할 수 있겠는데, 그러면 이 모델은 어떻게 만드나요?

그럼 텐서플로우를 이용하여 모델을 구현할 수 있다는 것은 알았는데, 그렇다면 모델은 어떻게 만들까? 정확도를 높이려면 수십 계층의 뉴럴 네트워크를 설계해야 하고, max pooling  함수의 위치와 padding등 여러가지를 고려해야 하는데, 과연 이게 가능할까?


물론 전문적인 지식을 가진 데이타 과학자라면 이런 모델을 직접 설계하고 구현하고 테스트 하는게 맞겠지만, 이런 모델들은 이미 다양한 모델이 만들어져서 공개 되어 있다.


그중에서 CNN 모델은 매년 이미지넷 (http://www.image-net.org/) 이라는데서 추최하는 ILSVRC (Large Scale Visual Recognition Competition) 이라는 대회에서, 주최측이 제시하는 그림을 누가 잘 인식하는지를 겨루는 대회이다.



<그림. 이미지넷 대회에 사용되는 이미지들 일부>


이 대회에서는 천만장의 이미지를 학습하여, 15만장의 이미지를 인식하는 정답률을 겨루게 된다. 매년 알고리즘이 향상되는데, 딥러닝이 주목 받은 계기가된 AlexNet은 12년도 우승으로, 8개의 계층으로 16.4%의 에러율을 내었고, 14년에는 19개 계층을 가진 VGG 알고리즘이 7.3%의 오차율을 기록하였고, 14년에는 구글넷이 22개의 레이어로 6.7%의 오차율을 기록하였다. 그리고 최근에는 마이크로소프트의 152개의 레이어로 ResNet이 3.57%의 오차율을 기록하였다. (참고로 인간의 평균 오류율은 5% 내외이다.)

현재는 ResNet을 가장 많이 참고해서 사용하고 있고, 쉽게 사용하려면 VGG 모델을 사용하고 있다.




결론

머신러닝과 딥러닝에 대해서 공부를 하면서 이게 더이상 수학자나 과학자만의 영역이 아니라 개발자도 들어갈 수 있는 영역이라는 것을 알 수 있었고, 많은 딥러닝과 머신러닝 강의가 복잡한 수학 공식으로 설명이 되지만, 이건 아무래도 설명하는 사람이 수학쪽에 배경을 두고 있기 때문 일것이고, 요즘은 텐서플로우 프레임웍을 사용하면 복잡한 수학적인 지식이 없이 기본적인 머신러닝에 대한 이해만을 가지고도 머신러닝 알고리즘을 개발 및 서비스에 적용이 가능한 시대가 되었다고 본다.


그림 출처 및 참고 문서