見出し画像

【機械学習】Pytorchでニューラルネットワークモデルを構築(シンプルなやつ)

機械学習では、
性質の違うモデルを組み合わせることで
高い精度を出すことができます。

このように複数のモデルを組み合わせることを
アンサンブルと言います。

ニューラルネットワークモデル(NNモデル)は、
画像解析や自然言語処理では高い精度を
発揮しますが、
機械学習でよくあるテーブルデータとなると、
精度はイマイチです。

しかしながら、NNモデルも、
アンサンブルの一つとして使うと
高い精度を発揮してくれることがあります。

特にブースティング系のモデルと組み合わせることで
高い精度が発揮されることが多いようです。

ここでは、アンサンブル用に、
シンプルなNNモデルを
構築する方法についてご紹介します。

フレームワークはPytorchを使います。

なお、データ分割においては、
クロスバリデーションを取り入れています。

ネットで調べても、
クロスバリデーションを取り入れた
NNモデルの構築例はあまりないと思います。

(自分自身は見つけられませんでした)
参考にしていただければと思います!


NNモデルを構築するにあたっての注意点


ニューラルネットワークモデルを構築する際には、以下の点に注意する必要があります。

  • 数値化: データは数値形式に変換する必要があります。文字列やカテゴリデータをそのまま扱えません、それらを適切に数値に変換することが求められます。

  • 欠損値の処理: 欠損値を扱うことができません。データセットに欠損値が含まれている場合、それらを適切に処理する必要があります。

  • 標準化・正規化: データの尺度を揃えるために、標準化や正規化を行うことが重要です。これにより、モデルは異なる尺度の特徴間で均衡を保つことができます。

ライブラリーのインポート


import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, Dataset, TensorDataset, Subset
from sklearn.metrics import f1_score
from sklearn.model_selection import KFold
import numpy as np
import pandas as pd


データの準備など


ここから、Pytorchで処理できるよう
データを揃えていきます。

ここでは、学習用にtrain_df_nn、テスト用にtest_df_nnを用意します。

”MIS_Status”がターゲットとします(二値分類タスク)
あらかじめ、欠損値は処理しているものとします。

❶データフレームの数値化


int型・float型のデータタイプのみにします

train_df_nn = train_df_nn.select_dtypes(include=["int","float"]).drop("MIS_Status",axis=1)
test_df_nn = test_df_nn.select_dtypes(include=["int","float"])
features = train_df_nn.columns

❷データの標準化


精度をあげるために標準化します。

# 訓練データだけで標準化を行う
scaler = StandardScaler()
train_df_sc = scaler.fit_transform(train_df_nn)

# テストデータに対しても、訓練データの平均、標準偏差を用いて標準化を行う
test_df_sc = scaler.transform(test_df_nn)


❸tensor化


Pytorchが処理できるよう
訓練データをtensorに変換します。
その後、データセットを作ります。

# トレーニングデータの準備(例)
train_features = torch.tensor(train_df_sc,dtype=torch.float32)  # トレーニングデータの特徴量
train_labels = torch.tensor(train_df_nn_load["MIS_Status"].values,dtype=torch.int64)    # トレーニングデータのラベル
train_labels = train_labels.unsqueeze(1)
train_dataset = torch.utils.data.TensorDataset(train_features,train_labels)


ニューラルネットワークモデルの定義


超シンプルなモデルを作ります。
3層構造です。
それぞれの層で標準化を行います。
活性化関数にはReluを使用しています。

class BinaryClassificationModel(nn.Module):
    def __init__(self, num_features):
        super(BinaryClassificationModel, self).__init__()
        self.layer_1 = nn.Linear(num_features, 64) 
        self.layer_2 = nn.Linear(64, 32)
        self.layer_out = nn.Linear(32, 1) 
        
        self.relu = nn.ReLU()
        self.dropout = nn.Dropout(p=0.1)
        self.batchnorm1 = nn.BatchNorm1d(64)
        self.batchnorm2 = nn.BatchNorm1d(32)
        
    def forward(self, x):
        x = self.relu(self.layer_1(x))
        x = self.batchnorm1(x)
        x = self.dropout(x)
        x = self.relu(self.layer_2(x))
        x = self.batchnorm2(x)
        x = self.dropout(x)
        x = torch.sigmoid(self.layer_out(x))
        return x


学習の実行


通常の機械学習のモデルと同じような実装になります。
ここでは評価指標としてMean F1スコアを用いています。
Pytorchでクロスバリデーションを行う際には、
Subsetを使う必要があります。

# クロスバリデーションの設定
n_splits = 7  # 分割数
kf = KFold(n_splits=n_splits)

# Mean F1スコアの初期化
mean_f1_score = 0
oof_predictions = np.zeros(len(train_df_nn))
models = []

# クロスバリデーション
for train_index, valid_index in kf.split(train_dataset):
    # トレーニングデータとバリデーションデータに分割
    
    train_subset = Subset(train_dataset, train_index)
    valid_subset = Subset(train_dataset, valid_index)
    
    # データローダーの作成
    train_dataloader = DataLoader(train_subset, batch_size=32, shuffle=True)
    valid_dataloader = DataLoader(valid_subset, batch_size=32, shuffle=False)
    
    # モデルの初期化
    model = BinaryClassificationModel(num_features=train_features.shape[1])
    
    # 損失関数とオプティマイザの設定
    criterion = nn.BCELoss()
    optimizer = optim.Adam(model.parameters(), lr=0.001)
    
    # トレーニングループ
    num_epochs = 10
    for epoch in range(num_epochs):
        for inputs, labels in train_dataloader:
            optimizer.zero_grad()
            outputs = model(inputs)
                     
            loss = criterion(outputs, labels.float())
            loss.backward()
            optimizer.step()
    
    # 予測
    predictions = []
    true_labels = []
    model.eval()
    with torch.no_grad():
        for inputs, labels in valid_dataloader:
            outputs = model(inputs)
            predictions.extend(outputs.tolist())
            true_labels.extend(labels.tolist())
    
    oof_predictions[valid_index] = np.array(predictions).flatten()
    models.append(model)
                    

    # F1スコアの計算
    f1 = f1_score(true_labels, [1 if p[0] > 0.5 else 0 for p in predictions],average="macro")
    print(f"Mean F1 Score_fold: {f1}")
    
    # Mean F1スコアの更新
    mean_f1_score += f1 / n_splits

oof_df_nn = pd.DataFrame({"nn_pred":oof_predictions,"MIS_Status":train_df_nn_load["MIS_Status"]})
cm = confusion_matrix(train_df_nn_load["MIS_Status"], np.round(oof_predictions))
plt.figure(figsize=(5, 5))
sns.heatmap(cm, annot=True, fmt="d", cmap="Blues")
plt.title(f'Confusion Matrix')
plt.xlabel('Predicted')
plt.ylabel('Actual')
plt.show()

# Mean F1スコアの出力
print(f"Mean F1 Score: {mean_f1_score}")


推論


テストデータを使って推論します

test = torch.tensor(test_df_sc,dtype=torch.float32)  

pred = torch.zeros(len(test), dtype=torch.float32)
with torch.no_grad():
    for model in models:
        model.eval()
        output = model(test)
        # ここでoutputの形状を調整
        output = output.squeeze()  # 余分な次元がある場合は削除
        # outputが複数の特徴量を持つ場合、適切な処理を行う
        pred += output
pred /= len(models)
pred_np = pred.numpy()


この記事が気に入ったらサポートをしてみませんか?