NeuroWhAI의 잡블로그

[TensorFlow] MNIST CNN(합성곱 신경망)으로 학습하기 본문

개발 및 공부/라이브러리&프레임워크

[TensorFlow] MNIST CNN(합성곱 신경망)으로 학습하기

NeuroWhAI 2018. 1. 20. 16:05


드디어 CNN을 써봤습니다.
이전부터 계속 진행했던 MNIST 데이터로 실습하였습니다.

코드:
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import tensorflow as tf
import numpy as np
 
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("./mnist/data/", one_hot=True)
 
# 입력X는 28x28 크기의 이미지들이며 그레이스케일 포맷이므로 채널은 1개
# 출력 Y는 원 핫 인코딩된 텐서들이며 [0,0,1,0,0,0,0,0,0,0]는 2를 뜻함.
= tf.placeholder(tf.float32, [None, 28281])
= tf.placeholder(tf.float32, [None, 10])
keep_prob = tf.placeholder(tf.float32)
 
# 합성곱 계층
# 커널(필터)은 3x3 크기이며 입력과 똑같은 1채널임.
# 커널의 개수는 하이퍼 파라미터이므로 적당히 32개로 한다.
W1 = tf.Variable(tf.random_normal([33132], stddev=0.01))
# 입력X와 커널W1의 합성곱 연산을 정의한다.
# 스트라이드는 1이다. ([batch, height, width, channel] 형식의 텐서)
# padding을 'SAME'으로 하면 출력의 크기가 줄어들지 않는다.
L1 = tf.nn.conv2d(X, W1, strides=[1111], padding='SAME')
L1 = tf.nn.relu(L1)
 
# 풀링 계층
# 2x2 영역에서 가장 큰 값 하나만을 취한다.
# 스트라이드는 2다.
# 때문에 출력의 크기는 반으로 줄어든다. (28x28 -> 14x14)
L1 = tf.nn.max_pool(L1, ksize=[1221], strides=[1221],
    padding='SAME')
 
# 합성곱 계층
# 크기는 이전과 똑같지만 이전의 필터 개수가 32개였으므로 3번째 Shape이 32다.
# 이번에는 커널의 개수를 64개로 한다.
# 나머지는 이전과 동일하다.
W2 = tf.Variable(tf.random_normal([333264], stddev=0.01))
L2 = tf.nn.conv2d(L1, W2, strides=[1111], padding='SAME')
L2 = tf.nn.relu(L2)
 
# 풀링 계층
# 이전과 동일하다.
L2 = tf.nn.max_pool(L2, ksize=[1221], strides=[1221],
    padding='SAME')
 
# 완전 연결 신경망
# 입력 7*7*64개, 출력 256개의 히든 레이어
W3 = tf.Variable(tf.random_normal([7 * 7 * 64256], stddev=0.01))
# 이전 신경망의 결과를 1차원 텐서로 Reshape한다.
# [-1, 7 * 7 * 64]는 행의 개수는 자동으로 계산하고
# 열의 개수는 이전 계층의 출력인 (7x7x64)의 크기와 같다.
L3 = tf.reshape(L2, [-17 * 7 * 64])
L3 = tf.nn.relu(tf.matmul(L3, W3))
# 드롭아웃
L3 = tf.nn.dropout(L3, keep_prob)
 
# 출력 계층
# 이전 계층의 출력 개수인 256개의 입력과 숫자의 개수인 10개의 출력을 가짐.
W4 = tf.Variable(tf.random_normal([25610], stddev=0.01))
model = tf.matmul(L3, W4)
 
# 오차 함수는 교차 엔트로피 오차 함수를 사용.
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(
    labels=Y, logits=model
))
 
# 최적화는 AdamOptimizer를 사용한다.
optimizer = tf.train.AdamOptimizer(learning_rate=0.001).minimize(cost)
 
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
 
    batch_size = 100
    total_batch = int(mnist.train.num_examples / batch_size)
 
    for epoch in range(15):
        total_cost = 0
 
        for i in range(total_batch):
            # 학습할 데이터를 미니배치로 얻어오는데
            # 입력은 nx28x28x1로 Reshape 한다.
            batch_xs, batch_ys = mnist.train.next_batch(batch_size)
            batch_xs = batch_xs.reshape(-128281)
 
            _, cost_val = sess.run([optimizer, cost],
                feed_dict={X: batch_xs, Y: batch_ys, keep_prob: 0.7})
 
            total_cost += cost_val
 
        print('Epoch:''%04d' % (epoch + 1),
            'Avg. cost =''{:.3f}'.format(total_cost / total_batch))
 
    print('완료!')
 
    is_correct = tf.equal(tf.argmax(model, 1), tf.argmax(Y, 1))
    accuracy = tf.reduce_mean(tf.cast(is_correct, tf.float32))
    print('정확도: %.2f' % sess.run(accuracy * 100,
        feed_dict={X: mnist.test.images.reshape(-128281),
            Y: mnist.test.labels, keep_prob: 1}))
 
cs


실행 결과:
Epoch: 0001 Avg. cost = 0.363
Epoch: 0002 Avg. cost = 0.111
Epoch: 0003 Avg. cost = 0.083
Epoch: 0004 Avg. cost = 0.065
Epoch: 0005 Avg. cost = 0.054
Epoch: 0006 Avg. cost = 0.046
Epoch: 0007 Avg. cost = 0.040
Epoch: 0008 Avg. cost = 0.035
Epoch: 0009 Avg. cost = 0.030
Epoch: 0010 Avg. cost = 0.026
Epoch: 0011 Avg. cost = 0.024
Epoch: 0012 Avg. cost = 0.021
Epoch: 0013 Avg. cost = 0.020
Epoch: 0014 Avg. cost = 0.018
Epoch: 0015 Avg. cost = 0.018
완료!
정확도: 98.93


서버 CPU가 구린것도 있지만 CPU 텐서플로라서 그런지 겨우 15 epoch 돌리는데 30분이나 걸렸네요.
왜 병렬처리가 필요한지 느낀;;

보십쇼 ㄷㄷ;;

CNN은 이전에 이론으로만 계속 공부해서 전체적으로 대충 이해하는데 무리는 없었지만
하나하나 곱씹어보면 왜 이렇게 코드가 나오는지, 이 코드가 어떻게 그 공식을 표현하는지 이해가 안가는 부분도 좀 있네요.
뜬금없지만 확실히 CNN은 3D로 표현하고 생각하는게 이해가 쉽네요.
당장 두번째 계층부터 커널이 3차원이니;;

이전부터 텐서플로에서 제 이해를 방해하는 요소 중 하나는
입력과 출력 데이터가 한번에 하나씩 처리된다고 생각하면 참 쉬운데
실제론 입력, 출력 데이터 n쌍이 한번에 처리되니까 코드도 이에 맞춰서 작성되어 있다는 사실입니다.
placeholder의 None이나 reshape의 -1 같은게 그런 이유에서죠.
뭐 이렇게 깊게 이해할 필요가 있나 싶기도 하지만....

아무튼 책의 다음 챕터에서는 좀더 고수준의 API를 사용해서 위 코드를 간단하게 만든다는것 같네요.




Comments