ResNetを、PyTorchを使ってGoogle Colabで動かす。

# 1. はじめに
ResNetを動かす際、ImageNetを使うのが一般的である。しかし、ImageNetは、データサイズが130GB程度と大きい。このため、大規模なGPGPUも必要である。ここでは、Google Colabで、現実的に処理できる小さいデータセットで動かす。
また、Google Colabでは、GPGPUとして、Tesla K80に続く第二世代(?)のTesla T4が提供されている。T4に搭載されているTensorCoreを動かすためAPEXを使ってみた。

# 2. 環境構築
## 2.1. データセット
ImageNetのスモールデータセットのひとつであるfast.aiが作成したImageNetteを使う。imagenette-320で300MB程度であった。参考までに、imagenetteだと、1.5GB程度、imagenette-160だと100MB程度のデータサイズである。

```
!wget https://s3.amazonaws.com/fast-ai-imageclas/imagenette-320.tgz
!tar xzf imagenette-320.tgz
```
参考までに、以下のURLを見ると、さまざまなデータセットを見ることが出来る。これ以外のパスは、ソースコードを参照のこと

```
!wget https://s3.amazonaws.com/fast-ai-imageclas/
```

## 2.2. プログラム設定
PyTorchのサンプルコードは、以下で取得できる。

```
!git clone http://github.com/pytorch/examples
```
# 3. プログラム実行および調査
## 3.1. プログラム実行
以下の通りにすると実行できる。そのままでもresnet18とresnet34は動いた。ただし、10エポックしか動かしていない。デフォルト(90エポック)だと、99分かかると思われる。

```
!python3 examples/imagenet/main.py -a resnet18 -j 2 --epochs 10 imagenette-320/
```
ここで、`-j 2`は、DataLoaderのプロセス数 (num_workersに該当する) である。デフォルトは４である。

|モデル|1.0.1|1.1.0|
|:--:|:--:|:--:|
|ResNet34|719.496|733.023|
|ResNet18|631.731|605.559|

なお、resnet50は、デフォルト設定(256バッチ)ではメモリが足りず実行できない。このため、バッチサイズを128に減らした。そしたら、10エポックは、1335秒で完走した。vgg19でも同様であった。このため、大規模のモデルではバッチサイズの調整が必須である。


```
!python3 examples/imagenet/main.py -a resnet50 -j 1 --batch-size 128 --epochs 10 imagenette-320/
```

なお、メモリが不足しているときは、以下のエラーメッセージが出て止まる。なお、PyTorchで獲得しているメモリがalready allocatedである。それ以外は、ソースコードのコメント(参考資料)を参照のこと

```
RuntimeError: CUDA out of memory. Tried to allocate 392.00 MiB (GPU 0; 14.73 GiB total capacity; 12.87 GiB already allocated; 74.94 MiB free; 331.18 MiB cached)
```

## 3.2. プログラム実行(APEX)
Mixed Precision対応のAPEXを使うと、早くなるようなので試してみた。
APEXの設定は、以下の通りである。

```
!git clone https://github.com/NVIDIA/apex
!cd apex;pip3 install -v --no-cache-dir --global-option="--cpp_ext" --global-option="--cuda_ext" .
```

ImageNet用ResNetの実行は、以下の通りである。O0がFP32であり、O3が一番最適化を行ったものである。詳細は、apex/examples/imagenetを参照のこと

```
!python apex/examples/imagenet/main_amp.py -a resnet18 --b 128 --workers 4 --opt-level O0 --epochs 10 imagenette-320/
!python apex/examples/imagenet/main_amp.py -a resnet18 --b 128 --workers 4 --opt-level O3 --epochs 10 imagenette-320/
```
結果は以下の通りであった。単位は、秒である。これから見るに、ResNetで深い場合は、Mixed Precisionの効果が大きい。ただし、浅い場合は効果が小さくなる。

|モデル|O0|O1|O2|O3|
|:--:|:--:|:--:|:--:|:--:|
|ResNet50|1440.268|754.425|734.879|741.573|
|ResNet34|726.023|589.396||543.729|
|ResNet18|543.681|553.569|547.377|510.681|

PyTorch 1.1にてcudnn 7.6.0もColabで使えるのでやってみると、464.623秒(ResNet18の場合)であった。このため、cudnn 7.5.xから7.6.0に変えると1割程度性能向上するようである。
参考までに、1.1.0でもやってみたが、あまり変わらなかった。なお、バッチサイズ256で動くようだが速度は変わっていない。参考までに、700秒台で、200img/sec相当、1400秒台で、100img/sec相当である。


|モデル|O0|O1|O2|O3|O3/b256|
|:--:|:--:|:--:|:--:|:--:|:--:|
|ResNet50|1467.376|||734.716|728.141|
|ResNet34||||||
|ResNet18||||||

ResNet50での精度については以下の通りであった。ここでは、ImageNetではなくそのサブセットであるImageNetteを使っており、精度の値が良いことに注意を要する。なお、O3の場合200img/secで、O0の場合95img/secであった。

|モデル|10|20|30|40|50|
|:--:|:--:|:--:|:--:|:--:|:--:|
|O3 Top1|79.2|83.8|87.0|93.0|90.6|
|O3 Top5|96.6|96.6|99.0|98.6|98.6|
|O0 Top1|67.8|83.2|83.4|93.4|92.6|
|O0 Top5|95.6|96.6|98.4|98.2|98.4|

## 3.3. プロファイル取得
CUDAでどんな関数が使われているかは以下のコマンドを実行することで見ることが出来る。

```
!nvprof --print-gpu-summary --print-api-summary python3 examples/imagenet/main.py -a resnet18 -j 2 --epochs 1 imagenette-320/
```
出力例としては、PyTorchの[インシデント#13412](https://github.com/pytorch/pytorch/issues/13412)を参照のこと

# 4. CPUでの演算
同じexamplesのコードでCPU(m5a.xlarge (4コア32GBメモリ))で動かしてみた。1エポックで以下の時間がかかった。設定は、以下の通りである。PyTorch 1.5.1を使った。なお、resnetは、34までしかうごかず、50は、メモリ不足で途中終了した。

```bash
sudo apt update
sudo apt install python3-pip
pip3 install torch
pip3 install torchvision
```

```bash
time python3 examples/imagenet/main.py -a resnet34 --e
pochs 1 imagenette-320/
```

```bash
real    78m35.971s
user    148m25.564s
sys     9m6.955s
```

# A. 参考資料
## A.1. PyTorch
+ [PyTorch](https://pytorch.org/)
   + [pytorch/examples](https://github.com/pytorch/examples) 
   + [メモリアロケータのエラー出力](https://github.com/pytorch/pytorch/blob/v1.0.1/aten/src/THC/THCCachingAllocator.cpp#L208)

+ [NVIDIA/apex](https://github.com/NVIDIA/apex)
  + [imagenet/README.md](https://github.com/NVIDIA/apex/blob/master/examples/imagenet/README.md)
  + [apex.amp](https://nvidia.github.io/apex/amp.html#apex-amp)

## A.2. データセット
+ [fast.ai](https://www.fast.ai/)
  + [fastai/imagenette](https://github.com/fastai/imagenette)
  + [fastai/fastai](https://github.com/fastai/fastai/)
     + [fastai/fastai/datasets.py] (https://github.com/fastai/fastai/blob/4b12f40e984be63fbd664e1705802ca34f255098/fastai/datasets.py#L8)
        + fast.aiのデータセットのパスを示したコード

## A.3. さらに深い情報

+ [Benchmark Analysis of Representative Deep Neural Network Architectures](https://arxiv.org/abs/1810.00736)
  + Fig.1の識別精度と計算量のグラフを見ると、ResNetは計算量が少なくて識別率の高いモデルであることがわかる。また、AlexNet(2012)やVGG(2014)も載っているので、ResNet(2015)までのCNNモデルの進化もわかる。 
+ [ResNet in TensorFlow](https://github.com/tensorflow/models/tree/r1.13.0/official/resnet)
  + ResNet v1/v1.5/v2のモデルの違い等を記載してある。

+ [(NVIDIA)Automatic Mixed Precision for NVIDIA Tensor Core Architecture in TensorFlow](https://devblogs.nvidia.com/nvidia-automatic-mixed-precision-tensorflow/)
     + V100だが性能指標として参考になる。混合計算(FP16を用いるAMP)とXLAを使った場合の性能について記載している。
 
||バッチサイズ|混合計算|XLA|img/sec|
|:--:|:--:|:--:|:--:|:--:|
|デフォルト|128|×|×|365|
|AMP|128|○|×|780|
|AMP/XLA|128|○|○|1010|
|AMP/XLA|256|○|○|1205|

+ [(NVIDIA)NVIDIA Tesla Deep Learning Product Performance](https://developer.nvidia.com/deep-learning-performance-training-inference)
    + さまざまなフレームワークでのCNNの学習や推論のベンチマーク結果の一覧である。V100やT4の性能について記載されている。

### A.3.1. プロファイラによる測定
nvprofを使って、プロファイルをとることができる。オプションについては、マニュアルを参照願いたい。なお、プロファイラは出力多い。そのままだとブラウザがハングしかねないので一旦ファイルに出力する必要がある。
なお、APIは、libcudart相当のI/Fでの時間を示している。一方GPUは、GPUカーネルコード等のGPUでの実行時間等を示している。

```
!nvprof --print-gpu-summary python3 examples/imagenet/main.py -a resnet18 -j 2 --epochs 1 imagenette-320/ >& gpusum.log
!head -200 gpusum.log
!nvprof --print-api-summary python3 examples/imagenet/main.py -a resnet18 -j 2 --epochs 1 imagenette-320/ >& gpusum.log
!head -200 apisum.log

```
### A.3.2. モデルの追加
examplesのコードは、torchvisionのコードを使っているので、最新版を使うこと等により新たなモデルを追加することが出来る。入れ替えは以下の手順で出来る。

```
!pip uninstall torchvision
!git clone http://github.com/pytorch/vision
!cd vision;python setup.py install
```
なお、モデルのコードは[torchvision/models](https://github.com/pytorch/vision/tree/master/torchvision/models)にある

### A.3.3. EfficientNet
最近EfficientNetという効率の良いアルゴリズムが出たようである。実行は、簡単で以下のようにすればよい。なお、gastaiのデータを使って、検証しているスクリプトも提供されている。これによるとあまり良い結果が出ていないようである。[Training from scratch on Imagewoof with the efficientnet_pytorch repo](https://github.com/sdoria/EfficientNet/blob/master/Imagewoof%20from%20scratch.ipynb)

```
!git clone https://github.com/lukemelas/EfficientNet-PyTorch
!pip install efficientnet_pytorch
!cd EfficientNet-PyTorch/examples/imagenet/;python main.py /content/imagenette-320 -e -a 'efficientnet-b3' --pretrained --gpu 0 --batch-size 128
```