ニューラルネットワークの実装(回帰)

本章では、前章に引き続き TensorFlow を使用して分類と同じく教師あり学習のトピックである回帰について実装する方法を学んでいきます。基本的には、分類と同じように実装することができるのでシンプルです。

32_3

復習ですが、回帰とは目標値tt が連続値のデータセットと入力値 xx の関係を当てはめることを指します。上記の図ではデータセットに対して線を引いていますが、簡単にイメージするとこのようにデータセットを十分に表現できる線を引きたいということです。

本章の流れ

  • データセットの準備
  • モデルの定義
  • モデルの学習
  • 練習問題

データセットの準備

それではデータセットを準備しましょう。家賃の予測をする問題設定です。Keras ではいくつかデータセットを準備されており、tf.keras.datasets の中から選択します。

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

家賃が目的変数となっており、家賃を求めるために 13 個の入力変数が用意されています。部屋の広さや犯罪発生率など、皆さんが家の家賃を推定するときに使っていそうな変数が使われていそうです。各変数の説明は下記になりますので、気になる方はチェックしてください。

変数名 説明
CRIM 人口 1 人当たりの犯罪発生数
ZN 25,000 平方フィート以上の住居区画の占める割合
INDUS 小売業以外の商業が占める面積の割合
CHAS チャールズ川によるダミー変数 (1: 川の周辺, 0: それ以外)
NOX NOx の濃度
RM 住居の平均部屋数
AGE 1940 年より前に建てられた物件の割合
DIS 5 つのボストン市の雇用施設からの距離 (重み付け済)
RAD 環状高速道路へのアクセスしやすさ
TAX $10,000 ドルあたりの不動産税率の総計
PTRATIO 町毎の児童と教師の比率
B 町毎の黒人 (Bk) の比率を次の式で表したもの。
LSTAT 給与の低い職業に従事する人口の割合 (%)

tf.keras.datasets.boston_housing と名前がついたデータセットを使用します。load_data() を使って、データをダウンロードしてきています。

# データセットの準備
dataset = tf.keras.datasets.boston_housing
train, test = dataset.load_data()
      
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/boston_housing.npz 57344/57026 [==============================] - 0s 0us/step
# 学習用データセット
train
      
(array([[1.23247e+00, 0.00000e+00, 8.14000e+00, ..., 2.10000e+01, 3.96900e+02, 1.87200e+01], [2.17700e-02, 8.25000e+01, 2.03000e+00, ..., 1.47000e+01, 3.95380e+02, 3.11000e+00], [4.89822e+00, 0.00000e+00, 1.81000e+01, ..., 2.02000e+01, 3.75520e+02, 3.26000e+00], ..., [3.46600e-02, 3.50000e+01, 6.06000e+00, ..., 1.69000e+01, 3.62250e+02, 7.83000e+00], [2.14918e+00, 0.00000e+00, 1.95800e+01, ..., 1.47000e+01, 2.61950e+02, 1.57900e+01], [1.43900e-02, 6.00000e+01, 2.93000e+00, ..., 1.56000e+01, 3.76700e+02, 4.38000e+00]]), array([15.2, 42.3, 50. , 21.1, 17.7, 18.5, 11.3, 15.6, 15.6, 14.4, 12.1, 17.9, 23.1, 19.9, 15.7, 8.8, 50. , 22.5, 24.1, 27.5, 10.9, 30.8, 32.9, 24. , 18.5, 13.3, 22.9, 34.7, 16.6, 17.5, 22.3, 16.1, 14.9, 23.1, 34.9, 25. , 13.9, 13.1, 20.4, 20. , 15.2, 24.7, 22.2, 16.7, 12.7, 15.6, 18.4, 21. , 30.1, 15.1, 18.7, 9.6, 31.5, 24.8, 19.1, 22. , 14.5, 11. , 32. , 29.4, 20.3, 24.4, 14.6, 19.5, 14.1, 14.3, 15.6, 10.5, 6.3, 19.3, 19.3, 13.4, 36.4, 17.8, 13.5, 16.5, 8.3, 14.3, 16. , 13.4, 28.6, 43.5, 20.2, 22. , 23. , 20.7, 12.5, 48.5, 14.6, 13.4, 23.7, 50. , 21.7, 39.8, 38.7, 22.2, 34.9, 22.5, 31.1, 28.7, 46. , 41.7, 21. , 26.6, 15. , 24.4, 13.3, 21.2, 11.7, 21.7, 19.4, 50. , 22.8, 19.7, 24.7, 36.2, 14.2, 18.9, 18.3, 20.6, 24.6, 18.2, 8.7, 44. , 10.4, 13.2, 21.2, 37. , 30.7, 22.9, 20. , 19.3, 31.7, 32. , 23.1, 18.8, 10.9, 50. , 19.6, 5. , 14.4, 19.8, 13.8, 19.6, 23.9, 24.5, 25. , 19.9, 17.2, 24.6, 13.5, 26.6, 21.4, 11.9, 22.6, 19.6, 8.5, 23.7, 23.1, 22.4, 20.5, 23.6, 18.4, 35.2, 23.1, 27.9, 20.6, 23.7, 28. , 13.6, 27.1, 23.6, 20.6, 18.2, 21.7, 17.1, 8.4, 25.3, 13.8, 22.2, 18.4, 20.7, 31.6, 30.5, 20.3, 8.8, 19.2, 19.4, 23.1, 23. , 14.8, 48.8, 22.6, 33.4, 21.1, 13.6, 32.2, 13.1, 23.4, 18.9, 23.9, 11.8, 23.3, 22.8, 19.6, 16.7, 13.4, 22.2, 20.4, 21.8, 26.4, 14.9, 24.1, 23.8, 12.3, 29.1, 21. , 19.5, 23.3, 23.8, 17.8, 11.5, 21.7, 19.9, 25. , 33.4, 28.5, 21.4, 24.3, 27.5, 33.1, 16.2, 23.3, 48.3, 22.9, 22.8, 13.1, 12.7, 22.6, 15. , 15.3, 10.5, 24. , 18.5, 21.7, 19.5, 33.2, 23.2, 5. , 19.1, 12.7, 22.3, 10.2, 13.9, 16.3, 17. , 20.1, 29.9, 17.2, 37.3, 45.4, 17.8, 23.2, 29. , 22. , 18. , 17.4, 34.6, 20.1, 25. , 15.6, 24.8, 28.2, 21.2, 21.4, 23.8, 31. , 26.2, 17.4, 37.9, 17.5, 20. , 8.3, 23.9, 8.4, 13.8, 7.2, 11.7, 17.1, 21.6, 50. , 16.1, 20.4, 20.6, 21.4, 20.6, 36.5, 8.5, 24.8, 10.8, 21.9, 17.3, 18.9, 36.2, 14.9, 18.2, 33.3, 21.8, 19.7, 31.6, 24.8, 19.4, 22.8, 7.5, 44.8, 16.8, 18.7, 50. , 50. , 19.5, 20.1, 50. , 17.2, 20.8, 19.3, 41.3, 20.4, 20.5, 13.8, 16.5, 23.9, 20.6, 31.5, 23.3, 16.8, 14. , 33.8, 36.1, 12.8, 18.3, 18.7, 19.1, 29. , 30.1, 50. , 50. , 22. , 11.9, 37.6, 50. , 22.7, 20.8, 23.5, 27.9, 50. , 19.3, 23.9, 22.6, 15.2, 21.7, 19.2, 43.8, 20.3, 33.2, 19.9, 22.5, 32.7, 22. , 17.1, 19. , 15. , 16.1, 25.1, 23.7, 28.7, 37.2, 22.6, 16.4, 25. , 29.8, 22.1, 17.4, 18.1, 30.3, 17.5, 24.7, 12.6, 26.5, 28.7, 13.3, 10.4, 24.4, 23. , 20. , 17.8, 7. , 11.8, 24.4, 13.8, 19.4, 25.2, 19.4, 19.4, 29.1]))
# テスト用データセット
test
      
(array([[1.80846e+01, 0.00000e+00, 1.81000e+01, ..., 2.02000e+01, 2.72500e+01, 2.90500e+01], [1.23290e-01, 0.00000e+00, 1.00100e+01, ..., 1.78000e+01, 3.94950e+02, 1.62100e+01], [5.49700e-02, 0.00000e+00, 5.19000e+00, ..., 2.02000e+01, 3.96900e+02, 9.74000e+00], ..., [1.83377e+00, 0.00000e+00, 1.95800e+01, ..., 1.47000e+01, 3.89610e+02, 1.92000e+00], [3.58090e-01, 0.00000e+00, 6.20000e+00, ..., 1.74000e+01, 3.91700e+02, 9.71000e+00], [2.92400e+00, 0.00000e+00, 1.95800e+01, ..., 1.47000e+01, 2.40160e+02, 9.81000e+00]]), array([ 7.2, 18.8, 19. , 27. , 22.2, 24.5, 31.2, 22.9, 20.5, 23.2, 18.6, 14.5, 17.8, 50. , 20.8, 24.3, 24.2, 19.8, 19.1, 22.7, 12. , 10.2, 20. , 18.5, 20.9, 23. , 27.5, 30.1, 9.5, 22. , 21.2, 14.1, 33.1, 23.4, 20.1, 7.4, 15.4, 23.8, 20.1, 24.5, 33. , 28.4, 14.1, 46.7, 32.5, 29.6, 28.4, 19.8, 20.2, 25. , 35.4, 20.3, 9.7, 14.5, 34.9, 26.6, 7.2, 50. , 32.4, 21.6, 29.8, 13.1, 27.5, 21.2, 23.1, 21.9, 13. , 23.2, 8.1, 5.6, 21.7, 29.6, 19.6, 7. , 26.4, 18.9, 20.9, 28.1, 35.4, 10.2, 24.3, 43.1, 17.6, 15.4, 16.2, 27.1, 21.4, 21.5, 22.4, 25. , 16.6, 18.6, 22. , 42.8, 35.1, 21.5, 36. , 21.9, 24.1, 50. , 26.7, 25. ]))

load_data() を使用すると、便利な機能ですでに学習用データセット (train) とテスト用データセット (test) に分かれて、さらに入力変数と目標値にも分かれて準備されています。それぞれのサンプル数を確認しましょう。

# サンプル数
train[0].shape, test[0].shape
      
((404, 13), (102, 13))

ただし、このまますぐに学習に向かうことはできず、目標値と入力値に分割する必要があります。今回のデータセットの形式では、(入力値, 目標値) で用意されているので、それぞれを定義します。NumPy の array 形式に格納し、さらにデータ型を 32bit へと変更しておきます。こちらは前章の分類と同じ作業です。

# 学習用データセット
x_train = np.array(train[0], np.float32)
t_train = np.array(train[1], np.int32)

# テスト用データセット
x_test = np.array(test[0], np.float32)
t_test = np.array(test[1], np.uint32)
      
x_train.dtype, x_train.shape
      
(dtype('float32'), (404, 13))
x_test.dtype, x_test.shape
      
(dtype('float32'), (102, 13))

t_train を除くと、x_train は 404 行 13 列であることが確認できます。また、テスト用データセットでは、102 行 13 列であることがわかりますので、これはサンプル数全体が 506、入力変数が 13 あることを意味しています。

モデルの定義と学習

モデルの定義から学習まで行います。回帰と分類では、目的関数や最終層の活性化関数が違いますので、注意しながら定義していきましょう。

import os, random

def reset_seed(seed=0):
    os.environ['PYTHONHASHSEED'] = '0'
    random.seed(seed)
    np.random.seed(seed)
    tf.random.set_seed(seed)
      

今回定義するモデルの全体像はこちらの図です。入力層のノード数が 13、中間層のノード数が 32、出力層のノード数が 1 としています。

32_2

モデルのコンパイルでは、最適化手法に adam を使用し、目的関数は平均ニ乗誤差 (mse)、評価指標は平均絶対誤差 (mae) を定義しました。

平均ニ乗誤差や平均絶対誤差は、回帰でよく扱われる指標です。簡単に数式を紹介しておくと、NN このデータにおける nn 番目のサンプルの目標値を tnt_n、予測値を yny_n をすると、

MSE=1Nn=1N(tnyn)2\begin{array}{c} MSE = \frac{1}{N} \sum_{n=1}^{N} (t_n - y_n)^2 \end{array}

平均ニ乗誤差 (Mean Squeard Error:MSE) の式として表され、

MAE=1Nn=1Ntnyn2\begin{array}{c} MAE = \frac{1}{N} \sum_{n=1}^{N} |t_n - y_n|^2 \end{array}

平均絶対誤差 (Mean Absolute Error:MAE) の式として表されます。

この段階では数式を完璧に理解する必要はありませんので、イメージとして正解と予想の離れ具合を評価している、と思っておいていただければ大丈夫です。

# シードの固定
reset_seed(0)

# モデルの定義
model = tf.keras.models.Sequential([
    tf.keras.layers.Dense(32, activation='relu', input_shape=(13, )),
    tf.keras.layers.Dense(1),
])

# モデルのコンパイル
model.compile(optimizer='adam', 
              loss='mse',
              metrics=['mae'])
      

このままモデルの学習に移ることができるのですが、いくつか便利な機能をご紹介します。

上のセルで定義したモデルの構造の詳細を確認する方法があります。model.summary() と実行してもらうと、層の順番や出力形状、パラメータの数が一覧で表記することができます。

# モデル構造の確認
model.summary()
      
Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense (Dense) (None, 32) 448 _________________________________________________________________ dense_1 (Dense) (None, 1) 33 ================================================================= Total params: 481 Trainable params: 481 Non-trainable params: 0 _________________________________________________________________

各層の構造、モデル全体のパラメータの数 (Total params) や学習するパラメータの数 (Trainable params) などがひと目でわかります。

また、tf.keras.utils.plot_model() を使用するとインプットからアウトプットまでの一連の流れを把握することができます。こちらもよく使用しますので、覚えておきましょう。

# モデル構造の可視化
tf.keras.utils.plot_model(model)
      
<IPython.core.display.Image object>

このように目で見て直感的に構築したモデルを確認できるので、便利な機能です。さらに、デフォルトで model.png という名前で保存されているので何か誰かに説明する際にも使用することができます。

# モデルの学習
history = model.fit(x_train, t_train, 
                    epochs=30,
                    batch_size=32,
                    validation_data=(x_test, t_test))
      
Epoch 1/30 13/13 [==============================] - 0s 14ms/step - loss: 20975.8086 - mae: 138.6180 - val_loss: 12942.1172 - val_mae: 108.2218 Epoch 2/30 13/13 [==============================] - 0s 4ms/step - loss: 8085.9565 - mae: 82.8000 - val_loss: 3880.9031 - val_mae: 55.7379 Epoch 3/30 13/13 [==============================] - 0s 4ms/step - loss: 2205.7747 - mae: 39.3423 - val_loss: 879.4034 - val_mae: 25.3244 Epoch 4/30 13/13 [==============================] - 0s 4ms/step - loss: 600.8228 - mae: 20.6313 - val_loss: 430.2430 - val_mae: 19.3275 Epoch 5/30 13/13 [==============================] - 0s 4ms/step - loss: 435.1966 - mae: 17.9603 - val_loss: 399.2363 - val_mae: 17.8600 Epoch 6/30 13/13 [==============================] - 0s 4ms/step - loss: 385.2977 - mae: 16.4839 - val_loss: 322.9353 - val_mae: 16.0113 Epoch 7/30 13/13 [==============================] - 0s 4ms/step - loss: 304.7414 - mae: 14.8693 - val_loss: 254.1875 - val_mae: 14.2709 Epoch 8/30 13/13 [==============================] - 0s 4ms/step - loss: 240.0194 - mae: 13.4124 - val_loss: 210.0820 - val_mae: 12.8126 Epoch 9/30 13/13 [==============================] - 0s 5ms/step - loss: 191.8285 - mae: 11.9368 - val_loss: 168.1060 - val_mae: 11.1303 Epoch 10/30 13/13 [==============================] - 0s 4ms/step - loss: 139.2565 - mae: 9.9764 - val_loss: 118.8742 - val_mae: 8.9487 Epoch 11/30 13/13 [==============================] - 0s 4ms/step - loss: 94.6820 - mae: 8.0050 - val_loss: 89.5985 - val_mae: 7.5939 Epoch 12/30 13/13 [==============================] - 0s 4ms/step - loss: 77.6320 - mae: 7.0387 - val_loss: 83.6274 - val_mae: 6.9503 Epoch 13/30 13/13 [==============================] - 0s 4ms/step - loss: 67.6483 - mae: 6.4145 - val_loss: 82.2600 - val_mae: 6.7456 Epoch 14/30 13/13 [==============================] - 0s 4ms/step - loss: 64.9114 - mae: 6.2112 - val_loss: 82.2190 - val_mae: 6.6509 Epoch 15/30 13/13 [==============================] - 0s 4ms/step - loss: 63.5892 - mae: 6.0927 - val_loss: 81.3566 - val_mae: 6.5999 Epoch 16/30 13/13 [==============================] - 0s 4ms/step - loss: 61.9284 - mae: 6.0450 - val_loss: 80.3694 - val_mae: 6.5562 Epoch 17/30 13/13 [==============================] - 0s 4ms/step - loss: 61.6534 - mae: 5.9216 - val_loss: 81.3881 - val_mae: 6.5109 Epoch 18/30 13/13 [==============================] - 0s 5ms/step - loss: 61.2519 - mae: 6.0036 - val_loss: 78.6100 - val_mae: 6.4859 Epoch 19/30 13/13 [==============================] - 0s 4ms/step - loss: 61.3412 - mae: 5.8212 - val_loss: 82.0617 - val_mae: 6.4345 Epoch 20/30 13/13 [==============================] - 0s 4ms/step - loss: 58.8644 - mae: 5.7818 - val_loss: 77.4799 - val_mae: 6.4263 Epoch 21/30 13/13 [==============================] - 0s 4ms/step - loss: 57.9597 - mae: 5.8063 - val_loss: 78.5869 - val_mae: 6.3703 Epoch 22/30 13/13 [==============================] - 0s 4ms/step - loss: 58.1547 - mae: 5.7380 - val_loss: 77.7616 - val_mae: 6.3410 Epoch 23/30 13/13 [==============================] - 0s 4ms/step - loss: 57.5167 - mae: 5.6285 - val_loss: 78.7333 - val_mae: 6.3008 Epoch 24/30 13/13 [==============================] - 0s 4ms/step - loss: 56.7108 - mae: 5.6844 - val_loss: 76.7925 - val_mae: 6.2759 Epoch 25/30 13/13 [==============================] - 0s 4ms/step - loss: 55.6786 - mae: 5.5600 - val_loss: 77.9585 - val_mae: 6.2409 Epoch 26/30 13/13 [==============================] - 0s 4ms/step - loss: 56.9136 - mae: 5.5638 - val_loss: 75.1860 - val_mae: 6.2306 Epoch 27/30 13/13 [==============================] - 0s 4ms/step - loss: 55.1065 - mae: 5.5380 - val_loss: 74.9940 - val_mae: 6.1978 Epoch 28/30 13/13 [==============================] - 0s 4ms/step - loss: 55.0926 - mae: 5.5086 - val_loss: 74.7341 - val_mae: 6.1651 Epoch 29/30 13/13 [==============================] - 0s 5ms/step - loss: 55.4747 - mae: 5.3941 - val_loss: 75.8503 - val_mae: 6.1194 Epoch 30/30 13/13 [==============================] - 0s 4ms/step - loss: 54.3815 - mae: 5.4466 - val_loss: 73.4464 - val_mae: 6.1154

学習済みモデルを評価する方法として、.evaluate() があります。学習が終了した一番最後の状態で、テスト用データセットを入力として目的関数の値 (loss) と評価指標の値 (metrics) を表示します。

# 学習済みモデルの評価
score = model.evaluate(x_test, t_test)
score
      
4/4 [==============================] - 0s 1ms/step - loss: 73.4464 - mae: 6.1154
[73.44642639160156, 6.115375995635986]

具体的に出力された [73.44642639160156, 6.115375995635986] の意味としては、最初の73.446・・ に目的関数の値 (loss) が、6.1153・・ に評価指標の値 (metrics) が格納されています。

それでは前章でも扱ったように、結果の可視化を Pandas を使って行いましょう。

# 結果の可視化
result = pd.DataFrame(history.history)

# 目的関数の可視化
result[['loss', 'val_loss']].plot();
      
<Figure size 432x288 with 1 Axes>
# 評価指標の可視化
result[['mae', 'val_mae']].plot();
      
<Figure size 432x288 with 1 Axes>

練習問題

上記の結果のように、平均ニ乗誤差 (MSE) が検証データに対して約 73 となっており、誤差を二乗しているとしても少し予測誤差が大きいことがわかります。この原因を考え、対策をうち、平均ニ乗誤差が小さくなるようなモデルを考えてみましょう。

ヒント

  1. Batch Normalization をいれる
  2. エポック数を増やす
  3. 最適化手法 (optimizer) を変更する
  4. データセットを可視化し、必要な特徴量を選択する or 増やす

試行錯誤することで、実際の動作などの理解が深まるのでぜひ取り組りくんでください。

shareアイコン