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


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

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点作っておく。

```python: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](https://qiita-image-store.s3.amazonaws.com/0/246113/0891e87a-d91b-b281-878c-db334547472b.png)

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


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

```python: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とすると

```python:test.py
x0 = np.linspace(-2.0, 2.0, n)
k = 1
phi = make_phi(x0,n,k)
```
となる。
その時の教師データはy0である。

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

```python: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としている。

```python: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()
```

となる。

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

```python: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であるとして、グラフを作る。

```python: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が隠れ層のユニットの数である。

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

```python: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回で止めている。

```python: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](https://qiita-image-store.s3.amazonaws.com/0/246113/6e4d5d1b-8036-cfca-f50c-73ce87ddb960.png)

### ユニット数10
![Unknown.png](https://qiita-image-store.s3.amazonaws.com/0/246113/d6307e7d-f385-9d5a-81d5-79ae89b59442.png)
### ユニット数20

![Unknown-2.png](https://qiita-image-store.s3.amazonaws.com/0/246113/173c69e3-2828-3952-6313-88a63113c79d.png)

### ユニット数50

![Unknown-3.png](https://qiita-image-store.s3.amazonaws.com/0/246113/c78cd34e-48a8-1ab5-5b52-15a52611f1a4.png)

### ユニット数100

![Unknown-4.png](https://qiita-image-store.s3.amazonaws.com/0/246113/aed594e1-630a-253a-0e83-1e3626bc12f9.png)

### ユニット数200

![Unknown-5.png](https://qiita-image-store.s3.amazonaws.com/0/246113/608cf6cd-a98b-81e1-a920-dab7d0f5c2f3.png)

### ユニット数500

![Unknown-6.png](https://qiita-image-store.s3.amazonaws.com/0/246113/b173805b-c801-8854-ddff-ea6cb878be5d.png)

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


### ユニット数500。データ数3000
Dataset APIに入れたデータは300個だったが、これを10倍すると何か変わるだろうか？

![Unknown-7.png](https://qiita-image-store.s3.amazonaws.com/0/246113/2a5c31a4-b127-72da-73ad-6e54357d7769.png)

とても綺麗になった。

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

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

![Unknown-8.png](https://qiita-image-store.s3.amazonaws.com/0/246113/4e868190-f0c4-ba83-32ee-d9bbbc77d80c.png)

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

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


![final.png](https://qiita-image-store.s3.amazonaws.com/0/246113/f252952c-0be0-50a8-5a12-81aafb799a00.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とする。

```python: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](https://qiita-image-store.s3.amazonaws.com/0/246113/85427b8f-9a20-f9e4-eb3c-29069edd9fac.png)


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

## シグモイドの場合
シグモイドの場合には、

グラフを

```python: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
```

と一箇所変更して、

```python: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](https://qiita-image-store.s3.amazonaws.com/0/246113/5ff47d84-a6f2-6fa0-2ec2-1103be32a4e8.png)

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