50
35

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

OpenMMLabの始め方@SUMMER 2023

Last updated at Posted at 2023-07-17

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

openmmlab.jpeg

  • 大雑把に言えば 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 への変換) まで対応している
    • データ構造含め、制約が強い
      • ここらへんも自分好みに対応できるが基本的に用意されているフォーマットに従ったほうが結局ローコスト
    • 慣れるまで大変
      • この記事が一助となれば幸いです

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> とか

3.1 Package Management

  • mim を使う場合 or 使わない場合のインストール方法の違いは以下の通りです
  • 一応 mim を使うことで mmengine のインストールや mmcv インストール時のオプションを省略できますが、以下の懸念点もあるので使う必要性は薄いかなと思っています
    • 現時点では poetry などと組み合わせるような想定ではない
    • 複数のOpenMMLabライブラリをインストールする前提ではない
    • Kaggleの submission に使う場合は結局 mmengine も mmcv もそれぞれ pip でダウンロードする必要がある
mim を使ったときのインストール方法の違い
# 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_apmask_ap などの結果も確認することができます。

image.png

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.shtest.sh を参照して使用する
      • ここらへんのコードの管理が発生する

4. 各パッケージのインストール方法

ここでは mmdetectionmmsegmentation をインストールする方法を紹介します。
ただしインストール方法はいくつかあるのでここではその切り分け方とおすすめの方法を紹介します。

  • 切り分け方
    • mimを使う or mimを使わない
    • ソースコードから or PyPIから
  • mimを使う or mimを使わない
    • 3.1 Package Management でも触れましたが、使うメリットが薄いので mimを使わない で良いかと思います
  • ソースコードから or PyPIから
    • 各方法は以下のsampleの通り
    • 各メリット
      • ソースコードから: --editable モードでインストールできる、最新版の機能やモデルを利用できる
      • PyPIから: バージョン管理が楽(指定できる)
    • どっちがおすすめ? → 個人的には PyPIから
      • OSS開発がしたい、最新版の機能を利用したい → ソースコードから
      • バージョン管理を楽にしたい、長期間使う予定あり → PyPIから
sample
# ソースコードから
$ 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 のバージョン確認
  • openmim & mmengine のインストール
    • これらのバージョンはあまり気にしなくて良い
    • $pip install openmim
    • $pip install mmengine
  • mmcv のインストール
  • 使いたいパッケージのインストール
    • 例: pip install mmdet==3.0.0

4.2 GET STARTED 例

5. 環境構築例

  • ここでは Docker を使った環境構築例を紹介します
  • 色々試しましたが、個人的には OpenMMLab の実行環境は Docker 毎切り分けたほうが楽という結論に至りました
    • やめよう!オール・インDockerfile!

5.1 Dockerfile

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 のように)、動作確認をします。

download weight
# Config ファイルと学習済 weight のダウンロード
$ mim download mmdet --config rtmdet_tiny_8xb32-300e_coco --dest .

# デモ実行
$ python demo.py
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 クラスを使うようになっています
  • OpenMMLab の Config の特徴

7.1 Config ファイル単体だと何しているのか分かりづらいとき

ここで例として公式リポジトリにある configs/yolox/yolox_l_8xb8-300e_coco.py を見てみましょう。

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 の内容を確認できます。

dump.py
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 があったとします。

base.py
model = dict(type='ResNet', depth=50)
optimizer=dict(
    type='SGD',
    lr=0.02,
    momentum=0.9,
    weight_decay=0.0001
)
adamw.py
_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 の内容を渡して学習しようとするとエラーが発生して終了します。

adam.pyを読み込んでみた結果
$ 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 などの値が挿入されます。

adam2.py
_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 はちゃんと削除されています。

adam2.py を読み込んでみた結果
$ 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 自分のデータで学習・推論させたいとき

8.2 Pretrain weight(config) を使いたいとき

以下のようにいくつか方法があります。
$mim search mmdet --model <model_name> やリポジトリ巡回とかで base にしたい config を探すのは共通です。

ここは好みで良いと思っていますが個人的には慣れるまで 方法1、慣れてきたら 方法2 で良いかなと思います。

  • 方法1: mim で base Config と weight をダウンロードする
    • そのあと自分の config ファイルで _base_load_from を設定する
  • 方法2: mim で base Config の場所と weight のパスを表示して my Config で直接指定
    • train 時に url から weight がダウンロードされる
      • weight は ${USER}/.cache/ 以下にダウンロードされるので、 docker container 毎に毎回ダウンロードしたくない人はこのパスを押さえて -v で共有するのがおすすめ
  • 方法3: mmengine の get_config() で base config を作成する
    • load_from に url が指定される
    • train 時に url から weight がダウンロードされる
      • weight は ${USER}/.cache/ 以下にダウンロードされるので、 docker container 毎に毎回ダウンロードしたくない人はこのパスを押さえて -v で共有するのがおすすめ

8.2.1 方法1: mim で base config と weight をダウンロードする

  • mim とかで weight(config) を探す
  • ダウンロードする
    • mim なら mim download を使う
  • Config ファイル内の _base_load_from でダウンロードした Config と weight のパスを指定する
config.py
_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

表示結果
image.png

そして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)の内容については別途自分で把握しておく必要がある
myconfig.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 を作成する

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())

8.4 fold 変えて試したいとき

  • 想定しているシーン
    • fold 毎にアノテーションファイルを作っている
      • e.g.
        • train_fold1.json, valid_fold1.json
        • train_fold2.json, valid_fold2.json
        • train_fold3.json, valid_fold3.json
    • fold 以外は同じ内容で学習する
  • 解決方法
    • 方法1: fold 毎に Config ファイルを作成する
      • 継承させる
        • e.g.: sample_fold1.py, sample_fold2.py, sample_fold3.py
    • 方法2: mim train/test 時の --cfg-options で fold によって変わるファイルパスを指定する
方法2の実装例
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) で学習することを指します。

8.6 KaggleでもOpenMMLab使いたいとき

実際のコンペ(HuBMAP - Hacking the Human Vasculature | Kaggle) でやってみました。

※記事執筆では現行コンペですが、それぞれ公開notebookにしてるので private sharing には該当しません。

Reference

50
35
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
50
35

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?