1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Python】PyTorchで2値分類

Last updated at Posted at 2024-10-27

はじめに

前回はPyTorchを使って、単回帰モデルの実装・訓練を行いました。今回は二値分類を行います。

やったこと

アイリス・データセットからラベルが0と1のデータを抽出し、二値分類を行いました。

  • 機械学習モデル: ロジスティック回帰
  • 損失関数: 交差エントロピー誤差
  • 最適化関数: SGD
    決定境界 - コピー.png
    学習曲線_損失 - コピー.png
    学習曲線_精度 - コピー.png

インポート

%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()

ch_06_scatter1.png

モデル定義

入力次元数は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

ch_06_学習曲線(損失).png

# 学習曲線の表示(損失)
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()

ch_06_学習曲線(精度).png

決定境界直線

# 検証データを散布図用に準備

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()

ch_06_scatter.png

おわりに

理論の説明は極力省き、必要な実装部分を抽出しました。

出典

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?