読者です 読者をやめる 読者になる 読者になる

デジタル・デザイン・ラボラトリーな日々

アラフィフプログラマーが数学と物理を基礎からやり直す

TensorFlowコトハジメ 八百屋で勾配降下法

はじめに

予測モデルを推測する分かりやすいサンプルを見つけたので、この資料を基に理解していく。

機会学習勉強会 (2016.2.27)のつぶやき

  • 実績データからパラメータを推定。パラメータを使って、結果を予測
  • パラメータ特定できない場合、合計金額の誤差が小さくなるような単価を採用する
  • 全ての組み合わせを検証するのではなく、少ない計算で最短距離で行う
  • 学習:予測式、誤差の計算、誤差の最小とする組み合わせ
  • 先程の例を線形回帰(線形重回帰)の式にする
  • 誤差を決める。誤差関数と最適化手法を記述
  • 学習率が大きすぎても失敗する。

勾配降下法とは

誤差の最小値を求める手法。
パラメータの特定できない場合、誤差が小さくなるような値を採用する。
イメージ例
f:id:Yaju3D:20160419021022p:plain

例題1

たかしくんは八百屋へお使いに行きました。
リンゴ1個とミカン3個を買うと190円,リンゴ3個とミカン1個を買うと330円するようです。
リンゴ2個とミカン4個を買うといくらになるでしょうか? f:id:Yaju3D:20160417225841p:plain f:id:Yaju3D:20160417225852p:plain
このくらいの問題なら、暗算で求めることが出来ます。
f:id:Yaju3D:20160417230109p:plain f:id:Yaju3D:20160417230118p:plain
では、次はどうでしょうか?
f:id:Yaju3D:20160417230234p:plain f:id:Yaju3D:20160417230455p:plain
f:id:Yaju3D:20160417230639p:plain
条件が当てはまる最小の値をひたすら繰り返し計算する。
f:id:Yaju3D:20160417230656p:plain f:id:Yaju3D:20160417230833p:plain
少ない計算で最小値を見つけたい。
f:id:Yaju3D:20160417230848p:plain f:id:Yaju3D:20160417231507p:plain

TensorFlowで同じように書いていく

f:id:Yaju3D:20160417231609p:plain f:id:Yaju3D:20160417233530p:plain

TensorFlow のほか高速な配列演算パッケージであるNumPyやプロットしたい場合はmatplotlib.pyplotなど使用するライブラリをロードする。

# 必要なモジュールを読み込む
import numpy as np
import tensorflow as tf

予測式(モデル)を記述

# 1. 予測式(モデル)を記述する
# 入力変数と出力変数のプレースホルダを生成
x = tf.placeholder(tf.float32, shape=(None, 2), name="x")
y_ = tf.placeholder(tf.float32, shape=(None, 1), name="y")
# モデルパラメータ
a = tf.Variable(tf.zeros((2, 1)), name="a")
# モデル式
y = tf.matmul(x, a)
  • 値を入れる"箱"を作成
    入出力 → placeholder,パラメータ → Variable
    (注意)shape = (訓練データの数, 次元数)
    ※"訓練データの数"は None にすると可変長扱い

補足説明
プレースホルダ(placeholder)とは、実際の内容を後から挿入するために仮に確保した場所のこと
tf.placeholderでセットした変数 x や y_ は特定の値ではなくplaceholderと呼ばれ
TensorFlowに計算を実行しろと頼むときに入力する値です。

tf.zerosは、要素が全て「0」である0行列をセット 参照:TensorflowのAPIについて

shape=(行, 列)は、各次元の要素数 (行数, 列数)。
shape=(None, 2)は、2列で後でサイズが決まるような場合に使用する。
f:id:Yaju3D:20160619200824p:plain
shape=(None, 1)は、1列で後でサイズが決まるような場合に使用する。
f:id:Yaju3D:20160619200836p:plain

tf.matmulは行列の掛け算(ここでは定義で実際に使うのは、loss = tf.reduce_mean(tf.square(y_ - y)) )
 { \displaystyle \binom{190}{330} = \binom{1\, \, 3}{3\, \, 1}\binom{a1}{a2}}
行列式を使った連立方程式の解き方
ke!san 連立方程式

  • 予測式(モデル)を記述
    f:id:Yaju3D:20160419002003p:plain

.誤差関数と最適化手法を記述

# 2. 学習に必要な関数を定義する
# 誤差関数(loss)
loss = tf.reduce_mean(tf.square(y_ - y))
# 最適化手段を選ぶ(最急降下法)
train_step = tf.train.GradientDescentOptimizer(0.02).minimize(loss)

mean も average も「平均値」ですが、mean のほうが学術用語で、average のほうが普通語です。
参照:meanとaverageの違いは?

  • 誤差関数を記述
    ふつうの回帰問題 → 平均二乗誤差  {\displaystyle \frac{1}{N}\sum_{i}^{n}{{(y_{i} - \tilde{y}_{i})^{2}}}}

yaju3d.hatenablog.jp

  • 最適化手法を選ぶ
    ・入門 → 最急降下法(勾配降下法) GradientDescent~
     ※どれを選ぶかで学習(最適化)の速さが変わる
    ・引数に適度な"学習率"を指定する
     ※大きすぎると学習失敗(発散)、小さすぎると学習が遅い

訓練データを作成(or読込)

# 3. 実際に学習処理を実行する
# (1) 訓練データを生成する
train_x = np.array([[1., 3.], [3., 1.], [5., 7.]])
train_y = np.array([190., 330., 660.]).reshape(3, 1)
print("x=", train_x)
print("y=", train_y)

f:id:Yaju3D:20160419005112p:plain
※予測式で定義した形状(shape)に合わせること
※実用場面では,外部データを(ファイルやSQLなどから) 読みとって2次元配列に整形する。

# (2) セッションを準備し,変数を初期化
sess = tf.Session()
init = tf.initialize_all_variables()
sess.run(init)

# (3) 最急勾配法でパラメータ更新 (100回更新する)
for i in range(100):
    _, l, a_ = sess.run([train_step, loss, a], feed_dict={x: train_x, y_: train_y})
    if (i + 1) % 10 == 0:
        print("step=%3d, a1=%6.2f, a2=%6.2f, loss=%.2f" % (i + 1, a_[0], a_[1], l))


# (4) 学習結果を出力
est_a = sess.run(a, feed_dict={x: train_x, y_: train_y})
print("Estimated: a1=%6.2f, a2=%6.2f" % (est_a[0], est_a[1]))

sess.run ( [出力, ...], feed_dict={入力リスト} )
sess.run を呼び出すことで、"出力"に指定した データフローグラフが計算される
※学習を回すには、先ほど作成した最適化手段(train_step)を出力値に指定して sess.run を呼び出す。

参考: 実行結果

f:id:Yaju3D:20160419010123p:plain

f:id:Yaju3D:20160419010158p:plain

# 4. 新しいデータに対して予測する
# (1) 新しいデータを用意
new_x = np.array([2., 4.]).reshape(1, 2)

# (2) 学習結果をつかって,予測実施
new_y = sess.run(y, feed_dict={x: new_x})
print(new_y)

予測でも sess.run を用いる
feed_dictには新しい入力値を指定することに留意。(当然ですが...)
f:id:Yaju3D:20160419010502p:plain

最終ソースコード

# coding: utf-8
# 必要なモジュールを読み込む
import numpy as np
import tensorflow as tf

# 1. 予測式(モデル)を記述する
# 入力変数と出力変数のプレースホルダを生成
x = tf.placeholder(tf.float32, shape=(None, 2), name="x")
y_ = tf.placeholder(tf.float32, shape=(None, 1), name="y")
# モデルパラメータ
a = tf.Variable(tf.zeros((2, 1)), name="a")
# モデル式
y = tf.matmul(x, a)

# 2. 学習に必要な関数を定義する
# 誤差関数(loss)
loss = tf.reduce_mean(tf.square(y_ - y))
# 最適化手段を選ぶ(最急降下法)
train_step = tf.train.GradientDescentOptimizer(0.02).minimize(loss)


# 3. 実際に学習処理を実行する
# (1) 訓練データを生成する
train_x = np.array([[1., 3.], [3., 1.], [5., 7.]])
train_y = np.array([190., 330., 660.]).reshape(3, 1)
print("x=", train_x)
print("y=", train_y)

# (2) セッションを準備し,変数を初期化
sess = tf.Session()
init = tf.initialize_all_variables()
sess.run(init)

# (3) 最急勾配法でパラメータ更新 (100回更新する)
for i in range(100):
    _, l, a_ = sess.run([train_step, loss, a], feed_dict={x: train_x, y_: train_y})
    if (i + 1) % 10 == 0:
        print("step=%3d, a1=%6.2f, a2=%6.2f, loss=%.2f" % (i + 1, a_[0], a_[1], l))


# (4) 学習結果を出力
est_a = sess.run(a, feed_dict={x: train_x, y_: train_y})
print("Estimated: a1=%6.2f, a2=%6.2f" % (est_a[0], est_a[1]))

# 4. 新しいデータに対して予測する
# (1) 新しいデータを用意
new_x = np.array([2., 4.]).reshape(1, 2)

# (2) 学習結果をつかって,予測実施
new_y = sess.run(y, feed_dict={x: new_x})
print(new_y)

# 5. 後片付け
# セッションを閉じる
sess.close()

実行結果

リンゴ 98.81 ≒ 99、ミカン 24.90 ≒ 25 → 99 x 2 + 25 x 4 ≒ 297  

x= [[ 1.  3.]
 [ 3.  1.]
 [ 5.  7.]]
y= [[ 190.]
 [ 330.]
 [ 660.]]
step= 10, a1= 70.35, a2= 46.23, loss=2189.06
step= 20, a1= 83.06, a2= 36.70, loss=771.90
step= 30, a1= 90.13, a2= 31.41, loss=334.34
step= 40, a1= 94.05, a2= 28.47, loss=199.24
step= 50, a1= 96.23, a2= 26.84, loss=157.52
step= 60, a1= 97.44, a2= 25.93, loss=144.64
step= 70, a1= 98.12, a2= 25.42, loss=140.67
step= 80, a1= 98.49, a2= 25.14, loss=139.44
step= 90, a1= 98.70, a2= 24.99, loss=139.06
step=100, a1= 98.81, a2= 24.90, loss=138.94
Estimated: a1= 98.81, a2= 24.90
[[ 297.22738647]]