LoginSignup
27
31

More than 5 years have passed since last update.

ニューラルネットで任意の関数が近似できる?ならやってみよう:TensorFlowで。

Last updated at Posted at 2018-08-02

機械学習でよく言われる、「隠れ層一層のニューラルネットワークは任意の関数を近似できる」ということを実際に確かめてみよう。隠れ層を一層として、ユニット数を増やしてみる。

TensorFlowを用いる。
基本的なことは

TensorFlowの高レベルAPIの使用方法2:Dataset APIを使ってみる
https://qiita.com/cometscome_phys/items/a95a91f9822353303dd8

を参照のこと。

バージョン

TensorFlow: 1.9.0
Python: 3.6.4

再現すべき関数

ここでは、ある関数
$$
y = a_0 x+ a_1 x^2 + b_0 + 3cos(\alpha x)
$$
という関数を考える。これまでは、最後のcosはノイズのようなものとして考えており、$a_0$と$a_1$と$b_0$によって得られる二次関数を得ることが目的としていた。今回は、この関数自体をフィッティングすることを目指す。$\alpha$を大きくすると振動が増え、より一致が難しくなる。
$\alpha = 3$としてみよう。
データを300点作っておく。

test.py
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

n = 300
x0 = np.linspace(-2.0, 2.0, n)
a0 = 3.0
a1 = 2.0
b0 = 1.0
kk = 3
y0 = np.zeros(n)
y0[:] = a0*x0+a1*x0**2 + b0 + 3*np.cos(kk*x0)

plt.plot(x0,y0 )
plt.show()
plt.savefig("graph.png")

この時のグラフは以下の通りである。

Unknown-6.png

これまではデータのフィッティングには多項式による展開をしていたが、今回は、$x$座標そのものを用いる。
つまり、インプットデータは$x$座標、教師データは$y$座標である。

インプットデータの生成

ここはこれまでの記事とほとんど同じである。
$x$そのものをインプットデータとするので、

test.py
def make_phi(x0,n,k):    
    phi = np.array([x0])
#    phi = np.array([x0**j for j in range(k)])
    return phi.T

という形でインプットデータを生成することにする。ここで$n$はデータの数である。前回のコードを使いまわしているので$k$があるが、この記事では使わない。
データの範囲は-2から2とすると

test.py
x0 = np.linspace(-2.0, 2.0, n)
k = 1
phi = make_phi(x0,n,k)

となる。
その時の教師データはy0である。

また、学習には使わない「テストデータ」を100点用意し、

test.py
n_test = 100
x0_test = np.linspace(-2.0, 2.0, n_test)
y0_test = np.zeros(n_test)
y0_test[:] = a0*x0_test+a1*x0_test**2 + b0 + 3*np.cos(kk*x0_test)
phi_test = make_phi(x0_test,n_test,k)

としておく。

Dataset APIの使用

ここは前回の記事と同じなので、コードだけ再掲する。
今、バッチサイズを20としている。

test.py
batch_size = 20
dataset = tf.data.Dataset.from_tensor_slices({"phi":phi, "y0":y0}).shuffle(10).batch(batch_size)

test_dataset = tf.data.Dataset.from_tensor_slices({"phi":phi_test, "y0":y0_test}).batch(n_test)
iterator = tf.data.Iterator.from_structure(dataset.output_types,
                                           dataset.output_shapes)
train_init_op = iterator.make_initializer(dataset)
test_init_op = iterator.make_initializer(test_dataset)
next_step= iterator.get_next()

となる。

グラフの構築

グラフの構築も前回の記事と同様。

test.py
def build_graph_layers_class(d_input,d_middle,d_type):
    x = next_step["phi"]
    yout= next_step["y0"]
    hidden1 = tf.layers.Dense(units=d_middle,activation=tf.nn.relu)
    x1 = hidden1(x)
    outlayer = tf.layers.Dense(units=1,activation=None)
    y = outlayer(x1)

    diff = tf.subtract(y,yout)
    loss = tf.nn.l2_loss(diff)
    minimize = tf.train.AdamOptimizer().minimize(loss)
    return x,y,yout,diff,loss,minimize,hidden1,outlayer

隠れ層1層としてそのユニットを10であるとして、グラフを作る。

test.py
d_type = tf.float32
d_input = 1
d_middle = 10
x,y,yout,diff,loss,minimize,hidden1,outlayer= build_graph_layers_class(d_input,d_middle,d_type)

で設定する。d_middleが隠れ層のユニットの数である。

学習

学習についても前回と同じであり、

test.py
np = 5000
with tf.Session() as sess:
    tf.global_variables_initializer().run()
    for i in range(np):
        sess.run(train_init_op)
        while True:
            try:
                sess.run(minimize)
                #print(sess.run(next_step))
                losstrain = sess.run(loss)/batch_size
                #print(losstrain)


            except tf.errors.OutOfRangeError:
                break

        sess.run(test_init_op)
        losstest = sess.run(loss)/n_test
        if i % 100 is 0:
            print("i",i,"loss",losstest)
    W = sess.run(hidden1.weights)
    print(W)
    a = sess.run(outlayer.weights)
    print(a)
    sess.run(test_init_op)
    yt = sess.run(y)

plt.plot(x0_test,y0_test )
plt.plot(x0_test,yt )
plt.show()
plt.savefig("graph2.png")

となる。

結果

それでは、ユニット数を変化させたときにどうなるか見てみよう。学習はどれも5000回で止めている。

test.py
d_type = tf.float32
d_input = 1
d_middle = 10
x,y,yout,diff,loss,minimize,hidden1,outlayer= build_graph_layers_class(d_input,d_middle,d_type)

のd_middleを変えて再実行すればよい。

まず、$\alpha = 3$とする。

ユニット数4

Unknown-1.png

ユニット数10

Unknown.png

ユニット数20

Unknown-2.png

ユニット数50

Unknown-3.png

ユニット数100

Unknown-4.png

ユニット数200

Unknown-5.png

ユニット数500

Unknown-6.png

だんだん良くなっている気はするが、思ったよりも上がっていないような気もする。

ユニット数500。データ数3000

Dataset APIに入れたデータは300個だったが、これを10倍すると何か変わるだろうか?

Unknown-7.png

とても綺麗になった。

ユニット数50。データ数3000

データ数は多いままユニット数を1/10にしてみた。

Unknown-8.png

あれ、ユニット数500とあまり変わらなく、良い感じ。

ユニット数10。データ数3000

final.png

さすがにこれは少なかったらしい。

結論

データ数重要。

(18/08/03)追記

activation関数を変化させたときにどうなるのか、というコメントがあったので、試しに変えてみた。
先ほどまで使っていたactivation関数はReLUと呼ばれるもので、
$$
f(x) = max(0,x)
$$
というものであった。これは、xが0より小さいときには0を、xが0より大きいときはxそのものを返す関数である。
activation関数には他にもあり、例えばシグモイド(sigmoid)関数は
$$
f(x) = \frac{1}{1+\exp(-x)}
$$
である。$x$が0より小さいときはほとんどゼロで、$x=0$の時には1/2、$x$が大きくなっていくと1になる関数である。
TensorFlowでは、activation=tf.nn.reluの代わりにactivation=tf.sigmoidを使えば、シグモイド関数に変更できる。

activation関数の影響がわかりやすいように、ユニット数を2とする。

test.py
d_type = tf.float32
d_input = 1
d_middle = 2
x,y,yout,diff,loss,minimize,hidden1,outlayer= build_graph_layers_class(d_input,d_middle,d_type)

データ点は3000点としておく。

ReLUの場合

これは先ほどと同じactivation関数である。
結果は、

tes1.png

である。今、隠れ層が1層なので、ユニット数が2個ということは二つの原点の異なるReLU関数の重ね合わせということである。
実際、カクカクっとしていて、そうなっていることがわかる。

シグモイドの場合

シグモイドの場合には、

グラフを

test.py
def build_graph_layers_class(d_input,d_middle,d_type):
    x = next_step["phi"]
    yout= next_step["y0"]
    hidden1 = tf.layers.Dense(units=d_middle,activation=tf.sigmoid)
    x1 = hidden1(x)
    outlayer = tf.layers.Dense(units=1,activation=None)
    y = outlayer(x1)

    diff = tf.subtract(y,yout)
    loss = tf.nn.l2_loss(diff)
    minimize = tf.train.AdamOptimizer().minimize(loss)
    return x,y,yout,diff,loss,minimize,hidden1,outlayer

と一箇所変更して、

test.py
d_type = tf.float32
d_input = 1
d_middle = 2
x,y,yout,diff,loss,minimize,hidden1,outlayer= build_graph_layers_class(d_input,d_middle,d_type)

を再実行する。その結果、

Unknown-1.png

となる。
sigmoid関数はゆっくり変化する関数なので、こちらのがふんわりしている。

27
31
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
31