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

アラフィフプログラマーが数学と物理を基礎からやり直す。https://qiita.com/yaju

TensorFlowコトハジメ 八百屋で識別問題

はじめに

前回、八百屋で勾配降下法を実行したので、次の識別問題をやってみる。 yaju3d.hatenablog.jp

前回に引き続きこの資料を基に理解していく。

識別問題とは

入力データから判断して分類させる問題。今回の例では買えるのか買えないのかを判断させる。
識別分類器としてロジスティック回帰を使う。

ロジスティック回帰とは

ロジスティック回帰は、発生確率を予測する手法です。
基本的な考え方は線形回帰分析と同じなのですが、予測結果が 0 から 1 の間を取るように数式やその前提に改良が加えられています。 今回のように購入有無(買える or 買えない)の2値しかとりえない値を従属変数の実績値として用い、説明変数を用いてその発生確率を説明するという構造になっています。

例題2

たかしくんは八百屋へ財布を預かってお使いに行きました。
しかし、たかしくんはお金を 数えられません。

気まぐれおやじ曰く、
リンゴ2個+ミカン3個、リンゴ0個+ミカン16個 なら買えるが、リンゴ3個+ミカン1個、 リンゴ2個+ミカン8個は買えないとのこと。

リンゴ1個+ミカン11個は買えますか? → 識別問題 f:id:Yaju3D:20160421011949p:plain

式で表そうとしてみる…
f:id:Yaju3D:20160421012015p:plain f:id:Yaju3D:20160421012037p:plain

シグモイド曲線

f:id:Yaju3D:20160421012100p:plain
※シグモイド曲線とは、入力した値を0から1の間に収めてくれる関数の1つ
 多くの自然界に存在する事柄は、このようなS字曲線を取る。
 生物の神経細胞、細胞の生存率曲線などなど

予測式(モデル)が作れた!
f:id:Yaju3D:20160421012204p:plain

ロジスティック回帰
f:id:Yaju3D:20160421012219p:plain

予測式(モデル)を記述

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

補足説明
tf.onesは、要素が全て「1」である1行列をセット 参照:TensorflowのAPIについて

今回の予測式に合わせてモデルとパラメーターを修正
f:id:Yaju3D:20160421012251p:plain

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

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

・誤差関数を変更
 識別(分類)問題→クロスエントロピー
f:id:Yaju3D:20160421012315p:plain

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

# 3. 実際に学習処理を実行する
# (1) 訓練データを生成する
train_x = np.array([[2., 3.], [0., 16.], [3., 1.], [2., 8.]])
train_y = np.array([1., 1., 0., 0.]).reshape(4, 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(1000):
    _, l, a_ = sess.run([train_step, loss, a], feed_dict={x: train_x, y_: train_y})
    if (i + 1) % 100 == 0:
        print("step=%3d, a1=%6.2f, a2=%6.2f, loss=%.2f" % (i + 1, a_[0], a_[1], l))
        
# (4) 学習結果を出力
est_a, est_b = sess.run([a, b], feed_dict={x: train_x, y_: train_y})
print("Estimated: a1=%6.2f, a2=%6.2f, b=%6.2f" % (est_a[0], est_a[1], est_b))   

線形回帰とほぼ同じ
【変更箇所】

  • パラメータbの出力を追加
  • 更新回数を100回→1000回に
    ※更新回数は、対象問題やデータ、初期値、モデルなどでまちまちです。

学習結果

f:id:Yaju3D:20160421012400p:plain

予測結果

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

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

0.95848435 ≒ 0.96 f:id:Yaju3D:20160424232047p:plain

最終ソースコード

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

# TensorFlow でロジスティック回帰する

# 1. 学習したいモデルを記述する
# 入力変数と出力変数のプレースホルダを生成
x = tf.placeholder(tf.float32, shape=(None, 2), name="x")
y_ = tf.placeholder(tf.float32, shape=(None, 1), name="y")
# モデルパラメータ
a = tf.Variable(-10 * tf.ones((2, 1)), name="a")
b = tf.Variable(200., name="b")
# モデル式
u = tf.matmul(x, a) + b
y = tf.sigmoid(u)

# 2. 学習やテストに必要な関数を定義する
# 誤差関数(loss)
loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(u, y_))
# 最適化手段(最急降下法)
train_step = tf.train.GradientDescentOptimizer(0.1).minimize(loss)


# 3. 実際に学習処理を実行する
# (1) 訓練データを生成する
train_x = np.array([[2., 3.], [0., 16.], [3., 1.], [2., 8.]])
train_y = np.array([1., 1., 0., 0.]).reshape(4, 1)
print("x=", train_x)
print("y=", train_y)

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

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

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

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

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

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

実行結果

0.95848435 ≒ 0.96 リンゴ 59.68 ≒ 60、ミカン 11.40 ≒ 11 → σ(185 - (60 x 1 + 11 x 11)) = 0.96(買えそう)

x= [[  2.   3.]
 [  0.  16.]
 [  3.   1.]
 [  2.   8.]]
y= [[ 1.]
 [ 1.]
 [ 0.]
 [ 0.]]
step=100, a1=-22.50, a2=-12.28, b=196.26, loss=42.75
step=200, a1=-35.00, a2=-12.06, b=192.68, loss=25.84
step=300, a1=-47.36, a2=-11.78, b=189.14, loss=9.24
step=400, a1=-55.13, a2=-11.51, b=186.75, loss=2.54
step=500, a1=-58.92, a2=-11.29, b=185.58, loss=0.02
step=600, a1=-59.26, a2=-11.23, b=185.47, loss=0.01
step=700, a1=-59.43, a2=-11.19, b=185.43, loss=0.00
step=800, a1=-59.53, a2=-11.17, b=185.39, loss=0.00
step=900, a1=-59.62, a2=-11.15, b=185.37, loss=0.00
step=1000, a1=-59.68, a2=-11.14, b=185.35, loss=0.00
Estimated: a1=-59.68, a2=-11.14, b=185.35
[[ 0.95848435]]