はじめに
この記事では簡単なニューラルネットワークを作成して、数値データの予測値を出力する推論モデルを作成します。
「回帰」は、複数の特徴データをもとに、連続値などの「数値」を予測するタスクです。 今回は、データセット「Boston house-prices」を使って、住宅の情報から価格を予測します。
この記事で使用されているソースコードは以下のGitHubや、Google Colabで試すことができます。
\begin{align*}
\newcommand{\mat}[1]{\begin{pmatrix} #1 \end{pmatrix}}
\newcommand{\f}[2]{\frac{#1}{#2}}
\newcommand{\pd}[2]{\frac{\partial #1}{\partial #2}}
\newcommand{\d}[2]{\frac{{\rm d}#1}{{\rm d}#2}}
\newcommand{\e}{{\rm e}}
\newcommand{\T}{\mathsf{T}}
\newcommand{\(}{\left(}
\newcommand{\)}{\right)}
\newcommand{\{}{\left\{}
\newcommand{\}}{\right\}}
\newcommand{\[}{\left[}
\newcommand{\]}{\right]}
\newcommand{\dis}{\displaystyle}
\newcommand{\eq}[1]{{\rm Eq}(\ref{#1})}
\newcommand{\n}{\notag\\}
\newcommand{\t}{\ \ \ \ }
\newcommand{\tt}{\t\t\t\t}
\newcommand{\argmax}{\mathop{\rm arg\, max}\limits}
\newcommand{\argmin}{\mathop{\rm arg\, min}\limits}
\def\l<#1>{\left\langle #1 \right\rangle}
\def\us#1_#2{\underset{#2}{#1}}
\def\os#1^#2{\overset{#2}{#1}}
\newcommand{\case}[1]{\{ \begin{array}{ll} #1 \end{array} \right.}
\newcommand{\s}[1]{{\scriptstyle #1}}
\definecolor{myblack}{rgb}{0.27,0.27,0.27}
\definecolor{myred}{rgb}{0.78,0.24,0.18}
\definecolor{myblue}{rgb}{0.0,0.443,0.737}
\definecolor{myyellow}{rgb}{1.0,0.82,0.165}
\definecolor{mygreen}{rgb}{0.24,0.47,0.44}
\newcommand{\c}[2]{\textcolor{#1}{#2}}
\newcommand{\ub}[2]{\underbrace{#1}_{#2}}
\end{align*}
住宅価格のデータセット「Boston house-prices」
「Boston house-prices」は、ボストン市の住宅の特徴と正解ラベルとなる価格を集めたデータセットです。訓練データ 404 件、テストデータ 102 件が含まれています。ボストン市の住宅の特徴としては、以下の 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 | 給与の低い職業に従事する人口の割合(%) |
TensorFlow には、このデータセットを読み込む機能が用意されています。
from tensorflow.keras.datasets import boston_housing
(train_data, train_labels), (test_data, test_labels) = boston_housing.load_data()
print(train_data.shape)
print(train_labels.shape)
print(test_data.shape)
print(test_labels.shape)
## 出力
(404, 13)
(404,)
(102, 13)
(102,)
訓練データと訓練ラベルは 404 件、テストデータとテストラベルは 102 件であることがわかります。特徴量の数は 13 件です。
Kerasによるニューラルネットワークの実装
この記事では以下のプロジェクト構成で実装を進めていきます。
.
├── requirements.txt
└── app/
├── logs/
├── model.py
├── params.py
├── predict.py
├── preprocessing.py
├── train.py
└── utils.py
app/
ディレクトリ配下にソースコードを配置します。それぞれの説明は下記の通りです。
logs/
: 機械学習モデルの学習過程の情報が出力されるディレクトリですmodel.py
: 機械学習モデルの定義を記載しますparams.py
: モデルのパラメータ等を記載しますpredict.py
: 学習済みモデルを用いた推論用コードを記載しますpreprocessing.py
: 前処理用の関数を記載しますtrain.py
: モデルの学習用コードを記載しますutils.py
: データを読み書きするコードを記載します
また、requirements.txt
の内容は次の通りです。
numpy==12.2.4
pandas==1.4.2
matplotlib==3.5.2
seaborn==0.11.2
scikit-learn==1.1.1
nptyping==2.1.1
tensorflow==2.9.1
# tensorflow-macos==2.9.2 # M1macの場合はこちらを使用する
pydot==1.4.2
graphviz==0.20
rich==12.5.1
今回作成するニューラルネットワークのアーキテクチャは下図のようなシンプルなものとします。
以上を踏まえて、それぞれのソースコードをみてみましょう。
params.py
params.py
ではモデルのアーキテクチャを決めるパラメータ等を記載します。
from datetime import datetime
from pathlib import Path
INPUT_SIZE = 13
HIDDEN1_SIZE = 64
HIDDEN2_SIZE = 64
OUTPUT_SIZE = 1
EPOCHS = 500
VALIDATION_SPLIT = 0.2
LOG_DIR = Path('logs/fit') / datetime.now().strftime("%Y%m%d-%H%M%S")
MODEL_FILE_PATH = Path('model.h5')
model.py
model.py
ではニューラルネットワークモデルの定義を記載します。ここでは Keras の Functional API を使用しています。
import tensorflow as tf
from keras.api._v2 import keras
from keras.layers import Input, Dense
from keras.models import Model
import params
class DNNModel:
def __init__(
self,
input_size: int = params.INPUT_SIZE,
hidden1_size: int = params.HIDDEN1_SIZE,
hidden2_size: int = params.HIDDEN2_SIZE,
output_size: int = params.OUTPUT_SIZE) -> None:
self.input: Input = Input(shape=(input_size,), name='input')
self.hidden1: Dense = Dense(hidden1_size, activation='relu', name='hidden1')
self.hidden2: Dense = Dense(hidden2_size, activation='relu', name='hidden2')
self.output: Dense = Dense(output_size, name='output')
def build(self) -> Model:
input = self.input
x = self.hidden1(input)
x = self.hidden2(x)
output = self.output(x)
return Model(inputs=input, outputs=output)
preprocessing.py
preprocessing.py
では画像の前処理用関数を記載します。
処理内容は、すべての特徴量を「平均 0、分散 1」に標準化しています。
import numpy as np
from nptyping import NDArray, Shape, Float, Int
from sklearn.preprocessing import StandardScaler
from utils import save_pkl, read_pkl
Features = NDArray[Shape['Sample, Feature'], Float]
def preprocess_dataset(dataset: Features, is_training: bool) -> Features:
if is_training:
sc = StandardScaler()
dataset = sc.fit_transform(dataset)
save_pkl(sc, path='scaler.pkl')
return dataset
sc = read_pkl(path='scaler.pkl')
dataset = sc.transform(dataset)
return dataset
ここでは型情報と numpy arrey の次元を明確にするために、nptyping ライブラリを使用しています。
また、 sklearn
の StandardScaler
を用いて標準化をおこなっています。テストデータを標準化する際は学習データを標準化する際に計算した平均、標準偏差を使って変換をおこないます。そのため、学習時に StandardScaler
オブジェクトを pickle 形式で保存しています。
utils.py
utils.py
ではオブジェクトを pickle 形式で保存、読み込みする関数を定義します。
import pickle
def save_pkl(obj, path):
with open(path, 'wb') as f:
pickle.dump(obj, f)
def read_pkl(path):
with open(path, 'rb') as f:
return pickle.load(f)
train.py
train.py
はモデルの学習を行います。
import tensorflow as tf
from keras.api._v2 import keras
from keras.models import Model
from keras.datasets import boston_housing
from keras.callbacks import EarlyStopping, ModelCheckpoint, TensorBoard
from keras.utils import plot_model
import params
from preprocessing import preprocess_dataset
from model import DNNModel
def main():
(train_data, train_labels), (test_data, test_labels) = boston_housing.load_data()
train_data, train_labels = preprocess_dataset(dataset=train_data, labels=train_labels)
model: Model = DNNModel().build()
model.compile(
optimizer='adam',
loss='mse',
metrics=['mae'])
model.summary()
plot_model(model, to_file='model.pdf', show_shapes=True)
callbacks = [
EarlyStopping(patience=20),
ModelCheckpoint(filepath=params.MODEL_FILE_PATH, save_best_only=True),
TensorBoard(log_dir=params.LOG_DIR)]
model.fit(
x=train_data,
y=train_labels,
epochs=params.EPOCHS,
validation_split=params.VALIDATION_SPLIT,
callbacks=callbacks)
if __name__ == '__main__':
main()
ここでは、Keras に用意されたコールバックを利用することで、学習中のバッチやエポックの開始 / 終了時にメゾットを呼び出しています。使用しているコールバックは以下の通りです。
EarlyStopping
: 過学習を避けるためのコールバックで、一定のエポック数、学習指標に改善が見られなくなった時に自動的に学習を停止します。ModelCheckpoint
: エポック終了時にモデルを保存するコールバックです。学習中にモデル保存が自動的にされ、もし学習が中断された場合、中断された時点から学習を再開することができます。TensorBoard
: 学習指標やモデルの構造を可視化できるツールです。引数log_dir
に指定したディレクトリに学習過程のさまざまな指標が自動的に格納されます。
また今回、ニューラルネットワークの評価関数は「MSE (Mean Squared Error)」、評価関数は「MAE (Mean Absolute Error)」を用いています。
\begin{align*}
MSE &= \f{1}{n} \sum_{i=1}^n \(y^{(i)} – \hat{y}^{(i)}\)^2 \n
MAE &= \f{1}{n} \sum_{i=1}^n \left|y^{(i)} – \hat{y}^{(i)}\right|
\end{align*}
($y^{(i)}$: $i$ 番目の正解データ、$\hat{y}^{(i)}$: $i$ 番目の予測値、$n$: データ数)
predict.py
predict.py
は、train.py
で学習したモデルを活用して推論を行います。
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from keras.api._v2 import keras
from keras.datasets import boston_housing
from rich import print
import params
from preprocessing import preprocess_dataset
def predict(dataset):
model = keras.models.load_model(params.MODEL_FILE_PATH)
pred = model.predict(dataset).flatten()
return pred
if __name__ == '__main__':
(train_data, train_labels), (test_data, test_labels) = boston_housing.load_data()
test_data, test_labels = test_data[:10], test_labels[:10] # 10データ分だけ推論をおこなう
test_data = preprocess_dataset(dataset=test_data, is_training=False)
pred = predict(dataset=test_data)
print(f'prediction: {np.round(pred, 1)}')
print(f'labels: {test_labels}')
ここでは、テストデータの 10 データ分だけ推論をおこないます。
ニューラルネットワークの実行
モデルの学習
train.py
を実行してニューラルネットワークモデルの学習をおこないます。
$ python train.py
## 出力
Model: "model"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input (InputLayer) [(None, 13)] 0
hidden1 (Dense) (None, 64) 896
hidden2 (Dense) (None, 64) 4160
output (Dense) (None, 1) 65
=================================================================
Total params: 5,121
Trainable params: 5,121
Non-trainable params: 0
_________________________________________________________________
Epoch 1/500
11/11 [==============================] - 0s 10ms/step - loss: 556.1224 - mae: 21.7004 - val_loss: 638.2590 - val_mae: 23.3441
Epoch 2/500
11/11 [==============================] - 0s 2ms/step - loss: 513.5609 - mae: 20.6846 - val_loss: 587.2802 - val_mae: 22.2735
.
.
.
Epoch 121/500
11/11 [==============================] - 0s 5ms/step - loss: 6.7154 - mae: 1.8254 - val_loss: 12.1797 - val_mae: 2.5738
Epoch 122/500
11/11 [==============================] - 0s 5ms/step - loss: 6.7295 - mae: 1.8319 - val_loss: 12.2754 - val_mae: 2.5554
学習が終了すると、app/
ディレクトリ内に学習済モデル model.h5
とモデルの構造が記載された model.pdf
が生成されます。model.pdf
内容は下図の通りです。
TensorBordをつかってみる
モデルの学習過程を確認するために、TensorBord を利用してみます。
コマンドラインで下記のコマンドを実行します。
$ tensorboard --logdir ./logs/fit
## 出力
Serving TensorBoard on localhost; to expose to the network, use a proxy or pass --bind_all
TensorBoard 2.9.1 at http://localhost:6006/ (Press CTRL+C to quit)
http://localhost:6006/ にアクセスすることで、下図のように、学習過程における各指標の推移を確認することができます。
学習済モデルによる推論
学習したモデルを用いて、Boston house-pricesデータセットのテストデータを推論してみます。コマンドラインで次のコマンドを実行します。
$ python predict.py
## 出力
prediction: [ 8.3 18. 21.1 34.6 26. 19.1 26. 21.6 19.6 22.3]
labels: [ 7.2 18.8 19. 27. 22.2 24.5 31.2 22.9 20.5 23.2]
以上のように、ニューラルネットワークによる、回帰モデルを作成することができました。
分類問題はこちら ▼