NNablaの静的・動的計算グラフの比較

Jul 25, 2017   #nnabla  #Python 

はじめに

NNablaはSonyによるニューラルネットワークライブラリです.特徴としては公式ページにあるように動的計算グラフと静的計算グラフの双方をサポートすること,PythonとC++のAPIが用意されていること,Xperia Earなどの小型端末をはじめ,さまざまな機器の上で動き実際に利用されていることなどがあります.

加えてレイヤーがParametric Functionsという名前で表されていること,バイナリレイヤーが用意されていること,学習のモニタ機能が充実していること(Monitors)なども特徴と言えるのではないでしょうか.逆にRNNレイヤーなどは用意されていません.

最近Python3に対応し使えるようになったこともあり,Sonyに愛着があったので試してみました.MNISTをやるだけです.

インストール

LinuxとWindowsではpip install -U nnablaで導入できるのですが,macOSではソースからのビルドの必要があります(2017/07/26現在).

brew install protoc
git clone https://github.com/sony/nnabla
cd nnabla
sudo pip install -U -r python/setup_requirements.txt
sudo pip install -U -r python/requirements.txt
mkdir build
cd build
export MACOSX_DEPLOYMENT_TARGET=10.9
cmake ../
make -j 16
cp lib/libnnabla.dylib /usr/local/lib/
cd dist
sudo pip install -U <build-wheel-file>.whl

モデル

LeNetの活性化函数をreluに変えたものを使います.とりあえずPyTorch風に書いています.nnabla.get_parameters()parameter_scope内を参照するようですので適宜設定します.

import nnabla
from nnabla import Variable
import nnabla.functions as F
import nnabla.parametric_functions as PF
from nnabla import solvers

class Lenet(object):
    def __init__(self):
        self.param_dict = None
        self.conv1 = lambda x: PF.convolution(x, 16, (5, 5), name="conv1")
        self.conv2 = lambda x: PF.convolution(x, 16, (5, 5), name="conv2")
        self.fc3 = lambda x: PF.affine(x, 50, name='fc3')
        self.fc4 = lambda x: PF.affine(x, 10, name='fc4')

    def __call__(self, x):
        with nnabla.parameter_scope("lenet"):
            x = self.conv1(x)
            x = F.relu(F.max_pooling(x, (2, 2)), inplace=True)
            x = self.conv2(x)
            x = F.relu(F.max_pooling(x, (2, 2)), inplace=True)
            x = F.relu(self.fc3(x), inplace=True)
            x = self.fc4(x)
            self.param_dict = nnabla.get_parameters()
        return x

静的計算グラフ

define-and-runです.

def static():
    ##  計算グラフの構築
    input = Variable((batch_size, *image_size))
    target = Variable((batch_size, 1))
    model = Lenet()
    loss = F.mean(F.softmax_cross_entropy(output, target))

    optimizer = solvers.Adam()
    optimizer.set_parameters(model.param_dict)

    for step, data in enumerate(train_data_iter):
        input.d, target.d = data
        ## 順伝播
        loss.forward()
        ## 勾配を初期化
        optimizer.zero_grad()
        ## 逆伝播,勾配を更新
        loss.backward()
        ## パラメーターを更新
        optimizer.update()

        if step % 100 == 0:
           logger.info("step: {}  loss: {:.2f}.".format(step, float(loss)))

動的計算グラフ

define-by-runです.動的計算グラフを使う場合にはグラフ構築箇所でnnabla.auto_forwardを使います.

def dynamic():

    model = Lenet()
    optimizer = solvers.Adam()

    for step, (input, target) in enumerate(train_data_iter):
        input = Variable.from_numpy_array(input)
        target = Variable.from_numpy_array(target)
        ##  計算グラフを構築
        with nnabla.auto_forward():
            output = model(input)
            loss = F.mean(F.softmax_cross_entropy(output, target))
        optimizer.set_parameters(self.model.param_dict, reset=False,
                                 retain_state=True)
        ## 勾配を初期化
        optimizer.zero_grad()
        ## 逆伝播,勾配を更新
        loss.backward()
        ## パラメーターを更新
        optimizer.update()

        if step % 100 == 0:
           logger.info("step: {}  loss: {:.2f}.".format(step, float(loss)))

まとめ

簡単にNNablaでMNISTを動かしてみました.静的計算グラフではTensorFlowよりも簡潔に書ける気がします.