
【後編】PyTorchでCIFAR-10をCNNに学習させる【PyTorch基礎】
2020.08.19
【後編】PyTorchでCIFAR-10をCNNに学習させる
【前編】の続きとなります。
引き続き、PyTorch(パイトーチ)で畳み込みニューラルネットワーク(CNN)を実装していきたいと思います。
今回は、学習結果からとなります!
前編の記事はこちら
こちらの記事もオススメ!
学習結果
学習が終わりましたが、やはり「MNIST」と違って学習に時間がかかりますね!
ですが、50エポックなので数十分で終わるかと思います。(マシンスペックに依存しますが...)
訓練ロスと訓練 / テスト精度
学習によって得られた、『訓練ロス』『訓練 / テスト精度』から見てみましょう。

学習結果 (訓練Loss)

学習結果 (精度)
まだ、精度は「70%程度」と低いですが、しっかり学習できていそうですね!
畳み込み層のフィルタ
ちなみに、学習後の畳み込み層のフィルタを見てみると

学習後 のconv.1の重み (50 epoch)

学習後のconv.2の重み (50 epoch)
学習前と比べて、何かしらフィルタに模様が見えてきましたね。
よく見ると、斜め方向に対応するフィルタや、横方向に対応するフィルタが見受けられますが、まだはっきりとは分かりませんね。
またグラフを見ると、学習回数を増やせば、まだ精度は伸びそうな雰囲気があります。
「もっともっと学習を増やしてみましょう!」
…と言いたいところですが、そうなると学習に膨大な時間がかかってしまいそうです。
GPUを使ってみる
「CUDA(Compute Unified Device Architecture:クーダ)」が使用可能であれば、PyTorchでは、簡単にGPUに演算を行わせることができます。
メイン処理部の冒頭に、以下を加筆して下さい。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | if __name__ == '__main__': epoch = 300 # 今回は300エポック! loader = load_cifar10() classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck') net: MyCNN = MyCNN() criterion = torch.nn.CrossEntropyLoss() # ロスの計算 optimizer = torch.optim.SGD(params=net.parameters(), lr=0.001, momentum=0.9) """ 以下を加筆 """ # もしGPUが使えるなら使う device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') net.to(device) print(device) |
torch.cuda.is_available() は、「CUDA」が使用可能ならTrueを返すといった、GPU を使用できるかを簡単に確認できる関数です。
その後、ネットワークを to() でデバイスに投げるだけです。
しかし、これだけではエラーを吐かれてしまいます。
GPU に使用するデータセットを投げる
使用するデータセットも、GPU に投げる必要があるので、各データローダーのループに、以下のように加筆して下さい。
1 2 3 4 5 | # ... for i, (images, labels) in enumerate(loader['train']): images = images.to(device) # to GPU? labels = labels.to(device) # ... |
matplotlib で重みを描画するには
最後に、重みを描画する「matplotlib」では、CPU で描画するため、その際は逆に CPU に投げる必要があります。
以下のように書けば OK です!
1 2 3 | # ... plt.imshow(weight.data.to('cpu').numpy(), cmap='winter') # to('cpu')を追加! # ... |
これで、GPU を使用する準備が整いました!
早速学習させてみましょう!
ちなみに筆者の環境は、GPU は「NVIDIA GeForce GTX Titan Blak 6GB」で、「CUDA」はバーション9.2で動作確認をしています。
学習結果 (300エポック)
さすがGPUを使うと、目に見えて学習が早いです!
時間はしっかりと測定していませんが、1.5 ~ 2倍くらい早いです。
では早速、300エポックの学習結果を見てみましょう!

300エポックの学習結果...?
なんと、訓練精度は「約100%」になりましたが、テスト精度は「60%程度」になってしまいました。
「過学習」が起こってしまいました!
【過学習とは?】
過学習とは、このように訓練データに適応しすぎて、テストデータなどに対する性能、いわゆる汎化性能が低下してしまう現象を言います。
Dropoutを導入してみる
それでは、過学習対策としてメジャーな「Dropout」を導入してみましょう。
「Dropout」とは、簡単に言えば、学習時に一部のニューロンを、わざと非活性化させ、訓練データに適合しすぎないようにする手法です。
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 | class MyCNN(torch.nn.Module): def __init__(self): super(MyCNN, self).__init__() self.conv1 = torch.nn.Conv2d(3, # チャネル入力 6, # チャンネル出力 5, # カーネルサイズ 1, # ストライド (デフォルトは1) 0, # パディング (デフォルトは0) ) self.conv2 = torch.nn.Conv2d(6, 16, 5) self.pool = torch.nn.MaxPool2d(2, 2) # カーネルサイズ, ストライド self.dropout1 = torch.nn.Dropout2d(p=0.3) # [new] Dropoutを追加してみる self.fc1 = torch.nn.Linear(16 * 5 * 5, 120) # 入力サイズ, 出力サイズ self.dropout2 = torch.nn.Dropout(p=0.5) # [new] Dropoutを追加してみる self.fc2 = torch.nn.Linear(120, 84) self.fc3 = torch.nn.Linear(84, 10) def forward(self, x): x = f.relu(self.conv1(x)) x = self.pool(x) x = f.relu(self.conv2(x)) x = self.pool(x) x = self.dropout1(x) # [new] Dropoutを追加 x = x.view(-1, 16 * 5 * 5) # 1次元データに変えて全結合層へ x = f.relu(self.fc1(x)) x = self.dropout2(x) # [new] Dropoutを追加 x = f.relu(self.fc2(x)) x = self.fc3(x) return x |
追加してみました!
さて、結果は、どうなるでしょうか!?
実験結果 (300エポック + Dropout)

学習結果
過学習はなくなりましたね!
ただ、やはり精度は「70%程度」といったところでしょうか。
もしかしたら、これが「LeNet」の限界なのかもしれません...。
他にも「Batch Normalization(バッチ正規化)」や、「Data Augmentation(データ拡張)」などの手法を用いれば、過学習を抑制しつつ精度向上が見込めるかもしれません。
ですが、本記事ではここまでとします。
ここまできたら、もう少し深い層の、畳み込みニューラルネットワーク (DCNN: Deep Convolutional Neural Networks)を構築した方が良いでしょう。
フィルタの重みの学習結果
ちなみに、フィルタの重みの学習結果も載せておきますが、50エポックの時と、大した差はありませんね。
やや、模様が明確になったような気もします(笑)

Conv.1のフィルタ

Conv.2のフィルタ
さいごに
長丁場になりましたが、今回は、PyTorchで畳み込みニューラルネットワークを構築し、カラー画像の「CIFAR10」を学習させてみました。
また、ネットワークの内部を観察したり、いろいろな考察もしてみたので、機械学習初学者の皆さまの参考になれば幸いです。
次回は、もう少し複雑なネットワークで試してみようと考えているのでお楽しみに!
ソースコード
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 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 | import torch import torch.nn.functional as f from torch.utils.data import DataLoader from torchvision import datasets, transforms import matplotlib.pyplot as plt from tqdm import tqdm class MyCNN(torch.nn.Module): def __init__(self): super(MyCNN, self).__init__() self.conv1 = torch.nn.Conv2d(3, # チャネル入力 6, # チャンネル出力 5, # カーネルサイズ 1, # ストライド (デフォルトは1) 0, # パディング (デフォルトは0) ) self.conv2 = torch.nn.Conv2d(6, 16, 5) self.pool = torch.nn.MaxPool2d(2, 2) # カーネルサイズ, ストライド self.dropout1 = torch.nn.Dropout2d(p=0.3) # [new] Dropoutを追加してみる self.fc1 = torch.nn.Linear(16 * 5 * 5, 120) # 入力サイズ, 出力サイズ self.dropout2 = torch.nn.Dropout(p=0.5) # [new] Dropoutを追加してみる self.fc2 = torch.nn.Linear(120, 84) self.fc3 = torch.nn.Linear(84, 10) def forward(self, x): x = f.relu(self.conv1(x)) x = self.pool(x) x = f.relu(self.conv2(x)) x = self.pool(x) x = self.dropout1(x) # [new] Dropoutを追加 x = x.view(-1, 16 * 5 * 5) # 1次元データに変えて全結合層へ x = f.relu(self.fc1(x)) x = self.dropout2(x) # [new] Dropoutを追加 x = f.relu(self.fc2(x)) x = self.fc3(x) return x def plot_conv1(self, prefix_num=0): weights1 = self.conv1.weight weights1 = weights1.reshape(3*6, 5, 5) for i, weight in enumerate(weights1): plt.subplot(3, 6, i + 1) plt.imshow(weight.data.to('cpu').numpy(), cmap='winter') plt.tick_params(labelbottom=False, labelleft=False, labelright=False, labeltop=False, bottom=False, left=False, right=False, top=False) plt.savefig('img/{}_conv1.png'.format(prefix_num)) plt.close() def plot_conv2(self, prefix_num=0): weights2 = self.conv2.weight weights2 = weights2.reshape(6*16, 5, 5) for i, weight in enumerate(weights2): plt.subplot(6, 16, i + 1) plt.imshow(weight.data.to('cpu').numpy(), cmap='winter') plt.tick_params(labelbottom=False, labelleft=False, labelright=False, labeltop=False, bottom=False, left=False, right=False, top=False) plt.savefig('img/{}_conv2.png'.format(prefix_num)) plt.close() def load_cifar10(batch=128): train_loader = DataLoader( datasets.CIFAR10('./data', train=True, download=True, transform=transforms.Compose([ transforms.ToTensor(), transforms.Normalize( [0.5, 0.5, 0.5], # RGB 平均 [0.5, 0.5, 0.5] # RGB 標準偏差 ) ])), batch_size=batch, shuffle=True ) test_loader = DataLoader( datasets.CIFAR10('./data', train=False, download=True, transform=transforms.Compose([ transforms.ToTensor(), transforms.Normalize( [0.5, 0.5, 0.5], # RGB 平均 [0.5, 0.5, 0.5] # RGB 標準偏差 ) ])), batch_size=batch, shuffle=True ) return {'train': train_loader, 'test': test_loader} if __name__ == '__main__': epoch = 300 loader = load_cifar10() classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck') net: MyCNN = MyCNN() criterion = torch.nn.CrossEntropyLoss() # ロスの計算 optimizer = torch.optim.SGD(params=net.parameters(), lr=0.001, momentum=0.9) # もしGPUが使えるなら使う device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') net.to(device) print(device) # 学習前のフィルタの可視化 net.plot_conv1() net.plot_conv2() history = { 'train_loss': [], 'train_acc': [], 'test_acc': [] } for e in range(epoch): net.train() loss = None for i, (images, labels) in enumerate(loader['train']): images = images.to(device) # to GPU? labels = labels.to(device) optimizer.zero_grad() output = net(images) loss = criterion(output, labels) loss.backward() optimizer.step() if i % 10 == 0: print('Training log: {} epoch ({} / 50000 train. data). Loss: {}'.format(e + 1, (i + 1) * 128, loss.item()) ) # 学習過程でのフィルタの可視化 # net.plot_conv1(e+1) # net.plot_conv2(e+1) history['train_loss'].append(loss.item()) net.eval() correct = 0 with torch.no_grad(): for i, (images, labels) in enumerate(tqdm(loader['train'])): images = images.to(device) # to GPU? labels = labels.to(device) outputs = net(images) _, predicted = torch.max(outputs.data, 1) correct += (predicted == labels).sum().item() acc = float(correct / 50000) history['train_acc'].append(acc) correct = 0 with torch.no_grad(): for i, (images, labels) in enumerate(tqdm(loader['test'])): images = images.to(device) # to GPU? labels = labels.to(device) outputs = net(images) _, predicted = torch.max(outputs.data, 1) correct += (predicted == labels).sum().item() acc = float(correct / 10000) history['test_acc'].append(acc) # 学習前のフィルタの可視化 net.plot_conv1(300) net.plot_conv2(300) # 結果をプロット plt.plot(range(1, epoch+1), history['train_loss']) plt.title('Training Loss [CIFAR10]') plt.xlabel('epoch') plt.ylabel('loss') plt.savefig('img/cifar10_loss.png') plt.close() plt.plot(range(1, epoch + 1), history['train_acc'], label='train_acc') plt.plot(range(1, epoch + 1), history['test_acc'], label='test_acc') plt.title('Accuracies [CIFAR10]') plt.xlabel('epoch') plt.ylabel('accuracy') plt.legend() plt.savefig('img/cifar10_acc.png') plt.close() |
前編の記事はこちら
こちらの記事もオススメ!
関連記事
ライトコードよりお知らせ






一緒に働いてくれる仲間を募集しております!
ライトコードでは、仲間を募集しております!
当社のモットーは「好きなことを仕事にするエンジニア集団」「エンジニアによるエンジニアのための会社」。エンジニアであるあなたの「やってみたいこと」を全力で応援する会社です。
また、ライトコードは現在、急成長中!だからこそ、あなたにお任せしたいやりがいのあるお仕事は沢山あります。「コアメンバー」として活躍してくれる、あなたからのご応募をお待ちしております!
なお、ご応募の前に、「話しだけ聞いてみたい」「社内の雰囲気を知りたい」という方はこちらをご覧ください。
ライトコードでは一緒に働いていただける方を募集しております!
採用情報はこちら書いた人はこんな人

IT技術2021.01.26【Python】Tkinterで簡易的な電卓作ってみた!
ITエンタメ2021.01.12【スティーブ・ウォズニアック】アップルコンピューターのもう一人の創業者!
IT技術2021.01.11React Hooks登場でコンポーネントはどう変わった?
IT技術2021.01.05【Unity】Rigidbodyの基本