##はじめに
以前分類を実施したが、回帰もトライしてみた。
↓前回の記事
##環境
windows 10
python 3.8.10
torch 1.9.0+cu111
##使うデータセット
下記のサイトから。コンクリートの圧縮力について。
説明変数に水の含有力などがある。
##内容
###データセットの準備
import pandas as pd
import numpy as np
import torch
from torch import nn
from torch.nn import functional as F
from torch.utils.data import DataLoader
from torch.utils.data import random_split
from torchvision import transforms
from tqdm import tqdm
import matplotlib.pyplot as plt
#ファイルの読み込みと確認
df = pd.read_csv('Concrete_Data.csv')
df.head()
#データを説明変数と目的変数にわける
data = df.drop(df.columns[[-1]],axis=1)
target = df.iloc[:,-1]
# PyTorch で学習に使用できる形式へ変換
data = torch.tensor(data.values, dtype=torch.float32)
target = torch.tensor(target.values, dtype=torch.float32) #今回は回帰なのでfloat32
# 目的変数と入力変数をまとめてdatasetに変換
dataset = torch.utils.data.TensorDataset(data,target)
# 各データセットのサンプル数を決定
# train : val : test = 50% : 20% : 20%
n_train = int(len(dataset) * 0.6)
n_val = int((len(dataset) - n_train) * 0.5)
n_test = len(dataset) - n_train - n_val
# データセットの分割
torch.manual_seed(0) #乱数を与えて固定
train, val, test = torch.utils.data.random_split(dataset, [n_train, n_val,n_test])
#バッチサイズ
batch_size = 32
# 乱数のシードを固定して再現性を確保
torch.manual_seed(0)
# shuffle はデフォルトで False のため、学習データのみ True に指定
train_loader = torch.utils.data.DataLoader(train, batch_size, shuffle=True)
val_loader = torch.utils.data.DataLoader(val, batch_size)
test_loader = torch.utils.data.DataLoader(test, batch_size)
# 辞書型変数にまとめる(trainとvalをまとめて出す)
dataloaders_dict = {"train": train_loader, "val": val_loader}
###ネットワークの定義と学習
class Net(nn.Module):
# 使用するオブジェクトを定義
def __init__(self):
super(Net, self).__init__()
self.fc1 = nn.Linear(8, 4)
self.fc2 = nn.Linear(4, 3)
self.fc3 = nn.Linear(3, 1)
# 順伝播
def forward(self, x):
x = self.fc1(x)
x = F.relu(x)
x = self.fc2(x)
x = x**2*+1
x = self.fc3(x)
x = F.relu(x)
return x
# インスタンス化
net = Net()
# ネットワークの確認
net
# 損失関数の設定(最小二乗誤差)
criterion = nn.MSELoss()
# 最適化手法の選択
optimizer = torch.optim.Adam(net.parameters(), lr=0.01)
入力変数が8、結合層を上記のように設定した(回帰なので最終的には1つに値が決まる)。2層目のところでReLU関数ではなく、放物線を入れ込んでみた。
損失関数は分類と異なり、最小二乗誤差で定義。
def train_model(net, dataloader_dict, lossfun, optimizer, num_epoch):
l= []
# 重みを保持する変数
best_acc = 0.0
# GPUが使えるのであればGPUを有効化する
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
net = net.to(device)
# (エポック)回分のループ
for epoch in range(num_epoch):
for phase in ['train', 'val']:
if phase == 'train':
# 学習モード
net.train()
else:
# 推論モード
net.eval()
epoch_loss = 0.0
# 第1回で作成したDataLoaderを使ってデータを読み込む
for inputs, labels in tqdm(dataloaders_dict[phase]):
inputs = inputs.to(device)
labels = labels.to(device)
labels = torch.reshape(labels,(-1,1)) #サイズ変更
# 勾配を初期化する
optimizer.zero_grad()
# 学習モードの場合のみ勾配の計算を可能にする
with torch.set_grad_enabled(phase == 'train'):
outputs = net(inputs)
_, preds = torch.max(outputs, 1)
# 損失関数を使って損失を計算する
loss = criterion(outputs, labels)
if phase == 'train':
# 誤差を逆伝搬する
loss.backward()
# パラメータを更新する
optimizer.step()
epoch_loss += loss.item() * inputs.size(0)
# 1エポックでの損失を計算
epoch_loss = epoch_loss / len(dataloaders_dict[phase].dataset)
#lossをデータで保存する
a_loss = np.array(epoch_loss)
l.append(a_loss)
#epoch数とlossを表示する
print('Epoch {}/{}'.format(epoch + 1, num_epoch))
print('epoch_loss:{:.4f}'.format(epoch_loss))
print('-'*20)
#モデルを保存
torch.save(net, 'best_model.pth')
#testとvalのlossとaccを抜き出してデータフレーム化
l_train = l[::2]
l_train = pd.DataFrame({'train_loss_kaiki':l_train})
l_val = l[1::2]
l_val = pd.DataFrame({'val_loss_kaiki':l_val})
df_loss = pd.concat((l_train,l_val),axis=1)
#ループ終了後にdfを保存
df_loss.to_csv('loss_kaiki.csv', encoding='shift_jis')
#学習と検証
num_epoch = 30
net = train_model(net, dataloaders_dict, criterion, optimizer, num_epoch)
labelを途中reshapeしている。なくてもコードは動くが警告が出る(tensorの型がinputと異なるらしい)ので、一応入れている。
##感想
割と不完全燃焼感が強い
・関数内でlossをプロットしたかったけど、カーネルが死んでしまう(未解決)
・epochの回数を増やすことでlossは減少傾向だけど、結構大きい(もっとepoch増やしたらいい?)
・力尽きたのであとはできる人に投げよう(暴論)。
・とりあえず使ったcsvファイルとlossの結果はgithubに挙げた。
https://github.com/dem-kk/File/tree/main/Concrete