TECHSTEP

ITインフラ関連/機械学習に関する記事を公開してます。

TensorFlow再入門 ~線形回帰~

はじめに

先日の記事の続きです。今回は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. データセットの読み込み

データセットを読み込んでからは、実際の計算処理をする上で効率よく処理できるように前処理をする必要があります。

  • データの分割

まずは与えられたデータセットを説明変数と目的変数とに分けます。今回のデータセットdatatargetと言うキーを持っており、それぞれ説明変数と目的変数とに対応しています。

機械学習では、モデルの予測性能を向上させるため、データセット正則化・正規化を行うことがあります。ここでも正則化を行っていますが、正則化を行わないと損失関数の値が発散してしまい、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.nntf.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に着手しようと思います。

参考リンク

TensorFlowとは?不動産の価格をTensorFlowを使って予測してみよう(入門編)

学習の種類と誤差関数

[TensorFlow] APIドキュメントを眺める -Math編-