PyTorch 0.4.1 examples (コード解説) : 画像分類 – MNIST (ResNet)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 08/10/2018 (0.4.1)
* 本ページは、github 上の以下の pytorch/examples と keras/examples レポジトリのサンプル・コードを参考にしています:
- https://github.com/pytorch/examples/tree/master/mnist
- https://github.com/keras-team/keras/tree/master/examples
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
ResNet 概要
◆ ResNet は MSRA が提案し、ILSVRC 2015 の分類タスクで優勝したモデルです :
ImageNet Large Scale Visual Recognition Challenge 2015 (ILSVRC2015)
そして Results
ペーパーは以下 :
Deep Residual Learning for Image Recognition
K. He, X. Zhang, S. Ren and J. Sun
Abstract だけ翻訳しておきます :
ニューラルネットワークはより深くなるに従ってトレーニングがより困難になります。ネットワークのトレーニングを容易にすることが可能な residual learning フレームワークを公開します。これは前から使われていたものよりも本質的にはより深くなります。層を、learning unreferenced function の代わりに、learning residual function として明示的に再公式化 (reformulate) します。これらの residual ネットワークが最適化するのがより簡単であり、かなり増やした深さからでも精度を得られることを示す、包括的で実証的な証拠を提供します。ImageNet データセット上で 152 層まで増やした深さで residual ネットを評価します — これは VGG ネットよりも 8 倍深いですが依然として複雑の度合いは低いです。これらの residual ネットのアンサンブルは ImageNet テスト・セット上でエラー率 3.57 % を達成しています。結果は ILSVRC 2015 分類タスクにおいて1位を勝ち取りました。また 100 と 1000 層による CIFAR-10 上の解析も示します。表現の深さは多くのビジュアル認識タスクのために中心的な重要性があります。極めて深い表現のみによって、COCO 物体検知データセット上で 28% の相対的改善を得ました。deep residual ネットは ILSVRC & COCO 2015 コンペへの提示の拠り所で、そこではまた ImageNet 検知、ImageNet localization、COCO 検知、そして COCO セグメンテーションのタスクにおいて1 位を獲得しました。
◆ 幾つかの関連記事 :
- How does deep residual learning work?
- Microsoft Wins ImageNet 2015 through Feedforward LSTM without Gates
- Why is Deep Residual Learning called that way?
最後の記事については一部抜粋して翻訳しておきます :
【質問】
何故 Deep Residual Lerning はそのように呼称されるのでしょう?F(X)=H(X)-X を学習することの本質は何でしょう?
フィッシャー・ベクトル (Fischer vectors) や他の residual テクニックとの関係は?
その関係がそこから名をとってこのテクニックに命名するに十分に本質的なのは何故でしょう?【回答】
深層 Convolutional ネットワークを解析し始めた時、直感に反して、より深いネットワークはより少ないエラーを持つわけではないことを発見しました。事実、追加層は恒等写像であるにもかかわらず、エラーはより浅いネットワークよりも大きいです。これは奇妙なことです、何故なら全ての追加層が恒等層であるならば、ネットワークは少なくとも基となったネットワークと同程度の性能は保持すべきです。この問題は、バッチ正規化で効果的に取り組まれてきた勾配消失問題とは異なっていました。この奇妙な挙動をガイド的な手がかりとして、特徴マッピングを学習するために、residual を学習して元の特徴ベクトルを追加することによりネットワークをトレーニングすることを決めました。こうして、residual が 0 だとしてもネットワークは恒等写像を学習します。residual は H(x) = F(x) – x によって与えられます、ここで x は画像で F(x) は通常ネットワークが学習するマッピング(写像)です。
この大きなアイデアは、もし AlexNet, VGG あるいは GoogLeNet のような成功したネットワークがあれば、そしてそれに更なる多くの層を追加したならば、後の層で基本的な恒等写像を学習することが今や可能になります。その結果、少なくとも元のネットワークと同程度には性能が出ます。この residual 公式化 (formulation) はそれを可能にします。
結果、1000 層以上を持つネットワークをトレーニングすることが今や可能となり、そして追加された深さは認識タスクにおいてより良い性能を提供する役割を果たします。
ResNet 実装
ResNet の基本的なビルディングブロックである Plain ブロックと Bottleneck ブロックの実装は、ショートカットが少し技巧的ですが特に難しくはありません。バッチ正規化層も追加しています :
Plain ブロック
class ResidualPlainBlock(nn.Module): def __init__(self, in_channels, out_channels, stride): super(ResidualPlainBlock, self).__init__() self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False) self.bn1 = nn.BatchNorm2d(out_channels) self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=False) self.bn2 = nn.BatchNorm2d(out_channels) self.shortcut = nn.Sequential() # just identity mapping if in_channels != out_channels: # downsampling self.shortcut.add_module( 'conv', nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride, padding=0, bias=False)) self.shortcut.add_module('bn', nn.BatchNorm2d(out_channels)) def forward(self, x): out = F.relu(self.bn1(self.conv1(x)), inplace=True) out = self.bn2(self.conv2(out)) out += self.shortcut(x) return F.relu(out, inplace=True)
Bottleneck ブロック
class ResidualBottleneckBlock(nn.Module): expansion = 4 def __init__(self, in_channels, out_channels, stride): super(ResidualBottleneckBlock, self).__init__() bottleneck_channels = out_channels // self.expansion self.conv1 = nn.Conv2d(in_channels, bottleneck_channels, kernel_size=1, stride=1, padding=0, bias=False) self.bn1 = nn.BatchNorm2d(bottleneck_channels) self.conv2 = nn.Conv2d(bottleneck_channels, bottleneck_channels, kernel_size=3, stride=stride, padding=1, bias=False) self.bn2 = nn.BatchNorm2d(bottleneck_channels) self.conv3 = nn.Conv2d(bottleneck_channels, out_channels, kernel_size=1, stride=1, padding=0, bias=False) self.bn3 = nn.BatchNorm2d(out_channels) self.shortcut = nn.Sequential() # identity mapping if in_channels != out_channels: # downsampling self.shortcut.add_module('conv', nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride, padding=0, bias=False)) self.shortcut.add_module('bn', nn.BatchNorm2d(out_channels)) def forward(self, x): out = F.relu(self.bn1(self.conv1(x)), inplace=True) out = F.relu(self.bn2(self.conv2(out)), inplace=True) out = self.bn3(self.conv3(out)) out += self.shortcut(x) return F.relu(out, inplace=True)
以上