はじめに
前回はPyTorchを使って、単回帰モデルの実装・訓練を行いました。今回は二値分類を行います。
やったこと
アイリス・データセットからラベルが0と1のデータを抽出し、二値分類を行いました。
インポート
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
from IPython.display import display
# torch関連ライブラリのインポート
import torch
import torch.nn as nn
import torch.optim as optim
from torchinfo import summary
from torchviz import make_dot
# scikit-learn関連ライブラリのインポート
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
# デフォルトフォントサイズ変更
plt.rcParams['font.size'] = 14
# デフォルトグラフサイズ変更
plt.rcParams['figure.figsize'] = (6,6)
# デフォルトで方眼表示ON
plt.rcParams['axes.grid'] = True
データ準備
アイリス・データセットは150件あり、先頭の100行を抽出すると、setosaとversicolorの二値分類になります。特徴量は、sepal_lengthとsepal_widthのみとします。
# データ読み込み
iris = load_iris()
# 入力データと正解データ取得
x_org, y_org = iris.data, iris.target
# 結果確認
print('元データ', x_org.shape, y_org.shape)
# 元データ (150, 4) (150,)
# 二次分類化
x_data = iris.data[:100, :2]
y_data = iris.target[:100]
# 結果確認
print('対象データ', x_data.shape, y_data.shape)
# 対象データ (100, 2) (100,)
データ分割
データサイズを確認しておきます。
# 元のデータのサイズ
print(x_data.shape, y_data.shape)
# (100, 2) (100,)
訓練データを70件、検証データを30件に分割します。
# 訓練データ、検証データに分割(シャッフルも同時に実施)
x_train, x_test, y_train, y_test = train_test_split(x_data,
y_data,
train_size=70,
test_size=30,
random_state=123,
)
print(x_train.shape, x_test.shape, y_train.shape, y_test.shape)
# (70, 2) (30, 2) (70,) (30,)
散布図を表示させる。
# 散布図の表示
x_t0 = x_train[y_train == 0]
x_t1 = x_train[y_train == 1]
plt.scatter(x_t0[:,0], x_t0[:, 1], marker='x', label='0 (setosa)')
plt.scatter(x_t1[:,0], x_t1[:, 1], marker='o', label='1 (versicolor)')
plt.xlabel('sepal_length')
plt.ylabel('sedal_width')
plt.legend()
plt.show()
モデル定義
入力次元数は2(sepalの幅と長さ)で出力次元数は1(setonaか否か)になります。
# 入力次元数(いまの場合2)
n_input = x_train.shape[1]
# 出力次元数
n_output = 1
# 結果確認
print(f"n_input: {n_input}, n_output: {n_output}")
# n_input: 2, n_output: 1
モデルを定義します。2入力1出力のロジスティック回帰モデルを定義します。(分類なのに、ロジスティック回帰…)
class Net(nn.Module):
def __init__(self, n_input, n_output):
super().__init__()
self.l1 = nn.Linear(n_input, n_output)
self.sigmoid = nn.Sigmoid()
# 初期値を全部1にする
# 前著「ディープラーニングの数学」と条件を合わせる目的
self.l1.weight.data.fill_(1.0)
self.l1.bias.data.fill_(1.0)
# 予測関数の定義
def forward(self, x):
# 最初に入力値を線形関数にかけた結果を計算する
x1 = self.l1(x)
# 計算結果にシグモイド関数をかける
x2 = self.sigmoid(x1)
return x2
インスタンス生成し、モデルの概要を確認しておきます。
# インスタンスの生成
net = Net(n_input, n_output)
# モデルの概要表示
print(net)
# モデルのサマリー表示
summary(net, (2,))
サマリは以下のような感じ。
==========================================================================================
Layer (type:depth-idx) Output Shape Param #
==========================================================================================
Net [1] --
├─Linear: 1-1 [1] 3
├─Sigmoid: 1-2 [1] --
==========================================================================================
Total params: 3
Trainable params: 3
Non-trainable params: 0
Total mult-adds (Units.MEGABYTES): 0.00
==========================================================================================
Input size (MB): 0.00
Forward/backward pass size (MB): 0.00
Params size (MB): 0.00
Estimated Total Size (MB): 0.00
==========================================================================================
最適化アルゴリズムと損失関数などの定義
# 学習率
lr = 0.01
# 損失関数: 交差エントロピー関数
criterion = nn.BCELoss()
# 最適化関数: 勾配降下法
optimizer = optim.SGD(net.parameters(), lr=lr)
# 繰り返し回数
num_epochs = 10000
# 記録用リストの初期化
# 1: 繰り返し数
# 2: 訓練データの損失
# 3: 訓練データの精度
# 4: 検証データの損失
# 5: 検証データの精度
history = np.zeros((0, 5))
メインループ
データをテンソル化します。
# 入力データx_trainと正解データy_trainをテンソル化
inputs = torch.tensor(x_train).float()
labels = torch.tensor(y_train).float()
# 正解データはN行1列の行列に変換する
# これをやっておかないと予測の時?にエラーになる。
labels1 = labels.view(-1, 1)
# 検証データのテンソル化
inputs_test = torch.tensor(x_test).float()
labels_test = torch.tensor(y_test).float()
# 検証用の正解データもN行1列の行列に変換
labels1_test = labels_test.view(-1, 1)
訓練します。
# 繰り返し計算メインループ
# 初期化
net = Net(n_input, n_output)
for epoch in range(num_epochs):
# 訓練フェーズ ------------------------------------------
#勾配値初期化
optimizer.zero_grad()
# 予測計算
outputs = net(inputs)
# 損失計算
loss = criterion(outputs, labels1)
# 勾配計算
loss.backward()
# パラメータ修正
optimizer.step()
# ★損失の保存(スカラー値の取得)
train_loss = loss.item()
# ★予測ラベル(1 or 0)計算
# 二値分類なのでこういう実装
predicted = torch.where(outputs < 0.5, 0, 1)
# ★精度計算
train_acc = (predicted == labels1).sum() / len(y_train)
# 予測フェーズ ------------------------------------------
# 予測計算
outputs_test = net(inputs_test)
# 損失計算
loss_test = criterion(outputs_test, labels1_test)
# 損失の保存(スカラー値の取得)
val_loss = loss_test.item()
# 予測ラベル(1 or 0)計算
predicted_test = torch.where(outputs_test < 0.5, 0, 1)
# 精度計算
val_acc = (predicted_test == labels1_test).sum() / len(y_test)
if ( epoch % 1000 == 0):
print (f'Epoch [{epoch}/{num_epochs}], loss: {train_loss:.5f} acc: {train_acc:.5f} val_loss: {val_loss:.5f}, val_acc: {val_acc:.5f}')
item = np.array([epoch, train_loss, train_acc, val_loss, val_acc])
history = np.vstack((history, item))
結果は以下の通り。検証データの精度は0.967です。
Epoch [0/10000], loss: 4.77289 acc: 0.50000 val_loss: 4.49384, val_acc: 0.50000
Epoch [1000/10000], loss: 0.36160 acc: 1.00000 val_loss: 0.40759, val_acc: 0.96667
Epoch [2000/10000], loss: 0.23677 acc: 1.00000 val_loss: 0.29188, val_acc: 0.96667
Epoch [3000/10000], loss: 0.18060 acc: 1.00000 val_loss: 0.24044, val_acc: 0.96667
Epoch [4000/10000], loss: 0.14842 acc: 1.00000 val_loss: 0.21159, val_acc: 0.96667
Epoch [5000/10000], loss: 0.12737 acc: 1.00000 val_loss: 0.19317, val_acc: 0.96667
Epoch [6000/10000], loss: 0.11243 acc: 1.00000 val_loss: 0.18042, val_acc: 0.96667
Epoch [7000/10000], loss: 0.10121 acc: 1.00000 val_loss: 0.17109, val_acc: 0.96667
Epoch [8000/10000], loss: 0.09243 acc: 1.00000 val_loss: 0.16398, val_acc: 0.96667
Epoch [9000/10000], loss: 0.08535 acc: 1.00000 val_loss: 0.15839, val_acc: 0.96667
結果確認
# 損失と精度の確認
print(f"初期状態: 損失: {history[0, 3]:.5f}, 精度: {history[0, 4]:.5f}")
print(f"最終状態: 損失: {history[-1, 3]:.5f}, 精度: {history[-1, 4]:.5f}")
# 初期状態: 損失: 4.49384, 精度: 0.50000
# 最終状態: 損失: 0.15395, 精度: 0.96667
# 学習曲線の表示(損失)
plt.plot(history[:, 0], history[:, 1], 'b', label='train_loss')
plt.plot(history[:, 0], history[:, 3], 'k', label='train_test')
plt.xlabel('繰り返し回数')
plt.ylabel('損失')
plt.title('学習曲線(損失)')
plt.legend()
plt.show()
決定境界直線
# 検証データを散布図用に準備
x_t0 = x_test[y_test==0]
x_t1 = x_test[y_test==1]
# パラメータの取得
bias = net.l1.bias.data.numpy()
weight = net.l1.weight.data.numpy()
print(f'BIAS = {bias}, WEIGHT = {weight}')
# BIAS = [0.33861226], WEIGHT = [[ 2.9700334 -5.300017 ]]
# 決定境界描画用 x1の値から x2の値を計算する
def decision(x):
return(-(bias + weight[0,0] * x)/ weight[0,1])
# 散布図のx1の最小値と最大値
xl = np.array([x_test[:,0].min(), x_test[:,0].max()])
yl = decision(xl)
# 結果確認
print(f'xl = {xl} yl = {yl}')
# xl = [4.4 7. ] yl = [2.52956916 3.98656204]
# 散布図表示
plt.scatter(x_t0[:,0], x_t0[:, 1], marker='x', c='b', s=50, label='class 0')
plt.scatter(x_t1[:,0], x_t1[:, 1], marker='o', c='k', s=50, label='class 1')
# 決定境界直線
plt.plot(xl, yl, c='b')
plt.xlabel('sepal_length')
plt.ylabel('sepal_width')
plt.legend()
plt.show()
おわりに
理論の説明は極力省き、必要な実装部分を抽出しました。
出典