惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
DataBreaches.Net
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
云风的 BLOG
云风的 BLOG
B
Blog
博客园 - Franky
I
InfoQ
A
About on SuperTechFans
博客园_首页
L
LangChain Blog
量子位
腾讯CDC
Microsoft Security Blog
Microsoft Security Blog
博客园 - 【当耐特】
美团技术团队
V
V2EX
Apple Machine Learning Research
Apple Machine Learning Research
雷峰网
雷峰网
MongoDB | Blog
MongoDB | Blog
Microsoft Azure Blog
Microsoft Azure Blog
月光博客
月光博客
T
The Blog of Author Tim Ferriss
P
Proofpoint News Feed
G
Google Developers Blog
Last Week in AI
Last Week in AI

お前はどこまで見えている

基于成交量预测市场走势 深入理解BashShell重定向 PowerShell常见问题 低延迟趋势线择时策略 波动率与换手率中的牛熊密码 通道突破择时策略 均线择时策略 Partial Plots 因子投资基础 Permutation Importance Use Cases for Model Insights 天文相关资源汇总 被讨厌的勇气 量化笔试数理知识复习 你当像鸟飞往你的山 Python模块logging 失去的二十年:日本经济长期停滞的真正原因 A股交易规则 强化学习概述
PyTorch的hook机制
hotarugali · 2023-09-13 · via お前はどこまで見えている

1. 简介

钩子编程(hooking),也称作「挂钩」,计算机程序设计术语,指通过拦截软件模块间的「函数调用」、「消息传递」、「事件传递」来修改或扩展操作系统、应用程序或其他软件组件的行为的各种技术。处理被拦截的函数调用、事件、消息的代码,被称为钩子(hook)

hook 在 PyTorch 中是一个非常有用的特性,利用它可以在不必改变网络输入输出结构的情况下,方便地获取、改变网络中间层变量的值和梯度。

2. Hook for Tensors

在 PyTorch 的计算图中,只有叶节点的变量会保留梯度,而所有中间变量的梯度只在反向传播中使用,一旦把向传播完成,中间变量的梯度将自动释放,从而节约内存。如果想在反向传播后保留他们的梯度,则需要使用 retain_grad() 函数特殊指定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import torch

x = torch.Tensor([0, 1, 2, 3]).requires_grad_()
y = torch.Tensor([4, 5, 6, 7]).requires_grad_()
w = torch.Tensor([1, 2, 3, 4]).requires_grad_()
z = x + y
o = torch.matmul(w, z)
# z.retain_grad()
# o.retain_grad()
o.backward()

print('x.requires_grad:', x.requires_grad) # True
print('y.requires_grad:', y.requires_grad) # True
print('z.requires_grad:', z.requires_grad) # True
print('w.requires_grad:', w.requires_grad) # True
print('o.requires_grad:', o.requires_grad) # True

# print grad after backward w/o retain_grad
print('x.grad:', x.grad) # tensor([1., 2., 3., 4.])
print('y.grad:', y.grad) # tensor([1., 2., 3., 4.])
print('w.grad:', w.grad) # tensor([4., 6., 8., 10.])
print('z.grad:', z.grad) # None / tensor([1., 2., 3., 4.])
print('o.grad:', o.grad) # None / tensor(1.)

使用 retain_grad() 会增加内存的占用,更好的方法是使用 hook 保存中间变量的梯度。比如对于中间变量 zz,hook 的使用方法为:z.register_hook(hook_fn),其中 hook_fn 是一个用户自定义的函数,为简化表示也可以直接使用 lambda 表达式代替函数:

1
2
3
4
def hook_fn(grad): -> Tensor or None
z.register_hook(hook_fn)
# z.register_hook(lambda x: 2*x) # output Tensor
# z.register_hook(lambda x: print(x)) # output None

该函数输入为变量 zz 的梯度,输出为一个 Tensor 或 None。反向传播时,梯度传播到变量 zz 后,再继续往前传播之前,会先传入 hook_fn 函数。如果 hook_fn 返回值是 None,则梯度不改变,继续向前传播;如果 hook_fn 的返回值是 Tensor 类型,则该 Tensor 将取代变量 zz 原有的梯度,继续向前传播。

3. Hook for Modules

网络模块的 hook 不像 Tensor 一样具有显式的变量名可以访问,而是被封装在神经网络中。PyTorch 设计了两种 hook:register_forward_hookregister_backward_hook,分别用来获取前向传播和反向传播时中间层模块的输入和输出特征及梯度。

register_forward_hook

register_forward_hook 的作用是获取前向传播过程中,网络各模块的输入和输出。对于模块 module,其使用方法为:module.register_forward_hook(hook_fn),其中 hook_fn 为一个用户自定义的函数。

1
def hook_fn(module, input, output): -> Tensor or None

其中,hook_fn 函数的输入变量分别为模块、模块的输入和模块的输出,输出为 None 或 Tensor,用于修改模块的输出。使用该 hook,可以主方便地使用预训练的神经网格提取特征,而不用改变预训练网络的结构。

register_backward_hook

register_backward_hook 的作用是获取反向传播过程中,网络各模块的输入和输出。对于模块 module,其使用方法为:module.register_backward_hook(hook_fn),其中 hook_fn 为一个用户自定义的函数。

1
def hook_fn(module, grad_input, grad_output): -> Tensor or None

其中,hook_fn 函数的输入变量分别为模块、模块输入端的梯度和模块输出端的梯度,这里的输入输出端是站在前向传播的角度来说的。如果模块有多个输入端或输出端,则对应的梯度是 tuple 类型。比如对于线性模块,其 grad_input 是一个三元组,排列顺序分别为:对 bias 的导数、对输入 x 的导数、对权重 w 的导数。

附录