Pytorchによる1D-CNN,2D-CNNスクラッチ実装まとめ


#はじめに


TensorFlowからPytorchに移行して半年ほど経ったので基礎的なところをまとめておきます。
今回は以下の３つに焦点を当てたいと思います。

1. 事前学習モデルの利用
2. 1DCNNの実装
3. 2DCNNの実装

*1DCNNは簡単に説明して、2DCNNに焦点を当てたいと思います。
*理論的なことについては書いておりません。実装中心の記事です。


#1.事前学習モデルの利用

現在使える事前学習済モデルは以下のモデルです。

- AlexNet
- VGG
- ResNet
- SqueezeNet
- DenseNet
- Inception v3
- GoogLeNet
- ShuffleNet v2
- MobileNet v2
- ResNeXt
- Wide ResNet
- MNasNet


[詳しくはこちら](https://pytorch.org/docs/stable/torchvision/models.html)

ImageNetでの学習済モデルを使用する際は以下のように使います。

```.py
import torchvision
model = torchvision.models.alexnet(pretrained=True)
```
- `pretrained=True`にしないとImageNetでの学習済の重みはロードされません。
- デフォが`pretrained=False`になっているので注意してください。

- モデルの構造を確認したければ`print(model)`で確認出来ます。以下が実行結果です。

```.py
AlexNet(
  (features): Sequential(
    (0): Conv2d(3, 64, kernel_size=(11, 11), stride=(4, 4), padding=(2, 2))
    (1): ReLU(inplace=True)
    (2): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
    (3): Conv2d(64, 192, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
    (4): ReLU(inplace=True)
    (5): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
    (6): Conv2d(192, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (7): ReLU(inplace=True)
    (8): Conv2d(384, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (9): ReLU(inplace=True)
    (10): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (11): ReLU(inplace=True)
    (12): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (avgpool): AdaptiveAvgPool2d(output_size=(6, 6))
  (classifier): Sequential(
    (0): Dropout(p=0.5, inplace=False)
    (1): Linear(in_features=9216, out_features=4096, bias=True)
    (2): ReLU(inplace=True)
    (3): Dropout(p=0.5, inplace=False)
    (4): Linear(in_features=4096, out_features=4096, bias=True)
    (5): ReLU(inplace=True)
    (6): Linear(in_features=4096, out_features=1000, bias=True)
  )
)
```

もし自分のデータでクラス分類したいときは、以下のように変更します。
２クラス分類を例にします。

```.py
model.classifier[6].out_features = 2
```

もう一度`print(model)`を実行すると変わっているのが確認出来ます。

```.py
AlexNet(
  (features): Sequential(
    (0): Conv2d(3, 64, kernel_size=(11, 11), stride=(4, 4), padding=(2, 2))
    (1): ReLU(inplace=True)
    (2): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
    (3): Conv2d(64, 192, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
    (4): ReLU(inplace=True)
    (5): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
    (6): Conv2d(192, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (7): ReLU(inplace=True)
    (8): Conv2d(384, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (9): ReLU(inplace=True)
    (10): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (11): ReLU(inplace=True)
    (12): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (avgpool): AdaptiveAvgPool2d(output_size=(6, 6))
  (classifier): Sequential(
    (0): Dropout(p=0.5, inplace=False)
    (1): Linear(in_features=9216, out_features=4096, bias=True)
    (2): ReLU(inplace=True)
    (3): Dropout(p=0.5, inplace=False)
    (4): Linear(in_features=4096, out_features=4096, bias=True)
    (5): ReLU(inplace=True)
    (6): Linear(in_features=4096, out_features=2, bias=True)
  )
)
```

#2. 1DCNNの実装


それでは本題に入ります。
今回は1DのCNNをスクラッチで実装します。
簡単な例です。




```.py
import torch
import torch.nn as nn


class Net1D(nn.Module):
    def __init__(self):
        super(SimpleNet,self).__init__()

        self.conv1 = nn.Conv1d(1, 8,kernel_size=3, stride=1)
        self.bn1 = nn.BatchNorm1d(8)
        self.relu = nn.ReLU()
        self.maxpool = nn.MaxPool1d(kernel_size=3, stride=2)

        self.conv2 = nn.Conv1d(8, 16,kernel_size=3, stride=1)
        self.bn2 = nn.BatchNorm1d(16)
        self.conv3 = nn.Conv1d(16,64,kernel_size=3, stride=1)
        self.gap = nn.AdaptiveAvgPool1d(1)
        self.fc = nn.Linear(64,2)


    def forward(self,x):

        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)
        x = self.maxpool(x)

        x = self.conv2(x)
        x = self.bn2(x)
        x = self.relu(x)
        x = self.maxpool(x)

        x = self.conv3(x)
        x = self.gap(x)
        x = x.view(x.size(0),-1)
        x = self.fc(x)
 
        return x


```
もし、このモデルがうまく出来ているか確認するなら以下のように試してください。

```.py

model = SimpleNet()
in_data = torch.randn(8,1,50)
out_data = model(data)
print(out_size.size()) #torch.Size([8, 2])
```

- `torch.randn()`で適当な入力データを用意します。←このやり方便利です！2Dでも応用出来ます!
- 入力は`torch.randn(バッチサイズ、チャネル数、１次元配列の大きさ)`です。
出力のサイズが`torch.Size([8, 2])`となっているのは、`torch.Size(バッチサイズ、最後の出力)`と言うことです。
- 分類タスクをやりたければこの後に、softmaxを通してあげれば良いです。


- また、`torchsummary`と言う特徴マップの大きさを確認できる便利なライブラリがあるのでそちらも使ってみてください。以前に記事を書いたのでリンクを貼っておきます。

===============================================================

####[Pytorchでモデル構築するとき、torchsummaryがマジ使える件について](https://qiita.com/tatsuya11bbs/items/bfd8f163dd918ea04432)

===============================================================


#####【nn.Conv1d】
```.py
nn.Conv1d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros')
```

| parameters       |      概要 |  
|:-----------------:|:-----------------:|
| in_channels   |      入力のチャネル数。 | 
| out_channels   |       畳み込みをした後のチャネル数。フィルター数。 |   
| kernel_size          |      カーネルの大きさ。|       
| stride             |     カーネルをどのくらい移動させるか。|     
| padding   　 |パディングの大きさ。1を指定すると両端に挿入するので２だけ大きくなる。デフォは0。|  
| dilation       | フィルターの間の空間を変更。atrous convなどで利用。 |      
|groups|  デフォは1。数を大きくすると計算コスト削減。  |
|bias| biasを含むかどうか。デフォはTrue|
|　padding_mode　　|パディングのモード。デフォは0。|


#####【nn.BatchNorm1d】
```.py
nn.BatchNorm1d(num_features, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
```
`num_features`はひつつ前のレイヤーの`out_channels`の値と同じ数を入れれば大丈夫です。











#3. 2DのCNN

簡単なCNNのサンプルを書きました。
今回フィルター数やカーネルサイズは適当に決めています。
自作でネットワークを作成する場合は、考慮しながら値を決めてください。


```.py

import torch
import torch.nn as nn

class Net2D(nn.Module):
    def __init__(self):
        super(Net,self).__init__()

        self.conv1 = nn.Conv2d(3,16,kernel_size=3,stride=2)
        self.bn1 = nn.BatchNorm2d(16)
        self.relu = nn.ReLU()
        self.maxpool = nn.MaxPool2d(2)
        self.conv2 = nn.Conv2d(16,32,kernel_size=3,stride=2)
        self.bn2 = nn.BatchNorm2d(32)
        self.conv3 = nn.Conv2d(32,64,kernel_size=3,stride=2)
        self.gap = nn.AdaptiveAvgPool2d(1)
        self.fc1 = nn.Linear(64,32)
        self.fc2 = nn.Linear(32,2)


    def forward(self,x):

        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)
        x = self.maxpool(x)

        x = self.conv2(x)
        x = self.bn2(x)
        x = self.relu(x)
        x = self.maxpool(x)

        x = self.conv3(x)
        x = self.gap(x)
        x = x.view(x.size(0),-1)
        x = self.fc1(x)
        x = self.fc2(x)
        return x

```

- 自作モデルを作る際は、`nn.Module`を継承する必要があります。
- 基本的には、`init`で使用する層を定義します。
パラメタがあるものを`init`に、パラメタがないものは`forward`に定義するような記事をよく見ますが、`print(model)`の際にreluなどが表示されないので、私はパラメタがないreluのようなものでも今回のように`init`に定義します。

- `forward`のほうで、モデルの構造を決めます。


#####【nn.Conv2d】

```.py

nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros')
```
| parameters       |      概要 |  
|:-----------------:|:-----------------:|
| in_channels   |      入力のチャネル数。RGBの画像では3になる。 | 
| out_channels   |       畳み込みをした後のチャネル数。フィルター数。 |   
| kernel_size          |      カーネルの大きさ。|       
| stride             |     カーネルをどのくらい移動させるか。|     
| padding   　 |パディングの大きさ。1を指定すると両端に挿入するので２だけ大きくなる。デフォは0。|  
| dilation       | フィルターの間の空間を変更。atrous convなどで利用。 |      
|groups|  デフォは1。数を大きくすると計算コスト削減。  |
|bias| biasを含むかどうか。デフォはTrue|
|　padding_mode　　|パディングのモード。デフォは0。|



#####【nn.BatchNorm2d】

```.py
nn.BatchNorm2d(num_features, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
```

- バッチ正規化は、バッチ内の要素ごとに平均と標準偏差を求めます。
畳み込みのときは、バッチ内のチャネルの対して正規化します。全結合層のときはユニットになります。

- 他にも、`Layer Norm`、`Instance Norm`、`Group Norm`などがあるので気になるのがある方は検索してみてください。


#####【nn.ReLU】

```.py
nn.ReLU(inplace=False)
```
- (x) = max(0,x)
- ReLUは活性化関数です。他には、`ReLU6`、`RReLU`、`SELU`、`CELU`、`Sigmoid`などあります。

#####【nn.MaxPool2d】

```.py
nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)
```

特徴を強調されるためにプーリング層を使います。

- 大きく分けて２パターンの方法があるので以下で確認します。

①poolのサイズが正方形のとき

```.py
m = nn.MaxPool2d(3, stride=2)  #(pool of square window of size=3, stride=2)
```

②poolの大きさをカスタマイズしたいとき

```.py
m = nn.MaxPool2d((3, 2), stride=(2, 1)) #(pool of non-square window)
```

#####【nn.AdaptiveMaxPool2d】


```.py
nn.AdaptiveMaxPool2d(output_size, return_indices=False)
```

よく、`Global Max Pooling`と呼ばれるものです。
各チャネルを値一つにしてくれるので、全結合層につなぐ前によく使われます。
`output_size`に１チャネルのアウトプットサイズを入れます。
`output_size=1`が使うことが多いと思います。


#####【nn.Linear】

```.py

nn.Linear(in_features, out_features, bias=True)
```

in_features, out_featuresを指定して使います。
全結合層を実装したときは、こちらを使います。


#終わりに


Pytorchに移行して半年ぐらい経ちますが、非常に使いやすいです。
今回の記事が少しでも参考になればと思います。


#参考文献

- [Pytorch master documentation](https://pytorch.org/docs/stable/nn.html)

- [Batch normalization effectiveness_20190206](https://www.slideshare.net/artimpact/batch-normalization-effectiveness20190206)