텐서플로우(TensorFlow) 시작하기

최근 GAN(Generative Adversarial Network)을 배우고 있다. 관심 있게 보고 있는 자료가 텐서플로우로 구현이 되어 있어, 이번 기회에 텐서플로우를 배워보기로 마음 먹었다. 이 글에서는 텐서플로우를 설치하고, 기본 사용법을 익히며, MNIST 이미지를 ConvNet으로 분류하는 예제를 차례차례 따라가 보고자 한다.

텐서플로우 설치하기

먼저 Installing TensorFlow에서 운영체제에 맞는 설치 가이드를 선택한다. 여기에서는 Mac을 기준으로 설명한다. 우분투에 GPU 버전을 설치하는 방법은 김도남님이 쓰신 Tensorflow 설치하기 (Ubuntu 16)가 도움이 될 것이다.

** CUDA 툴킷 설치 Updated(2017-03-21) **

우분투 14.04 버전에 GPU 버전으로 설치하려면 Ubuntu에 CUDA 설치가 도움이 되며, 2017년 3월 21일 기준으로 샘플을 컴파일 하는 부분을 제외하고는 문제없이 설치되었다. CUDA가 제대로 설치되었는지 샘플을 컴파일하는 과정에서 3_Imaging/cudaDecodeGL 샘플 예제에서 아래와 같은 에러가 날 수 있다.

1
2
3
4
5
6
7
8
9
10
11
make[1]: Entering directory `/usr/local/cuda-7.0/samples/3_Imaging/cudaDecodeGL'
/usr/local/cuda-7.0/bin/nvcc -ccbin g++   -m64      -gencode arch=compute_20,
code=compute_20 -o cudaDecodeGL FrameQueue.o ImageGL.o VideoDecoder.o
VideoParser.o VideoSource.o cudaModuleMgr.o cudaProcessFrame.o 
videoDecodeGL.o  -L../../common/lib/linux/x86_64 -L/usr/lib/"nvidia-367"
-lGL -lGLU -lX11 -lXi -lXmu -lglut -lGLEW -lcuda -lcudart -lnvcuvid
/usr/bin/ld: cannot find -lnvcuvid
collect2: error: ld returned 1 exit status
make[1]: *** [cudaDecodeGL] Error 1
make[1]: Leaving directory `/usr/local/cuda-7.0/samples/3_Imaging/cudaDecodeGL'
make: *** [3_Imaging/cudaDecodeGL/Makefile.ph_build] Error 2

문제는 NVIDIA 드라이버 라이브러리 버전이 367으로(-L/usr/lib/"nvidia-367")으로 하드코딩되어 있기 때문이다. 관련 이슈와 해결법은 CUDA 7.0 Error while compiling samples에서 확인할 수 있다. 문제가 된 해당 파일(3_Imaging/cudaDecodeGL/findgllib.mk)을 열어서 아래와 같이 드라이버 버전을 자신의 머신에 맞게 수정한다.

1
2
    #UBUNTU_PKG_NAME = "nvidia-367"
    UBUNTU_PKG_NAME = "nvidia-375"

컴파일된 샘플을 실행해서 설치된 CUDA 환경을 확인할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
$ bin/x86_64/linux/release/deviceQuery
bin/x86_64/linux/release/deviceQuery Starting...
CUDA Device Query (Runtime API) version (CUDART static linking)
Detected 1 CUDA Capable device(s)
Device 0: "GeForce GTX 1070"
CUDA Driver Version / Runtime Version 8.0 / 8.0
CUDA Capability Major/Minor version number: 6.1
Total amount of global memory: 8112 MBytes (8506048512 bytes)
(16) Multiprocessors, (128) CUDA Cores/MP: 2048 CUDA Cores
GPU Max Clock rate: 1645 MHz (1.64 GHz)
Memory Clock rate: 4004 Mhz
Memory Bus Width: 256-bit
L2 Cache Size: 2097152 bytes
Maximum Texture Dimension Size (x,y,z) 1D=(131072), 2D=(131072, 65536), 3D=(16384, 16384, 16384)
Maximum Layered 1D Texture Size, (num) layers 1D=(32768), 2048 layers
Maximum Layered 2D Texture Size, (num) layers 2D=(32768, 32768), 2048 layers
Total amount of constant memory: 65536 bytes
Total amount of shared memory per block: 49152 bytes
Total number of registers available per block: 65536
Warp size: 32
Maximum number of threads per multiprocessor: 2048
Maximum number of threads per block: 1024
Max dimension size of a thread block (x,y,z): (1024, 1024, 64)
Max dimension size of a grid size (x,y,z): (2147483647, 65535, 65535)
Maximum memory pitch: 2147483647 bytes
Texture alignment: 512 bytes
Concurrent copy and kernel execution: Yes with 2 copy engine(s)
Run time limit on kernels: Yes
Integrated GPU sharing Host Memory: No
Support host page-locked memory mapping: Yes
Alignment requirement for Surfaces: Yes
Device has ECC support: Disabled
Device supports Unified Addressing (UVA): Yes
Device PCI Domain ID / Bus ID / location ID: 0 / 1 / 0
Compute Mode:
< Default (multiple host threads can use ::cudaSetDevice() with device simultaneously) >
deviceQuery, CUDA Driver = CUDART, CUDA Driver Version = 8.0, CUDA Runtime Version = 8.0, NumDevs = 1, Device0 = GeForce GTX 1070
Result = PASS
1
2
3
4
5
6
7
8
9
10
11
./bin/x86_64/linux/release/simpleOccupancy 
starting Simple Occupancy
[ Manual configuration with 32 threads per block ]
Potential occupancy: 50%
Elapsed time: 0.076544ms
[ Automatic, occupancy-based configuration ]
Suggested block size: 1024
Minimum grid size for maximum occupancy: 32
Potential occupancy: 100%
Elapsed time: 0.042368ms
Test PASSED

파이썬 쉘을 열어서 샘플 코드를 작성할 때 아래와 같은 에러가 발생할 수 있다.

1
2
3
4
5
6
$ python
>>> import tensorflow as tf
...
  File "/home/itrocks/Works/Python/TensorFlow/local/lib/python2.7/site-packages/mock/mock.py", line 69, in <module>
    from pbr.version import VersionInfo
ImportError: No module named pbr.version

이와 같은 에러가 나는 경우 mock 모듈을 삭제후 재설치한다.(참고: one more import error(new) #3940)

1
2
$ pip uninstall mock
$ pip install mock

** cuDNN 설치 Updated(2017-03-21) **

NVIDIA cuDNN 사이트로 이동하여 cuDNN을 설치한다.

설치 방법 선택하기

텐서플로우는 아래의 4가지 설치 유형을 제공한다.

  • virtualenv
  • pip
  • 도커(docker)
  • 소스코드

이 글에서는 설치가 간편한 virtualenv를 사용하여 설치하며, 파이썬 2.7 버전을 기준으로 한다.

VirtualEnv로 텐서플로우 설치하기

먼저 pip과 virtualenv를 설치한다.

1
2
$ sudo easy_install pip
$ sudo pip install --upgrade virtualenv

virtualenv 환경을 생성할 디렉토리를 생성한 후, virtualenv 환경을 생성한다.

1
2
$ mkdir TensorFlow
$ virtualenv --system-site-packages TensorFlow

virtualenv 환경을 활성화한다.

1
2
$ source ~/TensorFlow/bin/activate
(tensorflow)$

텐서플로우를 설치한다.

1
2
3
4
$ pip install --upgrade tensorflow
Collecting tensorflow
  Could not find a version that satisfies the requirement tensorflow (from versions: )
No matching distribution found for tensorflow

실패했다. pip 버전이 8.1보다 낮거나, 파이썬 버전이 맞지 않는 경우 실패할 수 있다고 나와있다.

1
2
$ pip -V
pip 9.0.1 from /Users/socurites/Runnable/TensorFlow/lib/python2.7/site-packages (python 2.7)

하지만 pip, 파이썬 버전에는 문제가 없다. 위와 같은 에러가 나는 경우, 텐서플로우 바이너리 파일 URL 경로를 명시해준다.

1
pip install --upgrade https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.0.1-py2-none-any.whl

예제 프로젝트

이 글에서 설명하는 예제 프로젝트는 Github의 StartingTensorFlow에서 클론할 수 있다.

1
$ git clone https://github.com/socurites/StartingTensorflow.git

텐서플로우로 몸풀기

텐서플로우 설치가 끝났으니, 이제 텐서플로우를 사용하여 간단한 기계학습 프로그램을 만들어본다. 이 예제는 Getting Started With TensorFlow을 바탕으로 한다.

텐서플로우 API 분류

텐서플로우는 저수준 API(TensorFlow Core)와 tf.contrib.learn와 같은 고수준 API를 제공한다. 저수준 API는 기계학습 알고리즘을 직접 구현할 수 있는 프로그래밍 언어적 요소를 모두 제공한다. 고수준 API는 저수준 API를 기반으로 하며, 기계학습에 필요한 개발을 단순화할 수 있게 한다. 고수준 API는 아래와 같은 요소를 제공한다.

  • 반복적으로 훈련(train)하기
  • 반복적으로 평가(evaluation)하기
  • 데이터셋 조작하기
  • 데이터 피드(feed)하기

저수준 API(TensorFlow Core) 사용하기

이 번장의 예제는 예제 프로젝트의 getting_started_step_by_step.py에 위치한다. 텐서 플로우 API 사용하려면 tensorflow를 임포트한다.

1
2
$ python
>>> import tensorflow as tf

텐서플로우 프로그램은 computational graph로, 일련의 텐서플로우 오퍼레이션(operation)을 노드(node)들의 그래프로 구조화한 것이다. 노드는 텐서(tensor)를 입력으로 받아서 텐서를 출력한다.

constant

constant 노드를 생성한다.

1
2
3
# create constants
>>> node1 = tf.constant(3.0, tf.float32)
>>> node2 = tf.constant(4.0) # also tf.float32 implicitly

생성한 노드를 출력해 본다.

1
2
>>> print(node1, node2)
(<tf.Tensor 'Const:0' shape=() dtype=float32>, <tf.Tensor 'Const_1:0' shape=() dtype=float32>)

3.0과 4.0이 출력되는 대신 텐서 정보가 출력된다. 노드가 실제 값을 가지려면 computational graph을 평가해야하고, 평가하려면 세션(session)을 생성해야 한다.

1
2
# create session
>>> sess = tf.Session()

그리고 노드를 실제 평가하려면 세션안에서 computational graph를 실행해야 한다. 세션은 텐서플로우에 대한 제어와 상태를 캡슐화한다.

1
2
>>> print(sess.run([node1, node2]))
[3.0, 4.0]

오퍼레이션(operation)

computational graph에 값을 저장하는 노드를 연결하는 오퍼레이션을 추가하자. 오퍼레이션 자체도 노드다.

1
2
3
4
5
>>> node3 = tf.add(node1, node2)
>>> print("node3: ", node3)
('node3: ', <tf.Tensor 'Add:0' shape=() dtype=float32>)
>>> print("sess.run(node3): ",sess.run(node3))
('sess.run(node3): ', 7.0)

두 노드를 더하는 node3 또한 세션안에서 평가하기 전에는 값을 출력하지 않는다. 지금까지의 computational graph는 아래와 같다.

Screen Shot 2017-03-16 at 4.33.53 PM

placeholder

constant 노드는 말 그대로 변하지 않는 상수를 저장한다. 학습 데이터셋의 입력 값은 변경되며, 외부에서 입력받을 수 있어야 한다. 이러한 변수 역할을 하는 노드는 placeholder 노드다.

1
2
3
4
5
6
7
8
9
>>> a = tf.placeholder(tf.float32)
>>> b = tf.placeholder(tf.float32)
>>> adder_node = a + b  # + provides a shortcut for tf.add(a, b)
>>> a
<tf.Tensor 'Placeholder:0' shape=<unknown> dtype=float32>
>>> b
<tf.Tensor 'Placeholder_1:0' shape=<unknown> dtype=float32>
>>> adder_node
<tf.Tensor 'add:0' shape=<unknown> dtype=float32>

constant 노드와는 달리 생성하는 시점에 값이 고정되지 않는다. 마찬가지로 세션안에서 평가를 통해 값을 출력할 수 있다.

1
2
3
4
>>> print(sess.run(adder_node, {a: 3, b:4.5}))
7.5
>>> print(sess.run(adder_node, {a: [1,3], b: [2, 4]}))
[ 3.  7.]

이 경우의 computational graph는 아래와 같다.

Screen Shot 2017-03-16 at 4.42.08 PM

정리하면...

지금까지의 내용을 정리하면 텐서플로우 프로그램을 작성하는 일은 computational graph를 만드는 작업이며, 결과를 출력하려면 computational graph을 평가해야 한다.

computational graph는 노드를 추가로 연결하여 만들어 갈 수 있다.

1
2
3
>>> add_and_triple = adder_node * 3.
>>> print(sess.run(add_and_triple, {a: 3, b:4.5}))
22.5

Screen Shot 2017-03-16 at 4.53.29 PM

Variable

이제 선형 방정식 W * x + b를 위한 computational graph를 만들어 보자. x는 입력 변수이므로 placehoder를 사용한다.

1
>> x = tf.placeholder(tf.float32)

가중치 W와 바이어스 b는 placeholder와는 달리, 입력 텐서와 출력텐서가 다르다. 훈련을 통해 파라미터(가중치, 바이어스)는 학습해야 하며, 이 경우 Variable을 이용한다.

1
2
3
>>> W = tf.Variable([.3], tf.float32)
>>> b = tf.Variable([-.3], tf.float32)
>>> linear_model = W * x + b

변수는 초기화해서 사용해야 하며, tf.global_variables_initializer()를 이용한다.

1
2
>>> init = tf.global_variables_initializer()
>>> sess.run(init)

초기화된 변수 W와 b의 출력은 다음과 같다.

1
2
3
4
>>> print(sess.run(W))
[ 0.30000001]
>>> print(sess.run(b))
[-0.30000001]

이제 입력 x = [1,2,3,4]를 이용하여 선형 방정식을 평가해 보자.

1
2
>>> print(sess.run(linear_model, {x:[1,2,3,4]}))
[ 0.          0.30000001  0.60000002  0.90000004]

예를 들어 x[0]=1의 경우, 1 * 0.3 + (-0.3) = 0.으로 계산된다.

loss

모델이 정확한지를 평가하기 위해서는 예측하려는 실제 결과값 y가 필요하다.

1
>>> y = tf.placeholder(tf.float32)

모델의 예측값과 실제 결과값 사이의 차이, 즉 손실을 평가할 함수가 필요하다. 여기에서는 squared error 함수를 손실함수로 사용하자.

Screen Shot 2017-03-16 at 7.00.50 PM

1
2
>>> squared_deltas = tf.square(linear_model - y)
>>> loss = tf.reduce_sum(squared_deltas)

실제 y=[0, -1, -2, -3] 값을 이용하여 손실을 평가한다.

1
2
>>> print(sess.run(loss, {x:[1,2,3,4], y:[0,-1,-2,-3]}))
23.66

loss 계산은 다음과 같다.

(0. - 0.)^2 + (0.3 - (-1))^2 + (0.6 - (-2))^2 + (0.9 - (-3))^2

= 0 + 1.69 + 6.76 + 15.2

= 23.66

학습하려는 모델의 파라미터 W와 b의 진짜 값은 각각 -1, 1이다. 변수에 해당 값을 할당하면 손실은 당연히 0이 된다.

1
2
3
4
5
6
>>> fixW = tf.assign(W, [-1.])
>>> fixb = tf.assign(b, [1.])
>>> sess.run([fixW, fixb])
[array([-1.], dtype=float32), array([ 1.], dtype=float32)]
>>> print(sess.run(loss, {x:[1,2,3,4], y:[0,-1,-2,-3]}))
0.0

tf.train API

랜덤값으로 초기화된 파라미터 W와 b가 진짜 값인 -1, 1에 수렴하도록 이제 반복적으로 학습해보자. 최적화 함수로는 그래디언트 디센트(gradient descent)를 사용한다.

1
2
>>> optimizer = tf.train.GradientDescentOptimizer(0.01)
>>> train = optimizer.minimize(loss)

그리고 파라미터를 다시 랜덤값으로 초기화한 후, 반복 훈련시킨다.

1
2
3
>>> sess.run(init)
>>> for i in range(1000):
...  sess.run(train, {x:[1,2,3,4], y:[0,-1,-2,-3]})

학습된 파라미터를 출력해 본다.

1
2
>>> print(sess.run([W, b]))
[array([-0.9999969], dtype=float32), array([ 0.99999082], dtype=float32)]

W와 b가 각각 -1과 1에 근접했음을 확인할 수 있다. 현재까지 훈련된 파라미터와 손실을 출력해 보자.

1
2
3
>>> curr_W, curr_b, curr_loss  = sess.run([W, b, loss], {x:[1,2,3,4], y:[0,-1,-2,-3]})
>>> print("W: %s b: %s loss: %s"%(curr_W, curr_b, curr_loss))
W: [-0.9999969] b: [ 0.99999082] loss: 5.69997e-11

고수준 API(tf.contrib.learn) 사용하기

텐서플로우 고수준 API인 tf.contrib.learn을 사용하여 앞의 예를 다시 작성해 보자. 아래 코드는 예제 프로젝트의 getting_started_contrib.py에 위치한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import tensorflow as tf
import numpy as np
# Declare list of features of one real-valued feature
features = [tf.contrib.layers.real_valued_column("x", dimension=1)]
# Declare an estimator for linear regression
# An estimator is the front end to invoke training (fitting) and evaluation(inference)
estimator = tf.contrib.learn.LinearRegressor(feature_columns=features)
# Read training datasets
x = np.array([1., 2., 3., 4.])
y = np.array([0., -1., -2., -3.])
input_fn = tf.contrib.learn.io.numpy_input_fn({"x":x}, y, batch_size=4, num_epochs=1000)
# Invoke fit with training datasets and 1000 steps
estimator.fit(input_fn=input_fn, steps=1000)
# Evaluate the learned model with test datasets
estimator.evaluate(input_fn=input_fn)

  1. 입력 변수 x의 특성(feature)을 정의한다. 이 경우 입력변수의 차원은 1이다.
  2. 훈련 모델을 정의한다. 이 예제에서는 선형 회귀(linear regressoin) 모델을 사용한다.
  3. 훈련 데이터셋을 준비한다. 그리고 훈련데이터셋의 배치 사이즈와 에폭 수를 정의한다.
  4. 모델을 훈련시킨다. 1000 스텝을 반복한다.
  5. 훈련된 모델을 평가한다.

훈련이 완료되면 모델의 손실을 출력하며, 아래는 출력된 손실이다.

1
{'loss': 4.1342983e-06, 'global_step': 1000}

커스텀 모델

앞의 LinearRegressor와 같이 텐서플로우에서 제공하는 훈련 모델을 사용하지 않고, 직접 모델을 만들어야 한다고 해보자. 이때 tf.contrib.learn.Estimator을 사용하여 커스텀 모델을 만들 수 있다. 앞에서 사용한 LinearRegressor는 tf.contrib.learn.Estimator의 하위 클래스다. 커스텀 모델에서는 하위 클래스를 만드는 대신, Estimator에서 사용할 모델 함수를 생성한다. 예제 코드는 getting_started_contrib_custom.py에 위치한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np
import tensorflow as tf
# Define custom model
def model(features, labels, mode):
    ...
# Declare an estimator with custom model function
estimator = tf.contrib.learn.Estimator(model_fn=model)
# Read training datasets
x = np.array([1., 2., 3., 4.])
y = np.array([0., -1., -2., -3.])
input_fn = tf.contrib.learn.io.numpy_input_fn({"x": x}, y, 4, num_epochs=1000)
# Invoke fit with training datasets and 1000 steps
estimator.fit(input_fn=input_fn, steps=1000)
# Evaluate the learned model with test datasets
print(estimator.evaluate(input_fn=input_fn, steps=10))

커스텀 모델 함수를 정의하고, 모델 함수를 이용하여 estimator를 생성한다. 이후의 훈련/평가 과정은 앞의 코드와 동일하다. 이 예제에서는 선형 회귀 모델을 직접 만들어 본다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Define custom model
def model(features, labels, mode):
  # Build a linear model and predict values
  W = tf.get_variable("W", [1], dtype=tf.float64)
  b = tf.get_variable("b", [1], dtype=tf.float64)
  y = W*features['x'] + b
  # Loss sub-graph
  loss = tf.reduce_sum(tf.square(y - labels))
  # Training sub-graph
  global_step = tf.train.get_global_step()
  optimizer = tf.train.GradientDescentOptimizer(0.01)
  train = tf.group(optimizer.minimize(loss),
                   tf.assign_add(global_step, 1))
  # ModelFnOps connects subgraphs we built to the appropriate functionality.
  return tf.contrib.learn.ModelFnOps(
      mode=mode, predictions=y,
      loss=loss,
      train_op=train)

모델 함수는 3가지 파라미터를 받는다.

  • 입력 변수의 feature
  • 예측하려는 y 레이블
  • 학습 mode: 훈련(learn.ModeKeys.TRAIN) or 평가(learn.ModeKeys.EVAL)

먼저 커스텀으로 정의하려는 선형 모델 y = W*x + b을 구현한다. tf.get_variable는 tf.Variable을 이용하여 변수를 생성하는 구문과 거의 동일하다. 주요 차이는 변수 생성시 초기값을 생성하는 대신, 초기화 함수를 지정할 수 있다. 자세한 내용은 Sharing Variables을 참고한다.

그리고 모델의 손실(loss)와 최적화 함수(optimizer)를 정의한다. 파라미터 W, b를 학습하기 위한 1회의 훈련이 끝나면 글로벌 스텝(global_step)은 1 증가한다. 이 두개의 오퍼레이션(1회의 최적화 훈련, 글로벌 스텝 1 증가)을 그룹화(group)하여 하나의 오퍼레이션으로 합친다.

마지막으로 tf.contrib.learn.ModelFnOps을 생성하여 반환한다.

MNIST 분류하기

이 예제에서는 MNIST 분류 예제를 다룬다.  MNIST For ML Beginners에서 다루는 내용을 차례차례 설명하며, 예제 코드는 mnist_ml_step_by_step.py에 위치한다.

MNIST 분류 문제 정의

해결하려는 MNIST 분류 문제를 설명한다. 이 장 내용은 딥러닝 in Torch를 참고했다.

MNIST 필기체 인식문제는 머신 러닝의 이미지 분류 예제로써 가장 잘 알려진 예제다. 입력 변수는 0부터 9까지 필기체로 쓰여진 숫자 이미지이며, 출력 변수는 필기체에 적힌 숫자다. 입력 이미지로부터 이미지에 적힌 숫자를 인식할 수 있는 모델을 만들어야 한다.

image007

그림. MNIST 필기체 이미지 샘플

학습 데이터셋 탐색하기

학습 데이터셋은 얀 르쿤(Yann Lecunn) 교수의 홈페이지에서 다운로드할 수 있다. 학습 데이터셋을 numpy 배열로 로드한다.

1
2
3
4
5
import tensorflow as tf
import numpy as np
# Load MNIST datastes
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

MNIST 데이터셋은 55,000개의 훈련(train) 데이터셋과 10,000개의 테스트(test) 데이터셋으로 구성되며, 각 데이터셋은 입력 이미지(image)와 출력 레이블(label)로 구성된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> mnist
Datasets(train=<tensorflow.contrib.learn.python.learn.datasets.mnist.DataSet object at 0x10cd9bf50>, validation=<tensorflow.contrib.learn.python.learn.datasets.mnist.DataSet object at 0x10d894490>, test=<tensorflow.contrib.learn.python.learn.datasets.mnist.DataSet object at 0x10d8ada10>)
>>> mnist.train
<tensorflow.contrib.learn.python.learn.datasets.mnist.DataSet object at 0x10cd9bf50>
>>> mnist.train.images.shape
(55000, 784)
>>> mnist.train.labels.shape
(55000, 10)
>>> mnist.test
<tensorflow.contrib.learn.python.learn.datasets.mnist.DataSet object at 0x10d8ada10>
>>> mnist.test.images.shape
(10000, 784)
>>> mnist.test.labels.shape
(10000, 10)

각 이미지는 28x28 사이즈(784개의 값)를 가진다. 훈련 데이터셋의 3번째 이미지와 레이블을 살펴보자. 이미지 출력은 matplotlib을 사용하며, 1차원의 ndarray를 2차원의 28x28 ndarray로 형태를 변경한다.

1
2
3
4
5
6
7
8
9
10
11
>>> img_3 = mnist.train.images[2]
>>> img_3.shape
(784,)
>>> img_3_reshaped = mnist.train.images[2].reshape((28, 28))
>>> img_3_reshaped.shape
(28, 28)
>>> import matplotlib.pyplot as plt
>>> import matplotlib.cm as cm
>>> plt.imshow(img_3_reshaped, cmap = cm.Greys)
<matplotlib.image.AxesImage object at 0x12282f410>
>>> plt.show()

Screen Shot 2017-03-17 at 1.43.41 PM

3번째 이미지의 레이블도 4인지 확인하자.

1
2
>>> mnist.train.labels[2]
array([ 0.,  0.,  0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.])

one-hot 인코딩에 따라 4번째 인덱스(인덱스는 0부터 시작)의 값만 1이다.

학습하기

이전 예제는 선형 회귀 문제로, 1개의 입력 x에 대해 1개의 출력 y를 예측하는 W와 b를 훈련을 통해 학습했다. MNIST 분류 문제는 멀티 회귀(분류) 문제로, 1개의 입력 이미지 x에 대해 10개의 출력 y를 예측하는 W와 b를 학습해야 한다. 그리고 출력된 10개의 y값 중에서, 요소의 값이 가장 높은 요소의 인덱스가 이미지의 레이블에 해당한다. 이와 같은 경우에 소프트맥스(soft max) 함수를 이용할 수 있다. 아래 그림을 보자.

Screen Shot 2017-03-17 at 2.01.12 PM

  • 입력 x는 784개의 노드로 구성된다
  • 출력 y는 10개의 노드로 구성된다.
  • 훈련을 통해 파라미터 W와 b를 최적화한다.

행렬로 표현하면 아래와 같다. 보는 것처럼 이번 문제에서도 레이어가 1개인 네트워크를 구성한다.

Screen Shot 2017-03-17 at 2.01.23 PM

모델 정의하기

먼저 모델을 정의한다. 입력값 x를 전달발을 placeholder와 변수 W와 b를 선언한다.

1
2
3
4
# create variable for model: W * x + b
x = tf.placeholder(tf.float32, [None, 784])
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))

그리고 출력 함수로 soft max 함수를 선언하며, 실제 출력 값을 저장할 변수를 선언한다.

1
2
3
4
# define soft max funciton for output layer
y = tf.nn.softmax(tf.matmul(x, W) + b)
# create variable for real y
y_ = tf.placeholder(tf.float32, [None, 10])

손실 함수와 옵티마이저 정의하기

멀티 분류 문제에서 많이 사용하는 크로스 엔트로피(cross entropy) 함수를 손실함수로 정의한다.

Screen Shot 2017-03-17 at 2.15.36 PM

아래와 같이 직접 구현할 수 있지만,

1
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y), reduction_indices=[1]))

이미 안정적으로 구현되어 있는 함수를 사용하면 된다.

1
cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y))

최적화를 위한 옵티마이저로는 앞의 예제와 같이 그래디언트 디센트를 사용한다.

1
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)

훈련하기

위에서 정의한 모델을 훈련하기 위한 세션을 정의하고, 반복학습을 진행한다.

1
2
3
4
5
sess = tf.InteractiveSession()
tf.global_variables_initializer().run()
for _ in range(1000):
  batch_xs, batch_ys = mnist.train.next_batch(100)
  sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})

평가하기

훈련된 모델을 테스트 데이터셋을 이용하여 평가한다. 1개의 이미지에 대해 모델의 예측값은 10개로 구성된 y값이다. 이중에서 요소의 값이 가장 높은 요소의 인덱스가 이미지의 레이블에 해당하며, 이러한 경우 argmax 함수를 이용한다.

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

그리고 불린 값으로 저장된 correct_prediction을 float으로 변환한 후, 평균을 구한다. 그리고 이 평균값이 모델의 정확도다.

1
2
# cast boolean into float
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

출력 해보면 91~92% 정도의 정확도가 나온다.

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

ConvNet으로 MNIST 분류하기

이번 예제에서는 위의 MNIST 분류 문제를 ConvNet(컨볼루션 네트워크)을 이용하여 학습한다. Deep MNIST for Experts에서 다루는 내용을 차례차례 설명하며, 예제 코드는 mnist_deep_step_by_step.py에 위치한다.

ConvNet 설명

이 예제에서 사용할 ConvNet은 아래와 같다.

Screen Shot 2017-03-17 at 3.12.39 PM

  • Input 입력 이미지는 28x28 사이즈
  • C1: 1st convolution layer 5x5 커널 필터 32개를 적용하여 32개의 특성맵(feature map)을 생성
  • S2: 1st pooling layer 2x2 max 풀링
  • C3: 2nd convolution layer 5x5 커널 필터 64개를 적용하여 64개의 특성맵(feature map)을 생성
  • S4: 2nd pooling layer 2x2 max 풀링
  • C5: densely connected layer 1024 노드
  • Output 10개의 노드 overfitting을 막기 위해 dropout 적용

ConvNet 정의하기

각 레이어를 정의해보자. 먼저 데이터셋을 로드하고, 입력변수와 출력 변수를 선언한다.

1
2
3
4
5
6
7
8
9
10
import tensorflow as tf
import numpy as np
# Load MNIST datastes
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
# create session
sess = tf.InteractiveSession()
# create variable for input and real output y
x = tf.placeholder(tf.float32, shape=[None, 784])
y_ = tf.placeholder(tf.float32, shape=[None, 10])

입력 이미지x는 1차원의 형태를 가진다. 컨볼루션을 위해 입력 이미지를 원래의 형태(1 channel x 28 width x 28 height)를 가지도록 변경한다.

1
2
# reshape x
x_image = tf.reshape(x, [-1,28,28,1])

shape 파라미터의 첫 번째 -1은 입력 데이터셋의 전체 사이즈에 해당한다(negative array index)

C1: 첫번째 Convolution 레이어

첫 번째 컨볼루션 레이어는 1채널을 가지는 입력 이미지에 대해 5x5 사이즈의 32개의 커널 필터를 적용하여 32개의 특성맵을 생성한다.

1
2
3
4
5
# 1st ConvNet layer
## 32 Kernel Filter with size 5x5 on 1 input channel
W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)

가중치 W와 바이어스 b를 초기화 하는 함수는 공통적으로 사용하므로 weight_variable(..)과 bias_varaible(..) 함수로 공통화한다. 또한 컨볼루션 계산과 max pooling도 공통적으로 사용하므로 conv2d(..) 와 max_pool_2x2(..) 함수도 각각 정의한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Define weight(kernel filter) with shape
def weight_variable(shape):
  initial = tf.truncated_normal(shape, stddev=0.1)
  return tf.Variable(initial)
# Define bias with shape
def bias_variable(shape):
  initial = tf.constant(0.1, shape=shape)
  return tf.Variable(initial)
# Define convolution with x and W
def conv2d(x, W):
  return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')
# Define max pooling with x and W
def max_pool_2x2(x):
  return tf.nn.max_pool(x, ksize=[1, 2, 2, 1],
                        strides=[1, 2, 2, 1], padding='SAME')

S2: 첫번째 Pooling 레이어

풀링 레이어를 정의한다.

1
2
3
# 1st Pooling layer
## 2x2 max pooling
h_pool1 = max_pool_2x2(h_conv1)

C3: 두번째 Convolution 레이어와 S4: 두번째 Pooling 레이어

C3/S4레이어는 앞의 레이어와 동일하게 구성한다.

1
2
3
4
5
6
7
8
# 2nd ConvNet layer
## 64 Kernel Filter with size 5x5 on 32 input channel
W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
# 2nd Pooling layer
## 2x2 max pooling
h_pool2 = max_pool_2x2(h_conv2)

C5: Densely connected 레이어

7x7 사이즈의 64채널의 특성맵을 1차원으로 변경한 후, 1024개의 노드를 가지는 레이어와 연결한다.

1
2
3
4
5
# Densely connected layer
W_fc1 = weight_variable([7 * 7 * 64, 1024])
b_fc1 = bias_variable([1024])
h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)

그리고 과적합(overfitting)을 방지하기 위해 dropout을 적용한다. keep_prob은 훈련단계에서만 dropout을 적용하고 테스트 단계에서는 dropout을 적용하지 않기 위한 파라미터다(훈련단계에서는 0.5, 테스트 단계에서는 1을 할당).

1
2
3
## Apply dropout
keep_prob = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

Output 레이어

출력 레이어를 정의한다. 출력 레이어는 10개의 노드(레이블의 개수)로 구성된다.

1
2
3
W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])
y_conv = tf.matmul(h_fc1_drop, W_fc2) + b_fc2

훈련 및 평가하기

이제 로드한 데이터셋과 정의된 모델을 이용하여 훈련을 진행한다.

훈련하기

훈련 단계에도 모델의 정확도를 계산하기 위해 accuracy를 먼저 정의한다.

1
2
3
4
5
6
7
8
9
10
11
# run training repeatedly within session
correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
sess.run(tf.global_variables_initializer())
for i in range(20000):
  batch = mnist.train.next_batch(50)
  if i0 == 0:
    train_accuracy = accuracy.eval(feed_dict={
        x:batch[0], y_: batch[1], keep_prob: 1.0})
    print("step %d, training accuracy %g"%(i, train_accuracy))
  train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})

그리고 dropout을 위해 keep_prob을 0.5로 설정한다.

평가하기

훈련이 끝나면, 테스트 데이터셋을 이용하여 모델을 평가한다. 평가 단계에서는 dropout 없이 모든 노드를 사용하기 위해서 keep_prob을 1.0으로 설정한다.

1
2
3
>>> print("test accuracy %g"%accuracy.eval(feed_dict={
...     x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))
test accuracy 0.9805

참고자료

Portions of this page are modifications based on work created and shared by Google and used according to terms described in the Creative Commons 3.0 Attribution License.


Popit은 페이스북 댓글만 사용하고 있습니다. 페이스북 로그인 후 글을 보시면 댓글이 나타납니다.