シリーズで、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);