チュートリアル:ユニット機能を用いて複雑なネットワークを簡潔に記述する

Friday, June 15, 2018

App

Posted by Yoshiyuki Kobayashi

Windows版Neural Network Console Version 1.20以降では、ユニット機能を用いることで入れ子構造を持つネットワークなど、複雑なニューラルネットワークをより簡潔に記述することができるようになりました。

本チュートリアルでは、ユニット機能の利用方法について、基本的なConvolutional Neural NetworkであるLeNetを題材に解説します。


LeNet

 

1. ユニット機能でできること

上記LeNetを観察すると、Inputレイヤーの直後、Convolution→MaxPooling→ReLUという構造が2回繰り返して登場していることが分かります。

ユニット機能を用いない場合は、上記のようにConvolution→MaxPooling→ReLUを2回続けて記述することになります。同じ構造を2回も記述しなければならないというのは(コピー&ペースト機能を用いることができるとしても)面倒です。また、後からネットワーク構造を編集したい時、例えば各ReLUをELUに変更したいと思った場合、2つのReLUをそれぞれELUに置き換える操作を行う必要があり、やはり面倒です。

ユニット機能を用いると、Convolution→MaxPooling→ReLUの1かたまりをユニットとして定義することで、下記にようにネットワーク構造の使い回しを実現することができます。


ユニット機能を用いたLeNetの記述
MainネットワークにおけるConvUnitの処理は、ConvUnitネットワークに記述されている

 

ユニット機能を用いた場合、Convolution→MaxPooling→ReLUの構造はConvUnit内にのみ記述されているため、先ほどのReLUのELUへの変更についてもConvUnitのReLUをELUに置き換える操作のみで実現できるようになります。

ユニット機能は、プログラミングにおけるサブルーチンや関数と似た役割を果たします。プログラミングではよく使う機能を関数として定義して再利用できるようにしますが、ユニット機能はこれと同じようによく使うネットワーク構造をユニットとして定義し、再利用することを可能にするものです。

特に大きく複雑なネットワークを設計する際には、少ない労力でネットワーク全体を設計できる、ネットワーク全体の見通しを良くできる(可読性の向上)、高いメンテナンス性を実現できる(保守性の向上)など、その効果が顕著になります。

 

2. ユニットの定義方法

ユニットの定義はとても簡単です。編集タブのネットワークグラフ上方の+ボタンをクリックして新しいネットワーク構造を追加し、この中にユニットの内容を記述します。

新しいネットワーク構造の追加

 

例えば先ほどのConvolution→MaxPooling→ReLUの構造をユニットとして定義するには、以下のようにInputレイヤーに続きConvolution、MaxPooling、ReLUレイヤーを接続します。定義が終わったら、ネットワーク構造の名前部分をクリックして、ネットワーク構造の名前をユニット名(今回の場合「ConvUnit」)に変更します。

ユニットの定義

 

3. ユニットに引数を追加する

プログラミングにおいて関数に引数を与ることができるのと同じように、ユニットに対してもユニットの呼び出し元から指定可能な引数を定義することができます。ここでは、先ほど定義したConvUnit内のConvolutionのKernelShapeを外部から指定可能な引数とする例を取り上げながら、引数の定義方法について解説します。

まず、ユニットに引数を追加するには、Unitカテゴリに含まれるArgumentレイヤーをユニットに追加します。

Argumentレイヤーの追加

 

Argumentレイヤーを追加したら、次にArgumentレイヤーの引数を編集し、引数の名前や型、初期値を設定します。今回の例では、追加したArgumentレイヤーを選択し、Nameプロパティを任意の引数名(今回は「ConvKernel」)に、型を示すTypeプロパティを「PIntArray(正の整数配列)」に、デフォルト値を示す「Value」プロパティを「5,5」にそれぞれ編集します。

Argumentレイヤーの編集

 

最後に、定義した引数を、その引数を用いるレイヤーが参照するように設定します。引数は、ユニット内の任意のレイヤーの編集可能な任意のプロパティに対してアスタリスク+引数名を指定することで割り当てることができます。今回のケースでは、Convolutionレイヤーを選択し、KernelShapeプロパティを、「*ConvKernel」とします。これによりConvolutionレイヤーは、ConvKernel引数で指定された値をKernelShapeの値として用いるようになります。

レイヤーがArgumentレイヤーで定義した引数を利用するよう設定

 

4. 定義したユニットを、他のネットワーク構造から利用する

それでは、定義したユニットを用いたMainネットワークを設計してみましょう。定義したユニットをネットワーク中で用いるには、Unitカテゴリ内のUnitレイヤーを使用します。

Mainネットワークをクリックして選択し、Mainネットワークの編集を開始します。LeNetの場合、定義したConvUnitをネットワークの最初で2回用いることになるため、Inputレイヤーに続きUnitレイヤーを2つ配置します。

MainネットワークへのUnitレイヤーの配置

 

Unitレイヤーを配置した直後は、これらのUnitレイヤーがどのユニットを使用するのかが指定されていないため、上記のように警告が表示されます。Unitレイヤーを選択し、Networkプロパティに定義したユニットの名前(ネットワーク構造名)を指定することで、Unitレイヤーが正しく機能するようになります。今回の場合、追加した2つのUnitレイヤーのNetworkプロパティに「ConvUnit」を指定します。

Unitレイヤーのプロパティの編集

 

また、UnitレイヤーのPropertyには、ユニット内部で定義した引数が現れます。今回の場合、先ほどConvUnitにおいてArgumentレイヤーを用いて定義した「ConvKernel」引数が、Unitレイヤーのプロパティとして確認できました。このUnitレイヤーのプロパティに現れる引数を変更することで、ユニットに渡す引数を指定することができるというわけです。今回の場合、2つ目のConvolutionのKernelShapeを3,3とするため、2つ目のUnitに現れたConvKernelプロパティを「3,3」とします。

この後は通常通り、2つのUnitレイヤーのあとにAffine→ReLU→Affine→Softmax→CategoricalCrossEntropyレイヤーを追加することで、LeNetを完成させることができます。

 

5. 様々なユニット機能の用途

ユニット機能は、上記で紹介した用途以外に下記のような用途に活用することができます。

オリジナルのレイヤーを定義する
独自のレイヤーをMathレイヤー、Arithmeticレイヤーといった基本的なレイヤーを組み合わせて作成し、これをユニットとして定義するという使い方です。
Unitレイヤーを用いて定義したレイヤーを呼び出すことで、あたかもそのレイヤーが元々Neural Network Consoleに用意されていたかのように扱うことができます。

学習、評価、推論時で異なるネットワークの共通部分を一度だけ定義する
学習、評価、推論時でそれぞれ異なるロス関数や前処理を用いたい場合に、異なる部分のみをそれぞれのネットワークとして記述し、共通の部分はユニットとして共通化するという使い方です。
共通部分の定義が1か所になるため高いメンテナンス性を実現することができます。

 

6. ユニットを用いたネットワークのPythonコードを出力する

ユニットを用いて定義したネットワークについても、編集タブの右クリックメニュー内のExport、Python Code(NNabla)を選択することで、Neural Network Librariesから利用可能なソースコードを出力することができます。

以下は先ほど定義したLeNetの出力例です。

import nnabla as nn
import nnabla.functions as F
import nnabla.parametric_functions as PF

def network(x, y, test=False):
    # Input:x -> 1,28,28

    # ConvUnit -> 16,12,12
    with nn.parameter_scope('ConvUnit'):
    h = network_ConvUnit(x, (5,5))

    # ConvUnit_2 -> 16,5,5
    with nn.parameter_scope('ConvUnit_2'):
    h = network_ConvUnit(h, (3,3))

    # Affine -> 100
    h = PF.affine(h, (100,), name='Affine')
    # ReLU_3
    h = F.relu(h, True)
    # Affine_2 -> 10
    h = PF.affine(h, (10,), name='Affine_2')
    # Softmax
    h = F.softmax(h)
    # CategoricalCrossEntropy -> 1
    h = F.categorical_cross_entropy(h, y)
    return h

def network_ConvUnit(x, ConvKernel, test=False):
    # ConvInput:x -> 1,28,28

    # Convolution -> 16,24,24
    h = PF.convolution(x, 16, ConvKernel, (0,0), name='Convolution')
    # MaxPooling -> 16,12,12
    h = F.max_pooling(h, (2,2), (2,2))
    # ReLU
    h = F.relu(h, True)
    return h

ユニット機能を用いたプロジェクトのNeural Network Libraries用Pythonコードの出力例

 

この例では、network関数とは別に、ConvUnitに相当するnetwork_ConvUnit関数が出力されていることが分かります。network_ConvUnit関数はnetwork関数内で利用されています。また、network_ConvUnit関数は引数としてConvKernelを取り、この引数はnetwork関数内でnetwork_ConvUnitを呼び出す際に与えられていることが分かります。