MXNet API入門 パート1

(注:2017/06/16、いただいたフィードバックを元に翻訳を修正いたしました。)

1-H0-jFUWPK6TQIXeNqTnaUg
MXNetチュートリアル

このシリーズでは、MXnet深層学習ライブラリの概要をまとめていきます。主な機能やPython API(もっとも選択しやすいと思います)について説明します。それからMXNetチュートリアルやオンラインで提供されているノートブックを探究してみましょう。うまくいけば、なんとかコードの1行1行を理解出来るようになるでしょう。

MXNetの理論的根拠やアーキテクチャについて学びたい方は、ぜひ『MXNet: A Flexible and Efficient Machine Learning Library for Heterogeneous Distributed Systems(MXNet: ヘテロジニアス分散システムのための柔軟で効率的な機械学習ライブラリ)』という論文をお読みください。この論文に書かれている概念のほとんどに触れますが、より分かりやすく説明できればと思っています。

ゆっくりと進めながら可能なかぎり説明します。計算や専門用語も必要最低限にしますが、意図的にレベルを下げた説明はしません。これを読んだからといって専門家にはなれません。これを書いている私も専門家ではありません。それでも、独自のアプリケーションに深層学習機能を追加出来るだけの知識を得ることはできると思います。

MXNetをローカルで動かす

最初にMXNetをインストールしましょう。公式の使用説明書はここにありますが、さらに秘訣を幾つか教えましょう。

MXNetのカッコいい機能の1つにCPUとGPUで全く同じように動いてくれるものがあります(後で演算の際の選択方法について説明します)。これは、(私のMacBookのように)Nvidia GPUが搭載されていないコンピュータでも、後ほど、GPU対応システムで使うことになるMXNetコードを書くことも実行することも可能だということです。

もし使っているコンピュータにGPUなるものが搭載されていれば最高ですが、悪夢をもたらす可能性の高いCUDAツールキットおよびcuDNNツールキットをインストールする必要があります。MXNetバイナリとNvidiaツールの間で少しでも不一致が生じるとこれまでのセットアップは破たんし、使えなくなってしまいます。

このため、MXNetのWebサイトで提供されているDockerイメージをそれぞれ、CPU環境およびGPU環境に使うよう強く忠告します(nvidia-dockerが必要になります)。これらのイメージには、必要なもの全てがあらかじめインストールされているので、すぐに始めることができます。

sudo -H pip install mxnet  --upgrade
python
>>> import mxnet as mx
>>> mx.__version__
'0.9.3a3'

参考までに付け加えますが、Dockerイメージは「pip」で提供されるPythonパッケージよりも最新のバージョンが提供されていると思います。

docker run -it  mxnet/python
root@88a5fe9c8def:/# python
>>> import mxnet as mx
>>> mx.__version__
'0.9.5'

MXNetをAWSで動かす

AWSでは、Linux用とUbuntu用の深層学習AMIを提供しています。AMIには、MXNet含め、多くの深層学習フレームワークがあらかじめインストールされています。特別な調整は不要です。

====================================================================
       __|  __|_  )
       _|  (     /   Deep Learning AMI for Amazon Linux
      ___|\___|___|
====================================================================

[ec2-user@ip-172-31-42-173 ~]$ nvidia-smi -L
GPU 0: GRID K520 (UUID: GPU-d470337d-b59b-ca2a-fe6d-718f0faf2153)

[ec2-user@ip-172-31-42-173 ~]$ python
>>> import mxnet as mx
>>> mx.__version__
'0.9.3'

このAMIは、通常のインスタンス上またはGPUインスタンス上で実行できます。コンピュータにNvidia GPUを搭載していない場合は、後でネットワークのトレーニングを始める時に便利になります。最も安価なオプションはg2.2xlargeインスタンスを1時間約73円で使用することです。

今ここで必要となるのは、昔ながらのCPUです。では、始めましょう。

NDArrayはなぜ重要か

MXNet APIで最初に見るのは、NDArray APIです。NDArrayとは、全く同じ型とサイズの項目を含む(32ビット浮動小数点数や32ビット整数など)、n次元配列です。

なぜこれらの配列は重要なのでしょうか。前の記事でも説明しましたが、ニューラルネットワークのトレーニングや実行には多くの数学演算を必要とします。多次元配列でデータ保存を行います。

入力データやニューロンウェイト(重み値)、出力データはベクトルや行列に格納されるため、この種の構造体を使用するのは至って自然です。

簡単な例、画像の分類を見てみましょう。下記の画像は手書きの数字の「8」を18×18ピクセルで表示しています。

1-D6pp5hTfUl8FmzYwJ3N8LQ
18×18ピクセルで表した数字の8

これは、18×18行列の画像と同じ画像を表しています。該当するピクセルに対応するセルがそれぞれグレースケール値を持っています。例えば、「0」は白で、「255」は黒、その間の値は254種の灰色の色合いを表します。この行列の表現をニューラルネットワークに実行して、0から9までの数値を分類するトレーニングをさせます。

1-Ct7GN4a5gqONNUFV_qMu_A
上記の画像に対応するグレースケール値を持つ18×18行列

では、グレースケールの画像の代わりにカラーの画像を使った場合を想像してください。それぞれの画像は、それぞれのカラー用の3つの行列を使って表すため、入力データは若干複雑になります。

さらに進めてみましょう。自立粗鋼のためのリアルタイム画像認識をしていると仮定します。最新のデータを元に決定を下すために 1000×1000ピクセルのRBG画像を毎秒30フレームで使用します。毎秒90個の1000×1000行列(30フレーム×3色)が処理されます。各ピクセルは32ビット値で表しているので、90×1000×1000×4バイト、つまり343メガバイト前後になります。複数のカメラがある場合は、バイト数はすぐに膨れ上がってしまいます。

最高のパフォーマンス(例えば最低限のレイテンシ)で、ニューラルネットワークが処理するデータとしては膨大な量なので、GPUは画像を1つ1つ処理するのではなく、バッチで処理します。仮にバッチサイズが8だとすると、ニューラルネットワークは入力データを1000×1000×24の束で処理します。例えば、3次元配列が8個の3色の1000×1000画像を持つ場合です。

肝心なのは、NDArrayを理解することです。ニューラルネットワークに必須で、ほとんどのデータを格納するために使用されます。

NDArray API

なぜNDArrayが重要なのか分かったので、どのようにして動くかを見てみましょう(やっとコード部分です)。numpy Pythonライブラリを使ったことある方には朗報です。NDArrayはとても良く似ていて、すでにAPIのほとんどを知っていると思います。完全にドキュメント化されていますので、こちらで入手してください。

では、基礎から始めましょう。解説は不要ですよね。

>>> a = mx.nd.array([[1,2,3], [4,5,6]])
>>> a.size
6
>>> a.shape
(2L, 3L)
>>> a.dtype
<type 'numpy.float32'>

NDArrayはデフォルトで32ビット浮動小数点数を持っていますが、これは変更可能です。

>>> import numpy as np
>>> b = mx.nd.array([[1,2,3], [2,3,4]], dtype=np.int32)
>>> b.dtype

NDArrayの出力は下記のとおり簡単です。

>>> b.asnumpy()
array([[1, 2, 3],
       [2, 3, 4]], dtype=int32)

期待している数学演算子の全てが利用可能です。では、要素ごとの行列の乗算を試してみましょう。

>>> a = mx.nd.array([[1,2,3], [4,5,6]])
>>> b = a*a
>>> b.asnumpy()
array([[  1.,   4.,   9.],
       [ 16.,  25.,  36.]], dtype=float32)

正しい行列の乗算はどうでしょう(「ドット積」とも呼ばれます)。

>>> a = mx.nd.array([[1,2,3], [4,5,6]])
>>> a.shape
(2L, 3L)
>>> a.asnumpy()
array([[ 1.,  2.,  3.],
       [ 4.,  5.,  6.]], dtype=float32)
>>> b = a.T
>>> b.shape
(3L, 2L)
>>> b.asnumpy()
array([[ 1.,  4.],
       [ 2.,  5.],
       [ 3.,  6.]], dtype=float32)
>>> c = mx.nd.dot(a,b)
>>> c.shape
(2L, 2L)
>>> c.asnumpy()
array([[ 14.,  32.],
       [ 32.,  77.]], dtype=float32)

それでは、少し複雑なことを試してみましょう。

  • 1000×1000行列を一様分布で初期化し、GPU#0に保存します(ここではg2インスタンスを使用しています)。
  • 別の1000×1000行列を正規分布(平均値1と標準偏差値2)で初期化し、これもGPU#0に保存します。
>>> c = mx.nd.uniform(low=0, high=1, shape=(1000,1000), ctx="gpu(0)")
>>> d = mx.nd.normal(loc=1, scale=2, shape=(1000,1000), ctx="gpu(0)")
>>> e = mx.nd.dot(c,d)

MXNetがCPUとGPUで全く同じように動いてくれることを忘れないでください。例えば、上記の例で “gpu(0)” を “cpu(0)” に置きかえれば、ドット積はCPUで処理されることになります。

これで、NDArrayで遊べるだけの知識はついたと思います。ニューラルネットワークを構築するために使える高度な機能もありますが、ネットワークの説明の時に触れます(FullyConnectedニューラルネットワークなど)。

今回はここまでです。次のパートでは、ニューラルネットワークにとって重要なデータフローを定義させてくれるSymbol APIについて見ていきます。最後まで読んでいただきありがとうございます。今後もご期待ください。

続きのパート:
訳注:原文ウェブサイトに遷移します。

  • パート2: Symbol API
  • パート3: Module API
  • パート4: 事前トレーニングしたモデルで画像部類 (Inception v3)
  • パート5: さらに別の事前トレーニングしたモデル (VGG16やResNet-152)
  • パート6: Raspberry Piでリアルタイムオブジェクト検出(しかも話します)