HOOK
HOOK机制在OpenMMLab系列框架中应用广泛,结合Runner类可以实现训练过程中的整个生命周期的管理。例如调整学习率,保存模型,优化器等
通过register的形式诸如Runner中实现丰富的扩展功能。接下来我们以train工作流为例分析调用位置及机制。
1.调用位置
以EpochBasedRunner(BaseRunner)为例分析
mmcv/runner/epoch_base_runner.py。
class EpochBasedRunner(BaseRunner):
"""Epoch-based Runner.
This runner train models epoch by epoch.
"""
def train(self, data_loader, **kwargs):
self.model.train()
self.mode = 'train'
self.data_loader = data_loader
self._max_iters = self._max_epochs * len(self.data_loader)
######1.
self.call_hook('before_train_epoch')
time.sleep(2) # Prevent possible deadlock during epoch transition
for i, data_batch in enumerate(self.data_loader):
self.data_batch = data_batch
self._inner_iter = i
######2.
self.call_hook('before_train_iter')
self.run_iter(data_batch, train_mode=True, **kwargs)
######3.
self.call_hook('after_train_iter')
del self.data_batch
self._iter += 1
######4.
self.call_hook('after_train_epoch')
self._epoch += 1
mmcv/runner/base_runner.py
class BaseRunner(metaclass=ABCMeta):
def call_hook(self, fn_name: str) -> None:
"""Call all hooks.
Args:
fn_name (str): The function name in each hook to be called, such as
"before_train_epoch".
"""
for hook in self._hooks:
getattr(hook, fn_name)(self)
观察代码我们可以发现,在训练的整个生命周期,有四个时间可以引入hooks,分别是before_train_epoch, before_train_iter, after_train_iter, after_train_epoch. 为什么这么命名呢?
2.调用机制
在训练时,利用self.call_hook执行hooks具体的操作,以OptimizerHook为例。观察call_hook函数我们发现,利用for循环调用getattr,getattr的具体作用是通过fn_name来获得属性值或getattr(hook, name)或调用同名函数getattr(hook, name)(),这里明显是后者的作用。
现在我们解释为什么这么命名,我们可以发现,不同的hooks类在定义的时候,其主函数体是根据上面的方式唯一命名的,例如optimizer.py中的after_train_iter函数,二者一一对应,也就是说,通过这种命名结合getattr操作可以实现对hooks操作的执行。
总的来说,这里先通过register机制将所有的hooks操作都加入self._hooks中,然后通过call_hooks中的getattr函数对self._hooks的hooks进行调用,通过命名来区分不同阶段该调用的hooks。
熟悉getattr的同学可能会有疑问,既然每种hook都有唯一的成员函数与之对应,那么我循环遍历的时候,势必会出现当前函数在某一hook类没有定义的情况,例如,在执行self.call_hook('before_train_epoch') 的时候,OptimizerHook中没有before_train_epoch函数,那getattr不是会报错吗?
这个问题是个好问题,接下来解释原因,因为所有xxxHook都有一个父类Hook,在父类中定义了所有可能出现的方法,在子类中只需要重构需使用的函数即可,因此不会出现提到的问题,函数是存在的,只不过不执行具体操作而已。
mmcv/runner/hooks/optimizer.py
@HOOKS.register_module()
class OptimizerHook(Hook):
"""A hook contains custom operations for the optimizer.
Args:
grad_clip (dict, optional): A config dict to control the clip_grad.
Default: None.
detect_anomalous_params (bool): This option is only used for
debugging which will slow down the training speed.
Detect anomalous parameters that are not included in
the computational graph with `loss` as the root.
There are two cases
- Parameters were not used during
forward pass.
- Parameters were not used to produce
loss.
Default: False.
"""
def __init__(self,
grad_clip: Optional[dict] = None,
detect_anomalous_params: bool = False):
self.grad_clip = grad_clip
self.detect_anomalous_params = detect_anomalous_params
def clip_grads(self, params):
params = list(
filter(lambda p: p.requires_grad and p.grad is not None, params))
if len(params) > 0:
return clip_grad.clip_grad_norm_(params, **self.grad_clip)
def after_train_iter(self, runner):
runner.optimizer.zero_grad()
if self.detect_anomalous_params:
self.detect_anomalous_parameters(runner.outputs['loss'], runner)
runner.outputs['loss'].backward()
if self.grad_clip is not None:
grad_norm = self.clip_grads(runner.model.parameters())
if grad_norm is not None:
# Add grad norm to the logger
runner.log_buffer.update({'grad_norm': float(grad_norm)},
runner.outputs['num_samples'])
runner.optimizer.step()
def detect_anomalous_parameters(self, loss: Tensor, runner) -> None:
logger = runner.logger
parameters_in_graph = set()
visited = set()
def traverse(grad_fn):
if grad_fn is None:
return
if grad_fn not in visited:
visited.add(grad_fn)
if hasattr(grad_fn, 'variable'):
parameters_in_graph.add(grad_fn.variable)
parents = grad_fn.next_functions
if parents is not None:
for parent in parents:
grad_fn = parent[0]
traverse(grad_fn)
traverse(loss.grad_fn)
for n, p in runner.model.named_parameters():
if p not in parameters_in_graph and p.requires_grad:
logger.log(
level=logging.ERROR,
msg=f'{n} with shape {p.size()} is not '
f'in the computational graph \n')