TensorFlow使ってみた~GoogleColab/Auto MPG~回帰問題の実装
Auto MPGデータセットを使用した回帰問題
※チュートリアルより引用
回帰問題では、価格や確率といった連続的な値の出力を予測することが目的となります。
これは、分類問題の目的が、(例えば、写真にリンゴが写っているかオレンジが写っているかといった)離散的なラベルを予測することであるのとは対照的です。
MNIST手書き数字の認識ではインプットを0~9のクラスに分類したのに対して、予測した値を求めます。
Auto MPGモデルを使用した例では、車種の情報から、燃費性能を予測します。
AutoはAutoMobile、つまり自動車です。
MPGはMiles Per Gallon(1ガロンあたり何マイル走れるの?)です。
1マイルは約1.6km、1ガロンはイギリスでは約4.5リットル、アメリカでは約3.8リットルだそうです。
なんで国によって量が違うんですかね
実装
チュートリアルに従って実装を行っていきます。
インポートとか
# ペアプロットのためseabornを使用します !pip install -q seaborn from __future__ import absolute_import, division, print_function, unicode_literals import pathlib import matplotlib.pyplot as plt import pandas as pd import seaborn as sns import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers print(tf.__version__)
2.2.0-rc4
データセット読み込み
Downloading data from https://archive.ics.uci.edu/ml/machine-learning-databases/auto-mpg/auto-mpg.data 32768/30286 [================================] - 0s 4us/step
データクレンジング
# データクレンジング # このデータには、いくつか欠損値があります。 dataset.isna().sum() # この最初のチュートリアルでは簡単化のためこれらの行を削除します。 dataset = dataset.dropna() # "Origin"の列は数値ではなくカテゴリーです。このため、ワンホットエンコーディングを行います。 origin = dataset.pop('Origin') dataset['USA'] = (origin == 1)*1.0 dataset['Europe'] = (origin == 2)*1.0 dataset['Japan'] = (origin == 3)*1.0 dataset.tail()
データを訓練用セットとテスト用セットに分割
# データを訓練用セットとテスト用セットに分割 # データセットを訓練用セットとテスト用セットに分割しましょう。 # テスト用データセットは、作成したモデルの最終評価に使用します。 train_dataset = dataset.sample(frac=0.8,random_state=0) test_dataset = dataset.drop(train_dataset.index)
データの観察
# データの観察 sns.pairplot(train_dataset[["MPG", "Cylinders", "Displacement", "Weight"]], diag_kind="kde") # 全体の統計値 train_stats = train_dataset.describe() train_stats.pop("MPG") train_stats = train_stats.transpose() train_stats
MPG:燃費(Mile/Gallon)
Cylinders:気筒数
Displacement:排気量
Weight:車体重量
ラベルと特徴量の分離
# ラベルと特徴量の分離 # ラベル、すなわち目的変数を特徴量から切り離しましょう。このラベルは、モデルに予測させたい数量です。 train_labels = train_dataset.pop('MPG') test_labels = test_dataset.pop('MPG')
データの正規化
それぞれの特徴量の範囲がどれほど違っているかに注目してください。
スケールや値の範囲が異なる特徴量を正規化するのはよい習慣です。
特徴量の正規化なしでもモデルは収束するかもしれませんが、モデルの訓練はより難しいなり、結果として得られたモデルも入力で使われる単位に依存することになります。
def norm(x): return (x - train_stats['mean']) / train_stats['std'] normed_train_data = norm(train_dataset) normed_test_data = norm(test_dataset)
モデルの構築
それではモデルを構築しましょう。 ここでは、2つの全結合の隠れ層と、1つの連続値を返す出力層からなる、Sequentialモデルを使います。
def build_model(): model = keras.Sequential([ layers.Dense(64, activation='relu', input_shape=[len(train_dataset.keys())]), layers.Dense(64, activation='relu'), layers.Dense(1) ]) optimizer = tf.keras.optimizers.RMSprop(0.001) model.compile(loss='mse', optimizer=optimizer, metrics=['mae', 'mse']) return model model = build_model()
モデルの検証
summaryメソッドを使って、モデルの簡単な説明を表示します。
model.summary()
Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense (Dense) (None, 64) 640 _________________________________________________________________ dense_1 (Dense) (None, 64) 4160 _________________________________________________________________ dense_2 (Dense) (None, 1) 65 ================================================================= Total params: 4,865 Trainable params: 4,865 Non-trainable params: 0 _________________________________________________________________
モデルを試す
モデルを試してみましょう。
訓練用データのうち10個のサンプルからなるバッチを取り出し、それを使ってメソッドを呼び出します。
example_batch = normed_train_data[:10]
example_result = model.predict(example_batch)
example_result
_________________________________________________________________ array([[ 0.04067548], [ 0.11909638], [ 0.04571575], [ 0.3901804 ], [-0.12568757], [ 0.23469958], [-0.10220642], [ 0.4401984 ], [ 0.12558347], [-0.3357187 ]], dtype=float32)
うまく動作しているようです。
予定どおりの型と形状の出力が得られています。
モデルの訓練
モデルを1000エポック訓練し、訓練と検証の正解率をhistoryオブジェクトに記録します。
# エポックが終わるごとにドットを一つ出力することで進捗を表示 class PrintDot(keras.callbacks.Callback): def on_epoch_end(self, epoch, logs): if epoch % 100 == 0: print('') print('.', end='') EPOCHS = 1000 history = model.fit( normed_train_data, train_labels, epochs=EPOCHS, validation_split = 0.2, verbose=0, callbacks=[PrintDot()])
.................................................................................................... .................................................................................................... .................................................................................................... .................................................................................................... .................................................................................................... .................................................................................................... .................................................................................................... .................................................................................................... .................................................................................................... ....................................................................................................
モデルの訓練の様子を可視化
historyオブジェクトに保存された数値を使ってモデルの訓練の様子を可視化します。
hist = pd.DataFrame(history.history)
hist['epoch'] = history.epoch
hist.tail()
def plot_history(history): hist = pd.DataFrame(history.history) hist['epoch'] = history.epoch plt.figure() plt.xlabel('Epoch') plt.ylabel('Mean Abs Error [MPG]') plt.plot(hist['epoch'], hist['mae'], label='Train Error') plt.plot(hist['epoch'], hist['val_mae'], label = 'Val Error') plt.ylim([0,5]) plt.legend() plt.figure() plt.xlabel('Epoch') plt.ylabel('Mean Square Error [$MPG^2$]') plt.plot(hist['epoch'], hist['mse'], label='Train Error') plt.plot(hist['epoch'], hist['val_mse'], label = 'Val Error') plt.ylim([0,20]) plt.legend() plt.show() plot_history(history)
このグラフを見ると、検証エラーは100エポックを過ぎたあたりで改善が見られなくなり、むしろ悪化しているようです。
検証スコアの改善が見られなくなったら自動的に訓練を停止するように、model.fitメソッド呼び出しを変更します。
ここでは、エポック毎に訓練状態をチェックするEarlyStoppingコールバックを使用します。
設定したエポック数の間に改善が見られない場合、訓練を自動的に停止します。
EarlyStoppingによる訓練自動停止
model = build_model() # patience は改善が見られるかを監視するエポック数を表すパラメーター early_stop = keras.callbacks.EarlyStopping(monitor='val_loss', patience=10) history = model.fit(normed_train_data, train_labels, epochs=EPOCHS, validation_split = 0.2, verbose=0, callbacks=[early_stop, PrintDot()]) plot_history(history)
...................................................................
検証用データセットでのグラフを見ると、平均誤差は+/- 2 MPG(マイル/ガロン)前後です。
これはよい精度でしょうか? その判断はおまかせします。
テストデータでの評価
モデルの訓練に使用していないテスト用データセットを使って、モデルがどれくらい汎化できているか見てみましょう。
これによって、モデルが実際の現場でどれくらい正確に予測できるかがわかります。
loss, mae, mse = model.evaluate(normed_test_data, test_labels, verbose=2) print("Testing set Mean Abs Error: {:5.2f} MPG".format(mae))
3/3 - 0s - loss: 5.8512 - mae: 1.8385 - mse: 5.8512 Testing set Mean Abs Error: 1.84 MPG
モデルを使った予測
最後に、テストデータを使ってMPG値を予測します。
test_predictions = model.predict(normed_test_data).flatten() plt.scatter(test_labels, test_predictions) plt.xlabel('True Values [MPG]') plt.ylabel('Predictions [MPG]') plt.axis('equal') plt.axis('square') plt.xlim([0,plt.xlim()[1]]) plt.ylim([0,plt.ylim()[1]]) _ = plt.plot([-100, 100], [-100, 100])
そこそこよい予測ができているように見えます。誤差の分布を見てみましょう。
error = test_predictions - test_labels plt.hist(error, bins = 25) plt.xlabel("Prediction Error [MPG]") _ = plt.ylabel("Count")
とても正規分布には見えませんが、サンプル数が非常に小さいからだと考えられます。
結論
このノートブックでは、回帰問題を扱うためのテクニックをいくつか紹介しました。
・平均二乗誤差(MSE:Mean Squared Error)は回帰問題に使われる一般的な損失関数です(分類問題には異なる損失関数が使われます)。
・同様に、回帰問題に使われる評価指標も分類問題とは異なります。回帰問題の一般的な評価指標は平均絶対誤差(MAE:Mean Absolute Error)です。
・入力数値特徴量の範囲が異なっている場合、特徴量ごとにおなじ範囲に正規化するべきです。
・訓練用データが多くない場合、過学習を避けるために少ない隠れ層をもつ小さいネットワークを使うというのがよい方策の1つです。
・Early Stoppingは過学習を防止するための便利な手法の一つです。
まとめ
というわけで回帰問題の実装をチュートリアルに従い行いました。
分類問題との大きな違いは、損失関数と最適化手法ですかね。
細かい点はもっとありますが・・・
いろいろなパターンを実装していきノウハウを積んでいきましょう。