Tensorflow 그리고 ANN

Tensorflow 소개

텐서플로는 2015년 11월 처음으로 공개되었다. 텐서플로는 머신러닝과 심층인공신경망 연구를 수행하던 구글의 연구팀에서 개발한 이후 오픈소스로 세상에 공개되었다. 일반적인 머신러닝 문제나 표현 기반의 수학문제에도 적용이 가능한 범용 패키지다. 텐서플로의 특징을 한 마디로 요약하면 데이터 플로 그래프(Data Flow Graph: DFG)를 바탕으로 한 수치연산이다. 수학 연산을 하는 각각의 결절을 마디(Node)라고 하고 각각의 마디를 연결하는 선(Edge)은 다차원 배열 데이터이다. 데이터를 넘겨주면 마디에서 연산을 수행하고 다음 마디로 데이터를 넘겨주는 식이다. 겉으로 보기에는 변수=함수() 형태의 반복이다.


Tensorflow 받기

https://www.tensorflow.org/ 로 가보면 다양한 플랫폼에 설치하는 방법이 자세히 소개되어있다. 구체적인 설치 방법은 생략하기로 한다.

Python

Tensorflow를 사용하려면 Python을 배워야 한다. 또한 개인적으로는 Anaconda 환경을 설치하고 Conda를 활용해서 Tensorflow를 사용하는 편이 좋다고 본다. 당연한 이야기일 수 있지만, 프로그래밍 언어를 모르면 Tensorflow를 제대로 활용할 수 없다. Tensorflow와 Numpy를 함께 쓰는 방법, 혹은 Tensorflow와 R을 함께 쓰는 방법을 생각할 수 있다. 개인적으로 두 방법 모두 시도해보고 있기는 하다. 파이썬이 이해하기 편하기도 하고 원래 텐서플로 예제도 파이썬이므로 본 포스트는 파이썬 3.5를 기준으로 하자.

Data Mining

Tensorflow를 이해하려면 우선 기본적인 데이터 마이닝과 딥러닝에 대한 이해가 필요하다. 본인도 이 부분에 전공자가 아니기 때문에 평소에 공부를 하려고 노력 중이다(경영학 전공임). 경제수학 정도의 지식이 있고 집합론을 배운 정도로 깊이 있는 이해는 어렵지만 전체적인 맥락을 이해하기에는 무리가 없다는 생각이다.

좀 더 자세한 점이 궁금하면 http://neuralnetworksanddeeplearning.com/ 을 참고하기 바란다. 본 글은 이 책의 중심 내용을 참고로 진행된다.

부분적으로 2017년 5월 25일 연세대학교 새천년관에서 있었던 브라운백 세미나를 보충하려는 뜻도 있다.

신경망 기초

퍼셉트론

세 개의 입력값이 있다고 하자. 각각 x_1, x_2, 그리고 x_3라고 한다. 이들 각각은 0 혹은 1이다. 그리고 출력값도 하나 생각하자. 출력값도 0 아니면 1이다. 입력값에서 출력값을 어떻게 계산할까? 입력값에 곱해지는 가중치를 w_1, w_2, 그리고 w_3라고 하자. 그렇다면 선형결합(linear combination)을 생각해 볼 수 있다. 이때의 선형결합은 z=w_1*x_1+w_2*x_2+w_3*x_3 형태다. 이제 다시 z를 생각해보자. 적절한 절차를 거쳐 z를 0혹은 1로 바꿔줘야 한다(위에서 출력값이 0 혹은 1이라고 했다).  0 혹은 1을 가지는 입력값과, 그것을 선형결합하고 다시 0 혹은 1로 바꿔주는 절차를 거치는 이 과정 전체를 하나의 퍼셉트론이라고 한다.

여러 퍼셉트론

퍼셉트론을 잘 생각해보면, 간단한 선형결합 문제다. 출력값을 0 혹은 1로 바꾸는 어떤 함수가 이미 알려져 있다고 가정하면, 이분형 데이터(0 혹은 1)에서 이분형 데이터로 가는 퍼셉트론을 여러 개 묶어볼 수도 있다. 어차피 입력값도 이분형이고 출력값도 이분형이기 때문에 문제될 것이 없다. 다음 예를 생각해보자.

결과: 눈을 깜빡이다(=1), 눈을 깜빡이지 않는다(=0)

결과를 만들어내기 위해 필요한 입력값을 두 가지로 정하고, 그 결과를 모른다고 가정하자(각각을 x_21, x_22라 하자). 그런데 x_21를 출력값으로 하는 퍼셉트론과 x_22를 출력값으로 하는 퍼셉트론도 생각해 볼 수 있다. 

x21: 어떤 일이 일어나다(=1), 어떤 일이 일어나지 않는다(=0)
x22: 어떤 일이 일어나다(=1), 어떤 일이 일어나지 않는다(=0)

이제 x21에 관련된 퍼셉트론에 다음과 같은 입력값이 할당된다고 하자.

x11: 바람이 분다?(1=True, 0=False)
x12: 밝은 곳으로 간다?(1/0)

이제 x22에 관련된 퍼셉트론에 다음과 같은 입력값이 할당된다고 하자.

x13: 고개를 돌린다?(1/0)
x14: 고개를 숙인다?(1/0)

우리는 x11, x12, x13, 그리고 x14가 무엇인지 정확하게 안다. 그리고 발생되는 결과도 관찰할 수 있다(눈을 깜빡인다 혹은 그렇지 않다). 그러나 퍼셉트론들 안에서 일어나는 일들은 관찰이 어렵다(심지어 무엇인지도 모른다!). 한 가지 확실한 사실은 세 개의 퍼셉트론(입력 2, 출력 1)이 네트워크로 연결되었다는 점이다. 실제로 x21에 영향을 미치는 사실을 x11, x12, x13, x14 이렇게 놓고 x13와 x14의 가중치를 항상 0으로 두면 위에서 한 것과 마찬가지다. 굳이 할당할 것도 없이 어떤 일이 몇 개 일어날 것인지 결정해 두면 된다(우리는 두 개라고 했지만 더 있을 수도 있다).

참고로 최초의 입력값이 꼭 0이거나 1일 필요는 없다. 적절한 선형결합을 통해 계산된 값은 다중 퍼셉트론 구조에서 모두 0혹은 1로 변환된다(뒤에서 설명).

편차(Bias)

퍼셉트로은 편차를 가질 수 있다. 간단히 식으로 표현하면 y=W*x+b에서 W는 가중치, x는 입력값, b는 편차이다. 가중치를 전부 0으로 두어도 여전히 남는 값이 편차다.

퍼셉트론의 학습 방식

실제 나와야 할 바람직한 결과를 알고 있다면 우리가 구성한 퍼셉트론들의 연결망(혹은 인공신경망)을 바람직한 모습으로 재구성할 수 있다. 이러한 방식을 지도학습법(Supervised Learning)이라고 부른다. 예를 들어 결과가 1이 나와야 하는데 0이 나온다면 가중치와 편차를 적절하게 조정하여 해당 결과를 만들어 낼 수 있을 듯 하다. 이때 조정된 값(dA)가 결과에 미치는 영향(dB) 사이에 명확한 관계가 있는가가 중요한 문제가 된다.

불행히도 dA는 연결된 다른 퍼셉트론에게 크나큰 영향을 미칠 수 있어 우리가 바라는 바람직한 dB가 예측되지 못할 수 있다. z=W*x+b의 결과 z를 0과 1에 할당해야 할 때, z > k이면 1, z <= k 이면 0이라고 하자. 만약, k를 0.5라고 하면, z가 0.4 정도가 되었을 때 문제가 될 수 있다. dA를 0.3으로 하면 dB도 0.3이 되어서 z가 0.7(=0.4+0.3)이면 실제 결과(y=1)에 부합할 것 같다. 그런데 dA를 0.3으로 해도 dB가 0.1에 그칠 가능성이 있거나 dA를 -0.2로 했을 때 dB가 -0.8까지 커지는 등의 문제가 발생될 수 있다. 이런 문제가 발생되는 주된 원인은 목적으로 하는 가중치의 범위가 아주 공평하기 때문이다. W의 값을 0에서 1까지라고 했을 때 W가 선형적으로 변한다면 발생 확률이 아주 희박한 경우에도 여전히 W는 큰 값으로 변동될 수 있다. 저울에 물건을 올려두고 추를 올린다고 할 때, 특정한 시점까지는 아무리 작은 추를 올려도 저울은 움직이지 않는다. 그렇지만 W를 선형적으로 변한다고 가정하면 아무리 작은 무게에도 미세한 반응을 보여야 한다. 이는 현실적인 가정이 아닐 수 있고, 결과도 심각하게 뒤틀어버릴 위험도 있다.

S모양 뉴런(Sigmoid Neuron)

그렇다면, 특정 범위에서만 활발하게 반응하고 극단 값들에는 둔감하게 반응하는 함수를 생각하면 되지 않을까? S모양함수(혹은 Sigmoid function)은 이런 목적으로 유용하게 사용될 수 있다.

만약 z값이 엄청나게 작으면 1+exp(-z)는 무한대에 가까워지고 sigma(z)는 0에 수렴한다. 만약 z값이 엄청나게 커지면 1+exp(-z)는 1에 수렴하고 sigma(z)는 1에 수렴한다. 그 사이에의 변화는 지수적 증가와 지수적 감소를 거쳐 S자 모양의 곡선이 된다. 당연한 이야기지만 z=0일때는 sigma(z)가 0.5가 된다. 아무런 일도 일어나지 않을 때의 사례를 이에 해당한다고 보면 발생 확률을 0.5로 삼은 것과 마찬가지다.

z=W*x+b, 그리고 y=s(z)로 하고, 함수 s를 Sigmoid Function으로 삼는다는 정도로 논의를 정리하자.


다층 퍼셉트론(Multilayer Perceptron)

입력층과 출력층, 그리고 은닉층(hidden layer)로 이루어진 다층 퍼셉트론 구조를 생각해보자.


되먹임

입력층의 결과는 은닉층으로 차례차례 전달되고 출력층에 결국 하나의 결과로 나타난다(1 혹은 0). 이렇게 앞으로 되먹임하는(feed-forward) 특징이 다층 퍼셉트론에서 흔히 가정되는 것이다. 이러한 형태를 잘 생각해보면 한 가지 이상한 가정이 있음을 알 수 있다. 한 층의 결과가 한 번에 확실하게 일어난다는 점이다! 디지털적 변화(예를 들어 버튼을 강하게 누른다든지)의 경우에는 적절하지만, 변화가 시간이 걸리면서 천천히 진행될 경우에는 꼭 들어맞지는 않는 모습이다. 방아쇠를 당기는 경우 압력을 서서히 주면서 결국 공이를 풀어 노리쇠를 치게 된다. 이때는 방아쇠를 당기고 있는 압력이 최후의 압력값에 점진적인 변화를 가한다. 낙타의 허리를 부러뜨리는 짐도 마찬가지다. 반복 되먹임 구조(Recurrent Structure)가 형성되는 경우를 가정하면 일방향적 전방되먹임은 다소 현실적이지 않아 보인다.

손글씨 분류 문제

인공신경망의 힘을 보여줄 때 흔히 쓰는 예제가 MNIST handwriting dataset이다. 0부터 9까지 손으로 쓴 쓴 숫자를 컴퓨터가 분류하게 시키는 문제다. 이미지를 충분히 전처리해서 다루기 쉬운 형태로 되어 있다는 특징이 있다.


504192를 적절히 처리한 모습이다. 숫자를 가운데 두고 같은 크기의 박스 안에 넣었다. 한 박스는 새로 28 픽셀, 가로 28 픽셀(1개의 픽셀은 1개의 점을 나타낸다)로 구성되어 있다. 각각의 픽셀은 차 있거나 비어 있거나 둘 중 하나다. 28*28=784이므로 모두 784개의 퍼셉트론이 입력층에 배치되어 있다.


은닉층을 하나만 생각했을 때의 다중 퍼셉트론 구조다. 결과층의 퍼셉트론은 모두 10개(0~9)이다. 0이면서 2일 수는 없기 때문에 페셉트론의 결과들을 모두 합하면 1이 되는 결과층이다. 위에서 언급한 바와 같이 입력층의 퍼셉트론은 784개이다. 은닉층의 퍼셉트론을 모두 15개로 가정한 상황이 위의 그림이다.


경사하강법(Gradient Descent Algorithm)

학습을 하려면 결과를 알아야 한다. 결과가 나아지도록 값을 조금씩 고쳐나가는 것을 학습이라고 부르기 때문이다. 좀 더 좋아진다고 표현할 수 있는데 좀 덜 나빠진다고 말 할 수도 있다. 후자의 경우를 표현하기 위한 함수를 비용 함수(Cost Function)라고 부르자. 비용 함수를 쉽게 정의하면 다음과 같다.


통계를 공부한 사람들이 흔히 접하는 MSE(Mean-Squared Error) 형태다. 예측된 값(y(x))와 실제 값(a)의 벡터 거리를 계산하고(물론 squared) 평균값을 취했다. C(w,b)가 최소가 되는 방향으로(가중치오 편차가 알려져 있다고 할 때) 학습이 이루어진다.

경사하강법은 현재 주어진 값에서 아주 근소하게 조금씩 모든 방향으로 움직여보면서 좀 더 작은 값을 찾아가게 하는 방법이다. 현실에서 우리가 흔히 쓰는 방법이다. 뭔가를 해보고 좀 더 잘되는 방식으로 계속 진행하면서 답을 찾는 것이다. 아주 근소하게 변화를 주는 좋은 시도는 미분(derivate)이다. 주어진 근방에서 함수를 미분한 값을 찾고 그 중 가장 낮은 방향으로(경사를 향해 내려가는 방향) 움직이게 하는 것이 경사하강법의 기본 아이디어다.

예를 들어 두 개의 변화 상황에 대한 경사미분은 다음과 같다.

변화값이 음수가 되도록 조정해가면 된다.


Tensorflow 실습

MNIST 데이터

Tensorflow가 설치되어 있다고 가정하자. MNIST 데이터는 Tensorflow에서 다운로드 받을 수 있다. Anaconda를 기반으로 했다면 터미널(혹은 콘솔)에서 다음과 같이 입력하여 Jupyter Notebook을 실행하는 편이 좋다.

$ jupyter-notebook


MNIST 데이터 베이스에 대한 자세한 설명은 다음을 참조하자: http://yann.lecun.com/exdb/mnist

우선 데이터를 다운로드 하고(다운로드는 한번만), 압축파일을 메모리로 불러서 쓰자.

from tensorflow.examples.tutorials.mnist import input_data

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

Tensorflow를 가져오자.

import tensorflow as tf


단일 인공 신경망(ANN: Artificial Neural Network)

컴퓨터가 찾아야 할 곳을 표시하기 위해 Variable 함수를 사용하자. 우리는 W와 b를 찾게 해야 한다. 한편, 그냥 주어지는 값을 전달하기 위해 placeholder 함수를 쓰자. 텐서플로에 placeholder값은 넘겨줘야 한다.

W=tf.Variable(tf.zeros([784,10]))
b=tf.Variable(tf.zeros([10,])
x=tf.placeholder('float',[None,784])

W,b,x는 노드를 이어주는 엣지에 해당한다. 결과적으로 이들 엣지가 어떤 노드를 거쳐 다른 엣지로 파생되어 나가는 네트워크를 정의해야 한다(그래서 flow).

텐서플로의 기본 벡터는 행벡터다. 벡터의 곱을 위해 matmul()함수가 준비되어 있다. Sigmoid와 비슷하지만 좀더 일반적으로 사용되는 Softmax 함수를 적용하자. 이를 위해 softmax함수가 텐서플로에 포함되어 있다.

y=tf.nn.softmax(tf.matmul(x,W)+b)

비용함수를 계산하려면 원래 값을 입력해야 한다. 우리는 지도학습법을 쓸 것이기 때문에 정말 값을 y_이라고 하며 placeholder로 입력값을 텐서플로에 넘겨줘야 한다.

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

이제 비용함수를 생각하자. 위에서는 비용함수를 MSE로 계산했다. 이번에는 교차 엔트로피 오차법(Cross Entropy Error: CEE)을 쓰기로 하자. CEE는 실제값과 예측값의 로그를 곱하고 합한 값에 음수를 취함으로써 상당히 효율적인 계산을 가능하게 한다. 기본적인 목적은 MSE와 마찬가지다.

cross_entropy=-1*tf.reduce_sum(y_*tf.log(y))

지금쯤이면 눈치를 챘을 것 같다. 텐서플로의 계산은 노드의 표현식을 먼저 설정해주면서 진행된다. 파이썬에서 어떠한 계산도 먼저 수행하지 않았다. 단지 tf. 로 시작되는 노드 계산식을 계속 정의하고 이것을 받는 엣지(y, x, y_, cross_entropy 같은 것)를 정의했을 뿐이다.

경사하강법을 적용하자.

train_step=tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)

근방을 살펴보는 움직임을 0.01로 했다. 좀더 세밀한 움직임은 결과를 좋게 하지만 계산을 느리게 한다.

이제 텐서플로의 연결망이 완성되었다. train_step을 계속 돌리도록 하기 위해 세션을 만들고 텐서들을 집어 넣자.

sess=tf.Session()

우선 변수를 모두 초기화하자.

sess.run(tf.global_variables_initializer())

1000번 정도 경사하강을 시켜보자.

for i in range(1000):
  batch_xs,batch_ys=mnist.train.next_batch(100)
  sess.run(train_step,feed_dict={x:batch_xs,y_:batch_ys})


MNIST의 훈련데이터 중 100개 씩 가져와서 batch_xs와 batch_ys 벡터에 입력한다. 위에서 차원값을 None이라고 둔 부분이 100 이하로 채워지게 된다. train_step 엣지를 실행시키면서 placeholder를 채워줘야 한다. feed_dict에 딕셔너리를 입력함을써 이 필요가 채워진다.

얼마나 잘 맞췄나 알아보기 위해 correct_prediction 엣지를 만들자. 그리고 이 값을 평균하여 accuracy를 계산한다. tf.equal()은 True 혹은 False를 반환하기 때문에 평균값 계산을 위해 숫자은 1과 0으로 각각 바꿔줘야 한다. 이 작업은 tf.cast() 함수로 수행한다. 노드에서 가장 prediction이 큰 값의 위치를 찾아내고 비교하는 방식을 취하자.

correct_prediction=tf.equal(tf.argmax(y,1),tf.argmax(y_,1))
accuracy=tf.reduce_mean(tf.cast(correct_prediction,'float'))

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

파이썬 2.7.2 버전이면

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

다음 번 포스트에서 단일 인공신경망을 Convolution Neural Network (CNN)으로 학습하는 텐서플로 예제에 대해 해설한다.

댓글

이 블로그의 인기 게시물

Bradley-Terry Model: paired comparison models

xlwings tutorial - 데이터 계산하여 붙여 넣기

R에서 csv 파일 읽는 법