konchangakita

KPSを一番楽しんでいたブログ 会社の看板を背負いません 転載はご自由にどうぞ

PyTorchを使ったDeep Learningのお勉強 PyTorch Lightning編

f:id:konchangakita:20200424213935p:plain

PyTorchだけでも層のパラメータ定義など簡単なっていますが、コードを更にスッキリ(可読性を上げる)できるフレームワークPyTorch Lightning

github.com

PyTorchシリーズ第2回目です
PyTorch お勉強シリーズ
PyTorchを使ってDeep Learningのお勉強 基礎編
第2回 PyTorchを使ったDeep Learningのお勉強 PyTorch Lightning編(イマココ)
第3回 PyTorchを使ったDeep Learningのお勉強 TensorBoard編
第4回 PyTorchを使ったDeep Learningのお勉強 画像認識編(MNIST)
第5回 PyTorchを使ったDeep Learningのお勉強 画像認識編(CIFAR-10)

PyTorchを使ってみる目的は、複雑になりがちなDeep Learningのコードを簡単に実装してみることなので、まぁ、実際に使ってみないことにはどれくらいラクチンになるのか分からないので、とにもかくにもチャレンジしてみましょう

pytorch_lightningのインストール

anaconda環境でpytorch_lightningを使うには、anacondaでのGUI上ではみつけられなかったので
pipコマンドでインストールしてやりました

pip install pytorch-lightning

(だいたい10分くらいかかった)

※pipのPATHがとおってなければ、JupyterLabを起動して
f:id:konchangakita:20200421215339p:plain

JupyterLabのコマンドラインで実行可能
f:id:konchangakita:20200421215357p:plain


インストール確認

import pytorch_lightning as pl
pl.__version__

>>> 出力:
'0.7.3'



pytorch linghtningを使った学習モデル実装

pytorch lightningを使うことで、for文で回していた学習ループ部分簡略化できます
手作り感のある Epoch数、Batch処理 のこのあたり

f:id:konchangakita:20200424173342p:plain:w440



下準備

学習用のデータ前回と同じく「アヤメの品種」データを使います
データの読み込み部分は、「学習用データ」「検証用データ」「テスト用データ」に前回と同じく分けておきます


学習モデルの実装

学習モデルクラスの中にコスト関数(lossfun)や最適化手法(optimizer)など、PyTorch Lightningのお作法に倣って記載します
defの名前変えちゃダメ!

class Net(pl.LightningModule):
    def __init__(self, input_size=4, hidden_size=4, output_size=3, batch_size=10):
        super(Net, self).__init__()
        self.L1 = nn.Linear(input_size, hidden_size)
        self.L2 = nn.Linear(hidden_size, output_size)
        self.batch_size = batch_size
        
        self.bn = nn.BatchNorm1d(input_size)
        
    def forward(self, x):
        x = self.L1(x)
        x = F.relu(x)
        x = self.L2(x)
        return x
    
    def lossfun(self, y, t):
        return F.cross_entropy(y, t)
        #return nn.CrossEntropyLoss(self, y, t)
    
    def configure_optimizers(self):
        return torch.optim.SGD(self.parameters(), lr=0.1)
        
    @pl.data_loader
    def train_dataloader(self):
        return torch.utils.data.DataLoader(train, self.batch_size, shuffle=True)
    
    def training_step(self, batch, batch_nb):
        x, t = batch
        y = self.forward(x)
        loss = self.lossfun(y, t)
        results = {'loss': loss}
        return results



モデルの学習してみる

for文で回していたEpoch回数、Batchサイズなどの設定項目の指定が基本的には3行で済んじゃいます、ラクチン

net = Net() # 学習モデルのインスタンス化
trainer = Trainer() # 学習用のインスタンス化と学習の設定
trainer.fit(net) # 学習ループ実行


とりあえず Epoch 1回で実験実行してみます

# 学習に関する一連の流れを実行
torch.manual_seed(0)
net = Net() # インスタンス化

print(net.L1.weight, net.L1.bias) # 学習前のパラメータ

trainer = Trainer(max_epochs=1) # 学習用のインスタンス化と学習の設定
trainer.fit(net) # 学習ループ実行

print(net.L1.weight, net.L1.bias) # 学習後のパラメータ

出力:
f:id:konchangakita:20200424214735p:plain

おお、なんだか視覚的
学習前後でパラメータも変わっているので、なんらかの学習ができているようです
Trainer() を呼び出して学習ループを回しているわけですが、引数でいろいろ設定も変えられます

Trainer引数の代表的なの

引数名 デフォルトの値 説明
show_progress_bar True 学習時の進捗を標準出力
max_epochs 1000 学習時の最大エポック数
min_epochs 1 学習時の最小エポック数
train_percent_check 1.0 学習データに対する確認の比率 (%)
val_percent_check 1.0 検証データに対する確認の比率 (%)
test_percent_check 1.0 テストデータに対する確認の比率 (%)
early_stop_callback False 早期終了の使用の有無
gpus None 使用するGPUの数
distributed_backend None 分散学習の方法



モデルの学習とテストの実装実験

また、学習データ用、検証用データ、テストデータ用と学習モデル部分のクラスを分けてやります

# 学習データ用クラス
class TrainNet(pl.LightningModule):
    
    @pl.data_loader
    def train_dataloader(self):
        return torch.utils.data.DataLoader(train, self.batch_size, shuffle=True)
    
    def training_step(self, batch, batch_nb):
        x, t = batch
        y = self.forward(x)
        loss = self.lossfun(y, t)
        results = {'loss': loss}
        return results
# 検証データ用クラス
class ValidationNet(pl.LightningModule):

    @pl.data_loader
    def val_dataloader(self):
        return torch.utils.data.DataLoader(val, self.batch_size)

    def validation_step(self, batch, batch_nb):
        x, t = batch
        y = self.forward(x)
        loss = self.lossfun(y, t)
        y_label = torch.argmax(y, dim=1)
        acc = torch.sum(t == y_label) * 1.0 / len(t)
        results = {'val_loss': loss, 'val_acc': acc}
        return results

    def validation_end(self, outputs):
        avg_loss = torch.stack([x['val_loss'] for x in outputs]).mean()
        avg_acc = torch.stack([x['val_acc'] for x in outputs]).mean()
        results = {'val_loss': avg_loss, 'val_acc': avg_acc}
        return results
# テストデータ用クラス
class TestNet(pl.LightningModule):

    @pl.data_loader
    def test_dataloader(self):
        return torch.utils.data.DataLoader(test, self.batch_size)

    def test_step(self, batch, batch_nb):
        x, t = batch
        y = self.forward(x)
        loss = self.lossfun(y, t)
        y_label = torch.argmax(y, dim=1)
        acc = torch.sum(t == y_label) * 1.0 / len(t)
        results = {'test_loss': loss, 'test_acc': acc}
        return results

    def test_end(self, outputs):
        avg_loss = torch.stack([x['test_loss'] for x in outputs]).mean()
        avg_acc = torch.stack([x['test_acc'] for x in outputs]).mean()
        results = {'test_loss': avg_loss, 'test_acc': avg_acc}
        return results
# 学習、検証、テストの継承クラス
class Net(TrainNet, ValidationNet, TestNet):
    def __init__(self, input_size=4, hidden_size=4, output_size=3, batch_size=10):
        super(Net, self).__init__()
        self.L1 = nn.Linear(input_size, hidden_size)
        self.L2 = nn.Linear(hidden_size, output_size)
        self.batch_size = batch_size
        
        self.bn = nn.BatchNorm1d(input_size)
        
    def forward(self, x):
        x = self.L1(x)
        x = F.relu(x)
        x = self.L2(x)
        return x
    
    def lossfun(self, y, t):
        return F.cross_entropy(y, t)
        #return nn.CrossEntropyLoss(self, y, t)
    
    def configure_optimizers(self):
        return torch.optim.SGD(self.parameters(), lr=0.1)

このように、クラスを分けておくと
学習の層を深くしたり、正則化を加える時など学習モデルの修正・改造でいじる箇所限られるので
スッキリして可読性が上がりますね
(まぁだいたいコードは、一回じゃうまくいかないので、、、、修正がラク、、)

学習の実行
# 学習に関する一連の流れを実行
torch.manual_seed(0)
net = Net()

trainer = Trainer(max_epochs=100) # epoch 100回
trainer.fit(net)

出力
f:id:konchangakita:20200424230840p:plain

学習後のテストを実行
trainer.test()

出力:
f:id:konchangakita:20200424231115p:plain
Accuracy 1.0なので、なんと正解率100%の模様

なんだかうまくいってそうです!
この表示だといきなり結果だけの表示になっているので
次回は、この学習内容の可視化にチャレンジ