ReNomによる自動微分を用いた基本的な数値計算

ニューラルネットワークによる回帰を例にして自動微分の使い方を説明.

損失関数の自動微分は以下のように .grad の部分で行うことができます。

loss.grad()
例としてsin関数にノイズを加えた分布から得られたデータを,ニューラルネットワークを用いて回帰する問題を扱います.
まず2層からなるニューラルネットワーク表現を定義します。

必要なライブラリ

以下のライブラリを必要とします.
- matplotlib 2.0.2
- numpy 1.13.1
In [1]:
# -*- coding: utf-8 -*-
import matplotlib.pyplot as plt
import numpy as np
import renom as rm

データの準備

先に述べたとおり,このチュートリアルではsin関数から得られたデータを使用します.最初に,データを生成する分布である population_distribution を以下のように定義します.
そして,学習データとテストデータをそれぞれ分布から生成します.
In [2]:
population_distribution = lambda x:np.sin(x) + np.random.randn(*x.shape)*0.05

train_x = np.random.rand(1024, 1)*np.pi*2
train_y = population_distribution(train_x)

test_x = np.random.rand(128, 1)*np.pi*2
test_y = population_distribution(test_x)

下のグラフは生成されたデータセットの分布を表しています.青いドットは学習セット,オレンジのドットはテストセットを表します.

In [3]:
plt.clf()
plt.grid()
plt.scatter(train_x, train_y, label = "train")
plt.scatter(test_x, test_y, label="test")
plt.title("Population of dataset")
plt.ylabel("y")
plt.xlabel("x")
plt.legend()
plt.show()
../../../_images/notebooks_renom_dl_autodifferentiation_notebook_6_0.png

ニューラルネットワークの定義

ここでは二層ニューラルネットワークを定義します.二層ニューラルネットワークは2つの重みパラメータと2つのバイアスパラメータを持ちます.これらのパラメータについて勾配を計算し,勾配降下法によって更新するために,Variableオブジェクトとしてパラメータを定義します.今回構築するニューラルネットワークのモデルのイメージを以下に示します。

In [4]:
INPUT_SIZE = 1
OUTPUT_SIZE = 1
HIDDEN_SIZE = 5

w1 = rm.Variable(np.random.randn(INPUT_SIZE, HIDDEN_SIZE)*0.01)
b1 = rm.Variable(np.zeros((1, HIDDEN_SIZE)))
w2 = rm.Variable(np.random.randn(HIDDEN_SIZE, OUTPUT_SIZE)*0.01)
b2 = rm.Variable(np.zeros((1, OUTPUT_SIZE)))

optimiser = rm.Sgd(0.01)

def nn_forward(x):
    z = rm.dot(rm.tanh(rm.dot(x, w1) + b1), w2) + b2
    return z

def nn(x, y):
    z = nn_forward(x)
    loss = rm.sum(((z - y)**2)/2)
    return loss

学習ループ

学習ループは以下の様になります.
.grad ではReNomのオブジェクトで定義された式に対して自動微分を行います。 update(optimizer) ではoptimizerで指定された最適化のメソッドに基づいてパラメータを更新します。
In [5]:
N = len(train_x)
batch_size = 32
train_curve = []
for i in range(1, 101):
    perm = np.random.permutation(N)
    total_loss = 0
    for j in range(N//batch_size):
        index = perm[j*batch_size:(j+1)*batch_size]
        train_batch_x = train_x[index]
        train_batch_y = train_y[index]
        loss = nn(train_batch_x, train_batch_y)
        loss.grad().update(optimiser)
        total_loss += loss.as_ndarray()
    train_curve.append(total_loss/(j+1))
    if i%10 == 0:
        print("epoch %02d train_loss:%f"%(i, train_curve[-1]))

plt.clf()
plt.grid()
plt.plot(train_curve)
plt.title("Training curve")
plt.ylabel("train error")
plt.xlabel("epoch")
plt.show()
epoch 10 train_loss:1.506160
epoch 20 train_loss:0.620538
epoch 30 train_loss:0.554743
epoch 40 train_loss:0.605716
epoch 50 train_loss:0.503734
epoch 60 train_loss:0.393485
epoch 70 train_loss:0.247103
epoch 80 train_loss:0.219726
epoch 90 train_loss:0.221648
epoch 100 train_loss:0.181218
../../../_images/notebooks_renom_dl_autodifferentiation_notebook_10_1.png

予測

最後に学習済みモデルを用いて,テストデータに対する予測を実行します.基本的にReNomの関数は計算の履歴(計算グラフ)を残すためにNodeオブジェクトを返します.そのため計算グラフをこれ以上伸ばす必要がなければ as_ndarray を呼び出し,Numpyの行列オブジェクトへ変換することを推奨します.

In [6]:
predicted = nn_forward(test_x).as_ndarray()

テストデータに対する予測と,正解データをグラフに重ねて表示し,その予測結果を確認すると,以下のように正解データを回帰する曲線を見ることが出来ます.

In [7]:
plt.clf()
plt.grid()
plt.scatter(test_x, test_y, label = "true")
plt.scatter(test_x, predicted, label="predicted")
plt.title("Prediction result")
plt.ylabel("y")
plt.xlabel("x")
plt.legend()
plt.show()
../../../_images/notebooks_renom_dl_autodifferentiation_notebook_14_0.png