Functionalモデル

Functionalモデルの使用方法を紹介.

このチュートリアルでは,以下の内容を紹介します.

  • どのようにニューラルネットワークを定義するか.
  • どのように学習させるか.
  • 定義したニューラルネットワークの重みパラメータにどのようにアクセスするか.

このチュートリアルでは,データセットやGPUの設定は必要ありません.ReNom 2.0 とNumpyが必要となります.

必要なライブラリ

  • numpy 1.12.1
In [1]:
from __future__ import division, print_function
import numpy as np
import renom as rm
from renom.optimizer import Sgd
from renom.cuda.cuda import set_cuda_active

モデルを用いたニューラルネットワークの定義

ReNomにはニューラルネットワークを定義する方法として主に2つの方法が存在します.一つはsequential modelでもう一つはfunctional modelです.
単純なモデルに関してはsequential modelを使うことをお勧めします.
しかし時々sequential modelでは記述できないようなネットワーク構造も存在します.
そのような場合にはfunctional modelは効果を発揮するでしょう.
renom.Model クラスは,メモリリークを防ぎコードを簡単化するのに有効です.更に,学習済み重みパラメータの保存などのユーティリティ関数を使用出来ます.
最初に, renom.Model クラスを継承して,新たなニューラルネットワークモデルクラスを定義します.
まずは __init()__ 内に,学習対象となる全結合レイヤを定義します.
そして, forward() 関数の中に順伝播計算を記述します.
In [2]:
class Tutorial02(rm.Model):

    def __init__(self):
        # Definition of learnable layer.
        # Layer objects are created but weights are not created yet.
        self._layer1 = rm.Dense(100)
        self._layer2 = rm.Dense(10)

    # Definition of forward calculation.
    def forward(self, x):
        return self._layer2(rm.relu(self._layer1(x)))

全結合(Dense)オブジェクト

Denseオブジェクトは層間のユニット同士が密に結合していることを表す全結合オブジェクトです.

z = W・x + b

そのため,Denseオブジェクトは学習対象のパラメータとして,重みパラメータ W とバイアスパラメータ b を持ちます.

上で定義した二層ニューラルネットワークを表すTutorial02クラスにおける順伝播は以下の式のように書きます.

z = w2・activation(w1・x + b1) + b2

以下のプログラムで確認できるように,学習対象のパラメータを持つクラスはすべてParametrizedクラスを継承しています.

In [3]:
# Instantiation.
layer = rm.Dense(2)
layer(np.random.rand(1, 2))

# Type
print(type(layer))

# Learnable parameters.
# 'Params' is a dictionary.
keys = layer.params.keys()
print("This object has {} learnable parameters {}".format(len(keys), keys))

# Confirmation of the inheritence
print("Is this object a child of Parametrized?",
      isinstance(layer, rm.Parametrized))
<class 'renom.layers.function.dense.Dense'>
This object has 2 learnable parameters dict_keys(['w', 'b'])
Is this object a child of Parametrized? True

活性化関数

上記で定義したTutorial02クラスでは活性化関数として rm.relu を使用しています.その他の活性化関数については,ドキュメントのAPIセクションを確認してください.

In [4]:
r = np.random.randn(2)
a = rm.relu(r)
print("func    :          input            →          output  ")
print("relu    : {} → {}".format(r, a))
a = rm.sigmoid(r)
print("sigmoid : {} → {}".format(r, a))
a = rm.tanh(r)
print("tanh    : {} → {}".format(r, a))
func    :          input            →          output
relu    : [-0.10757246  1.86587957] → [ 0.          1.86587954]
sigmoid : [-0.10757246  1.86587957] → [ 0.47313279  0.8659808 ]
tanh    : [-0.10757246  1.86587957] → [-0.10715944  0.95321912]

順伝播計算の実行

入力データ x と教師データ y を準備します.さらに,定義したTutorial02クラスをインスタンス化します.インスタンス化したモデルオブジェクトに対して入力データ x を与えると順伝播計算を実行することが出来ます.

In [5]:
# This input matrix has 10 datas(records) and each data has 100 dims.
x = np.random.rand(10, 100)
# This target matrix has 10 datas(records) and each data has 10 dims.
y = np.random.rand(10, 10)

# Instantiation.
model = Tutorial02()

# Forward propagation.
z = model(x)
print("Output shape is {}.".format(z.shape))
Output shape is (10, 10).

誤差関数の定義

モデルを学習させるために,モデルの出力と教師データ間の誤差を測る誤差関数を定義します.

renom.mean_squared_error(z, y) はモデルの出力と教師データ間の誤差を平均二乗誤差関数で測ります.平均二乗誤差は,(ex.2)のように書くことも出来ます.

In [6]:
# These are same.
loss = rm.mean_squared_error(model(x), y) # ex.1
loss = rm.sum((model(x) - y)**2)/10/2      # ex.2

誤差逆伝播の実行

誤差逆伝播を実行するには grad() メソッドを呼びます。grad()はVariableオブジェクトの勾配を持つGradのオブジェクトを返します.得られたGradクラスのオブジェクトの update メソッドを実行することで,計算された勾配を用いて重みパラメータを更新します.

ReNom version1と2では,学習時に二点ほど違いがあります.1つは,逆伝播を実行する際のメソッド名です.version 1では backward() でしたが,version2では,grad()へと変更されました.

また二点目として,逆伝播時に遡る計算履歴(計算グラフ)を作成するか否かを明示的に書く必要があります.

そのためReNom versoin2では以下のような with block の中での計算グラフが作られます.つまり, with block 内で行われた計算については自動微分を実行することが出来ます.with block の外部で実行された計算については,勾配計算に考慮されません.

下記の例では with model.train() ブロック内で実行された計算のみ計算グラフが作られます.

下記の例はwithブロックが無いと,grad()メソッドを呼び出しても勾配が計算されないので重みパラメータが更新されません.

In [7]:
for _ in range(5):
    with model.train():
        loss = rm.sum((model(x) - y)**2)/10/2
    loss.grad().update()
    print(loss.as_ndarray())
3.8187854290008545
2509.43603515625
2421.71435546875
0.39176782965660095
0.39176779985427856

勾配降下アルゴリズムを用いたパラメータの更新

ReNomはAdamやAdagradのような,いくつかの勾配降下アルゴリズムを提供しています.

In [8]:
optimizer = Sgd(lr = 0.01)
for _ in range(5):
    with model.train():
        loss = rm.sum((model(x) - y)**2)/10/2
    loss.grad().update(optimizer)
    print(loss.as_ndarray())
0.39176779985427856
0.39176779985427856
0.39176779985427856
0.39176779985427856
0.39176779985427856

重みパラメータへのアクセス方法

ReNomでは重みパラメータは最初の順伝播実行時に初期化されます.一度重みパラメータが作成されると,それらのパラメータはparams属性を通してアクセスすることが出来ます.

paramsは辞書型オブジェクトなので,キーを用いたパラメータへのアクセスも可能です.

In [9]:
# Confirm the weight parameters
print("keys of weight")
print(model._layer1.params.keys())

print()

# Get the parameters using either of the following ways
print("The weight of first layer's 'w'.")
print(model._layer1.params.w[:2, 0])
print(model._layer1.params["w"][:2, 0])

print()

# Initialize the parameters with random values
shape = model._layer1.params.w.shape
model._layer1.params.w = rm.Variable(np.random.randn(*shape)*0.1)
print("Set another value to the above weight.")
print(model._layer1.params.w[:2, 0])
keys of weight
dict_keys(['w', 'b'])

The weight of first layer's 'w'.
[ -7.86825895 -13.92789936]
[ -7.86825895 -13.92789936]

Set another value to the above weight.
[ 0.056779   -0.09167486]