Metrics Collector 알아보기
앞서 하이퍼 파라미터 튜닝에서 사용했던 메트릭 수집기는 기본 수집기인 StdOut 메트릭 수집기였습니다. 이번에는 StdOud 메트릭 수집기에 필터를 적용하는 방법과 TensorFlowEvent, File 그리고 Custom 메트릭 수집기에 대해서 알아보겠습니다.
StdOud 메트릭 수집기에 필터 적용하기
StdOut 메트릭 수집기에 필터를 적용하는 방법에 대해서 알아보겠습니다. 기존 예제에서는 StdOut 으로 출력되는 메트릭을 수집하기 위해서 {{MetricsName}}={{MetricsValue}} 형태로 출력을 하였습니다. 필터를 사용하면 메트릭을 나타내는 형식을 지정할 수 있기 때문에, 모델 학습시 출력되는 기본적인 로그를 그대로 사용할 수 있습니다.
예를 든다면, mnist-simple.py 를 실행하면 다음과 같은 로그가 출력됩니다.
Epoch 1/5 50000/50000 [==============================] - 2s 46us/sample - loss: 0.3268 - accuracy: 0.9055 - val_loss: 0.1509 - val_accuracy: 0.9574 Epoch 2/5 50000/50000 [==============================] - 2s 42us/sample - loss: 0.1581 - accuracy: 0.9534 - val_loss: 0.1115 - val_accuracy: 0.9684 Epoch 3/5 50000/50000 [==============================] - 2s 40us/sample - loss: 0.1166 - accuracy: 0.9642 - val_loss: 0.1017 - val_accuracy: 0.9708 Epoch 4/5 50000/50000 [==============================] - 2s 40us/sample - loss: 0.0959 - accuracy: 0.9707 - val_loss: 0.0836 - val_accuracy: 0.9756 Epoch 5/5 50000/50000 [==============================] - 2s 42us/sample - loss: 0.0808 - accuracy: 0.9747 - val_loss: 0.0774 - val_accuracy: 0.9773
로그를 보면, 메트릭이 “accuracy: 0.9055 “, “val_accuracy: 0.9574” 이런 형식으로 출력되는 것을 확인 할 수 있습니다. 필터에 {{MetricsName}}:{{MetricsValue}} 형식을 추가해서 기본 로그에서 메트릭을 추출하도록 하겠습니다. 형식은 go 언어의 정규표현식을 사용할 수 있습니다.
다음은 {{MetricsName}}:{{MetricsValue}} 형식을 필터로 사용하는 metricsCollectorSpec 입니다.
metricsCollectorSpec: collector: kind: StdOut source: filter: metricsFormat: - "([\\\\w|-]+)\\\\s*:\\\\s*((-?\\\\d+)(\\\\.\\\\d+)?)"
모델 코드 만들기
텐서플로우 케라스로 작성한 mnist 숫자를 판별하는 모델입니다. Katib를 위한 별도의 로그는 출력하지 않습니다.
from __future__ import absolute_import, division, print_function, unicode_literals import argparse import tensorflow as tf import numpy as np def train(): print("TensorFlow version: ", tf.__version__) parser = argparse.ArgumentParser() parser.add_argument('--learning_rate', default=0.01, type=float) parser.add_argument('--dropout', default=0.2, type=float) args = parser.parse_args() mnist = tf.keras.datasets.mnist (x_train, y_train), (x_test, y_test) = mnist.load_data() x_train, x_test = x_train / 255.0, x_test / 255.0 # Reserve 10,000 samples for validation x_val = x_train[-10000:] y_val = y_train[-10000:] x_train = x_train[:-10000] y_train = y_train[:-10000] model = tf.keras.models.Sequential([ tf.keras.layers.Flatten(input_shape=(28, 28)), tf.keras.layers.Dense(128, activation='relu'), tf.keras.layers.Dropout(args.dropout), tf.keras.layers.Dense(10, activation='softmax') ]) model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=args.learning_rate), loss='sparse_categorical_crossentropy', metrics=['accuracy']) print("Training...") training_history = model.fit(x_train, y_train, epochs=5, validation_data=(x_val, y_val)) print("Average test loss: ", np.average(training_history.history['loss'])) if __name__ == '__main__': train()
모델 컨테이너 이미지 만들기
모델 학습용 컨테이너 이미지를 만들기 위해서 Dockerfile을 생성하겠습니다.
다음은 텐서플로우 2.1을 기반 이미지로 해서, 모델 파일을 추가하는 Dockerfile 입니다.
Dockerfile
FROM tensorflow/tensorflow:2.1.0-py3 RUN mkdir -p /app ADD mnist-simple.py /app/
다음 명령어로 “kangwoo/mnist-simple:katib” 라는 이름으로 컨테이너 이미지를 빌드할 수 있습니다.
docker build -t kangwoo/mnist-simple:katib.
빌드한 컨테이너 이미지를 컨테이너 이미지 레지스트리에 푸시합니다.
docker push kangwoo/mnist-simple:katib
Experiment 생성하기
Experiment라는 사용자 리소스를 정의합니다. metricsCollectorSpec 필드에 filter가 추가되어 있습니다.
random-stdout-filter-example.yaml
apiVersion: "kubeflow.org/v1alpha3" kind: Experiment metadata: namespace: admin name: random-stdout-filter-example spec: metricsCollectorSpec: collector: kind: StdOut source: filter: metricsFormat: - "([\\\\w|-]+)\\\\s*:\\\\s*((-?\\\\d+)(\\\\.\\\\d+)?)" parallelTrialCount: 1 maxTrialCount: 12 maxFailedTrialCount: 3 objective: type: maximize goal: 0.99 objectiveMetricName: val_accuracy additionalMetricNames: - accuracy algorithm: algorithmName: random parameters: - name: --learning_rate parameterType: double feasibleSpace: min: "0.01" max: "0.2" - name: --dropout parameterType: double feasibleSpace: min: "0.1" max: "0.5" trialTemplate: goTemplate: rawTemplate: |- apiVersion: batch/v1 kind: Job metadata: name: {{.Trial}} namespace: {{.NameSpace}} spec: template: spec: containers: - name: {{.Trial}} image: kangwoo/mnist-simple:katib imagePullPolicy: Always command: - "python3" - "/app/mnist-simple.py" {{- with .HyperParameters}} {{- range .}} - "{{.Name}}={{.Value}}" {{- end}} {{- end}} restartPolicy: Never
정의한 Experiment 사용자 리소스를 쿠버네티스 클러스터에 생성합니다.
kubectl apply -f random-stdout-filter-example.yaml
Experiment 결과 보기
Katib UI를 통해서 다음과 같은 결과를 확인할 수 있습니다.

TensorFlowEvent 메트릭 수집기 사용하기
TensorFlowEvent 메트릭 수집기를 사용해 보겠습니다. TensorFlowEvent 메트릭 수집기는 텐서플로우에서 생성하는 이벤트를 추출해서 메트릭을 수집합니다. 그래서 기존의 텐서플로우 코드를 사용할 때 유용합니다. 다만 혀재는 텐서플로우 1 버전만을 지원하기 때문에, 텐서플로우 2 버전에 사용하기에는 약간의 문제가 있습니다.
다음은 TensorFlowEvent 메트릭 수집기를 사용하는 metricsCollectorSpec 입니다. fileSystemPath 필드를 사용해서 이벤트가 저장되어 있는 경로를 지정해 주어야합니다.
metricsCollectorSpec: collector: kind: TensorFlowEvent source: fileSystemPath: path: /train kind: Directory
모델 코드 만들기
텐서플로우 1 버전으로 작성한 mnist 숫자를 판별하는 모델입니다. tf.summary를 사용하여 이벤트를 출력하고 있습니다.
from __future__ import absolute_import from __future__ import division from __future__ import print_function import argparse import os 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, 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.int64, [None], 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.losses.sparse_softmax_cross_entropy on the # raw logit outputs of the nn_layer above, and then average across # the batch. with tf.name_scope('total'): cross_entropy = tf.losses.sparse_softmax_cross_entropy( labels=y_, logits=y) 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), y_) 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/tensorflow/mnist/logs/mnist_with_summaries (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): # pylint: disable=redefined-outer-name """Make a TensorFlow feed_dict: maps data onto Tensor placeholders.""" if train or FLAGS.fake_data: xs, ys = mnist.train.next_batch(FLAGS.batch_size, 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('--batch_size', type=int, default=100, help='Training batch size') parser.add_argument('--dropout', type=float, default=0.9, help='Keep probability for training dropout.') parser.add_argument( '--data_dir', type=str, default=os.path.join(os.getenv('TEST_TMPDIR', '/tmp'), 'tensorflow/mnist/input_data'), help='Directory for storing input data') parser.add_argument( '--log_dir', type=str, default=os.path.join(os.getenv('TEST_TMPDIR', '/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)
모델 컨테이너 이미지 만들기
모델 학습용 컨테이너 이미지를 만들기 위해서 Dockerfile을 생성하겠습니다.
다음은 텐서플로우 1.11을 기반 이미지로 해서, 모델 파일을 추가하는 Dockerfile 입니다.
Dockerfile
FROM tensorflow/tensorflow:1.11.0 RUN mkdir -p /app ADD mnist-with-summaries.py /app/
다음 명령어로 “kangwoo/mnist-simple:katib” 라는 이름으로 컨테이너 이미지를 빌드할 수 있습니다.
docker build -t kangwoo/mnist-with-summaries:katib .
빌드한 컨테이너 이미지를 컨테이너 이미지 레지스트리에 푸시합니다.
docker push kangwoo/mnist-with-summaries:katib
Experiment 생성하기
Experiment라는 사용자 리소스를 정의합니다. metricsCollectorSpec 필드에 filter가 추가되어 있습니다.
random-tf-event-example.yaml
apiVersion: "kubeflow.org/v1alpha3" kind: Experiment metadata: namespace: admin name: random-tf-event-example spec: metricsCollectorSpec: source: fileSystemPath: path: /train kind: Directory collector: kind: TensorFlowEvent parallelTrialCount: 1 maxTrialCount: 12 maxFailedTrialCount: 3 objective: type: maximize goal: 0.99 objectiveMetricName: accuracy_1 algorithm: algorithmName: random parameters: - name: --learning_rate parameterType: double feasibleSpace: min: "0.01" max: "0.05" - name: --batch_size parameterType: int feasibleSpace: min: "100" max: "200" trialTemplate: goTemplate: rawTemplate: |- apiVersion: "kubeflow.org/v1" kind: TFJob metadata: name: {{.Trial}} namespace: {{.NameSpace}} spec: tfReplicaSpecs: Worker: replicas: 1 restartPolicy: OnFailure template: spec: containers: - name: tensorflow image: kangwoo/mnist-with-summaries:katib imagePullPolicy: Always command: - "python" - "/app/mnist-with-summaries.py" - "--log_dir=/train/metrics" {{- with .HyperParameters}} {{- range .}} - "{{.Name}}={{.Value}}" {{- end}} {{- end}}
정의한 Experiment 사용자 리소스를 쿠버네티스 클러스터에 생성합니다.
kubectl apply -f random-tf-event-example.yaml
Experiment 결과 보기
Katib UI를 통해서 다음과 같은 결과를 확인할 수 있습니다.

File 메트릭 수집기 사용하기
File 메트릭 수집기를 사용해 보겠습니다. File 메트릭 수집기는 파일로 출력되는 로그를 추출해서 메트릭을 수집합니다. File 메트릭 수집기도 필터를 사용하여 메트릭 형식을 지정할 수 있습니다. 메트릭 형식을 지정하지 않으면, 기본 형식인 “([\w|-]+)\s*=\s*((-?\d+)(\.\d+)?)” 즉 {{MetricsName}}={{MetricsValue}} 을 사용합니다.
다음은 File 메트릭 수집기를 사용하는 metricsCollectorSpec 입니다. fileSystemPath 필드를 사용해서 로그가 저장되어 있는 파일 경로를 지정해 주어야 합니다. 파일 경로를 지정하지 않으면 기본 경로인 “/var/log/katib/metrics.log”을 사용합니다.
metricsCollectorSpec: source: filter: metricsFormat: - "([\\\\w|-]+)\\\\s*=\\\\s*((-?\\\\d+)(\\\\.\\\\d+)?)" fileSystemPath: path: "/var/log/katib/mnist.log" kind: File collector: kind: File
모델 코드 만들기
텐서플로우 케라스로 작성한 mnist 숫자를 판별하는 모델입니다. logging 패키지를 사용하여 파일로 로그를 출력하고 있습니다.
from __future__ import absolute_import, division, print_function, unicode_literals import tensorflow as tf import argparse import numpy as np from datetime import datetime, timezone import logging logging.basicConfig(filename='/var/log/katib/mnist.log', level=logging.DEBUG) def train(): print("TensorFlow version: ", tf.__version__) parser = argparse.ArgumentParser() parser.add_argument('--learning_rate', default=0.01, type=float) parser.add_argument('--dropout', default=0.2, type=float) args = parser.parse_args() mnist = tf.keras.datasets.mnist (x_train, y_train), (x_test, y_test) = mnist.load_data() x_train, x_test = x_train / 255.0, x_test / 255.0 # Reserve 10,000 samples for validation x_val = x_train[-10000:] y_val = y_train[-10000:] x_train = x_train[:-10000] y_train = y_train[:-10000] model = tf.keras.models.Sequential([ tf.keras.layers.Flatten(input_shape=(28, 28)), tf.keras.layers.Dense(128, activation='relu'), tf.keras.layers.Dropout(args.dropout), tf.keras.layers.Dense(10, activation='softmax') ]) model.compile(optimizer=tf.keras.optimizers.SGD(learning_rate=args.learning_rate), loss='sparse_categorical_crossentropy', metrics=['acc']) print("Training...") katib_metric_log_callback = KatibMetricLog() training_history = model.fit(x_train, y_train, batch_size=64, epochs=10, validation_data=(x_val, y_val), callbacks=[katib_metric_log_callback]) print("Average test loss: ", np.average(training_history.history['loss'])) class KatibMetricLog(tf.keras.callbacks.Callback): def on_epoch_end(self, epoch, logs=None): # RFC 3339 local_time = datetime.now(timezone.utc).astimezone().isoformat() logging.info("\\n{} accuracy={:.4f} loss={:.4f} Validation-accuracy={:.4f} Validation-loss={:.4f}" .format(local_time, logs['acc'], logs['loss'], logs['val_acc'], logs['val_loss'])) if __name__ == '__main__': train()
모델 컨테이너 이미지 만들기
모델 학습용 컨테이너 이미지를 만들기 위해서 Dockerfile을 생성하겠습니다.
다음은 텐서플로우 2.1을 기반 이미지로 해서, 모델 파일을 추가하는 Dockerfile 입니다.
Dockerfile
FROM tensorflow/tensorflow:2.1.0-py3 RUN mkdir -p /app ADD mnist-with-log.py /app/
다음 명령어로 “kangwoo/mnist-with-log:katib” 라는 이름으로 컨테이너 이미지를 빌드할 수 있습니다.
docker build -t kangwoo/mnist-with-log:katib.
빌드한 컨테이너 이미지를 컨테이너 이미지 레지스트리에 푸시합니다.
docker push kangwoo/mnist-with-log:katib
Experiment 생성하기
Experiment라는 사용자 리소스를 정의합니다. metricsCollectorSpec 필드에 filter가 추가되어 있습니다.
random-tf-event-example.yaml
apiVersion: "kubeflow.org/v1alpha3" kind: Experiment metadata: namespace: admin name: random-file-example spec: metricsCollectorSpec: source: fileSystemPath: path: "/var/log/katib/mnist.log" kind: File collector: kind: File parallelTrialCount: 1 maxTrialCount: 12 maxFailedTrialCount: 3 objective: type: maximize goal: 0.99 objectiveMetricName: Validation-accuracy additionalMetricNames: - accuracy algorithm: algorithmName: random parameters: - name: --learning_rate parameterType: double feasibleSpace: min: "0.01" max: "0.2" - name: --dropout parameterType: double feasibleSpace: min: "0.1" max: "0.5" trialTemplate: goTemplate: rawTemplate: |- apiVersion: batch/v1 kind: Job metadata: name: {{.Trial}} namespace: {{.NameSpace}} spec: template: spec: containers: - name: {{.Trial}} image: kangwoo/mnist-with-log:katib imagePullPolicy: Always command: - "python3" - "/app/mnist-with-log.py" {{- with .HyperParameters}} {{- range .}} - "{{.Name}}={{.Value}}" {{- end}} {{- end}} restartPolicy: Never
정의한 Experiment 사용자 리소스를 쿠버네티스 클러스터에 생성합니다.
kubectl apply -f random-tf-event-example.yaml
Experiment 결과 보기
Katib UI를 통해서 다음과 같은 결과를 확인할 수 있습니다.

