PyTorchを使用した深層学習

前々回の記事でKerasの他にPyTorchのインストール方法も紹介しました。

以前の記事はこちら

今回の記事では前回Kerasで実装した手書き数字認識プログラムをPyTorchで作成した場合どのようになるかを紹介したいと思います。

手書き数字認識(MNIST) PyTorch編

早速ですが、PyTorchで前回紹介したプログラムを書き直したものがこちらになります。

import torch
import torchvision
import torchvision.transforms as transforms
import torch.nn as nn
import torch.optim as optim

import time
import numpy as np
import os

 
#モデルを作成する関数
def build_model():
    model = nn.Sequential(nn.Conv2d(in_channels = 1,out_channels = 16,kernel_size = 3),
                          nn.ReLU(),
                          nn.MaxPool2d(kernel_size = 2),
                          nn.Conv2d(16,32,kernel_size = 3),
                          nn.ReLU(),
                          nn.MaxPool2d(kernel_size = 2),
                          nn.Flatten(),
                          nn.Linear(in_features = 800,out_features = 64),
                          nn.ReLU(),
                          nn.Dropout(p = 0.2),
                          nn.Linear(64,10),
                          nn.Softmax(1)
                         )
 
    return model

#モデルを学習する関数
def train_model(model,dataloader_train,dataloader_test,num_epochs,optimizer,criterion):
    device = "cuda:0" if torch.cuda.is_available() else "cpu"
    
    model.to(device)
    
    
    for epoch in range(num_epochs):
        running_loss = 0.0
        for inputs, labels in dataloader_train:
            
            inputs.to(device)
            labels.to(device)
            
            optimizer.zero_grad()
            
            outputs = model(inputs)
            loss = criterion(outputs, labels)
            loss.backward()
            optimizer.step()
            
            running_loss += loss.item()
            
        print("epoch{}/{} loss:{:.4f}".format(epoch + 1,num_epochs,running_loss))
            
        with torch.no_grad():
            correct = 0
            total = 0
            
            for inputs, labels in dataloader_test:
                inputs.to(device)
                labels.to(device)
                outputs = model(inputs)
                _, predicted = torch.max(outputs.data, 1)
                total += labels.size(0)
                correct += (predicted == labels).sum().item()
                
            print('Accuracy: {:.2f} %%'.format(100 * float(correct/total)))
            
            

                


#ここから実際に実行
if __name__ == "__main__":
    
    #学習用データの読み込みと下準備(MNIST:手書きデータ)
    dataset_train = torchvision.datasets.MNIST(root='./data',train=True,download=True,transform=transforms.ToTensor())
    dataloader_train = torch.utils.data.DataLoader(dataset_train,batch_size=128,shuffle=True)
    

    dataset_test = torchvision.datasets.MNIST(root='./data', train=False, download=True,transform=transforms.ToTensor())
    dataloader_test = torch.utils.data.DataLoader(dataset_test, batch_size=128,shuffle=False)

    
    #モデルを作成
    model = build_model()    
    
    #損失の定義
    criterion = nn.CrossEntropyLoss()
    
    #最適化手法の定義
    optimizer = optim.Adam(model.parameters())
    
    #学習を実行
    time_start = time.time()
    train_model(model,dataloader_train,dataloader_test,20,optimizer,criterion)

    #(学習にかかった時間を表示) 
    time_end = time.time()
    print("time:",(time_end - time_start))
    
    
    # モデルの評価を行う
    model.to("cpu")
    with torch.no_grad():
        loss = 0.0
        correct = 0
        total = 0
            
        for inputs, labels in dataloader_test:
            outputs = model(inputs)
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()
            loss += criterion(outputs, labels).item()
            
        print('loss=', (float(loss)/total))
        print('accuracy=', (float(correct)/total))

  

一応Kerasを使用した場合のプログラムも再掲しておきます。
紹介している記事はこちら

from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation,MaxPooling2D,Conv2D,Flatten
from keras.optimizers import Adam 
from keras.utils import np_utils
import time
import numpy as np
import os

 
#モデルを作成する関数
def build_model():
    model = Sequential()
    model.add(Conv2D(16,(3,3),input_shape = (28,28,1)))
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size = (2,2)))

    model.add(Conv2D(32,(3,3)))
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size = (2,2)))

    model.add(Flatten())
 
    model.add(Dense(64))
    model.add(Activation('relu'))
    model.add(Dropout(0.2))
 
    model.add(Dense(10))
    model.add(Activation('softmax'))
 

    model.compile(
    loss='categorical_crossentropy',
    optimizer=Adam(),
    metrics=['accuracy'])
 
    return model
 
#ここから実際に実行
if __name__ == "__main__":
    
   
    #学習用データの読み込みと下準備(MNIST:手書きデータ)
    (X_train, y_train), (X_test, y_test) = mnist.load_data()
    X_train = X_train.reshape(60000, 28,28,1).astype('float32')
    X_test = X_test.reshape(10000, 28,28,1).astype('float32')
    X_train /= 255
    X_test /= 255
 
    y_train = np_utils.to_categorical(y_train, 10)
    y_test = np_utils.to_categorical(y_test, 10)
 
    #モデルを作成、モデルの内容を表示させる。
    model = build_model()
    model.summary()
    
    #学習を実行
    time_start = time.time()
    model.fit(X_train, y_train,batch_size=128,epoch=20,
    validation_data=(X_test, y_test)
    )

    #(学習にかかった時間を表示) 
    time_end = time.time()
    print("time:",(time_end - time_start))
 
    # モデルの評価を行う
    score = model.evaluate(X_test, y_test, verbose=1)
 
    print('loss=', score[0])
    print('accuracy=', score[1])

  

1.必要なライブラリのimport

torch:PyTorchのモジュール。
    Tensorの操作等、学習に使用する様々な機能がまとめられています。
torch.nn:ニューラルネットワークを構成するモデルがまとめられた
     モジュール
torch.optim:最適化手法に関する機能がまとめられたモジュール
torchvision:画像に関する機能がまとめられたモジュール
torchvision.transoforms:画像の前処理に関する機能がまとめられた
             モジュール
※以下は前回と同じ。
time:時間に関するモジュール。計算にかかった時間を計測する。
numpy:数値計算用のライブラリ
os:ファイル操作等、OSに関わる機能がまとまったモジュール。
  今回は使用していないが、学習内容を記録する際などに使用する。

前回は使用するものを抜粋してfrom○○import××の形でimportしましたが、今回はモジュールをimportして( import○○)、○○.××の形で使用しています。

2.モデル作成用の関数build_model()

作成しているモデルについては以前の記事を見ていただくということで、説明しませんが、いくつかKerasとは違っている部分があります。

一つは層のクラス名です。

Kerasクラス名 : PyTorchクラス名
Conv2D : Conv2d
Dense : Linear
MaxPooling2D : MaxPool2d
Activation(“relu”) : ReLU
Activation(“Softmax”) : Softmax
各クラス初期化時の引数も異なっています。

また、同じSequentialを使用していますが、実はPyTorchのSequentialにはaddに相当する関数がありません。今回のようにSequential初期化時に内部で初期化するか、先にインスタンスを作成しておいてSequentialに与えるかします。

このような手順が面倒な場合は、Pytorchではtorch.nn.Moduleを継承して、forward()関数(順伝播用関数) をオーバーライドする場合が多いようです。
Sequentialはこの順伝播用関数内で、与えられたModuleを順番順伝播させるModuleクラスです。

更にKerasと大きく違う点がもう1点あります。
PyTorchではモデルをcompileしません。これはKerasが複数のバックエンドを持つため、各バックエンドに合わせた形式に変換する必要があるのではないかと思います。

3.モデルを学習する関数train_model()

Kerasではモデルの学習用関数fit()を使用しましたが、PyTorchでは学習の内容を自分で実装しなければいけません。

とはいえ逆伝播などの手順はしっかりと関数かされているので、実際に実装するのはバッチ毎に誤差を計算し、それを基に最適化用クラスに学習実行させることです。

#抜粋
    for epoch in range(num_epochs):
        running_loss = 0.0
        for inputs, labels in dataloader_train:
            
            inputs.to(device)
            labels.to(device)
            
            optimizer.zero_grad()
            
            outputs = model(inputs)
            loss = criterion(outputs, labels)
            loss.backward()
            optimizer.step()
            
            running_loss += loss.item()

最初にcuda(GPU)が使用可能か調べ、可能であればモデルとインプットするデータをcudaに移しています。
labelsは移さなくてもよかったかもしれませんが、criterionの処理でエラーが出ると嫌なので一応移しておきました。

num_epochs分だけ繰り返し、その中で各バッチ毎に学習をさせています。
dataloader は作成時に指定したbatch_size分のデータを自動的に吐き出してくれます。

行っている内容の概要は以下の通りです。

最適化手法の勾配をリセットし、入力をモデルに順伝播させます。
順伝播させた結果と正解ラベルを比較して誤差を計算します。
誤差を逆伝播させて各ニューロンの誤差を計算します。
計算した誤差を基に最適化クラスで最適化を行います。

少し面倒だったので学習データのAccuracyの計算は省略しました。

4.MNISTデータの読み込み

#抜粋:学習用データの読み込みと下準備(MNIST:手書きデータ)
    dataset_train = torchvision.datasets.MNIST(root='./data',train=True,download=True,transform=transforms.ToTensor())
    dataloader_train = torch.utils.data.DataLoader(dataset_train,batch_size=128,shuffle=True)
    

    dataset_test = torchvision.datasets.MNIST(root='./data', train=False, download=True,transform=transforms.ToTensor())
    dataloader_test = torch.utils.data.DataLoader(dataset_test, batch_size=128,shuffle=False)

PyTorchではすでに使用できるDataSetの形で用意されているようです。
ただし、中身は画像データのようですので、transofromにToTensorクラスを指定して計算用のTensorクラスに変換するようにしています。
ラベルもセットで吐き出してくれます。

中身については 以前の記事を参照してください。

ついでにDataSetからデータをバッチサイズ分吐き出してくれるDataLoaderを作成します。学習用データはshuffleをTrueにし、ランダムに取り出して学習します。
学習に使用しない評価用データはshuffleをFalseにしてあります。

5.モデル作成と学習の実行

#抜粋
#モデルを作成
    model = build_model()    
    
    #損失の定義
    criterion = nn.CrossEntropyLoss()
    
    #最適化手法の定義
    optimizer = optim.Adam(model.parameters())
    
    #学習を実行
    time_start = time.time()
    train_model(model,dataloader_train,dataloader_test,20,optimizer,criterion)

今回作成した学習用関数には損失と最適化手法クラスを引数に与えるので、それぞれ作成して学習を実行します。
一般的な損失クラスや最適化手法クラスはPyTorchに用意されています。

6.モデルの評価

#抜粋
    # モデルの評価を行う
    model.to("cpu")
    with torch.no_grad():
        loss = 0.0
        correct = 0
        total = 0
            
        for inputs, labels in dataloader_test:
            outputs = model(inputs)
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()
            loss += criterion(outputs, labels).item()
            
        print('loss=', (float(loss)/total))
        print('accuracy=', (float(correct)/total))

評価はCPU上で行うことにしました。
計算結果は学習に反映させないので、with torch.no_grad():のインデント内で処理を行います。

Kerasと違い、lossやaccuracyを勝手に計算してくれないので、結果をバッチ毎に集計して最後に計算して表示しています。

  

以上がプログラムの簡単な説明になります。

  

PyTorchについて(使用した感想)

紹介した通り、PyTorchはKerasに比べるとコーディングの手間が多いです。
ただし、今回実装した内容はKerasでは勝手にやってくれているだけで、やっていることの中身はほぼ同じです。

今取り扱っているような簡単なモデルの学習であればそれで十分ですが、複雑なモデルを取り扱う場合には、結局学習ルーチンの改造が必要になってきます。

Kerasであれば、今回使用したfit()関数の他に、PyTorchでやっているようにバッチ単位での学習を実行するtrain_on_batch()関数が存在しており、これらを使用してPytorchと同じように学習ルーチンを書き下していくことになると思います。

そう考えれば、普段からPyTorchのように学習の内容を書き下して慣れておくのも悪くないのではないかと思います。
今後しばらくはPyTorchをメインで使用していくつもりです。

ここで気になるのが、各ライブラリの計算速度です。
Kerasはモデルのコンパイルという動作が入るので、もしかしたら計算が早いかもしれません・・・

これについては今後同一モデルによるベンチマークを行って記事にしたいと思います。

コメントする

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です