ニューラルネットのオブジェクトに触ってみる

先日初めてTensorflowに触ってみましたが、今回はちょっとニューラルネットのオブジェクトに触ってみました。
最初に色々組もうとハイレベルな挑戦をしてみましたが、全く上手く動いてくれなかったので、基本を大切に･･･の精神で、超超初歩的なことを書いてみます。

触ってみたのは、
tensorflow.layers.dense
という部分。
どうやら、NewralNetworkの”層”に相当するようです。
探してみると、以下のような例文がありました。

```
hidden1 = tf.layers.dense(x_ph, 32, activation=tf.nn.relu)
```

これを組んでいくと、結構複雑なことができそうです。
ただ、今回はシンプルに･･･以下のようなモデルを考えてみます。
入力は2個、出力は1個です。

![math_0329_1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/607345/fdcce540-6cf3-fe43-ca51-a282e808c091.jpeg)

もはやニューラルネットワークとはいえないような気もしますが･･･
これが理解できなきゃダメだろうと思って、まずはここからスタートしてみます。

まずは、ニューラルネットワークの層を作ってみます。

```
newral_out = tf.layers.dense(x_ph, 1)
```

これで、入力のはx_phで定義、出力の数が1個となるようです。
オプションで色々出来るそうですが、今回は単なる線形結合っぽいような感じでいってみます。
x_phは、データ入力用の箱のplaceholderで、今回は以下で定義しておきます。

```
x_ph = tf.placeholder(tf.float32, [None, 2])
```

サイズの[None 2]、Noneは入れる数のサンプル数で任意の数突っ込めるという意味で、2は入力する変数の数なので、ここでは2個(X1,X2)としておきます。

今回は、本当にシンプルに、以下の数式のアウトプットをy1相当としました。

```math
y_1 = w_1 x_1 + w_2 x_2 + w_0
```

任意の(x1,x2)とy1を適当に突っ込んでみて、上手くw_1,w_2,w_0が推定できるか??
という本当にシンプルな問題です。

実はここまでいくと、最初のサンプルのコードがそのまま流用できるため、恐らく凄い簡単に実装できて、以下のような感じでいけそうです。

```
# Minimize the mean squared errors.
loss = tf.reduce_mean(tf.square(newral_out - y_ph))
optimizer = tf.train.GradientDescentOptimizer(0.5)
train = optimizer.minimize(loss)
```

あとは、これを学習させていけばOKかな？

```
# initialize tensorflow session
sess = tf.Session()
sess.run(tf.global_variables_initializer())

for k in range(101):
    # shuffle train_x and train_y
    n = np.random.permutation(len(train_x))
    train_x = train_x[n]
    train_y = train_y[n].reshape([len(train_y), 1])

    # execute train process
    sess.run(train,feed_dict = {
                     x_ph: train_x, # x is input data
                     y_ph: train_y # y is true data
                     })
```

これで一旦動きそうなのですが、折角なので、NewralNetworkのパラメタがどういう挙動をしているか知りたい。
直接読む方法もあるようなのですが、色々難しそう。
問題としてもシンプルなので、折角なので自前の解析機能を考えてみました。

考え方としては、最小二乗法でw0,w1,w2を求める方法。
ニューラルネットワークを仮想の線形近似方程式と見なして、そのパラメタをチェックしてみます。
まずは、入力するデータを

```math
{{x_1}_k},{{x_2}_k}
```

と設定し、Newralを通して推測された情報を

```math
{y^{(new)}}_k = Newral({{x_1}_k},{{x_2}_k})
```
とします。この{y^{(new)}}_kは何らかの決定的な数値が入ります。
ニューラルネットの推定にはBiasがあるかもしれないので、それを加えた形の以下の方程式を考えます。

```math
{y^{(new)}}_k = w_1{x_1}_k + w_2{x_2}_k + w0
```
このとき、w_1,w_2,w_0が未知数、他は数値が決定しているという状態。
ここで、各kにおいて、連立方程式として考えると以下のようになります。

```math
\left(
\begin{matrix}
{y^{(new)}}_1 \\
... \\
{y^{(new)}}_K \\
\end{matrix}
\right)
 = 
\left(
\begin{matrix}
{x_1}_1 & {x_2}_1 & 1 \\
... & ... \\
{x_1}_K & {x_2}_K & 1 \\
\end{matrix}
\right)
\left(
\begin{matrix}
w_1 \\
w_2 \\
w_0 \\
\end{matrix}
\right)
```

ここまでくれば、単純な最小二乗法の問題に帰着します。簡単のため、

```math
A
 = 
\left(
\begin{matrix}
{x_1}_1 & {x_2}_1 & 1 \\
... & ... \\
{x_1}_K & {x_2}_K & 1 \\
\end{matrix}
\right)
```
と、おくと、

```math
\left(
\begin{matrix}
w_1 \\
w_2 \\
w_0 \\
\end{matrix}
\right)
=
\left(
A^T A
\right)^{-1}
A^T
\left(
\begin{matrix}
{y^{(new)}}_1 \\
... \\
{y^{(new)}}_K \\
\end{matrix}
\right)
```

で、パラメタを求めることができそうです。
実際に真値を作るときには、w_1,w_2,w_0を何らかの数値に設定しておけば、その正解にどうやって近づいているか???を観察できれば、ニューラルネットワークの中身が少しだけ分かった気になれそうです(笑)


というわけで、そんなコードの全体を貼り付けておきます。

```python
import numpy as np
#import matplotlib.pyplot as plt
import tensorflow as tf


# deta making???
N = 50
x = np.random.rand(N,2)
# true param???
w = np.array([0.5,0.5]).reshape(2,1)
# sum > 1.0 > 1 : else > 0
#y = np.floor(np.sum(x,axis=1))
y = np.matmul(x,w)
train_x = x
train_y = y


# make placeholder
x_ph = tf.placeholder(tf.float32, [None, 2])
y_ph = tf.placeholder(tf.float32, [None, 1])
# create newral parameter(depth=1,input:2 > output:1)
newral_out = tf.layers.dense(x_ph, 1)


# Minimize the mean squared errors.
loss = tf.reduce_mean(tf.square(newral_out - y_ph))
optimizer = tf.train.GradientDescentOptimizer(0.5)
train = optimizer.minimize(loss)


# initialize tensorflow session
sess = tf.Session()
sess.run(tf.global_variables_initializer())


for k in range(101):

    
    if np.mod(k,10) == 0:
        # get Newral predict data
        y_newral = sess.run( newral_out
                         ,feed_dict = {
                         x_ph: x, # xに入力データを入れている
                         y_ph: y.reshape(len(y),1) # yに正解データを入れている
                         })
        
        # check for newral_parameter(w0,w1,w2)???
        # 
        x_ext = np.hstack([x,np.ones(N).reshape(N,1)])
        A = np.linalg.inv( np.matmul(np.transpose(x_ext),x_ext) )
        A = np.matmul(A,np.transpose(x_ext))
        w_ext = np.matmul(A,y_newral)
        
        # errcheck??? ([newral predict] vs [true value])
        err = y_newral - y
        err = np.matmul(np.transpose(err),err)
        
        
        # check y_newral
        # check LS solution(approaching to NewralNet Parameter)
        # check predict NewralParam
        print('[%d] err:%.5f w1:%.2f w2:%.2f bias:%.2f' % (k,err,w_ext[0],w_ext[1],w_ext[2]))


    # shuffle train_x and train_y
    n = np.random.permutation(len(train_x))
    train_x = train_x[n]
    train_y = train_y[n].reshape([len(train_y), 1])

    # execute train process
    sess.run(train,feed_dict = {
                     x_ph: train_x, # x is input data
                     y_ph: train_y # y is true data
                     })
```

何も書いていませんでしたが、誤差は二乗和で求めることにしちゃいました。
この結果を見てみると･･･???

```
[0] err:1.06784 w1:0.36 w2:0.36 bias:0.00
[10] err:0.02231 w1:0.45 w2:0.45 bias:0.06
[20] err:0.00795 w1:0.47 w2:0.47 bias:0.03
[30] err:0.00283 w1:0.48 w2:0.48 bias:0.02
[40] err:0.00101 w1:0.49 w2:0.49 bias:0.01
[50] err:0.00036 w1:0.49 w2:0.49 bias:0.01
[60] err:0.00013 w1:0.50 w2:0.50 bias:0.00
[70] err:0.00005 w1:0.50 w2:0.50 bias:0.00
[80] err:0.00002 w1:0.50 w2:0.50 bias:0.00
[90] err:0.00001 w1:0.50 w2:0.50 bias:0.00
[100] err:0.00000 w1:0.50 w2:0.50 bias:0.00
```

真値のパラメタは、w1=0.5,w2=0.5,bias(w0)=0なので、30回ぐらい回るといい感じに収束してきている様子が見られました。

ニューラルネットワークというと、なんだか複雑そうな印象でしたが、ここまでシンプルにすることで、単なる線形結合と等価回路っぽくなるんですね。
玄人の方からすると当たり前だろうという結果かもしれませんが、私にとっては一つ大きな収穫となりました。

こんな感じで、次はもうちょっと複雑なことにも挑戦してみようと思います！