MNIST For ML Beginners 에서 작성한 모델의 경우 그 정확도가 92% 정도가 됩니다.
다음 TensorFlow tutorial 에서 조금 더 높은 정확도가 나오는 딥러닝 기초 모델을 안내해줍니다.
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, [-1, 28, 28, 1]) 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, 784, 500, '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, 500, 10, '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, [-1, 28, 28, 1]) 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, 784, 500, '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 을 사용해야 됩니다)
y = nn_layer(dropped, 500, 10, '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을 입력해서 볼 수 있습니다.