ニューラルネットワークで"Z = x*x + y*y"を学習させてみた

いきなりですが、関数 "Z=x*x + y*y" の様な曲面をニューラルネットワークで学習させてみます。ディープラーニングの話です。

目的:多層ニューラルネットワークが色々な関数を近似できるとは聞いていたが、それが本当かどうか確かめる。多層ニューラルネットワークを学習させた関数のグラフが関数"Z=x*x + y*y"と同様な曲面になるか確かめる。

注意:細かい話ですが、実際の関数は"Z=k(x*x + y*y) + c"(kとcは定数)です。xとyの範囲は[-1,1]です。

・モデルの構築
ニューラルネットワークのモデルは以下の条件1、2を満たす様に構築しました。

(条件1)「3層以上であること(全て全結合層)」
(条件2)「1層のニューロンの数が2以上であること」

(条件1の理由):3層以上でないと関数の非線形性を表現しきれないから
(条件2の理由):独立変数x,yを一つの変数で現すことは不可能、よって1層のニューロンの数は入力の独立変数の数より多くなければならないから

f:id:keimina:20180324025738p:plain

構築したネットワークを連立方程式で書くと以下の様になります。
今回は活性化関数としてtanh(ハイパボリックタンジェント)を用いることとしました。

# layer 1
x11 = tanh(a*x + b*y + c)
x21 = tanh(d*x + e*y + f)

# layer 2
x13 = tanh(g*x11 + h*x21 + i)
x23 = tanh(j*x11 + k*x21 + l)

# layer 3(output)
z= m*x13 + n*x23 + o

範囲「-1<x<1, -1<y<1」において、目的(Z=x*x + y*y)の関数と出力が同じになる様にパラメータを学習させます。パラメータの最適化には勾配下降法を用いています。

・実装
参考として、TensorFlow のコードを載せておきます。

import numpy as np
import tensorflow as tf
import random
from itertools import product
import time
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import math

def Mokuteki(x1, x2):
    return x1**2 + x2**2

k1 = math.sqrt(15)

I = tf.placeholder(tf.float32, (1, 2))
W1 = tf.Variable(k1*(1-2*tf.random_uniform((2, 2))))
B1 = tf.Variable(k1*(1-2*tf.random_uniform((1, 2))))

I2 = tf.nn.tanh(tf.matmul(I, W1) + B1)

W2 = tf.Variable(k1*(1-2*tf.random_uniform((2, 2))))
B2 = tf.Variable(k1*(1-2*tf.random_uniform((1, 2))))
I3 = tf.nn.tanh(tf.matmul(I2, W2) +  B2)

W3 = tf.Variable(k1*(1-2*tf.random_uniform((2, 1))))
B3 = tf.Variable(k1*(1-2*tf.random_uniform((1,))))
Y = tf.matmul(I3, W3) + B3

T = tf.placeholder(tf.float32)
L = tf.square(T - Y)
train_step = tf.train.GradientDescentOptimizer(0.03).minimize(L)

#training data
N = 16
x1_range = np.linspace(-1,1,N, dtype=np.float32)
x2_range = np.linspace(-1,1,N, dtype=np.float32)
train_x1, train_x2 = np.meshgrid(x1_range, x2_range)
train_y = np.array([Mokuteki(val1, val2) for val1, val2 in zip(train_x1.flatten(), train_x2.flatten())], dtype=float).reshape(N,N)
train_y -= np.mean(train_y)
train_y /= np.std(train_y)

init = tf.global_variables_initializer()

sess = tf.Session()
sess.run(init)

start_time = time.time()
for n_epoch in range(100):
    sample_all = random.sample(list(zip(train_x1.flatten(), train_x2.flatten(), train_y.flatten())), N*N)
    for x1v, x2v, tv in sample_all:
        sess.run(train_step, feed_dict={I:[[x1v,x2v]], T:tv})

    total_err = 0.0
    for x1v, x2v, tv in sample_all:
        total_err +=  sess.run(L, feed_dict={I:[[x1v, x2v]], T:tv})
    print("%d epoch:"%n_epoch, "loss:%f"%(total_err/N/N))

print("%d sec"%(time.time()-start_time))
print("finished")

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(train_x1, train_x2, np.array([sess.run(Y, feed_dict={I:[[x1v, x2v]]}) for x1v, x2v in zip(train_x1.flatten(), train_x2.flatten())], dtype=float).reshape(N,N))
plt.show()

sess.close()


やっていることは、まず、範囲「-1<x<1, -1<y<1」における Z = x*x + x*x の集合を訓練データとして生成し、あらかじめシャッフルしておく。ニューラルネットワークの出力を表す関数と、訓練データの2乗誤差をとる新たな関数Lを定義。勾配下降法により、その関数Lを最小にする様にパラメータを調整。
注意:表示される"loss:"は2乗誤差の値の平均値が記載されます。

最後に、6000エポックまでの学習中の関数のグラフの変化を動画にしました。
関数のグラフが、平衡状態から崩れて別の形になったりと興味深いです。

youtu.be

パラメータの初期値と学習係数を変えて色々と試してみましたが、ソースコードにあるもので落ち着いています。一番困ったのが、局所解で平衡状態になって抜け出せなくなってしまう場合です。局所解におちいらない様な設定値の作り方やアルゴリズムを知っていたら教えてください。

以上です。