アルパカ三銃士

〜アルパカに酔いしれる獣たちへ捧げる〜

MXNet の基礎を Perl で学んでみた part 1

最近 MXNet の論文を読んだり、MXNet はこれから使われるのかどうかについて調査していた。
結構将来ありそうな感じがした。

そこで、何かしら基本になりそうなチュートリアルはないかと漁っていたら以下のページにたどり着いた。 becominghuman.ai

MXNet の基礎をそれぞれ重要だろうなと思った部分部分で分けてやっていこうと思う。 Perl の repl である Reply を用いた結果を記していく。

Symbol: Declarative Symbolic Expressions

これについてはここを参考にするとかなり詳しく把握することができる。
Symbol API は各プログラミング間での MXNet 関連の情報のやりとりについてとても重要になってくる。例えば、以下のコードのように宣言することが可能である。この API を用いることでニューラルネットワークにとって、重要なデータフローを定義させてくれる。

0> use AI::MXNet qw(mx);
1> my $a = mx->sym->Variable('A');
$res[0] = bless( {
         'handle' => bless( do{\(my $o = '140656734787584')}, 'SymbolHandle' )
       }, 'AI::MXNet::Symbol' )

2> my $b = mx->sym->Variable('B');
$res[1] = bless( {
         'handle' => bless( do{\(my $o = '140656737826608')}, 'SymbolHandle' )
       }, 'AI::MXNet::Symbol' )

3> my $c = mx->sym->Variable('C');
$res[2] = bless( {
         'handle' => bless( do{\(my $o = '140656731703904')}, 'SymbolHandle' )
       }, 'AI::MXNet::Symbol' )

4> my $d = mx->sym->Variable('D');
$res[3] = bless( {
         'handle' => bless( do{\(my $o = '140656737037344')}, 'SymbolHandle' )
       }, 'AI::MXNet::Symbol' )

5> my $e = ($a * $b) + ($c * $d);
$res[4] = bless( {
         'handle' => bless( do{\(my $o = '140656737827616')}, 'SymbolHandle' )
       }, 'AI::MXNet::Symbol' )

結果を見て分かる通り、これらは全て有効なシンタックスである。これらで行われているのは MXNet 内部での宣言である。更に $e に着目して以下を実行していく

6> $e->list_arguments
$res[5] = [
  'A',
  'B',
  'C',
  'D'
]

7> $e->list_outputs
$res[6] = [
  '_plus0_output'
]

8> $e->get_internals->list_outputs
$res[7] = [
  'A',
  'B',
  '_mul0_output',
  'C',
  'D',
  '_mul1_output',
  '_plus0_output'
]

これらより分かることは以下の通りになる

  • $e は変数 A, B, C, D に依存している
  • $e は足し算を行っている
  • $e は確かに ($a * $b) + ($c * $d) を行っている

Perl 上でどのように Symbol API を扱えるかは AI::MXNet::Symbol のドキュメントを読むと良い。

NDArray: Imperative Tensor Computation

ここからは以下のページも参考にしながら行った。

medium.com

ニューラルネット上で扱うための情報全てが n 次元の配列(行列)であるため、この NDArray API はとても重要になってくる。NDArray を用いた行列計算は次の方法で可能となる。

9> use PDL
10> my $f = mx->nd->array([[1,2,3], [4,5,6]], dtype => 'int32')
$res[11] = bless( {
         'handle' => bless( do{\(my $o = '140656695593488')}, 'NDArrayHandle' ),
         'writable' => 1
       }, 'AI::MXNet::NDArray' )

11> my $g = $f * $f
$res[17] = bless( {
         'handle' => bless( do{\(my $o = '140656737168304')}, 'NDArrayHandle' )
       }, 'AI::MXNet::NDArray' )

12> print $g->aspdl

[
 [ 1  4  9]
 [16 25 36]
]
$res[22] = 1

また、ドット積も以下のように簡単に行うことが可能である

13> my $f = mx->nd->array([[1,2,3], [4,5,6]], dtype => 'float32')
$res[0] = bless( {
         'handle' => bless( do{\(my $o = '140226728273088')}, 'NDArrayHandle' ),
         'writable' => 1
       }, 'AI::MXNet::NDArray' )

14> $f->shape
$res[1] = [
  2,
  3
]

15> print $f->aspdl

[
 [1 2 3]
 [4 5 6]
]
$res[2] = 1

16> my $g = $f->T
$res[3] = bless( {
         'handle' => bless( do{\(my $o = '140226729807504')}, 'NDArrayHandle' )
       }, 'AI::MXNet::NDArray' )

17> $g->shape
$res[4] = [
  3,
  2
]

18> print $g->aspdl

[
 [1 4]
 [2 5]
 [3 6]
]
$res[5] = 1

19> my $h = mx->nd->dot($f, $g)
$res[6] = bless( {
         'handle' => bless( do{\(my $o = '140226729834560')}, 'NDArrayHandle' )
       }, 'AI::MXNet::NDArray' )

20> $h->shape
$res[7] = [
  2,
  2
]

21> print $h->aspdl

[
 [14 32]
 [32 77]
]
$res[8] = 1

これで NDArray の大体の使い方を把握することができた。
ここで、Symbol API と NDArray API を組み合わせて使ってみる。
始めに上記の Symbol API で定義した変数を NDArray へ紐付けを行うために、それぞれの変数に紐付けを行いたい NDArray を定義していく。

22> my $a_data = mx->nd->array([1], dtype => 'int32')
$res[14] = bless( {
         'handle' => bless( do{\(my $o = '140226729837008')}, 'NDArrayHandle' ),
         'writable' => 1
       }, 'AI::MXNet::NDArray' )

23> my $b_data = mx->nd->array([2], dtype => 'int32')
$res[15] = bless( {
         'handle' => bless( do{\(my $o = '140226726637904')}, 'NDArrayHandle' ),
         'writable' => 1
       }, 'AI::MXNet::NDArray' )

24> my $c_data = mx->nd->array([3], dtype => 'int32')
$res[16] = bless( {
         'handle' => bless( do{\(my $o = '140226726632016')}, 'NDArrayHandle' ),
         'writable' => 1
       }, 'AI::MXNet::NDArray' )

25> my $d_data = mx->nd->array([4], dtype => 'int32')
$res[17] = bless( {
         'handle' => bless( do{\(my $o = '140226726639984')}, 'NDArrayHandle' ),
         'writable' => 1
       }, 'AI::MXNet::NDArray' )

以下で紐付けを行った後、AI::MXNet::Executor オブジェクトを作成し、($a * $b) + ($c * $d)を行う。(この時の ($a * $b) + ($c * $d)(1 * 2) + (3 * 4) となる)

26> my $executor = $e->bind(ctx => mx->cpu, args => {'A'=>$a_data, 'B'=>$b_data, 'C'=>$c_data, 'D'=>$d_data})
$res[20] = bless( {
         '_ctx' => bless( {
                            'device_id' => 0,
                            'device_type' => 'cpu'
                          }, 'AI::MXNet::Context' ),
         '_grad_req' => 'write',
         '_group2ctx' => undef,
         '_symbol' => bless( {
                               'handle' => bless( do{\(my $o = '140226726617152')}, 'SymbolHandle' )
                             }, 'AI::MXNet::Symbol' ),
         'arg_arrays' => [
                           bless( {
                                    'handle' => bless( do{\(my $o = '140226729837008')}, 'NDArrayHandle' ),
                                    'writable' => 1
                                  }, 'AI::MXNet::NDArray' ),
                           bless( {
                                    'handle' => bless( do{\(my $o = '140226726637904')}, 'NDArrayHandle' ),
                                    'writable' => 1
                                  }, 'AI::MXNet::NDArray' ),
                           bless( {
                                    'handle' => bless( do{\(my $o = '140226726632016')}, 'NDArrayHandle' ),
                                    'writable' => 1
                                  }, 'AI::MXNet::NDArray' ),
                           bless( {
                                    'handle' => bless( do{\(my $o = '140226726639984')}, 'NDArrayHandle' ),
                                    'writable' => 1
                                  }, 'AI::MXNet::NDArray' )
                         ],
         'aux_arrays' => [],
         'grad_arrays' => undef,
         'handle' => bless( do{\(my $o = '140226726839136')}, 'ExecutorHandle' ),
         'outputs' => [
                        bless( {
                                 'handle' => bless( do{\(my $o = '140226726830848')}, 'NDArrayHandle' )
                               }, 'AI::MXNet::NDArray' )
                      ]
       }, 'AI::MXNet::Executor' )

27> my $e_data = $executor->forward
$res[21] = [
  bless( {
           'handle' => bless( do{\(my $o = '140226726830848')}, 'NDArrayHandle' )
         }, 'AI::MXNet::NDArray' )
]

28> $e_data->[0]
$res[22] = bless( {
         'handle' => bless( do{\(my $o = '140226726830848')}, 'NDArrayHandle' )
       }, 'AI::MXNet::NDArray' )

29> print $e_data->[0]->aspdl
[14]$res[24] = 1

最終的な結果で [14] が出力されたため、Symbol API を用いて宣言した通りの計算を行っていることが分かる。
Perl 上でどのように NDArray API を扱えるかは AI::MXNet::NDArray のドキュメントを読むと良い。

今回はここまで。
次はこちら。

codehex.hateblo.jp