白黒画像(1チャネル)の数字判定までは実装できるようになったので次のステップです
PyTorch お勉強シリーズ
第1回 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)(イマココ)
環境
・Windows 10
・anaconda:4.8.3
・PyTorch:1.4.0
・PyTorch_Lighgning:0.7.3
・GPU:NVIDIA Geforce GTX 1070
Cuda cuda_10.2.89_441.22_win10
CuDNN v7.6.5.32
使うデータ
巷のDeep Learning界隈御用達(?)CIFAR-10という60000枚の画像のデータセットを使います
CIFAR-10 and CIFAR-100 datasets
こんな感じの 32x32 画像とクラスラベルが付与されています
正解ラベル | 種別 |
---|---|
0 | airplane |
1 | automobile |
2 | bird |
3 | cat |
4 | deer |
5 | dog |
6 | frog |
7 | horse |
8 | ship |
9 | truck |
データのインポート
CIFAR10のデータは PyTorch のデータセットに用意されているので簡単にインポートできます
import torch, torchvision from torchvision import transforms transform = transforms.Compose([ transforms.ToTensor() ]) # CIFAR10のダウンロード train_val = torchvision.datasets.CIFAR10(root='data', train=True, download=True, transform=transform) test = torchvision.datasets.CIFAR10(root='data', train=False, download=True, transform=transform)
1枚だけ取り出して画像とクラスを確認してみます
import numpy as np import matplotlib.pyplot as plt print(train_val[0][0].shape) img = np.transpose(train_val[0][0], (1, 2, 0)) plt.imshow(img) --- torch.Size([3, 32, 32])
カエルちゃんです
train_val[0][1] --- 6
6なので、Frog 示していますね、OK
学習用、検証用にデータをランダムに分割してデータをセット
# train : val = 8 : 2 n_train = int(len(train_val) * 0.8) n_val = len(train_val) - n_train # train と val を分割 train, val = torch.utils.data.random_split(train_val, [n_train, n_val]) len(train), len(val), len(test)
(40000, 10000, 10000)
学習モデル
まずは前回(MNIST)のものをちょっと改造して、試します
毎回固定の、データの読み込みと学習ステップのクラスはそのまま流用できます
import pytorch_lightning as pl boardtag = "cifar10-" # 学習データ用クラス 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) y_label = torch.argmax(y, dim=1) acc = torch.sum(t == y_label) * 1.0 / len(t) tensorboard_logs = {boardtag+'train/train_loss': loss, boardtag+'train/train_acc': acc} # tensorboard results = {'loss': loss, 'log': tensorboard_logs} #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() tensorboard_logs = {boardtag+'val/avg_loss': avg_loss, boardtag+'val/avg_acc': avg_acc} results = {'val_loss': avg_loss, 'val_acc': avg_acc, 'log': tensorboard_logs} #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
学習モデルの層定義を CIFAR-10用(カラー画像)に畳み込み処理に改良を加えます
畳み込み層とバッチノーマライゼーションを複数層重ねてチャネルを増やしながら、最後全結合層で合体させます
(VGGの縮小版のような感じです)
# 学習データ、検証データ、テストデータクラスの継承クラス class Net(TrainNet, ValidationNet, TestNet): def __init__(self, batch_size=256): super(Net, self).__init__() self.batch_size = batch_size self.conv1 = nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3, padding=1, stride=1) self.bn1 = nn.BatchNorm2d(64) self.conv2 = nn.Conv2d(64, 128, 3, padding=1) self.bn2 = nn.BatchNorm2d(128) self.conv3 = nn.Conv2d(128, 256, 3, padding=1) self.bn3 = nn.BatchNorm2d(256) self.conv4 = nn.Conv2d(256, 512, 3, padding=1) self.bn4 = nn.BatchNorm2d(512) self.L1 = nn.Linear(2048, 10) # 10クラス分類 def forward(self, x): # 3ch > 64ch, shape 32 x 32 > 16 x 16 x = self.conv1(x) # [64,32,32] x = self.bn1(x) x = F.relu(x) x = F.max_pool2d(x, 2, 2) # [64,16,16] # 64ch > 128ch, shape 16 x 16 > 8 x 8 x = self.conv2(x) # [128,16,16] x = self.bn2(x) x = F.relu(x) x = F.max_pool2d(x, 2, 2) # [128,8,8] # 128ch > 256ch, shape 8 x 8 > 4 x 4 x = self.conv3(x) # [256,8,8] x = self.bn3(x) x = F.relu(x) x = F.max_pool2d(x, 2, 2) # [256,4,4] # 256ch > 512ch, shape 4 x 4 > 2 x 2 x = self.conv4(x) # [512,4,4] x = self.bn4(x) x = F.relu(x) x = F.max_pool2d(x, 2, 2) # [512,2,2] # 全結合層 x = x.view( -1, 2048) # [256,2048] x = self.L1(x) return x def lossfun(self, y, t): return F.cross_entropy(y, t) def configure_optimizers(self): return torch.optim.SGD(self.parameters(), lr=0.01)
# 学習モデルをインスタンス化
net = Net()
net
Net(
(conv1): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv3): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv4): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(bn4): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(L1): Linear(in_features=2048, out_features=10, bias=True)
)
エポック50 で学習開始
trainer = Trainer(max_epochs=50, gpus=1) trainer.fit(net)
学習は進んでいるようですが、検証データに対しての結果はイマイチですね
テストデータで学習結果を確認
trainer.test() ~~~ -------------------------------------------------------------------------------- TEST RESULTS {'test_acc': 0.7618164420127869, 'test_loss': 0.8731542825698853} --------------------------------------------------------------------------------
Lossはまだまだ大きいですが、テストデータで76%の正解率とのことでまぁまぁな感じです
精度向上へ向けての戦略
畳み込み層の工夫(層の数、チャネル数)や、今回導入もしているバッチノーマライゼーションがそれにあたります
他にも、コスト関数(損失関数)や重み更新の最適化アルゴリズムを変えてみるというのもありますので、やってみましょう
最適化アルゴリズム
パラメータ最適化アルゴリズムには以下のようなアルゴリズムがあります
・SGD
・Momentum SGD
・RMSprop
・Adam
いろいろ勉強したところによると、だいたい Adam を使っておけばよい、と言われたので Adamを使ってみます
def configure_optimizers(self): #return torch.optim.SGD(self.parameters(), lr=0.01) return torch.optim.Adam(self.parameters(), lr=1e-3, weight_decay=1e-3)
オレンジが Adam を使ってモノです
早い段階で学習が進んだのは分かりますが、検証/テスト結果もあんまり変わりませんねー
-------------------------------------------------------------------------------- TEST RESULTS {'test_acc': 0.7494140863418579, 'test_loss': 1.027146816253662} --------------------------------------------------------------------------------
次の精度向上戦略です
過学習の防止
過学習とは、いろいろ工夫した結果、学習データでは良い結果だが、検証やテストデータや本番ではイマイチな結果を招いてしまうあれです
対策としては、
・正則化(Regularization)
・ドロップアウト(DropOut)
・バッチノーマライゼーション(Batch Normalization)
今回はすでにバッチノーマライゼーションは取り込んでいるので、ドロップアウトも追加してみます
青が Adam + Dropout になります
少しだけ向上したように見えますが、まだまだですね
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
TEST RESULTS
{'test_acc': 0.7850586175918579,
'test_loss': 0.9156473278999329}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
逆説的には、この VGG の縮小版を模した畳み込み処理とバッチノーマライゼーションの組み合わせは、優秀であるとも言える?なんでも継ぎ足せばよいってもんじゃないですね
推論してみるぞ
まずは推論に使う画像を読み込んで、内容を確認しておきます
# 保存したパラメータの読み込み net = Net() net.load_state_dict(torch.load('pytorch_cifar10_resnet.pt')) # テストデータから推論用画像の読み込み(正解画像) import numpy as np import random import matplotlib.pyplot as plt classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck') sample = test[random.randint(0, 10000)] #sample = train[random.randint(0, 10000)] sample_label = sample[1] img = np.transpose(sample[0], (1, 2, 0)) print("正解ラベル:", classes[sample[1]]) plt.imshow(img)
カエルくんの画像です
推論して、カエルくんで出れば推論成功となります
# 推論モード net.eval() net.freeze() # 画像をモデルと形式を合わせる x = sample[0].unsqueeze(0) # 推論開始 y_predict = net(x) print(y_predict) print("推論結果: ", classes[y_predict.argmax()])
tensor(-0.6798, -3.3624, 3.0243, 0.7041, 1.7608, -0.2765, 5.3353, 0.5897,
-0.1576, -0.1895)
推論結果: frog
何回か試してみましたが、結果通り80%くらいは正解の模様です
学習済みモデルの活用
今回はいろいろなとこからのコード参考にしながら、VGGの縮小版のようなことをしましたが、自分で一からモデルを構築し、検証を繰り返すのなかなか骨の折れる作業です(何が正解か分からない)
そこで既にエライ人たちが考えて公開されているモデルを活用することができます
ファインチューニングとか転移学習とか言われるやつです
torchvision.models では、画像認識コンペティション ImageNet Large Scale Visual Recognition Competition (ILSVRC) で使用された有名なモデルを 呼び出すことができます
・AlexNet
・VGG
・ResNet
・GoogLeNet
などなど
利用する際の注意点として、画像入力のサイズの変更や正規化をしてやる必要がありますので、本家サイトを参考にしましょう
pytorch.org
あと大抵複雑なモデルをしているので、GPUが無いとキツイですし時間もかかりますので要検討事項ですね