Rist Kaggle チームの藤本(@fam_taro)です。
今回は Rist Kaggle合宿2023夏の時間を使って、最近の OpenMMLab の始め方をまとめてみました。本記事内ではその中の mmdetection を使って説明していきますが、他の OpenMMLab の使い方もカバーする内容となってます。
また記事の後半では Kaggle のコードコンペなどに参加したいときの使い方も記載します。
1. OpenMMLabとは
- 下図と紹介文は 公式サイト より引用
OpenMMLab builds the most influential open-source computer vision algorithm system in the deep learning era. It aims to
- provide high-quality libraries to reduce the difficulties in algorithm reimplementation
- create efficient deployment toolchains targeting a variety of backends and devices
- build a solid foundation for computer vision research and development
- bridge the gap between academic research and industrial applications with full-stack toolchains
- 大雑把に言えば CV のタスクごとのライブラリをまとめた OSS
- 上図の通り Pytorch を一番下の base としてその上に MMCV・MMEngine があり、各タスク毎にライブラリ(e.g. MMDetection)がある
- MMCV: 各NN用演算子(layer) や Data Transform をまとめたライブラリ
- MMEngine: training・evaluation 時の runner などをまとめたライブラリ
- Config 周りの処理もここにまとまっている
- MMEngine 自体は OpenMMLab 以外でも使用できる(これ単体でも使用できる)
- また MMEngine を使い始めたのはここ1年ぐらいの動きであり、使用する前後で各ライブラリのバージョンも大きく異なる
- e.g. MMDetection2.X と MMdetection3.X
- 個人的に感じた特徴は以下の通り
- Config によって変えられることが多い
- 大体 Config でなんとかなる
- Config で変えられないことも対応できる
- Config も最初は慣れが必要
- 高い再現性
- Deploy(e.g. ONNX への変換) まで対応している
- データ構造含め、制約が強い
- ここらへんも自分好みに対応できるが基本的に用意されているフォーマットに従ったほうが結局ローコスト
- 慣れるまで大変
- この記事が一助となれば幸いです
- Config によって変えられることが多い
2. 自作パイプラインと比べてどう?
- ここでいう自作pipelineとは以下のように Config ファイル等を用意して学習済 weight を得るコードのことを指すとします
- 入力: Config ファイル(e.g. .txt, .py, .yaml)
- 出力: 学習済 weight
- たとえば PyTorch Lightning をさらに個別タスクや自分用にラッピングしたものなどが該当します
- 例えば短期のPJやKaggleなどのコンペであればどちらでも良い気がしますが、長期間での業務となると自作パイプラインだと負債が大きくなってしまう可能性があります
長所 | 短所 | |
---|---|---|
OpenMMLab | ・Config で変更できることが多い ・public dataset に対する再現性 ・タスクを変えても大体同じような使い方ができる(e.g. Detection, Segmentation, Classification) |
・初期学習コストが高い ・モジュールを追加したい場合ライブラリへの高い理解が要求されるときがある ・AIタスクに制限がかかる |
自作パイプライン | ・スモールスタートさせやすい ・自由度が高い ・タスクに制限がない |
・複数CVタスクを組み合わせるときめんどくさい ・メンテナンスコストが高い ・他人へそのまま引き継ぐのは難しいことが多い |
3. mim(Openmim) について
また最近では MIM を使ってパッケージのインストールや Config をダウンロードすることも流行っています。
mim は公式曰く以下のようなことができます。must ではありませんが便利なのでインストール推奨です。
- Package Management(ちょっと便利だがあまり使わない)
- OpenMMLab の各ライブラリのバージョンは主に MMCV・MMEngine のバージョンに依存し、MMCV はさらに PyTorch と CUDA のバージョンに依存している
- mim を使って各ライブラリ(e.g. MMSegmentation, MMDetection) をインストールすることができ、その際対応している MMCV や MMEngine もセットでインストールしてくれる
- Model Management(便利)
- Config + Weight の検索やダウンロードができる
- Unified Entrypoint for Scripts(便利)
- ライブラリ毎に Train や Test で使用するコード(スクリプト)が若干違うがそのまわりのインタフェースを共通化してくれる
-
$mim train mmdet <config_file>
とか$mim train mmseg <config_file>
とか
-
- ライブラリ毎に Train や Test で使用するコード(スクリプト)が若干違うがそのまわりのインタフェースを共通化してくれる
3.1 Package Management
- mim を使う場合 or 使わない場合のインストール方法の違いは以下の通りです
- mmcv はこちらの Installation — mmcv 2.0.1 documentation 内で各 pytorch や CUDA のバージョンごとのインストール方法を確認できます
- 一応 mim を使うことで mmengine のインストールや mmcv インストール時のオプションを省略できますが、以下の懸念点もあるので使う必要性は薄いかなと思っています
- 現時点では poetry などと組み合わせるような想定ではない
- 複数のOpenMMLabライブラリをインストールする前提ではない
- Kaggleの submission に使う場合は結局 mmengine も mmcv もそれぞれ pip でダウンロードする必要がある
# mim を使わない場合
$ pip install mmengine==0.7.1
$ pip install mmcv==2.0.0 -f https://download.openmmlab.com/mmcv/dist/cu118/torch2.0/index.html
$ pip install mmdet==3.0.0
# mim を使う場合
$ pip install openmim
$ mim install "mmcv>=2.0.0"
$ mim install "mmdet==3.0.0"
3.2 Model Management
- ここでいう
Model Management
とは実験管理ではなく Config や weight をどう探してどう引っ張ってくるかという意味です
3.2.1 検索
例えば mim をインストール済の環境で $mim search mmdet --model "mask r-cnn"
と config(と weight)の検索結果を表示させることができます。(画像のような検索結果では j と k で縦移動ができます。
このとき box_ap
や mask_ap
などの結果も確認することができます。
3.2.2 ダウンロード
例えば上記の検索結果にあった mask-rcnn_hrnetv2p-w18-2x_coco
をダウンロードしたいときは下記のように mim download
コマンドを使うことでダウンロードすることができます。
$ mim download mmdet --config mask-rcnn_hrnetv2p-w18-2x_coco --dest .
processing mask-rcnn_hrnetv2p-w18-2x_coco...
downloading ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 115.4/115.4 MiB 2.8 MB/s eta 0:00:00
Successfully downloaded mask_rcnn_hrnetv2p_w18_2x_coco_20200212-b3c825b1.pth to /workspace/tmp
Successfully dumped mask-rcnn_hrnetv2p-w18-2x_coco.py to /workspace/tmp
$ ls
mask-rcnn_hrnetv2p-w18-2x_coco.py mask_rcnn_hrnetv2p_w18_2x_coco_20200212-b3c825b1.pth
3.3 Unified Entrypoint for Scripts
- mim を使う場合
$ mim train <package> <config> --gpus <gpus>
- mim を使わない場合
- 各パッケージのリポジトリ内にある
train.sh
やtest.sh
を参照して使用する- ここらへんのコードの管理が発生する
- 各パッケージのリポジトリ内にある
4. 各パッケージのインストール方法
ここでは mmdetection
や mmsegmentation
をインストールする方法を紹介します。
ただしインストール方法はいくつかあるのでここではその切り分け方とおすすめの方法を紹介します。
- 切り分け方
-
mimを使う
ormimを使わない
-
ソースコードから
orPyPIから
-
-
mimを使う
ormimを使わない
-
3.1 Package Management
でも触れましたが、使うメリットが薄いのでmimを使わない
で良いかと思います
-
-
ソースコードから
orPyPIから
- 各方法は以下のsampleの通り
- 各メリット
-
ソースコードから
:--editable
モードでインストールできる、最新版の機能やモデルを利用できる -
PyPIから
: バージョン管理が楽(指定できる)
-
- どっちがおすすめ? → 個人的には
PyPIから
- OSS開発がしたい、最新版の機能を利用したい →
ソースコードから
- バージョン管理を楽にしたい、長期間使う予定あり →
PyPIから
- OSS開発がしたい、最新版の機能を利用したい →
# ソースコードから
$ git clone <repository>
$ cd <repository>
$ pip install -e .
# PyPI から
$ pip install mmdet==3.0.0
4.1 おすすめインストール方法(mimを使わない
& PyPIから
)
ここでは自分が使いたい OpenMMLab パッケージ(e.g. MMDetectioin)をインストールする際の大まかな流れを紹介します。
具体的なコマンドは 5. 環境構築例 で紹介します。
- 自分の環境を確認
- 確認する内容
- python version
- CUDA version
- pytorch version
- pytorch も OpenMMLab パッケージに合わせて入れ直すのもあり
- もしくは先に OpenMMLab パッケージ用の環境を確認してから合わせても良い
- 確認する内容
- 使いたいパッケージのインストールに必要な mmcv のバージョン確認
- 確認方法(いくつかある)
- 各パッケージの document に書いてある場合
- 各パッケージのリポジトリ内にある Dockerfile に書いてある場合
- 各パッケージを mim を使ってインストールしてその後 mmcv のバージョンを確認する
- mim を使っているが、バージョン確認を終えたらその環境を捨てて、mim を使わずインストールしなおす
- 確認方法(いくつかある)
- openmim & mmengine のインストール
- これらのバージョンはあまり気にしなくて良い
$pip install openmim
$pip install mmengine
- mmcv のインストール
-
https://mmcv.readthedocs.io/en/latest/get_started/installation.html#install-with-pip
- ここで pytorch や CUDA、mmcv のバージョンごとのインストール方法を確認できる
- 例:
pip install mmcv==2.0.0 -f https://download.openmmlab.com/mmcv/dist/cu118/torch2.0/index.html
-
https://mmcv.readthedocs.io/en/latest/get_started/installation.html#install-with-pip
- 使いたいパッケージのインストール
- 例:
pip install mmdet==3.0.0
- 例:
4.2 GET STARTED 例
- detection
- semantic segmentation
- classification
-
Prerequisites — MMPretrain 1.0.0 documentation
- classification ではこれまで MMClassification というライブラリが使われていたが、自己教師あり学習用のライブラリである MMSelfSup と merge されて MMPreTrain になった
-
Prerequisites — MMPretrain 1.0.0 documentation
5. 環境構築例
- ここでは Docker を使った環境構築例を紹介します
- 色々試しましたが、個人的には OpenMMLab の実行環境は Docker 毎切り分けたほうが楽という結論に至りました
- やめよう!オール・インDockerfile!
5.1 Dockerfile
FROM python:3.10.10-slim AS base
ENV NVIDIA_VISIBLE_DEVICES all
ENV NVIDIA_DRIVER_CAPABILITIES utility,compute
RUN apt-get update && apt-get install -y \
ffmpeg libsm6 libxext6 git ninja-build \
libglib2.0-0 libxrender-dev build-essential \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
RUN pip install --upgrade pip \
&& pip install --no-cache-dir torch==2.0.0+cu118 torchvision==0.15.1+cu118 --index-url https://download.pytorch.org/whl/cu$118 \
&& pip install --no-cache-dir openmim \
&& pip install --no-cache-dir mmengine==0.7.1 \
&& pip install --no-cache-dir mmcv==2.0.0 -f https://download.openmmlab.com/mmcv/dist/cu118/torch2.0/index.html \
&& pip install --no-cache-dir mmdet==3.0.0 \
&& pip install --no-cache-dir mmsegmentation==1.0.0
ENV LANG C.UTF-8
ENV LANGUAGE en_US
WORKDIR workspace
5.2 使用例
# build
$ ls tmp
Dockerfile
$ docker build -t sample -f tmp/Dockerfile ./tmp
# run
$ docker run --gpus '"device=0"' -v `pwd`:/workspace --name sample.tmp -itd sample
# exec
$ docker exec -it sample.tmp bash
6. GET STARTED(mmdetection)
ここではmmdetectionをインストールしたとして(上記の Dockerfile のように)、動作確認をします。
# Config ファイルと学習済 weight のダウンロード
$ mim download mmdet --config rtmdet_tiny_8xb32-300e_coco --dest .
# デモ実行
$ python demo.py
import mmcv
from mmdet.registry import VISUALIZERS
from mmdet.apis import init_detector, inference_detector
# model init
config_file = 'rtmdet_tiny_8xb32-300e_coco.py'
checkpoint_file = 'rtmdet_tiny_8xb32-300e_coco_20220902_112414-78e30dcc.pth'
model = init_detector(config_file, checkpoint_file, device='cuda:0') # cpu or device='cuda:0'
# inference
img = mmcv.imread('./demo.jpg')
result = inference_detector(model, img)
# visualize result
img = mmcv.imconvert(img, 'bgr', 'rgb')
visualizer = VISUALIZERS.build(model.cfg.visualizer)
visualizer.dataset_meta = model.dataset_meta
visualizer.add_datasample(
'result',
img,
data_sample=result,
draw_gt=False,
show=False,
out_file='./output.jpg'
)
7. Configの書き方について
- Config はどのパッケージも MMEngine 内ので定義された Config クラスを使うようになっています
- ですので困ったら下記のドキュメントを確認すると良いかと思います
- Config — mmengine 0.8.2 documentation
- OpenMMLab の Config の特徴
- 基本
.py
ファイル- YAML(
.yml
) や Json(.json
) にも対応している
- YAML(
- 継承可能
- https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#inheritance-between-configuration-files
- Config ファイル内で
_base_ = ['a.py', 'b.py']
のように指定する - base の Config から少ししか変更を加えない場合は有用
- ただし
_base_
で複数ファイル指定する場合、そのファイル間でのキーの重複は許されない - 一方で Config ファイル単体では全体像が分かりにくくなっている
- 基本
7.1 Config ファイル単体だと何しているのか分かりづらいとき
ここで例として公式リポジトリにある configs/yolox/yolox_l_8xb8-300e_coco.py
を見てみましょう。
_base_ = './yolox_s_8xb8-300e_coco.py'
# model settings
model = dict(
backbone=dict(deepen_factor=1.0, widen_factor=1.0),
neck=dict(
in_channels=[256, 512, 1024], out_channels=256, num_csp_blocks=3),
bbox_head=dict(in_channels=256, feat_channels=256))
これだけで epoch とか optimizer の設定とかを把握するのは難しいです。
愚直にすべての継承元を辿ることも可能ですがかなり手間です。
このように Config ファイル単体が何をしているのか分かりづらいときは一度 mmengine.config.Config.fromfile
で読み込んでしまって print や dump すると良いです。
Config.fromfile()
で読み込んだときに継承した後の最終的な Config の内容を確認できます。
from mmengine.config import Config
cfg = Config.fromfile('<config_path>')
print(cfg)
cfg.dump('sample_dump.py')
7.2 継承元の特定の key について消したいとき
例えば下記のような base.py
(ベースとしたいConfigファイル)と、base.py
を継承しつつも optimizer を SGD から AdamW に変えようとした Config ファイル adamw.py
があったとします。
model = dict(type='ResNet', depth=50)
optimizer=dict(
type='SGD',
lr=0.02,
momentum=0.9,
weight_decay=0.0001
)
_base_ = ['base.py']
optimizer=dict(
type='AdamW',
lr=0.0001 / 2,
betas=(0.9, 0.999),
weight_decay=0.05
)
ですがいざ adam.py
を読み込んでみると、たしかに optimizer は AdamW に変わったものの momentum=0.9
が残ってしまっています。つまり残すつもりがなかった引数が残ってしまっています。
ちなみに AdamW には momentum
という名前の引数はないので、このまま Config の内容を渡して学習しようとするとエラーが発生して終了します。
$ ipython
Python 3.10.10 (main, Mar 23 2023, 03:59:34) [GCC 10.2.1 20210110]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.14.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: from mmengine.config import Config
In [2]: cfg = Config.fromfile("adamw.py")
In [3]: cfg.optimizer
Out[3]:
{'type': 'AdamW',
'lr': 5e-05,
'momentum': 0.9,
'weight_decay': 0.05,
'betas': (0.9, 0.999)}
これを防ぐためには _delete_=True
を使います。
これを入力されたキー名(ここでは optimizer)は継承された場合でも一度リセットされます。
その後 adam2.py
で定義したような lr や weight_decay などの値が挿入されます。
_base_ = ['base.py']
optimizer=dict(
_delete_=True,
type='AdamW',
lr=0.0001 / 2,
betas=(0.9, 0.999),
weight_decay=0.05
)
実際に adam2.py
を読み込んでみると、adamw.py
を読み込んだときのような momentum
はちゃんと削除されています。
$ ipython
Python 3.10.10 (main, Mar 23 2023, 03:59:34) [GCC 10.2.1 20210110]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.14.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: from mmengine.config import Config
In [2]: cfg = Config.fromfile("adamw2.py")
In [3]: cfg.optimizer
Out[3]: {'type': 'AdamW', 'lr': 5e-05, 'betas': (0.9, 0.999), 'weight_decay': 0.05}
8. 各ユースケース
8.1 自分のデータで学習・推論させたいとき
- 各パッケージの推奨フォーマットへの変換を推奨
- フォーマット例
- このようなフォーマット整形をしなくとも
register_module()
で MyDataset を追加することで対応できたりするが、のちのメンテナンスコストも考慮して選択できると良い
- config ファイルの作成
-
mmdetの場合
- 大体 dataset 周りのクラスの定義の変更のみで使うことができます
-
mmdetの場合
- train・test
- 例:
$mim train mmseg <config_file> --gpus 1
- 例:
8.2 Pretrain weight(config) を使いたいとき
以下のようにいくつか方法があります。
$mim search mmdet --model <model_name>
やリポジトリ巡回とかで base にしたい config を探すのは共通です。
ここは好みで良いと思っていますが個人的には慣れるまで 方法1
、慣れてきたら 方法2
で良いかなと思います。
- 方法1: mim で base Config と weight をダウンロードする
- そのあと自分の config ファイルで
_base_
とload_from
を設定する
- そのあと自分の config ファイルで
- 方法2: mim で base Config の場所と weight のパスを表示して my Config で直接指定
- train 時に url から weight がダウンロードされる
- weight は
${USER}/.cache/
以下にダウンロードされるので、 docker container 毎に毎回ダウンロードしたくない人はこのパスを押さえて-v
で共有するのがおすすめ
- weight は
- train 時に url から weight がダウンロードされる
- 方法3: mmengine の
get_config()
で base config を作成する-
load_from
に url が指定される - train 時に url から weight がダウンロードされる
- weight は
${USER}/.cache/
以下にダウンロードされるので、 docker container 毎に毎回ダウンロードしたくない人はこのパスを押さえて-v
で共有するのがおすすめ
- weight は
-
8.2.1 方法1: mim で base config と weight をダウンロードする
- mim とかで weight(config) を探す
- ダウンロードする
- mim なら
mim download
を使う
- mim なら
- Config ファイル内の
_base_
とload_from
でダウンロードした Config と weight のパスを指定する
_base_ = '/workspace/checkpoints/mask-rcnn_r101_fpn_2x_coco.py'
# _base_ = ['/workspace/checkpoints/mask-rcnn_r101_fpn_2x_coco.py']
load_from = 'checkpoints/mask_rcnn_r101_fpn_2x_coco_bbox_mAP-0.408__segm_mAP-0.366_20200505_071027-14b391c7.pth'
8.2.2 方法2: mim で base Config の場所と weight のパスを表示して my Config で直接指定
例えば下記のように Config を検索して特定の Config と weight のパスを表示することができます。
# mask-rcnn のConfig一覧検索
$ mim search mmdet --model "mask r-cnn"
# 上記の結果で見つけた mask-rcnn_r101_fpn_2x_coco の Config の場所と weight のパス表示
$ mim search mmdet --config mask-rcnn_r101_fpn_2x_coco --field config weight --display-width 200
そしてmyconfig.py
ように自分の Config ファイルに設定します。このときの注意事項は以下2点です。
- 検索結果の Config ファイルパスは少し変更する必要がある
- 変更前:
configs/mask_rcnn/mask-rcnn_r101_fpn_2x_coco.py
- 変更後:
mmdet::mask_rcnn/mask-rcnn_r101_fpn_2x_coco.py
- ここの
mmdet
は各 OpenMMLab パッケージ名に変更する
- ここの
- 変更前:
- 元の Config ファイル(下記での
mask-rcnn_r101_fpn_2x_coco.py
)の内容については別途自分で把握しておく必要がある
_base_ = 'mmdet::mask_rcnn/mask-rcnn_r101_fpn_2x_coco.py'
load_from = 'https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r101_fpn_2x_coco/mask_rcnn_r101_fpn_2x_coco_bbox_mAP-0.408__segm_mAP-0.366_20200505_071027-14b391c7.pth'
# 以下自分がしたい設定
~~~
~~~
8.2.3 方法3: mmengine の get_config()
で base config を作成する
- mmengine の
get_config()
で他の OpenMMLab パッケージの config を取ってくることができます- また
pretrained=True
とすることでその config での学習済み weight を指定できます - https://mmengine.readthedocs.io/en/latest/advanced_tutorials/config.html#get-configuration-files-across-repository
- また
from mmengine.hub import get_config
cfg = get_config(
'mmdet::faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py', pretrained=True)
# https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_1x_coco/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth
print(cfg.model_path)
cfg.dump('my_config.py')
8.3 configの変更じゃ対応できないことをしたいとき(register_module())
- 自分で定義したモジュールを追加して、Config で指定して使用することができる
- ちょっとしたモデルや data augmentation の追加で OpenMMLab のパッケージ側を変更する必要はない
8.4 fold 変えて試したいとき
- 想定しているシーン
- fold 毎にアノテーションファイルを作っている
- e.g.
- train_fold1.json, valid_fold1.json
- train_fold2.json, valid_fold2.json
- train_fold3.json, valid_fold3.json
- e.g.
- fold 以外は同じ内容で学習する
- fold 毎にアノテーションファイルを作っている
- 解決方法
- 方法1: fold 毎に Config ファイルを作成する
- 継承させる
- e.g.: sample_fold1.py, sample_fold2.py, sample_fold3.py
- 継承させる
- 方法2:
mim train/test
時の--cfg-options
で fold によって変わるファイルパスを指定する
- 方法1: fold 毎に Config ファイルを作成する
import os
from pathlib import Path
from mmengine.config import Config
import click
@click.command()
@click.option("--config", default="./config_mmdet/sample.py")
@click.option("--fold", default=1)
def main(config, fold):
cfg = Config.fromfile(config)
# modify existed values in config
fold_str = f"fold{fold}"
ann_path_val1 = cfg.val_dataloader.dataset.ann_file.replace("fold1", fold_str)
ann_path_val2 = cfg.val_evaluator.ann_file.replace("fold1", fold_str)
ann_path_train = cfg.train_dataloader.dataset.ann_file.replace("fold1", fold_str)
config_options = " ".join([
f"fold={fold}",
f"train_dataloader.dataset.ann_file='{ann_path_train}'"
f"val_dataloader.dataset.ann_file='{ann_path_val1}'",
f"val_evaluator.ann_file='{ann_path_val2}'",
])
work_dir = Path("./output/mmdet/") / Path(config).stem / fold_str
cmd_str = f"mim train mmdet {config} --gpus 2 --launcher pytorch " \
f"--work-dir {str(work_dir)} --cfg-options {config_options}"
os.system(cmd_str)
if __name__ == '__main__':
main()
8.5 分散学習(Distributed Training) したいとき
ここでの分散学習とは、マルチ Node(PC) で学習することを指します。
- 各パッケージの document 見る限りオプションで対応できそう
- mim のコマンドでも
torch.distributed.launch
やslurm
を用いた分散学習はできそうだった
8.6 KaggleでもOpenMMLab使いたいとき
実際のコンペ(HuBMAP - Hacking the Human Vasculature | Kaggle) でやってみました。
※記事執筆では現行コンペですが、それぞれ公開notebookにしてるので private sharing には該当しません。
- 必要なパッケージをダウンロード&build 編
- 推論してみた編