Now Loading...

Now Loading...

本記事は「PyTorch チュートリアル Part2:2クラスデータセットの分類を体験」の続きです。今回は3クラスデータの分類に挑戦します。

本記事の構成は下記の通りです。

  • 3クラスデータセットの生成とデータの確認
  • ディープラーニング:モデルの学習による3クラス分類
  • モデルの改善
  • まとめ

3クラスデータセットの生成とデータの確認

今回は3クラスデータセットの分類を行ってみます。使用するデータは、sklearnなどのフレームワークの機能を用いたりせずに作成します。各データは原点からの距離、角度の二つの数字を変化させることで、プロットすると螺旋状になるように作成します。

 

上の出力を見てわかる通り、3つのクラスが存在します。今回もモデルに学習させることで、試験用データをこのように上手く分類することが目標です。以降本記事では、データセットの中のデータを「データ」、データセットの中で各データが属するクラスを「ラベル」と呼びます。

では、生成したデータについてより詳しく見てみましょう。データX、ラベルYの形状及びYの中身を出力してみます。

 

1クラスあたり500個のデータ×3で1500個というデータ数です。クラスはクラス0、クラス1、クラス2の三種類存在しますが、今回はラベルをワンホットエンコーディングしているため、Yの形状は(1500,3)となっていることに注意してください。

以上より、このデータセットは以下のような構造となっています。

図1

ディープラーニング:モデルの学習による3クラス分類

使用するデータについては把握できたのではないかと思いますので、早速学習に移りたいと思います。一般的に、ディープラーニングの流れは以下のようになっています。

  1. 訓練用データセット、試験用データセットの用意
  2. モデルを用意
  3. 訓練用データセットでモデルに学習させる
  4. 試験用データセットで推論

訓練用データセット、試験用データセットの用意

まずは訓練用データセットと試験用データセットの用意ということで、生成したデータセットを訓練用データセット : 試験用データセット = 8 : 2 の割合で分割します。また、データをシャッフルしてから分割する必要がある点に注意しましょう。便利なことに、sklearnのtrain_test_splitクラスは、データをシャッフルしながら分割してくれます。

from sklearn.model_selection import train_test_split

train_X, test_X, train_Y, test_Y = train_test_split(X, Y, test_size=0.2) #データを8:2に分割

分割した結果、データセットの構造は下の図のようになります。

図2

これらはnumpy.ndarrayなのでtorch.tensorに変換します。

#float32のtensorに変換
train_X = torch.FloatTensor(train_X)
train_Y = torch.FloatTensor(train_Y) 
test_X = torch.FloatTensor(test_X)
test_Y = torch.FloatTensor(test_Y)

さらに今回もデータローダーを作成するため、訓練用データセットはデータとラベルをまとめておきます。

from torch.utils.data import TensorDataset

train = TensorDataset(train_X, train_Y) #train_X、train_Yを一つにまとめる

torch.utils.dataのDataloaderクラスを用いてデータローダーを作成します。ミニバッチのサイズは10に設定しました。

from torch.utils.data import DataLoader

BATCH_SIZE = 10 #ミニバッチのサイズ

#訓練用データのDataloaderを作成
train_dataloader = DataLoader(
    train, 
    batch_size=BATCH_SIZE, 
    shuffle=True
)

これで訓練用、試験用のデータセットを用意できました。

モデルを用意

続いて、モデルを用意します。今回のモデルもnn.Moduleを継承したクラスとして定義します。モデルの出力層のサイズと活性化関数、及び損失関数に注意してください。今回は3クラス分類なので出力層のサイズは3で、活性化関数にはソフトマックス関数を用い、損失関数にクロスエントロピー誤差を用います。多クラス分類では活性化関数にソフトマックス関数を、損失関数にはクロスエントロピー誤差を用いることが多いということを思い出してください。また、PyTorchの仕様ではnn.CrossEntropyLoss()内でソフトマックス関数を同じ処理を行うようになっているため、以下のコードでは出力層の活性化関数を省略しています。

import torch
import torch.nn as nn
from torch.nn import functional as F

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(2, 8) 
        self.fc2 = nn.Linear(8, 3) #pytorchの仕様のため、出力層の活性化関数は省略
        
    # 順伝播
    def forward(self, x):
        x = self.fc1(x)
        x = F.relu(x)
        x = self.fc2(x)
        return x    

# インスタンス化
net = Net()

# 損失関数の設定(クロスエントロピー誤差)
criterion = nn.CrossEntropyLoss() #この中でソフトマックス関数と同じ処理をしている

# 最適化手法の選択(SGD)
optimizer = torch.optim.SGD(net.parameters(), lr=0.1)

層数は2クラス分類のときと同じく2層に設定しました。隠れ層におけるニューロンの数も2クラス分類のときと同じく、8つのままです。

訓練用データセットでモデルに学習させる

では、ここまでのまとめを兼ねて訓練用データセットで学習させてみましょう。

 

損失の減少があまり芳しくないですね。これは、データ数が前回よりも格段に多いためです。前回は500個でしたが今回は3倍の1500個のデータだったため、このモデルでは上手く学習できなかったのでしょう。あまり期待できませんが、このまま推論させてみましょう。

前回2クラス分類でワンホットエンコーディングによるラベル付けを行ったときと同様、あるデータを入力したときの3つの出力の内、最も値が大きいクラスにデータが分類されるようにします。

図3

この処理は、以下のように実装できます。

with torch.no_grad():# 試験用データでは勾配を計算しない
    pred_labels = [] # 各バッチごとの結果格納用

    for x in test_X:
            pred = net(x) #モデルの出力
            #argmax関数で出力における最大値のインデックスを取得し、ワンホットエンコーディングされたラベルに変換
            if torch.argmax(pred) == torch.tensor(0) : 
              pred_labels.append([1.,0.,0.])

            elif torch.argmax(pred) == torch.tensor(1):
              pred_labels.append([0.,1.,0.])

            else:
              pred_labels.append([0.,0.,1.])  
 
pred_labels = np.array(pred_labels) #numpy arrayに変換

試験用データセットで推論

では、推論させ、その結果を描画してみましょう。

 

ある程度上手く分類できているようですが、いくつかのデータは別の色に誤分類されてしまっているのがわかります。このように、データに対してモデルが単純すぎると、学習させてもバイアスと呼ばれる汎化誤差が高いままです。これはモデルのパラメータを調整したり、より複雑なモデルにすることで改善できます。

モデルの改善

分類の精度がいまいちだったので、モデルを少しだけ変更します。モデルには調整できるパラメータがたくさんあります。モデルの層数、隠れ層のニューロン数、用いる活性関数、用いる損失関数、オプティマイザ、学習率などです。今回はそれほど大きな変更をする必要はなさそうなので、簡単に隠れ層の数を増やしてみましょう。

import torch
import torch.nn as nn
from torch.nn import functional as F

class Net_2(nn.Module):
    def __init__(self):
        super(Net_2, self).__init__()
        self.fc1 = nn.Linear(2, 8) 
        self.fc2 = nn.Linear(8, 8) #新しく線形変換層を追加
        self.fc3 = nn.Linear(8, 3) 
        
    # 順伝播
    def forward(self, x):
        x = self.fc1(x)
        x = F.relu(x)
        x = self.fc2(x)
        x = F.relu(x)
        x = self.fc3(x)
        return x    

net2 = Net_2()

criterion_2 = nn.CrossEntropyLoss() 

optimizer_2 = torch.optim.SGD(net2.parameters(), lr=0.1)

学習させてみます。訓練データは同じものを用い、訓練ループにおける処理も同じです。ただ、このモデルだと200エポックも学習さなくても100エポック程度で十分だと思われるので100エポックだけ学習します。

 

上手く学習できているようです。では、推論させてみましょう。

 

層を増やさなかったときと異なり、かなり上手く分類できているように見えます。

まとめ

今回は3クラスデータセットの分類に挑戦しました。2クラス分類のときと比べて新しいことはほとんどやっていないので、よい復習になったのではないかと思います。このように、3クラス以上の多クラスを分類するときもPyTorchの機能を用いれば簡単に実装することができます。また、モデルは様々な改善を施すことで精度を上げることができます。自分でも改造に挑戦してみてはいかがでしょうか。

参考文献

  • Eli stevens, Luca Antiga, Thomas Viehmann 『Pytorch実践入門 ディープラーニングの基礎から実装へ』後藤勇輝, 小川雄太郎, 櫻井亮佑, 大串和正 訳. 株式会社マイナビ. 2021
  • PYTORCH DOCUMENTATION , https://pytorch.org/docs/stable/index.html

\ シェア /

E資格スピードパッケージの紹介