マイブログ リスト

医療言語処理講座

2019年2月23日土曜日

Pyゼミ3.06 ChainerのTrainer Extensionを使ってみる

Chainerを使った学習(訓練)時の進捗状況を見てみよう


Keyword:DICOM,Chainer,Trainer, Extension,Extend

これまでPyゼミ3.04,3.05ではニューラルネットワークについて説明してきました。
そして学習時に精度と損失の変化を観察して,学習の進捗状況を知る方法を知っています。

本章では学習の進捗状況をグラフ化したり,学習を検証したりする方法を説明します。



学習の進捗を見る


Pytゼミ3.05など既に学習の進捗を見るTrainerのextend関数を使ってきました。
既に使ってきた3つの関数を見てみましょう。

LogReportは各エポックごとに損失や精度などをログに記録します。
ログファイルはresultディレクトリの中に作成されます。
    trainer.extend(extensions.LogReport())

PrintReportは引数で指定した内容(ここでは,エポック数,損失と精度)をターミナルに表示ます。
    trainer.extend(extensions.PrintReport(['epoch' , 'main/loss' , 'main/accuracy']))

ProgressBarは,学習の進捗状況を進捗バーを用いてパーセンテージで表示します。
大量のデータを使って長時間学習するような場合にどこまで,進んだのか進捗を見るのに便利です。
また,1エポックにかかる時間や終了までにかかる時間を示すので,時間のかかる学習には便利です。
     trainer.extend(extensions.ProgressBar())


学習データを訓練用と検証用に分けて学習する


学習中に過学習を起こしていないか確認するために学習データの一部を検証データにします。
学習に用いるもとのデータはtrainに格納されています。
このデータを学習用と検証用の2つに分けます。
データを8割を学習用trnに,残り2割を検証用にvldに,split_dataset_randomを使ってランダムに振り分けます。
seedは乱数のシードで,毎回,同じシード値を用いることで同じ乱数系が生成されます。
つまり毎回同じ組み合わせのtrnとvldのデータが生成されます。
    trn, vld = split_dataset_random(train, int( len(train)*0.8 ), seed = 0) 

学習用trnと検証用vldそれぞれのiteratorを作成します。   
    t_iterator = iterators.SerialIterator(trn, batch_size, shuffle = True)
    v_iterator = iterators.SerialIterator(vld, batch_size, repeat = False, shuffle = False)

検証用のデータを評価に用いるために次の一行を追加します。
    trainer.extend(extensions.Evaluator(v_iterator, self.model))  


学習の進捗と検証データの評価を確認する。


PrintReportに'validation/main/accuracy'を追加することで,学習の進捗と同時に検証の結果もターミナルに表示することができます。
    trainer.extend(extensions.PrintReport(['epoch', 'main/loss', 'main/accuracy', 'validation/main/accuracy']))

進捗状況をグラフにプロットする


学習や検証の進捗状況をPlotReportを使ってグラフにプロットしてリアルタイムで参照することができます。

損失の変化がloss.pngという画像ファイルとしてresultディレクトリ内に記録されます。
画像ファイルは1エポックごとに更新されます。
loss.pngを表示しておくと,エッポックごとに損失の変化を観察することができます。
    trainer.extend(extensions.PlotReport(['main/loss', 'validation/main/loss'], x_key='epoch', file_name='loss.png'))

精度の変化はaccuracy.pngに記録されます。
    trainer.extend(extensions.PlotReport(['main/accuracy', 'validation/main/accuracy'], x_key='epoch', file_name='accuracy.png'))


プログラムを入力実行してみよう。


下に示すプログラムはPyゼミ3.05のAlexnetの一部(先に説明した部分,doTrain関数内)を変更しました。
違いを確かめながら入力し実行してみましょう。



import os, sys
import numpy as np
import cv2
import chainer
from chainer import Function, report, training, utils, Variable
from chainer import datasets, iterators, optimizers, serializers
from chainer import Link, Chain, ChainList
import chainer.functions as F
import chainer.links as L
from chainer.datasets import tuple_dataset
from chainer.training import extensions
from chainer.datasets import split_dataset_random #new
import pydicom

def constractTrainData( fname , resz ):
    image = []
    label = []
    for i, line in enumerate(open(fname, 'r')):
        data = line.split(",")          # 画像ファイル名と正解データに分割
        print("Train",i, data[0], "Label#",data[1])
        img = readDicom2png(data[0], "tmp.png")
        if resz > 0:                    #画像のリサイズ        
            img = cv2.resize(img,(resz, resz))        
        img = img.astype(np.float32)    
        img = (img - 128)/128           #画素値0-255を-1.0から+1.0に変換
        image.append([img])
                
        t = np.array(int(data[1]), dtype=np.int32)
        label.append(t)
               
    train = tuple_dataset.TupleDataset(image, label)        
    return train
    
def readDicom2png(dcmfnm, tmpfnm):
    ds  = pydicom.read_file(dcmfnm)      
    wc  = ds.WindowCenter                
    ww  = ds.WindowWidth                         
    img = ds.pixel_array                         
    max = wc + ww / 2                      
    min = wc - ww / 2      
    img = 255 * (img - min)/(max - min)   
    img[img > 255] = 255                  
    img[img < 0  ] = 0                    
    img = img.astype(np.uint8)
    cv2.imwrite(tmpfnm, img)
    return img   

def getLabelName(fname):
    lblnm = []
    for line in open(fname, 'r'):
        data = line.split(",")
        lblnm.append(data[1].strip())
        #lblnm[int(data[0])]= data[1]   
    print("LabelName:",lblnm)              
    return len(lblnm), lblnm
        
class Alexnet(Chain):  ### AlexNet ###
    def __init__(self, n_label, n_channel=1):
        super(Alexnet, self).__init__(
            conv1 = L.Convolution2D(n_channel, 96, 11, stride=4),
            conv2 = L.Convolution2D(96, 256, 5, pad=2),
            conv3 = L.Convolution2D(256, 384, 3, pad=1),
            conv4 = L.Convolution2D(384, 384, 3, pad=1),
            conv5 = L.Convolution2D(384, 256, 3, pad=1),
            fc6 = L.Linear(None, 4096),
            fc7 = L.Linear(4096, 4096),
            fc8 = L.Linear(4096, n_label),
        )

    def __call__(self, x):
        h = F.max_pooling_2d(F.local_response_normalization(
            F.relu(self.conv1(x))), 3, stride=2)
        h = F.max_pooling_2d(F.local_response_normalization(
            F.relu(self.conv2(h))), 3, stride=2)
        h = F.relu(self.conv3(h))
        h = F.relu(self.conv4(h))
        h = F.max_pooling_2d(F.relu(self.conv5(h)), 2, stride=2)
        h = F.dropout(F.relu(self.fc6(h)))
        h = F.dropout(F.relu(self.fc7(h)))
        return self.fc8(h)

class Classifier():
    def __init__(self, mdlnm, nlbl):
        model = Alexnet(nlbl)
        self.model = L.Classifier(model,lossfun=F.softmax_cross_entropy)
        self.opt   = optimizers.Adam()
        self.opt.setup(self.model)        
        self.modelName = mdlnm       
            
    def doTrain(self, nLbl, train, batch_size, n_epoch, modelName):
        trn, vld = split_dataset_random(train, int(len(train)*0.8), seed=0)       
        t_iterator = iterators.SerialIterator(trn, batch_size, shuffle=True)
        v_iterator = iterators.SerialIterator(vld, batch_size, repeat=False, shuffle=False)
        updater  = training.StandardUpdater(t_iterator, self.opt)
        trainer  = training.Trainer(updater, (n_epoch, 'epoch'), out='result')
 
        trainer.extend(extensions.Evaluator(v_iterator, self.model))                 
        trainer.extend(extensions.LogReport())
        trainer.extend(extensions.PrintReport(['epoch', 'main/loss', 'main/accuracy', 'validation/main/accuracy']))
        trainer.extend(extensions.ProgressBar())
        trainer.extend(extensions.PlotReport(['main/loss', 'validation/main/loss'], x_key='epoch', file_name='loss.png'))
        trainer.extend(extensions.PlotReport(['main/accuracy', 'validation/main/accuracy'], x_key='epoch', file_name='accuracy.png'))
       
        trainer.run()
                
        print("学習モデル",self.modelName,"を保存します... .   .")
        chainer.serializers.save_hdf5( self.modelName, self.model )
        print("\t... .  .学習モデルを保存しました")

if __name__=='__main__':
    trainfnm = "./TrainTestList/TrainData.0.3.csv"
    labelfnm = "./TrainTestList/labelName.csv"
    resize   = 128       #画像リサイズ 0のときそのままの画素サイズ
    modelnm  = "Alexnet.0.3.hdf5"  #モデル保存名    
    batch_size = 10  # バッチサイズ
    n_epoch    = 30  # エポック数
    
    nLbl, lblName = getLabelName(labelfnm)
    train = constractTrainData(trainfnm, resize)
    print("\ntrain\n") 
    print("画像データ数:",len(train),"画像/ラベル:",len(train[0]))
    print("画像Chanel数:",len(train[0][0]),"高さ:",len(train[0][0][0]),"幅:",len(train[0][0][0][0]))
    
    dl = Classifier(modelnm, nLbl)
    dl.doTrain(nLbl, train, batch_size, n_epoch, modelnm)



実行結果


プログラムを実行すると,最初にラベル名のファイルを読み込みLungなど4つのラベル名を表示します。

その後DICOM画像を読み込み,画像ファイル名とラベル番号が表示されます。

そして,学習が始まります。
今回,検証用のvalidation/main/accuracyの項目の値も一緒に表示されています。


$ python␣dicomAlexnetTrainExtd.py⏎
LabelName: ['Lung', 'Abdomen', 'Chest', 'Head']
Train 0 ../dcmdir2/Lung/W5305890 Label# 0
Train 1 ../dcmdir2/Lung/K5303593 Label# 0
Train 2 ../dcmdir2/Lung/R5305640 Label# 0
Train 3 ../dcmdir2/Lung/T5304000 Label# 0
Train 4 ../dcmdir2/Lung/A5304343 Label# 0
Train 5 ../dcmdir2/Lung/D5304468 Label# 0
     ・・・・・
Train 137 ../dcmdir2/Head/D5221046 Label# 3
Train 138 ../dcmdir2/Head/F5221140 Label# 3
Train 139 ../dcmdir2/Head/G5221187 Label# 3
Train 140 ../dcmdir2/Head/Q5221671 Label# 3
Train 141 ../dcmdir2/Head/E5221093 Label# 3

train

画像データ数: 73 画像/ラベル: 2
画像Chanel数: 1 高さ: 128 幅: 128
epoch       main/loss   main/accuracy  validation/main/accuracy
1           3.06279     0.341667       0.481481                  
2           1.19522     0.536364       0.725926                  
3           0.943893    0.6            0.725926                  
4           0.729782    0.675          0.7                       
5           0.612126    0.7            0.488889                 
     ・・・・・
26          0.0378268   0.981818       1                         
27          0.00758042  1              1                         
28          0.0108063   0.990909       1                         
29          0.0802395   0.981818       1                         
30          0.0146818   0.990909       1                      
学習モデル Alexnet.0.3.hdf5 を保存します... .   .
 ... .  .学習モデルを保存しました          


resultディレクトリの中に精度と損失の変化を記録した画像ファイルが保存されます。
学習の進捗をグラフに自動的にプロットできるのはとても助かります。


精度の変化を観察できる。


損失の変化を観察できる。


まとめ


TrainerのExtensionを使った学習の進捗を観察する方法を説明しました。
Extensionにはまだたくさんの機能が提供されています。
たとえば,学習途中のスナップショットを保存したり,学習率をコントロールしたりすることができます。
いろいろ調べて使いこなせるようになると,より最適な学習が可能になると思います。

0 件のコメント:

コメントを投稿