EN

Python-like C++API 解説 第1回 Python-like C++APIの概要

2019年7月23日 火曜日

API

Posted by Naoki Ide

シリーズで、NNablaのPython-like C++APIをご紹介したいと思います。

第一回目は、Python-like C++APIの概要を紹介します。

 

Python-like C++ APIは、NNablaのAPIの一つです。

このAPIは、深層学習開発フレームワークに対する、

・C++で設計したい

・とはいえ、Pythonで設計するように簡潔に設計したい

という潜在的な要望に応えて開発されたAPIです。

 

特徴は、NNablaのPythonAPIとそっくりにC++を使ってネットワーク設計と学習、推論ができる点です。

NNablaのPythonAPIと、C++APIで同一のネットワークを記述したプログラムを掲載します。

どちらがC++で、どちらがPythonか区別できないのではないでしょうか。

 

LeNet for MNIST Classification by ###


auto h = pf::convolution(x, 1, 16, {5, 5}, parameters["conv1"]); h = f::max_pooling(h, {2, 2}, {2, 2}, true, {0, 0}); h = f::relu(h, false); h = pf::convolution(h, 1, 16, {5, 5}, parameters["conv2"]); h = f::max_pooling(h, {2, 2}, {2, 2}, true, {0, 0}); h = f::relu(h, false); h = pf::affine(h, 1, 50, parameters["affine3"]); h = f::relu(h, false); h = pf::affine(h, 1, 10, parameters["affine4"]);

 

LeNet for MNIST Classification by ###


h = PF.convolution(x, 16, (5, 5), name="conv1") h = F.max_pooling(h, (2, 2)) h = F.relu(h, inplace=False) h = PF.convolution(h, 16, (5, 5), name="conv2") h = F.max_pooling(h, (2, 2)) h = F.relu(h, inplace=False) h = PF.affine(h, 50, name="affine3") h = F.relu(h, inplace=False) h = PF.affine(h, 10, name="affine4")

 

少し眺めていると、上段がC++API、下段がPythonAPIであることがわかると思います。

C++APIとPythonAPIを比較すると、C++APIでは、PythonAPIと比べて、一行あたりでは一、二割程度文字数が多くなっています。

しかし、行数では全く同じです。

また、行数のみならず、PythonAPIの様々な記法が、C++APIでも模倣されていることがみてとれます。

 

なお、NNablaには、ローレベルのC++APIも用意されています。

NNablaの元のローレベルのC++APIで同等のネットワーク(LeNet for MNIST classification)を書くと以下のように長くなります。

あまりにも長いため、一部の共通化した関数の詳細を省略させていただきました。

このような冗長なソースコードは、バグの温床になり、また、メンテナンス性も非常に悪い、ということは言うまでもないでしょう。

お時間のある方は、下記ソースコードも眺めてみてください。

 


float multiplier; multiplier = conv_init_multiplier(1, 16, 25); CgVariablePtr conv1_w = make_parameter(ctx, { 16, 1, 5, 5 }, multiplier, "uniform"); CgVariablePtr conv1_b = make_parameter(ctx, { 16 }, 0, "fixed"); vector c1_pad = { 0, 0 }; vector c1_stride = { 1, 1 }; vector c1_dilation = { 1, 1 }; auto conv1 = make_shared(create_Convolution(ctx, 1, c1_pad, c1_stride, c1_dilation, 1)); auto h1 = connect(conv1, { x, conv1_w, conv1_b }, 1); vector m1_kernel = { 2, 2 }; vector m1_stride = { 2, 2 }; vector m1_pad = { 0, 0 }; auto max_pool1 = make_shared(create_MaxPooling(ctx, m1_kernel, m1_stride, true, m1_pad)); h1 = connect(max_pool1, { h1[0] }, 1); auto relu1 = make_shared(create_ReLU(ctx, false)); h1 = connect(relu1, { h1[0] }, 1); multiplier = conv_init_multiplier(16, 16, 25); CgVariablePtr conv2_w = make_parameter(ctx, { 16, 16, 5, 5 }, multiplier, "uniform"); CgVariablePtr conv2_b = make_parameter(ctx, { 16 }, 0, "fixed"); vector c2_pad = { 0, 0 }; vector c2_stride = { 1, 1 }; vector c2_dilation = { 1, 1 }; auto conv2 = make_shared(create_Convolution(ctx, 1, c2_pad, c2_stride, c2_dilation, 1)); auto h2 = connect(conv2, { h1[0], conv2_w, conv2_b }, 1); vector m2_kernel = { 2, 2 }; vector m2_stride = { 2, 2 }; vector m2_pad = { 0, 0 }; auto max_pool2 = make_shared(create_MaxPooling(ctx, m2_kernel, m2_stride, true, m2_pad)); h2 = connect(max_pool2, { h2[0] }, 1); auto relu2 = make_shared(create_ReLU(ctx, false)); h2 = connect(relu2, { h2[0] }, 1); multiplier = affine_init_multiplier(256, 50); auto affine3 = make_shared(create_Affine(ctx, 1)); CgVariablePtr affine3_w = make_parameter(ctx, { 256, 50 }, multiplier, "uniform"); CgVariablePtr affine3_b = make_parameter(ctx, { 50 }, 0, "fixed"); auto h3 = connect(affine3, { h2[0], affine3_w, affine3_b }, 1); auto relu3 = make_shared(create_ReLU(ctx, false)); h3 = connect(relu3, { h3[0] }, 1); multiplier = affine_init_multiplier(50, 10); auto affine4 = make_shared(create_Affine(ctx, 1)); CgVariablePtr affine4_w = make_parameter(ctx, { 50, 10 }, multiplier, "uniform"); CgVariablePtr affine4_b = make_parameter(ctx, { 10 }, 0, "fixed"); auto h4 = connect(affine4, { h3[0], affine4_w, affine4_b }, 1);