1. 環境は、Window 10 Home (64bit) 上で行った。
2. Anaconda3 (64bit) – Spyder上で、動作確認を行った。
3. python の バージョンは、python 3.7.0 である。
4. pytorch の バージョンは、pytorch 0.4.1 である。
5. GPU は, NVIDIA社 の GeForce GTX 1050 である。
6. CPU は, Intel社 の Core(TM) i7-7700HQ である。
今回確認した内容は、現場で使える! PyTorch開発入門 深層学習モデルの作成とアプリケーションへの実装 (AI & TECHNOLOGY) の 4.5 DCGANによる画像生成(P.093 – P.104) である。
※1. プログラムの詳細は, 書籍を参考(P.093 – P.104)にして下さい.
※2. 書籍上は, 訓練した生成モデル, 識別モデルの保存については, 書かれていたが, ロードについては記載されてなかったので, 動作確認時に, 参照サイト① ~ 参照サイト② を参考に, 動作確認した.
※3. 参照サイト② (“A common PyTorch convention is to save models using either a .pt or .pth file extension.”) にあるように, 訓練した生成モデル, 識別モデルの拡張子を, “.pth” で保存したため, 書籍上の “.prm” と異なっている.
※4. 生成モデルをロードした際の動作確認は, 訓練済みの生成モデルをロードする形で行ったので, 非常に短時間(1秒未満)で, 画像の生成を確認できた.
■生成モデル, 識別モデルの確認(書籍から一部抜粋・加筆).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
# -*- coding: utf-8 -*- # 1. library import. from __future__ import print_function import torch from torch import nn, optim from torchvision.datasets import ImageFolder from torchvision import transforms from torch.utils.data import DataLoader import time, os # 2. prepare DataLoader. start = time.time() folder_path = os.path.expanduser('~') folder_path = folder_path + '\\.spyder-py3\\pytorch\\oxford-102' ~(略)~ print('-img_data size----------------------------------') print(len(img_data)) # 3. build an image generation model. nz = 100 ngf = 32 # Input: torch.Size([64, 100, 1, 1]) # Output: torch.Size([64, 3, 64, 64]) class GNet(nn.Module): def __init__(self): super().__init__() # 次元の計算について, 公式サイト通りとする. # ex. # kernel_size = (2, 3) ならば, kernel_size[0] = 2, kernel_size[1] = 3 # kernel_size = 5 ならば, kernel_size[0] = 5, kernel_size[1] = 5 # padding = (3, 5) ならば, padding[0] = 3, padding[1] = 5 # padding = 2 ならば, padding[0] = 2, padding[1] = 2 # stride = (4, 5) ならば, stride[0] = 4, stride[1] = 5 # stride = 3 ならば, stride[0] = 3, stride[1] = 3 # dilation は, ここでは, 初期値 (1, 1) を 使用するため, dilation[0] = 1, dilation[1] = 1 # output_padding は, ここでは, 初期値 0 を 使用するため, output_padding[0] = 0, output_padding[1] = 0 # Conv2d # Input: (N, Cin, Hin, Win) # Output: (N, Cout, Hout, Wout) # Hout = (Hin + 2 × padding[0] − dilation[0] × (kernel_size[0] − 1) − 1) / stride[0] + 1 # Wout = (Win + 2 × padding[1] − dilation[1] × (kernel_size[1] − 1) − 1) / stride[1] + 1 # ConvTranspose2d # Input: (N, Cin, Hin, Win) # Output: (N, Cout, Hout, Wout) # Hout = (Hin − 1) × stride[0] − 2 × padding[0] + kernel_size[0] + output_padding[0] # Wout = (Win − 1) × stride[1] − 2 × padding[1] + kernel_size[1] + output_padding[1] self.main = nn.Sequential( # 1. ConvTranspose2d # Input: (64, 100, 1, 1) # Hout = (1 - 1) × 1 - 2 × 0 + 4 + 0 = 4 # Wout = (1 - 1) × 1 - 2 × 0 + 4 + 0 = 4 # Output: (64, 256, 4, 4) nn.ConvTranspose2d(nz, ngf * 8, 4, stride = 1, padding = 0, bias = False), ~(略)~ # 5. ConvTranspose2d # Input: (64, 32, 32, 32) # Hout = (32 - 1) × 2 - 2 × 1 + 4 + 0 = 64 # Wout = (32 - 1) × 2 - 2 × 1 + 4 + 0 = 64 # Output: (64, 3, 64, 64) nn.ConvTranspose2d(ngf, 3, 4, stride = 2, padding = 1, bias = False), nn.Tanh() ) def forward(self, x): out = self.main(x) return out # 4. build an identification model. ndf = 32 # Input: torch.Size([64, 3, 64, 64]) # Output: torch.Size([64]) class DNet(nn.Module): def __init__(self): super().__init__() # 次元の計算について, 公式サイト通りとする. # ex. # kernel_size = (2, 3) ならば, kernel_size[0] = 2, kernel_size[1] = 3 # kernel_size = 5 ならば, kernel_size[0] = 5, kernel_size[1] = 5 # padding = (3, 5) ならば, padding[0] = 3, padding[1] = 5 # padding = 2 ならば, padding[0] = 2, padding[1] = 2 # stride = (4, 5) ならば, stride[0] = 4, stride[1] = 5 # stride = 3 ならば, stride[0] = 3, stride[1] = 3 # dilation は, ここでは, 初期値 (1, 1) を 使用するため, dilation[0] = 1, dilation[1] = 1 # output_padding は, ここでは, 初期値 0 を 使用するため, output_padding[0] = 0, output_padding[1] = 0 # Conv2d # Input: (N, Cin, Hin, Win) # Output: (N, Cout, Hout, Wout) # Hout = (Hin + 2 × padding[0] − dilation[0] × (kernel_size[0] − 1) − 1) / stride[0] + 1 # Wout = (Win + 2 × padding[1] − dilation[1] × (kernel_size[1] − 1) − 1) / stride[1] + 1 # ConvTranspose2d # Input: (N, Cin, Hin, Win) # Output: (N, Cout, Hout, Wout) # Hout = (Hin − 1) × stride[0] − 2 × padding[0] + kernel_size[0] + output_padding[0] # Wout = (Win − 1) × stride[1] − 2 × padding[1] + kernel_size[1] + output_padding[1] self.main = nn.Sequential( # 1. Conv2d # Input: (64, 3, 64, 64) # Hout = (64 + 2 × 1 − 1 × (4 − 1) − 1) / 2 + 1 = 32 # Wout = (64 + 2 × 1 − 1 × (4 − 1) − 1) / 2 + 1 = 32 # Output: (64, 32, 32, 32) nn.Conv2d(3, ndf, 4, stride = 2, padding = 1, bias = False), nn.LeakyReLU(0.2, inplace = True), ~(略)~ # 5. Conv2d # Input: (64, 256, 4, 4) # Hout = (4 + 2 × 0 − 1 × (4 − 1) − 1) / 1 + 1 = 1 # Wout = (4 + 2 × 0 − 1 × (4 − 1) − 1) / 1 + 1 = 1 # Output: (64, 1, 1, 1) nn.Conv2d(ndf * 8, 1, 4, stride = 1, padding = 0, bias = False), ) def forward(self, x): out = self.main(x) return out.squeeze() # 5. create a training function. ~(略)~ # RuntimeError: Expected object of type torch.FloatTensor # but found type torch.cuda.FloatTensor for argument #2 'weight' # -> .to("cuda:0") is necessary at dummy data. # gnet_test_input = torch.ones(64, 100, 1, 1) gnet_test_input = torch.ones(64, 100, 1, 1).to("cuda:0") gnet_output_size = g(gnet_test_input).size() print() print('-gnet input --------------------------------------') print(gnet_test_input.size()) print('-GNet---------------------------------------------') print(g) print('-gnet output--------------------------------------') print(gnet_output_size) print() # RuntimeError: Expected object of type torch.FloatTensor # but found type torch.cuda.FloatTensor for argument #2 'weight' # -> .to("cuda:0") is necessary at dummy data. # dnet_test_input = torch.ones(64, 3, 64, 64) dnet_test_input = torch.ones(64, 3, 64, 64).to("cuda:0") dnet_output_size = d(dnet_test_input).size() print('-dnet input --------------------------------------') print(dnet_test_input.size()) print('-DNet---------------------------------------------') print(d) print('-dnet output--------------------------------------') print(dnet_output_size) # 9. display processing time. end = time.time() print('--------------------------------------------------') print('Elapsed Time: ' + str(end - start) + "[sec]") |
■実行結果.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
-img_data size---------------------------------- 8189 -gnet input -------------------------------------- torch.Size([64, 100, 1, 1]) -GNet--------------------------------------------- GNet( (main): Sequential( (0): ConvTranspose2d(100, 256, kernel_size=(4, 4), stride=(1, 1), bias=False) (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): ReLU(inplace) (3): ConvTranspose2d(256, 128, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False) (4): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (5): ReLU(inplace) (6): ConvTranspose2d(128, 64, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False) (7): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (8): ReLU(inplace) (9): ConvTranspose2d(64, 32, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False) (10): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (11): ReLU(inplace) (12): ConvTranspose2d(32, 3, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False) (13): Tanh() ) ) -gnet output-------------------------------------- torch.Size([64, 3, 64, 64]) -dnet input -------------------------------------- torch.Size([64, 3, 64, 64]) -DNet--------------------------------------------- DNet( (main): Sequential( (0): Conv2d(3, 32, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False) (1): LeakyReLU(negative_slope=0.2, inplace) (2): Conv2d(32, 64, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False) (3): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (4): LeakyReLU(negative_slope=0.2, inplace) (5): Conv2d(64, 128, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False) (6): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (7): LeakyReLU(negative_slope=0.2, inplace) (8): Conv2d(128, 256, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False) (9): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (10): LeakyReLU(negative_slope=0.2, inplace) (11): Conv2d(256, 1, kernel_size=(4, 4), stride=(1, 1), bias=False) ) ) -dnet output-------------------------------------- torch.Size([64]) -------------------------------------------------- Elapsed Time: 0.3280467987060547[sec] |
■生成モデル, 識別モデルの訓練~画像生成(書籍から一部抜粋・加筆).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# -*- coding: utf-8 -*- # 1. library import. from __future__ import print_function import torch from torchvision.utils import save_image from torch import nn, optim from torchvision.datasets import ImageFolder from torchvision import transforms from torch.utils.data import DataLoader from statistics import mean from tqdm import tqdm import time, os # 2. prepare DataLoader. start = time.time() folder_path = os.path.expanduser('~') folder_path = folder_path + '\\.spyder-py3\\pytorch\\oxford-102' ~(略)~ # 8. train dcgan. for epoch in range(101): loss = train_dcgan(g, d, opt_g, opt_d, img_loader) print('log_loss_g = ' + str(loss[0]) + ' log_loss_d = ' + str(loss[1])) # save the learning result every ten iterations. if epoch % 10 == 0: # SAVING AND LOADING MODELS # https://pytorch.org/tutorials/beginner/saving_loading_models.html # -> A common PyTorch convention is # to save models using either a .pt or .pth file extension. # torch.save(g.state_dict(), folder_path + "\\g_{:03d}.prm".format(epoch), pickle_protocol = 4) # torch.save(d.state_dict(), folder_path + "\\d_{:03d}.prm".format(epoch), pickle_protocol = 4) torch.save(g.state_dict(), folder_path + "\\g_{:03d}.pth".format(epoch), pickle_protocol = 4) torch.save(d.state_dict(), folder_path + "\\d_{:03d}.pth".format(epoch), pickle_protocol = 4) # save images generated from z. generated_img = g(fixed_z) save_image(generated_img, folder_path + "\\{:03d}.jpg".format(epoch)) # 9. display processing time. end = time.time() print('--------------------------------------------------') print('Elapsed Time: ' + str(end - start) + "[sec]") |
■実行結果.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
100%|██████████| 128/128 [01:03<00:00, 2.03it/s] log_loss_g = 2.2799523485591635 log_loss_d = 0.4408646282972768 100%|██████████| 128/128 [01:01<00:00, 2.19it/s] log_loss_g = 2.5616258112713695 log_loss_d = 0.3513457009103149 100%|██████████| 128/128 [00:59<00:00, 2.17it/s] ~(略)~ 100%|██████████| 128/128 [00:57<00:00, 2.26it/s] log_loss_g = 3.2437106231227517 log_loss_d = 0.20105345878982916 100%|██████████| 128/128 [00:58<00:00, 2.25it/s] log_loss_g = 3.300489889457822 log_loss_d = 0.2834379920968786 100%|██████████| 128/128 [00:58<00:00, 2.23it/s] log_loss_g = 3.283644081093371 log_loss_d = 0.23608757741749287 -------------------------------------------------- Elapsed Time: 5991.337444782257[sec] |
■訓練済み生成モデルのロード~画像生成(書籍から一部抜粋・加筆).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# -*- coding: utf-8 -*- # 1. library import. from __future__ import print_function import torch from torchvision.utils import save_image from torch import nn from torchvision.datasets import ImageFolder from torchvision import transforms from torch.utils.data import DataLoader import time, os # 2. prepare DataLoader. start = time.time() folder_path = os.path.expanduser('~') folder_path = folder_path + '\\.spyder-py3\\pytorch\\oxford-102' ~(略)~ # 5. declare variables for monitoring. fixed_z = torch.randn(batch_size, nz, 1, 1).to("cuda:0") # 6. load model. g = GNet().to("cuda:0") g.load_state_dict(torch.load(folder_path + "\\g_100.pth")) # 7. save image. generated_img = g(fixed_z) save_image(generated_img, folder_path + "\\test_image_from_load_model.jpg") # . display processing time. end = time.time() print('--------------------------------------------------') print('Elapsed Time: ' + str(end - start) + "[sec]") |
■実行結果.
1 2 |
-------------------------------------------------- Elapsed Time: 0.11433911323547363[sec] |
■以上の実行結果から, 以下のことが分かった.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
1. 生成モデル, 識別モデルの確認. RuntimeError: Expected object of type torch.FloatTensor but found type torch.cuda.FloatTensor for argument #2 'weight' -> 注意点として, ダミーデータで確認するときに, 上記のような error が出力される場合があった. -> 本件は, ダミーデータ(Tensor) に, .to("cuda:0") を設定することで回避できた. 2. 生成モデル, 識別モデルの訓練. ① epoch 300 で, 約17185秒 かかった ※1. こちらは, 画像生成失敗したときの結果であるため, 割愛した. ※2. 画像生成失敗の原因は, コーディング誤りであった. 具体的には, 2つのオプティマイザーに渡すネットワーク情報を, 両方とも識別モデルのパラメータを渡していたためである. Elapsed Time: 17184.786625623703[sec] ② epoch 101 で, 約5992秒 かかった. Elapsed Time: 5991.337444782257[sec] 3. 生成画像の成否確認. epoch 0 で, いったん出力された画像を確認されることをお勧めする. 下記のように, 画像生成が上手くいく場合は, epoch 0 でも, 何かしら変化が分かるからである. epoch 300 で, 画像生成失敗したときは, 下記のように, epoch 0 で, 真っ黒な画像が出力され, 以降 の epochでも, 同様に, 真っ黒な画像が出力された. 4. 生成画像の比較. 以下のように, epoch 50 で, 十分綺麗な画像が生成されたことが分かった. 5. 訓練済み生成モデルのロード. epoch 100 の画像を, ごく短時間(1秒未満)で, 画像の生成を確認できた. |
3. 画像生成失敗時(epoch 0)
4-1. 画像生成成功時(epoch 0)
4-2. 画像生成成功時(epoch 50)
4-3. 画像生成成功時(epoch 100)
5. 訓練済み生成モデルによる画像生成
■参照サイト
【参照URL①】Saving torch models
【参照URL②】SAVING AND LOADING MODELS
■参考書籍
現場で使える! PyTorch開発入門 深層学習モデルの作成とアプリケーションへの実装 (AI & TECHNOLOGY)