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点作っておく。
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")
この時のグラフは以下の通りである。
上のデータをフィッティングする際には、
$$
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
を参照。
インプットデータの生成
ここはこれまでの記事と同じである。
多項式をインプットデータとするので、
def make_phi(x0,n,k):
phi = np.array([x0**j for j in range(k)])
return phi.T
という形でインプットデータを生成することにする。ここで$n$はデータの数である。
最大の次数を$3$とし、データの範囲は-2から2とすると
x0 = np.linspace(-2.0, 2.0, n)
k = 4
phi = make_phi(x0,n,k)
となる。
その時の教師データはy0である。
また、学習には使わない「テストデータ」を100点用意し、
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は
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分束ねることを意味している。
同様に、テストデータに対しても、
test_dataset = tf.data.Dataset.from_tensor_slices({"phi":phi_test, "y0":y0_test}).batch(n_test)
としておく。テストデータはランダムに取る必要がなく、誤差評価には全体を使いたいのでn_testをバッチサイズとしている。
次に、iteratorと呼ばれる、TensorFlowにデータを供給する部分を定義しておく。
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)
を定義しておく。この二つは、使うデータをスイッチする役目を持っており、例えば、sess.run(train_init_op)とすることで学習データを対象とすることをTensorFlowに教える。
そして、
next_step= iterator.get_next()
がsess.runで呼び出された時、Datasetに格納されたデータが吐き出される。これは、呼ぶたびに次のバッチを呼び出す。一度使ったデータは使わないようなので、何回も呼んでいるとデータが空になる。空になったとき、学習データをすべて使ったことになる。
グラフの構築
グラフの構築は
https://qiita.com/cometscome_phys/items/95ed1b89acc7829950dd
と同じように、tf.layersを使う。
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であるとして、グラフを作る。
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)
学習
最後に、学習を行う。コードは以下のようになる。
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回繰り返す。
得られたグラフは
となる。