TensorFlowの高レベルAPIの使用方法2:Dataset APIを使ってみる

TensorFlowにデータを食べさせるには色々なやり方がある。ここでは、Dataset APIを使った方法
https://www.tensorflow.org/programmers_guide/datasets
を用いてみる。Dataset APIを使うと、ランダムバッチなどを簡単に取り扱うことができる。Dataset APIに慣れると、カスタムEstimatorsに慣れやすい、はず。

これまでずっとやってきている、
https://qiita.com/cometscome_phys/items/95ed1b89acc7829950dd
でやっていたことを、Dataset APIを使ってやってみることにする。

#バージョン
TensorFlow: 1.8
Python: 3.6.5

#再現すべき関数
ここでは、ある関数
$$
y = a_0 x+ a_1 x^2 + b_0 + 3cos(20x)
$$
という関数を考える。ここで、最後のcosはノイズのようなものとして考えており、$a_0$と$a_1$と$b_0$によって得られる二次関数を得ることが目的となる。
データを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
y0 = np.zeros(n)
y0[:] = a0*x0+a1*x0**2 + b0 + 3*np.cos(20*x0)

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

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

![image.png](https://qiita-image-store.s3.amazonaws.com/0/246113/6460be79-1e50-7ded-066b-4799b72af2ee.png)

上のデータをフィッティングする際には、
$$
y = \sum_{k=0}^{k_{\rm max}} a_k x^k + b_0 
$$
という形を考える。ここでは、$x^k$を基底関数として線形回帰をしていることになる。
詳しくは、
JuliaでTensorFlow その4: 線形基底関数を用いた回帰
https://qiita.com/cometscome_phys/items/92dba9f82cd58d877ec5
を参照。

# インプットデータの生成
ここはこれまでの記事と同じである。
多項式をインプットデータとするので、

```python:test.py
def make_phi(x0,n,k):    
    phi = np.array([x0**j for j in range(k)])
    return phi.T
```
という形でインプットデータを生成することにする。ここで$n$はデータの数である。
最大の次数を$3$とし、データの範囲は-2から2とすると

```python:test.py
x0 = np.linspace(-2.0, 2.0, n)
k = 4
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(20*x0_test)
phi_test = make_phi(x0_test,n_test,k)
```
としておく。

# Dataset APIの使用
さて、Dataset APIを使ってみよう。
まず、学習データはランダムバッチを食べさせることにする。このとき、datasetは


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

となる。tf.data.Dataset.from_tensor_slicesはnumpy arrayからDataset フォーマットに変換するものである。また、インプットに名前をつけている。
ここで、shuffle(10)の10はバッファーサイズらしいが、この数字の具体的な意味についてはわからなかった。最後のbatch(batch_size)というのは、データをbatch_size分束ねることを意味している。
同様に、テストデータに対しても、

```python:test.py
test_dataset = tf.data.Dataset.from_tensor_slices({"phi":phi_test, "y0":y0_test}).batch(n_test)
```
としておく。テストデータはランダムに取る必要がなく、誤差評価には全体を使いたいのでn_testをバッチサイズとしている。

次に、iteratorと呼ばれる、TensorFlowにデータを供給する部分を定義しておく。

```python:test.py
iterator = tf.data.Iterator.from_structure(dataset.output_types,
                                           dataset.output_shapes)
```
ここで、学習データにもテストデータにも対応できるように、構造だけを指定している。
さらに、

```python:test.py
train_init_op = iterator.make_initializer(dataset)
test_init_op = iterator.make_initializer(test_dataset)
```

を定義しておく。この二つは、使うデータをスイッチする役目を持っており、例えば、sess.run(train_init_op)とすることで学習データを対象とすることをTensorFlowに教える。
そして、


```python:test.py
next_step= iterator.get_next()
```
がsess.runで呼び出された時、Datasetに格納されたデータが吐き出される。これは、呼ぶたびに次のバッチを呼び出す。一度使ったデータは使わないようなので、何回も呼んでいるとデータが空になる。空になったとき、学習データをすべて使ったことになる。

# グラフの構築
グラフの構築は
https://qiita.com/cometscome_phys/items/95ed1b89acc7829950dd
と同じように、tf.layersを使う。

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

となる。ここで、以前ではplaceholderだった部分が、next_stepに変わっている。これは、上で定義したnext_stepからデータをもらっていることを意味している。最初の定義のときに名前をつけたことにより、ここで名前を指定してインプットデータを取り出すことができる。
feedしていないのでなんとなく気持ち悪いような気もするが、以下のように考えるとまだましかもしれない。
このTensorFlowのグラフは、
minimizeにはlossが必要、lossにはdiffが必要、diffにはyとyoutが必要...という形につながっており、youtはnext_step["y0"]が必要、next_stepはiterator.get_next()が必要、という形でDatasetとつながっている。つまり、食べさせるデータもグラフで入れた、ということになる。

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

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

# 学習
最後に、学習を行う。コードは以下のようになる。

```python:test.py
np = 1000
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")
```
ここで、sess.run(train_init_op)で学習データを呼ぶことを宣言している。その後は、sess.run(minimize)で学習を進めるが、これを呼ぶたびにDataset APIがphiからサイズ20のランダムバッチを作って供給してくれる。while Trueとエラー分岐は、データが空っぽになるまでsess.run(minimize)を繰り返すことを意味しており、エラーがでたらwhileループを抜ける。
次のsess.run(test_init_op)では、テストデータを呼ぶことを宣言している。そして、sess.run(loss)でlossを計算している。
ここまで行った後、次の学習ステップに進む。これを、全部でnp回繰り返す。

得られたグラフは

![image.png](https://qiita-image-store.s3.amazonaws.com/0/246113/f864f99a-12fb-4ad0-04e6-cce140e59c4f.png)

となる。