Deep learning

[Supervised Learning / TensorFlow tutorial] MNIST deep neural network with summaries

JaykayChoi 2017. 1. 30. 15:03

MNIST For ML Beginners 에서 작성한 모델의 경우 그 정확도가 92% 정도가 됩니다.

다음 TensorFlow tutorial 에서 조금 더 높은 정확도가 나오는 딥러닝 기초 모델을 안내해줍니다.

https://github.com/tensorflow/tensorflow/blob/56fc8834c736878af34f00caa95e7d4a57ab01d2/tensorflow/examples/tutorials/mnist/mnist_with_summaries.py


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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an 'AS IS' BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""A simple MNIST classifier which displays summaries in TensorBoard.
 This is an unimpressive MNIST model, but it is a good example of using
tf.name_scope to make a graph legible in the TensorBoard graph explorer, and of
naming summary tags so that they are grouped meaningfully in TensorBoard.
It demonstrates the functionality of every TensorBoard dashboard.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
 
import argparse
import sys
 
import tensorflow as tf
 
from tensorflow.examples.tutorials.mnist import input_data
 
FLAGS = None
 
 
def train():
  # Import data
  mnist = input_data.read_data_sets(FLAGS.data_dir,
                                    one_hot=True,
                                    fake_data=FLAGS.fake_data)
 
  sess = tf.InteractiveSession()
  # Create a multilayer model.
 
  # Input placeholders
  with tf.name_scope('input'):
    x = tf.placeholder(tf.float32, [None, 784], name='x-input')
    y_ = tf.placeholder(tf.float32, [None, 10], name='y-input')
 
  with tf.name_scope('input_reshape'):
    image_shaped_input = tf.reshape(x, [-128281])
    tf.summary.image('input', image_shaped_input, 10)
 
  # We can't initialize these variables to 0 - the network will get stuck.
  def weight_variable(shape):
    """Create a weight variable with appropriate initialization."""
    initial = tf.truncated_normal(shape, stddev=0.1)
    return tf.Variable(initial)
 
  def bias_variable(shape):
    """Create a bias variable with appropriate initialization."""
    initial = tf.constant(0.1, shape=shape)
    return tf.Variable(initial)
 
  def variable_summaries(var):
    """Attach a lot of summaries to a Tensor (for TensorBoard visualization)."""
    with tf.name_scope('summaries'):
      mean = tf.reduce_mean(var)
      tf.summary.scalar('mean', mean)
      with tf.name_scope('stddev'):
        stddev = tf.sqrt(tf.reduce_mean(tf.square(var - mean)))
      tf.summary.scalar('stddev', stddev)
      tf.summary.scalar('max', tf.reduce_max(var))
      tf.summary.scalar('min', tf.reduce_min(var))
      tf.summary.histogram('histogram', var)
 
  def nn_layer(input_tensor, input_dim, output_dim, layer_name, act=tf.nn.relu):
    """Reusable code for making a simple neural net layer.
    It does a matrix multiply, bias add, and then uses relu to nonlinearize.
    It also sets up name scoping so that the resultant graph is easy to read,
    and adds a number of summary ops.
    """
    # Adding a name scope ensures logical grouping of the layers in the graph.
    with tf.name_scope(layer_name):
      # This Variable will hold the state of the weights for the layer
      with tf.name_scope('weights'):
        weights = weight_variable([input_dim, output_dim])
        variable_summaries(weights)
      with tf.name_scope('biases'):
        biases = bias_variable([output_dim])
        variable_summaries(biases)
      with tf.name_scope('Wx_plus_b'):
        preactivate = tf.matmul(input_tensor, weights) + biases
        tf.summary.histogram('pre_activations', preactivate)
      activations = act(preactivate, name='activation')
      tf.summary.histogram('activations', activations)
      return activations
 
  hidden1 = nn_layer(x, 784500'layer1')
 
  with tf.name_scope('dropout'):
    keep_prob = tf.placeholder(tf.float32)
    tf.summary.scalar('dropout_keep_probability', keep_prob)
    dropped = tf.nn.dropout(hidden1, keep_prob)
 
  # Do not apply softmax activation yet, see below.
  y = nn_layer(dropped, 50010'layer2', act=tf.identity)
 
  with tf.name_scope('cross_entropy'):
    # The raw formulation of cross-entropy,
    #
    # tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(tf.softmax(y)),
    #                               reduction_indices=[1]))
    #
    # can be numerically unstable.
    #
    # So here we use tf.nn.softmax_cross_entropy_with_logits on the
    # raw outputs of the nn_layer above, and then average across
    # the batch.
    diff = tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y)
    with tf.name_scope('total'):
      cross_entropy = tf.reduce_mean(diff)
  tf.summary.scalar('cross_entropy', cross_entropy)
 
  with tf.name_scope('train'):
    train_step = tf.train.AdamOptimizer(FLAGS.learning_rate).minimize(
        cross_entropy)
 
  with tf.name_scope('accuracy'):
    with tf.name_scope('correct_prediction'):
      correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
    with tf.name_scope('accuracy'):
      accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
  tf.summary.scalar('accuracy', accuracy)
 
  # Merge all the summaries and write them out to /tmp/mnist_logs (by default)
  merged = tf.summary.merge_all()
  train_writer = tf.summary.FileWriter(FLAGS.log_dir + '/train', sess.graph)
  test_writer = tf.summary.FileWriter(FLAGS.log_dir + '/test')
  tf.global_variables_initializer().run()
 
  # Train the model, and also write summaries.
  # Every 10th step, measure test-set accuracy, and write test summaries
  # All other steps, run train_step on training data, & add training summaries
 
  def feed_dict(train):
    """Make a TensorFlow feed_dict: maps data onto Tensor placeholders."""
    if train or FLAGS.fake_data:
      xs, ys = mnist.train.next_batch(100, fake_data=FLAGS.fake_data)
      k = FLAGS.dropout
    else:
      xs, ys = mnist.test.images, mnist.test.labels
      k = 1.0
    return {x: xs, y_: ys, keep_prob: k}
 
  for i in range(FLAGS.max_steps):
    if i % 10 == 0:  # Record summaries and test-set accuracy
      summary, acc = sess.run([merged, accuracy], feed_dict=feed_dict(False))
      test_writer.add_summary(summary, i)
      print('Accuracy at step %s: %s' % (i, acc))
    else:  # Record train set summaries, and train
      if i % 100 == 99:  # Record execution stats
        run_options = tf.RunOptions(trace_level=tf.RunOptions.FULL_TRACE)
        run_metadata = tf.RunMetadata()
        summary, _ = sess.run([merged, train_step],
                              feed_dict=feed_dict(True),
                              options=run_options,
                              run_metadata=run_metadata)
        train_writer.add_run_metadata(run_metadata, 'step%03d' % i)
        train_writer.add_summary(summary, i)
        print('Adding run metadata for', i)
      else:  # Record a summary
        summary, _ = sess.run([merged, train_step], feed_dict=feed_dict(True))
        train_writer.add_summary(summary, i)
  train_writer.close()
  test_writer.close()
 
 
def main(_):
  if tf.gfile.Exists(FLAGS.log_dir):
    tf.gfile.DeleteRecursively(FLAGS.log_dir)
  tf.gfile.MakeDirs(FLAGS.log_dir)
  train()
 
 
if __name__ == '__main__':
  parser = argparse.ArgumentParser()
  parser.add_argument('--fake_data', nargs='?', const=True, type=bool,
                      default=False,
                      help='If true, uses fake data for unit testing.')
  parser.add_argument('--max_steps', type=int, default=1000,
                      help='Number of steps to run trainer.')
  parser.add_argument('--learning_rate', type=float, default=0.001,
                      help='Initial learning rate')
  parser.add_argument('--dropout', type=float, default=0.9,
                      help='Keep probability for training dropout.')
  parser.add_argument('--data_dir', type=str, default='/tmp/tensorflow/mnist/input_data',
                      help='Directory for storing input data')
  parser.add_argument('--log_dir', type=str, default='/tmp/tensorflow/mnist/logs/mnist_with_summaries',
                      help='Summaries log directory')
  FLAGS, unparsed = parser.parse_known_args()
  tf.app.run(main=main, argv=[sys.argv[0]] + unparsed)
cs


이 tutorial 에서는 TensorBoard 기능도 같이 설명을 해주고 있습니다.

TensorBoard 란 최적화를 쉽게 하기 위한 시각도구 입니다. 

https://www.tensorflow.org/how_tos/summaries_and_tensorboard/

https://www.tensorflow.org/how_tos/graph_viz/


TensorFlow 그래프에는 많은 노드가 있기에 tensor 의 이름을 그룹으로 묶는 tf.name_scope 라는 함수가 있습니다.

with tf.name_scope('input'):
 
    x = tf.placeholder(tf.float32, [None, 784], name='x-input')
 
    y_ = tf.placeholder(tf.float32, [None, 10], name='y-input')
cs

위와 같이 tensor 을 만들면 x 와 y_ 은 input 이라는 라벨이 붙은 노드가 됩니다.


아래 코드도 TensorBoard 을 위한 코드로 summary 은 TensorBoard 을 위한 클래스입니다.

with tf.name_scope('input_reshape'):
 
    image_shaped_input = tf.reshape(x, [-128281])
 
    tf.summary.image('input', image_shaped_input, 10)
cs

x 를 4D tensor 로 reshape 하여 summary.image 메써드로 등록을 하게 되는데  reshape 메써드의 첫 번째 인자 -1은 정해진 개수가 아닌 n 개를 넣겠다는 의미이며 두, 세번째 인자는 28 * 28 사이즈의 이미지 x, y 길이, 마지막 1은 이미지의 채널이 한 가지이기 때문에(흑,백) 1을 넣습니다. 


다음으로 이 tutorial 에서는 학습 효과를 높이기 위해 Deep Neural Network (DNN) 이라는 모델을 사용합니다.

https://en.wikipedia.org/wiki/Deep_learning


DNN 은 정확도를 높이기 위해 input layer 와 output layer 사이에 hidden layers 을 둔 multilayer perceptrons 구조로써

perceptron 이란 각 neural 의 수학적 모델(y = Wx + b)을 일컫는 용어입니다. 그리고 이런 perceptron 이 layer으로 층층이 구성되었기 때문에 multilayer perceptrons 이라 합니다. 그리고 이 multilayer perceptrons 은 


backpropagation 알고리즘을 통한 학습을 통해 다수의 hidden layer 으로 정확도를 높일 수 있습니다. backpropagation 을 통해 학습을 하는 방법은 input layer 에서 시작하여 output layer 을 통해 y 값을 얻었을 때 라벨값(y_) 과 비교하여 오답일 경우 다시 output layer 에서 input layer 방향으로 값을 전달하며 계산된 cost 에 따라 각 W 와 b 값을 업데이트 하는 방법을 통해 학습을 합니다.

하지만 naive 하게 학습을 할 경우 높은 정확도만큼 높은 시간 복잡도와 overfitting 문제가 발생될 수 있습니다. overfitting 은 추가된 다수의 layer 들 때문에 모델이 지나치게 학습 데이터에 맞추어져 발생되는 문제입니다.

이런 DNN 의 문제들을 보완하기 위해 dropout regularization 방법 등이 고안되어 있고 이 tutorial 에서도 dropout 을 사용하고 있습니다.


dropout은 학습 시 neural network 의 몇몇 연결을 랜덤하게 끊어서 학습을 하는 것입니다. 이런 과정을 통해 overfitting 을 낮출 수 있습니다.



그리고 dropout과 함께 DNN 의 단점을 극복하기 위해 sigmoid 대신 Rectified linear unit (ReLU) 을 사용하고 있습니다.

위에서 설명한 DNN 의 backpropagation 학습 도중 sigmoid 를 통해 학습을 할 경우 전달된 값이 아무리 크거나 작아도 0~1 으로 변형시키게 되는데 이때 지나친 변형이 발생하여 hidden layer 가 늘어날수록 정확도가 많이 떨어지게 됩니다. 그래서 sigmoid 와 비슷하지만 0보다 작을 때는 0을 반환하고 0보다 클 때는 해당 값을 그대로 사용하는 수정된 방법을 고안했고 이를 ReLU 라 합니다.





MNIST For ML Beginners 에서는 W와 b의 초기값을 0으로 하였지만 DNN 에서는 더 많은 layer 을 사용하기에 조금 더 신경을 써서 초기값을 정해줘야 됩니다. 여기서는 weight 는 truncated normal distribution 을 하였고 bias 는 0.1로 초기화 하였습니다. 

  def weight_variable(shape):
 
    """Create a weight variable with appropriate initialization."""
 
    initial = tf.truncated_normal(shape, stddev=0.1)
 
    return tf.Variable(initial)
 
 
 
  def bias_variable(shape):
 
    """Create a bias variable with appropriate initialization."""
 
    initial = tf.constant(0.1, shape=shape)
 
    return tf.Variable(initial)
cs


<weight 을 초기화 하는 방법은 여러 가지 방법이 있고 그 중 많이 쓰는 몇 가지 방법은 아래와 같습니다>

1. 0과 같은 상수로 초기화 하는 방법: tf.constant(0, shape)

2. normal distribution: tf.random_normal(shape)

3. truncated normal distribution (정규 분포와 같지만 2 standard deviations 보다 큰 얻어진 값은 버리는 방법): tf.truncated_normal(shape, stddev=0.1)

4. Xaivier initialization: tf.get_variable("w", shape,initializer=tf.contrib.layers.xavier_initializer())




아래 코드는 tensorBoard 를 위한 코드입니다. summary.scalar 은 스칼라값을 보기위한, summary.histogram 은 histogram 을 보기위한 함수입니다.

  def variable_summaries(var):
 
    """Attach a lot of summaries to a Tensor (for TensorBoard visualization)."""
 
    with tf.name_scope('summaries'):
 
      mean = tf.reduce_mean(var)
 
      tf.summary.scalar('mean', mean)
 
      with tf.name_scope('stddev'):
 
        stddev = tf.sqrt(tf.reduce_mean(tf.square(var - mean)))
 
      tf.summary.scalar('stddev', stddev)
 
      tf.summary.scalar('max', tf.reduce_max(var))
 
      tf.summary.scalar('min', tf.reduce_min(var))
 
      tf.summary.histogram('histogram', var)
cs



아래 코드는 layer을 만드는 함수입니다. activation function 으로 relu 함수를 사용했습니다.

그리고 앞선 포스팅과 마찬가지로 y=wx+b 회기식을 사용했습니다. 

def nn_layer(input_tensor, input_dim, output_dim, layer_name, act=tf.nn.relu):
 
    """Reusable code for making a simple neural net layer.
    It does a matrix multiply, bias add, and then uses relu to nonlinearize.
    It also sets up name scoping so that the resultant graph is easy to read,
    and adds a number of summary ops.
    """
 
    # Adding a name scope ensures logical grouping of the layers in the graph.
 
    with tf.name_scope(layer_name):
 
      # This Variable will hold the state of the weights for the layer
 
      with tf.name_scope('weights'):
 
        weights = weight_variable([input_dim, output_dim])
 
        variable_summaries(weights)
 
      with tf.name_scope('biases'):
 
        biases = bias_variable([output_dim])
 
        variable_summaries(biases)
 
      with tf.name_scope('Wx_plus_b'):
 
        preactivate = tf.matmul(input_tensor, weights) + biases
 
        tf.summary.histogram('pre_activations', preactivate)
 
      activations = act(preactivate, name='activation')
 
      tf.summary.histogram('activations', activations)
 
      return activations
cs

  


여기서는 하나의 hidden layer 을 두었습니다. x 에서 784개를 받고 500개를 output 으로 내보내는 layer 입니다.

hidden1 = nn_layer(x, 784500'layer1')
cs

 

그리고 다음으로 dropout 을 실행합니다. dropout rate 는 placeholder 로 값을 넣도록 하였습니다.

  with tf.name_scope('dropout'):
 
    keep_prob = tf.placeholder(tf.float32)
 
    tf.summary.scalar('dropout_keep_probability', keep_prob)
 
    dropped = tf.nn.dropout(hidden1, keep_prob)
cs

 

마지막으로 y 를 만드는데 hidden layer1 에서 input 으로 500개를 받았기 때문에 input 으로 500을 하였고 output 은 MNIST 이기 때문에 10으로 해야 됩니다.

주의해야 될 점은 여기서는 값을 0~1 사이의 값으로 얻어야 되기 때문에 ReLU 를 사용하지 않아야 됩니다. 그리고 dropout 또한 하지 않아야 됩니다.(모든 W 을 사용해야 됩니다)

= nn_layer(dropped, 50010'layer2', act=tf.identity)
cs

  


이제 cost 를 구해야 될 차례입니다. 앞선 tutorial 과 같은 방법을 사용합니다.

  with tf.name_scope('cross_entropy'):
 
    diff = tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y)
 
    with tf.name_scope('total'):
 
      cross_entropy = tf.reduce_mean(diff)
 
  tf.summary.scalar('cross_entropy', cross_entropy)
cs


이제 학습을 하면서 cost 값을 낮춰야 됩니다. 단, 여기서는 train 함수를 AdamOptimizer 이라는 다른 방법을 사용했습니다. 학습 방법은 이와같이 일반적인 gradient descent 이외에도 다른 몇 가지 방법이 있습니다. AdamOptimizer 은 단순한 gradient descent 보다 효율이 좋다고 알려져 있습니다.

with tf.name_scope('train'):
 
    train_step = tf.train.AdamOptimizer(FLAGS.learning_rate).minimize(cross_entropy)
cs



아래 코드는 TensorBoard 를 위한 코드로 TensorBoard 을 이용하기 위해서는 merge_all 함수를 통해 그동안 만든 변수들을 모두 머지해야 됩니다.

그리고 FileWriter 함수는 그 결과를 정해진 폴더에 저장하게 됩니다. 

  merged = tf.summary.merge_all()
 
  train_writer = tf.summary.FileWriter(FLAGS.log_dir + '/train', sess.graph)
 
  test_writer = tf.summary.FileWriter(FLAGS.log_dir + '/test')
 
 
cs

이렇게 저장된 결과는 콘솔에

tensorboard --logdir=path/to/log-directory 
cs

을 입력하면 볼 수 있고 (logdir은 데이터를 FileWriter가 데이터를 저장해놓은 디렉토리)

한 번 TensorBoard가 실행되면 웹브라우저 주소창에 localhost:6006을 입력해서 볼 수 있습니다.