ニューラルネットワークの実装(回帰)
本章では、前章と同様に PyTorch Lightning を使用し、回帰を下記の流れで実装していきます。復習になりますが、分類はカテゴリを予測し、回帰では数値(連続値)を予測します。本章の問題設定では、家賃の中央値を予測するような問題になっています。
本章の構成
- データセットの準備
- PyTorch Lightning によるモデルと学習手順の定義
- モデルの学習
データセットの準備
今回はこちらから housing.csv
を使用して回帰の実装を練習してみましょう。どのような形式でデータを準備しておくと良いのかといった参考になるため、格納されているデータセットの中身も確認しておきます。
# ライブラリのインストール
!pip install pytorch_lightning
import numpy as np
import pandas as pd
from google.colab import files
uploaded = files.upload()
# データの読み込み(df: DataFrame)
df = pd.read_csv('housing.csv')
# データの表示
df.head()
x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | x10 | x11 | x12 | x13 | y | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0.00632 | 18.0 | 2.31 | 0 | 0.538 | 6.575 | 65.2 | 4.0900 | 1 | 296 | 15.3 | 396.90 | 4.98 | 24.0 |
1 | 0.02731 | 0.0 | 7.07 | 0 | 0.469 | 6.421 | 78.9 | 4.9671 | 2 | 242 | 17.8 | 396.90 | 9.14 | 21.6 |
2 | 0.02729 | 0.0 | 7.07 | 0 | 0.469 | 7.185 | 61.1 | 4.9671 | 2 | 242 | 17.8 | 392.83 | 4.03 | 34.7 |
3 | 0.03237 | 0.0 | 2.18 | 0 | 0.458 | 6.998 | 45.8 | 6.0622 | 3 | 222 | 18.7 | 394.63 | 2.94 | 33.4 |
4 | 0.06905 | 0.0 | 2.18 | 0 | 0.458 | 7.147 | 54.2 | 6.0622 | 3 | 222 | 18.7 | 396.90 | 5.33 | 36.2 |
df.shape
目的変数 y
を除くと、x
は 506 行 13 列であることが確認できます。これはサンプル数が 506、入力変数が 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 | 給与の低い職業に従事する人口の割合 (%) |
データの切り分け
t = df['y']
x = df.drop('y', axis=1)
こちらのように、正しく切り分けられているかデータの中身を表示して確認しておきましょう。
# 表示して確認
x.head()
x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | x10 | x11 | x12 | x13 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0.00632 | 18.0 | 2.31 | 0 | 0.538 | 6.575 | 65.2 | 4.0900 | 1 | 296 | 15.3 | 396.90 | 4.98 |
1 | 0.02731 | 0.0 | 7.07 | 0 | 0.469 | 6.421 | 78.9 | 4.9671 | 2 | 242 | 17.8 | 396.90 | 9.14 |
2 | 0.02729 | 0.0 | 7.07 | 0 | 0.469 | 7.185 | 61.1 | 4.9671 | 2 | 242 | 17.8 | 392.83 | 4.03 |
3 | 0.03237 | 0.0 | 2.18 | 0 | 0.458 | 6.998 | 45.8 | 6.0622 | 3 | 222 | 18.7 | 394.63 | 2.94 |
4 | 0.06905 | 0.0 | 2.18 | 0 | 0.458 | 7.147 | 54.2 | 6.0622 | 3 | 222 | 18.7 | 396.90 | 5.33 |
x.shape, t.shape
type(x), type(t)
tensor に変換
import torch
# PyTorch で学習に使用できる形式へ変換
x = torch.tensor(x.values, dtype=torch.float32)
t = torch.tensor(t.values, dtype=torch.float32)
dataset にまとめる
TensorDataset
を使用し、dataset にまとめましょう。
import torch.utils.data
# 入力変数と目的変数をまとめて、ひとつのオブジェクト dataset に変換
dataset = torch.utils.data.TensorDataset(x, t)
dataset
# (入力変数, 目的変数) のようにタプルで格納されている
dataset[0]
学習用データ、検証用データ、テスト用データに分割
DataLoader
の設定は PyTorch Lightning 側で用意されているので、必要ありませんでした。
# 各データセットのサンプル数を決定
# train : val : test = 60% : 20% : 20%
n_train = int(len(dataset) * 0.6)
n_val = int(len(dataset) * 0.2)
n_test = len(dataset) - n_train - n_val
# サンプル数の確認
n_train, n_val, n_test
# ランダムに分割を行うため、シードを固定して再現性を確保
torch.manual_seed(0)
# データセットの分割
train, val, test = torch.utils.data.random_split(dataset, [n_train, n_val, n_test])
PyTorch Lightning によるモデルと学習手順の定義
前章の最後に紹介した、学習データ、検証データ、テストデータのそれぞれに対する処理を TrainNet
、ValidationNet
、TestNet
のクラスにそれぞれ記述し、それらを継承した Net
に変化のある部分を記述していく形式で定義していきます。
import torch.nn as nn
import torch.nn.functional as F
import pytorch_lightning as pl
# 学習データに対する処理
class TrainNet(pl.LightningModule):
@pl.data_loader
def train_dataloader(self):
return torch.utils.data.DataLoader(train, self.batch_size, shuffle=True)
def training_step(self, batch, batch_nb):
x, t = batch
y = self.forward(x)
loss = self.lossfun(y, t)
results = {'loss': loss}
return results
# 検証データに対する処理
class ValidationNet(pl.LightningModule):
@pl.data_loader
def val_dataloader(self):
return torch.utils.data.DataLoader(val, self.batch_size)
def validation_step(self, batch, batch_nb):
x, t = batch
y = self.forward(x)
loss = self.lossfun(y, t)
results = {'val_loss': loss}
return results
def validation_end(self, outputs):
avg_loss = torch.stack([x['val_loss'] for x in outputs]).mean()
results = {'val_loss': avg_loss}
return results
# テストデータに対する処理
class TestNet(pl.LightningModule):
@pl.data_loader
def test_dataloader(self):
return torch.utils.data.DataLoader(test, self.batch_size)
def test_step(self, batch, batch_nb):
x, t = batch
y = self.forward(x)
loss = self.lossfun(y, t)
results = {'test_loss': loss}
return results
def test_end(self, outputs):
avg_loss = torch.stack([x['test_loss'] for x in outputs]).mean()
results = {'test_loss': avg_loss}
return results
それでは、3 つのクラスを継承した Net
クラスを記述していきます。前章の分類とは 1 点変更があり、目的関数 lossfun
が F.mse_loss
になっています。回帰には平均ニ乗誤差(MSE:Mean Squared Error) を採用することが多く、以下のような式になっています。
データ全体のサンプル数が で、目標値が 、予測値が となっています。モデルの性能がどれだけ悪いかを定量評価してくれる指標であり、MSE を小さくすることを目指します。
# 学習データ、検証データ、テストデータへの処理を継承したクラス
class Net(TrainNet, ValidationNet, TestNet):
def __init__(self, input_size=13, hidden_size=5, output_size=1, batch_size=10):
super(Net, self).__init__()
self.fc1 = nn.Linear(input_size, hidden_size)
self.fc2 = nn.Linear(hidden_size, output_size)
self.batch_size = batch_size
def forward(self, x):
x = self.fc1(x)
x = F.relu(x)
x = self.fc2(x)
return x
# New: 平均ニ乗誤差
def lossfun(self, y, t):
return F.mse_loss(y, t)
def configure_optimizers(self):
return torch.optim.SGD(self.parameters(), lr=0.1)
モデルの学習
それでは、モデルの定義ができたので学習を実行していきましょう。
from pytorch_lightning import Trainer
# 再現性の確保
torch.manual_seed(0)
# インスタンス化
net = Net()
trainer = Trainer()
# 学習の実行
trainer.fit(net)
検証データとテストデータに対する精度の確認
trainer.test()
trainer.callback_metrics
# 元のスケールに調整
torch.sqrt(torch.tensor(trainer.callback_metrics['test_loss']))
練習課題 本章のまとめ
上記の結果のように、平均二乗誤差がテストデータにおいて 79.76 となっており、オリジナルのスケールだと平均で 8.77 の誤差が生じていることになり、予測誤差が大きいことがわかります。今のモデルでは、まだまだ家賃予測の現場で使うことが難しいです。この原因を考え、対策をうち、平均二乗誤差を小さくできるようなモデルを考えてみましょう。
ヒント
- Batch Normalization を入れてスケールを統一してみよう
- さらに全結合層を追加し、モデルを複雑にしてみよう
- エポック数を増やしてみよう
- データを可視化して、関係性をイメージで捉えよう
ぜひ、取り組んでみてください。