2022年度第10回卒研セミナー(2022/06/23)

関連サイトと資料

検証用異常データの準備

  1. 共用ハードディスクからファイルchapt03-validate.mp4をコピーする。
  2. フィルダunnormal-validationsを作成する。
  3. Windows PowerShellのウィンドウを開き、コマンド「ffmpeg -i chapt03-validate.mp4 -f image2 unnormal-validations/%03d.png」を実行する。

学習用プログラム

使用したGPU処理に要した時間
GeForce RTX208021分11秒
GeForce GTX1050TiGPUのメモリ不足のため、処理が不可能

import numpy as np
import pandas as pd
import itertools
import shutil
from PIL import Image
import os
import glob
import matplotlib.pyplot as plt
import json
  
import torch
from torch import nn, utils, optim
from torchvision import transforms, models
from torch.utils.data import DataLoader
from torch.utils.data import Dataset
from sklearn.metrics import f1_score, accuracy_score
from sklearn.model_selection import train_test_split
    

# GPUを使うかどうか
USE_DEVICE = 'cuda:0' if torch.cuda.is_available() else 'cpu'
device = torch.device(USE_DEVICE)
# データがあるディレクトリ
INPUT_DIR = '../20220526/forest-path-movie-dataset-main/forest-path-movie-dataset-main/'
    

# 検証用データを使う
USE_VALIDATE = True
# 検証用データ
VALIDATIONS = 'unnormal-validations/*.png'
    

# PyTorchの内部を決定論的に設定する
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False
  
# 乱数を初期化する
np.random.seed(0)
torch.manual_seed(0)
    

# データセットの定義ファイルを読み込む
df = pd.read_csv(INPUT_DIR+'all_file2.csv')
  
# シーン毎に分割するので、groupbyして取り出す
file = []
for g in df.groupby(df.scene):
    file.append(g[1].file.values.tolist())
  
# シーン毎に学習用と評価用データに分ける 
train_X, test_X = [], []
for i in range(len(file)):
    trainX, testX = train_test_split(file[i], test_size=0.3, random_state=0)
    train_X.append(trainX)
    test_X.append(testX)
  
# 全てのシーン内のデータを繋げた配列にする
train_X = sum(train_X, [])
test_X = sum(test_X, [])
  
# ファイル名をディレクトリを含むものにする
train_X = [INPUT_DIR+f for f in train_X]
test_X = [INPUT_DIR+f for f in test_X]
    

if USE_VALIDATE:
    valid_X = glob.glob(VALIDATIONS) # 検証用データ一覧
    

class MyTransform:
    def __init__(self):
        self.data_transform = {
            'train': transforms.Compose([
                transforms.Resize((224,224)),
                transforms.ToTensor(),
                transforms.RandomErasing(p=1.0, scale=(0.3, 0.6), ratio=(0.5, 2.0), value='random'),
                transforms.Normalize(mean=[0.485, 0.456, 0.406],
                                 std=[0.229, 0.224, 0.225])
            ]),
            'val': transforms.Compose([
                transforms.Resize((224,224)),
                transforms.ToTensor(),
                transforms.Normalize(mean=[0.485, 0.456, 0.406],
                                     std=[0.229, 0.224, 0.225])
            ])
        }
    
    def __call__(self, img, phase='train'):
        return self.data_transform[phase](img)
    

# PyTorchの流儀でデータセットをクラスで定義する
class MyDataset(Dataset):
    def __init__(self, X, transform, phase):
        # 初期化 Xはファイル名のリスト
        self.X = X
        self.transform = transform
        self.phase = phase
  
    def __len__(self):
        # データセットの長さを返す
        return len(self.X)
  
    def __getitem__(self, pos):
        # posの場所にあるデータを返す
        if self.phase == 'train':
            # 学習時には、Tripletを返す
            f1 = self.X[pos] # ベースとなるファイルのパス
            f2 = self.X[np.random.randint(len(self))]
            f3 = self.X[np.random.randint(len(self))]
            f1 = Image.open(f1) # ファイルを読み込む
            f2 = Image.open(f2) # ファイルを読み込む
            f3 = Image.open(f3) # ファイルを読み込む

            if np.random.random() < 0.5: # ランダムに、ペアを選ぶ
                X1 = self.transform(f1, phase='val') # ノーマルペア
                X2 = self.transform(f2, phase='val') # ノーマルペア
                X3 = self.transform(f3, phase='train') # 異常値
            else:
                X1 = self.transform(f1, phase='train') # 異常値ペア
                X2 = self.transform(f2, phase='train') # 異常値ペア
                X3 = self.transform(f3, phase='val') # ノーマル
            return X1, X2, X3
        else:
            # 検証時にはデータファイルのみを返す
            f1 = self.X[pos] # ファイルパス
            f1 = Image.open(f1) # ファイルを読み込む
            X1 = self.transform(f1, phase='val') # tensorにする
            return X1
    

# 保存しておいたモデルを読み込む
model = models.resnet50(pretrained=False)
model.fc = nn.Linear(2048, 2)
model.load_state_dict(torch.load('../20220526/YAMA-DNN1/chapt02-model1.pth'))
  
# 出力の次元数を8次元にする
model.fc = nn.Linear(2048, 8)
model.to(device)
    

params = model.parameters() # モデル全体のトレーニング
optimizer = optim.SGD(params, lr=5e-5, momentum=0.9) # 学習率を設定
  
loss = nn.TripletMarginLoss(margin=10.0) # 損失関数を用意する
    

# 学習時と評価時のバッチサイズ
BATCH_SIZE = 16
BATCH_SIZE_VALID = 4
# 学習エポック数
NUM_EPOCHS = 3
    

# 学習用のデータセットを作る
train_ds = MyDataset(train_X, transform=MyTransform(), phase='train')
# 検証用データを使う場合
if USE_VALIDATE:
    test_ds = MyDataset(test_X + valid_X, transform=MyTransform(), phase='val')
else:
    test_ds = MyDataset(test_X, transform=MyTransform(), phase='val')
  
data_loader = DataLoader(train_ds, batch_size=BATCH_SIZE, shuffle=True)
data_loader_v = DataLoader(test_ds, batch_size=BATCH_SIZE_VALID, shuffle=False)
    

# 学習ループ
for epoch in range(NUM_EPOCHS):
    total_loss = [] # 各バッチ実行時の損失値
    model.train() # モデルを学習用に設定する
    for X1, X2, X3 in data_loader: # 画像を読み込んでtensorにする
        X1 = X1.to(device) # GPUを使うときはGPUメモリ上に乗せる
        X2 = X2.to(device) # GPUを使うときはGPUメモリ上に乗せる
        X3 = X3.to(device) # GPUを使うときはGPUメモリ上に乗せる

        # ニューラルネットワークを実行して損失値を求める
        losses = loss(model(X1), model(X2), model(X3))

        # 新しいバッチ分の学習を行う
        optimizer.zero_grad() # 一つ前の勾配をクリア
        losses.backward() # 損失値を逆伝播させる
        optimizer.step() # 新しい勾配からパラメーターを更新する

        # 損失値を保存しておく
        total_loss.append(losses.detach().cpu().numpy())

    # エポック終了時のスコアを求める
    total_loss = np.mean(total_loss) # 各バッチの損失の平均
    # エポック終了時のスコアを表示する
    print(f'epoch #{epoch}: train_loss:{total_loss}')

# 終了時のモデルを保存する
torch.save(model.state_dict(), 'chapt03-model1.pth')
    

GeForce RTX2080での動作結果

GeForce GTX1050Tiでの動作結果

評価1

# 評価データに対して実行結果を求める
with torch.no_grad():
    results = [] # 結果を入れる配列
    model.eval() # モデルを推論用に設定する
    for X in data_loader_v:
        X = X.to(device) # GPUを使うときはGPUメモリ上に乗せる
        res = model(X) # ニューラルネットワークの実行
        res = res.detach().cpu().numpy() # CPUメモリに入れてnumpy化
        results.extend(res.tolist()) # バッチ内のデータを結果に追加
  
# 最後の検証結果の、評価側の平均値を保存する
with open('chapt03-normal.json', 'w') as f:
    test_result = results[:len(test_X)]  # 実行結果の評価データ
    mean = np.mean(test_result, axis=0)  # 平均値
    std = np.std(test_result, axis=0, ddof=1)  # 標準偏差
    # 必要な情報をJSONにして保存
    jsonobj = {
        'mean':mean.astype(float).tolist(),
        'std':std.astype(float).tolist() }
    f.write(json.dumps(jsonobj))
    

chapt03-normal.json
{"mean": 
  [-1.8343112895477092, -0.922488931777048, -13.627072575536543, 15.296871670292377, 
  7.22925847714711, -10.513004684564706, -3.5175285682508517, 4.918352894614979],
 "std": 
  [0.1685449808030565, 0.15452998731547973, 0.8660358608077123, 0.9035150665678513,
  0.4539830573350091, 0.6456903330168292, 0.2434253724930038, 0.2838490809825809]}
    

評価2

if USE_VALIDATE:  # 検証用データを使う場合
    # 実行結果を散布図にして保存する
    results = np.array(results) # テストデータ+検証用データの結果
    colors = ['pink'] * len(test_X) + ['blue'] * len(valid_X) # 色
    plt.plot(figsize=(6,6))
    # 8次元を2次元分ずつ散布図にする
    plt.subplot(221)
    plt.scatter(x=results[:,0], y=results[:,1], s=20, c=colors, alpha=0.5)
    plt.subplot(222)
    plt.scatter(x=results[:,2], y=results[:,3], s=20, c=colors, alpha=0.5)
    plt.subplot(223)
    plt.scatter(x=results[:,4], y=results[:,5], s=20, c=colors, alpha=0.5)
    plt.subplot(224)
    plt.scatter(x=results[:,6], y=results[:,7], s=20, c=colors, alpha=0.5)
    plt.savefig('scatter.png') # 画像として保存
    plt.clf()
    

scatter.png

Google Colaboratoryの準備

  1. Googleドライブをブラウザで開く。
  2. 左側の項目から、「My Drive」を選択する。

  3. 右クリックして表示されるコンテキストメニューから、「新しいフォルダ」を選択する。

  4. 表示されるダイアログに作成するフォルダ名「data」を入力し、「作成」ボタンをクリックする。

  5. 作成した「data」フォルダに移動し、中央の「ここにファイルをドロップ」と表示されている箇所に「forest-path-movie-dataset-main2.zip」ファイルをドロップする。

  6. 右クリックして表示されるコンテキストメニューから、「ファイルのアップロード」を選択する。

  7. 表示されるファイルダイアログで「chapt02-model1.pth」を選択し、「開く」ボタンをクリックする。
  8. 同様の手順で、「unnormal-validations.zip」もアップロードする。
  9. 左側の項目から「新規」ボタンをクリックし、メニュー「その他」-「Google Colaboratory」を選択する。

  10. Google Colaboratoryのコードセルに、以下のプログラムをコピペし、実行ボタンをクリックする。

    from google.colab import drive
    drive.mount('/content/drive')
            

  11. 以下のようなダイアログが表示されるので、「Googleドライブに接続」ボタンをクリックする。

  12. 以下のようなWindowが表示されるので、「自分のアカウント」をクリックする。

  13. 以下のようなWindowが表示されるので、最下行の「許可」ボタンをクリックする。

  14. 先に実行したGoogle Colaboratoryのコードセルの直下に「Mounted at /content/drive」と表示される。 これは、マイドライブの下の「data」フォルダーは「/content/drive/MyDrive/data」でアクセスできることを意味している。

  15. コードセルを追加し、 コマンド「!unzip -d /content/drive/MyDrive/data /content/drive/MyDrive/data/forest-path-movie-dataset-main2.zip」を実行する。

  16. 解凍が終了したら、「forest-path-movie-dataset-main2.zip」ファイルを右クリックし、表示されるコンテキストメニューから「削除」を選択して削除する。

  17. 同様に、コードセルを追加し、 コマンド「!unzip -d /content/drive/MyDrive/data /content/drive/MyDrive/data/unnormal-validations.zip」を実行する。 解凍が終了したら、「unnormal-validations.zip」ファイルを削除する。

Google Colaboratoryによる学習

処理に要した時間
1時間25分54秒

  1. Googleドライブから、Google Colaboratoryを開く。
  2. メニュー「ランタイム」-「ランタイムのタイプを変更」を選択する。

  3. 表示されるダイアログの「ハードウェアアクセラレータ」を「GPU」に設定し、「保存」ボタンをクリックする。

  4. マイドライブをマウントする。
  5. 以下のプログラムをコードセルを追加しながら、実行していく。


  6. import numpy as np
    import pandas as pd
    import itertools
    import shutil
    from PIL import Image
    import os
    import glob
    import matplotlib.pyplot as plt
    import json
      
    import torch
    from torch import nn, utils, optim
    from torchvision import transforms, models
    from torch.utils.data import DataLoader
    from torch.utils.data import Dataset
    from sklearn.metrics import f1_score, accuracy_score
    from sklearn.model_selection import train_test_split
        

    # GPUを使うかどうか
    USE_DEVICE = 'cuda:0' if torch.cuda.is_available() else 'cpu'
    device = torch.device(USE_DEVICE)
    # データがあるディレクトリ
    INPUT_DIR = '/content/drive/MyDrive/data/forest-path-movie-dataset-main2/'
        

    # 検証用データを使う
    USE_VALIDATE = True
    # 検証用データ
    VALIDATIONS = '/content/drive/MyDrive/data/unnormal-validations/*.png'
        

    # PyTorchの内部を決定論的に設定する
    torch.backends.cudnn.deterministic = True
    torch.backends.cudnn.benchmark = False
      
    # 乱数を初期化する
    np.random.seed(0)
    torch.manual_seed(0)
        

    # データセットの定義ファイルを読み込む
    df = pd.read_csv(INPUT_DIR+'all_file2.csv')
      
    # シーン毎に分割するので、groupbyして取り出す
    file = []
    for g in df.groupby(df.scene):
        file.append(g[1].file.values.tolist())
      
    # シーン毎に学習用と評価用データに分ける 
    train_X, test_X = [], []
    for i in range(len(file)):
        trainX, testX = train_test_split(file[i], test_size=0.3, random_state=0)
        train_X.append(trainX)
        test_X.append(testX)
      
    # 全てのシーン内のデータを繋げた配列にする
    train_X = sum(train_X, [])
    test_X = sum(test_X, [])
      
    # ファイル名をディレクトリを含むものにする
    train_X = [INPUT_DIR+f for f in train_X]
    test_X = [INPUT_DIR+f for f in test_X]
        

    if USE_VALIDATE:
        valid_X = glob.glob(VALIDATIONS) # 検証用データ一覧
        

    class MyTransform:
        def __init__(self):
            self.data_transform = {
                'train': transforms.Compose([
                    transforms.Resize((224,224)),
                    transforms.ToTensor(),
                    transforms.RandomErasing(p=1.0, scale=(0.3, 0.6), ratio=(0.5, 2.0), value='random'),
                    transforms.Normalize(mean=[0.485, 0.456, 0.406],
                                     std=[0.229, 0.224, 0.225])
                ]),
                'val': transforms.Compose([
                    transforms.Resize((224,224)),
                    transforms.ToTensor(),
                    transforms.Normalize(mean=[0.485, 0.456, 0.406],
                                         std=[0.229, 0.224, 0.225])
                ])
            }
        
        def __call__(self, img, phase='train'):
            return self.data_transform[phase](img)
        

    # PyTorchの流儀でデータセットをクラスで定義する
    class MyDataset(Dataset):
        def __init__(self, X, transform, phase):
            # 初期化 Xはファイル名のリスト
            self.X = X
            self.transform = transform
            self.phase = phase
      
        def __len__(self):
            # データセットの長さを返す
            return len(self.X)
      
        def __getitem__(self, pos):
            # posの場所にあるデータを返す
            if self.phase == 'train':
                # 学習時には、Tripletを返す
                f1 = self.X[pos] # ベースとなるファイルのパス
                f2 = self.X[np.random.randint(len(self))]
                f3 = self.X[np.random.randint(len(self))]
                f1 = Image.open(f1) # ファイルを読み込む
                f2 = Image.open(f2) # ファイルを読み込む
                f3 = Image.open(f3) # ファイルを読み込む
    
                if np.random.random() < 0.5: # ランダムに、ペアを選ぶ
                    X1 = self.transform(f1, phase='val') # ノーマルペア
                    X2 = self.transform(f2, phase='val') # ノーマルペア
                    X3 = self.transform(f3, phase='train') # 異常値
                else:
                    X1 = self.transform(f1, phase='train') # 異常値ペア
                    X2 = self.transform(f2, phase='train') # 異常値ペア
                    X3 = self.transform(f3, phase='val') # ノーマル
                return X1, X2, X3
            else:
                # 検証時にはデータファイルのみを返す
                f1 = self.X[pos] # ファイルパス
                f1 = Image.open(f1) # ファイルを読み込む
                X1 = self.transform(f1, phase='val') # tensorにする
                return X1
        

    # 保存しておいたモデルを読み込む
    model = models.resnet50(pretrained=False)
    model.fc = nn.Linear(2048, 2)
    model.load_state_dict(torch.load('/content/drive/MyDrive/data/chapt02-model1.pth'))
      
    # 出力の次元数を8次元にする
    model.fc = nn.Linear(2048, 8)
    model.to(device)
        

    params = model.parameters() # モデル全体のトレーニング
    optimizer = optim.SGD(params, lr=5e-5, momentum=0.9) # 学習率を設定
      
    loss = nn.TripletMarginLoss(margin=10.0) # 損失関数を用意する
        

    # 学習時と評価時のバッチサイズ
    BATCH_SIZE = 16
    BATCH_SIZE_VALID = 4
    # 学習エポック数
    NUM_EPOCHS = 3
        

    # 学習用のデータセットを作る
    train_ds = MyDataset(train_X, transform=MyTransform(), phase='train')
    # 検証用データを使う場合
    if USE_VALIDATE:
        test_ds = MyDataset(test_X + valid_X, transform=MyTransform(), phase='val')
    else:
        test_ds = MyDataset(test_X, transform=MyTransform(), phase='val')
      
    data_loader = DataLoader(train_ds, batch_size=BATCH_SIZE, shuffle=True)
    data_loader_v = DataLoader(test_ds, batch_size=BATCH_SIZE_VALID, shuffle=False)
        

    import time
      
    time_start = time.perf_counter()
    # 学習ループ
    for epoch in range(NUM_EPOCHS):
        total_loss = [] # 各バッチ実行時の損失値
        model.train() # モデルを学習用に設定する
        for X1, X2, X3 in data_loader: # 画像を読み込んでtensorにする
            X1 = X1.to(device) # GPUを使うときはGPUメモリ上に乗せる
            X2 = X2.to(device) # GPUを使うときはGPUメモリ上に乗せる
            X3 = X3.to(device) # GPUを使うときはGPUメモリ上に乗せる
    
            # ニューラルネットワークを実行して損失値を求める
            losses = loss(model(X1), model(X2), model(X3))
    
            # 新しいバッチ分の学習を行う
            optimizer.zero_grad() # 一つ前の勾配をクリア
            losses.backward() # 損失値を逆伝播させる
            optimizer.step() # 新しい勾配からパラメーターを更新する
    
            # 損失値を保存しておく
            total_loss.append(losses.detach().cpu().numpy())
    
        # エポック終了時のスコアを求める
        total_loss = np.mean(total_loss) # 各バッチの損失の平均
        # エポック終了時のスコアを表示する
        print(f'epoch #{epoch}: train_loss:{total_loss}')
    
    # 終了時のモデルを保存する
    torch.save(model.state_dict(), '/content/drive/MyDrive/data/chapt03-model1.pth')
      
    time_end = time.perf_counter()
    time_process = time_end- time_start
    print(f'処理時間: {time_process} [s]')
        

Google Colaboratory(GPU使用)での動作結果