diff --git a/docs/zh_cn/tutorials/logging.md b/docs/zh_cn/tutorials/logging.md index 45c047d63713f7c4ebf293f0c48ea7c79e8a0d2e..4d50ba6b718515de1515181dfe7b0d2358a9e6d4 100644 --- a/docs/zh_cn/tutorials/logging.md +++ b/docs/zh_cn/tutorials/logging.md @@ -2,334 +2,208 @@ ## 概述 -算法库ä¸æ—¥å¿—çš„ç§ç±»å’Œç»Ÿè®¡æ–¹å¼ä¼—多,而且需è¦ä»Žä¸åŒç»„件ä¸æ”¶é›†ç»Ÿè®¡ï¼Œæœ€åŽä»¥ç»Ÿä¸€çš„å½¢å¼å¯¼å‡ºã€‚MMEngine 设计了如下模å—æ¥æ»¡è¶³è¿™äº›å¤æ‚的需求: +[执行器(Runner)](TODO)在è¿è¡Œè¿‡ç¨‹ä¸ä¼šäº§ç”Ÿå¾ˆå¤šæ—¥å¿—ï¼Œä¾‹å¦‚åŠ è½½çš„æ•°æ®é›†ä¿¡æ¯ã€æ¨¡åž‹çš„åˆå§‹åŒ–ä¿¡æ¯ã€è®ç»ƒè¿‡ç¨‹ä¸çš„å¦ä¹ 率ã€æŸå¤±ç‰ã€‚ä¸ºäº†è®©ç”¨æˆ·èƒ½å¤Ÿæ›´åŠ è‡ªç”±çš„èŽ·å–这些日志信æ¯ï¼ŒMMEngine 实现了消æ¯æž¢çº½ï¼ˆMessageHub)ã€åŽ†å²ç¼“冲区(HistoryBuffer)ã€æ—¥å¿—处ç†å™¨ï¼ˆLogProcessor) 和记录器(MMLogger)æ¥æ”¯æŒä»¥ä¸‹åŠŸèƒ½ï¼š -- 日志缓冲区 `LogBuffer` :用æ¥è®°å½•ç»Ÿè®¡ä¸åŒç§ç±»çš„日志 -- 全局å¯è®¿é—®åŸºç±»ï¼ˆ`BaseGlobalAccessible`):为有全局访问需求的类æ供统一的创建/获å–æŽ¥å£ -- 消æ¯æž¢çº½ï¼ˆ`MessageHub`):全局å¯è®¿é—®åŸºç±»çš„å类,用于组件之å‰çš„ä¿¡æ¯äº¤äº’ -- `MMLogger` :全局å¯è®¿é—®åŸºç±»çš„åç±»ï¼Œç”¨äºŽå¯¼å‡ºç»Ÿä¸€é£Žæ ¼çš„æ—¥å¿— +- 用户å¯ä»¥é€šè¿‡é…ç½®æ–‡ä»¶ï¼Œæ ¹æ®ä¸ªäººå好æ¥é€‰æ‹©æ—¥å¿—统计方å¼ï¼Œä¾‹å¦‚在终端输出整个è®ç»ƒè¿‡ç¨‹ä¸çš„å¹³å‡æŸå¤±è€Œä¸æ˜¯åŸºäºŽå›ºå®šè¿ä»£æ¬¡æ•°å¹³æ»‘çš„æŸå¤± +- 用户å¯ä»¥åœ¨ä»»æ„组件ä¸èŽ·å–当å‰çš„è®ç»ƒçŠ¶æ€ï¼Œä¾‹å¦‚当å‰çš„è¿ä»£æ¬¡æ•°ã€è®ç»ƒè½®æ¬¡ç‰ +- 用户å¯ä»¥é€šè¿‡é…置文件æ¥æŽ§åˆ¶æ˜¯å¦ä¿å˜åˆ†å¸ƒå¼è®ç»ƒä¸‹çš„多进程日志 - + -## 日志类型 +è®ç»ƒè¿‡ç¨‹ä¸çš„产生的æŸå¤±ã€å¦ä¹ 率ç‰æ•°æ®ç”±åŽ†å²ç¼“冲区管ç†å’Œå°è£…,汇总åŽäº¤ç»™æ¶ˆæ¯æž¢çº½ç»´æŠ¤ã€‚日志处ç†å™¨å°†æ¶ˆæ¯æž¢çº½ä¸çš„æ•°æ®è¿›è¡Œæ ¼å¼åŒ–,最åŽé€šè¿‡[记录器钩å(LoggerHook)](TODO) 展示到å„ç§å¯è§†åŒ–åŽç«¯ã€‚**ä¸€èˆ¬æƒ…å†µä¸‹ç”¨æˆ·æ— éœ€æ„ŸçŸ¥æ•°æ®å¤„ç†æµç¨‹ï¼Œå¯ä»¥ç›´æŽ¥é€šè¿‡é…置日志处ç†å™¨æ¥é€‰æ‹©æ—¥å¿—的统计方å¼**。 -按照日志的功能划分,算法库的日志å¯ä»¥è¢«åˆ†æˆè®ç»ƒæ—¥å¿—和组件日志,å‰è€…用于观察模型的è®ç»ƒçŠ¶æ€ï¼Œä¾‹å¦‚ loss 下é™æ˜¯å¦æ£å¸¸ç‰ï¼›åŽè€…用于监测程åºçš„è¿è¡ŒçŠ¶æ€ï¼Œä¾‹å¦‚模型è¿ä»£æ—¶é—´ã€å†…å˜å 用是å¦æ£å¸¸ç‰ã€‚ +## 历å²ç¼“冲区(HistoryBuffer) -### è®ç»ƒæ—¥å¿— +MMEngine 实现了历å²æ•°æ®å˜å‚¨çš„抽象类历å²ç¼“冲区(HistoryBuffer),用于å˜å‚¨è®ç»ƒæ—¥å¿—的历å²è½¨è¿¹ï¼Œå¦‚模型æŸå¤±ã€ä¼˜åŒ–器å¦ä¹ 率ã€è¿ä»£æ—¶é—´ç‰ã€‚通常情况下,历å²ç¼“冲区作为内部类,é…åˆæ¶ˆæ¯æž¢çº½ï¼ˆMessageHub)ã€[记录器钩å(LoggerHook )](TODO)和日志处ç†å™¨ï¼ˆLogProcessor) 实现了è®ç»ƒæ—¥å¿—çš„å¯é…置化。 -è®ç»ƒæ—¥å¿—指模型在è®ç»ƒ/测试/推ç†è¿‡ç¨‹ä¸çš„状æ€æ—¥å¿—,包括å¦ä¹ 率(lr)ã€æŸå¤±ï¼ˆloss)ã€è¯„ä»·æŒ‡æ ‡ï¼ˆmetric) ç‰ã€‚[TensorBoard](https://www.tensorflow.org/tensorboard?hl=zh-cn) 〠[Wandb](https://wandb.ai/site) ç‰å·¥å…·èƒ½å°†è®ç»ƒæ—¥å¿—以图表的形å¼å±•ç¤ºï¼Œä¾¿äºŽæˆ‘们观察模型的è®ç»ƒæƒ…况。为了让用户能够通过修改é…置文件æ¥é€‰æ‹©è¾“出哪些日志,以åŠå¦‚何统计日志,**MMEngine** 设计了日志缓冲区和消æ¯æž¢çº½ä»¥æ”¯æŒè¿™ä¸€ç‰¹æ€§ã€‚ +用户也å¯ä»¥å•ç‹¬ä½¿ç”¨åŽ†å²ç¼“冲区æ¥ç®¡ç†è®ç»ƒæ—¥å¿—,能够éžå¸¸ç®€å•çš„使用ä¸åŒæ–¹æ³•æ¥ç»Ÿè®¡è®ç»ƒæ—¥å¿—。我们先æ¥ä»‹ç»å¦‚何å•ç‹¬ä½¿ç”¨åŽ†å²ç¼“冲区,在消æ¯æž¢çº½ä¸€èŠ‚å†è¿›ä¸€æ¥ä»‹ç»äºŒè€…çš„è”动。 -- **统一的日志å˜å‚¨æ ¼å¼** +### 历å²ç¼“冲区åˆå§‹åŒ– -ä¸åŒç±»åž‹çš„日志统计方å¼ä¸åŒï¼ŒæŸå¤±ä¸€ç±»çš„日志需è¦è®°å½•åŽ†å²ä¿¡æ¯ï¼ˆç”¨äºŽå¹³æ»‘),而å¦ä¹ 率ã€åŠ¨é‡ä¹‹ç±»çš„å´ä¸éœ€è¦ã€‚å› æ¤ **MMEnging** 在确ä¿æ—¥å¿—统计方å¼çµæ´»æ€§çš„å‰æ下,抽象出了具有统一接å£çš„日志缓冲区,用户å¯ä»¥å分自然地使用日志缓冲区æ¥ç®¡ç†æ—¥å¿—ä¿¡æ¯ã€‚ - -- **跨模å—çš„æ—¥å¿—ä¼ è¾“** - -在 **MMEngine** ä¸ï¼Œå„组件使用日志缓冲区æ¥å˜å‚¨è®ç»ƒæ—¥å¿—,例如æŸå¤±ã€å¦ä¹ 率和è¿ä»£æ—¶é—´ç‰ã€‚最åŽ[日志钩å(LoggerHook)](TODO)会将这些日志汇总导出。上述æµç¨‹æ¶‰åŠäº†ä¸åŒç»„件的信æ¯äº¤äº’, å› æ¤éœ€è¦ä¸€å¥—组件之间的消æ¯ä¼ 输方案。**MMEngine** 设计了消æ¯æž¢çº½ï¼ˆ`MessageHub`)类实现跨模å—通讯,让åŒä¸€ä¸ª[执行器(Runner)](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`: 返回日志历å²è®°å½•ã€‚ - -接下æ¥ç®€å•ä»‹ç»å¦‚何使用日志缓冲区记录日志。 - -### 日志缓冲区åˆå§‹åŒ– +历å²ç¼“冲区的åˆå§‹åŒ–å¯ä»¥æŽ¥å— `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 -log_buffer = LogBuffer() # 空åˆå§‹åŒ– -log_history, count_history = log_buffer.data +from mmengine.logging import HistoryBuffer + +history_buffer = HistoryBuffer() # 空åˆå§‹åŒ– +log_history, count_history = history_buffer.data # [] [] -log_buffer = LogBuffer([1, 2, 3], [1, 2, 3]) # list åˆå§‹åŒ– -log_history, count_history = log_buffer.data +history_buffer = HistoryBuffer([1, 2, 3], [1, 2, 3]) # list åˆå§‹åŒ– +log_history, count_history = history_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] +history_buffer = HistoryBuffer([1, 2, 3], [1, 2, 3], max_length=2) +# The length of history buffer(3) exceeds the max_length(2), the first few elements will be ignored. +log_history, count_history = history_buffer.data # 最大长度为2,åªèƒ½å˜å‚¨ [2, 3] # [2 3] [2 3] ``` -### 日志缓冲区更新 +我们å¯ä»¥é€šè¿‡ `history_buffer.data` æ¥è¿”回日志的历å²è½¨è¿¹ã€‚æ¤å¤–,我们å¯ä»¥ä¸ºåŽ†å²ç¼“冲区设置最大队列长度,当历å²ç¼“冲区的长度大于最大队列长度时,会自动丢弃最早更新的数æ®ã€‚ + +### 更新历å²ç¼“冲区 + +我们å¯ä»¥é€šè¿‡ `update` 接å£æ¥æ›´æ–°åŽ†å²ç¼“冲区。update 接å—两个å‚数,第一个å‚数用于更新 `log_history `,第二个å‚数用于更新 `count_history`。 ```python -log_buffer = LogBuffer([1, 2, 3], [1, 1, 1]) -log_buffer.update(4, 1) # 更新日志 -log_history, count_history = log_buffer.data +history_buffer = HistoryBuffer([1, 2, 3], [1, 1, 1]) +history_buffer.update(4) # 更新日志 +log_history, count_history = history_buffer.data # [1, 2, 3, 4] [1, 1, 1, 1] +history_buffer.update(5, 2) # 更新日志 +log_history, count_history = history_buffer.data +# [1, 2, 3, 4, 5] [1, 1, 1, 1, 2] ``` -### 统计最大最å°å€¼ +### 基本统计方法 + +历å²ç¼“冲区æ供了基本的数æ®ç»Ÿè®¡æ–¹æ³•ï¼š + +- `current()`:获å–最新更新的数æ®ã€‚ +- `mean(window_size=None)`:获å–窗å£å†…æ•°æ®çš„å‡å€¼ï¼Œé»˜è®¤è¿”回数æ®çš„全局å‡å€¼ +- `max(window_size=None)`:获å–窗å£å†…æ•°æ®çš„最大值,默认返回全局最大值 +- `min(window_size=None)`:获å–窗å£å†…æ•°æ®çš„最å°å€¼ï¼Œé»˜è®¤è¿”回全局最å°å€¼ ```python -log_buffer = LogBuffer([1, 2, 3], [1, 1, 1]) -log_buffer.min(2) +history_buffer = HistoryBuffer([1, 2, 3], [1, 1, 1]) +history_buffer.min(2) # 2,从 [2, 3] ä¸ç»Ÿè®¡æœ€å°å€¼ -log_buffer.min() +history_buffer.min() # 返回全局最å°å€¼ 1 -log_buffer.max(2) +history_buffer.max(2) # 3,从 [2, 3] ä¸ç»Ÿè®¡æœ€å¤§å€¼ -log_buffer.min() +history_buffer.min() # 返回全局最大值 3 -``` - -### 统计å‡å€¼ - -```python -log_buffer = LogBuffer([1, 2, 3], [1, 1, 1]) -log_buffer.mean(2) +history_buffer.mean(2) # 2.5,从 [2, 3] ä¸ç»Ÿè®¡å‡å€¼, (2 + 3) / (1 + 1) -log_buffer.mean() # (1 + 2 + 3) / (1 + 1 + 1) +history_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) +history_buffer = HistoryBuffer([1, 2, 3], [2, 2, 2]) # 当 count ä¸ä¸º 1æ—¶ +history_buffer.mean() # (1 + 2 + 3) / (2 + 2 + 2) # 返回å‡å€¼ 1 +history_buffer = HistoryBuffer([1, 2, 3], [1, 1, 1]) +history_buffer.update(4, 1) +history_buffer.current() +# 4 ``` -### 统计最近更新的值 +### 注册统计方法 + +为了ä¿è¯åŽ†å²ç¼“冲区的å¯æ‰©å±•æ€§ï¼Œç”¨æˆ·å¯ä»¥é€šè¿‡ `register_statistics` 接å£æ³¨å†Œè‡ªå®šä¹‰çš„统计函数 ```python -log_buffer = LogBuffer([1, 2, 3], [1, 1, 1]) -log_buffer.update(4, 1) -log_buffer.current() -# 4 +from mmengine.logging import HistoryBuffer +import numpy as np + + +@HistoryBuffer.register_statistics +def weighted_mean(self, window_size, weight): + assert len(weight) == window_size + return (self._log_history[-window_size:] * np.array(weight)).sum() / \ + self._count_history[-window_size:] + + +history_buffer = HistoryBuffer([1, 2], [1, 1]) +history_buffer.statistics('weighted_mean', 2, [2, 1]) # get (2 * 1 + 1 * 2) / (1 + 1) ``` -### 使用ä¸åŒçš„统计方法 +用户å¯ä»¥é€šè¿‡ `statistics` 接å£ï¼Œä¼ 入方法å和对应å‚æ•°æ¥è°ƒç”¨è¢«æ³¨å†Œçš„函数。 -为了让用户å¯ä»¥é€šè¿‡é…置文件æ¥é€‰æ‹©æ—¥å¿—的统计方å¼ï¼Œæ—¥å¿—缓冲区æ供了 `statistics` 接å£ï¼Œå…许用户通过å—符串æ¥é€‰æ‹©æ–¹æ³•ã€‚需è¦æ³¨æ„,在调用 `statistics(name, *args, **kwargs)` 时,需è¦ä¿è¯ name 是已注册的方法å,并且å‚数和方法相匹é…。 +### ç»Ÿè®¡æ–¹æ³•çš„ç»Ÿä¸€å…¥å£ + +è¦æƒ³æ”¯æŒåœ¨é…置文件ä¸é€šè¿‡é…ç½® 'max','min' ç‰å—段æ¥é€‰æ‹©æ—¥å¿—的统计方å¼ï¼Œé‚£ä¹ˆ HistoryBuffer 就需è¦ä¸€ä¸ªæŽ¥å£æ¥æŽ¥å— 'min','max' ç‰ç»Ÿè®¡æ–¹æ³•å—符串和相应å‚数,进而找到对应的统计方法,最åŽè¾“出统计结果。`statistics(name, *args, **kwargs)` 接å£å°±èµ·åˆ°äº†è¿™ä¸ªä½œç”¨ã€‚å…¶ä¸ name 是已注册的方法å(基本统计方法已ç»è¢«æ³¨å†Œï¼‰ï¼Œ`*arg` å’Œ `**kwarg` 用于接å—对应方法的å‚数。 ```python -log_buffer = LogBuffer([1, 2, 3], [1, 1, 1]) -log_buffer.statistics('mean') +history_buffer = HistoryBuffer([1, 2, 3], [1, 1, 1]) +history_buffer.statistics('mean') # 2 返回全局å‡å€¼ -log_buffer.statistics('mean', 2) +history_buffer.statistics('mean', 2) # 2.5 返回 [2, 3] çš„å‡å€¼ -log_buffer.statistics('mean', 2, 3) # 错误ï¼ä¼ 入了ä¸åŒ¹é…çš„å‚æ•° -log_buffer.statistics('data') # é”™è¯¯ï¼ data æ–¹æ³•æœªè¢«æ³¨å†Œï¼Œæ— æ³•è¢«è°ƒç”¨ - +history_buffer.statistics('mean', 2, 3) # 错误ï¼ä¼ 入了ä¸åŒ¹é…çš„å‚æ•° +history_buffer.statistics('data') # é”™è¯¯ï¼ data æ–¹æ³•æœªè¢«æ³¨å†Œï¼Œæ— æ³•è¢«è°ƒç”¨ ``` -### 使用日志缓冲区统计è®ç»ƒæ—¥å¿— +### ä½¿ç”¨æ ·ä¾‹ + +用户å¯ä»¥ç‹¬ç«‹ä½¿ç”¨åŽ†å²ç¼“冲区æ¥è®°å½•æ—¥å¿—,通过简å•çš„接å£è°ƒç”¨å°±èƒ½å¾—到期望的统计接å£ã€‚ ```Python -logs = dict(lr=LogBuffer(), loss=LogBuffer()) # å—å…¸é…åˆ LogBuffer 记录ä¸åŒå—段的日志 +logs = dict(lr=HistoryBuffer(), loss=HistoryBuffer()) # å—å…¸é…åˆ HistoryBuffer 记录ä¸åŒå—段的日志 max_iter = 10 log_interval = 5 for iter in range(1, max_iter+1): lr = iter / max_iter * 0.1 # 线性å¦ä¹ 率å˜åŒ– loss = 1 / iter # loss - logs['lr'].update('lr', 1) - logs['loss'].update('loss', 1) + 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}\n' # 返回最近一次更新的å¦ä¹ 率。 f'loss: {mean_loss}') # 平滑最新更新的 log_interval 个数æ®ã€‚ # lr: 0.05 -# loss: 45.67 +# loss: 0.45666666666666667 # lr: 0.1 -# loss: 26.01 -``` - -### è‡ªå®šä¹‰ç»Ÿè®¡æ–¹å¼ - -考虑到数æ®çš„统计方法ä¸ä¼šè¿‡äºŽå¤æ‚ï¼Œå› æ¤ä¸æŽ¨è通过继承日志缓冲区æ¥æ–°å¢žåŠŸèƒ½ã€‚我们更倾å‘于用户使用 `LogBuffer.register_statistics` 注册自定义的统计函数,被注册的函数å¯ä»¥é€šè¿‡ `statistics` 接å£è°ƒç”¨ã€‚ - -```Python -@LogBuffer.register_statistics() -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åˆ›å»ºï¼Œæ— æ³•èŽ·å– +# loss: 0.12912698412698415 ``` -调用 `create_instance` æ—¶ï¼Œä¼ å…¥ `name` å‚数会返回对应åå—的实例,但是ä¸å…许创建é‡å¤åå—的实例。 +MMEngine 利用历å²ç¼“冲区的特性,结åˆæ¶ˆæ¯æž¢çº½ï¼Œå®žçŽ°äº†è®ç»ƒæ—¥å¿—的高度å¯å®šåˆ¶åŒ–。 -```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未被创建 -``` +## 消æ¯æž¢çº½ï¼ˆMessageHub) -当ä¸ä¼ å…¥ `name`,且 `current=False` æ—¶ï¼Œä¼šè¿”å›žæ ¹å®žä¾‹ã€‚ +历å²ç¼“冲区(HistoryBuffer)å¯ä»¥å分简å•åœ°å®Œæˆå•ä¸ªæ—¥å¿—的更新和统计,而在模型è®ç»ƒè¿‡ç¨‹ä¸ï¼Œæ—¥å¿—çš„ç§ç±»ç¹å¤šï¼Œå¹¶ä¸”æ¥è‡ªäºŽä¸åŒçš„ç»„ä»¶ï¼Œå› æ¤å¦‚何完æˆæ—¥å¿—的分å‘和收集是需è¦è€ƒè™‘的问题。 MMEngine 使用消æ¯æž¢çº½ï¼ˆMessageHub)æ¥å®žçŽ°ç»„件与组件ã€æ‰§è¡Œå™¨ä¸Žæ‰§è¡Œå™¨ä¹‹é—´çš„æ•°æ®å…±äº«ã€‚消æ¯æž¢çº½ç»§æ‰¿è‡ªå…¨å±€ç®¡ç†å™¨ï¼ˆManageMixin),支æŒè·¨æ¨¡å—访问。 -```python -instance.instance_name # root ä¸ä¼ å‚,默认返回 root -``` +消æ¯æž¢çº½å˜å‚¨äº†ä¸¤ç§å«ä¹‰çš„æ•°æ®ï¼š -当ä¸ä¼ å…¥ `name`,且 `current=True` 时,会返回最近一次被创建的实例。 +- 历å²ç¼“冲区å—典:消æ¯æž¢çº½ä¼šæ”¶é›†å„个模å—æ›´æ–°çš„è®ç»ƒæ—¥å¿—,如æŸå¤±ã€å¦ä¹ 率ã€è¿ä»£æ—¶é—´ï¼Œå¹¶å°†å…¶æ›´æ–°è‡³å†…部的历å²ç¼“冲区å—å…¸ä¸ã€‚历å²ç¼“冲区å—å…¸ç»[消æ¯å¤„ç†å™¨ï¼ˆLogProcessor)](TODO)处ç†åŽï¼Œä¼šè¢«è¾“出到终端/ä¿å˜åˆ°æœ¬åœ°ã€‚如果用户需è¦è®°å½•è‡ªå®šä¹‰æ—¥å¿—,å¯ä»¥å¾€åŽ†å²ç¼“冲区å—å…¸ä¸æ›´æ–°ç›¸åº”内容。 +- è¿è¡Œæ—¶ä¿¡æ¯å—典:è¿è¡Œæ—¶ä¿¡æ¯å—典用于å˜å‚¨è¿ä»£æ¬¡æ•°ã€è®ç»ƒè½®æ¬¡ç‰è¿è¡Œæ—¶ä¿¡æ¯ï¼Œæ–¹ä¾¿ MMEngine ä¸æ‰€æœ‰ç»„件共享这些信æ¯ã€‚ -```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 最近被创建 +```{note} +当用户想在终端输出自定义日志,或者想跨模å—共享一些自定义数æ®æ—¶ï¼Œæ‰ä¼šç”¨åˆ°æ¶ˆæ¯æž¢çº½ã€‚ ``` -å¦‚æžœæ— æ³•ä¿è¯ç›®æ ‡å®žä¾‹æ˜¯æœ€è¿‘一次被创建的,使用 `get_instance(current=True)` 方法å¯èƒ½ä¼šè®¿é—®åˆ°ä¸ç¬¦åˆé¢„期的实例。 +为了方便用户ç†è§£æ¶ˆæ¯æž¢çº½åœ¨è®ç»ƒè¿‡ç¨‹ä¸æ›´æ–°ä¿¡æ¯ä»¥åŠåˆ†å‘ä¿¡æ¯çš„æµç¨‹ï¼Œæˆ‘ä»¬é€šè¿‡å‡ ä¸ªä¾‹åæ¥ä»‹ç»æ¶ˆæ¯æž¢çº½çš„使用方法,以åŠå¦‚何使用消æ¯æž¢çº½å‘终端输出自定义日志。 -```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的实例 -``` - -对于上述情况,建议用户将全局实例实例固化为类的属性,在åˆå§‹åŒ–阶段完æˆå¯¹åº”实例的绑定。 +历å²ç¼“冲区以å—典的形å¼å˜å‚¨åœ¨æ¶ˆæ¯æž¢çº½ä¸ã€‚当我们第一次调用 `update_scalar` 时,会åˆå§‹åŒ–对应å—段的历å²ç¼“冲区,åŽç»çš„æ¯æ¬¡æ›´æ–°ç‰ä»·äºŽè°ƒç”¨å¯¹åº”å—段历å²ç¼“冲区的 `update` 方法。åŒæ ·çš„我们å¯ä»¥é€šè¿‡ `get_scalar` æ¥èŽ·å–对应å—段的历å²ç¼“冲区,并按需计算统计值。如果想获å–消æ¯æž¢çº½çš„全部日志,å¯ä»¥è®¿é—®å…¶ `log_scalars` 属性。 ```python -class ModuleA: - def __init__(self, name): - self.instance = GlobalAccessible.create_instance(name) - self.module = ModuleB() +from mmengine import MessageHub - def run_module(self): - self.module.run() - -class ModuleB: - def __init__(self): - self.instance = GlobalAccessible.get_instance(current=True) # åˆå§‹åŒ–阶段绑定 +message_hub = MessageHub.get_instance('task') +message_hub.update_scalar('train/loss', 1, 1) +message_hub.get_scalar('train/loss').current() # 1,最近一次更新值为 1 +message_hub.update_scalar('train/loss', 3, 1) +message_hub.get_scalar('train/loss').mean() # 2,å‡å€¼ä¸º (3 + 1) / (1 + 1) +message_hub.update_scalar('train/lr', 0.1, 1) - def run(self): - print(f'moduleB: {self.instance.instance_name} is called') +message_hub.update_scalars({'train/time': {'value': 0.1, 'count': 1}, + 'train/data_time': {'value': 0.1, 'count': 1}}) +train_time = message_hub.get_scalar('train/time') # 获å–å•ä¸ªæ—¥å¿— -if __name__ == '__main__': - a1 = ModuleA('a1') - a2 = ModuleA('a2') - a1.run_module() # moduleB: a1 is called,åˆå§‹åŒ–阶段绑定,确ä¿åŽç»è®¿é—®åˆ°æ£ç¡®å®žä¾‹ã€‚ +log_dict = message_hub.log_scalars # 返回å˜å‚¨å…¨éƒ¨ HistoryBuffer çš„å—å…¸ +lr_buffer, loss_buffer, time_buffer, data_time_buffer = ( + log_dict['train/lr'], log_dict['train/loss'], log_dict['train/time'], + log_dict['train/data_time']) ``` -## 消æ¯æž¢çº½ï¼ˆ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'] +```{note} +消æ¯æž¢çº½çš„历å²ç¼“冲区å—典对 key 没有特殊è¦æ±‚,但是 MMEngine 约定历å²ç¼“冲区å—典的 key è¦æœ‰ train/val/test çš„å‰ç¼€ï¼Œåªæœ‰å¸¦å‰ç¼€çš„日志会被输出当终端。 ``` ### æ›´æ–°/获å–è¿è¡Œæ—¶ä¿¡æ¯ -è¿è¡Œæ—¶ä¿¡æ¯ä»¥å—典的形å¼å˜å‚¨åœ¨æ¶ˆæ¯æž¢çº½ä¸ï¼Œæ”¯æŒä»»æ„æ•°æ®ç±»åž‹ï¼Œæ¯æ¬¡æ›´æ–°éƒ½ä¼šè¦†ç›–。 +è¿è¡Œæ—¶ä¿¡æ¯ä»¥å—典的形å¼å˜å‚¨åœ¨æ¶ˆæ¯æž¢çº½ä¸ï¼Œèƒ½å¤Ÿå˜å‚¨ä»»æ„æ•°æ®ç±»åž‹ï¼Œæ¯æ¬¡æ›´æ–°éƒ½ä¼šè¢«è¦†ç›–。 ```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.runtime_info # 返回å˜å‚¨å…¨éƒ¨ LogBuffer çš„å—å…¸ -meta = log_dict['meta'] +message_hub = MessageHub.get_instance('task') +message_hub.update_info('iter', 1) +message_hub.get_info('iter') # 1 +message_hub.update_info('iter', 2) +message_hub.get_info('iter') # 2 覆盖上一次结果 ``` ### 消æ¯æž¢çº½çš„跨组件通讯 @@ -337,15 +211,17 @@ meta = log_dict['meta'] 执行器è¿è¡Œè¿‡ç¨‹ä¸ï¼Œå„个组件会通过消æ¯æž¢çº½æ¥åˆ†å‘ã€æŽ¥å—消æ¯ã€‚日志钩å会汇总其他组件更新的å¦ä¹ 率ã€æŸå¤±ç‰ä¿¡æ¯ï¼Œå°†å…¶å¯¼å‡ºåˆ°ç”¨æˆ·æŒ‡å®šçš„输出端(Tensorboard,Wandb ç‰ï¼‰ã€‚由于上述æµç¨‹è¾ƒä¸ºå¤æ‚,这里用一个简å•ç¤ºä¾‹æ¥æ¨¡æ‹Ÿæ—¥å¿—é’©å和其他组件通讯的过程。 ```python -class Receiver: +from mmengine import MessageHub + +class LogProcessor: # 汇总ä¸åŒæ¨¡å—更新的消æ¯ï¼Œç±»ä¼¼ LoggerHook def __init__(self, name): self.message_hub = MessageHub.get_instance(name) # èŽ·å– MessageHub 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')}") + print(f"Learning rate is {self.message_hub.get_scalar('train/lr').current()}") + print(f"loss is {self.message_hub.get_scalar('train/loss').current()}") + print(f"meta is {self.message_hub.get_info('meta')}") class LrUpdater: @@ -354,7 +230,9 @@ class LrUpdater: self.message_hub = MessageHub.get_instance(name) # èŽ·å– MessageHub def run(self): - self.message_hub.update_log('lr', 0.001) # æ›´æ–°å¦ä¹ 率,以 LogBuffer å½¢å¼å˜å‚¨ + self.message_hub.update_scalar('train/lr', 0.001) + # æ›´æ–°å¦ä¹ 率,以 HistoryBuffer å½¢å¼å˜å‚¨ + class MetaUpdater: # æ›´æ–°å…ƒä¿¡æ¯ @@ -367,19 +245,20 @@ class MetaUpdater: 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) + self.message_hub.update_scalar('train/loss', 0.1) -class Task: +class ToyRunner: # 组åˆä¸ªå„ä¸ªæ¨¡å— def __init__(self, name): - self.message_hub = MessageHub.create_instance(name) # 创建 MessageHub - self.receiver = Receiver(name) + self.message_hub = MessageHub.get_instance(name) # 创建 MessageHub + self.log_processor = LogProcessor(name) self.updaters = [LossUpdater(name), MetaUpdater(name), LrUpdater(name)] @@ -387,69 +266,179 @@ class Task: def run(self): for updater in self.updaters: updater.run() - self.receiver.run() + self.log_processor.run() if __name__ == '__main__': - task = Task('name') + task = ToyRunner('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'} + # loss is 0.1 + # meta {'experiment': 'retinanet_r50_caffe_fpn_1x_coco.py', 'repo': 'mmdetection'} +``` + +### æ·»åŠ è‡ªå®šä¹‰æ—¥å¿— +我们å¯ä»¥åœ¨ä»»æ„模å—里更新消æ¯æž¢çº½çš„历å²ç¼“冲区å—典,历å²ç¼“冲区å—å…¸ä¸æ‰€æœ‰å†…容的统计结果都会在终端显示 + +```python +class CustomModule: + def __init__(self): + self.message_hub = MessageHub.get_current_instance() + + def custom_method(self): + self.message_hub.update_scalar('train/a', 100) + self.message_hub.update_scalars({'train/b': 1, 'train/c': 2}) ``` -## 记录器(MMLogger) +默认情况下,终端上é¢å¤–显示 aã€bã€c最åŽä¸€æ¬¡æ›´æ–°çš„结果。我们也å¯ä»¥é€šè¿‡é…置日志处ç†å™¨æ¥åˆ‡æ¢è‡ªå®šä¹‰æ—¥å¿—的统计方å¼ã€‚ + +## 日志处ç†å™¨ï¼ˆLogProcessor) + +用户å¯ä»¥é€šè¿‡é…置日志处ç†å™¨ï¼ˆLogProcessor)æ¥æŽ§åˆ¶æ—¥å¿—的统计窗å£å’Œç»Ÿè®¡æ–¹æ³•ã€‚默认é…置下,日志处ç†å™¨ä¼šç»Ÿè®¡æœ€è¿‘一次更新的å¦ä¹ 率ã€åŸºäºŽè¿ä»£æ¬¡æ•°å¹³æ»‘çš„æŸå¤±å’Œè¿ä»£æ—¶é—´ã€‚用户å¯ä»¥åœ¨æ—¥å¿—处ç†å™¨ä¸é…置已知å—段的统计方å¼ã€‚ + +### 最简é…ç½® -为了能够导出层次分明ã€æ ¼å¼ç»Ÿä¸€çš„组件日志,**MMEngine** 在 `logging` 模å—的基础上设计了 `MMLogger`,其继承于 `BaseGlobalAccessible` å’Œ `logging.Logger`。由 `MMLogger.get_instance` 获å–çš„è®°å½•å™¨å…·å¤‡ç»Ÿä¸€çš„æ—¥å¿—æ ¼å¼ï¼Œä¸”ä¸ä¼šç»§æ‰¿ `logging.root` ï¼Œå› æ¤ä¸ä¼šå—åˆ°ç¬¬ä¸‰æ–¹åº“ä¸ logger é…置的影å“。 +```python +log_processor = dict( + interval=10, +) +``` + +æ¤æ—¶ç»ˆç«¯ä¼šè¾“å‡ºæ¯ 10 次è¿ä»£çš„å¹³å‡æŸå¤±å’Œå¹³å‡è¿ä»£æ—¶é—´ã€‚å‡è®¾æ¤æ—¶ç»ˆç«¯çš„输出为 + +```bash +04/15 12:34:24 - mmengine - INFO - Iter [10/12] , eta: 0:00:00, time: 0.003, data_time: 0.002, loss: 0.13 +``` + +### è‡ªå®šä¹‰çš„ç»Ÿè®¡æ–¹å¼ + +我们å¯ä»¥é€šè¿‡é…ç½® `custom_cfg` 列表æ¥é€‰æ‹©æ—¥å¿—的统计方å¼ã€‚`custom_cfg` ä¸çš„æ¯ä¸€ä¸ªå…ƒç´ 需è¦åŒ…括以下信æ¯ï¼š + +- `data_src`:日志的数æ®æºï¼Œç”¨æˆ·é€šè¿‡æŒ‡å®š `data_src` æ¥é€‰æ‹©éœ€è¦è¢«é‡æ–°ç»Ÿè®¡çš„日志(必填项) +- `method_name`:统计方法,å³åŽ†å²ç¼“冲区ä¸çš„基本统计方法以åŠç”¨æˆ·æ³¨å†Œçš„自定义统计方法(必填项) +- `log_name`:日志被é‡æ–°ç»Ÿè®¡åŽçš„åå—,如果ä¸å®šä¹‰ `log_name`,新日志会覆盖旧日志(选填项) +- 其他å‚数:统计方法会用到的å‚æ•°ï¼Œå…¶ä¸ `window_size` 为特殊å—段,å¯ä»¥ä¸ºæ™®é€šçš„æ•´åž‹ã€å—符串 epoch å’Œå—符串 global。LogProcessor 会实时解æžè¿™äº›å‚数,以返回基于 iterationã€epoch 和全局平滑的统计结果(选填项) + +1. è¦†ç›–æ—§çš„ç»Ÿè®¡æ–¹å¼ + +```python +log_processor = dict( + window_size=10, + by_epoch=True, + custom_cfg=[ + dict(data_src='loss', + method_name='mean', + window_size=100)]) +``` + +æ¤æ—¶ä¼šæ— 视日志处ç†å™¨çš„é»˜è®¤çª—å£ 10ï¼Œç”¨æ›´å¤§çš„çª—å£ 100去统计 loss çš„å‡å€¼ï¼Œå¹¶å°†åŽŸæœ‰ç»“果覆盖。 + +```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 +``` + +2. 新增统计方å¼ï¼Œä¸è¦†ç›– + +```python +log_processor = dict( + window_size=10, + by_epoch=True, + custom_cfg=[ + dict(data_src='loss', + log_name='loss_min' + method_name='min', + window_size=100)]) +``` - +```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, loss_min: 0.08 +``` + +## 记录器(MMLogger) -`MMLogger` åœ¨æž„é€ å‡½æ•°ä¸å®Œæˆäº†è®°å½•å™¨çš„é…置,除了`BaseGlobalAccessible` å’Œ `logging.Logger` 的基类接å£å¤–,没有æä¾›é¢å¤–的接å£ã€‚`MMLogger` 创建/获å–çš„æ–¹å¼å’Œæ¶ˆæ¯æž¢çº½ç›¸åŒï¼Œæ¤å¤„ä¸å†èµ˜è¿°ï¼Œæˆ‘们主è¦ä»‹ç»é€šè¿‡ `MMLogger.create_instance` 获å–的记录器具备哪些功能。 +为了能够导出层次分明ã€æ ¼å¼ç»Ÿä¸€ã€ä¸”ä¸å—三方库日志系统干扰的日志,MMEngine 在 `logging` 模å—的基础上实现了 `MMLogger`。`MMLogger` 继承自全局管ç†å™¨ï¼ˆ`ManagerMixin`),相比于 `logging.Logger`ï¼Œå…¶èƒ½å¤Ÿåœ¨æ— æ³•èŽ·å–记录器å称(logger name)的情况下,拿到当å‰æ‰§è¡Œå™¨çš„记录器。 -### æ—¥å¿—æ ¼å¼ +### 创建记录器 + +我们å¯ä»¥é€šè¿‡ `get_instance` 接å£åˆ›å»ºå…¨å±€å¯èŽ·å–çš„è®°å½•å™¨å®žä¾‹ï¼Œé»˜è®¤çš„æ—¥å¿—æ ¼å¼å¦‚下 ```python -logger = MMLogger.create_instance('mmengine') +logger = MMLogger.get_instance('mmengine', log_level='INFO') logger.info("this is a test") -# 2022-02-20 22:26:38,860 - mmengine - INFO - this is a test +# 04/15 14:01:11 - mmengine - INFO - this is a test ``` -记录器除了输出消æ¯å¤–,还会é¢å¤–输出时间戳ã€è®°å½•å™¨åå—和日志ç‰çº§ã€‚对于 ERROR ç‰çº§çš„日志,我们会用红色高亮日志ç‰çº§ï¼Œå¹¶é¢å¤–输出日志的代ç ä½ç½® +记录器除了输出消æ¯å¤–,还会é¢å¤–输出时间戳ã€è®°å½•å™¨åå—和日志ç‰çº§ã€‚对于 ERROR ç‰çº§çš„日志,我们会用红色高亮日志ç‰çº§ï¼Œå¹¶é¢å¤–输出错误日志的代ç ä½ç½® ```python -logger = MMLogger.create_instance('mmengine') +logger = MMLogger.get_instance('mmengine', log_level='INFO') 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†+# 04/15 14:01:56 - mmengine - ERROR - /mnt/d/PythonCode/DeepLearning/OpenMMLab/mmengine/a.py - <module> - 4 - division by zero ``` ### 导出日志 -调用 create_instance 时,如果指定了 log_file,会将日志记录的信æ¯ä»¥æ–‡æœ¬æ ¼å¼å¯¼å‡ºåˆ°æœ¬åœ°ã€‚ +调用 `get_instance` 时,如果指定了 log_file,会将日志记录的信æ¯ä»¥æ–‡æœ¬æ ¼å¼å¯¼å‡ºåˆ°æœ¬åœ°ã€‚ ```Python -logger = MMLogger.create_instance('mmengine', log_file='tmp.log') +logger = MMLogger.get_instance('mmengine', log_file='tmp.log', log_level='INFO') logger.info("this is a test") -# 2022-02-20 22:26:38,860 - mmengine - INFO - this is a test +# 04/15 14:01:11 - mmengine - INFO - this is a test ``` -`tmp.log`: +`tmp/tmp.log`: ```text -2022-02-20 22:26:38,860 - mmengine - INFO - this is a test +04/15 14:01:11 - mmengine - INFO - this is a test ``` +由于分布å¼æƒ…å†µä¸‹ä¼šåˆ›å»ºå¤šä¸ªæ—¥å¿—æ–‡ä»¶ï¼Œå› æ¤æˆ‘ä»¬åœ¨é¢„å®šçš„å¯¼å‡ºè·¯å¾„ä¸‹ï¼Œå¢žåŠ ä¸€çº§å’Œå¯¼å‡ºæ–‡ä»¶åŒå的目录,用于å˜å‚¨æ‰€æœ‰è¿›ç¨‹çš„日志。上例ä¸å¯¼å‡ºè·¯å¾„为 `tmp.log`,实际å˜å‚¨è·¯å¾„为 `tmp/tmp.log`。 + ### 分布å¼è®ç»ƒæ—¶å¯¼å‡ºæ—¥å¿— -在使用分布å¼è®ç»ƒæ—¶ï¼Œä¸åŒçš„进程会导出ä¸åŒçš„日志,日志文件å为执行器实例化时的时间戳。除主进程外(`rank=0`),æ¯ä¸ªè¿›ç¨‹çš„日志å的抬头为å„自的 rank 数。 +我们å¯ä»¥é€šè¿‡é…ç½® `distributed=True` æ¥å¯¼å‡ºåˆ†å¸ƒå¼æ—¥å¿—(默认关é—)。 + +```python +logger = MMLogger.get_instance('mmengine', log_file='tmp.log', distributed=True, log_level='INFO') +``` + +å•æœºå¤šå¡ï¼Œæˆ–者多机多å¡ä½†æ˜¯å…±äº«å˜å‚¨çš„情况下,导出的分布å¼æ—¥å¿—路径如下 + +```text +# 共享å˜å‚¨ +./tmp +├── tmp.log +├── tmp_rank1.log +├── tmp_rank2.log +├── tmp_rank3.log +├── tmp_rank4.log +├── tmp_rank5.log +├── tmp_rank6.log +└── tmp_rank7.log +... +└── tmp_rank63.log +``` + +多机多å¡ï¼Œç‹¬ç«‹å˜å‚¨çš„情况: ```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 +# 独立å˜å‚¨ +# 设备0: +work_dir/ +└── exp_name_logs + ├── exp_name.log + ├── exp_name_rank1.log + ├── exp_name_rank2.log + ├── exp_name_rank3.log + ... + └── exp_name_rank7.log + +# 设备7: +work_dir/ +└── exp_name_logs + ├── exp_name_rank56.log + ├── exp_name_rank57.log + ├── exp_name_rank58.log + ... + └── exp_name_rank63.log ``` diff --git a/docs/zh_cn/tutorials/utils.md b/docs/zh_cn/tutorials/utils.md new file mode 100644 index 0000000000000000000000000000000000000000..5fdde05a247075026adc198562f2b27a2d8389ef --- /dev/null +++ b/docs/zh_cn/tutorials/utils.md @@ -0,0 +1,58 @@ +# utils + +## 全局管ç†å™¨ï¼ˆManagerMixin) + +Runner 在è®ç»ƒè¿‡ç¨‹ä¸ï¼Œéš¾å…会使用全局å˜é‡æ¥å…±äº«ä¿¡æ¯ï¼Œä¾‹å¦‚我们会在 model ä¸èŽ·å–全局的 [logger](TODO) æ¥æ‰“å°åˆå§‹åŒ–ä¿¡æ¯ï¼›åœ¨ model ä¸èŽ·å–全局的 [Visualizer](TODO) æ¥å¯è§†åŒ–预测结果ã€ç‰¹å¾å›¾ï¼›åœ¨ [Reigistry](TODO) ä¸èŽ·å–全局的 [DefaultScope](TODO) æ¥ç¡®å®šæ³¨å†ŒåŸŸã€‚为了管ç†è¿™äº›åŠŸèƒ½ç›¸ä¼¼çš„模å—,MMEngine 实现了管ç†å™¨ï¼ˆManagerMix)æ¥ç»Ÿä¸€å…¨å±€å˜é‡çš„创建和获å–æ–¹å¼ã€‚ + + + +### 接å£ä»‹ç» + +- _instance_name:被创建的全局实例å +- get_instance(name='', **kwargs):创建或者返回对应åå—的的实例。 +- get_current_instance():返回最近被创建的实例。 +- instance_name::获å–对应实例的 name。 + +### 使用方法 + +1. 定义有全局访问需求的类 + +```python +from mmengine.utils import ManagerMixin + + +class GlobalClass(ManagerMixin): + def __init__(self, name, value): + super().__init__(name) + self.value = value +``` + +注æ„å…¨å±€ç±»çš„æž„é€ å‡½æ•°å¿…é¡»å¸¦æœ‰ name å‚æ•°ï¼Œå¹¶åœ¨æž„é€ å‡½æ•°ä¸è°ƒç”¨ `super().__init__(name)`,以确ä¿åŽç»èƒ½å¤Ÿæ ¹æ® name æ¥èŽ·å–对应的实例。 + +2. 在任æ„ä½ç½®å®žä¾‹åŒ–该对象,以 Hook 为例(è¦ç¡®ä¿è®¿é—®è¯¥å®žä¾‹æ—¶ï¼Œå¯¹è±¡å·²ç»è¢«åˆ›å»ºï¼‰ï¼š + +```python +from mmengine import Hook + +class CustomHook(Hook): + def before_run(self, runner): + GlobalClass.get_instance('mmengine', value=50) + 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` 实例。 + +3. 在任æ„组件ä¸è®¿é—®è¯¥å®žä¾‹ + +```python +import torch.nn as nn + + +class CustomModule(nn.Module): + def forward(self, x): + value = GlobalClass.get_current_instance().value # 最近一次被创建的实例 value 为 100(æ¥éª¤äºŒä¸æŒ‰é¡ºåºåˆ›å»ºï¼‰ + value = GlobalClass.get_instance('mmengine').value # å为 mmengine 的实例 value 为 50 + # value = GlobalClass.get_instance('mmengine', 1000).value # mmengine å·²ç»è¢«åˆ›å»ºï¼Œä¸èƒ½å†æŽ¥å—é¢å¤–å‚æ•° +``` + +在åŒä¸€è¿›ç¨‹é‡Œï¼Œæˆ‘们å¯ä»¥åœ¨ä¸åŒç»„件ä¸è®¿é—® `GlobalClass` 实例。例如我们在 `CustomModule` ä¸ï¼Œè°ƒç”¨ `get_instance` å’Œ `get_current_instance` 接å£æ¥èŽ·å–对应åå—的实例和最近被创建的实例。需è¦æ³¨æ„的是,由于 "mmengine" 实例已ç»è¢«åˆ›å»ºï¼Œå†æ¬¡è°ƒç”¨æ—¶ä¸èƒ½å†ä¼ å…¥é¢å¤–å‚数,å¦åˆ™ä¼šæŠ¥é”™ã€‚