diff --git a/docs/zh_cn/tutorials/logging.md b/docs/zh_cn/tutorials/logging.md new file mode 100644 index 0000000000000000000000000000000000000000..860d9b302c1229baf56c8d6c7748a6940850b608 --- /dev/null +++ b/docs/zh_cn/tutorials/logging.md @@ -0,0 +1,452 @@ +# 记录日志 (logging) + +## 概述 + +算法库ä¸æ—¥å¿—çš„ç§ç±»å’Œç»Ÿè®¡æ–¹å¼ä¼—多,而且需è¦ä»Žä¸åŒç»„件ä¸æ”¶é›†ç»Ÿè®¡ï¼Œæœ€åŽä»¥ç»Ÿä¸€çš„å½¢å¼å¯¼å‡ºã€‚MMEngine 设计了如下模å—æ¥æ»¡è¶³è¿™äº›å¤æ‚的需求: + +- 日志缓冲区 `LogBuffer` :用æ¥è®°å½•ç»Ÿè®¡ä¸åŒç§ç±»çš„日志 +- 全局å¯è®¿é—®åŸºç±»ï¼ˆ`BaseGlobalAccessible`):为有全局访问需求的类æ供统一的创建/获å–æŽ¥å£ +- 消æ¯æž¢çº½ï¼ˆ`MessageHub`):全局å¯è®¿é—®åŸºç±»çš„å类,用于组件之å‰çš„ä¿¡æ¯äº¤äº’ +- `MMLogger` :全局å¯è®¿é—®åŸºç±»çš„åç±»ï¼Œç”¨äºŽå¯¼å‡ºç»Ÿä¸€é£Žæ ¼çš„æ—¥å¿— + + + +## 日志类型 + +按照日志的功能划分,算法库的日志å¯ä»¥è¢«åˆ†æˆè®ç»ƒæ—¥å¿—和组件日志,å‰è€…用于观察模型的è®ç»ƒçŠ¶æ€ï¼Œä¾‹å¦‚ loss 下é™æ˜¯å¦æ£å¸¸ç‰ï¼›åŽè€…用于监测程åºçš„è¿è¡ŒçŠ¶æ€ï¼Œä¾‹å¦‚模型è¿ä»£æ—¶é—´ã€å†…å˜å 用是å¦æ£å¸¸ç‰ã€‚ + +### è®ç»ƒæ—¥å¿— + +è®ç»ƒæ—¥å¿—指模型在è®ç»ƒ/测试/推ç†è¿‡ç¨‹ä¸çš„状æ€æ—¥å¿—,包括å¦ä¹ 率(lr)ã€æŸå¤±ï¼ˆloss)ã€è¯„ä»·æŒ‡æ ‡ï¼ˆmetric) ç‰ã€‚[TensorBoard](https://www.tensorflow.org/tensorboard?hl=zh-cn) 〠[Wandb](https://wandb.ai/site) ç‰å·¥å…·èƒ½å°†è®ç»ƒæ—¥å¿—以图表的形å¼å±•ç¤ºï¼Œä¾¿äºŽæˆ‘们观察模型的è®ç»ƒæƒ…况。为了让用户能够通过修改é…置文件æ¥é€‰æ‹©è¾“出哪些日志,以åŠå¦‚何统计日志,**MMEngine** 设计了日志缓冲器和消æ¯æž¢çº½ä»¥æ”¯æŒè¿™ä¸€ç‰¹æ€§ã€‚ + +- **统一的日志å˜å‚¨æ ¼å¼** + +ä¸åŒç±»åž‹çš„日志统计方å¼ä¸åŒï¼ŒæŸå¤±ä¸€ç±»çš„日志需è¦è®°å½•åŽ†å²ä¿¡æ¯ï¼ˆç”¨äºŽå¹³æ»‘),而å¦ä¹ 率ã€åŠ¨é‡ä¹‹ç±»çš„å´ä¸éœ€è¦ã€‚å› æ¤ **MMEnging** 在确ä¿æ—¥å¿—统计方å¼çµæ´»æ€§çš„å‰æ下,抽象出了具有统一接å£çš„日志缓冲区,用户å¯ä»¥å分自然地使用日志缓冲区æ¥ç®¡ç†æ—¥å¿—ä¿¡æ¯ã€‚ + +- **跨模å—çš„æ—¥å¿—ä¼ è¾“** + +在 **MMEngine** ä¸ï¼Œå„组件使用日志缓冲区æ¥å˜å‚¨è®ç»ƒæ—¥å¿—,例如æŸå¤±ã€å¦ä¹ 率和è¿ä»£æ—¶é—´ç‰ã€‚最åŽ[日志钩å(LoggerHook)](TODO)会将这些日志会汇总导出。上述æµç¨‹æ¶‰åŠäº†ä¸åŒç»„件的信æ¯äº¤äº’, å› æ¤éœ€è¦ä¸€å¥—组件之间的消æ¯ä¼ 输方案。**MMEngine** 设计了消æ¯æž¢çº½ï¼ˆ`MessageHub`)类实现跨模å—通讯,让åŒä¸€ä¸ª[执行器](TODO)çš„ä¸åŒç»„件能够轻æ¾è¯»å†™åŒä¸€ä»½æ—¥å¿—。 + +消æ¯æž¢çº½å’Œæ—¥å¿—缓冲区的é…åˆè®©ç”¨æˆ·å¯ä»¥é€šè¿‡æ›´æ”¹é…置文件,æ¥å†³å®šå“ªäº›æ—¥å¿—需è¦è¢«è®°å½•ï¼Œä»¥ä½•ç§æ–¹å¼è¢«è®°å½•ã€‚**MMEngine** ä¸æ¶ˆæ¯æž¢çº½ ã€æ—¥å¿—缓冲区与å„组件之间的关系结构关系如下: + + + +å¯ä»¥çœ‹åˆ°æ¶ˆæ¯æž¢çº½é™¤äº†è®°å½•è®ç»ƒæ—¥å¿—(`log_buffers`)外,还会å˜å‚¨è¿è¡Œæ—¶ä¿¡æ¯ï¼ˆ`runtime_info`)。è¿è¡Œæ—¶ä¿¡æ¯ä¸»è¦æ˜¯æ‰§è¡Œå™¨çš„元信æ¯ï¼ˆmeta)ã€è¿ä»£æ¬¡æ•°ç‰ã€‚ + +### 组件日志 + +组件日志指模型è®ç»ƒè¿‡ç¨‹ä¸äº§ç”Ÿçš„所有日志,包括模型åˆå§‹åŒ–æ–¹å¼ã€æ¨¡åž‹çš„è¿ä»£è€—æ—¶ã€å†…å˜å 用ã€ç¨‹åºæŠ›å‡ºçš„è¦å‘Šå¼‚常ç‰ã€‚组件日志用于监视程åºçš„è¿è¡ŒçŠ¶æ€ï¼Œä¸€èˆ¬ä¼šåœ¨ç»ˆç«¯æ˜¾ç¤ºã€‚ä¸ºäº†è®©ç»„ä»¶æ—¥å¿—æ ¼å¼ç»Ÿä¸€ï¼Œå¹¶ä¸”能够以文本的形å¼å¯¼å‡ºåˆ°æœ¬åœ°ï¼Œ**MMEngine** 在 `logging` 模å—基础上,简化了é…ç½®æµç¨‹ï¼Œè®©ç”¨æˆ·å¯ä»¥å分简å•çš„通过 `MMLogger` 获å–记录器(`logger`)。使用 `MMLogger` 获å–的记录器具备以下优点: + +- 分布å¼è®ç»ƒæ—¶ï¼Œèƒ½å¤Ÿä¿å˜æ‰€æœ‰è¿›ç¨‹çš„组件日志,以体现ä¸åŒè¿›ç¨‹çš„è®ç»ƒçŠ¶å†µ +- 组件日志ä¸å— **OpenMMLab** 算法库外的代ç 的日志影å“,ä¸ä¼šå‡ºçŽ°å¤šé‡æ‰“å°æˆ–æ—¥å¿—æ ¼å¼ä¸ç»Ÿä¸€çš„情况 +- 错误(Error)或è¦å‘Šï¼ˆWarning)级别的日志能够输出代ç 在哪个文件的哪一行,便于用户调试,且ä¸åŒçº§åˆ«çš„日志有ä¸åŒçš„色彩高亮 + +## 日志缓冲区(LogBuffer) + +日志缓冲区(`LogBuffer`)用于å˜å‚¨ã€ç»Ÿè®¡ä¸åŒç±»åž‹çš„日志,为更新/统计æŸå¤±ã€è¿ä»£æ—¶é—´ã€å¦ä¹ 率ç‰æ—¥å¿—æ供了统一的接å£ï¼Œå¯¹å¤–接å£å¦‚下: + +- `__init__(log_history=[], count_history=[], max_length=1000000)`: `log_history`,`count_history ` å¯ä»¥æ˜¯ `list`ã€`np.ndarray` 或 `torch.Tensor`,用于åˆå§‹åŒ–日志的历å²ä¿¡æ¯ï¼ˆ`log_history`)队列和日志的历å²è®¡æ•°ï¼ˆ`count_history`)。`max_length` 为队列的最大长度。当日志的队列超过最大长度时,会èˆå¼ƒæœ€æ—©æ›´æ–°çš„日志。 + +- `update(value, count=1)`: `value` 为需è¦è¢«ç»Ÿè®¡çš„日志信æ¯ï¼Œ`count ` 为 `value ` çš„ç´¯åŠ æ¬¡æ•°ï¼Œé»˜è®¤ä¸º 1。如果 `value` å·²ç»æ˜¯æ—¥å¿—ç´¯åŠ n 次的结果(例如模型的è¿ä»£æ—¶é—´ï¼Œå®žé™…上是 batch å¼ å›¾ç‰‡çš„ç´¯è®¡è€—æ—¶ï¼‰ï¼Œéœ€è¦ä»¤ `count=n`。 +- `statistics('name', *args, **kwargs)`: 通过å—符串æ¥è®¿é—®ç»Ÿè®¡æ–¹æ³•ï¼Œä¼ å…¥å‚数必须和对应方法匹é…。 +- `register_statistics(method=None, name=None)`: 被 `register_statistics` 注册的方法能通过 `statistics` 函数调用。 +- `mean(window_size=None)`: 返回最近 `window_size` 个日志的å‡å€¼ï¼Œé»˜è®¤è¿”回全局平å‡å€¼ï¼Œå¯ä»¥é€šè¿‡ `statistics` 方法访问。 +- `min(window_size=None)`: 返回最近 `window_size` 个日志的最å°å€¼ï¼Œé»˜è®¤è¿”回全局最å°å€¼ï¼Œå¯ä»¥é€šè¿‡ `statistics` 方法访问。 +- `max(window_size=None)`: 返回最近 `window_size` 个日志的最大值,默认返回全局最大值,å¯ä»¥é€šè¿‡ `statistics` 方法访问。 +- `current()`: 返回最近一次更新的日志,å¯ä»¥é€šè¿‡ `statistics` 方法访问。 +- `data`: 返回日志历å²è®°å½•ã€‚ + +接下æ¥ç®€å•ä»‹ç»å¦‚何使用日志缓冲区记录日志。 + +### 日志缓冲区åˆå§‹åŒ– + +```python +log_buffer = LogBuffer() # 空åˆå§‹åŒ– +log_history, count_history = log_buffer.data +# [] [] +log_buffer = LogBuffer([1, 2, 3], [1, 2, 3]) # list åˆå§‹åŒ– +log_history, count_history = log_buffer.data +# [1 2 3] [1 2 3] +log_buffer = LogBuffer([1, 2, 3], [1, 2, 3], max_length=2) +log_history, count_history = log_buffer.data # 最大长度为2,åªèƒ½å˜å‚¨ [2, 3] +# [2 3] [2 3] +``` + +### 日志缓冲区更新 + +```python +log_buffer = LogBuffer([1, 2, 3], [1, 1, 1]) +log_buffer.update(4, 1) # 更新日志 +log_history, count_history = log_buffer.data +# [1, 2, 3, 4] [1, 1, 1, 1] +``` + +### 统计最大最å°å€¼ + +```python +log_buffer = LogBuffer([1, 2, 3], [1, 1, 1]) +log_buffer.min(2) +# 2,从 [2, 3] ä¸ç»Ÿè®¡æœ€å°å€¼ +log_buffer.min() +# 返回全局最å°å€¼ 1 + +log_buffer.max(2) +# 3,从 [2, 3] ä¸ç»Ÿè®¡æœ€å¤§å€¼ +log_buffer.min() +# 返回全局最大值 3 +``` + +### 统计å‡å€¼ + +```python +log_buffer = LogBuffer([1, 2, 3], [1, 1, 1]) +log_buffer.mean(2) +# 2.5,从 [2, 3] ä¸ç»Ÿè®¡å‡å€¼, (2 + 3) / (1 + 1) +log_buffer.mean() # (1 + 2 + 3) / (1 + 1 + 1) +# 返回全局å‡å€¼ 2 +log_buffer = LogBuffer([1, 2, 3], [2, 2, 2]) # 当 count ä¸ä¸º 1æ—¶ +log_buffer.mean() # (1 + 2 + 3) / (2 + 2 + 2) +# 返回å‡å€¼ 1 +``` + +### 统计最近更新的值 + +```python +log_buffer = LogBuffer([1, 2, 3], [1, 1, 1]) +log_buffer.update(4, 1) +log_buffer.current() +# 4 +``` + +### 使用ä¸åŒçš„统计方法 + +为了让用户å¯ä»¥é€šè¿‡é…置文件æ¥é€‰æ‹©æ—¥å¿—的统计方å¼ï¼Œæ—¥å¿—缓冲区æ供了 `statistics` 接å£ï¼Œå…许用户通过å—符串æ¥é€‰æ‹©æ–¹æ³•ã€‚需è¦æ³¨æ„,在调用 `statistics(name, *args, **kwargs)` 时,需è¦ä¿è¯ name 是已注册的方法å,并且å‚数和方法相匹é…。 + +```python +log_buffer = LogBuffer([1, 2, 3], [1, 1, 1]) +log_buffer.statistics('mean') +# 2 返回全局å‡å€¼ +log_buffer.statistics('mean', 2) +# 2.5 返回 [2, 3] çš„å‡å€¼ +log_buffer.statistics('mean', 2, 3) # 错误ï¼ä¼ 入了ä¸åŒ¹é…çš„å‚æ•° +log_buffer.statistics('data') # é”™è¯¯ï¼ data æ–¹æ³•æœªè¢«æ³¨å†Œï¼Œæ— æ³•è¢«è°ƒç”¨ + +``` + +### 使用日志缓冲区统计è®ç»ƒæ—¥å¿— + +```Python +logs = dict(lr=LogBuffer(), loss=LogBuffer()) # å—å…¸é…åˆ LogBuffer 记录ä¸åŒå—段的日志 +max_iter = 100 +log_interval = 20 +for iter in range(max_iter): + lr = iter / max_iter * 0.1 # 线性å¦ä¹ 率å˜åŒ– + loss = 1 / iter # loss + logs['lr'].update('lr', 1) + logs['loss'].update('loss', 1) + if iter % log_interval == 0: + latest_lr = logs['lr'].statistics('current') # 通过å—符串æ¥é€‰æ‹©ç»Ÿè®¡æ–¹æ³• + mean_loss = logs['loss'].statistics('mean', log_interval) + print(f'lr: {latest_lr}' # 平滑最新更新的 log_interval 个数æ®ã€‚ + f'loss: {mean_loss}') # 返回最近一次更新的å¦ä¹ 率。 + +``` + +### è‡ªå®šä¹‰ç»Ÿè®¡æ–¹å¼ + +考虑到数æ®çš„统计方法ä¸ä¼šè¿‡äºŽå¤æ‚ï¼Œå› æ¤ä¸æŽ¨è通过继承日志缓冲区æ¥æ–°å¢žåŠŸèƒ½ã€‚我们更倾å‘于用户使用 `LogBuffer.register_statistcs` 注册自定义的统计函数,被注册的函数å¯ä»¥é€šè¿‡ `statistics` 接å£è°ƒç”¨ã€‚ + +```Python +@LogBuffer.register_statistcs() +def custom_method(self, *args, *kwargs): + ... + +log_buffer = LogBuffer() +custom_log = log_buffer.statistics('custom_method') # 使用 statistics 接å£è°ƒç”¨è‡ªå®šæ–¹æ³•ã€‚ +``` + +## 全局å¯è®¿é—®åŸºç±»ï¼ˆBaseGlobalAccessible) + +执行器ä¸å˜åœ¨å¤šä¸ªæœ‰å…¨å±€è®¿é—®éœ€æ±‚的类,例如记录器(`MMLogger`),[ComposedWriter](https://github.com/open-mmlab/mmengine/blob/main/docs/zh_cn/tutorials/visualizer.md),和消æ¯æž¢çº½ï¼ˆ`MessageHub`ï¼‰ï¼Œå› æ¤è®¾è®¡äº†å…¨å±€å¯è®¿é—®åŸºç±»ï¼ˆ`BaseGlobalAccessible`)。继承自全局å¯è®¿é—®åŸºç±»çš„å类,å¯ä»¥é€šè¿‡ `create_instance` 方法创建实例,然åŽåœ¨ä»»æ„ä½ç½®é€šè¿‡ `get_instance` 接å£èŽ·å–。 + + + +- `create_instance(name='', *args, **kwargs)`: å½“ä¼ å…¥ `name` 时,创建指定 `name` 的实例,å¦åˆ™é»˜è®¤è¿”å›žæ ¹å®žä¾‹ã€‚è¯¥æ–¹æ³•å…¥å‚è¦æ±‚å’Œåç±»æž„é€ å‡½æ•°å®Œå…¨ç›¸åŒ +- `get_instance(name='', current=False)`: å½“ä¼ å…¥ `name` 时,返回指定 `name` 的实例,如果对应 `name` ä¸å˜åœ¨ï¼Œä¼šæŠ›å‡ºå¼‚常。当ä¸ä¼ å…¥ `name` æ—¶ï¼Œè¿”å›žæœ€è¿‘åˆ›å»ºçš„å®žä¾‹æˆ–è€…æ ¹å®žä¾‹ï¼ˆ`root_instance`)。 + +### 定义åç±» + +全局å¯è®¿é—®åŸºç±»çš„åç±»æž„é€ å‡½æ•°å¿…é¡»æŽ¥æ”¶ name å‚数,且需è¦è°ƒç”¨åŸºç±»çš„æž„é€ å‡½æ•°ï¼Œè¿™æ ·èƒ½å¤Ÿä¿è¯æ¯ä¸ªå®žä¾‹éƒ½æœ‰ name 属性,与实例一一对应。 + +```python +class GlobalAccessible(BaseGlobalAccessible): + def __init__(self, name='', *args, **kwargs): # å¿…é¡»æä¾› name å‚æ•° + super().__init__(name) + ... +``` + +### 创建实例 + +ä¸é€šè¿‡ `create_instance(name='', *args, **kwargs)` 创建的å类实例åªæ˜¯æ™®é€šå®žä¾‹ï¼Œä¸å…·å¤‡å…¨å±€è®¿é—®ç‰¹æ€§ï¼Œæ— 法通过 `get_instance` 获å–。 + +```python +instance_local = GlobalAccessible('local') +instance_local = GlobalAccessible.get_instance('local') # 错误,local ä¸æ˜¯é€šè¿‡ create_instanceåˆ›å»ºï¼Œæ— æ³•èŽ·å– +``` + +调用 `create_instance` æ—¶ï¼Œä¼ å…¥ `name` å‚数会返回对应åå—的实例,但是ä¸å…许创建é‡å¤åå—的实例。 + +```python +instance_local = GlobalAccessible.create_instance('global') +instance_local = GlobalAccessible.get_instance('global') # èƒ½å¤ŸèŽ·å– global +instance_local.instance_name # global +instance_local = GlobalAccessible.create_instance('global') # 错误,ä¸å…许é‡å¤åˆ›å»ºå…¨å±€å®žä¾‹ +``` + +ä¸ä¼ å…¥ `name` æ—¶ï¼Œåˆ™ä¼šè¿”å›žæ ¹å®žä¾‹ + +```python +instance_local = GlobalAccessible.create_instance() +instance_local.instance_name # root +``` + +### 获å–实例 + +调用 `get_instance(name='', current=False)` æ—¶ï¼Œå¦‚æžœä¼ å…¥ `name` 会返回对应的å类实例。如果对应 `name` 的实例未被创建,则会报错。 + +```python +instance = GlobalAccessible.create_instance('task1') +instance = GlobalAccessible.get_instance('task1') +instance.instance_name # task1 +instance = GlobalAccessible.get_instance('task2') # 错误,task2未被创建 +``` + +当ä¸ä¼ å…¥ `name`,且 `current=False` æ—¶ï¼Œä¼šè¿”å›žæ ¹å®žä¾‹ã€‚ + +```python +instance.instance_name # root ä¸ä¼ å‚,默认返回 root +``` + +当ä¸ä¼ å…¥ `name`,且 `current=True` 时,会返回最近一次被创建的实例。 + +```python +instance = GlobalAccessible.get_instance(current=True) # é”™è¯¯ï¼Œå°šæœªåˆ›å»ºä»»ä½•å®žä¾‹ï¼Œæ— æ³•è¿”å›žæœ€è¿‘åˆ›å»ºçš„å®žä¾‹ +instance = GlobalAccessible.create_instance('task1') +instance = GlobalAccessible.get_instance(current=True) +instance.instance_name # task1 返回 task1 最近被创建 +instance = GlobalAccessible.create_instance('task2') +instance = GlobalAccessible.get_instance(current=True) +instance.instance_name # task2 task2 最近被创建 +``` + +å¦‚æžœæ— æ³•ä¿è¯ç›®æ ‡å®žä¾‹æ˜¯æœ€è¿‘一次被创建的,使用 `get_instance(current=False)` 方法å¯èƒ½ä¼šè®¿é—®åˆ°ä¸ç¬¦åˆé¢„期的实例。 + +```python +class ModuleA: + def __init__(self, name): + self.instance = GlobalAccessible.create_instance(name) + self.module = ModuleB() + + def run_module(self): + self.module.run() + +class ModuleB: + def run(self): + instance = GlobalAccessible.get_instance(current=True) + print(f'moduleB: {instance.instance_name} is called') + + +if __name__ == '__main__': + a1 = ModuleA('a1') + a2 = ModuleA('a2') + a1.run_module() # moduleB: a2 is called,命å是 a1 è¿è¡Œï¼Œå´èŽ·å–了 a2的实例 +``` + +对于上述情况,建议用户将全局实例实例固化为类的属性,在åˆå§‹åŒ–阶段完æˆå¯¹åº”实例的绑定。 + +```python +class ModuleA: + def __init__(self, name): + self.instance = GlobalAccessible.create_instance(name) + self.module = ModuleB() + + def run_module(self): + self.module.run() + +class ModuleB: + def __init__(self): + self.instance = GlobalAccessible.get_instance(current=True) + + def run(self): + print(f'moduleB: {self.instance.instance_name} is called') + + +if __name__ == '__main__': + a1 = ModuleA('a1') + a2 = ModuleA('a2') + a1.run_module() # moduleB: a1 is called,åˆå§‹åŒ–阶段绑定,确ä¿åŽç»è®¿é—®åˆ°æ£ç¡®å®žä¾‹ã€‚ +``` + +## 消æ¯æž¢çº½ï¼ˆMessageHub) + +日志缓冲区(`LogBuffer`)å¯ä»¥å分简å•çš„完æˆå•ä¸ªæ—¥å¿—的更新和统计,而在模型è®ç»ƒè¿‡ç¨‹ä¸ï¼Œæ—¥å¿—çš„ç§ç±»ç¹å¤šï¼Œå¹¶ä¸”æ¥è‡ªäºŽä¸åŒçš„ç»„ä»¶ï¼Œå› æ¤å¦‚何完æˆæ—¥å¿—的分å‘和收集是需è¦è€ƒè™‘的问题。 **MMEngine** 使用全局å¯è®¿é—®çš„消æ¯æž¢çº½ï¼ˆ`MessageHub`)æ¥å®žçŽ°ç»„件与组件ã€æ‰§è¡Œå™¨ä¸Žæ‰§è¡Œå™¨ä¹‹é—´çš„互è”互通。消æ¯æž¢çº½ä¸ä»…会管ç†å„个模å—分å‘的日志缓冲区,还会记录è¿è¡Œæ—¶ä¿¡æ¯ä¾‹å¦‚执行器的元信æ¯ï¼Œè¿ä»£æ¬¡æ•°ç‰ã€‚è¿è¡Œæ—¶ä¿¡æ¯æ¯æ¬¡æ›´æ–°éƒ½ä¼šè¢«è¦†ç›–。消æ¯æž¢çº½ç»§æ‰¿è‡ªå…¨å±€å¯è®¿é—®åŸºç±»ï¼Œå…¶å¯¹å¤–接å£å¦‚下 + +- `update_log(key, value, count=1)`: 更新指定å—段的日志缓冲区。`value`,`count` 对应 `LogBuffer.update` 接å£çš„å…¥å‚。该方法用于更新è®ç»ƒæ—¥å¿—,例如å¦ä¹ 率ã€æŸå¤±ã€è¿ä»£æ—¶é—´ç‰ã€‚ +- `update_info(key, value)`: æ›´æ–°è¿è¡Œæ—¶ä¿¡æ¯ï¼Œä¾‹å¦‚执行器的元信æ¯ã€è¿ä»£æ¬¡æ•°ç‰ã€‚è¿è¡Œæ—¶ä¿¡æ¯æ¯æ¬¡æ›´æ–°éƒ½ä¼šè¦†ç›–上一次的内容。 +- `get_log(key)`: 获å–指定å—段的日志。 +- `get_info(key)`: 获å–指定å—段的è¿è¡Œæ—¶ä¿¡æ¯ã€‚ +- `log_buffers`: 返回所有日志 +- `runtime_info`: 返回所有è¿è¡Œæ—¶ä¿¡æ¯ã€‚ + +### æ›´æ–°/获å–日志 + +日志缓冲区以å—典的形å¼å˜å‚¨åœ¨æ¶ˆæ¯æž¢çº½ä¸ã€‚当我们第一次调用 `update_log` 时,会åˆå§‹åŒ–对应å—段的日志缓冲区,åŽç»æ¯æ¬¡æ›´æ–°ç‰ä»·äºŽå¯¹åº”å—段的日志缓冲区调用 `update` 方法。åŒæ ·çš„我们å¯ä»¥é€šè¿‡ `get_log` æ¥èŽ·å–对应å—段的日志缓冲区,并按需计算统计值。如果想获å–消æ¯æž¢çº½çš„全部日志,å¯ä»¥è®¿é—®å…¶ `log_buffers` 属性。 + +```python +message_hub = MessageHub.create_instance('task') +message_hub.update_log('loss', 1, 1) +message_hub.get_log('loss').current() # 1,最近一次更新值为 1 +message_hub.update_log('loss', 3, 1) +message_hub.get_log('loss').mean() # 2,å‡å€¼ä¸º (3 + 1) / (1 +1) +message_hub.update_log('lr', 0.1, 1) + +log_dict = message_hub.log_buffers # 返回å˜å‚¨å…¨éƒ¨ LogBuffer çš„å—å…¸ +lr_buffer, loss_buffer = log_dict['lr'], log_dict['loss'] +``` + +### æ›´æ–°/获å–è¿è¡Œæ—¶ä¿¡æ¯ + +è¿è¡Œæ—¶ä¿¡æ¯ä»¥å—典的形å¼å˜å‚¨åœ¨æ¶ˆæ¯æž¢çº½ä¸ï¼Œæ”¯æŒä»»æ„æ•°æ®ç±»åž‹ï¼Œæ¯æ¬¡æ›´æ–°éƒ½ä¼šè¦†ç›–。 + +```python +message_hub = MessageHub.create_instance('task') +message_hub.update_info('meta', dict(task=task)) # æ›´æ–° meta +message_hub.get_info('meta') # {'task'='task'} èŽ·å– meta +message_hub.update_info('meta', dict(task=task1)) # 覆盖 meta +message_hub.get_info('meta') # {'task'='task1'} 之å‰çš„ä¿¡æ¯è¢«è¦†ç›– + +runtime_dict = message_hub.rumtime_info # 返回å˜å‚¨å…¨éƒ¨ LogBuffer çš„å—å…¸ +meta = log_dict['meta'] +``` + +### 消æ¯æž¢çº½çš„跨组件通讯 + +执行器è¿è¡Œè¿‡ç¨‹ä¸ï¼Œå„个组件会通过消æ¯æž¢çº½æ¥åˆ†å‘ã€æŽ¥å—消æ¯ã€‚日志钩å会汇总其他组件更新的å¦ä¹ 率ã€æŸå¤±ç‰ä¿¡æ¯ï¼Œå°†å…¶å¯¼å‡ºåˆ°ç”¨æˆ·æŒ‡å®šçš„写端(Tensorboard,Wandb ç‰ï¼‰ã€‚由于上述æµç¨‹è¾ƒä¸ºå¤æ‚,这里用一个简å•ç¤ºä¾‹æ¥æ¨¡æ‹Ÿæ—¥å¿—é’©å和其他组件通讯的过程。 + +```python +class Receiver: + # 汇总ä¸åŒæ¨¡å—更新的消æ¯ï¼Œç±»ä¼¼ LoggerHook + def __init__(self, name): + self.message_hub = MessageHub.get_instance(name) # èŽ·å– MessaeHub + + def run(self): + print(f"Learning rate is {self.message_hub.get_log('lr').current()}") + print(f"Learning rate is {self.message_hub.get_log('loss').current()}") + print(f"Learning rate is {self.message_hub.get_info('meta')}") + + +class LrUpdater: + # æ›´æ–°å¦ä¹ 率 + def __init__(self, name): + self.message_hub = MessageHub.get_instance(name) # èŽ·å– MessaeHub + + def run(self): + self.message_hub.update_log('lr', 0.001) # æ›´æ–°å¦ä¹ 率,以 LogBuffer å½¢å¼å˜å‚¨ + +class MetaUpdater: + # æ›´æ–°å…ƒä¿¡æ¯ + def __init__(self, name): + self.message_hub = MessageHub.get_instance(name) + + def run(self): + self.message_hub.update_info( + 'meta', + dict(experiment='retinanet_r50_caffe_fpn_1x_coco.py', + repo='mmdetection')) # 更新元信æ¯ï¼Œæ¯æ¬¡æ›´æ–°ä¼šè¦†ç›–ä¸Šä¸€æ¬¡çš„ä¿¡æ¯ + +class LossUpdater: + # æ›´æ–°æŸå¤±å‡½æ•° + def __init__(self, name): + self.message_hub = MessageHub.get_instance(name) + + def run(self): + self.message_hub.update_log('loss', 0.1) + +class Task: + # 组åˆä¸ªå„ä¸ªæ¨¡å— + def __init__(self, name): + self.message_hub = MessageHub.create_instance(name) # 创建 MessageHub + self.receiver = Receiver(name) + self.updaters = [LossUpdater(name), + MetaUpdater(name), + LrUpdater(name)] + + def run(self): + for updater in self.updaters: + updater.run() + self.receiver.run() + +if __name__ == '__main__': + task = Task('name') + task.run() + # Learning rate is 0.001 + # Learning rate is 0.1 + # Learning rate is {'experiment': 'retinanet_r50_caffe_fpn_1x_coco.py', 'repo': 'mmdetection'} + +``` + +## 记录器(MMLogger) + +为了能够导出层次分明ã€æ ¼å¼ç»Ÿä¸€çš„组件日志,**MMEnging** 在 `logging` 模å—的基础上设计了 `MMLogger`,其继承于 `BaseGlobalAccessible` å’Œ `logging.Logger`。由 `MMLogger.get_instance` 获å–çš„è®°å½•å™¨å…·å¤‡ç»Ÿä¸€çš„æ—¥å¿—æ ¼å¼ï¼Œä¸”ä¸ä¼šç»§æ‰¿ `logging.root` ï¼Œå› æ¤ä¸ä¼šå—åˆ°ç¬¬ä¸‰æ–¹åº“ä¸ logger é…置的影å“。 + + + +`MMLogger` åœ¨æž„é€ å‡½æ•°ä¸å®Œæˆäº†è®°å½•å™¨çš„é…置,除了`BaseGlobalAccessible` å’Œ `logging.Logger` 的基类接å£å¤–,没有æä¾›é¢å¤–的接å£ã€‚`MMLogger` 创建/获å–çš„æ–¹å¼å’Œæ¶ˆæ¯æž¢çº½ç›¸åŒï¼Œæ¤å¤„ä¸å†èµ˜è¿°ï¼Œæˆ‘们主è¦ä»‹ç»é€šè¿‡ `MMLogger.create_instance` 获å–的记录器具备哪些功能。 + +### æ—¥å¿—æ ¼å¼ + +```python +logger = MMLogger.create_instance('mmengine') +logger.info("this is a test") +# 2022-02-20 22:26:38,860 - mmengine - INFO - this is a test +``` + +记录器除了输出消æ¯å¤–,还会é¢å¤–输出时间戳ã€è®°å½•å™¨åå—和日志ç‰çº§ã€‚对于 ERROR ç‰çº§çš„日志,我们会用红色高亮日志ç‰çº§ï¼Œå¹¶é¢å¤–输出日志的代ç ä½ç½® + +```python +logger = MMLogger.create_instance('mmengine') +logger.error('division by zero') +#2022-02-20 22:49:11,317 - mmengine - ERROR - Found error in file: /path/to/test_logger.py function:div line: 111 “division by zero†+``` + +### 导出日志 + +调用 create_instance 时,如果指定了 log_file,会将日志记录的信æ¯ä»¥æ–‡æœ¬æ ¼å¼å¯¼å‡ºåˆ°æœ¬åœ°ã€‚ + +```Python +logger = MMLogger.create_instance('mmengine', log_file='tmp.log') +logger.info("this is a test") +# 2022-02-20 22:26:38,860 - mmengine - INFO - this is a test +``` + +`tmp.log`: + +```text +2022-02-20 22:26:38,860 - mmengine - INFO - this is a test +``` + +### 分布å¼è®ç»ƒæ—¶å¯¼å‡ºæ—¥å¿— + +在使用分布å¼è®ç»ƒæ—¶ï¼Œä¸åŒçš„进程会导出ä¸åŒçš„日志,日志文件å为执行器实例化时的时间戳。除主进程外(`rank=0`),æ¯ä¸ªè¿›ç¨‹çš„日志å的抬头为å„自的 rank 数。 + +```text +./work_dir +├── rank1_20220228_121221.log +├── rank2_20220228_121221.log +├── rank3_20220228_121221.log +├── rank4_20220228_121221.log +├── rank5_20220228_121221.log +├── rank6_20220228_121221.log +├── rank7_20220228_121221.log +└── 20220228_121221.log +```