MMCV学习——基础篇4(Hook)| 八千字:从设计模式到源码解读

news2024/11/18 0:32:54

MMCV学习——基础篇4(Hook)

Hook 机制在MMCV的各个开源库中应用的十分广泛,它主要用于管理和扩展Runner的整个生命周期。通过Hook机制在Runner的各个生命周期节点调用自定义的函数,可以实现丰富的定制功能

文章目录

  • MMCV学习——基础篇4(Hook)
    • 1. 从观察者模式谈起
      • 1.1 课程更改,学生如何知道?
      • 1.2 观察者模式到底与Hook有什么关系?
    • 2. MMCV中的Hook
      • 2.1 MMCV Runner的生命周期与Hook
      • 2.2 MMCV的Hook分类
      • 2.3 如何自定义Hook?
    • 3. 参考资料

1. 从观察者模式谈起

Hook本身是一种程序设计的机制,并不是某种语言或者框架独有的。在程序设计模式中,有一种模式叫观察者模式就可以通过Hook机制去实现。观察者模式描述的是被观察者(Subject)观察者(Observer)之间的一对多关系,在观察者不需要知道被观察者是谁的情况下,将被观察者的状态改变推送到观察者这里。

1.1 课程更改,学生如何知道?

 下面我们通过一个简单的场景代码来介绍观察者模式:假设一群人订阅了一门课程,如果课程有内容更新,如何比较优雅地让这群人知道这门课程更新了呢?我们天然的方案可能是,每个人定期查询一下课程看看是否更新,但是这样做会导致每隔一段时间大量用户去访问一个课程,显然这样是不合理并且浪费资源的。
 所以我们在这里引入观察者模式,仅仅在被观察者(Subject)和观察者(Observer)之间建立一个抽象的耦合关系,就可以做到让观察者及时感知到被观察者的变化。首先,我们先定义一个Subject(课程)类:

class Subject(object):

    def __init__(self, state: int) -> None:
        self._state = state
        self._observers = []
    
    @property
    def state(self) -> int:
        return self._state
    
    @state.setter
    def state(self, state: int):
        print('===== start change state! =====')
        old_state = self._state
        self._state = state
        self.notify_observers(old_state)
        print('===== end change state! =====')
    
    @property
    def observers(self):
        # read-only property
        return self._observers
    
    def attach(self, observer):
        self._observers.append(observer)
    
    def detach(self, observer):
        self._observers.remove(observer)
    
    def notify_observers(self, old_state):
        for observer in self._observers:
            observer.update(old_state)

    def __str__(self) -> str:
        return f'Subject(state: {self._state})'
  • Subject类有一个state(状态)和observers(观察者列表)实例属性。
  • 要实现观察者模式Subject就需要实现notify_observers方法,在state发生改变时通知所有观察者。
  • attachdetach方法用来管理观察者列表。

 然后再定义一个Observer(观察者)类:

class Observer(object):

    def __init__(self, name: str, subject: Subject) -> None:
        self._name = name
        self._subject = subject
        self._subject.attach(self)
    
    @property
    def name(self):
        # read-only
        return self._name
    
    @property
    def subject(self):
        # read-only
        return self._subject
    
    def update(self, old_state):
        print(f'{self._name}: subject from {old_state} to {self._subject.state}')
    
    def __str__(self) -> str:
        return f'Observer(name: {self._name}, subject: {self._subject})'
  • Observer类有一个name(名字)和subject(课程)只读实例属性。
  • 要实现观察者模式Observer就需要实现update方法以供Subjectnotify_observers中调用。

接下来我们来写一段Running script运行一下观察者模式的示例代码:

if __name__ == '__main__':
    subject = Subject(1)
    observers = [Observer(name, subject) for name in ['Tom', 'Ben', 'Jerry']]
    subject.state = 2
    print('Now detach Tom and change the state!')
    subject.detach(observers[0])
    subject.state = 3
'''
Output:
===== start change state! =====
Tom: subject from 1 to 2
Ben: subject from 1 to 2
Jerry: subject from 1 to 2
===== end change state! =====
Now detach Tom and change the state!
===== start change state! =====
Ben: subject from 2 to 3
Jerry: subject from 2 to 3
===== end change state! =====
'''

1.2 观察者模式到底与Hook有什么关系?

 上面的观察者设计模式的实现依赖于SubjectObserver两个类,但是如果有些时候我们只是想在Subject状态改变的时候唤起某个自定义的函数,而不想费这么大功夫去专门去写个Observer类的时候该怎么做呢?对于Python来说,函数是里面的一等公民,所以我们可以按照下面的方式去实现观察者设计模式

from functools import partial


class Subject(object):

    def __init__(self, state: int) -> None:
        self._state = state
        self._hooks = []
    
    @property
    def state(self) -> int:
        return self._state
    
    @state.setter
    def state(self, state: int):
        print('===== start change state! =====')
        old_state = self._state
        self._state = state
        self.notify_hooks(old_state)
        print('===== end change state! =====')
    
    def attach(self, hook):
        self._hooks.append(hook)
    
    def detach(self, hook):
        self._hooks.remove(hook)
    
    def notify_hooks(self, old_state):
        for hook in self._hooks:
            hook(old_state)

    def __str__(self) -> str:
        return f'Subject(state: {self._state})'


def obs_hook(old_state, name, subject):
    print(f'{name}: subject from {old_state} to {subject.state}')


if __name__ == '__main__':
    subject = Subject(1)
    hook0 = partial(obs_hook, name='Tom', subject=subject)
    print(type(hook0))
    subject.attach(hook0)
    # Now change the state of subject
    subject.state = 2
'''
Output:
<class 'functools.partial'>
===== start change state! =====
Tom: subject from 1 to 2
===== end change state! =====
Now detach Tom and change the state!
'''
  • 我们将Observer这个类在这里简化成了一个obs_hook函数,并通过partial工具给obs_hook函数绑定name和subject参数。
  • Subject的state的setter方法中去激活所有注册的Hook函数并执行。

2. MMCV中的Hook

MMCV这类第三方框架都会按照工作流程进行一定程度地抽象并归纳出一套通用的执行流程(Runner),但是对于第三方框架的开发者来说,并不知道我们用户在使用这个框架时碰到的具体问题,所以既要保证开发时框架的通用性,又要保证使用时用户可以定制化地修改框架的部分逻辑,就需要用到Hook函数了。

2.1 MMCV Runner的生命周期与Hook

 如下图所示,MMCV Runner的生命周期大体上分为这6个阶段,每个阶段都可以插入Hook从而实现扩展功能。当然,Runner中还涉及到了训练(train)和验证(val)模式,可以按照不同的模式划分不同的阶段,具体可以参看MMCV Hook源码。
在这里插入图片描述
 下面的代码以EpochBasedRunner为例展示了Runner调用hook的基本流程,用户要做的只是在各个点位注册好自己写的Hook函数就可以实现自定义的功能:

# 运行前准备工作
before_run()

while self.epoch < self._max_epochs:

    # 开始 epoch 迭代前调用
    before_train_epoch()

    for i, data_batch in enumerate(self.data_loader):
        # 开始 iter 迭代前调用
        before_train_iter()

        self.model.train_step()

        # 经过一次迭代后调用
        after_train_iter()

    # 经过一个 epoch 迭代后调用
    after_train_epoch()

# 运行完成后调用
after_run()

2.2 MMCV的Hook分类

 上图是在MMCV官方分享的知乎上拿来的一张Hook分类图,可以看到MMCV中Hook可以分为默认和定制Hook两类。其各自的相关功能图上也写的很清楚了。如果想知道更细节一点的各类Hook的功能实现可以参看MMCV常用 Hook 类简析。
在这里插入图片描述

2.3 如何自定义Hook?

 用户如果想要自定义一个Hook就可以选择继承基类或者利用ClosureHook快速注册。如果是选择继承的方式自定义Hook,就需要创建子类去重写插入阶段的函数,下面是v1.7.0版本Hook基类部分源代码:

# Copyright (c) OpenMMLab. All rights reserved.
from mmcv.utils import Registry, is_method_overridden

HOOKS = Registry('hook')


class Hook:
    stages = ('before_run', 'before_train_epoch', 'before_train_iter',
              'after_train_iter', 'after_train_epoch', 'before_val_epoch',
              'before_val_iter', 'after_val_iter', 'after_val_epoch',
              'after_run')

    def before_run(self, runner):
        pass

    def after_run(self, runner):
        pass

    def before_epoch(self, runner):
        pass

    def after_epoch(self, runner):
        pass

    def before_iter(self, runner):
        pass

    def after_iter(self, runner):
        pass

    def before_train_epoch(self, runner):
        self.before_epoch(runner)

    def before_val_epoch(self, runner):
        self.before_epoch(runner)

    def after_train_epoch(self, runner):
        self.after_epoch(runner)

    def after_val_epoch(self, runner):
        self.after_epoch(runner)

    def before_train_iter(self, runner):
        self.before_iter(runner)

    def before_val_iter(self, runner):
        self.before_iter(runner)

    def after_train_iter(self, runner):
        self.after_iter(runner)

    def after_val_iter(self, runner):
        self.after_iter(runner)
	...
  • 为了方便模块管理和从config构建自定义Hook,除了需要继承Hook基类,还需要用@HOOKS.register_module()去注册模块。

 写好了自定义的Hook,就可以创建实例并注册到runner实例中使用了,MMCV中BaseRunner提供了register_hook按照priority优先级方法注册自定义的Hook。

    def register_hook(self,
                      hook: Hook,
                      priority: Union[int, str, Priority] = 'NORMAL') -> None:
        """Register a hook into the hook list.
        The hook will be inserted into a priority queue, with the specified
        priority (See :class:`Priority` for details of priorities).
        For hooks with the same priority, they will be triggered in the same
        order as they are registered.
        Args:
            hook (:obj:`Hook`): The hook to be registered.
            priority (int or str or :obj:`Priority`): Hook priority.
                Lower value means higher priority.
        """
        assert isinstance(hook, Hook)
        if hasattr(hook, 'priority'):
            raise ValueError('"priority" is a reserved attribute for hooks')
        priority = get_priority(priority)
        hook.priority = priority  # type: ignore
        # insert the hook to a sorted list
        inserted = False
        for i in range(len(self._hooks) - 1, -1, -1):
            if priority >= self._hooks[i].priority:  # type: ignore
                self._hooks.insert(i + 1, hook)
                inserted = True
                break
        if not inserted:
            self._hooks.insert(0, hook)
  • 对于那几个训练过程中需要用到的默认Hook,BaseRunner也提供了register_training_hooks去修改它们。

3. 参考资料

  • MMCV Runner源代码
  • MMCV Hook源代码
  • MMCV 核心组件分析(六): Hook

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/72829.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C语言航路】第八站:调试(第一幕)

前言 调试的这一站&#xff0c;对于市面上大部分的书籍都是缺失的&#xff0c;然而调试这个内容是非常重要的&#xff0c;尤其是在数据结构部分&#xff0c;将会频繁的使用&#xff0c;这也为我们后面讲解数据结构做一个铺垫。同时&#xff0c;在以后未来工作的时候&#xff0…

Generative Cooperative Learning for Unsupervised Video Anomaly Detection

介绍 在现实世界中&#xff0c;基于学习的异常检测任务极具挑战性&#xff0c;这主要是因为此类事件很少发生。由于这些事件的无约束性质&#xff0c;这一挑战进一步加剧。因此&#xff0c;获取足够的异常示例是相当麻烦的&#xff0c;而人们可以安全地假设&#xff0c;将永远…

Xcode9 无证书真机调试​

写在前面​ 公司分配了新的测试机,证书99台名额已满,所以上网找教程,学习了一下如何使用Xcode无证书进行真机调试。​ 一. 创建证书​ 1. 运行Xcode&#xff0c; Xcode–》Preference–》添加账号&#xff08;能在appstore下载的账号&#xff09;​ 2. 选中刚才添加的AppleID…

光华股份在深交所上市:市值突破51亿元,前三季度收入约10亿元

12月8日&#xff0c;浙江光华科技股份有限公司&#xff08;下称“光华股份”&#xff0c;SZ:001333&#xff09;在深圳证券交易所主板上市。本次上市&#xff0c;光华股份的发行价格27.76元/股&#xff0c;发行数量为3200万股&#xff0c;募资总额约为8.88亿元&#xff0c;扣除…

JDK19都出来了~是时候梳理清楚JDK的各个版本的特性了【JDK11特性讲解】

JDK各个版本特性讲解-JDK11特性 lecture&#xff1a;波哥 一、JAVA11 概述 2018年9月26日,Oracle官方发布JAVA11.这是JAVA大版本周期变化后的第一个长期支持版本,非常值得关注.最新发布的JAVA11将带来ZGC HttpClient等重要特性,一共17个需要我们关注的JEP,参考文档http://openj…

Docker_简介、优势、架构、常用命令

Docker简介 Docker是什么 Docker就是将环境在不消耗大量资源的情况下复制出一个一样的环境 一次镜像&#xff0c;处处运行 内核级虚拟化 基于GO语言实现的开源项目 解决运行环境和配置问题的软件容器 容器与虚拟机比较 虚拟机是模拟的整套操作系统&#xff0c;会有资源占用…

Unity Cg着色器开发教程

Unity Cg着色器开发教程 学习在 Unity 中对图形管道进行编程&#xff0c;以便为游戏对象创建独特的视觉表面 课程英文名&#xff1a;Shader Development from Scratch for Unity with Cg 此视频教程共2.0小时&#xff0c;中英双语字幕&#xff0c;画质清晰无水印&#xff0c…

代码随想录算法训练营第二天| 977.有序数组的平方, 209.长度最小的子数组, 59.螺旋矩阵II

代码随想录算法训练营第二天| 977.有序数组的平方&#xff0c; 209.长度最小的子数组&#xff0c; 59.螺旋矩阵II 题目链接: 977.有序数组的平方 给你一个按 非递减顺序 排序的整数数组 nums&#xff0c;返回 每个数字的平方 组成的新数组&#xff0c;要求也按 非递减顺序 排…

ASEMI整流桥KBU610和KBP210封装参数区别

编辑-Z 很多人在选型时容易把KBU和KBP给搞混&#xff0c;这两种封装是有区别的&#xff0c;下面是整流桥KBU610和KBP210封装参数区别。 整流桥KBU610参数&#xff1a; 型号&#xff1a;KBU610 封装&#xff1a;KBU-4 最大重复峰值反向电压&#xff08;VRRM&#xff09;&…

1557_AURIX_TC275_复位控制单元以及相关寄存器

全部学习汇总&#xff1a; GreyZhang/g_TC275: happy hacking for TC275! (github.com) 这是之前没看完的一张表&#xff0c;结合之前的一般看起来&#xff0c;大部分的模块还是支持重启机制的。 状态寄存器中可以读到上一次复位的触发原因&#xff0c;这个对于软件的一些状态判…

嵌入式分享合集118

一、模电--数电 晶体管 VS 二进制数 模电里面的二极管、三极管&#xff08;开关状态&#xff09;、晶闸管&#xff0c;分别对应数电的二进制数0和1。 放大器 VS 乘法/移位器 模电里的放大器就是把信号放大N倍&#xff0c;对应数电里面的乘法&#xff0c;当然如果乘的系数是2的…

C# SuperSocket 手把手教你入门 傻瓜教程---6(SuperSocket内置的命令行协议)

C# SuperSocket 手把手教你入门 傻瓜教程系列教程 C# SuperSocket 手把手教你入门 傻瓜教程---1&#xff08;服务器单向接收客户端发送数据&#xff09; C# SuperSocket 手把手教你入门 傻瓜教程---2&#xff08;服务器和客户端双向通信&#xff09; C# SuperSocket 手把手教…

No.1 初步认识Vue2.0

目录一、准备工作二、简单使用Vue2.1 初步使用Vue绑定2.2 Vue中数据绑定&#xff08;用于表单元素&#xff0c;即有value属性的元素&#xff09;2.2.1 单向数据绑定2.2.2 双向双向数据绑定2.2.3 数据绑定的简写2.3 Vue中el与data的两种书写方式2.3.1 el的写法——方式一2.3.2 e…

流体力学课上的老师这句话,让我义无反顾的上了CFD这艘“贼船”

CAE&#xff08;Computer Aided Engineering&#xff09;&#xff1a;利用计算机辅助求解分析复杂工程和产品的结构力学性能并进行优化&#xff0c;涵盖的分析对象包括结构、流体、热、电磁等。在工业4.0和中国制造2025大趋势下&#xff0c;CAE技术已上升为国家战略&#xff0c…

葡聚糖修饰线性聚乙烯亚胺(Dex-SS-LPEI)|A54修饰葡聚糖-PLGA嫁接物胶束

葡聚糖修饰线性聚乙烯亚胺(Dex-SS-LPEI)|A54修饰葡聚糖-PLGA嫁接物胶束 葡聚糖修饰线性聚乙烯亚胺(Dex-SS-LPEI) 产品描述&#xff1a;通过二硫吡啶端基化的线性聚乙烯亚胺(LPEI-SSPy,M_n5 000)与巯基化葡聚糖(Dex-SH,Mw5 000)的偶联反应,制备出生物还原响应的葡聚糖基线…

基于数字孪生概念,开启精细化城市管理模式

一座城市囊括森罗万象&#xff0c;各个领域的数据资源繁冗复杂&#xff0c;政务、经济、民生等问题层出不穷&#xff0c;管理者基本上很难对整座城市进行统一管理。由此&#xff0c;业内提出了“智慧城市”的概念。 最开始的时候&#xff0c;智慧城市可视化仅仅停留在把数据图形…

集成Springboot+Prometheus+Grafana

Springboot pom.xml导入prometheus依赖 <!--prometheus--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><dependency><groupId>io.mi…

Docker——DockerCompose简单使用

目录 一、DockerCompose 1.1 基本介绍 1.2 Centos7安装DockerCompose 1.2.1 安装 1.2.2 修改文件权限 1.2.3 Base自动补全命令 二、部署微服务集群 一、DockerCompose 1.1 基本介绍 DockerCompose&#xff1a;可以基于Compose文件帮我们快速部署分布式应用&#xff0c;而…

Mysql int(11)和Oracles nubmer(11) 的区别

先说Mysql int(11)到底代表什么意思 这里的int(11)不是表示限制int的长度为11&#xff0c;而是字符的显示宽度&#xff0c;在字段类型为int时&#xff0c;无论你显示宽度设置为多少&#xff0c;int类型能存储的最大值和最小值永远都是固定的 那么这个显示宽度到底有什么用呢&am…

vue项目中,js代码动态控制网页的link标签与title内容

最近正在开发一个微模块系统&#xff0c;产品想要根据子系统的不同&#xff0c;动态生成link标签与title中的内容&#xff0c;于是就做了一个简单的demo&#xff0c;希望分享出来给到后续有同样需求的伙伴&#xff0c;共勉。 首先肯定是需要有一套**.svg**的图标来对应相应的系…