Detectron2中的模型初始化@configurable和from_config

Detectron2中的函数装饰器(decorator)

在__init__()函数上通常可以看到@configurable。它的作用是用config文件中的参数去初始化模型。简单来说,它没有首先执行__init__()函数,而是而是直接进入config.py的configurable函数,通过from_config函数取出取出变量,初始化__init__()。


我们先整理一下Python中的decorator

使用函数装饰器A()装饰另一个函数B(),底层执行了2步操作:(1)将B作为参数传给A()函数;(2)将A()函数执行完成的返回值反馈回B。参考[1],我们进行了实验:

def funA(fn):
    print("A_1")
    fn("B")
    print("A_2")
    return "Result"

@funA
def funB(str):
    print(str)

可以看到,此时funB在Python中本质上是个字符串,相当于funB = funA(funB),函数装饰器在这里有赋值的作用,被装饰函数的属性发生变化。因此,在detectron2中,被configurable装饰的__init__()函数,应为:__init__ = configurable(__init__)。

那他有什么用呢?他的用处就如同他的名字一样,包装修饰这个函数。简单来说,就是在configurable()函数里,通常会定义funC,funC会在执行funB之前,执行一些事情,然后执行funB,然后再执行一些事情,最后将funC返回回去,使得funB = funC。举例如下:

def funA(fn):
    def wrapped(str):
        print("do something before funA...")
        fn(str)
        print("do something after funA...")    
    return wrapped

@funA
def funB(str):
    print(str)

此时只剩下一个问题,那就是原函数的名字和注释文档(docstring)被重写了[2](上图funB的名字变成了wrapped)。这可以用Python的functools.wraps来解决,具体如下:

from functools import wraps

def funA(fn):
    @wraps(fn)
    def wrapped(str):
        print("do something before funA...")
        fn(str)
        print("do something after funA...")    
    return wrapped

@funA
def funB(str):
    print(str)

和上一段代码的唯一区别是给wrapped()函数添加了修饰符@wraps(fn)

这样保证被装饰的函数仍有原来的属性,消除了装饰函数的副作用。


回到Detectron2中:

def configurable(init_func=None, *, from_config=None):
    if init_func is not None:
        assert (
            inspect.isfunction(init_func)
            and from_config is None
            and init_func.__name__ == "__init__"
        ), "Incorrect use of @configurable. Check API documentation for examples."

        @functools.wraps(init_func)
        def wrapped(self, *args, **kwargs):
            try:  
                from_config_func = type(self).from_config
            except AttributeError as e:
                raise AttributeError(
                    "Class with @configurable must have a 'from_config' classmethod."
                ) from e
            if not inspect.ismethod(from_config_func):
                raise TypeError("Class with @configurable must have a 'from_config' classmethod.")

            if _called_with_cfg(*args, **kwargs):
                explicit_args = _get_args_from_config(from_config_func, *args, **kwargs)
                init_func(self, **explicit_args)
            else:
                init_func(self, *args, **kwargs)
        return wrapped

    else:
        if from_config is None:
            return configurable
        assert inspect.isfunction(
            from_config
        ), "from_config argument of configurable must be a function!"

        def wrapper(orig_func):
            @functools.wraps(orig_func)
            def wrapped(*args, **kwargs):
                if _called_with_cfg(*args, **kwargs):
                    explicit_args = _get_args_from_config(from_config, *args, **kwargs)
                    return orig_func(**explicit_args)
                else:
                    return orig_func(*args, **kwargs)
            return wrapped
        return wrapper

可以看到,实际上被重写后,__init__实际上执行的是wrapped函数。具体来说,通过type(self).from_config函数拿到__init__所需参数,初始化__init__。

以box_head.py中的FastRCNNConvFChead()为例,它首先被@ROI_BOX_HEAD_REGISTRY.register()装饰(fvcore.common.registry),之后可以通过ROI_BOX_HEAD_REGISTRY.get("FastRCNNConvFChead")()来调用。因此真正的实例化部分实现在:

def build_box_head(cfg, input_shape):
    """
    Build a box head defined by `cfg.MODEL.ROI_BOX_HEAD.NAME`.
    """
    name = cfg.MODEL.ROI_BOX_HEAD.NAME
    return ROI_BOX_HEAD_REGISTRY.get(name)(cfg, input_shape)

在roi_head.py中,box_head = build_box_head(cfg, ShapeSpec())来获取。

#1 这里的*args和**kwargs对应参数(1, 2, 3,a=1,b=2)中的前半部分和键值对的后半部分。

#2 self是实例(instance),cls是类(class)本身。

参考: 

[1] https://www.cnblogs.com/MorStar/p/14956423.html

[2] https://www.runoob.com/w3cnote/python-func-decorators.html

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值