ResNet18の構造をやんわりと理解する


<!-- 発端や概要を記載 -->
最近ResNet18を使用する機会があり、かつ構造を理解することを迫られたので、色々調べていると以下の表にたどり着きました。
<a id="#Table"></a>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2338148/0a3787c8-02bd-fccf-3304-dde3ac8dea04.png)
- [Deep Residual Learning for Image Recognition](https://arxiv.org/pdf/1512.03385.pdf)より引用

なるほどなるほどこうなっているのね...
入力がこれで出力がこれで...__???いっちょんわからん...__
7×7や3×3の隣にある数字は何?計算してもoutput sizeにならない...そもそも各層がどのようにつながっているのかわからない...
と多くの疑問を持ったので色々調べたことからこの表を読み解いていこうと思います。
# 目次
<!-- タイトルとアンカー名を編集 -->
1. [そもそもResNetとは?](#そもそもResNetとは?)
1. [表を読み解く](#表を読み解く)
1. [終わりに](#終わりに)
1. [参考文献](#参考文献)

<!-- 各チャプター -->
# そもそもResNetとは?
ResNetがCNNの一つであるというのはconvやらpoolやらが前出の表に出てきていることからもお分かりかと思います。まずCNNをよくわかっていないという方は[こちら](https://qiita.com/icoxfog417/items/5fd55fad152231d706c2) の記事がわかりやすかったので読むことをお勧めします。
本来CNNは理論的には層が深いほどより高次の特徴を捉えることができ精度が高まるとされています。しかし層を深くすると学習が進まないという問題 (学習が進まないといえば勾配消失問題が思い浮かびますが、これはdegradation problemらしい) があり、それを解決したのが残差(Residual)を足し合わせる処理があるResidual Network通称ResNetです。具体的には
input→
↓
畳み込み層
↓
畳み込み層
↓
output + input ←
のように畳み込み層を通った値と通る前の値を足し合わせます。この外側のinputの流れをshortcutと呼び、表の
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2338148/154500b1-fbe5-55f3-bc4f-7740da5b01fb.png)
のようなブロックごとにshortcutが作られています。

# 表を読み解く
表をよく見ると基本的にはデータの流れが分かりにくいために構造が理解できなかったと思います。
そのため表を入出力のデータサイズに着目して上から読み解いていこうと思いますが、

* relu,batchnormなどの直接データサイズに関係ないものについては言及しない
* shortcutについても触れない

ので留意してください。
なおResNetはImageNetを前提にしているので入力サイズは224*224です。(余談ですがpytorchではnn.AdaptiveAvgPool2d((1, 1))を全結合層の前に入れることによって画像のサイズによらず学習ができるようになっているらしいです。)

##conv1
224*224の入力が入ると7*7のカーネル(フィルタ)、stride 2で畳み込まれます。この時入力値を上下左右3ずつpaddingするので230*230のデータを畳み込んでると考えて問題ないです。すると1~7マス目,3~9マス目...223~229マス目が畳み込まれ結果として112*112の出力が得られます。この操作を64(channel)枚のカーネルで行うため結果112*112*64のサイズの出力が得られます。

簡潔にサイズの遷移をまとめると
224*224*1
↓
(230*230*1)
↓
112*112*64
となりました。
##conv2_x
この層では最初にpooling層があります。3*3のカーネルサイズ、stride 2,padding 1なので実質114*114から縦横1~3マス目,3~9マス目...111~113マス目の範囲の最大値が抽出され結果56*56*64の出力が得られます。
次に、畳み込み層があり3*3のカーネルサイズ、stride 1,padding 1で畳み込まれます。今までのように実質58*58から縦横1~3マス目...55~57マス目の範囲が畳み込まれ、結局56*56*64の出力が得られます。
このように3*3のカーネルサイズ、stride 1,padding 1で畳み込むと入力サイズと同様な出力が得られるので今後説明は割愛します。そして、ここではこのような層が後三層続きます。

この層でも同じようにサイズの遷移をまとめると
112*112*64
↓
(114*114*64)
↓
56*56*64
↓
...
↓
56*56*64
となりました。
##conv3_x
以下畳み込み層が続きますが、[表](#Table)の説明をよく読むと各層の先頭の畳み込み層だけstrideが2であることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2338148/39048cc9-597b-7585-b7f6-c5f0d5454d5b.png)
- [Deep Residual Learning for Image Recognition](https://arxiv.org/pdf/1512.03385.pdf)より引用

また、64から128に横の数字が増えていますが、これがカーネルの枚数だと辻褄が合わないことから出力のチャネル数だということが推測でき、pytorchの実装コードを読むことで確信に変わると思います。
入力のチャネル数と出力のチャネル数が変わってもいいの??
とイメージがわかない方は[こちら](https://qiita.com/koshian2/items/885aba771190267a3a1c)を読むことでイメージの助けになると思います。

この層の先頭畳み込み層では3*3のカーネルサイズ、stride 2,padding 1出力チャネル128なので実質58*58から縦横1~3マス目,3~9マス目...55~57マス目の範囲の最大値が抽出され結果28*26*128の出力が得られます。
以下三つの畳み込み層はconv2_xの最後三層と出力チャネル以外同じなので省略してサイズの遷移をまとめると
56*56*64
↓
(58*58*64)
↓
28*28*128
↓
...
↓
28*28*128
##conv4_x
conv2_xと同じなので省略してまとめると
28*28*128
↓
...
14*14*256
##conv5_x
同上
14*14*256
↓
...
7*7*512
##avg pool及びcf(全結合層)
avg pool(Average pooling)層で7*7のそれぞれの要素の平均を取って1*1*512にしています。また、全結合層で1*1*1000にしています。1000はImageNetのクラス数です。
まとめると
7*7*512
↓
1*1*512
↓
1*1*1000
よってResNet18のすべての工程が読み解けました!!
# 終わりに
もともとは表のすべてを解説しようと思っていたのですが面d...時間がないので辞めました。ResNet34はResNet18とほぼ同じ構成、ResNet50以上はソースコードのBottleneckクラスあたりを読めば解読できると思います。
# 参考文献
- [Deep Residual Learning for Image Recognition](https://arxiv.org/pdf/1512.03385.pdf)
- [pytorchのResNetソースコード](https://github.com/pytorch/vision/blob/main/torchvision/models/resnet.py)
- [Residual Network(ResNet)の理解とチューニングのベストプラクティス](https://deepage.net/deep_learning/2016/11/30/resnet.html)
- [Why AvgPool2d instead of AdaptiveAvgPool2d for ResNet?](https://github.com/HobbitLong/CMC/issues/16)
- [具体例で覚える畳み込み計算（Conv2D、DepthwiseConv2D、SeparableConv2D、Conv2DTranspose）](https://qiita.com/koshian2/items/885aba771190267a3a1c)