機械学習でのやらかしメモ

# はじめに
- 非常にくだらないことで躓いた経験を繰り返さないためにここに備忘録として示す

## 内容
### 全結合ディープニューラルネットワークで出力層にDropoutを置いていた
- 出力層にdropoutを置くと、p=0.5の場合、出力の半分が0になるため、途中でロスが下がらなくなる

### learning rate(lr)の決定方法
- lrをだんだん増加させていき、**train** lossが下がるlrの範囲で最もslope(loss)が大きい場所のlrを用いる[1]
    - ウェブサイトによってはvalidation, test lossをプロットしているものもある
    - しかし、test lossが最も下がるlrではtrain lossは下がらなかったため、train lossをlrの決定に用いた
- なお、バッチサイズが小さい場合はlossの振れ幅が大きすぎて、途中で減少して増加する傾向が見えない可能性がある
    - 移動平均とかのスムージングを用いて振れ幅を減らす
    - この方法を用いずに、各lrで個別に10 epochくらい試して、最もtrain lossが小さくなるものを選ぶ

### batch gradient descent(GD), mini-batch GDの混乱
- batch gradient descent
    - **データセット全部**を順伝搬->逆伝搬
        - この動作が1 epoch
- mini-batch GD
    - データセットを32個とかのmini-batchに分ける
    - mini-batchを順伝搬->逆伝搬
        - この動作が1 iteration
        - データセットが128個の場合、128/32=4なので 4 iteration = 1 epoch
    - **mini-batchをbatchと呼ぶこともあるため、混乱した**
- loss accumulation, gradient accumulation
    - GPUのメモリに格納できるデータのサイズは限られている
    - 格納できるデータ数が2で、mini-batchが32だとする場合
    - 1 iteration = (16回計算をし、そこで得られたlossとgradientを足し合わせる)
        - batch normalizationはmini-batchごとに正規化するため、非推奨
    - 最初はmini-batch GDと勘違いしていた
- 実装方法を間違えないように。自分は実装方法を以下のリンクで確認した
    - https://stackoverflow.com/questions/62067400/understanding-accumulated-gradients-in-pytorch

### どうしてもoverfitしてしまう場合
自分は複数の記事の内容を用いて次のような対策を行った
- batch normalizationを用いる(batch sizeは32以上、batch accumulationを用いる場合は非推奨)
    - activationの前(場所は諸説あり)
- dropoutを用いる
    - activationの後(場所は諸説あり)
    - **regressionではbatch normalizationかdropoutのみにする**。(どこに書いてあったか忘れた)
- バッチサイズが大きい場合は32まで**いったん**下げる
- モデルのサイズを極端に下げる
    - FCDNNの場合
        - 最悪の場合、入力、隠れ層x1、出力層のみ
        - この状態でoverfitするなら多分今のモデルでは無理
            - これ以上パラメータ数を下げられないから
            - CNNでパラメータ数を下げて実装できないか？
- regularizationを用いる。
    - L1とL2の両方を用いる。Adamを用いているからL2 regularizationはすでにできているし、L1はやらなくていいやではなくて、L1も用いる。
- データがゴミでないかを確認
    - pycaretなどのautomlを用いて、ディープでない機械学習モデルで20%とかの精度であればデータがおかしいかデータ数が少ない。
    - 自作のデータセットの場合は次元数に注意する
        - もともと次元数30のデータセットを50000用意していたが、圧倒的に足りていなかった。次元数を13に減らしたところ、90%超えの精度が出た。

### PytorchでCNNでsemantic segmentationのtransfer learningを行う場合
- 以下のライブラリを使うと便利
- https://smp.readthedocs.io/en/latest/install.html

### Pytorchでモデルを定義するときの注意点
#### インスタンスの意識
- 各レイヤー(`nn.Linear,nn.ReLU,nn.Conv2d`)はクラスで定義されており、使うときはインスタンス(`nn.Linear()`)を作る。
- インスタンスが同じものを再利用すると重みも同じ値になる。以下の例を参照
    - 全結合層が2層あるが、インスタンスが同じため、この場合は**同じ重みをもつ**。
```python
import torch.nn as nn

class Model(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc = nn.Linear(5, 5)

    def forward(self, x):
        x = self.fc(x)
        x = self.fc(x)
        return x
```
- 正しくは以下のように書く。インスタンスが異なるので、重みも異なる。
```python
import torch.nn as nn

class Model(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc1 = nn.Linear(5, 5)
        self.fc2 = nn.Linear(5, 5)

    def forward(self, x):
        x = self.fc1(x)
        x = self.fc2(x)
        return x
```
#### `nn.Sequential`におけるインスタンスの意識
- 同様に、次の書き方はnn.Sequentialの中身が同じインスタンスのため、重みが同じになってしまう。
```python
import torch.nn as nn

class Model(nn.Module):
    def __init__(self):
        super().__init__()
        self.layers = [
            nn.Linear(5, 5),
            nn.Linear(5, 5),
        ]
        self.seq = nn.Sequential(*self.layers)
    def forward(self, x):
        x = self.seq(x)
        x = self.seq(x)
        return x
```
- インスタンスを別にしようとしてこの書き方をすると今度は`torchinfo`に認識されくなる
```python
import torch.nn as nn

class Model(nn.Module):
    def __init__(self):
        super().__init__()
        self.layers = [
            nn.Linear(5, 5),
            nn.Linear(5, 5),
        ]
    def forward(self, x):
        x = nn.Sequential(*self.layers)(x)
        x = nn.Sequential(*self.layers)(x)
        return x
model = Model()
```
- 同じ構造のsequentialな層を複数作りたい場合は次のように書く
    - 条件分岐を含む場合は次の節に書くように`nn.ModuleList`を使うことを推奨
```python
layers = [
    nn.Linear(5, 5),
    nn.Linear(5, 5),
]
model = nn.Sequential(
    nn.Sequential(*layers),
    nn.Sequential(*layers),
)
```
#### `nn.Module`と`nn.ModuleList`を組み合わせるとき
- 層の構成に条件分岐を含む場合は下のように書く
```python
import torch.nn as nn

class Model(nn.Module):
    def __init__(self, use_activation=True):
        super().__init__()
        self.layers = nn.ModuleList()
        self.layers.append(nn.Linear(5, 5))
        if use_activation == True:
            self.layers.append(nn.ReLU())
        self.layers.append(nn.Linear(5, 5))

    def forward(self, x):
        for l in self.layers:
            x = l(x)
        return x
```
- `nn.ModuleList`を用いずに下のように書くと、`torchinfo`で層が認識されなくなる。`print(model)`でも同様
    - デバッグができなくなるので、自分は`torchinfo`で認識されるように書くように意識している
```python
import torch.nn as nn

class Model(nn.Module):
    def __init__(self, use_activation=True):
        super().__init__()
        self.use_activation = use_activation

    def forward(self, x):
        x = nn.Linear(5, 5)(x)
        if self.use_activation == True:
            x = nn.ReLU()(x)
        x = nn.Linear(5, 5)(x)
        return x
```
- なお、`nn.ModuleList`は以下のように呼べないので注意
```python
use_activation = True
layers = nn.ModuleList()
layers.append(nn.Linear(5, 5))
if use_activation == True:
    layers.append(nn.ReLU())
layers.append(nn.Linear(5, 5))
layers() # NotImplementedError
```
- 呼びたいなら`nn.Sequential`を使用する
```python
use_activation = True
layers = []
layers.append(nn.Linear(5, 5))
if use_activation == True:
    layers.append(nn.ReLU())
layers.append(nn.Linear(5, 5))
seq = nn.Sequential(*layers)
print(seq)
```

## デバッグするときは以下の手段を使った
### モデルが小さすぎないか精査
- モデルのサイズを先に述べたように最小にする
- 1つのデータセットにoverfitさせる[2]
    - もしoverfitできなければモデルがおかしい
        - 先に述べた出力層のdropoutに注意
        - regression問題の場合、出力層にシグモイドやReLUを用いていないか
            - 出力の範囲が絞られるため、不正確になる可能性がある
- 32個のデータセットにoverfitさせる
    - もしoverfitできなければモデルのパラメータが足りないかも
- 32,64,128,256,...とデータ数をどんどん増やしていく。train lossがだんだん下がるはず。
    - 同じ状況の論文があればそれと同じくらいのデータ数を用意する
    - あるデータ数でtrain lossが減少しなくなったらモデルをもうちょっと大きくする
### モデルが大きすぎないか精査
- モデルを用意
- lrを振って、10 epochほど試す。train lossとtest lossが同時に単調減少するlrがなければoverfitしていると思われる
    - モデルを小さくする
    - モデルの構造を変化させ、パラメータ数を下げる
        - FCDNN -> CNN
### 勾配が爆発、消失していないか
- Weights and Biasesを使って確認
    - https://stackoverflow.com/questions/69145174/when-is-one-supposed-to-run-wandb-watch-so-that-weights-and-biases-tracks-params
        - 勾配が1e9とかになっていたら爆発している。早めの段階で0になっていたら消失している
            - batch normalizationを用いる
            - sigmoidの代わりに(Leaky)ReLUを用いる
- Weights and Biasesについて
    - pytorchにtensorboardを導入する手間と同じくらいの手間で導入できる
    - 正直tensorboardより使い勝手が良いから使用をおすすめ
        - グラフがきれいに書けるように調整できる
        - プロットの種類が多い
        - ブラウザ上から見えるようになっている
            - ポートをあけたりCloudflare Tunnelの設定をする必要がない
        - 勾配やパラメータが見える

### 層の構成があっているか
`torchinfo`、`print(model)`、Weights and Biases等で設定した層がすべて表示されていることを確認

### 参考にしたサイト
[1] https://course.fast.ai/index.html
[2] https://towardsdatascience.com/tips-and-tricks-for-neural-networks-63876e3aad1a