webエンジニアの日常

RubyやPython, JSなど、IT関連の記事を書いています

初めてのTensorFlow入門~最小二乗法による多項式近似~

こんにちは、エンジニアのさもです。

今回からいよいよTensorFlowで機械学習を実装していきます。

第1回目は、最小二乗法を使って、予測を行います。

目次

内容はこちらの書籍に沿っていきます

TensorFlowで学ぶディープラーニング入門 ~畳み込みニューラルネットワーク徹底解説~

TensorFlowで学ぶディープラーニング入門 ~畳み込みニューラルネットワーク徹底解説~

書籍と同じ内容を書いても意味がないので、もう少し詳しく書けそうだなというところを追加していきます。

あと、「この記事で解説があるから書籍は買わなくていいや」となってしまわないように、実装以外で解説されている部分は省く、または自分で勉強した内容で置き換えます。

実験対象

今回は、1月から12月までの平均気温の変化を4次関数

 f(x) = a_{0} + a_{1}x + a_{2}x^{2} + a_{3}x^{3} + a_{4}x^{4}

で近似します。

近似するデータは以下のものを用意しておきます

import numpy as np
import matplotlib.pyplot as plt

train_t = np.array([5.2, 5.7, 8.6, 14.9, 18.2, 20.4, 25.5, 26.4, 22.8, 17.5, 11.1, 6.6])

1月の平均気温が5.2度で、12月が6.6度になっています

こちらをグラフに表示するとこんな感じです

# jupyter notebookでグラフを表示するために必要
%matplotlib inline

# train_tをプロット
fig = plt.figure()
# 複数グラフを表示する場合に位置を指定する
# 1枚だけ表示する場合は(1,1,1)で固定
subplot = fig.add_subplot(1,1,1)
# 表示するx軸の範囲
subplot.set_xlim(1,12)
# scatterメソッドで散布図を準備
# 第一引数がx軸の配列で、第二引数がそのペアとなるy軸の配列
# rangeは第二引数の一つ手前までの範囲なので、第二引数は13にしなければならない
subplot.scatter(range(1,13), train_t)
subplot.plot()

f:id:s-uotani-zetakansu:20170920125120p:plain

スポンサーリンク

最小二乗法とは

最小二乗法とは、予測した値と、正解の値とがどれくらい違うかを、差の2乗(2乗誤差)で定義し、この値を最小になるようにパラメータを修正しようという方法です。

例えば、上記の今回のデータを見るとなんとなく、ひっくり返った二次関数で近似できそうです

 g(x) = -\frac{1}{2}(x-8)^{2} + 25

この関数で近似したとして、実際に2乗誤差を調べてみましょう

# g(x)を使った予測データの準備
x = np.array(range(1,13))
y = -0.5*(x - 8) ** 2 + 25
y #=> array([  0.5,   7. ,  12.5,  17. ,  20.5,  23. ,  24.5,  25. ,  24.5, 23. ,  20.5,  17. ])

# 差の計算
err = np.sum((train_t - y)**2)
err #=> 288.07

2乗誤差は288.07と計算されました。最小2乗法では、この値を最小にすることを目標とします。

ちなみに、g(x)も一緒にプロットするとこんな感じです

%matplotlib inline
fig = plt.figure()
subplot = fig.add_subplot(1,1,1)
subplot.set_xlim(1,12)
subplot.scatter(range(1,13), train_t)
linex = np.linspace(1,12,100)
liney = -0.5*(linex - 8) ** 2 + 25
subplot.plot(linex, liney)

6~8行目が編集した箇所です

f:id:s-uotani-zetakansu:20170920132706p:plain

後半が結構ずれていますね。

二次関数でもっとよりよく近似したい場合は、g(x)内の0.5や8, 25などのパラメータを変えていくことになると思います。

機械学習では、このパラメータの決定をプログラムでやってもらおうというわけです。

では、実際にそのプログラムを組んでいきたいと思います。

実装と解説

必要なライブラリのインポート

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt

変数の定義

3次式だとどうなんだろうと思ったときにすぐ変えれるように、何次式で近似するかdimという変数で管理しておきます。

dim = 4
x = tf.placeholder(tf.float32, [None, dim + 1])
w = tf.Variable(tf.zeros([dim+1,1]))
y = tf.matmul(x,w)
t = tf.placeholder(tf.float32, [None, 1])

xは入力値で、各月を0乗から4乗した値が配列形式で入力されます。

学習時に入力される値は、placeholderで定義しておきます。

wは4次式の係数で、5個の要素からなる配列です。

学習時にプログラムから更新される変数はVariableで定義しておきます。

y は入力と係数の内積を取った変数で、4次式そのものになります。

tは正解の変数です。

TensorFlowではこんな感じで、計算ルールを記述していき、学習時にはじめてplaceholderに値が入り、計算が実行されます。

メソッドを定義する感覚に似ている気がします。

誤差の定義

loss = tf.reduce_sum(tf.square(y - t))

2乗誤差を定義します。

yが4次式での計算結果、tが正解値なので、差を取って、tf.squareで2乗し、reduce_sumで足し合わせています。

学習方法の定義

train_step = tf.train.AdamOptimizer().minimize(loss)

2乗誤差をどのように係数にフィードバックするか定義しています。

上記のコードは、2乗誤差(loss)を最小化する(minimize)ように、tf.train.AdamOptimizer()という最適化アルゴリズムで学習(フィードバックして係数を更新)するという意味です。

セッションの初期化

sess = tf.Session()
sess.run(tf.global_variables_initializer())

セッションが何というのは上手く説明できないですが、学習や予測の実行単位みたいなものです。

sess1,sess2みたいに二つ用意して、それぞれ学習させると、それぞれが持つ係数などは別の値になっています。RPGでいうセーブデータみたいな感じ?

tf.global_variables_initializerで、変数などを初期化しています。

入力データの準備

train_t = np.array([5.2, 5.7, 8.6, 14.9, 18.2, 20.4,25.5, 26.4, 22.8, 17.5, 11.1, 6.6])
train_t = train_t.reshape([12,1])
train_x = np.zeros([12, dim+1])
for row, month in enumerate(range(1, 13)):
    for col, n in enumerate(range(0, dim+1)):
        train_x[row][col] = month**n

実際に学習で用いるデータを準備します。

train_tが正解値で、変数の準備で定義しておいた変数tに学習実行時に代入されます。同様に、train_x は変数xに代入されます。

train_xの中身は次のようになっています

f:id:s-uotani-zetakansu:20170920174739p:plain

(注)ただし、train_xをそのまま表示しても見にくいので、次のコードで整数になおして表示しています

train_x = np.array(list(map(int, train_x.reshape(12 * (dim+1))))).reshape(12, (dim+1))

学習の実行

i = 0
for _ in range(100000):
    i += 1
    sess.run(train_step, feed_dict={x: train_x, t: train_t})
    if i % 10000 == 0:
        loss_val = sess.run(loss, feed_dict={x: train_x, t: train_t})
        print('Step: %d, Loss: %f' % (i, loss_val))

sess.runで第一引数に渡した変数を1回だけ評価(実行)します。また、第2引数のfeed_dictに、変数の定義でplaceholderとして定義しておいた変数に値を入れます。

第一引数がtrain_stepのときは学習を1度実行し、lossを渡したときは、2乗誤差を計算して返すといった具合です。

学習は全部で10万回行います。そのうち1万回ごとに2乗誤差を計算して標準出力に表示しています。

Step: 10000, Loss: 31.014380
Step: 20000, Loss: 29.290693
Step: 30000, Loss: 28.022751
Step: 40000, Loss: 27.663746
Step: 50000, Loss: 25.792316
Step: 60000, Loss: 24.766474
Step: 70000, Loss: 23.838539
Step: 80000, Loss: 22.974419
Step: 90000, Loss: 22.176279
Step: 100000, Loss: 22.416885

こんな感じで実行過程が表示されます

学習結果を確認する

w_val = sess.run(w)
w_val

で学習済みの係数を見ることができます。以下が表示内容です。

array([[ 3.76468992],
       [-1.58954322],
       [ 1.78510237],
       [-0.20117806],
       [ 0.00539352]], dtype=float32)

学習結果を使って4次式を表示する

def predict(x):
    result = 0.0
    for n in range(0,dim+1):
        result += w_val[n][0] * x ** n
    return result

上記のメソッドで4次式をあらわしています。はじめの方に書いた f(x)に相当します。

次に、実験データの散布図に、この4次式を重ねて表示します。

%matplotlib inline
fig = plt.figure()
subplot=fig.add_subplot(1,1,1)
subplot.set_xlim(1,12)
subplot.scatter(range(1,13), train_t)
linex = np.linspace(1,12,100)
liney = predict(linex)
subplot.plot(linex, liney)

7行目で4次式を使うように指定しています。

以下が実行結果です。

f:id:s-uotani-zetakansu:20170920180852p:plain

私が適当に当てはめた2次式よりずっといい近似になっていますね!

2乗誤差は22.416885と小さくなりました。

学習部分をもう少し多く(50万回ぐらい)やると2乗誤差は15程度まで小さくなりました。

その他の次数での近似

最後に、せっかく次数をdimという変数で定義したので、ここを変えて実験したいと思います。

  • dim=3, loss: 32.606544

f:id:s-uotani-zetakansu:20170920181345p:plain

  • dim=2, loss: 90.494446

f:id:s-uotani-zetakansu:20170920181654p:plain

  • dim=5, loss: 12.882786

f:id:s-uotani-zetakansu:20170920182049p:plain

まとめ

処理を大きく分けると、変数と損失関数の準備、データの準備、学習の実行、学習結果を見る・使うって感じでしょうか。

はじめてTensorFlowを触ってみましたが、モデルと実装が近い形で書けるので、「これは楽しい」って思いました。

グラフを書くのも簡単ですね!

次回は分類を実装していきます。

読者登録をしていただけると、ブログを続ける励みになりますので、よろしくお願いします。