Detectron2 よくあるエラーについて解説してみた

# はじめに

この記事では，物体検出タスクなどでよく用いられるDetectron2でよく生じるエラーについて解説していこうと思います．同じエラーが出たという人はぜひこの記事を参考にしてみてください．

## ImportError: cannotimport name  '_C' from detectron2（最初の注意書きをしっかり読んでから実践してください）
時々，githubからクローンしてきたリポジトリをVSCode上などのエディタ上で実行しようとすると，

```
ImportError: cannotimport name  '_C' from detectron2  
```

というエラーに出くわすことがあります．


このエラーはクローンしてきたgithubリポジトリに含まれているdetectron2をそのまま使用している時に生じることが多いです．
****※注意
大体の場合githubのリポジトリをしっかり読んでbuildを行えば，このエラーは解決される(orそもそも出ない)はずです．まずは，モデルを実行する前にgithubのREADME.mdをよく読んで動かすための準備をしっかりして，それでもダメな場合のみ下記のエラー解決法を実践するようにしましょう．面倒くさがって最初からこの方法をやってしまうと，モデルのプログラムと整合性が取れない場合があり，余計におかしくなる場合があります．****

例えば，あるモデルのリポジトリをgithubからクローンしてくると，

```:リポジトリのディレクトリの階層構造の例
cloned_repo/
│
├── modeling/
│   ├── __init__.py
│   ├── meta_arch.py        
│   └── fcos.py             
│
├── utils/
│   ├── __init__.py
│   └── registry.py 
│ 
├── detectron2/ #クローンしてきたリポジトリに元から含まれていたもの
│            ├── .cicleci
│            ├── __init__.py
│            └── registry.py 
│  　　　    
│  
├── train.py
├── test.py
└── ・・・
```
上記のように，元からDetectron2が含まれていることが多いです．しかし，これをそのまま使用すると，現在説明しているImportErrorが生じることがあリます．

解決策としては，クローンしてきたgithubリポジトリに含まれているdetectron2フォルダは削除するか名前を変更して，公式のdetectron2を改めて対象ディレクトリにクローンして，公式のdetectron2をモジュールとして参照するようにpip installを行えば良いです．参照モードにするには，pip installする際に-eコマンドを以下のようにつけます．
```bash:インストール方法
python -m pip install -e path/to/detectron2
```

これで公式のdetectron2を参照するようになります．これでもエラーが解消されない場合は他の対処法を考える必要があります．



## ImportError: cannot import name 'CityscapesEvaluator' from 'detectron2.evaluation' 



上記のエラーを解決すると，公式のdetectron2とgithubのリポジトリで作成されたフォルダやファイルが混合したようなディレクトリ構造になります．detectron2の公式のリポジトリは常にアップデートされて最新のものになっていますが，githubのリポジトリが作成された年数によっては最新のdetectron2を利用していない場合もあり，作成されたファイルもその公式のアップデートにそぐわない記述になっていることもしばしばあります．

ここで解説するImportErrorもそのうちの一つです．
公式のdetectron2では，現在（2024年4月現在）evaluationの__init__.pyは次のように定義されています．



```python
# Copyright (c) Facebook, Inc. and its affiliates.
from .cityscapes_evaluation import CityscapesInstanceEvaluator, CityscapesSemSegEvaluator
from .coco_evaluation import COCOEvaluator
from .rotated_coco_evaluation import RotatedCOCOEvaluator
from .evaluator import DatasetEvaluator, DatasetEvaluators, inference_context, inference_on_dataset
from .lvis_evaluation import LVISEvaluator
from .panoptic_evaluation import COCOPanopticEvaluator
from .pascal_voc_evaluation import PascalVOCDetectionEvaluator
from .sem_seg_evaluation import SemSegEvaluator
from .testing import print_csv_format, verify_results

__all__ = [k for k in globals().keys() if not k.startswith("_")]
```



この__init__.pyによって，evaculation/以下の各ファイルのクラスがモジュールとしてimportできるようになるのですが，少し前までは，'CityscapesInstanceEvaluator'の部分は'CityscapesEvaluator'となっていました．
そのため，少し前に作成されたgithubリポジトリの学習などを行うためのファイルには，



```python
from detectron2.evaluation import (
    CityscapesEvaluator,
    COCOEvaluator,
    COCOPanopticEvaluator,
    DatasetEvaluators,
    LVISEvaluator,
    PascalVOCDetectionEvaluator,
    SemSegEvaluator,
    verify_results,
)
```



と書かれていることが多いです．
最新のdetectron2をライブラリとして参照する設定にしていると，CityscapesEvaluatorは当然 最新のdetectron2の__init__.pyには定義されていないので，このようなImportErrorが出るわけです．
解決方法は単純で，単に上記の'CityscapesEvaluator'を下記のように'CityscapesInstanceEvaluator'に変更するだけです．




```python
from detectron2.evaluation import (
    CityscapesInstanceEvaluator,    #ここを変更する
    COCOEvaluator,
    COCOPanopticEvaluator,
    DatasetEvaluators,
    LVISEvaluator,
    PascalVOCDetectionEvaluator,
    SemSegEvaluator,
    verify_results,
)
```



このようにすれば，エラーは解決されるはずです．このようなエラーが発生したときは，そのフォルダの `__init__.py`まで行って， モジュールがどのように定義されているか確認しに行くのも１つの手かもしれません．



## KeyError:'Non-existent config key:XXX'


このエラーは実行時にconfigオブジェクトに`XXX（存在しないキーの名前がここに入ります）`というキーが見つからないことを意味しています．使用しているyamlファイルに`XXX`というキーが含まれていなかった場合，書き足すか別のyamlファイルを使用すればいいのですが，yamlファイルにそのような記述があってもこのエラーが出ることがあります．

その原因と対処法を今から説明します．

detectron2を含むリポジトリをgithubからクローンしてきて，中身を見
るとこのようなコードをよく見かけます．





```python
def setup(args):
    """
    Create configs and perform basic setups.
    """
    cfg = get_cfg()
    cfg.merge_from_file(args.config_file)
    cfg.merge_from_list(args.opts)
    cfg.freeze()
    default_setup(cfg, args)
    return cfg
```



- これは，簡単に言えばconfigファイルのセットアップを行う関数で，main関数の最初に呼び出されることが多いです．
- この関数では`get_cfg()`関数を用いて，cfgオブジェクトにデフォルトのconfig(ここでは_C)を代入します．
- defaultのconfigが何になっているかは，大体の場合，`get_cfg()`の定義に移動（⌘＋クリック，WindowsではおそらくCtrl＋クリック）すれば確認することができます．
- その後，タスクのconfigファイルとして指定したyamlファイルの中身をデフォルトのconfigファイルに`merge_from_file()`関数を使って上書きします．
- この際に，defaultのconfigである_Cにないようなconfig keyを持つconfigファイルを上書きしようとしても，_Cにそのconfig keyがない限り，上書きされることはなく，　cfgオブジェクトもそのconfig keyを保持することができません．
- つまり_C（defaultのconfig）に含まれていないconfig keyを，上書き（マージ）によってcfgオブジェクトに持たせることは，この上記の関数記述では不可能なのです．
- そのため，上記のような関数のまま実行をしようとすると，yamlファイルを_Cにマージして追加したはずの`XXX`というキーは追加されていないという現象が発生して，yamlファイルで指定したはずの`XXX`というキーが実は存在しないというエラーが発生してしまうのです．

これを解決するために，`set_new_allowed(True)`という関数が存在します．



```python
def setup(args):
    """
    Create configs and perform basic setups.
    """
    cfg = get_cfg()
    cfg.set_new_allowed(True)  #ここに追加します
    cfg.merge_from_file(args.config_file)
    cfg.merge_from_list(args.opts)
    cfg.freeze()
    default_setup(cfg, args)
    return cfg
```



この関数を追加することで，マージする際にマージ先に存在しないconfig keyでもconfigオブジェクトに保持させることができます．
このようにすることで，cfgオブジェクトに保持させたいconfig keyを確実に反映させることができて，ここで説明しているエラーも出なくなります．
もし，それでもダメな場合は他の方法を考える必要があります．



## KeyError: "No object named 'XXX' found in 'META_ARCH' registry!"



- このエラーは，`META_ARCH`というレジストリの中に`XXX`という名前のオブジェクトが登録されていない時に生じるエラーです．
- 深層学習におけるレジストリとは，特定のタスクや機能のためにクラスや関数などのオブジェクトを登録して，必要な時に取り出すことができるシステムです．
- メタアーキテクチャ（Meta Architecture）は，深層学習モデルの設計の中核をなす構造や戦略のことを指し，モデルの基本的な構成要素や全体的なアプローチを定義します．
- detectron2でもいろいろなメタアーキテクチャが利用されているわけですが，detectron2のディレクトリの`modeling/meta_arch/build.py`に次のようなコードがあります．




```python
def build_model(cfg):
    """
    Build the whole model architecture, defined by ``cfg.MODEL.META_ARCHITECTURE``.
    Note that it does not load any weights from ``cfg``.
    """
    meta_arch = cfg.MODEL.META_ARCHITECTURE
    cfg.set_new_allowed(True)
    cfg.freeze()
    model = META_ARCH_REGISTRY.get(meta_arch)(cfg)

    model.to(torch.device(cfg.MODEL.DEVICE))
    _log_api_usage("modeling.meta_arch." + meta_arch)
    return model

```


このコードは，単純にモデルをビルドするための関数ですが，その中の



```python
model = META_ARCH_REGISTRY.get(meta_arch)(cfg)
```



というコードで，configオブジェクトで指定したモデルを`META_ARCH_REGISTRY`から取得してくる際に，そのモデルがMETA_ARCH_REGISTRY内に登録されていないと，ここで説明しているエラーが発生します．
そのための解決策として，まず`META_ARCH_REGISTRY`にどのモデルが登録されているかを把握する必要があります．



エラーが出る直前などでチェックポイントを設定してからデバックを行って，デバックコンソールで
```bash:デバッグコンソール
> META_ARCH_REGISTRY
```
と入力して，エンターを押すと

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3727181/b970c009-ae46-a584-d4e1-780fa6724639.png)

このように，レジストリに登録されている一覧を取得することができます．ここに，'XXX'という名前のオブジェクトを登録する必要があります．

どのように登録するかは，一覧を取得した時に登録されているモデルのクラスを作成しているファイルを確認すればすぐわかるようになっています．
`modeling/meta_arch`内に様々なモデルを定義したファイルが置いてありますが，レジストリ内に登録してあるモデルのファイルを確認すると，class宣言の１行上に次のような記述があるはずです．



```python
@META_ARCH_REGISTRY.register()
class GeneralizedRCNN(nn.Module):
    ・・・
```



このようにデコレータ（既存の関数やクラスに機能を追加するための特別な構文）を用いて，`META_ARCH`にそのモデルを登録することができます．この記述を追加したいモデルのクラスの１行上に加えるとエラーは解消されるはずです．



## TypeError (note: full exception trace is shown but execution is paused at: _run_module_as_main) FCOS.\__init\__() takes 1 positional argument but 2 were given

公式のdetectron2ではモデルとして`FCOS`というものが定義されています．
今回では，`FCOS`というモデルで一例を話しますが，他のモデルにも使用できるノウハウなのでぜひ参考にしてください．



```python:build.py
meta_arch = cfg.MODEL.META_ARCHITECTURE
model = META_ARCH_REGISTRY.get(meta_arch)(cfg)
```
githubのリポジトリを見てみるとこのように，modelを定義しているコードがあります．
これは簡単に言えば`META_ARCH_REGISTRY`の中でconfigで定義したモデルを，`model`という変数名で使用できるようにしているのですが，実際にデバックなどを行なって確認してみると`META_ARCH_REGISTRY.get(meta_arch)`の値は，configで`META_ARCHTECTURE`に`FCOS`を使用しているため，実質`FCOS(cfg)`と記述したのと同じであるということになります．
つまり，`FCOS`といううクラスにconfigオブジェクトの値が渡されるようになっているのです．
ここで公式の`detectron2/modeling/meta_arch/fcos.py`を見てみると下記のような記述があります．



```python:fcos.py
class FCOS(DenseDetector):
    """
    Implement FCOS in :paper:`fcos`.
    """

    def __init__(
        self,
        *,
        backbone: Backbone,
        head: nn.Module,
        head_in_features: Optional[List[str]] = None,
        box2box_transform=None,
        num_classes,
        center_sampling_radius: float = 1.5,
        focal_loss_alpha=0.25,
        focal_loss_gamma=2.0,
        test_score_thresh=0.2,
        test_topk_candidates=1000,
        test_nms_thresh=0.6,
        max_detections_per_image=100,
        pixel_mean,
        pixel_std,
    ):
```



この`__init__`の部分で色々値を受け取って処理を進めていくのですが，この`__init__`に値を渡すときにこのままだとエラーが出てしまいます．

このエラーを説明するために`positional argument`と`keyward argument`について理解しないといけません．pythonでは，関数やメソッドに引数を与える方法としてその二つがあります．その二つの違いがわからない場合は他にわかりやすく説明しているサイトがたくさんあるのでそちらを参考にして理解しましょう．そもそもこの`def __init__`では`positional argument`が`keyward argument`よりも後ろにあるのでエラーが出るのは自明ですが，今回のエラーはおそらくそれが原因ではありません．

関数の仮引数を設定するときにpythonでは`*`という文字を置くと，それ以降の引数は`keyward argument`でないといけません．つまり，`*`以降の仮引数では関数が呼ばれる際に引数として`keyward argument`が期待されているのです．
つまり，位置引数として存在できるのは
ここでの例では，`model = META_ARCH_REGISTRY.get(meta_arch)(cfg)`として`FCOS`にconfigオブジェクトの値を渡していますが，このconfigオブジェクトが正常に期待通り引数に値を渡してくれるとも限りません．
`__init__`の中に直接必要な情報を書き込んで引数を全て`keyward argument`にしてエラーを回避する方法をありますが，今回はあえて遠回りとも言える方法も紹介します．
それは，configオブジェクトを受け取り，それを辞書型で返す関数を作成することです．detectron2では，必要な情報を求めてそれを辞書型でreturnする関数が多々存在しますので，そのような関数に慣れておくのも悪くはないでしょう．
具体的には，下記のようにします．
```python:configオブジェクトの中身を辞書型で返す関数の作成
def from_config(cfg):
    backbone = build_backbone(cfg)
    head = "RetinaNet"
    head_in_features = cfg.MODEL.RETINANET.IN_FEATURES
    num_classes = cfg.MODEL.RETINANET.NUM_CLASSES
    pixel_mean = cfg.MODEL.PIXEL_MEAN
    pixel_std = cfg.MODEL.PIXEL_STD
    return {
        "backbone": backbone,
        "head": head,
        "head_in_features": head_in_features,
        "num_classes": num_classes,
        "pixel_mean": pixel_mean,
        "pixel_std": pixel_std,
    }
```
そしてこの関数を使って，下記のようにFCOSクラスに値を渡すことができます．
```python
meta_arch = cfg.MODEL.META_ARCHITECTURE
cfg_dict = from_config(cfg)
model = META_ARCH_REGISTRY.get(meta_arch)(**cfg_dict)
```


このようにすれば，問題なくモデルに値を渡すことができるはずです．


## まとめ
今回はdetectron2でよく生じるエラーについて解説してみました．
今回の解説では私の環境での話になりますのであくまで参考程度に試してみていただけると幸いです．
他にもエラーの内容を検索してみると詳しく解説しているサイトも多くあるので，エラーの根本的な原因を理解して修正していくことが大切です．