Skip to content
Snippets Groups Projects
Unverified Commit 44538e56 authored by Mashiro's avatar Mashiro Committed by GitHub
Browse files

[Doc]: refine logging doc (#320)

parent e1422a34
No related branches found
No related tags found
No related merge requests found
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
## 概述 ## 概述
[执行器(Runner)](TODO)在运行过程中会产生很多日志,例如加载的数据集信息、模型的初始化信息、训练过程中的学习率、损失等。为了让用户能够更加自由的获取这些日志信息,MMEngine 实现了消息枢纽(MessageHub)、历史缓冲区(HistoryBuffer)、日志处理器(LogProcessor) 和记录器(MMLogger)来支持以下功能: [执行器(Runner)](https://mmengine.readthedocs.io/zh_CN/latest/tutorials/runner.html)在运行过程中会产生很多日志,例如加载的数据集信息、模型的初始化信息、训练过程中的学习率、损失等。为了让用户能够更加自由的获取这些日志信息,MMEngine 实现了消息枢纽(MessageHub)、历史缓冲区(HistoryBuffer)、日志处理器(LogProcessor) 和记录器(MMLogger)来支持以下功能:
- 用户可以通过配置文件,根据个人偏好来选择日志统计方式,例如在终端输出整个训练过程中的平均损失而不是基于固定迭代次数平滑的损失 - 用户可以通过配置文件,根据个人偏好来选择日志统计方式,例如在终端输出整个训练过程中的平均损失而不是基于固定迭代次数平滑的损失
- 用户可以在任意组件中获取当前的训练状态,例如当前的迭代次数、训练轮次等 - 用户可以在任意组件中获取当前的训练状态,例如当前的迭代次数、训练轮次等
...@@ -10,17 +10,17 @@ ...@@ -10,17 +10,17 @@
![image](https://user-images.githubusercontent.com/57566630/163441489-47999f3a-3259-44ab-949c-77a8a599faa5.png) ![image](https://user-images.githubusercontent.com/57566630/163441489-47999f3a-3259-44ab-949c-77a8a599faa5.png)
训练过程中的产生的损失、学习率等数据由历史缓冲区管理和封装,汇总后交给消息枢纽维护。日志处理器将消息枢纽中的数据进行格式化,最后通过[记录器钩子(LoggerHook)](TODO) 展示到各种可视化后端。**一般情况下用户无需感知数据处理流程,可以直接通过配置日志处理器来选择日志的统计方式** 训练过程中的产生的损失、学习率等数据由历史缓冲区管理和封装,汇总后交给消息枢纽维护。日志处理器将消息枢纽中的数据进行格式化,最后通过[记录器钩子(LoggerHook)](https://mmengine.readthedocs.io/zh_CN/latest/tutorials/hook.html) 展示到各种可视化后端。**一般情况下用户无需感知数据处理流程,可以直接通过配置日志处理器来选择日志的统计方式**
## 历史缓冲区(HistoryBuffer) ## 历史缓冲区(HistoryBuffer)
MMEngine 实现了历史数据存储的抽象类历史缓冲区(HistoryBuffer),用于存储训练日志的历史轨迹,如模型损失、优化器学习率、迭代时间等。通常情况下,历史缓冲区作为内部类,配合消息枢纽(MessageHub)、[记录器钩子(LoggerHook )](TODO)和日志处理器(LogProcessor) 实现了训练日志的可配置化。 MMEngine 实现了历史数据存储的抽象类历史缓冲区(HistoryBuffer),用于存储训练日志的历史轨迹,如模型损失、优化器学习率、迭代时间等。通常情况下,历史缓冲区作为内部类,配合消息枢纽(MessageHub)、[记录器钩子(LoggerHook )](https://mmengine.readthedocs.io/zh_CN/latest/tutorials/hook.html)和日志处理器(LogProcessor) 实现了训练日志的可配置化。
用户也可以单独使用历史缓冲区来管理训练日志,能够非常简单的使用不同方法来统计训练日志。我们先来介绍如何单独使用历史缓冲区,在消息枢纽一节再进一步介绍二者的联动。 用户也可以单独使用历史缓冲区来管理训练日志,能够非常简单的使用不同方法来统计训练日志。我们先来介绍如何单独使用历史缓冲区,在消息枢纽一节再进一步介绍二者的联动。
### 历史缓冲区初始化 ### 历史缓冲区初始化
历史缓冲区的初始化可以接受 `log_history` `count_history` 两个参数。`log_history` 表示日志的历史轨迹,例如前三次迭代的 loss 为 0.3,0.2,0.1。我们就可以记 `log_history=[0.3, 0.2, 0.3]``count_history` 是一个比较抽象的概念,如果按照迭代次数来算,0.3,0.2,0.1 分别是三次迭代的结果,那么我们可以记 `count_history=[1, 1, 1]`,其中 “1” 表示一次迭代。如果按照 batch 来算,例如每次迭代的 `batch_size` 为 8,那么 `count_history=[8, 8, 8]``count_history` 只会在统计均值时用到,用于控制返回均值的粒度。就拿上面那个例子来说,`count_history=[1, 1, 1]` 时会统计每次迭代的平均 loss,而 `count_history=[8, 8, 8]` 则会统计每张图片的平均 loss。 历史缓冲区的初始化可以接受 `log_history``count_history` 两个参数。`log_history` 表示日志的历史轨迹,例如前三次迭代的 loss 为 0.3,0.2,0.1。我们就可以记 `log_history=[0.3, 0.2, 0.3]``count_history` 是一个比较抽象的概念,如果按照迭代次数来算,0.3,0.2,0.1 分别是三次迭代的结果,那么我们可以记 `count_history=[1, 1, 1]`,其中 “1” 表示一次迭代。如果按照 batch 来算,例如每次迭代的 `batch_size` 为 8,那么 `count_history=[8, 8, 8]``count_history` 只会在统计均值时用到,用于控制返回均值的粒度。就拿上面那个例子来说,`count_history=[1, 1, 1]` 时会统计每次迭代的平均 loss,而 `count_history=[8, 8, 8]` 则会统计每张图片的平均 loss。
```python ```python
from mmengine.logging import HistoryBuffer from mmengine.logging import HistoryBuffer
...@@ -110,7 +110,7 @@ history_buffer.statistics('weighted_mean', 2, [2, 1]) # get (2 * 1 + 1 * 2) / ( ...@@ -110,7 +110,7 @@ history_buffer.statistics('weighted_mean', 2, [2, 1]) # get (2 * 1 + 1 * 2) / (
### 统计方法的统一入口 ### 统计方法的统一入口
要想支持在配置文件中通过配置 'max','min' 等字段来选择日志的统计方式,那么 HistoryBuffer 就需要一个接口来接受 'min','max' 等统计方法字符串和相应参数,进而找到对应的统计方法,最后输出统计结果。`statistics(name, *args, **kwargs)` 接口就起到了这个作用。其中 name 是已注册的方法名(基本统计方法已经被注册),`*arg``**kwarg` 用于接受对应方法的参数。 要想支持在配置文件中通过配置 'max','min' 等字段来选择日志的统计方式,那么 HistoryBuffer 就需要一个接口来接受 'min','max' 等统计方法字符串和相应参数,进而找到对应的统计方法,最后输出统计结果。`statistics(name, *args, **kwargs)` 接口就起到了这个作用。其中 name 是已注册的方法名(已经注册 `min``max`基本统计方法),`*arg``**kwarg` 用于接受对应方法的参数。
```python ```python
history_buffer = HistoryBuffer([1, 2, 3], [1, 1, 1]) history_buffer = HistoryBuffer([1, 2, 3], [1, 1, 1])
...@@ -150,11 +150,11 @@ MMEngine 利用历史缓冲区的特性,结合消息枢纽,实现了训练 ...@@ -150,11 +150,11 @@ MMEngine 利用历史缓冲区的特性,结合消息枢纽,实现了训练
## 消息枢纽(MessageHub) ## 消息枢纽(MessageHub)
历史缓冲区(HistoryBuffer)可以十分简单地完成单个日志的更新和统计,而在模型训练过程中,日志的种类繁多,并且来自于不同的组件,因此如何完成日志的分发和收集是需要考虑的问题。 MMEngine 使用消息枢纽(MessageHub)来实现组件与组件、执行器与执行器之间的数据共享。消息枢纽继承自全局管理器(ManageMixin),支持跨模块访问。 历史缓冲区(HistoryBuffer)可以十分简单地完成单个日志的更新和统计,而在模型训练过程中,日志的种类繁多,并且来自于不同的组件,因此如何完成日志的分发和收集是需要考虑的问题。 MMEngine 使用消息枢纽(MessageHub)来实现组件与组件、执行器与执行器之间的数据共享。消息枢纽继承自全局管理器(ManageMixin),支持跨模块访问。
消息枢纽存储了两种含义的数据: 消息枢纽存储了两种含义的数据:
- 历史缓冲区字典:消息枢纽会收集各个模块更新的训练日志,如损失、学习率、迭代时间,并将其更新至内部的历史缓冲区字典中。历史缓冲区字典经[消息处理器(LogProcessor)](TODO)处理后,会被输出到终端/保存到本地。如果用户需要记录自定义日志,可以往历史缓冲区字典中更新相应内容。 - 历史缓冲区字典:消息枢纽会收集各个模块更新的训练日志,如损失、学习率、迭代时间,并将其更新至内部的历史缓冲区字典中。历史缓冲区字典经[消息处理器(LogProcessor)](#%E6%97%A5%E5%BF%97%E5%A4%84%E7%90%86%E5%99%A8%EF%BC%88LogProcessor%EF%BC%89)处理后,会被输出到终端/保存到本地。如果用户需要记录自定义日志,可以往历史缓冲区字典中更新相应内容。
- 运行时信息字典:运行时信息字典用于存储迭代次数、训练轮次等运行时信息,方便 MMEngine 中所有组件共享这些信息。 - 运行时信息字典:运行时信息字典用于存储迭代次数、训练轮次等运行时信息,方便 MMEngine 中所有组件共享这些信息。
```{note} ```{note}
...@@ -208,7 +208,7 @@ message_hub.get_info('iter') # 2 覆盖上一次结果 ...@@ -208,7 +208,7 @@ message_hub.get_info('iter') # 2 覆盖上一次结果
### 消息枢纽的跨组件通讯 ### 消息枢纽的跨组件通讯
执行器运行过程中,各个组件会通过消息枢纽来分发、接受消息。日志钩子会汇总其他组件更新的学习率、损失等信息,将其导出到用户指定的输出端(Tensorboard,Wandb 等)。由于上述流程较为复杂,这里用一个简单示例来模拟日志钩子和其他组件通讯的过程。 执行器运行过程中,各个组件会通过消息枢纽来分发、接受消息。[RuntimeInfoHook](https://mmengine.readthedocs.io/zh_CN/latest/api.html#mmengine.hooks.RuntimeInfoHook) 会汇总其他组件更新的学习率、损失等信息,将其导出到用户指定的输出端(Tensorboard,Wandb 等)。由于上述流程较为复杂,这里用一个简单示例来模拟日志钩子和其他组件通讯的过程。
```python ```python
from mmengine import MessageHub from mmengine import MessageHub
...@@ -278,7 +278,11 @@ if __name__ == '__main__': ...@@ -278,7 +278,11 @@ if __name__ == '__main__':
### 添加自定义日志 ### 添加自定义日志
我们可以在任意模块里更新消息枢纽的历史缓冲区字典,历史缓冲区字典中所有内容的统计结果都会在终端显示 我们可以在任意模块里更新消息枢纽的历史缓冲区字典,历史缓冲区字典中所有的字段经统计后最后显示到终端。
```{note}
更新历史缓冲区字典时,需要保证更新的日志名带有 train,val,test 前缀,否则日志不会在终端显示。
```
```python ```python
class CustomModule: class CustomModule:
...@@ -290,17 +294,17 @@ class CustomModule: ...@@ -290,17 +294,17 @@ class CustomModule:
self.message_hub.update_scalars({'train/b': 1, 'train/c': 2}) self.message_hub.update_scalars({'train/b': 1, 'train/c': 2})
``` ```
默认情况下,终端上额外显示 a、b、c最后一次更新的结果。我们也可以通过配置日志处理器来切换自定义日志的统计方式。 默认情况下,终端上额外显示 a、b、c 最后一次更新的结果。我们也可以通过配置[日志处理器](#%E6%97%A5%E5%BF%97%E5%A4%84%E7%90%86%E5%99%A8%EF%BC%88LogProcessor%EF%BC%89)来切换自定义日志的统计方式。
## 日志处理器(LogProcessor) ## 日志处理器(LogProcessor)
用户可以通过配置日志处理器(LogProcessor)来控制日志的统计窗口和统计方法。默认配置下,日志处理器会统计最近一次更新的学习率、基于迭代次数平滑的损失和迭代时间。用户可以在日志处理器中配置已知字段的统计方式。 用户可以通过配置日志处理器(LogProcessor)来控制日志的统计方法及其参数。默认配置下,日志处理器会统计最近一次更新的学习率、基于迭代次数平滑的损失和迭代时间。用户可以在日志处理器中配置已知字段的统计方式。
### 最简配置 ### 最简配置
```python ```python
log_processor = dict( log_processor = dict(
interval=10, window_size=10,
) )
``` ```
...@@ -314,8 +318,8 @@ log_processor = dict( ...@@ -314,8 +318,8 @@ log_processor = dict(
我们可以通过配置 `custom_cfg` 列表来选择日志的统计方式。`custom_cfg` 中的每一个元素需要包括以下信息: 我们可以通过配置 `custom_cfg` 列表来选择日志的统计方式。`custom_cfg` 中的每一个元素需要包括以下信息:
- `data_src`:日志的数据源,用户通过指定 `data_src` 来选择需要被重新统计的日志(必填项) - `data_src`:日志的数据源,用户通过指定 `data_src` 来选择需要被重新统计的日志,一份数据源可以有多种统计方式(必填项)
- `method_name`:统计方法,即历史缓冲区中的基本统计方法以及用户注册的自定义统计方法(必填项) - `method_name`日志的统计方法,即历史缓冲区中的基本统计方法以及用户注册的自定义统计方法(必填项)
- `log_name`:日志被重新统计后的名字,如果不定义 `log_name`,新日志会覆盖旧日志(选填项) - `log_name`:日志被重新统计后的名字,如果不定义 `log_name`,新日志会覆盖旧日志(选填项)
- 其他参数:统计方法会用到的参数,其中 `window_size` 为特殊字段,可以为普通的整型、字符串 epoch 和字符串 global。LogProcessor 会实时解析这些参数,以返回基于 iteration、epoch 和全局平滑的统计结果(选填项) - 其他参数:统计方法会用到的参数,其中 `window_size` 为特殊字段,可以为普通的整型、字符串 epoch 和字符串 global。LogProcessor 会实时解析这些参数,以返回基于 iteration、epoch 和全局平滑的统计结果(选填项)
...@@ -331,7 +335,7 @@ log_processor = dict( ...@@ -331,7 +335,7 @@ log_processor = dict(
window_size=100)]) window_size=100)])
``` ```
此时会无视日志处理器的默认窗口 10,用更大的窗口 100去统计 loss 的均值,并将原有结果覆盖。 此时会无视日志处理器的默认窗口 10,用更大的窗口 100 去统计 loss 的均值,并将原有结果覆盖。
```bash ```bash
04/15 12:34:24 - mmengine - INFO - Iter [10/12] , eta: 0:00:00, time: 0.003, data_time: 0.002, loss: 0.11 04/15 12:34:24 - mmengine - INFO - Iter [10/12] , eta: 0:00:00, time: 0.003, data_time: 0.002, loss: 0.11
...@@ -345,7 +349,7 @@ log_processor = dict( ...@@ -345,7 +349,7 @@ log_processor = dict(
by_epoch=True, by_epoch=True,
custom_cfg=[ custom_cfg=[
dict(data_src='loss', dict(data_src='loss',
log_name='loss_min' log_name='loss_min',
method_name='min', method_name='min',
window_size=100)]) window_size=100)])
``` ```
...@@ -356,7 +360,7 @@ log_processor = dict( ...@@ -356,7 +360,7 @@ log_processor = dict(
## 记录器(MMLogger) ## 记录器(MMLogger)
为了能够导出层次分明、格式统一、且不受三方库日志系统干扰的日志,MMEngine 在 `logging` 模块的基础上实现了 `MMLogger``MMLogger` 继承自全局管理器(`ManagerMixin`),相比于 `logging.Logger`能够在无法获取记录器名(logger name)的情况下,拿到当前执行器的记录器。 为了能够导出层次分明、格式统一、且不受三方库日志系统干扰的日志,MMEngine 在 `logging` 模块的基础上实现了 `MMLogger``MMLogger` 继承自全局管理器(`ManagerMixin`),相比于 `logging.Logger``MMLogger` 能够在无法获取记录器名(logger name)的情况下,拿到当前执行器的记录器。
### 创建记录器 ### 创建记录器
...@@ -396,7 +400,7 @@ logger.info("this is a test") ...@@ -396,7 +400,7 @@ logger.info("this is a test")
### 分布式训练时导出日志 ### 分布式训练时导出日志
我们可以通过配置 `distributed=True` 来导出分布式日志(默认关闭)。 使用 pytorch 分布式训练时,我们可以通过配置 `distributed=True` 来导出分布式训练时各个进程的日志(默认关闭)。
```python ```python
logger = MMLogger.get_instance('mmengine', log_file='tmp.log', distributed=True, log_level='INFO') logger = MMLogger.get_instance('mmengine', log_file='tmp.log', distributed=True, log_level='INFO')
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
## 全局管理器(ManagerMixin) ## 全局管理器(ManagerMixin)
Runner 在训练过程中,难免会使用全局变量来共享信息,例如我们会在 model 中获取全局的 [logger](TODO) 来打印初始化信息;在 model 中获取全局的 [Visualizer](TODO) 来可视化预测结果、特征图;在 [Reigistry](TODO) 中获取全局的 [DefaultScope](TODO) 来确定注册域。为了管理这些功能相似的模块,MMEngine 实现了管理器(ManagerMix)来统一全局变量的创建和获取方式。 Runner 在训练过程中,难免会使用全局变量来共享信息,例如我们会在 model 中获取全局的 [logger](https://mmengine.readthedocs.io/zh/latest/api.html#mmengine.logging.MMLogger) 来打印初始化信息;在 model 中获取全局的 [Visualizer](https://mmengine.readthedocs.io/zh_CN/latest/tutorials/visualization.html) 来可视化预测结果、特征图;在 [Reigistry](https://mmengine.readthedocs.io/zh_CN/latest/tutorials/registry.html) 中获取全局的 [DefaultScope](https://mmengine.readthedocs.io/zh/latest/api.html#mmengine.registry.DefaultScope) 来确定注册域。为了管理这些功能相似的模块,MMEngine 实现了管理器(ManagerMix)来统一全局变量的创建和获取方式。
![ManagerMixin](https://user-images.githubusercontent.com/57566630/163429552-3c901fc3-9cc1-4b71-82b6-d051f452a538.png) ![ManagerMixin](https://user-images.githubusercontent.com/57566630/163429552-3c901fc3-9cc1-4b71-82b6-d051f452a538.png)
...@@ -40,7 +40,7 @@ class CustomHook(Hook): ...@@ -40,7 +40,7 @@ class CustomHook(Hook):
GlobalClass.get_instance(runner.experiment_name, value=100) GlobalClass.get_instance(runner.experiment_name, value=100)
``` ```
当我们调用子类的 get_instance 接口时,`ManagerMixin` 会根据名字来判断对应实例是否已经存在,进而创建/获取实例。如上例所示,当我们第一次调用 `GlobalClass.get_instance('mmengine', value=50)` 时,会创建一个名为 "mmengine" 的 `GlobalClass` 实例,其初始 value 为 50。为了方便后续介绍 `get_current_instance` 接口,这里我们创建了两个 `GlobalClass` 实例。 当我们调用子类的 `get_instance` 接口时,`ManagerMixin` 会根据名字来判断对应实例是否已经存在,进而创建/获取实例。如上例所示,当我们第一次调用 `GlobalClass.get_instance('mmengine', value=50)` 时,会创建一个名为 "mmengine" 的 `GlobalClass` 实例,其初始 value 为 50。为了方便后续介绍 `get_current_instance` 接口,这里我们创建了两个 `GlobalClass` 实例。
3. 在任意组件中访问该实例 3. 在任意组件中访问该实例
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment