05-02 06:32
Notice
Recent Posts
Recent Comments
관리 메뉴

Scientific Computing & Data Science

[Artificial Intelligence / TensorFlow] TensorBoard를 이용하여 TensorFlow 데이터 시각화 본문

Artificial Intelligence/TensorFlow

[Artificial Intelligence / TensorFlow] TensorBoard를 이용하여 TensorFlow 데이터 시각화

cinema4dr12 2017. 10. 21. 23:34

Written by Geol Choi | 


이번 포스팅에서는 TensorFlow™(TF)의 시각화 도구인 TensorBoard를 이용하여 Computation Graph를 시각화하는 방법에 대하여 알아보도록 하겠습니다.


실행환경은 다음과 같습니다:

  • OS: Windows 7 64-bit

  • Anaconda: Python 3.6 (5.0.0)

  • TensorFlow: r1.3

  • R: 3.4.2 short summer

  • RStudio: 1.0.136


필자의 실행환경은 위와 같지만, Windows가 아닌 다른 OS 환경에서도 동일한 방식으로 실행할 수 있으리라 예상됩니다.


본 튜토리얼은 딥러닝(Deep Learning;DL)에 대한 기본적인 개념을 이해하고 있으며, TensorFlow의 DL 구현에 대한 기본적인 코딩 방법을 이해하고 있다고 가정하고 설명을 진행합니다.


만약 DL에 대한 기본 개념을 이해하고자 한다면 필자가 작성한 아래 포스팅들을 읽어주시기 바랍니다:


TensorFlow와 관련된 유용한 포스팅은 아래 링크를 참고하시기 바랍니다:

TF 데이터 쓰기

TensorBoard는 TF 실행 시 생성할 수 있는 TensorFlow Event 파일로 운영되며, 이 파일은 Summary Data(tf$summary)를 포함하고 있습니다 (TensorBoard 실행에 있어 이 Summary Data를 이행하는 것이 매우 중요합니다).


우선 TensorBoard에 표시할 TF 그래프 생성해야 하는데, 이 때 어떠한 노드 데이터를 표시하도록 할 것인지 결정해야 합니다 - 참고로, TensorBoard에는 크게 SCALARS, IMAGES, AUDIO, GRAPHS, DISTRIBUTIONS, EMBEDDING, TEXT로 데이터를 분류하며, tf$summary$scalartf$summary$imagetf$summary$audio, tf$summary$text 등으로 지정합니다 (아래에 코드를 통해 자세하게 설명하겠습니다).


TF를 통해 DL 예제코드를 실행해 보신 분은 경험해 보셨겠지만 Epoch의 진행에 따라 Loss Function, Accuracy 등이 어떻게 업데이트 되어 가는지 알고 싶으실 것입니다. 이러한 데이터들을 tf$summary$scalar이라는 태그에 달아 데이터로 저장할 수 있습니다 (이 역시 샘플 코드를 통해 방법을 설명드리겠습니다).


또한 특정 레이어의 활성함수 적용 전, 후의 Weights 또는 Biases의 분포 등이 궁금할 수도 있습니다. tf$summary$histogram 오퍼레이션(op라고도 합니다)에 이러한 데이터를 붙여서 저장할 수 있습니다. Summary Operations에 대한 자세한 정보는 다음 링크를 참고하시기 바랍니다: 

https://www.tensorflow.org/api_guides/python/summary


TF에 작성한 오퍼레이션들은 코드가 실행되기 전에는 아무 일도 하지 않습니다. 따라서 앞서 설명하였던 Summary들(tf$summary$[content_type])을 생성하려면 먼저 모든 Summary 노드들을 실행해야 하는데, 이 노드들을 일일이 관리하는 것은 여간 불편한 일이 아닙니다(노드 데이터를 일일이 개별적으로 저장할 수는 있습니다). 다행히도 TF는 한 번에 관리할 수 있는 오퍼레이션인 tf$summary$merge_all을 제공하며 이는 모든 노드들을 하나의 오퍼레이션으로 합쳐서 모든 Summary 데이터를 생성합니다.


그리고나서 합쳐진 합쳐진(Merged) Summary 오퍼레이션을 실행하면 각 Epoch에 대한 모든 Summary 데이터를 포함하는 Summary protobuf 오브젝트(protobuf는 Google에서 개발한 구조화 데이터 시리얼화를 메커니즘인 Protocol Bufffers를 의미합니다)를 생성할 수 있습니다. 최종적으로 Summary 데이터를 디스크에 기록할 수 있도록 Summary protobuf를 tf$summary$FileWriter 오퍼레이션으로 전달합니다.


tf$summary$FileWriter는 모든 events 기록들이 저장된 디렉터리 경로인 logdir를 인수로 취합니다. 또한 Graph 오브젝트를 옵션으로 취할 수도 있는데, TensorBoard는 Tensor Shape 정보에 따라 그래프를 시각화합니다. 이 시각화를 통해 데이터의 흐름을 한 눈에 파악하는데 큰 도움이 됩니다.


이제 그래프를 수정했고 tf$summary$FileWriter도 얻었으므로 네트워크를 실행할 준비가 되었습니다. 필요할 경우 각 스텝마다 Merged Summary를 실행하고 Training Data를 기록할 수도 있지만 대개는 이런 필요까지는 없을 것 같습니다. 매번 스텝마다 하기 보다는 일정 간격의 스텝을 정하여 기록하는 것이 훨씬 효과적일 것입니다.

샘플 코드 살펴보기

이제 샘플 코드를 통해 TensorBoard를 실행하는 구체적인 방법을 알아보겠습니다. 코드는 링크로부터 가져온 것입니다. 전체 코드는 다음과 같습니다:


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
#!/usr/bin/env Rscript
 
# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
# Copyright 2016 RStudio, Inc. 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.
# ==============================================================================
 
library(tensorflow)
 
flags <- tf$app$flags
flags$DEFINE_boolean('fake_data', FALSE, 'If true, uses fake data for unit testing.')
flags$DEFINE_integer('max_steps', 1000L, 'Number of steps to run trainer.')
flags$DEFINE_float('learning_rate'0.001'Initial learning rate.')
flags$DEFINE_float('dropout'0.9'Keep probability for training dropout.')
flags$DEFINE_string('summaries_dir''./tmp/mnist_logs''Summaries directory')
FLAGS <- parse_flags()
 
train <- function() {
  # Import data
  datasets <- tf$contrib$learn$datasets
  mnist <- datasets$mnist$read_data_sets("MNIST-data", one_hot = TRUE)
  
  sess <- tf$InteractiveSession()
  
  # Create a multilayer model.
  
  # Input placeholders
  with(tf$name_scope("input"), {
    x <- tf$placeholder(tf$float32, shape(NULL, 784L), name = "x-input")
    y_ <- tf$placeholder(tf$float32, shape(NULL, 10L), name = "y-input")
  })
  
  with(tf$name_scope("input_reshape"), {
    image_shaped_input <- tf$reshape(x, c(-1L, 28L, 28L, 1L))
    tf$summary$image("input", image_shaped_input, 10L)
  })
  
  # We can't initialize these variables to 0 - the network will get stuck.
  weight_variable <- function(shape) {
    initial <- tf$truncated_normal(shape, stddev = 0.1)
    tf$Variable(initial)
  }
  
  bias_variable <- function(shape) {
    initial <- tf$constant(0.1, shape = shape)
    tf$Variable(initial)
  }
  
  # Attach a lot of summaries to a Tensor
  variable_summaries <- function(var, name) {
    with(tf$name_scope("summaries"), {
      mean <- tf$reduce_mean(var)
      tf$summary$scalar(paste0("mean/", name), mean)
      with(tf$name_scope("stddev"), {
        stddev <- tf$sqrt(tf$reduce_mean(tf$square(var - mean)))
      })
      tf$summary$scalar(paste0("stddev/", name), stddev)
      tf$summary$scalar(paste0("max/", name), tf$reduce_max(var))
      tf$summary$scalar(paste0("min/", name), tf$reduce_min(var))
      tf$summary$histogram(name, var)
    })
  }
  
  # 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.
  #
  nn_layer <- function(input_tensor, input_dim, output_dim, layer_name, act=tf$nn$relu) {
    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(shape(input_dim, output_dim))
        variable_summaries(weights, paste0(layer_name, "/weights"))
      })
      with(tf$name_scope("biases"), {
        biases <- bias_variable(shape(output_dim))
        variable_summaries(biases, paste0(layer_name, "/biases"))
      })
      with (tf$name_scope("Wx_plus_b"), {
        preactivate <- tf$matmul(input_tensor, weights) + biases
        tf$summary$histogram(paste0(layer_name, "/pre_activations"), preactivate)
      })
      activations <- act(preactivate, name = "activation")
      tf$summary$histogram(paste0(layer_name, "/activations"), activations)
    })
    activations
  }
  
  hidden1 <- nn_layer(x, 784L, 500L, "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)
  })
  
  y <- nn_layer(dropped, 500L, 10L, "layer2", act = tf$nn$softmax)
  
  with(tf$name_scope("cross_entropy"), {
    diff <- y_ * tf$log(y)
    with(tf$name_scope("total"), {
      cross_entropy <- -tf$reduce_mean(diff)
    })
    tf$summary$scalar("cross entropy", cross_entropy)
  })
  
  with(tf$name_scope("train"), {
    optimizer <- tf$train$AdamOptimizer(FLAGS$learning_rate)
    train_step <- optimizer$minimize(cross_entropy)
  })
  
  with(tf$name_scope("accuracy"), {
    with(tf$name_scope("correct_prediction"), {
      correct_prediction <- tf$equal(tf$argmax(y, 1L), tf$argmax(y_, 1L))
    })
    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(file.path(FLAGS$summaries_dir, "train"), sess$graph)
  test_writer <- tf$summary$FileWriter(file.path(FLAGS$summaries_dir, "test"))
  sess$run(tf$global_variables_initializer())
  
  # 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
  
  # Make a TensorFlow feed_dict: maps data onto Tensor placeholders.
  feed_dict <- function(train) {
    if (train || FLAGS$fake_data) {
      batch <- mnist$train$next_batch(100L, fake_data = FLAGS$fake_data)
      xs <- batch[[1]]
      ys <- batch[[2]]
      k <- FLAGS$dropout
    } else {
      xs <- mnist$test$images
      ys <- mnist$test$labels
      k <- 1.0
    }
    dict(x = xs,
         y_ = ys,
         keep_prob = k)
  }
  
  for (i in 1:FLAGS$max_steps) {
    if (i %% 10 == 0) { # Record summaries and test-set accuracy
      result <- sess$run(list(merged, accuracy), feed_dict = feed_dict(FALSE))
      summary <- result[[1]]
      acc <- result[[2]]
      test_writer$add_summary(summary, i)
    } 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()
        result <- sess$run(list(merged, train_step),
                           feed_dict = feed_dict(TRUE),
                           options = run_options,
                           run_metadata = run_metadata)
        summary <- result[[1]]
        train_writer$add_run_metadata(run_metadata, sprintf("step%03d", i))
        train_writer$add_summary(summary, i)
        cat("Adding run metadata for ", i, "\n")
      } else {  # Record a summary
        result <- sess$run(list(merged, train_step), feed_dict = feed_dict(TRUE))
        summary <- result[[1]]
        train_writer$add_summary(summary, i)
      }
    }
  }
  
  train_writer$close()
  test_writer$close()
}
 
# initialize summaries_dir (remove existing if necessary)
if (tf$gfile$Exists(FLAGS$summaries_dir))
  tf$gfile$DeleteRecursively(FLAGS$summaries_dir)
tf$gfile$MakeDirs(FLAGS$summaries_dir)
 
# train
train()
 
# run tensorboard
tensorflow::tensorboard(log_dir = './tmp/mnist_logs')
cs


Line 198에서 train() 함수를 실행하는데, 이 함수 안에서 DNN의 모든 계산과 데이터 기록이 이루어진다고 보시면 되겠습니다.


이제 코드를 부분부분 살펴보도록 하겠습니다.


1
2
3
4
5
6
7
8
9
library(tensorflow)
 
flags <- tf$app$flags
flags$DEFINE_boolean('fake_data', FALSE, 'If true, uses fake data for unit testing.')
flags$DEFINE_integer('max_steps', 1000L, 'Number of steps to run trainer.')
flags$DEFINE_float('learning_rate'0.001'Initial learning rate.')
flags$DEFINE_float('dropout'0.9'Keep probability for training dropout.')
flags$DEFINE_string('summaries_dir''./tmp/mnist_logs''Summaries directory')
FLAGS <- parse_flags()
cs


Line 1에서 tensorflow 라이브러리를 불러오고, 이후 TF의 flags 인터페이스를 정의합니다.


이제 train() 함수 내부를 들여다 보겠습니다.


1
2
3
4
5
# Import data
datasets <- tf$contrib$learn$datasets
mnist <- datasets$mnist$read_data_sets("MNIST-data", one_hot = TRUE)
 
sess <- tf$InteractiveSession()
cs


잘 아시다시피 위의 코드는 MNIST 손글씨 데이터셋을 불러온 후, TF Session을 정의한 것입니다 (일반 Session과 InteractiveSession의 차이는 이 링크를 참고하세요).


본 포스팅은 DNN에 대한 설명이 아닌, TensorBoard의 실행 방법에 대한 설명을 다룬 것이므로, 관련 없는 코드는 스킵하겠습니다. 다음 코드를 살펴보면,


1
2
3
4
5
6
7
8
9
10
# Input placeholders
with(tf$name_scope("input"), {
    x <- tf$placeholder(tf$float32, shape(NULL, 784L), name = "x-input")
    y_ <- tf$placeholder(tf$float32, shape(NULL, 10L), name = "y-input")
})
 
with(tf$name_scope("input_reshape"), {
    image_shaped_input <- tf$reshape(x, c(-1L, 28L, 28L, 1L))
    tf$summary$image("input", image_shaped_input, 10L)
})
cs


우선 가장 눈에 띄는 부분은 with(tf$name_scope(...), {})으로 묶여진 부분입니다. name_scope는 TF에서 그래프를 효율적으로 관리하기 위한 일종의 컨텍스트 관리자(Context Manager)입니다. 즉, 동일한 name_scope를 정의하여 주어진 값들이 동일한 그래프에서 온 것임을 확인하도록 하고 해당 name_scope를 그래프에 넣습니다. 


Line 1~4에 보시면, name_scope "input"에 tf$placeholder 변수, xy_가 멤버로 등록된 것을 확인할 수 있습니다. 지금까지 말로 설명하려니 잘 이해가 되지 않을 듯하여 실행된 TensorBoard 화면을 미리 공개합니다.



[그림 1.] TensorBoard 실행 첫 화면.




[그림 2.] GRAPHS 대쉬보드 화면.



[그림 3.] "input" name_scope 그래프 확장.



[그림 1.]은 TensorBoard를 실행했을 때의 첫 화면입니다. 상단을 보시면 SCALARS, IMAGES, AUDIO, GRAPHS, DISTRIBUTIONS, HISTOGRAMS, EMBEDDING, TEXT로 구성된 메뉴(대쉬보드(Dashboard))를 보실 수 있습니다. 이 중, GRAPHS 대쉬보드로 이동하면 [그림 2.]와 같이 그래프를 보실 수 있는데 그래프의 맨 아래 "input"으로 지정되어 있는 name_scope가 그래프에 등록되어 있는 것을 확인할 수 있습니다.


[그림 3.]은 "input" name_scope를 확장한 것입니다. 보시다시피 tf$placeholder로 정의된 두 개의 변수 xy_name인 "x-input"과 "y-input"이 존재함을 확인할 수 있습니다.


앞의 코드 설명을 계속 이어나가겠습니다. Line 7~10에는  "input_reshape"인 name_scope가 있습니다. 이 역시 [그림 3.]에 보시면 그래프에 등록되어 있습니다. Line 9에 보시면, tf$summary$image() 함수를 통해 입력 이미지(x로부터 reshaped 된 tensor)를 Summary protobuf를 출력합니다.


아래 [그림 4.]는 TensorBoard의 IMAGES 대쉬보드에 출력된 입력 이미지들을 보여 줍니다.




[그림 4.] TensorBoard의 IMAGES 대쉬보드.



이제 다음 코드를 살펴보도록 하겠습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Attach a lot of summaries to a Tensor
variable_summaries <- function(var, name) {
  with(tf$name_scope("summaries"), {
    mean <- tf$reduce_mean(var)
    tf$summary$scalar(paste0("mean/", name), mean)
    with(tf$name_scope("stddev"), {
      stddev <- tf$sqrt(tf$reduce_mean(tf$square(var - mean)))
    })
    tf$summary$scalar(paste0("stddev/", name), stddev)
    tf$summary$scalar(paste0("max/", name), tf$reduce_max(var))
    tf$summary$scalar(paste0("min/", name), tf$reduce_min(var))
    tf$summary$histogram(name, var)
  })
}
cs


"summaries"라는 name_scope에 "mean", "stddev", "max", "min" 등을 tf$summary$scalar에 등록하는 과정을 보실 수 있습니다. 특히, 이들 Scalar의 이름에 variable_summaries() 함수의 인자로 전달받은 name과(R의 paste0() 함수를 통해) 결합되는 것을 볼 수 있는데, variable_summaries() 함수의 name 파라미터는 주로 layer의 이름(가령, hidden.layer1, hidden.layer2, output 등)이라고 보시면 됩니다.



[그림 5.] TensorBoard의 SCALARS 대쉬보드.



[그림 5.]는 TensorBoard의 SCALARS 대쉬보드의 내용을 확인한 것입니다. 앞의 코드에서 tf$summary$scalar에 등록되어 있는 layer1과 layer2 내 Scalar 멤버들을 보실 수 있습니다.


Neural Networks를 정의하는 nn_layer() 함수를 살펴보겠습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
nn_layer <- function(input_tensor, input_dim, output_dim, layer_name, act=tf$nn$relu) {
  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(shape(input_dim, output_dim))
      variable_summaries(weights, paste0(layer_name, "/weights"))
    })
    with(tf$name_scope("biases"), {
      biases <- bias_variable(shape(output_dim))
      variable_summaries(biases, paste0(layer_name, "/biases"))
    })
    with (tf$name_scope("Wx_plus_b"), {
      preactivate <- tf$matmul(input_tensor, weights) + biases
      tf$summary$histogram(paste0(layer_name, "/pre_activations"), preactivate)
    })
    activations <- act(preactivate, name = "activation")
    tf$summary$histogram(paste0(layer_name, "/activations"), activations)
  })
  activations
}
cs


이 함수의 입력 파라미터인 layer_name으로부터 name_scope가 등록되고, 이 name_scope 내에 weights, biases, Wx_plus_b가 name_scope로 등록되고, weights와 biases는 variable_summaries() 함수를 통해 tf$summary$scalar로 등록됩니다.


Line 12~18에 보시면 activation 함수를 적용 전, 후를 tf$summary$histogram()을 통해 히스토그램Summary protobuf를 추가합니다. 역시 TensorBoard의 HISTOGRAM 대쉬보드를 통해 결과를 확인할 수 있습니다.




[그림 6.] TensorBoard의 HISTOGRAM 대쉬보드.



아래 코드에서는 "dropout_keep_probability", "cross entropy", "accuracy" 등이 Scalar로 등록됩니다. 결과는 물론 SCALARS 대쉬보드에서 확인할 수 있습니다.


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
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)
})
  
with(tf$name_scope("cross_entropy"), {
  diff <- y_ * tf$log(y)
  with(tf$name_scope("total"), {
    cross_entropy <- -tf$reduce_mean(diff)
  })
  tf$summary$scalar("cross entropy", cross_entropy)
})
  
with(tf$name_scope("train"), {
  optimizer <- tf$train$AdamOptimizer(FLAGS$learning_rate)
  train_step <- optimizer$minimize(cross_entropy)
})
  
with(tf$name_scope("accuracy"), {
  with(tf$name_scope("correct_prediction"), {
    correct_prediction <- tf$equal(tf$argmax(y, 1L), tf$argmax(y_, 1L))
  })
  with(tf$name_scope("accuracy"), {
    accuracy <- tf$reduce_mean(tf$cast(correct_prediction, tf$float32))
  })
  tf$summary$scalar("accuracy", accuracy)
})
cs


다음 코드는 매우 중요한 코드 중의 하나입니다.


1
2
3
4
# Merge all the summaries and write them out to /tmp/mnist_logs (by default)
merged <- tf$summary$merge_all()
train_writer <- tf$summary$FileWriter(file.path(FLAGS$summaries_dir, "train"), sess$graph)
test_writer <- tf$summary$FileWriter(file.path(FLAGS$summaries_dir, "test"))
cs


앞서 설명한 바, tf$summary$merge_all() 함수는 모든 그래프로부터 수집한 모든 summary를 합칩니다. tf$summary$FileWriter() 함수를 통해 traintest를 별도로 기록하기 위한 저장 메커니즘을 정의하는데 test의 경우에는 그래프를 기록할 필요가 없으므로 그래프를 지정하지 않았습니다.


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
for (i in 1:FLAGS$max_steps) {
  if (i %% 10 == 0) { # Record summaries and test-set accuracy
    result <- sess$run(list(merged, accuracy), feed_dict = feed_dict(FALSE))
    summary <- result[[1]]
    acc <- result[[2]]
    test_writer$add_summary(summary, i)
  } 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()
      result <- sess$run(list(merged, train_step),
                         feed_dict = feed_dict(TRUE),
                         options = run_options,
                         run_metadata = run_metadata)
      summary <- result[[1]]
      train_writer$add_run_metadata(run_metadata, sprintf("step%03d", i))
      train_writer$add_summary(summary, i)
      cat("Adding run metadata for ", i, "\n")
    } else {  # Record a summary
      result <- sess$run(list(merged, train_step), feed_dict = feed_dict(TRUE))
      summary <- result[[1]]
      train_writer$add_summary(summary, i)
    }
  }
}
cs


Line 6과 Line 22에서 앞의 코드에서 정의하였던 train_watertest_writer에 해당 스텝의 traintest의 결과를 각각 기록합니다.


그리고, 모든 기록이 완료되었으면 다음과 같이,


1
2
train_writer$close()
test_writer$close()
cs


해당 FILE I/O를 닫습니다 (여느 FILE I/O처럼 닫지 않으면 삭제하거나 이동하는 것이 불가능합니다).


이로써 train() 함수에 대한 설명이 끝났는데, train() 함수를 호출하기 전 tf$summary$FileWriter에 의해 기록될 event 파일의 경로를 초기화 하는 것이 필요합니다. 초기화란 거창한 것이 아니라 해당 event 파일의 경로를 모두 삭제하고 재생성하는 것입니다:


1
2
3
4
# initialize summaries_dir (remove existing if necessary)
if (tf$gfile$Exists(FLAGS$summaries_dir))
  tf$gfile$DeleteRecursively(FLAGS$summaries_dir)
tf$gfile$MakeDirs(FLAGS$summaries_dir)
cs


즉, 해당 파일이 있는지 확인해서 있으면 (폴더 포함하여) 삭제하고 디렉터리를 다시 만듭니다.


train() 함수 실행이 모두 완료되면 해당 경로에 event 파일이 생성되어 있는지 확인해 봅니다. 본 코드의 경우, event 파일의 생성 경로 위치는 다음과 같았습니다:


1
flags$DEFINE_string('summaries_dir''./tmp/mnist_logs''Summaries directory')
cs


즉, 현재의 R Working Directory를 기준으로 Working Directory 내에 'tmp/mnist_logs'에 event 파일이 저장됩니다. 해당 경로를 살펴보면, 경로 내에 [test] 폴더와 [train] 폴더가 존재하는 것을 확인할 수 있을 것입니다. 그리고 각 파일은 모두 비슷한 형식의 이름으로 되어 있는데, 아마도 파일 이름의 마지막에 컴퓨터 이름이 붙어 있을 것입니다. 예를 들어, 저의 경우는,


events.out.tfevents.1508655342.GCHOI-PC


과 같습니다. 그런데, 주의하실 사항이 있습니다. TensorBoard가 한글로 된 파일이름을 인식하지 못한다는 점입니다.


가령, 자신의 컴퓨터 이름이 한글이 포함된 경우 event 파일이름에 한글이 섞여들어가기 때문에 TensorBoard 실행에 실패할 것입니다. 경로에 한글이 포함되는 경우도 마찬가지로 실행에 실패하게 됩니다.


따라서, event 파일이름에 한글이 없도록 이름을 수정해야 하며 디렉터리 경로에도 한글 경로가 없도록 경로 조정을 하시기 바랍니다.

TensorBoard 실행

이제 정말 마지막까지 왔습니다. TensorBoard만 실행하면 됩니다.


우선 R-TensorFlow의 TensorBoard 실행함수인 tensorboard()의 프로토타입은 다음과 같습니다:

tensorboard(log_dir, action = c("start", "stop"), host = "127.0.0.1",
  port = "auto",
  launch_browser = getOption("tensorflow.tensorboard.browser", interactive()),
  reload_interval = 5, purge_orphaned_data = TRUE)


가장 중요한 파라미터는 log_dir인데 앞서 설명드린 바와 같이 event 파일의 경로 위치입니다. 해당 경로는 FLAGS$summaries_dir 변수에 저장되어 있으므로 이 값을 지정하면 되겠습니다. host는 별도의 입력이 없는 한 localhost(127.0.0.1)로 지정되며 port는 자동으로 지정됩니다. 설명을 위해 자동으로 할당한 port 번호를 6151이라 하겠습니다.


즉, log_dir만 지정하였을 때 TensorBoard는 Web Browser의 주소창에 http://localhost:6151에 입력하면 됩니다 (또는 http://127.0.0.1:6151).


TensorBoard의 명령 실행 예시는 다음 코드와 같습니다:


1
2
# run tensorboard
tensorflow::tensorboard(log_dir = FLAGS$summaries_dir)
cs


전체 코드가 정상적으로 실행되었다면 다음과 유사한 메시지 출력을 보실 수 있을 것입니다:


Adding run metadata for  99 
Adding run metadata for  199 
Adding run metadata for  299 
Adding run metadata for  399 
Adding run metadata for  499 
Adding run metadata for  599 
Adding run metadata for  699 
Adding run metadata for  799 
Adding run metadata for  899 
Adding run metadata for  999 
Started TensorBoard at http://127.0.0.1:6151 




[그림 7.] Web Browser에 TensorBoard가 실행된 화면 (클릭하면 확대된 이미지를 보실 수 있습니다).

추가 팁

R에서 tensorboard() 함수를 통해 TensorBoard를 실행할 수도 있지만, Command Line Tool에서도 실행이 가능합니다.


설명을 위해 event 파일의 경로를 다음과 같이 가정합니다 (현재 필자가 지정한 경로입니다 ^^):


D:/MyProjects/PROGRAMMING/R-project/Working/TensorFlow/Sources/tmp/mnist_logs/


아래와 같이 Command Line Tool을 실행하여 event 파일이 존재하는 위치로 경로를 이동하여 tensorboard 명령을 통해 TensorBoard를 실행하였습니다. 주의할 점은 R의 tensorboard() 함수의 입력 파라미터인 log_dir과는 달리 Log Directory를 지정하는 옵션이 logdir이라는 점입니다(언더바 "log"와 "dir" 사이에 "_"가 없습니다).



[그림 8.] Command Line Tool을 이용하여 실행한 TensorBoard 명령.

참고 사이트

본 포스팅 작성을 위해 참고한 사이트는 다음과 같습니다:


 

아무쪼록 본 포스팅이 TensorBoard를 활용하는데 유용한 가이드가 되길 바랍니다.

Comments