레이블이 mnist인 게시물을 표시합니다. 모든 게시물 표시
레이블이 mnist인 게시물을 표시합니다. 모든 게시물 표시

2018년 8월 21일 화요일

Estimator-based tensorflow training에 LMS 적용한 MNIST python code


이 MNIST training을 위한 tensorflow python code는 원래 /opt/DL/tensorflow/lib/python3.6/site-packages/tensorflow/examples/tutorials/layers/cnn_mnist.py 에 LMS를 적용한 것입니다.  보시다시피 hook를 사용하기 때문에 Estimator-based tensorflow training이고, 그에 따라 LMS가 적용되어 있습니다. 

LMS가 동작하는 message 확인이라든가, 다중 사용자를 위한 permission 등을 위한 부분도 있습니다만 그건 LMS와는 무관한 부분이고, 그 부분들은 빨간색으로 표시를 했습니다.  실제 LMS 구현을 위한 부분은 굵은 파란색으로 표시했습니다.  의외로 간단하다는 것을 보실 수 있습니다.  해당 부분들을 제거하면 그냥 LMS 없는 평범한 MNIST training code가 됩니다.

이 example code도 PowerAI 5.2를 설치하면 딸려오는 /opt/DL/tensorflow/lib/python3.6/site-packages/tensorflow/contrib/lms/examples/cnn_mnist_lms.py 을 그대로 가져다 놓은 것입니다.

실제 수행해보면 다음과 같이 동작하며, 12개의 tensor가 host 서버의 RAM으로 swap-out/in 되는 것을 보실 수 있습니다.

[bsyu@p57a22 ~]$ cd /opt/DL/tensorflow/lib/python3.6/site-packages/tensorflow/contrib/lms/examples

[bsyu@p57a22 examples]$ source /opt/DL/tensorflow/bin/tensorflow-activate

[bsyu@p57a22 examples]$ python cnn_mnist_lms.py
...
INFO:tensorflow:[LMS][1] Tensor sparse_softmax_cross_entropy_loss/Sum_1:0 will be placed on /cpu:0
INFO:tensorflow:[LMS][1] Operation: adam_optimizer/gradients/sparse_softmax_cross_entropy_loss/div_grad/RealDiv_1, order 23, type RealDiv
INFO:tensorflow:[LMS][1] Tensor adam_optimizer/gradients/sparse_softmax_cross_entropy_loss/div_grad/RealDiv_1:0 will be placed on /cpu:0
INFO:tensorflow:[LMS][1] Consuming op adam_optimizer/gradients/sparse_softmax_cross_entropy_loss/div_grad/RealDiv_2 (order 24) swaps in adam_optimizer/gradients/sparse_softmax_cross_entropy_loss/div_grad/RealDiv_1:0
INFO:tensorflow:[LMS][1] No control dependency op needed for swap in of op adam_optimizer/gradients/sparse_softmax_cross_entropy_loss/div_grad/RealDiv_1.
INFO:tensorflow:[LMS][0] Edited model is valid and logically equivalent to the original one
INFO:tensorflow:[LMS][0] Added 25 ops into the model
INFO:tensorflow:[LMS][0] Editing model for LMS, took: 88.58513832092285 ms
INFO:tensorflow:[LMS][0] 12 tensors will be swapped out(in) to(from) the host
INFO:tensorflow:Graph was finalized.
...
{'accuracy': 0.9702, 'loss': 0.098796368, 'global_step': 20000}



전체 code 내용은 아래를 보시기 바랍니다.

[bsyu@p57a22 examples]$ cat /opt/DL/tensorflow/lib/python3.6/site-packages/tensorflow/contrib/lms/examples/cnn_mnist_lms.py

#  Copyright 2016 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.
"""Convolutional Neural Network Estimator for MNIST, built with tf.layers."""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import numpy as np
import tempfile # Change not related to LMS
import tensorflow as tf

tf.logging.set_verbosity(tf.logging.INFO)  #LMS 기능과는 무관. LMS 메시지를 보기 위한 설정.


def cnn_model_fn(features, labels, mode):
  """Model function for CNN."""
  # Input Layer
  # Reshape X to 4-D tensor: [batch_size, width, height, channels]
  # MNIST images are 28x28 pixels, and have one color channel
  input_layer = tf.reshape(features["x"], [-1, 28, 28, 1])

  # Convolutional Layer #1
  # Computes 32 features using a 5x5 filter with ReLU activation.
  # Padding is added to preserve width and height.
  # Input Tensor Shape: [batch_size, 28, 28, 1]
  # Output Tensor Shape: [batch_size, 28, 28, 32]
  conv1 = tf.layers.conv2d(
      inputs=input_layer,
      filters=32,
      kernel_size=[5, 5],
      padding="same",
      activation=tf.nn.relu)

  # Pooling Layer #1
  # First max pooling layer with a 2x2 filter and stride of 2
  # Input Tensor Shape: [batch_size, 28, 28, 32]
  # Output Tensor Shape: [batch_size, 14, 14, 32]
  pool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=[2, 2], strides=2)

  # Convolutional Layer #2
  # Computes 64 features using a 5x5 filter.
  # Padding is added to preserve width and height.
  # Input Tensor Shape: [batch_size, 14, 14, 32]
  # Output Tensor Shape: [batch_size, 14, 14, 64]
  conv2 = tf.layers.conv2d(
      inputs=pool1,
      filters=64,
      kernel_size=[5, 5],
      padding="same",
      activation=tf.nn.relu)

  # Pooling Layer #2
  # Second max pooling layer with a 2x2 filter and stride of 2
  # Input Tensor Shape: [batch_size, 14, 14, 64]
  # Output Tensor Shape: [batch_size, 7, 7, 64]
  pool2 = tf.layers.max_pooling2d(inputs=conv2, pool_size=[2, 2], strides=2)

  # Flatten tensor into a batch of vectors
  # Input Tensor Shape: [batch_size, 7, 7, 64]
  # Output Tensor Shape: [batch_size, 7 * 7 * 64]
  pool2_flat = tf.reshape(pool2, [-1, 7 * 7 * 64])

  # Dense Layer
  # Densely connected layer with 1024 neurons
  # Input Tensor Shape: [batch_size, 7 * 7 * 64]
  # Output Tensor Shape: [batch_size, 1024]
  dense = tf.layers.dense(inputs=pool2_flat, units=1024, activation=tf.nn.relu)

  # Add dropout operation; 0.6 probability that element will be kept
  dropout = tf.layers.dropout(
      inputs=dense, rate=0.4, training=mode == tf.estimator.ModeKeys.TRAIN)

  # Logits layer
  # Input Tensor Shape: [batch_size, 1024]
  # Output Tensor Shape: [batch_size, 10]
  logits = tf.layers.dense(inputs=dropout, units=10)

  predictions = {
      # Generate predictions (for PREDICT and EVAL mode)
      "classes": tf.argmax(input=logits, axis=1),
      # Add `softmax_tensor` to the graph. It is used for PREDICT and by the
      # `logging_hook`.
      "probabilities": tf.nn.softmax(logits, name="softmax_tensor")
  }
  if mode == tf.estimator.ModeKeys.PREDICT:
    return tf.estimator.EstimatorSpec(mode=mode, predictions=predictions)

  # Calculate Loss (for both TRAIN and EVAL modes)
  loss = tf.losses.sparse_softmax_cross_entropy(labels=labels, logits=logits)

  # Configure the Training Op (for TRAIN mode)
  if mode == tf.estimator.ModeKeys.TRAIN:
    with tf.name_scope('adam_optimizer'):
      optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.001)
      train_op = optimizer.minimize(
        loss=loss,
        global_step=tf.train.get_global_step())
    return tf.estimator.EstimatorSpec(mode=mode, loss=loss, train_op=train_op)

  # Add evaluation metrics (for EVAL mode)
  eval_metric_ops = {
      "accuracy": tf.metrics.accuracy(
          labels=labels, predictions=predictions["classes"])}
  return tf.estimator.EstimatorSpec(
      mode=mode, loss=loss, eval_metric_ops=eval_metric_ops)


def main(unused_argv):
  # Load training and eval data
  mnist = tf.contrib.learn.datasets.load_dataset("mnist")
  train_data = mnist.train.images  # Returns np.array
  train_labels = np.asarray(mnist.train.labels, dtype=np.int32)
  eval_data = mnist.test.images  # Returns np.array
  eval_labels = np.asarray(mnist.test.labels, dtype=np.int32)

  # The graph_location changes are not related to LMS enablement
  # but rather allow multiple users to run the example without
  # having permission issues on temp directories.
  graph_location = tempfile.mkdtemp()
  print('Saving graph to: %s' % graph_location)

  # Create the Estimator
  mnist_classifier = tf.estimator.Estimator(
      model_fn=cnn_model_fn, model_dir=graph_location)

  # Set up logging for predictions
  # Log the values in the "Softmax" tensor with label "probabilities"
  tensors_to_log = {"probabilities": "softmax_tensor"}
  logging_hook = tf.train.LoggingTensorHook(
      tensors=tensors_to_log, every_n_iter=50)

  # Train the model
  train_input_fn = tf.estimator.inputs.numpy_input_fn(
      x={"x": train_data},
      y=train_labels,
      batch_size=100,
      num_epochs=None,
      shuffle=True)

  # Hook for Large Model Support
  from tensorflow.contrib.lms import LMSHook
  lms_hook = LMSHook({'adam_optimizer'}, lb=3, debug=True)

  mnist_classifier.train(
      input_fn=train_input_fn,
      steps=20000,
      hooks=[logging_hook, lms_hook])

  # Evaluate the model and print results
  eval_input_fn = tf.estimator.inputs.numpy_input_fn(
      x={"x": eval_data},
      y=eval_labels,
      num_epochs=1,
      shuffle=False)
  eval_results = mnist_classifier.evaluate(input_fn=eval_input_fn)
  print(eval_results)


if __name__ == "__main__":
  tf.app.run()

2017년 10월 25일 수요일

Minsky에서의 Caffe2 설치 및 jupyter notebook에서 MNIST 돌려보기


PyTorch와 함께 Caffe2를 찾으시는 고객분들도 점점 늘고 계십니다.  PyTorch처럼 Caffe2도 아직 PowerAI 속에 포함되어 있지는 않지만, 그래도 ppc64le 아키텍처에서도 소스에서 빌드하면 잘 됩니다.  현재의 최신 Caffe2 소스코드에서는 딱 한군데, benchmark 관련 모듈에서 error가 나는 부분이 있는데, 거기에 대해서 IBM이 patch를 제공합니다.  아래를 보시고 따라 하시면 됩니다.

먼저, 기본적인 caffe2의 build는 caffe2 홈페이지에 나온 안내(아래 링크)를 그대로 따르시면 됩니다.

https://caffe2.ai/docs/getting-started.html?platform=ubuntu&configuration=compile

저는 아래와 같은 환경에서 빌드했습니다.  CUDA 8.0과 libcuDNN이 설치된 Ubuntu 16.04 ppc64le 환경입니다.   특기할 부분으로는, caffe2는 Anaconda와는 뭔가 맞지 않습니다.  Anaconda가 혹시 설치되어 있다면 관련 PATH를 모두 unset하시고 빌드하시기 바랍니다.

u0017649@sys-89697:~$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 16.04.2 LTS
Release:        16.04
Codename:       xenial

u0017649@sys-89697:~$ dpkg -l | grep cuda | head -n 4
ii  cuda                                                     8.0.61-1                                   ppc64el      CUDA meta-package
ii  cuda-8-0                                                 8.0.61-1                                   ppc64el      CUDA 8.0 meta-package
ii  cuda-command-line-tools-8-0                              8.0.61-1                                   ppc64el      CUDA command-line tools
ii  cuda-core-8-0                                            8.0.61-1                                   ppc64el      CUDA core tools

u0017649@sys-89697:~$ dpkg -l | grep libcudnn
ii  libcudnn6                                                6.0.21-1+cuda8.0                           ppc64el      cuDNN runtime libraries
ii  libcudnn6-dev                                            6.0.21-1+cuda8.0                           ppc64el      cuDNN development libraries and headers

위에서 언급한 대로, python이든 pip든 모두 Anaconda가 아닌, OS에서 제공하는 것을 사용하십시요.  여기서는 python 2.7을 썼습니다.

u0017649@sys-89697:~$ which python
/usr/bin/python


먼저, 기본적으로 아래의 Ubuntu OS package들을 설치하십시요.

u0017649@sys-89697:~$ sudo apt-get install -y --no-install-recommends build-essential cmake git libgoogle-glog-dev libprotobuf-dev protobuf-compiler

u0017649@sys-89697:~$ sudo apt-get install -y --no-install-recommends libgtest-dev libiomp-dev libleveldb-dev liblmdb-dev libopencv-dev libopenmpi-dev libsnappy-dev openmpi-bin openmpi-doc  python-pydot libgflags-dev

그리고 PowerAI에서 제공하는 openblas와 nccl도 설치하십시요.

u0017649@sys-89697:~$ sudo dpkg -i mldl-repo-local_4.0.0_ppc64el.deb

u0017649@sys-89697:~$ sudo apt-get update

u0017649@sys-89697:~$ sudo apt-get install libnccl-dev
u0017649@sys-89697:~$ sudo apt-get install libopenblas-dev libopenblas

그리고 아래의 pip package들을 설치하십시요.

u0017649@sys-89697:~$ pip install flask future graphviz hypothesis jupyter matplotlib pydot python-nvd3 pyyaml requests scikit-image scipy setuptools six tornado protobuf

이제 caffe2의 source를 download 받습니다.

u0017649@sys-89697:~$ git clone --recursive https://github.com/caffe2/caffe2.git
Cloning into 'caffe2'...
remote: Counting objects: 36833, done.
remote: Compressing objects: 100% (64/64), done.
remote: Total 36833 (delta 37), reused 34 (delta 12), pack-reused 36757
Receiving objects: 100% (36833/36833), 149.17 MiB | 11.42 MiB/s, done.
Resolving deltas: 100% (26960/26960), done.
Checking connectivity... done.
Submodule 'third_party/NNPACK' (https://github.com/Maratyszcza/NNPACK.git) registered for path 'third_party/NNPACK'
Submodule 'third_party/NNPACK_deps/FP16' (https://github.com/Maratyszcza/FP16.git) registered for path 'third_party/NNPACK_deps/FP16'
...
remote: Counting objects: 353, done.
remote: Total 353 (delta 0), reused 0 (delta 0), pack-reused 353
Receiving objects: 100% (353/353), 119.74 KiB | 0 bytes/s, done.
Resolving deltas: 100% (149/149), done.
Checking connectivity... done.
Submodule path 'third_party/pybind11/tools/clang': checked out '254c7a91e3c6aa254e113197604dafb443f4d429'

이제 ppc64le를 위한 source 수정을 하나 하겠습니다.  Patch가 필요한 부분은 주요 부분은 아니고, 벤치마크를 위한 third party 모듈 중 하나입니다.

u0017649@sys-89697:~$ cd caffe2/third_party/benchmark/src

u0017649@sys-89697:~/caffe2/third_party/benchmark/src$ vi cycleclock.h
...
#elif defined(__powerpc__) || defined(__ppc__)
  // This returns a time-base, which is not always precisely a cycle-count.
  int64_t tbl, tbu0, tbu1;
  asm("mftbu %0" : "=r"(tbu0));
  asm("mftb  %0" : "=r"(tbl));
  asm("mftbu %0" : "=r"(tbu1));
  tbl &= -static_cast<long long>(tbu0 == tbu1);
//  tbl &= -static_cast<int64_t>(tbu0 == tbu1);

수정의 핵심은 저 int64 대신 long long을 넣는 것입니다.

** 그 사이에 하나 더 늘었네요.  아래와 같이 third_party/NNPACK/CMakeLists.txt의 30번째 줄에 ppc64le를 넣어줍니다.

u0017649@sys-89697:~/caffe2/build# vi ../third_party/NNPACK/CMakeLists.txt
...
ELSEIF(NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^(i686|x86_64|armv5te|armv7-a|armv7l|aarch64|ppc64le)$")
  MESSAGE(FATAL_ERROR "Unrecognized CMAKE_SYSTEM_PROCESSOR = ${CMAKE_SYSTEM_PROCESSOR}")
...

그 다음으로는 나중에 cblas 관련 error를 없애기 위해, 아래와 같이 Dependencies.cmake 파일에서 cblas 관련 1줄을 comment-out 시킵니다.

u0017649@sys-89697:~/caffe2/cmake$ vi Dependencies.cmake
...
elseif(BLAS STREQUAL "OpenBLAS")
  find_package(OpenBLAS REQUIRED)
  caffe2_include_directories(${OpenBLAS_INCLUDE_DIR})
  list(APPEND Caffe2_DEPENDENCY_LIBS ${OpenBLAS_LIB})
#  list(APPEND Caffe2_DEPENDENCY_LIBS cblas)
...

그리고 OpenBLAS_HOME을 아래와 같이 PowerAI에서 제공하는 openblas로 설정합니다.

u0017649@sys-89697:~/caffe2/cmake$ export OpenBLAS_HOME=/opt/DL/openblas/

또 하나 고칩니다.  FindNCCL.cmake에서 아래 부분을 PowerAI의 NCCL directory로 명기하면 됩니다.

root@26aa285b6c46:/data/imsi/caffe2/cmake# vi Modules/FindNCCL.cmake
...
#set(NCCL_ROOT_DIR "" CACHE PATH "Folder contains NVIDIA NCCL")
set(NCCL_ROOT_DIR "/opt/DL/nccl" CACHE PATH "Folder contains NVIDIA NCCL")
...

이제 cmake 이후 make를 돌리면 됩니다.  단, default인 /usr/local이 아닌, /opt/caffe2에 caffe2 binary가 설치되도록 CMAKE_INSTALL_PREFIX 옵션을 줍니다.

u0017649@sys-89697:~$ cd caffe2
u0017649@sys-89697:~/caffe2$ mkdir build
u0017649@sys-89697:~/caffe2$ cd build

u0017649@sys-89697:~/caffe2/build$ cmake -DCMAKE_INSTALL_PREFIX=/opt/caffe2 ..

u0017649@sys-89697:~/caffe2/build$ make

약 1시간 넘게 꽤 긴 시간이 걸린 뒤에 make가 완료됩니다.  이제 make install을 하면 /opt/caffe2 밑으로 binary들이 설치됩니다.

u0017649@sys-89697:~/caffe2/build$ sudo make install

u0017649@sys-89697:~/caffe2/build$ ls -l /opt/caffe2
total 28
drwxr-xr-x  2 root root 4096 Oct 25 03:16 bin
drwxr-xr-x  3 root root 4096 Oct 25 03:16 caffe
drwxr-xr-x 24 root root 4096 Oct 25 03:16 caffe2
drwxr-xr-x  4 root root 4096 Oct 25 03:16 include
drwxr-xr-x  2 root root 4096 Oct 25 03:16 lib
drwxr-xr-x  3 root root 4096 Oct 25 03:16 share
drwxr-xr-x  2 root root 4096 Oct 25 03:16 test

다른 일반 user들도 사용할 수 있도록 필요에 따라 /opt/caffe2의 ownership을 바꿔줍니다.  (optional)

u0017649@sys-89697:~/caffe2/build$ sudo chown -R u0017649:u0017649 /opt/caffe2

이제 테스트를 해봅니다.  먼저, PYTHONPATH를 설정해줍니다.

u0017649@sys-89697:~/caffe2/build$ export PYTHONPATH=/opt/caffe2

다음과 같이 기본 테스트를 2가지 해봅니다.  먼저 import core를 해봅니다. 

u0017649@sys-89697:~/caffe2/build$ python -c 'from caffe2.python import core' 2>/dev/null && echo "Success" || echo "Failure"
Success

다음으로는 operator_test.relu_op_test를 수행해봅니다.   제가 가진 환경은 GPU가 안 달린 환경이라서 아래 붉은색의 warning이 있습니다만, 여기서는 무시하십시요.

u0017649@sys-89697:~/caffe2/build$ pip install hypothesis

u0017649@sys-89697:~$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/caffe2/lib

u0017649@sys-89697:~$ python -m caffe2.python.operator_test.relu_op_test
NVIDIA: no NVIDIA devices found
WARNING: Logging before InitGoogleLogging() is written to STDERR
E1025 05:09:33.528787 12222 common_gpu.cc:70] Found an unknown error - this may be due to an incorrectly set up environment, e.g. changing env variable CUDA_VISIBLE_DEVICES after program start. I will set the available devices to be zero.
Trying example: test_relu(self=<__main__.TestRelu testMethod=test_relu>, X=array([ 0.], dtype=float32), gc=, dc=[], engine=u'')
Trying example: test_relu(self=<__main__.TestRelu testMethod=test_relu>, X=array([[[-0.03770517, -0.03770517, -0.03770517, -0.03770517],
        [-0.03770517, -0.03770517, -0.03770517, -0.03770517],
        [-0.03770517, -0.03770517, -0.03770517, -0.03770517]],
...
        [ 0.96481699,  0.96481699,  0.96481699,  0.96481699],
        [ 0.96481699,  0.96481699,  0.96481699, -0.74859387]]], dtype=float32), gc=, dc=[], engine=u'')
Trying example: test_relu(self=<__main__.TestRelu testMethod=test_relu>, X=array([ 0.61015409], dtype=float32), gc=, dc=[], engine=u'CUDNN')
.
----------------------------------------------------------------------
Ran 1 test in 0.957s

OK

테스트도 정상적으로 완료되었습니다.   이제 jupyter notebook에서 MNIST를 수행해보시지요.

먼저 jupyter notebook을 외부 browser에서 접속할 수 있도록 config를 수정합니다.

u0017649@sys-89697:~$  jupyter notebook --generate-config
Writing default config to: /home/u0017649/.jupyter/jupyter_notebook_config.py

u0017649@sys-89697:~$ vi /home/u0017649/.jupyter/jupyter_notebook_config.py
...
#c.NotebookApp.ip = 'localhost'
c.NotebookApp.ip = '*'

jupyter notebook을 kill 했다가 다시 띄웁니다.   이제 아래 붉은색처럼 token이 display 됩니다.

u0017649@sys-89697:~$ jupyter notebook &

[I 05:13:29.492 NotebookApp] Writing notebook server cookie secret to /home/u0017649/.local/share/jupyter/runtime/notebook_cookie_secret
[W 05:13:29.578 NotebookApp] WARNING: The notebook server is listening on all IP addresses and not using encryption. This is not recommended.
[I 05:13:29.665 NotebookApp] Serving notebooks from local directory: /home/u0017649
[I 05:13:29.666 NotebookApp] 0 active kernels
[I 05:13:29.666 NotebookApp] The Jupyter Notebook is running at: http://[all ip addresses on your system]:8888/?token=ca44acc48f0f2daa6dc9d935904f1de9a1496546efc95768
[I 05:13:29.666 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[W 05:13:29.667 NotebookApp] No web browser found: could not locate runnable browser.
[C 05:13:29.667 NotebookApp]

    Copy/paste this URL into your browser when you connect for the first time,
    to login with a token:
        http://localhost:8888/?token=ca44acc48f0f2daa6dc9d935904f1de9a1496546efc95768

이 token을 복사해서 외부의 browser에서 입력하면 됩니다.  여기서 사용된 서버의 ip가 172.29.160.94이므로, 먼저 browser에서 http://172.29.160.94:8888의 주소를 찾아들어갑니다.



접속이 완료되면 다시 명령어 창에서 아래의 MNIST python notebook을 download 받습니다.

u0017649@sys-89697:~$ wget https://raw.githubusercontent.com/caffe2/caffe2/master/caffe2/python/tutorials/MNIST.ipynb


이 MNIST.ipynb이 jupyter 화면에 뜨면, 그걸 클릭합니다.




이제 문단 하나하나씩 play button을 누르면서 진행해보실 수 있습니다.







2017년 7월 24일 월요일

Minsky 서버에서 NVIDIA DIGITS로 MNIST LeNet training 해보기

이번 posting 내용은 아래 URL에 있는 것을 IBM ppc64le 아키텍처 위에서 제공되는 IBM PowerAI tool에 포함된 digits로 구현해본 것입니다.

https://github.com/NVIDIA/DIGITS/blob/master/docs/GettingStarted.md

DIGITS는 NVIDIA에서 내놓은 오픈소스 기반의 딥 러닝 toolkit입니다.  DIGITS에 대해서 NVIDIA가 소개해놓은 글 ( https://developer.nvidia.com/digits )을 읽어보면 대체 이게 뭐라는 것인지 명확하지는 않습니다.  아래와 같이 그냥 좋은 말만 나와 있거든요.  사실 이런 경향은 IBM을 포함한 모든 벤더들이 다 보여주고 있습니다.

The NVIDIA Deep Learning GPU Training System (DIGITS) puts the power of deep learning into the hands of engineers and data scientists. DIGITS can be used to rapidly train the highly accurate deep neural network (DNNs) for image classification, segmentation and object detection tasks.

결론적으로, DIGITS는 caffe나 torch와 같은 딥 러닝용 framework 자체는 아니고, caffe나 torch를 이용하여 딥 러닝을 수행할 때 좀 더 쉽고 빠르게 할 수 있게 해주는 유용한 웹 기반의 tool입니다.  아마 당장 나오는 질문이 '그럼 tensorflow는 안 되나요'라는 것일텐데, 2017년 7월 24일 현재 당장은 안 됩니다.  그러나 NVIDIA도 DIGITS에 tensorflow 지원을 추가하려고 하고 있고, 이번 달에 나온다고 합니다.

여기서는 caffe를 이용해서 가장 간단한 딥 러닝 training인 MNIST dataset에 대한 LeNet 신경망 training을 DIGITS를 통해 해보도록 하겠습니다.

nvidia-caffe와 digits의 설치는 IBM PowerAI toolkit에서 제공되는 것을 그대로 사용하겠습니다.   그 설치 방법에 대해서는 지난번 posting ( http://hwengineer.blogspot.kr/2017/05/minsky-cuda-powerai-tuning.html )을 참조하시기 바랍니다.

먼저, 그냥 caffe를 이용하여 MNIST dataset에 대한 LeNet 신경망 training을 하는 방법을 봐야 하는데, 이에 대해서는 역시 지난번 posting ( http://hwengineer.blogspot.kr/2017/06/mnist-gpu-cpu-deep-learning.html )에 잘 나와 있으니 그를 참조하시기 바랍니다.

오늘 사례는 GPU가 없는 환경에서 digits를 수행해야 합니다.

시작하기 전에 DIGITS 관련 환경변수를 setup해야 합니다.  DIGITS는 위에서 언급한 것처럼 caffe와 torch를 사용하는 것이기 때문에, "apt-get install digits" 명령만 내려도 자동으로 caffe-nv와 torch를 함께 설치합니다.  마찬가지로, DIGITS를 사용하기 위해 PowerAI에서 제공하는 환경변수 setup script인 /opt/DL/digits/bin/digits-activate를 수행해도, caffe-nv와 torch 관련 환경변수들이 함께 setup 됩니다.   먼저 기본 상태의 PATH 관련 환경 변수를 확인하겠습니다.

u0017496@sys-88165:~$ env | grep PATH
PATH=/home/u0017496/bin:/home/u0017496/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

이제 PowerAI에서 제공하는 환경변수 setup script인 /opt/DL/digits/bin/digits-activate를 수행하겠습니다.  앞에 sh 또는 .을 함께 입력하셔야 한다는 것에 유의하십시요.

u0017496@sys-88165:~$ . /opt/DL/digits/bin/digits-activate

이제 다시 환경변수를 확인하면, openblas와 nccl, cuda 등은 물론이고, torch를 위한 LUA 관련 PATH들도 자동 설정된 것을 보실 수 있습니다.

u0017496@sys-88165:~$ env | grep PATH
LD_LIBRARY_PATH=/opt/DL/openblas/lib:/opt/DL/nccl/lib:/opt/DL/caffe-nv/lib:/usr/local/cuda-8.0/lib64:/opt/DL/torch/lib:/opt/DL/torch/lib/lua/5.1
PATH=/home/u0017496/bin:/home/u0017496/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/DL/caffe-nv/bin:/opt/DL/torch/bin:/opt/DL/digits
LUA_PATH=/home/u0017496/.luarocks/share/lua/5.1/?.lua;/home/u0017496/.luarocks/share/lua/5.1/?/init.lua;/opt/DL/torch/share/lua/5.1/?.lua;/opt/DL/torch/share/lua/5.1/?/init.lua;
LUA_CPATH=/home/u0017496/.luarocks/lib/lua/5.1/?.so;/opt/DL/torch/lib/lua/5.1/?.so;
DYLD_LIBRARY_PATH=/opt/DL/torch/lib:/opt/DL/torch/lib/lua/5.1
PYTHONPATH=/opt/DL/caffe-nv/python:/opt/DL/digits

이제 python을 이용하여 홈 디렉토리 밑에 mnist dataset을 download 받겠습니다.  이때 digits의 download_data 모듈을 유용하게 사용할 수 있습니다.

u0017496@sys-88165:~$ python -m digits.download_data mnist ~/mnist
Downloading url=http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz ...
Downloading url=http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz ...
Downloading url=http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz ...
Downloading url=http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz ...
Uncompressing file=train-images-idx3-ubyte.gz ...
Uncompressing file=train-labels-idx1-ubyte.gz ...
Uncompressing file=t10k-images-idx3-ubyte.gz ...
Uncompressing file=t10k-labels-idx1-ubyte.gz ...
Reading labels from /home/u0017496/mnist/train-labels.bin ...
Reading images from /home/u0017496/mnist/train-images.bin ...
Reading labels from /home/u0017496/mnist/test-labels.bin ...
Reading images from /home/u0017496/mnist/test-images.bin ...
Dataset directory is created successfully at '/home/u0017496/mnist'
Done after 24.0719361305 seconds.

이 python 명령 한줄이면 mnist dataset이 모두 자동으로 다 준비된 것입니다.  아래에서 보시다시피 ~/mnist/train 밑에 train data가 들어가 있는 것을 확인하실 수 있습니다.  ~/mnist/test 밑의 data는 이름과는 달리 test가 아니라 validation을 위해 사용될 것입니다.

u0017496@sys-88165:~$ cd mnist

u0017496@sys-88165:~/mnist$ pwd
/home/u0017496/mnist

u0017496@sys-88165:~/mnist$ ls
t10k-images-idx3-ubyte.gz  test-images.bin  train-images.bin            train-labels-idx1-ubyte.gz
t10k-labels-idx1-ubyte.gz  test-labels.bin  train-images-idx3-ubyte.gz
test                       train            train-labels.bin

u0017496@sys-88165:~/mnist$ cd train

u0017496@sys-88165:~/mnist/train$ ls
0  1  2  3  4  5  6  7  8  9  labels.txt  train.txt


이제 digits-server를 수행합니다.  이는 shell cript로 되어 있으며, daemon화 되어 있지는 않으므로 nohup ~ & 구문을 써서 구동하시는 것이 좋습니다.

u0017496@sys-88165:~$ /opt/DL/digits/digits-devserver &
  ___ ___ ___ ___ _____ ___
 |   \_ _/ __|_ _|_   _/ __|
 | |) | | (_ || |  | | \__ \
 |___/___\___|___| |_| |___/ 5.0.0

/usr/lib/python2.7/dist-packages/matplotlib/font_manager.py:273: UserWarning: Matplotlib is building the font cache using fc-list. This may take a moment.
  warnings.warn('Matplotlib is building the font cache using fc-list. This may take a moment.')
NVIDIA: no NVIDIA devices found
cudaRuntimeGetVersion() failed with error #30
2017-07-20 02:33:14 [INFO ] Loaded 0 jobs.

위에서 보시다시피 no NVIDIA devices found라고 error/warning이 있습니다만, 이는 제가 쓰는 이 시스템에 GPU가 없어서 그런 것이니 일단 무시하십시요.


이제 웹 브라우저를 이용해서 http://서버IP주소:5000/ 로 접속하십시요.   그러면 아래와 같이 'Models'라는 메뉴 화면에 접속하게 됩니다.



여기서 아래와 같이 Datasets > New Dataset > Images > Classification 순으로 click 하면 login page로 접속하게 됩니다.  사용자 id를 넣으라고 나올텐데, 이는 사용하시는 OS user명을 넣으시면 됩니다.



이제 아래와 같은 New Image Classification Dataset 메뉴에 들어왔습니다.  여기서 training images에는 위에서 보신 mnist train 디렉토리를 아래와 같이 입력하시고, image type은 Grayscale로, image size는 28 x 28로 변경하십시요.  또 'separate images validation folder'에 check 표시를 클릭해주시면 추가 메뉴가 나오는데, 거기에는 위에서 언급한 대로 mnist test 디렉토리를 입력해주시면 됩니다.  Backend DB는 LMDB로 지정하시고, dataset 이름을 적절한 것으로 지정하십시요.  여기서는 MNIST로 정하겠습니다.




그리고나서 이제 바닥의 파란색 Create 버튼을 click하시면 됩니다.  그러면 우측 상태 메뉴에서 Job Status가 running으로 나오는 것을 보실 수 있습니다.   아래 그림에서는 'Create DB (train)'이 현재 8% 진행되었고 전체적으로는 5분 34초 걸릴 것이라고 예상되어 있는 것을 보실 수 있습니다.  여기서 DB라 함은 LMDB를 말하는 것입니다.  LMDB는 Lightning Memory – Mapped Database라는 것으로서, 간단히 말하면 jpeg 등의 작은 파일로 된 data들을 담는 key-value store이며, 일반 DBMS는 아니지만 고속의 성능을 내기 때문에 이런 딥 러닝 training dataset을 저장하는데 자주 사용됩니다.



이렇게 LMDB가 생성되고 있는데, 그 data는 어떤 규칙에 의해서 만들어지고 있는 것일까요 ?  그건 오른쪽의 스크롤바를 내려 더 아래쪽으로 내려가보시면 볼 수 있습니다.  보시면 "Input File"이라는 항목에 train.txt라는 text file이 보이지요.  거기에는 각 png file 이름과 그에 따른 label이 정의되어 있고, 그에 따라 LMDB가 생성되고 있습니다.  
그 밑에는 그 카테고리, 즉 label에 해당하는 image file들의 숫자가 그래프로 표시되어 있고, 그 그래프 밑에는 'Explore DB'라는 버튼이 보입니다.



이 'Explore DB'라는 버튼을 클릭해보면 아래와 같이 0~9의 숫자마다 어떤 손글씨 그림이 들어있는지를 눈으로 보실 수 있습니다.



LMDB 생성 규칙을 담은 train.txt라는 link를 클릭해보면, 어떤 png file이 어느 숫자로 labeling되어 있는지 보실 수 있습니다.   아래 화면에서는 다 0으로 labeling 되어 있습니다.


좌측 상단의 DIGITS를 click하여 다시 홈페이지로 복귀합니다.   이제 MNIST 라는 dataset을 보실 수 있습니다.


다음으로는 model을 생성하겠습니다.   다음과 같이 Model tab에서 오른쪽 중단의 Images > Classification을 택합니다.


Dataset으로는 방금 생성한 MNIST를 선택합니다.



(여기서부터는 K80이 2장 장착된 Firestone 장비를 구했기 때문에 그것 기준으로 작성했습니다.)

더 밑으로 내려보면 아래와 같이 어떤 신경망을 이용해 training을 할 것인지 정하게 되어 있습니다.  여기서는 LeNet을 선택합니다.


그 밑으로는 몇개의 GPU를 사용하여 training할지 숫자를 적거나, 또는 현재 이 시스템에 장착된 GPU의 종류 및 개수를 보여주며 그 중 어느것을 사용할지 특정하여 선택하게 되어 있습니다.  여기서는 0번과 1번의 2개를 선택하겠습니다.  이렇게 해서 training될 모델의 이름은 LeNet으로 정하겠습니다.


'Create' 버튼을 클릭하면 training이 시작됩니다.   (위에서와는 달리 GPU가 장착된 서버이므로) 이젠 training 속도가 매우 빨라서 3분도 채 안 걸릴 것이라는 예상이 나오는 것을 보실 수 있습니다.


스크롤바를 밑으로 내려보면 accuracy와 loss가 실시간 graph로 보여집니다.  가로축은 epoch, 즉 전체 dataset을 몇번 반복해서 training했는지의 횟수인데, accuracy가 첫 epoch에서 이미 98%에 도달한 이후 더 개선이 없는 것을 보실 수 있습니다.   즉, 이런 경우는 여러번 반복 training하는 것이 큰 의미는 없습니다.  아무튼 여기서는 default인 30-epoch 반복하여 training합니다.


더 아래로 내리면 learning rate가 step size에 따라 계단식으로 줄어드는 것을 보실 수 있습니다.  또한 Caffe process ID 및 그것의 CPU 및 메모리 사용량도 보여줍니다.


아래 시점에서는 이미 19-epoch 반복 training이 끝났고, 그 snapshot이 저장되었기 때문에 'Trained Models'의 'Select Model' 항목에 Epoch #19가 display 되고 있는 것입니다.  Training이 원래대로 30회 반복되고 나면 저 숫자가 30까지 display 될 것입니다.



자, 30회 반복되어 training이 끝났습니다.  이제 trained model을 가지고 inference를 해볼 수 있습니다.  아래의 'Test a single image' 항목에서 Image Path에 실제 png file의 full path 이름을 입력합니다.






입력 후 'Classify One' 버튼을 누르면 새 tab이 생기면서 그 결과가 보여집니다.


여기서는 기존 training dataset 중 하나를 넣은 것이므로, 당연히 100% 정확한 답을 맞춥니다.


이번에는 다음과 같이 8과 1이라는 숫자의 이미지의 URL 주소를 가진 text 파일 'URL1.txt'를 upload하여 분류하도록 해보겠습니다.

http://www.clker.com/cliparts/V/1/Y/3/j/Z/blue-number-1-md.png
https://upload.wikimedia.org/wikipedia/commons/c/cf/NShw_8.png

이 URL에서 가리키는 URL은 각각 다음과 같습니다.





저 URL 주소를 가진 'URL1.txt'를 아래와 같이 'Upload Images List' 항목에 입력하고 'Classify Many'를 클릭합니다.


그 결과는 아래와 같습니다.   즉, 1과 8 대신 4와 9라고 답한 것이지요.  확실히 더 많은 image로 더 많은 training을 거쳐야 제대로 된 답을 내놓을 수 있습니다.


그렇다면 아래처럼 2글자로 된 이미지에는 뭐라고 답을 할까요 ?  참고로 이 LeNet이라는 모델은 하나의 숫자에 대해서만 training이 되어 있습니다.


이번에는 아래처럼 직접 image file을 upload하여 'Classify One' 버튼을 눌러 보겠습니다.  여기서는 특히 'Show visualizations and statistics' 체크 박스도 체크해보겠습니다.


결과는 아래와 같습니다.  3과 8에서 고민하다가 결국 3을 택한 것을 보실 수 있습니다.


그 아래로 내려보면 'Show visualizations and statistics'에 체크를 한 것 때문에 다양한 비주얼라이제이션과 통계치를 보실 수 있습니다.



digits에는 더욱 많은 기능들이 있으니 차츰 더 알아가 보시기 바랍니다.