はじめに
先日の記事の続きです。今回はTensorFlowで線形回帰をする際の流れや利用できる関数などを紹介します。また今回から損失関数や最適化関数などを利用するので、そちらの紹介もします。
TensorFlowで線形回帰をする際の要素
TensorFlowで線形回帰をする際には、前回記事の要素に加えて損失関数、最適化関数(optimizer)が必要になります。実際のコードを見る前に、新しく登場したこれらの要素について、簡単に紹介します。
損失関数
機械学習の中でも教師あり学習では、実際の数値と予測した数値との誤差を小さくしようとすることで、より正確に予測ができることを目指します。そのような「正解ラベルの数値と予測した数値との誤差」を損失関数として表します。
最適化(optimizer)
損失関数によって正解値と予測値との間でどれほど差分があるかわかりましたが、次にその差分を埋めるためにモデルを修正する必要があります。そのような関数を最適化(optimizer)関数と言います。
実装
今回はScikit-Learnで提供されているデータセットの中から、boston house-prices datasetを使います。
実際のコード
最初に全体像を紹介します。
# 線形回帰 ## 必要なライブラリのインポート import tensorflow as tf import numpy as np from sklearn.datasets import load_boston from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler ## ボストン住宅街のデータセット boston = load_boston() # 特徴量とラベルにデータを分ける ## boston.data: 説明変数(13のラベル) ## boston.target: 目的関数(住宅価格の中央値) data_x = np.array(boston.data) data_y = np.array(boston.target) ## 正則化 ss = StandardScaler() ss.fit(data_x) data_x = ss.transform(data_x) ## 訓練データとテストデータに分ける train_x, test_x , train_y, test_y = train_test_split(data_x, data_y, test_size=0.2, random_state=42) ## データの変形 train_y = train_y.reshape(404, 1) test_y = test_y.reshape(102, 1) ## ハイパーパラメータ learning_rate = 0.01 num_epochs = 1000 # 計算グラフの作成 X = tf.placeholder(tf.float32, [None, 13]) Y = tf.placeholder(tf.float32, [None, 1]) W = tf.Variable(tf.zeros([13, 1])) b = tf.Variable(0.0) ## 線形関数 y = tf.add(tf.matmul(X, W), b) ## 損失関数(二乗和誤差) loss = tf.losses.mean_squared_error(labels = y, predictions = Y) ## 最適化 optimizer = tf.train.GradientDescentOptimizer(learning_rate) train_step = optimizer.minimize(loss) # 変数の初期化 init = tf.global_variables_initializer() # 計算処理 with tf.Session() as sess: sess.run(init) for epoch in range(num_epochs): sess.run(train_step, feed_dict={X: train_x, Y: train_y}) if epoch != 0 and epoch % 100 == 0: train_loss = sess.run(loss, feed_dict={X: train_x, Y: train_y}) print("train_loss: ", train_loss) # 評価 pred_y = sess.run(y, feed_dict={X: test_x}) for i in range(9): print("実際の不動産価格: ", test_y[i,0], " 予測した不動産価格: ", pred_y[i,0]) # 計算グラフの初期化 tf.reset_default_graph()
0. ライブラリのインポート
今回はTensorFlow以外にも複数のライブラリをインポートします。
# 必要なライブラリのインポート import tensorflow as tf import numpy as np from sklearn.datasets import load_boston from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler
1. データセットの読み込み
データセットを読み込んでからは、実際の計算処理をする上で効率よく処理できるように前処理をする必要があります。
- データの分割
まずは与えられたデータセットを説明変数と目的変数とに分けます。今回のデータセットはdata
とtarget
と言うキーを持っており、それぞれ説明変数と目的変数とに対応しています。
- 数値の変形(正規化・正則化)
機械学習では、モデルの予測性能を向上させるため、データセットの正則化・正規化を行うことがあります。ここでも正則化を行っていますが、正則化を行わないと損失関数の値が発散してしまい、NaN(Not A Number)を表示してしまいます。
- 訓練データとテストデータ
教師あり学習では、与えられたデータセットを、訓練用のデータセットと汎化性能を見るためのテストデータとに分けて学習を行います。ここではScikit-Learnのtrain_test_split
メソッドを利用してデータセットを分けます。
## ボストン住宅街のデータセット boston = load_boston() # 特徴量とラベルにデータを分ける ## boston.data: 説明変数(13のラベル) ## boston.target: 目的関数(住宅価格の中央値) data_x = np.array(boston.data) data_y = np.array(boston.target) ## 正則化 ss = StandardScaler() ss.fit(data_x) data_x = ss.transform(data_x) ## 訓練データとテストデータに分ける train_x, test_x , train_y, test_y = train_test_split(data_x, data_y, test_size = 0.2, random_state = 42) ## データの変形 train_y = train_y.reshape(404, 1) test_y = test_y.reshape(102, 1)
2. データフローグラフの構築
TensorFlowにおける損失関数
TensorFlowでは損失関数を設定する際には、tf、tf.mathモジュールに加え、tf.nn、tf.lossesモジュールなどを利用できます。Deep Learningでよく使われるものは組み込み関数として提供されていますが、組み合わせて関数を作成することも可能です。
関数名 | 用途 |
---|---|
tf.reduce_mean | 別テンソルにまたがる各要素の平均を計算する |
tf.math.reduce_mean | 別テンソルにまたがる各要素の平均を計算する |
tf.reduce_sum | 別テンソルにまたがる各要素の和を計算する |
tf.math.reduce_sum | 別テンソルにまたがる各要素の和を計算する |
tf.nn.softmax_cross_entropy_with_logits | logitsで与えられた入力に対してソフトマックス交差エントロピーを計算する |
tf.nn.softmax_cross_entropy_with_logits_v2 | logits又はlabelsで与えられた入力に対してソフトマックス交差エントロピーを計算する |
tf.nn.sigmoid_cross_entropy_with_logits | logitsで与えられた入力に対してシグモイド交差エントロピーを計算する |
tf.losses.mean_squared_error | 正解値と予測値から二乗和誤差を計算する |
tf.losses.sigmoid_cross_entropy | マルチクラスの正解値とlogitsからシグモイド交差エントロピーを計算する |
tf.losses.softmax_cross_entropy | one-hot変換された正解値とlogitsからソフトマックス交差エントロピーを計算する |
今回は二乗和誤差を利用します。二乗和誤差を表現する方法は複数あります。
# 二乗和誤差 ## labels: 正解値 ## predictions: 予測値 tf.losses.mean_squared_error( labels, predictions, weights=1.0, scope=None, loss_collection=tf.GraphKeys.LOSSES, reduction=Reduction.SUM_BY_NONZERO_WEIGHTS ) tf.reduce_mean(tf.square(labels - predictions)) tf.reduce_sum(tf.pow(labels - predictions, 2) / (2 * n_samples))
また、交差エントロピーを表現する方法も以下のようになります。
# 交差エントロピー ## labels: 正解値 ## predictions: 予測値 tf.nn.softmax_cross_entropy_with_logits( _sentinel=None, labels=None, logits=None, name=None ) tf.losses.softmax_cross_entropy( onehot_labels, logits, weights=1.0, label_smoothing=0, scope=None, loss_collection=tf.GraphKeys.LOSSES, reduction=Reduction.SUM_BY_NONZERO_WEIGHTS ) tf.reduce_sum(labels*tf.log(predictions))
TensorFlowにおける最適化関数
TensorFlowには最適化関数も複数用意されており、多くはtf.trainで利用できます。
関数名 | 用途 |
---|---|
tf.train.GradientDescentOptimizer | 勾配降下法を実行する |
tf.train.MomentumOptimizer | Momentum法を実行する |
tf.train.AdagradOptimizer | Adagrad法を実行する |
tf.train.AdamOptimizer | Adam法を実行する |
今回はtf.train.GradientDescentOptimizer
を使っています。またminimize
メソッドを使うことで、損失関数を最小化するように計算を実行します。
# 計算グラフの作成 X = tf.placeholder(tf.float32, [None, 13]) Y = tf.placeholder(tf.float32, [None, 1]) W = tf.Variable(tf.zeros([13, 1])) b = tf.Variable(0.0) ## 線形関数 y = tf.add(tf.matmul(X, W), b) ## 損失関数(二乗和誤差) loss = tf.losses.mean_squared_error(labels = y, predictions = Y) ## 最適化 optimizer = tf.train.GradientDescentOptimizer(learning_rate) train_step = optimizer.minimize(loss)
3. 各変数の初期化
今回は変数(tf.Variable)があるので、変数の初期化を行います。
# 変数の初期化 init = tf.global_variables_initializer()
4. 各関数の実行(実行フェーズ)
今回は繰り返し計算を実行し、損失関数を最小化するようモデルを改善し続ける必要があります。そのためfor
文を使って計算を繰り返します。
また訓練結果を100回ごとに表示しています。
# 計算処理 with tf.Session() as sess: sess.run(init) for epoch in range(num_epochs): sess.run(train_step, feed_dict={X: train_x, Y: train_y}) if epoch != 0 and epoch % 100 == 0: train_loss = sess.run(loss, feed_dict={X: train_x, Y: train_y}) print("train_loss: ", train_loss)
5. 計算結果の確認・評価
線形回帰の計算結果はRMSE(Root Mean Squared Error)で評価するのが正しいのですが、今回は簡易的に予測結果と実際の値を並べて表示しています。
# 評価 pred_y = sess.run(y, feed_dict={X: test_x}) for i in range(9): print("実際の不動産価格: ", test_y[i,0], " 予測した不動産価格: ", pred_y[i,0])
6. 計算グラフのリセット
TensorFlowで繰り返し計算を実行する際は、計算グラフをリセットしましょう。一番最後にあるtf.reset_default_graph()が計算グラフの初期化を実行する関数になります。
計算結果
それでは今回のコードの計算結果を表示します。なおハイパーパラメータを変更しても、計算結果にほとんど影響はありませんでした。
train_loss: 32.010765 train_loss: 22.759813 train_loss: 22.208197 train_loss: 21.997263 train_loss: 21.878563 train_loss: 21.80662 train_loss: 21.760534 train_loss: 21.729523 train_loss: 21.70777 実際の不動産価格: 23.6 予測した不動産価格: 28.982582 実際の不動産価格: 32.4 予測した不動産価格: 35.82864 実際の不動産価格: 13.6 予測した不動産価格: 15.828376 実際の不動産価格: 22.8 予測した不動産価格: 25.011866 実際の不動産価格: 16.1 予測した不動産価格: 18.713873 実際の不動産価格: 20.0 予測した不動産価格: 23.408972 実際の不動産価格: 17.8 予測した不動産価格: 17.68681 実際の不動産価格: 14.0 予測した不動産価格: 14.559787 実際の不動産価格: 19.6 予測した不動産価格: 22.812624
注意点
データのreshapeを行わないとどうなるか
データセットの変形を行った際reshape
を行っています。これはTensorFlowで計算を行う際、そのままではデータの型が合わず、エラーを吐いてしまうからです。
# reshapeを行わなかった場合のエラーメッセージ ValueError: Cannot feed value of shape (404,) for Tensor 'Placeholder_3:0', which has shape '(?, 1)' # reshapeをしない場合のデータ型 print(train_y[0:5]) > [12. 19.9 19.4 13.4 18.2] # reshapeをした場合のデータ型 print(train_y[0:5]) > [[12. ] [19.9] [19.4] [13.4] [18.2]]
正則化を行わない場合
正則化を行わない場合は、以下のようにNaNが出力されます。これはハイパーパラメータの値を変更しても変わりませんでした。これは損失関数の値が発散した(∞になった)ためと思われます。
# 正則化を行わない場合の出力結果 train_loss: nan train_loss: nan train_loss: nan train_loss: nan train_loss: nan train_loss: nan train_loss: nan train_loss: nan train_loss: nan 実際の不動産価格: 23.6 予測した不動産価格: nan 実際の不動産価格: 32.4 予測した不動産価格: nan 実際の不動産価格: 13.6 予測した不動産価格: nan 実際の不動産価格: 22.8 予測した不動産価格: nan 実際の不動産価格: 16.1 予測した不動産価格: nan 実際の不動産価格: 20.0 予測した不動産価格: nan 実際の不動産価格: 17.8 予測した不動産価格: nan 実際の不動産価格: 14.0 予測した不動産価格: nan 実際の不動産価格: 19.6 予測した不動産価格: nan
ですが、最適化関数を変更すると改善され、数値が出力されるようになります。ただtrain_loss
の数値は正則化した場合と比べても高いため、やはり正則化した方が性能は向上するようです。
# Adagradを使った場合 optimizer = tf.train.AdagradOptimizer(learning_rate) > train_loss: 71.82181 train_loss: 68.44326 train_loss: 65.977745 train_loss: 63.925743 train_loss: 62.17434 train_loss: 60.65328 train_loss: 59.314636 train_loss: 58.124134 train_loss: 57.05623 実際の不動産価格: 23.6 予測した不動産価格: 25.57679 実際の不動産価格: 32.4 予測した不動産価格: 28.002806 実際の不動産価格: 13.6 予測した不動産価格: 23.69553 実際の不動産価格: 22.8 予測した不動産価格: 20.848318 実際の不動産価格: 16.1 予測した不動産価格: 23.087069 実際の不動産価格: 20.0 予測した不動産価格: 22.962362 実際の不動産価格: 17.8 予測した不動産価格: 21.679335 実際の不動産価格: 14.0 予測した不動産価格: 20.155552 実際の不動産価格: 19.6 予測した不動産価格: 25.182972 # Adamを使った場合 optimizer = tf.train.AdamOptimizer(learning_rate) > train_loss: 60.566013 train_loss: 49.024643 train_loss: 43.398365 train_loss: 40.164436 train_loss: 37.729042 train_loss: 35.65119 train_loss: 33.810036 train_loss: 32.166935 train_loss: 30.707003 実際の不動産価格: 23.6 予測した不動産価格: 26.805815 実際の不動産価格: 32.4 予測した不動産価格: 35.081577 実際の不動産価格: 13.6 予測した不動産価格: 17.97925 実際の不動産価格: 22.8 予測した不動産価格: 23.187765 実際の不動産価格: 16.1 予測した不動産価格: 20.638412 実際の不動産価格: 20.0 予測した不動産価格: 22.202286 実際の不動産価格: 17.8 予測した不動産価格: 18.063892 実際の不動産価格: 14.0 予測した不動産価格: 15.885112 実際の不動産価格: 19.6 予測した不動産価格: 23.200396
まとめ
TensorFlowで線形回帰を実施しました。TensorFlowは大規模なNN向けのツールのため、線形回帰をするのは不適ですが、学習のステップとしてやっておくのは良いかと思います。なお、線形回帰を実施したい場合はScikit-Learn等のライブラリを利用するべきです。
次回からNeural Networkに着手しようと思います。