この本について
本書では、Pytorchを使って、KaggleのHouse Priceを解いていきます。
最終的な精度は0.18367です。
この本を読むことによって
データの正規化
回帰の仕方
早期終了
深層学習での回帰の仕方
が学べます。
実行環境
OS:macOS 10.15.7
開発環境:Nova(Python対応テキストエディタならOK)
パッケージのinstall:Anaconda 4.9.2
Python:3.8.3
必要なパッケージ
本書では、以下のライブラリがインストールされているとして話を進めていきます。
torch==1.6.0
numpy==1.19.2
pandas==1.1.5
scikit_learn==0.24.1
お問い合わせ
内容に関する質問は
mailto:deepblack.inc@gmail.com
までお願いします。
データの読み込み
まずは、Pandasを用いて、タイタニックのCSVを読み込みます。
import pandas as pd
data = pd.read_csv("data/train.csv")
用いる特徴量を決める、教師を確定する
次に、用いる特徴量と教師を確定します。
今回は、数値が最初から入っている、特徴量を用いました。
他の特徴量を用いて見たい場合は、この本が終わった後に自分で試してみてください。
X = data[["MSSubClass", "LotFrontage", "LotArea", "OverallQual", "OverallCond", "YearBuilt", "YearRemodAdd", "MasVnrArea", "BsmtFinSF1", "BsmtFinSF2", "BsmtUnfSF", "TotalBsmtSF", "1stFlrSF", "2ndFlrSF", "LowQualFinSF", "GrLivArea", "BsmtFullBath", "BsmtHalfBath", "FullBath", "HalfBath", "BedroomAbvGr", "KitchenAbvGr", "TotRmsAbvGrd", "Fireplaces", "GarageYrBlt", "GarageCars", "GarageArea", "WoodDeckSF", "OpenPorchSF", "EnclosedPorch", "3SsnPorch", "ScreenPorch", "PoolArea", "MiscVal", "MoSold", "YrSold"]].fillna(data.mean()).values
y = data["SalePrice"].values
MinMaxScalerによる正規化とは
以下の式による 0 ~ 1 の範囲への変換
scikit-learnによる正規化
以下の式で簡単に正規化が可能となる。
実に、簡単だ。
# データの正規化
mm = preprocessing.MinMaxScaler()
X = mm.fit_transform(X)
訓練とEarlyStoppng用のデータに分ける
from sklearn.model_selection import train_test_split
X_train, X_valid, y_train, y_valid = train_test_split(X, target, test_size=0.33, random_state=42)
モデル
Deep Learningのモデルは以下のように書けます。
自分自身で、層を増やしたり、減らしたりして試してみてください。
import torch
from torch import nn
import torch.nn.functional as F
class Model(nn.Module):
def __init__(self):
super(Model, self).__init__()
self.linear1 = nn.Linear(36, 10)
self.linear2 = nn.Linear(10, 10)
self.linear3 = nn.Linear(10, 1)
def forward(self, x):
batch_size = x.shape[0]
x = self.linear1(x)
x = F.relu(x)
x = self.linear2(x)
x = F.relu(x)
out = self.linear3(x)
return out
PytorchのDatasetとDataLoader
この章では、PytorchのDatasetとDataLoaderについて解説していきます。
この章はhttps://gotutiyan.hatenablog.com/entry/2020/04/21/182937 を参考に記述されています。
Pytorchでは、DatasetとDataLoaderを用いることで、簡単にミニバッチ化をすることができます。
Datasetの実装
DataSetを実装する際には、クラスのメンバ関数として__len__()と__getitem__()を必ず作ります。
len()は、len()を使ったときに呼ばれる関数です。
getitem()は、array[i]のように[ ]を使って要素を参照するときに呼ばれる関数です。これが呼ばれる際には、必ず何かしらのindexが指定されているので、引数にindexの情報を取ります。また、入出力のペアを返すように設計します。
以上を踏まえて、Datasetを作成してみましょう。
class DataSet:
def __init__(self):
self.X = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # 入力
self.t = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1] # 出力
def __len__(self):
return len(self.X) # データ数(10)を返す
def __getitem__(self, index):
# index番目の入出力ペアを返す
return self.X[index], self.t[index]
さて、実際にこのDataSetがどのような振る舞いをするか試してみましょう。
dataset = DataSet()
print('全データ数:',len(dataset)) # 全データ数: 10
print('3番目のデータ:',dataset[3]) # 3番目のデータ: (3, 1)
print('5~6番目のデータ:',dataset[5:7]) # 5~6番目のデータ: ([5, 6], [1, 0])
DataLoaderの実装
バッチサイズを2、訓練時のデータのシャッフルをFalseとした実装は以下のようになります。
# さっき作ったDataSetクラスのインスタンスを作成
dataset = DataSet()
# datasetをDataLoaderの引数とすることでミニバッチを作成.
dataloader = torch.utils.data.DataLoader(dataset, batch_size=2, shuffle=False)
これでミニバッチ学習をする準備が整いました。
ミニバッチ用のデータはfor文で取り出すことができます。
for data in dataloader:
print(data)
'''
出力:
[tensor([0, 1]), tensor([0, 1])]
[tensor([2, 3]), tensor([0, 1])]
[tensor([4, 5]), tensor([0, 1])]
[tensor([6, 7]), tensor([0, 1])]
[tensor([8, 9]), tensor([0, 1])]
'''
上記のdataloaderを用いて、10epoch学習をする場合には以下のように書けます。
epoch = 10
model = #何かしらのモデル
for _ in range(epoch):
for data in dataloader:
X = data[0]
t = data[1]
y = model(X)
# lossの計算とか
PytorchのDatasetとDataloaderについての説明は、以上になります。
House PriceのDataset
import torch
class DataSet(torch.utils.data.Dataset):
def __init__(self, data, reg):
self.data = data
self.reg = reg
self.length = len(data)
def __len__(self):
return self.length
def __getitem__(self, index):
data = self.data[index]
reg = self.reg[index]
return data, reg
Early Stopping
この章は深層学習 (アスキードワンゴ) と https://github.com/Bjarten/early-stopping-pytorch を参考に書かれています。
十分な表現要領を持つ大きなモデルを訓練して、あるタスクに対して学習するとき訓練誤差は減少するが、検証誤差が再び増加し始めることがあります。
そこで、検証誤差が改善されるたびに、モデルを保存することにします。
一定のエポック数検証誤差が改善されない場合、学習は終了します。
コードは以下のようになります。
以下のコードをearlystopping.pyとして保存してください。
import numpy as np
import torch
class EarlyStopping:
def __init__(self, patience=7, verbose=False):
self.patience = patience
self.verbose = verbose
self.counter = 0
self.best_score = None
self.early_stop = False
self.val_loss_min = np.Inf
self.force_cancel = False
def __call__(self, val_loss, model):
score = -val_loss
if self.best_score is None:
self.best_score = score
self.save_checkpoint(val_loss, model)
elif score < self.best_score:
self.counter += 1
print(
f'EarlyStopping counter: {self.counter} out of {self.patience}')
if self.counter >= self.patience:
self.early_stop = True
else:
self.best_score = score
self.save_checkpoint(val_loss, model)
self.counter = 0
def save_checkpoint(self, val_loss, model):
if self.verbose:
print(
f'Validation loss decreased ({self.val_loss_min:.6f} --> {val_loss:.6f}). Saving model ...')
torch.save(model.state_dict(), 'models/model.pth')
self.val_loss_min = val_loss
Deep Learningモデルの訓練
Deep Learningの訓練は以下のようになります。
for epoch in range(EPOCHS):
train_loss = 0
total = 0
model.train()
for data in trainloader:
optimizer.zero_grad()
output = model(data[0].float())
target = data[1].float()
loss = criterion(output.squeeze(), target)
train_loss += loss.item()
total += data[1].size(0)
loss.backward()
optimizer.step()
train_loss = train_loss / total
model.eval()
vali_total = 0
vali_loss = 0
for data in validloader:
with torch.no_grad():
out = model.forward(data[0].float())
target = data[1].float()
loss = criterion(out.squeeze(), data[1])
vali_loss += loss.item()
vali_total += data[1].size(0)
vali_loss = vali_loss / vali_total
earlystopping(vali_loss, model)
if earlystopping.early_stop:
print("Early stopping")
break
訓練のCode
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
import pickle
import torch
from torch import nn
from sklearn import preprocessing
from sklearn.model_selection import train_test_split
from model import Model
from earlystopping import EarlyStopping
from data_loader import *
# HyperParameter
LEARNING_RATE = 0.0001
TRAIN_BATCH_SIZE = 10
VALID_BATCH_SIZE = 5
EPOCHS = 1000000
PATIENCE = 100
data = pd.read_csv("data/train.csv")
X = data[["MSSubClass", "LotFrontage", "LotArea", "OverallQual", "OverallCond", "YearBuilt", "YearRemodAdd", "MasVnrArea", "BsmtFinSF1", "BsmtFinSF2", "BsmtUnfSF", "TotalBsmtSF", "1stFlrSF", "2ndFlrSF", "LowQualFinSF", "GrLivArea", "BsmtFullBath", "BsmtHalfBath", "FullBath", "HalfBath", "BedroomAbvGr", "KitchenAbvGr", "TotRmsAbvGrd", "Fireplaces", "GarageYrBlt", "GarageCars", "GarageArea", "WoodDeckSF", "OpenPorchSF", "EnclosedPorch", "3SsnPorch", "ScreenPorch", "PoolArea", "MiscVal", "MoSold", "YrSold"]].fillna(data.mean()).values
target = data["SalePrice"].values
scaler = MinMaxScaler()
X = scaler.fit_transform(X)
X_train, X_valid, y_train, y_valid = train_test_split(X, target, test_size=0.33, random_state=42)
model = Model()
earlystopping = EarlyStopping(PATIENCE)
criterion = nn.MSELoss()
optimizer = torch.optim.AdamW(model.parameters(), lr=LEARNING_RATE)
trainset = DataSet(X_train, y_train)
trainloader = torch.utils.data.DataLoader(
trainset, batch_size=TRAIN_BATCH_SIZE, shuffle=True)
validset = DataSet(X_valid, y_valid)
validloader = torch.utils.data.DataLoader(
validset, batch_size=VALID_BATCH_SIZE, shuffle=True)
for epoch in range(EPOCHS):
train_loss = 0
total = 0
model.train()
for data in trainloader:
optimizer.zero_grad()
output = model(data[0].float())
target = data[1].float()
loss = criterion(output.squeeze(), target)
train_loss += loss.item()
total += data[1].size(0)
loss.backward()
optimizer.step()
train_loss = train_loss / total
model.eval()
vali_total = 0
vali_loss = 0
for data in validloader:
with torch.no_grad():
out = model.forward(data[0].float())
target = data[1].float()
loss = criterion(out.squeeze(), data[1])
vali_loss += loss.item()
vali_total += data[1].size(0)
vali_loss = vali_loss / vali_total
earlystopping(vali_loss, model)
if earlystopping.early_stop:
print("Early stopping")
break
with open('models/scaler.pkl', 'wb') as f:
pickle.dump(scaler, f)
テストのCode
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
import pickle
import torch
from torch import nn
from sklearn import preprocessing
from model import Model
from data_loader import *
with open('models/scaler.pkl', 'rb') as f:
scaler = pickle.load(f)
data = pd.read_csv("data/test.csv")
X = data[["MSSubClass", "LotFrontage", "LotArea", "OverallQual", "OverallCond", "YearBuilt", "YearRemodAdd", "MasVnrArea", "BsmtFinSF1", "BsmtFinSF2", "BsmtUnfSF", "TotalBsmtSF", "1stFlrSF", "2ndFlrSF", "LowQualFinSF", "GrLivArea", "BsmtFullBath", "BsmtHalfBath", "FullBath", "HalfBath", "BedroomAbvGr", "KitchenAbvGr", "TotRmsAbvGrd", "Fireplaces", "GarageYrBlt", "GarageCars", "GarageArea", "WoodDeckSF", "OpenPorchSF", "EnclosedPorch", "3SsnPorch", "ScreenPorch", "PoolArea", "MiscVal", "MoSold", "YrSold"]].fillna(data.mean()).values
ids_lst = list(data["Id"].values)
X = scaler.transform(X)
model = Model()
model.load_state_dict(torch.load("models/model.pth"))
res = model(torch.tensor(X).float()).squeeze().detach().numpy()
for i in range(0, len(ids_lst)):
ids_lst[i] = int(ids_lst[i])
sub_df = pd.DataFrame([ids_lst, res]).T
sub_df = sub_df.rename(columns={0:"Id", 1:"SalePrice"})
sub_df["Id"] = sub_df["Id"].round().astype(int)
sub_df.to_csv("sub.csv", index=False)