YOLOを、計る

# はじめに
Object Detection の手法である YOLO では、これまでさまざまなモデルが発表されてきましたが、[YOLOv7] の論文（以下、[論文]と言います）では、代表的な YOLO のパラメータ数、 計算量（flops）、FPS (Frame per Second)、精度を一挙に掲載しており、それによって YOLO 間での比較が可能になっています。
こういった比較を手元でできないかと試行しましたので、ここに結果をまとめます。実際に測定した値（以下、実測値と言います）と[論文]に記載された値（以下、論文値と言います）を比較することで、その実測手法の妥当性を検証したいと思います。

# YOLO を計る
それでは、実際に、推論時間（FPS）、精度、計算量、パラメータ数の順に計測していきます。

## 比較対象
[論文] Table 2 の表にあるモデルのうち、PPYOLOE を除く総てのモデルに対し、FPS (推論時間の逆数)、精度、 計算量（flops）、パラメータ数を測定します。
![table2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629790/828f1762-b118-00f6-8f47-9af37c2ac0a3.png)

## 推論時間（FPS）を計る
推論時間の計測は、Web 記事「[YOLOv7はYOLOシリーズで最強か【第二版】]」で詳細に記載しましたが、Colab Pro で V100 を使って 10 回測定し、平均を算出しました。論文値を X 軸、実測値を Y 軸として比較したのが以下です。破線上に乗っていれば、論文値と実測値が一致することを意味します。ここでは、破線上に乗っているモデルもあれば、外れているものもあります。

![論文値との比較(FPS).JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629790/c304f7c0-d441-7957-8893-9bdb02571a49.jpeg)

この結果を解釈するため、Colab Pro ( GPU: Tesla T4) 上で、[YOLOv7] のモデルを 100 回実行した結果を測定し、グラフ化したのが以下です。X 軸は試行回、Y 軸は推論時間 (単位：ミリ秒) です。最初の数回は時間がかかりますが、以降は比較的安定した挙動を示しています。ただ、50 回目以降でも値が乱れたり、実行のたびに挙動が違って見えたりします。この結果は Tesla T4 上で測定したものですが、V100 でも同様に値が暴れていた可能性があります。このために、10 回試行の平均をとっても、なお、値がうまくとれないものがあるのだと思います。この傾向は、[YOLOv7] だけではなく、他の YOLO でも同様でしたので、YOLO の特性の問題ではなく、クラウド環境での測定の問題と思われます。

![Trials.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629790/6b7e0046-fb36-ab29-403e-25fb15da0487.jpeg)

このように、クラウド環境である Colab Pro で性能測定をする際には、値が暴れることを考慮し、適当な回数を実行し、最小値をとるなど、工夫する必要があります。

## 精度を計る
各 YOLO ともに、MS COCO データセットの val2017 で AP (Average Precision) をとります。`--conf` は 0.001、`--iou` は 0.65 で総ての YOLO で統一します。例えば、[YOLOv7] では以下のように実行します。

なお、Web 記事「[YOLOv7はYOLOシリーズで最強か【第二版】]」では、各 YOLO での FP16 や Fusion の扱い方が違うため、それらの組み合わせで推論速度を比較しました。しかし、精度に対しては、FP16 や Fusion はほとんど影響を与えないことを確認済みですので、各 YOLO ともデフォルトのやり方でモデルを作成します。

~~~python
!python test.py --data data/coco.yaml --img 640 --batch 32 --conf 0.001 --iou 0.65 --device 0 --weights yolov7.pt --name yolov7_640_val
~~~

そうすると、以下のように結果が編集されて出力されます。各 YOLO 比較には、赤枠で囲った部分の数値を使います。

![精度出力.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629790/1c9915b4-0492-2a0f-2831-8cfb08abf1a1.jpeg)

各 YOLO に対して精度実測を行った結果を論文値と比較したのが以下です。YOLOv7-tiny と [YOLOR] シリーズは破線上に乗っておらず、論文値と異なった結果になっています。

![論文値との比較(精度).JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629790/f0d85133-2d36-2046-68cf-af6f12a8a2e9.jpeg)

YOLOv7-tiny は、[論文]では SiLU を活性化関数で使用していると記載されていますが、事前学習モデルで使用したものは、Leaky ReLU を使用しています。精度の違いはこのあたりにあるのかもしれません。

[YOLOR] シリーズに対して[論文]の精度が再現できていませんが、[YOLOR github] (大きいモデルは、こちらの [github](https://github.com/WongKinYiu/yolor/tree/paper)) では、実測値と完全に一致する値が掲載されています。[論文]では、[YOLOR github] とは違う Weights を使ったのかもしれません。

|YOLOR モデル |論文値 |YOLOR<BR>github|実測値|
|--|--|--|--|
|YOLOR-CSP|50.8%|49.2%|49.2%|
|YOLOR-CSP-X|52.7%|51.1%|51.1%|
|YOLOR-P6|53.5%|52.5%|52.5%|
|YOLOR-W6|54.8%|54.0%|54.0%|
|YOLOR-E6|55.7%|54.6%|54.6%|
|YOLOR-D6|56.1%|55.4%|55.4%|

## 計算量、パラメータ数を計る
計算量、パラメータ数は、[thop] パッケージの profile で測定します。profile にモデルと画像スケルトンを入力するとその戻りが計算量 (flops) とパラメータ数 (params) になりますので、それぞれ、ギガ、メガのスケールに修正します。ここで、flops に 2 をかけていますが、その理由は[この記事](https://github.com/ultralytics/yolov5/issues/1664)で説明されています。実際のコードは「[参考：コード](#参考コード)」を参照願います。

~~~python
# flops, params計算
device = select_device('')
weights, img_sz = 'yolov7.pt', 640
model = attempt_load(weights, map_location=device) 
input = torch.zeros((1, 3, img_sz, img_sz), device=next(model.parameters()).device)
flops, params = profile(deepcopy(model), inputs=(input, ))
flops = flops / 1E9 * 2
params /= 1E6
~~~

このやり方で対象の YOLO モデルに対して計算を行い、論文値と比較したのが以下です。

![論文値との比較(G_P).JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629790/96c830a7-f23d-edcd-a8d0-71ca069d299c.jpeg)

YOLOv7-D6 のみ外れています。精度や FPS は実測値とあっていますので、[論文]での記載ミスかもしれません。

## 測定結果
最後に、上記手法によって測定した実測値を、論文値と並べて表にまとめました。
![論文値との比較.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629790/866dc2db-2b3b-c5a3-df90-814a713f99e5.jpeg)

# まとめ
以上により、YOLO を FPS、精度、計算量、パラメータ数で計る方法を確認できました。FPS (or 推論時間) はクラウド環境では、測定に注意が必要なこともわかりました。

なお、上記実行にあたっては、いくつかの問題に遭遇しました。以下のコードでは、それら問題の対策も含め、記載しています。

# 参考：コード
ここでは、各 YOLO での、インストール、精度測定、計算量・パラメータ数測定について、コードを記載します。
FPS 測定については、Web 記事「YOLOv7はYOLOシリーズで最強か【第二版】」で詳細に記載していますので、そちらをご参照ください。

## [YOLOv7]
### インストール

~~~python
import os
os.chdir('/content')

!git clone https://github.com/WongKinYiu/yolov7.git > /dev/null
os.chdir('yolov7')
!pip3 install -U pip && pip3 install -r requirements.txt > /dev/null
~~~

### 精度測定
以下のように実行します。ここで、`--img`はモデルにあわせ、640、あるいは 1280 を指定します。`--conf 0.001 --iou 0.65` は全 YOLO で共通です。

~~~python
!python test.py --data data/coco.yaml --img 640 --batch 32 --conf 0.001 --iou 0.65 --device 0 --weights yolov7-tiny.pt --name yolov7-tiny_640_val
~~~
この場合、以下のような出力になります。

~~~
Namespace(augment=False, batch_size=32, conf_thres=0.001, data='data/coco.yaml', device='0', exist_ok=False, img_size=640, iou_thres=0.65, name='yolov7-tiny_640_val', no_trace=False, project='runs/test', save_conf=False, save_hybrid=False, save_json=True, save_txt=False, single_cls=False, task='val', v5_metric=False, verbose=False, weights=['yolov7-tiny.pt'])
YOLOR 🚀 v0.1-115-g072f76c torch 1.12.1+cu113 CUDA:0 (Tesla T4, 15109.75MB)

Downloading https://github.com/WongKinYiu/yolov7/releases/download/v0.1/yolov7-tiny.pt to yolov7-tiny.pt...
100% 12.1M/12.1M [00:00<00:00, 27.8MB/s]

Fusing layers... 
Model Summary: 200 layers, 6219709 parameters, 6219709 gradients
 Convert model to Traced-model... 
/usr/local/lib/python3.7/dist-packages/torch/_tensor.py:1083: UserWarning: The .grad attribute of a Tensor that is not a leaf Tensor is being accessed. Its .grad attribute won't be populated during autograd.backward(). If you indeed want the .grad field to be populated for a non-leaf Tensor, use .retain_grad() on the non-leaf Tensor. If you access the non-leaf Tensor by mistake, make sure you access the leaf Tensor instead. See github.com/pytorch/pytorch/pull/30531 for more informations. (Triggered internally at  aten/src/ATen/core/TensorBody.h:477.)
  return self._grad
 traced_script_module saved! 
 model is traced! 


WARNING: Dataset not found, nonexistent paths: ['/content/yolov7/coco/val2017.txt']
Downloading bash ./scripts/get_coco.sh ...
Downloading https://github.com/ultralytics/yolov5/releases/download/v1.0/coco2017labels-segments.zip  ...
Downloading http://images.cocodataset.org/zips/train2017.zip ...
Downloading http://images.cocodataset.org/zips/val2017.zip ...
Downloading http://images.cocodataset.org/zips/test2017.zip ...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0  % T ota l    % Received % Xferd  Average Speed %  Ti Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0me    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100  168M  100  168M    0     0  23.2M      0  0:00:07  0:00:07 --:--:-- 21.5M
100  777M  100  777M    0     0  43.0M      0  0:00:18  0:00:18 --:--:-- 44.1M
100 6339M  100 6339M    0     0  42.2M      0  0:02:30  0:02:30 --:--:-- 31.6M
100 18.0G  100 18.0G    0     0  37.1M      0  0:08:16  0:08:16 --:--:-- 47.3M
Dataset autodownload success

/usr/local/lib/python3.7/dist-packages/torch/functional.py:478: UserWarning: torch.meshgrid: in an upcoming release, it will be required to pass the indexing argument. (Triggered internally at  ../aten/src/ATen/native/TensorShape.cpp:2894.)
  return _VF.meshgrid(tensors, **kwargs)  # type: ignore[attr-defined]
val: Scanning 'coco/val2017' images and labels... 4952 found, 48 missing, 0 empty, 0 corrupted: 100% 5000/5000 [00:12<00:00, 408.92it/s]
val: New cache created: coco/val2017.cache
               Class      Images      Labels           P           R      mAP@.5  mAP@.5:.95: 100% 157/157 [01:09<00:00,  2.27it/s]
                 all        5000       36335       0.646        0.51       0.545        0.36
Speed: 2.5/1.7/4.2 ms inference/NMS/total per 640x640 image at batch-size 32

Evaluating pycocotools mAP... saving runs/test/yolov7-tiny_640_val/yolov7-tiny_predictions.json...
loading annotations into memory...
Done (t=0.95s)
creating index...
index created!
Loading and preparing results...
DONE (t=6.05s)
creating index...
index created!
Running per image evaluation...
Evaluate annotation type *bbox*
DONE (t=74.28s).
Accumulating evaluation results...
DONE (t=13.78s).
 Average Precision  (AP) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.374
 Average Precision  (AP) @[ IoU=0.50      | area=   all | maxDets=100 ] = 0.552
 Average Precision  (AP) @[ IoU=0.75      | area=   all | maxDets=100 ] = 0.403
 Average Precision  (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.191
 Average Precision  (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.418
 Average Precision  (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.526
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=  1 ] = 0.311
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets= 10 ] = 0.519
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.571
 Average Recall     (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.356
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.632
 Average Recall     (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.745
/usr/local/lib/python3.7/dist-packages/torch/_tensor.py:1083: UserWarning: The .grad attribute of a Tensor that is not a leaf Tensor is being accessed. Its .grad attribute won't be populated during autograd.backward(). If you indeed want the .grad field to be populated for a non-leaf Tensor, use .retain_grad() on the non-leaf Tensor. If you access the non-leaf Tensor by mistake, make sure you access the leaf Tensor instead. See github.com/pytorch/pytorch/pull/30531 for more informations. (Triggered internally at  aten/src/ATen/core/TensorBody.h:477.)
  return self._grad
Results saved to runs/test/yolov7-tiny_640_val
~~~

### 計算量・パラメータ数測定

~~~python
import torch
from thop import profile
from models.experimental import attempt_load
from utils.torch_utils import select_device
from copy import deepcopy
import numpy as np
import os

weights_list = ['yolov7-tiny.pt', 
                'yolov7.pt',
                'yolov7x.pt',
                'yolov7-w6.pt',
                'yolov7-e6.pt',
                'yolov7-d6.pt',
                'yolov7-e6e.pt']

img_sz_list = [640, 640, 640, 1280, 1280, 1280, 1280]

device = select_device('')
for weights, img_sz in zip(weights_list, img_sz_list):

    model_name = weights.split('.')[0]
   
    model = attempt_load(weights, map_location=device) 
    input = torch.zeros((1, 3, img_sz, img_sz), device=next(model.parameters()).device)
    flops, params = profile(deepcopy(model), inputs=(input, ))
    flops = flops / 1E9 * 2
    params /= 1E6
    print(f'model_name: flops = {flops}, params = {params}')
~~~

## [YOLOv5]
### インストール

~~~python
import os
os.chdir('/content')

!git clone https://github.com/ultralytics/yolov5  # clone
os.chdir('yolov5')
!pip install -r requirements.txt  # install
~~~

### 精度測定
以下のように実行します。ここで、`--img`はモデルにあわせ、640、あるいは 1280 を指定します。conf はデフォルトで 0.001 となっているため、指定を省略しています。`--iou 0.65` は全 YOLO で共通です。

~~~python
!python val.py --weights yolov5n.pt --data coco.yaml --img 640 --iou 0.65
~~~

### 計算量・パラメータ数測定

~~~python
import torch
from thop import profile
from utils.torch_utils import select_device
from copy import deepcopy
from models.yolo import DetectionModel
import yaml
import numpy as np
import os

weights_list = ['yolov5n.pt', 'yolov5s.pt', 'yolov5m.pt', 'yolov5l.pt', 'yolov5x.pt', 
                'yolov5n6.pt', 'yolov5s6.pt', 'yolov5m6.pt', 'yolov5l6.pt', 'yolov5x6.pt', ]

img_sz_list = [640, 640, 640, 640, 640, 1280, 1280, 1280, 1280, 1280]

device = select_device('')
for weights, img_sz in zip(weights_list, img_sz_list):

    model_name = weights.split('.')[0]
   
    # model = model = DetectMultiBackend(weights, device=device, dnn=False, data='data/coco128.yaml', fp16=True)

    addon_path = 'hub/' if model_name[-1] == '6' else ''
    yaml_path = '/content/yolov5/models/' + addon_path + model_name + '.yaml'
    with open(yaml_path, 'r') as yml:
        config = yaml.safe_load(yml)

    model = DetectionModel(config)

    input = torch.zeros((1, 3, img_sz, img_sz), device=next(model.parameters()).device)
    # gflop_dict[model_name] = profile(model, inputs=(input, ))
    flops, params = profile(deepcopy(model), inputs=(input, ))
    flops = flops / 1E9 * 2
    params /= 1E6
    print(f'model_name: flops = {flops}, params = {params}')
~~~

## [YOLOR (yolor_csp、yolor_csp_x、yolor_p6)]

### インストール

~~~python
os.chdir('/content')
import os

!git clone https://github.com/WongKinYiu/yolor
os.chdir('yolor')
!sed -i '30s/^pycocotools/# pycocotools/' requirements.txt

# pip install required packages
!pip install -qr requirements.txt

# install mish-cuda if you want to use mish activation
# https://github.com/thomasbrandon/mish-cuda
# https://github.com/JunnYu/mish-cuda
!git clone https://github.com/JunnYu/mish-cuda
os.chdir('mish-cuda')
!python setup.py build install
os.chdir('..')

# install pytorch_wavelets if you want to use dwt down-sampling module
# https://github.com/fbcotter/pytorch_wavelets
!git clone https://github.com/fbcotter/pytorch_wavelets
os.chdir('pytorch_wavelets')
!pip install .
os.chdir('..')
~~~
上記で requirements.txt に対して sed をして、pycocotools をコメントアウトしました。Colab では、pycocotools はすでに 2.0.5 (2022年10月10日現在) になっています。この行を実行すると2.0.0 にダウングレードされてしまい、そのバージョンに内在する問題のために後続の処理でエラーが発生します。それを回避するためこの対応を行っています。 

### 事前学習モデルの取得
画像サイズ640 向けの事前学習モデル（yolor_csp.pt、yolor_csp_star.pt）と画像サイズ1280向けの事前学習モデルのうち yolor_p6.ptは、[YOLOR github] からダウンロードできます。

### 精度測定
テスト実行前に以下を実行し、後続での問題発生を回避します。
~~~python
!sed -i '97s/for pred in o:/for pred in o.cpu().numpy():/' utils/plots.py
!sed -i '1adownload: bash ./scripts/get_coco.sh' data/coco.yaml
~~~
テストは以下のように実行します。ここで、`--img`はモデルにあわせ、640、あるいは 1280 を指定します。`--conf 0.001 --iou 0.65` は全 YOLO で共通です。
~~~python
!python test.py --data data/coco.yaml --img 640 --batch 32 --conf 0.001 --iou 0.65 --device 0 --cfg cfg/yolor_csp.cfg --weights yolor_csp.pt --name yolor_csp_val
~~~

この場合、以下のような出力になります。
~~~
Namespace(augment=False, batch_size=32, cfg='cfg/yolor_csp.cfg', conf_thres=0.001, data='data/coco.yaml', device='0', exist_ok=False, img_size=640, iou_thres=0.65, name='yolor_csp_val', names='data/coco.names', project='runs/test', save_conf=False, save_json=True, save_txt=False, single_cls=False, task='val', verbose=False, weights=['yolor_csp.pt'])
Using torch 1.7.0 CUDA:0 (Tesla T4, 15109MB)

Model Summary: 529 layers, 52923994 parameters, 52923994 gradients, 121.027788800 GFLOPS

WARNING: Dataset not found, nonexistent paths: ['/content/coco/val2017.txt']
Downloading bash ./scripts/get_coco.sh ...
Downloading https://github.com/ultralytics/yolov5/releases/download/v1.0/coco2017labels.zip  ...
Downloading http://images.cocodataset.org/zips/train2017.zip ...
Downloading http://images.cocodataset.org/zips/val2017.zip ...
Downloading http://images.cocodataset.org/zips/test2017.zip ...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100 46.3M  100 46.3M    0     0  19.2M      0  0:00:02  0:00:02 --:--:-- 23.9M
100  777M  100  777M    0     0  37.1M      0  0:00:20  0:00:20 --:--:-- 45.3M
100 6339M  100 6339M    0     0  36.2M      0  0:02:54  0:02:54 --:--:-- 46.4M
100 18.0G  100 18.0G    0     0  44.6M      0  0:06:52  0:06:52 --:--:-- 47.1M
Dataset autodownload success

Scanning images: 100% 5000/5000 [00:10<00:00, 489.22it/s]
Scanning labels ../coco/labels/val2017.cache3 (4952 found, 0 missing, 48 empty, 0 duplicate, for 5000 images): 5000it [00:00, 14444.56it/s]
               Class      Images     Targets           P           R      mAP@.5  mAP@.5:.95: 100% 157/157 [01:39<00:00,  1.58it/s]
                 all       5e+03    3.63e+04       0.434       0.748       0.671       0.481
Speed: 11.9/1.9/13.8 ms inference/NMS/total per 640x640 image at batch-size 32

Evaluating pycocotools mAP... saving runs/test/yolor_csp_val2/yolor_csp_predictions.json...
loading annotations into memory...
Done (t=0.90s)
creating index...
index created!
Loading and preparing results...
DONE (t=5.16s)
creating index...
index created!
Running per image evaluation...
Evaluate annotation type *bbox*
DONE (t=68.45s).
Accumulating evaluation results...
DONE (t=10.85s).
 Average Precision  (AP) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.492
 Average Precision  (AP) @[ IoU=0.50      | area=   all | maxDets=100 ] = 0.676
 Average Precision  (AP) @[ IoU=0.75      | area=   all | maxDets=100 ] = 0.537
 Average Precision  (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.329
 Average Precision  (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.544
 Average Precision  (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.630
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=  1 ] = 0.376
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets= 10 ] = 0.618
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.672
 Average Recall     (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.508
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.727
 Average Recall     (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.810
Results saved to runs/test/yolor_csp_val2
~~~

### 計算量・パラメータ数測定

~~~python
import torch
from thop import profile
from copy import deepcopy
from models.models import *
import os
import numpy as np

weights_list = [['yolor_csp.pt'],
                ['yolor_csp_x.pt'],
                ['yolor_p6.pt']]

cfg_list = ['cfg/yolor_csp.cfg',
            'cfg/yolor_csp_x.cfg',
            'cfg/yolor_p6.cfg',
            ]

img_sz_list = [640, 640, 1280]

for weights, cfg, img_sz in zip(weights_list, cfg_list, img_sz_list):
    model_name = weights[0].split('.')[0]

    with torch.no_grad():
        model = Darknet(cfg, img_sz)

        input = torch.zeros((1, 3, img_sz, img_sz), device=next(model.parameters()).device)
        flops, params = profile(deepcopy(model), inputs=(input, ))
        flops = flops / 1E9 * 2
        params /= 1E6
        print(f'model_name: flops = {flops}, params = {params}')
~~~

## [YOLOR (yolor-w6、yolor-e6、yolor-d6)]

### インストール

~~~python
import os
import shutil
os.chdir('/content')
if os.path.isdir('/content/yolor'): shutil.rmtree('/content/yolor')

!git clone -b paper https://github.com/WongKinYiu/yolor
os.chdir('yolor')
!sed -i '30s/^pycocotools/# pycocotools/' requirements.txt

# pip install required packages
!pip install -qr requirements.txt

# install mish-cuda if you want to use mish activation
# https://github.com/thomasbrandon/mish-cuda
# https://github.com/JunnYu/mish-cuda
!git clone https://github.com/JunnYu/mish-cuda
os.chdir('mish-cuda')
!python setup.py build install
os.chdir('..')

# install pytorch_wavelets if you want to use dwt down-sampling module
# https://github.com/fbcotter/pytorch_wavelets
!git clone https://github.com/fbcotter/pytorch_wavelets
os.chdir('pytorch_wavelets')
!pip install .
os.chdir('..')
~~~

requireents.txt における pycocotools の扱いは、[YOLOR (yolor_csp、yolor_csp_x、yolor_p6)] の場合と同様です。

### 事前学習モデルの取得
yolor-w6、yolor-e6、yolor-d6 の事前学習モデルは [YOLOR paper github] からダウンロードできます。

### 精度測定
テスト実行前に以下を実行し、後続での問題発生を回避します。
~~~python
!sed -i '97s/for pred in o:/for pred in o.cpu().numpy():/' utils/plots.py
!sed -i '1adownload: bash ./scripts/get_coco.sh' data/coco.yaml
~~~
テストは以下のように実行します。ここで、`--img`は 1280 を指定します。`--conf 0.001 --iou 0.65` は全 YOLO で共通です。
なお、筆者が実行した Tesla T4 では CUDA メモリ不足になったため、`--batch` を 16 とすることで、回避しました。
~~~python
!python test.py --img 1280 --conf 0.001 --iou 0.65 --batch 16 --device 0 --data data/coco.yaml --weights yolor-w6.pt --name yolor-w6_val
~~~

### 計算量・パラメータ数測定

~~~python
import torch
from thop import profile
from copy import deepcopy
from models.experimental import attempt_load
from utils.torch_utils import select_device
import os
import numpy as np

weights_list = [['yolor-w6.pt'],
                ['yolor-e6.pt'],
                ['yolor-d6.pt']]

img_sz_list = [1280, 1280, 1280]
device = select_device('')

for weights, img_sz in zip(weights_list, img_sz_list):
    model_name = weights[0].split('.')[0]

    with torch.no_grad():

        # Load model
        model = attempt_load(weights, map_location=device)

        input = torch.zeros((1, 3, img_sz, img_sz), device=next(model.parameters()).device)
        flops, params = profile(deepcopy(model), inputs=(input, ))
        flops = flops / 1E9 * 2
        params /= 1E6
        print(f'model_name: flops = {flops}, params = {params}')
~~~

## [YOLOX]
### インストール

~~~python
import os
os.chdir('/content')

!git clone https://github.com/Megvii-BaseDetection/YOLOX.git > /dev/null
os.chdir('YOLOX')
!pip3 install -U pip && pip3 install -r requirements.txt > /dev/null
!pip3 install -v -e . > /dev/null # or  python3 setup.py develop

!pip3 install cython; pip3 install git+https://github.com/cocodataset/cocoapi.git
~~~

### 事前学習モデルの取得
[YOLOX github] の Benchmark セクションにモデルの表があります。各モデルに対応する事前学習モデルを weights 欄から取得します。yolox_s の事前学習モデルのダウンロード例です。

~~~python
!wget https://github.com/Megvii-BaseDetection/YOLOX/releases/download/0.1.1rc0/yolox_s.pth
~~~

### 精度測定
事前に MS COCO データセットをインストールする必要があります。先の[YOLOR]の実行で、MS COCO データセットが /content/coco にインストールされているので、シンボリックリンクをはって流用します。自前でインストールする場合、[YOLOX github] には他の YOLO で用意されているようなスクリプトが見当たらなかったため、例えば、[YOLOv7 github] にある scripts/get_coco.sh をコピーして実行すればよいと思います。

~~~python
!ln -s /content/coco ./datasets/COCO
~~~

[YOLOX] で予定しているデータセットのパスをインストールしているものにあわせます。
~~~python
!sed -i '191s/self.data_dir, self.name/self.data_dir, "images", self.name/' yolox/data/datasets/coco.py
~~~

テストは以下のように実行します。[YOLOX] では、iou の指定は nms を使いますので、以下のようになります。
~~~python
!python -m yolox.tools.eval -n  yolox-s -c yolox_s.pth -b 64 -d 8 --conf 0.001 --nms 0.65 --fp16 --fuse --devices 1
~~~
以下のような出力がでます。
~~~
(snip)
2022-10-10 21:30:48 | INFO     | yolox.data.datasets.coco:64 - loading annotations into memory...
2022-10-10 21:30:49 | INFO     | yolox.data.datasets.coco:64 - Done (t=0.58s)
2022-10-10 21:30:49 | INFO     | pycocotools.coco:86 - creating index...
2022-10-10 21:30:49 | INFO     | pycocotools.coco:86 - index created!
2022-10-10 21:30:54 | INFO     | __main__:165 - loading checkpoint from yolox_s.pth
2022-10-10 21:30:54 | INFO     | __main__:169 - loaded checkpoint done.
2022-10-10 21:30:54 | INFO     | __main__:175 - 	Fusing model...
/usr/local/lib/python3.7/dist-packages/torch/nn/modules/module.py:390: UserWarning: The .grad attribute of a Tensor that is not a leaf Tensor is being accessed. Its .grad attribute won't be populated during autograd.backward(). If you indeed want the gradient for a non-leaf Tensor, use .retain_grad() on the non-leaf Tensor. If you access the non-leaf Tensor by mistake, make sure you access the leaf Tensor instead. See github.com/pytorch/pytorch/pull/30531 for more informations.
  if param.grad is not None:
100%|##########| 79/79 [01:27<00:00,  1.11s/it]
2022-10-10 21:32:21 | INFO     | yolox.evaluators.coco_evaluator:256 - Evaluate in main process...
2022-10-10 21:32:31 | INFO     | yolox.evaluators.coco_evaluator:289 - Loading and preparing results...
2022-10-10 21:32:34 | INFO     | yolox.evaluators.coco_evaluator:289 - DONE (t=3.39s)
2022-10-10 21:32:34 | INFO     | pycocotools.coco:366 - creating index...
2022-10-10 21:32:35 | INFO     | pycocotools.coco:366 - index created!
Running per image evaluation...
Evaluate annotation type *bbox*
COCOeval_opt.evaluate() finished in 13.68 seconds.
Accumulating evaluation results...
COCOeval_opt.accumulate() finished in 2.06 seconds.
2022-10-10 21:32:53 | INFO     | __main__:196 - 
Average forward time: 4.66 ms, Average NMS time: 1.67 ms, Average inference time: 6.33 ms
 Average Precision  (AP) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.404
 Average Precision  (AP) @[ IoU=0.50      | area=   all | maxDets=100 ] = 0.593
 Average Precision  (AP) @[ IoU=0.75      | area=   all | maxDets=100 ] = 0.437
 Average Precision  (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.232
 Average Precision  (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.448
 Average Precision  (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.541
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=  1 ] = 0.326
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets= 10 ] = 0.531
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.574
 Average Recall     (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.365
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.634
 Average Recall     (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.723
per class AP:
(snip)
~~~

### 計算量・パラメータ数測定

他とモデル作成方法が違うため、tools/eval.py を流用しました。以下のコードを実行して measure.py を作成します。
~~~python
txt = '''
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Copyright (c) Megvii, Inc. and its affiliates.

import argparse
import os
import random
import warnings
from loguru import logger
from thop import profile
import numpy as np
from copy import deepcopy

import torch
import torch.backends.cudnn as cudnn
from torch.nn.parallel import DistributedDataParallel as DDP

from yolox.core import launch
from yolox.exp import get_exp
from yolox.utils import (
    configure_module,
    configure_nccl,
    fuse_model,
    get_local_rank,
    get_model_info,
    setup_logger
)


def make_parser():
    parser = argparse.ArgumentParser("YOLOX Eval")
    parser.add_argument("-expn", "--experiment-name", type=str, default=None)
    parser.add_argument("-n", "--name", type=str, default=None, help="model name")

    # distributed
    parser.add_argument(
        "--dist-backend", default="nccl", type=str, help="distributed backend"
    )
    parser.add_argument(
        "--dist-url",
        default=None,
        type=str,
        help="url used to set up distributed training",
    )
    parser.add_argument("-b", "--batch-size", type=int, default=1, help="batch size")
    parser.add_argument(
        "-d", "--devices", default=1, type=int, help="device for training"
    )
    parser.add_argument(
        "--num_machines", default=1, type=int, help="num of node for training"
    )
    parser.add_argument(
        "--machine_rank", default=0, type=int, help="node rank for multi-node training"
    )
    parser.add_argument(
        "-f",
        "--exp_file",
        default=None,
        type=str,
        help="please input your experiment description file",
    )
    parser.add_argument("-c", "--ckpt", default=None, type=str, help="ckpt for eval")
    parser.add_argument("--conf", default=None, type=float, help="test conf")
    parser.add_argument("--nms", default=None, type=float, help="test nms threshold")
    parser.add_argument("--tsize", default=None, type=int, help="test img size")
    parser.add_argument("--seed", default=None, type=int, help="eval seed")
    parser.add_argument(
        "--fp16",
        dest="fp16",
        default=False,
        action="store_true",
        help="Adopting mix precision evaluating.",
    )
    parser.add_argument(
        "--fuse",
        dest="fuse",
        default=False,
        action="store_true",
        help="Fuse conv and bn for testing.",
    )
    parser.add_argument(
        "--trt",
        dest="trt",
        default=False,
        action="store_true",
        help="Using TensorRT model for testing.",
    )
    parser.add_argument(
        "--legacy",
        dest="legacy",
        default=False,
        action="store_true",
        help="To be compatible with older versions",
    )
    parser.add_argument(
        "--test",
        dest="test",
        default=False,
        action="store_true",
        help="Evaluating on test-dev set.",
    )
    parser.add_argument(
        "--speed",
        dest="speed",
        default=False,
        action="store_true",
        help="speed test only.",
    )
    parser.add_argument(
        "opts",
        help="Modify config options using the command-line",
        default=None,
        nargs=argparse.REMAINDER,
    )
    return parser


@logger.catch
def main(exp, args, num_gpu):
    if args.seed is not None:
        random.seed(args.seed)
        torch.manual_seed(args.seed)
        cudnn.deterministic = True
        warnings.warn(
            "You have chosen to seed testing. This will turn on the CUDNN deterministic setting, "
        )

    is_distributed = num_gpu > 1

    # set environment variables for distributed training
    configure_nccl()
    cudnn.benchmark = True

    rank = get_local_rank()

    file_name = os.path.join(exp.output_dir, args.experiment_name)

    if rank == 0:
        os.makedirs(file_name, exist_ok=True)

    setup_logger(file_name, distributed_rank=rank, filename="val_log.txt", mode="a")
    logger.info("Args: {}".format(args))

    if args.conf is not None:
        exp.test_conf = args.conf
    if args.nms is not None:
        exp.nmsthre = args.nms
    if args.tsize is not None:
        exp.test_size = (args.tsize, args.tsize)

    model = exp.get_model()
    logger.info("Model Summary: {}".format(get_model_info(model, exp.test_size)))

    img = torch.zeros((1, 3, 640, 640), device=next(model.parameters()).device)
    flops, params = profile(deepcopy(model), inputs=(img,), verbose=False)
    params /= 1e6
    flops /= 1e9
    flops *= 2  # Gflops
    print(f'{args.experiment_name}: flops = {flops}, params = {params}')

if __name__ == "__main__":
    configure_module()
    args = make_parser().parse_args()
    exp = get_exp(args.exp_file, args.name)
    exp.merge(args.opts)

    if not args.experiment_name:
        args.experiment_name = exp.exp_name

    num_gpu = torch.cuda.device_count() if args.devices is None else args.devices
    assert num_gpu <= torch.cuda.device_count()

    dist_url = "auto" if args.dist_url is None else args.dist_url
    launch(
        main,
        num_gpu,
        args.num_machines,
        args.machine_rank,
        backend=args.dist_backend,
        dist_url=dist_url,
        args=(exp, args, num_gpu),
    )
'''
with open('measure.py', mode='w') as fd:
    fd.writelines(txt)
~~~

上記 measure.py を使い、、以下のようにモデル名と Weights を指定して実行します。
~~~
!python measure.py -n yolox-s -c yolox_s.pth
~~~

[YOLOv7]: https://github.com/WongKinYiu/yolov7.git
[YOLOv7 github]:https://github.com/WongKinYiu/yolov7.git
[YOLOv5]: https://github.com/ultralytics/yolov5
[YOLOv5 github]: https://github.com/ultralytics/yolov5
[YOLOR (yolor_csp、yolor_csp_x、yolor_p6)]: https://github.com/WongKinYiu/yolor
[YOLOR]: https://github.com/WongKinYiu/yolor
[YOLOR github]: https://github.com/WongKinYiu/yolor
[YOLOR (yolor-w6、yolor-e6、yolor-d6)]: https://github.com/WongKinYiu/yolor/tree/paper
[YOLOR paper github]: https://github.com/WongKinYiu/yolor/tree/paper
[YOLOX]: https://github.com/Megvii-BaseDetection/YOLOX.git
[YOLOX github]: https://github.com/Megvii-BaseDetection/YOLOX.git
[論文]: https://arxiv.org/pdf/2207.02696.pdf
[YOLOv7はYOLOシリーズで最強か【第二版】]: https://qiita.com/masashi-ai/items/6975eca92b7ab6531a16

[YOLOR github]: https://github.com/WongKinYiu/yolor
[thop]: https://pypi.org/project/thop/