初心者向けPyTorch篇：5.二値分類

以下は、PyTorchを使用して人事データを基に二値分類モデルを構築するコードの解説です。
人事のデータを用いて離職するかを推定する。

データダウンロード：https://www.kaggle.com/datasets/fahadrehman07/hr-comma-sep-csv?resource=download

データのleftのカラムが離職を表しています。

# PyTorchを使った分類モデル構築の手順

以下は、PyTorchを使用して人事データを基に二値分類モデルを構築するコードの解説です。

## 必要なライブラリのインポート

```python
import torch
import pandas as pd
import numpy as np
from torch import nn
from torch.utils.data import TensorDataset, DataLoader
from sklearn.model_selection import train_test_split
```

## モデルの定義

```python
class Model(nn.Module):
    def __init__(self):
        super().__init__()
        self.liner_1 = nn.Linear(20, 64)
        self.liner_2 = nn.Linear(64, 64)
        self.liner_3 = nn.Linear(64, 1)
        self.relu = nn.ReLU()
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        x = self.liner_1(x)
        x = self.relu(x)
        x = self.liner_2(x)
        x = self.relu(x)
        x = self.liner_3(x)
        x = self.sigmoid(x)
        return x
```

- **nn.Linear**: 全結合層。入力特徴数と出力特徴数を指定します
    - nn.Linearの引数：nn.Linear(20, 64)では、入力データが20次元で、64次元の出力を生成される。最初の層の入力はデータのカラム数と一致する必要があります（入力データは20個のカラムがある）
- **ReLU**: 活性化関数。非線形性を加えるため
- **Sigmoid**: 出力を0〜1に制限します。二値分類でよく使われています


## モデルとオプティマイザの取得

```python
def get_model():
    model = Model()
    opt = torch.optim.Adam(model.parameters(), lr=0.0001)
    return model, opt
```

- **Adam**: 学習率が動的に調整される最適化手法

## データの読み込みと前処理

```python
data = pd.read_csv('HR_comma_sep.csv')

salary_one_hot = pd.get_dummies(data.salary).astype(int)
part_one_hot = pd.get_dummies(data.part).astype(int)

data = data.join(salary_one_hot)
data = data.join(part_one_hot)
del data['salary']
del data['part']

Y_data = data.left.values.reshape(-1, 1)
X_data = data[[c for c in data.columns if c != "left"]].values
train_x, test_x, train_y, test_y = train_test_split(X_data, Y_data)
```

- データをCSVから読み込みます
- 「salary」と「part」の列をOne-Hotエンコーディングし、元データと入れ替える
- 目的変数「離職（left）」と特徴量を分けます

※**One-Hotエンコーディングとは**：機械学習アルゴリズムが理解できる数値形式です。各カテゴリを1つの次元で「1」、他を「0」にする。

## データのTensor化とデータローダーの作成

```python
train_x = torch.from_numpy(train_x).type(torch.float32)
train_y = torch.from_numpy(train_y).type(torch.float32)
test_x = torch.from_numpy(test_x).type(torch.float32)
test_y = torch.from_numpy(test_y).type(torch.float32)

train_dataset = TensorDataset(train_x, train_y)
train_dataloader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_dataset = TensorDataset(test_x, test_y)
test_dataloader = DataLoader(test_dataset, batch_size=64)
```

- **torch.from_numpy**: Numpy配列をPyTorchのTensorに変換
- **DataLoader**: バッチサイズ指定とシャッフル機能付きのデータローダー
- **shuffle**: 訓練データに対してシャッフルを行うことで、モデルの学習の安定性を向上させるためです。テストデータはモデルのパフォーマンスを評価するためシャッフルする意味がありません

## 学習ループ

```python
model, optim = get_model()
loss_fn = nn.BCELoss()

batch_size = 64
batch_num = len(data) // batch_size
epochs = 1000

for epoch in range(epochs):
    for x, y in train_dataloader:
        y_pred = model(x)
        loss = loss_fn(y_pred, y)
        optim.zero_grad()
        loss.backward()
        optim.step()
    with torch.no_grad():
        epoch_accuracy = accuracy(model(train_x), train_y)
        epoch_loss = loss_fn(model(train_x), train_y).data

        epoch_test_accuracy = accuracy(model(test_x), test_y)
        epoch_test_loss = loss_fn(model(test_x), test_y).data
        print(f"echo: {epoch}, loss: {round(epoch_loss.item(), 3)}, accuracy: {round(epoch_accuracy.item(), 3)}, test loss: {round(epoch_test_loss.item(), 3)}, test accuracy: {round(epoch_test_accuracy.item(), 3)}")
```


- **loss_fn**: 損失関数
- **optim.step**: 勾配更新
- **torch.no_grad**: 評価時に勾配計算を無効化

# 学習結果と考察
```
echo: 0, loss: 0.566, accuracy: 0.762, test loss: 0.566, test accuracy: 0.761
echo: 1, loss: 0.565, accuracy: 0.762, test loss: 0.565, test accuracy: 0.761
echo: 2, loss: 0.562, accuracy: 0.762, test loss: 0.562, test accuracy: 0.761
echo: 3, loss: 0.572, accuracy: 0.762, test loss: 0.572, test accuracy: 0.761
echo: 4, loss: 0.562, accuracy: 0.762, test loss: 0.562, test accuracy: 0.761
echo: 5, loss: 0.558, accuracy: 0.762, test loss: 0.558, test accuracy: 0.761
...
echo: 995, loss: 0.181, accuracy: 0.937, test loss: 0.189, test accuracy: 0.933
echo: 996, loss: 0.176, accuracy: 0.939, test loss: 0.186, test accuracy: 0.932
echo: 997, loss: 0.185, accuracy: 0.932, test loss: 0.192, test accuracy: 0.928
echo: 998, loss: 0.176, accuracy: 0.94, test loss: 0.186, test accuracy: 0.934
echo: 999, loss: 0.176, accuracy: 0.94, test loss: 0.186, test accuracy: 0.933
```
訓練とテストデータの損失と精度の推移を見てみると、最初のエポックから徐々に損失が減少し、精度が向上していく様子が確認できます。最終的に、訓練データでの精度は約94%、テストデータでの精度も93%程度となり、モデルはかなり高い予測精度を達成しています。
最後の数エポックでは、訓練データとテストデータの精度がほぼ一致しており、過学習（オーバーフィッティング）の兆候は見られません。