Parameter 和 Buffer 的整体理解
- 在 PyTorch 的
nn.Module里,Parameter和Buffer都是张量类型(是两种类型的张量) - 两者的关键区别有:
- 可训练性 :
- Parameter 可训练 ,通常用于模型的权重和偏置,会在反向传播时被优化器更新
- Buffer 不可训练 ,通常用于存储需要在训练或推理过程中保留,但不需要梯度更新的值(如 BatchNorm 的统计信息)
- 注册方式 :
- Parameter 显示定义字段:通过
nn.Parameter()初始化,或通过nn.Linear()等类初始化 - Buffer 一般是隐式定义:通过
register_buffer或 BN 层等隐式自动定义
- Parameter 显示定义字段:通过
- 访问方式:
- Parameter 作为可训练的张量,会被自动添加到模型的
parameters()迭代器中 - Buffer 是不可训练的张量,不会被添加到
parameters()中,也不会被优化器更新
- Parameter 作为可训练的张量,会被自动添加到模型的
- 可训练性 :
- 两者的共同点有:
- 两者都会被保存在模型的
state_dict中,因此在保存/加载模型时都会被保留 - 当调用
model.to(device)时,Parameter和Buffer都会被移动到指定设备
- 两者都会被保存在模型的
- 最佳实践:
- Parameter用于
- 定义模型权重、偏置等需要学习的参数
- Buffer用于
- 非训练状态的统计量(如 BatchNorm 的均值/方差)
- 固定的预训练权重或常量张量
- 以及其他需要与模型一起保存 ,但不需要梯度的中间结果
- Parameter用于
Parameter 和 Buffer 的定义方式
- 需要注意的关键经验和知识点:
- 定义位置 :建议将 Parameter 和 Buffer 都定义在
__init__中(虽然可以动态定义到__init__之外) - 同名覆盖规则 :
- Buffer 对象之间可以互相覆盖
- Parameter 对象之间可以互相覆盖
- Parameter 对象可以覆盖 Buffer 对象
- Buffer 对象不可以覆盖 Parameter 对象
- Parameter 或 Buffer 为 None 时 ,仅仅是一个声明 ,不会被
parameters()、buffers()或state_dict()等包含(注:named_xx()和xx()数量一样,也不会包含) - 冻结参数 :属性为
requires_grad=False的参数不会被更新(但可以被parameters()返回,也可以被加入优化器,此时有优化器状态,但是没有梯度,也不会被更新) - 初始化类型要求 :
- Parameter 对象一定要用
nn.Parameter对象初始化 - Buffer 对象可以用
Tensor对象初始化
- Parameter 对象一定要用
- Parameter 和 Buffer 更新规则 :
- in-place update :
- 可使用
model.x.data += 2或model.x.data.fill_(2.0)的方式修改 Buffer 或 Parameter 的值,实现 in-place update - 此时针对 Parameter,不需要重新初始化 优化器
- 可使用
- 替换张量数据:
- 当使用类似
model.x.data = torch.tensor(2.0)的方式修改,或重新注册新的参数对象时,此时会替换整个 Buffer 或 Parameter 对象 - 此时针对 Parameter,需要重新初始化 优化器 ,否则优化器无法识别到被修改后的参数的张量
- 当使用类似
- in-place update :
- 优化器更新规则 :
- 对于被重新赋值
data张量的参数,需要重新初始化 优化器 ,否则优化器无法识别到被修改后的参数的张量 - 对于新增加的参数 ,必须重新初始化优化器 ,以保证优化器能够优化到新的参数
- 如果一个参数没有被优化器追踪(被追踪的参数在
optimizer.param_groups()中),该参数不会被更新(即使在loss.backward()阶段已经计算了梯度,参数也不会被更新) - 更多补充见附录
- 对于被重新赋值
- 定义位置 :建议将 Parameter 和 Buffer 都定义在
- Parameter 和 Buffer 定义代码和测试(详细阅读注释和输出部分)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285import torch
import torch.nn as nn
import math
class RegistrationDemo(nn.Module):
def __init__(self):
super().__init__()
# 1. 直接定义Parameter
self.direct_param = nn.Parameter(torch.randn(3, 3))
# 2. 使用register_parameter()方法
custom_tensor = torch.ones(2, 2) * 0.5
self.register_parameter('explicit_param', nn.Parameter(custom_tensor)) # 必须是 nn.Parameter 对象或 None,如果是其他对象会出错
# 3. 通过nn.Linear隐式注册Parameter
self.linear = nn.Linear(8 * 32 * 32, 4) # 输入维度=8×32×32
# 4. 通过nn.Conv2d隐式注册Parameter
self.conv = nn.Conv2d(3, 8, kernel_size=3, padding=1)
# 5. 通过nn.BatchNorm2d隐式注册Parameter和Buffer
self.bn = nn.BatchNorm2d(8) # weight(Parameter)和bias(Parameter), running_mean和running_var(Buffer)
# 6. 自定义Buffer
self.register_buffer('custom_buffer', torch.tensor([math.pi])) # 必须是 Tensor 对象或 None,如果是其他对象会出错
# 7. 使用不同初始化方式的Parameter
self.xavier_init = nn.Parameter(torch.zeros(5, 5))
nn.init.xavier_uniform_(self.xavier_init)
self.kaiming_init = nn.Parameter(torch.zeros(5, 5))
nn.init.kaiming_normal_(self.kaiming_init)
# 8. 可学习的标量Parameter
self.scalar_param = nn.Parameter(torch.tensor(0.1))
# 9. 冻结的Parameter (requires_grad=False)
self.frozen_param = nn.Parameter(torch.tensor(0.1), requires_grad=False)
# 10. 预声明动态注册的参数名(重要!避免state_dict问题,注意,不推荐这么使用,但部分情况下可以用来做高阶的模型设计)
self.register_parameter('dynamic_param', None) # 仅声明参数名,但该参数不会被加入 state_dict,直到被初始化为实际的 Parameter 对象
self.register_buffer('dynamic_buffer', None) # 仅声明缓冲区名,但该参数不会被加入 state_dict,直到被初始化为实际的 Buffer 对象
# 11. 测试通过register重复注册对象
self.register_parameter('test_multiple_param', nn.Parameter(torch.tensor(1.0))) # 定义参数 test_multiple_param
self.register_parameter('test_multiple_param', nn.Parameter(torch.tensor(2.0))) # 重新修改参数对象,值为2.0(注意不是简单的修改值)
self.test_multiple_param = nn.Parameter(torch.tensor(3.0)) # 重新修改参数对象,值为3.0(注意不是简单的修改值)
self.test_multiple_param = nn.Parameter(torch.tensor(4.0)) # 重新修改参数对象,值为4.0(注意不是简单的修改值)
self.register_parameter('test_multiple_param', nn.Parameter(torch.tensor(5.0))) # 重新修改参数对象,值为5.0(注意不是简单的修改值)
# 11(a)特别地,buffer的注册和参数类似,但 Parameter 可以覆盖 Buffer 对象,但 Buffer 不可覆盖 Parameter 对象
self.register_buffer('test_multiple_buffer', nn.Parameter(torch.tensor(1.0)))
self.register_buffer('test_multiple_buffer', nn.Parameter(torch.tensor(2.0)))
self.test_multiple_buffer = nn.Parameter(torch.tensor(3.0)) # 重新修改 Buffer 对象为 Parameter 对象,值为3.0(注意不是简单的修改值)
self.test_multiple_buffer = nn.Parameter(torch.tensor(4.0)) # 重新修改 Parameter 对象,值为4.0(注意不是简单的修改值)
# self.register_buffer('test_multiple_buffer', nn.Parameter(torch.tensor(5.0))) # 这行会报错 KeyError: "attribute 'test_multiple_buffer' already exists":Buffer 不允许使用 register_buffer 覆盖 Parameter对象
# 11(b) Buffer 不可 覆盖 Parameter 对象的再次验证
self.register_buffer('test_multiple_buffer_param', nn.Parameter(torch.tensor(1.0)))
self.test_multiple_buffer_param = torch.tensor(1.5) # 仍然是 Buffer 对象,值变成1.5
self.test_multiple_buffer_param += 10 # 仍然是 Buffer 对象,值变成11.5,且是 原地修改(in-place update)
# self.register_parameter('test_multiple_buffer_param', nn.Parameter(torch.tensor(2.0))) # 这行会报错,KeyError: "attribute 'test_multiple_buffer_param' already exists",不允许 Buffer 和 Parameter 互相覆盖
self.test_multiple_buffer_param = nn.Parameter(torch.tensor(3.0)) # 通过重写将 Buffer修改为 Parameter 对象
self.register_parameter('test_multiple_buffer_param', nn.Parameter(torch.tensor(4.0))) # 这行不会报错,因为test_multiple_buffer_param已经是 Parameter 对象了,可以被参数重写
# 11(c) Buffer 不可 覆盖 Parameter 对象的再次验证(即使是通过 register_parameter 注册的参数也不能覆盖)
self.register_parameter('test_multiple_param_buffer', nn.Parameter(torch.tensor(1.0)))
# self.register_buffer('test_multiple_param_buffer', nn.Parameter(torch.tensor(2.0))) # 这行会报错,KeyError: "attribute 'test_multiple_param_buffer' already exists",不允许 Buffer 和 Parameter 互相覆盖
def init_dynamic_params(self, input_size):
"""在__init__外动态注册Parameter和Buffer(注意,不推荐在 __init__ 外定义 Parameter 和 Buffer ,但部分情况下可以用来做高阶的模型设计)"""
# 动态注册Parameter(注册__init__中声明过的参数)
self.register_parameter('dynamic_param', nn.Parameter(torch.randn(input_size)))
# 动态注册Buffer(注册__init__中声明过的参数)
self.register_buffer('dynamic_buffer', torch.arange(5).float())
# 注册__init__中未声明的参数,也可以被正常更新,但需要保证定义执行此语句后再执行 forward 操作
self.register_parameter('dynamic_param2', nn.Parameter(torch.randn(1)))
def forward(self, x):
x = self.conv(x)
x = self.bn(x)
x = x * self.scalar_param
x = x * self.frozen_param
if self.dynamic_param is not None: # 不论是否提前初始化,均没有问题,因为 __init__ 中已经声明
x = x + self.dynamic_param.mean()
if self.dynamic_param2 is not None: # 如果在调用 forward 前未定义 dynamic_param2,会报错,建议像是 dynamic_param 一样在 __init__ 函数中进行声明
x = x + self.dynamic_param2
x = x.view(x.size(0), -1) # 展平为 (batch_size, 8*32*32)
x = self.linear(x)
return x
def print_registration_info(model):
print("\n=== 模型整体结构 ===")
print(model)
print("\n=== Parameters ===")
for name, param in model.named_parameters():
print(f"参数名称: {name}, 形状: {param.shape}, 类型: {type(param)}, 是否可训练: {param.requires_grad}")
print("\n=== Buffers ===")
for name, buffer in model.named_buffers():
print(f"缓冲区名称: {name}, 形状: {buffer.shape}, 类型: {type(buffer)}")
print("\n=== state_dict ===")
for key in model.state_dict().keys():
print(f"Key: {key}")
if __name__ == "__main__":
# 创建模型但不初始化动态参数
model = RegistrationDemo()
# 打印注册信息(动态参数尚未初始化)
print("初始化前的参数状态:")
print_registration_info(model)
# 初始化动态参数
model.init_dynamic_params(input_size=4)
# 打印注册信息(动态参数已初始化)
print("\n初始化后的参数状态:")
print_registration_info(model)
# 测试前向传播
x = torch.randn(2, 3, 32, 32)
output = model(x)
# 验证动态参数是否参与训练
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
loss = output.sum()
loss.backward()
print()
print("训练前 dynamic_param:", model.dynamic_param.data)
print("训练前 dynamic_param2:", model.dynamic_param2.data)
print("训练前 scalar_param:", model.scalar_param.data)
print("训练前 frozen_param:", model.frozen_param.data)
optimizer.step() # 更新参数,注意:这里只更新需要更新的参数,不参与训练的参数不会被更新(即使他们被加入了优化器中)
print("训练后 dynamic_param:", model.dynamic_param.data)
print("训练前 dynamic_param2:", model.dynamic_param2.data)
print("训练后 scalar_param:", model.scalar_param.data)
print("训练后 frozen_param(未发生改变):", model.frozen_param.data)
print()
print("test_multiple_param:", model.test_multiple_param)
print("test_multiple_buffer:", model.test_multiple_buffer)
# 初始化前的参数状态:
#
# === 模型整体结构 ===
# RegistrationDemo(
# (linear): Linear(in_features=8192, out_features=4, bias=True)
# (conv): Conv2d(3, 8, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
# (bn): BatchNorm2d(8, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
# )
#
# === Parameters ===
# 参数名称: direct_param, 形状: torch.Size([3, 3]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
# 参数名称: explicit_param, 形状: torch.Size([2, 2]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
# 参数名称: xavier_init, 形状: torch.Size([5, 5]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
# 参数名称: kaiming_init, 形状: torch.Size([5, 5]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
# 参数名称: scalar_param, 形状: torch.Size([]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
# 参数名称: frozen_param, 形状: torch.Size([]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: False
# 参数名称: test_multiple_param, 形状: torch.Size([]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
# 参数名称: test_multiple_buffer, 形状: torch.Size([]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
# 参数名称: test_multiple_buffer_param, 形状: torch.Size([]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
# 参数名称: test_multiple_param_buffer, 形状: torch.Size([]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
# 参数名称: linear.weight, 形状: torch.Size([4, 8192]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
# 参数名称: linear.bias, 形状: torch.Size([4]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
# 参数名称: conv.weight, 形状: torch.Size([8, 3, 3, 3]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
# 参数名称: conv.bias, 形状: torch.Size([8]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
# 参数名称: bn.weight, 形状: torch.Size([8]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
# 参数名称: bn.bias, 形状: torch.Size([8]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
#
# === Buffers ===
# 缓冲区名称: custom_buffer, 形状: torch.Size([1]), 类型: <class 'torch.Tensor'>
# 缓冲区名称: bn.running_mean, 形状: torch.Size([8]), 类型: <class 'torch.Tensor'>
# 缓冲区名称: bn.running_var, 形状: torch.Size([8]), 类型: <class 'torch.Tensor'>
# 缓冲区名称: bn.num_batches_tracked, 形状: torch.Size([]), 类型: <class 'torch.Tensor'>
#
# === state_dict ===
# Key: direct_param
# Key: explicit_param
# Key: xavier_init
# Key: kaiming_init
# Key: scalar_param
# Key: frozen_param
# Key: test_multiple_param
# Key: test_multiple_buffer
# Key: test_multiple_buffer_param
# Key: test_multiple_param_buffer
# Key: custom_buffer
# Key: linear.weight
# Key: linear.bias
# Key: conv.weight
# Key: conv.bias
# Key: bn.weight
# Key: bn.bias
# Key: bn.running_mean
# Key: bn.running_var
# Key: bn.num_batches_tracked
#
# 初始化后的参数状态:
#
# === 模型整体结构 ===
# RegistrationDemo(
# (linear): Linear(in_features=8192, out_features=4, bias=True)
# (conv): Conv2d(3, 8, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
# (bn): BatchNorm2d(8, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
# )
#
# === Parameters ===
# 参数名称: direct_param, 形状: torch.Size([3, 3]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
# 参数名称: explicit_param, 形状: torch.Size([2, 2]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
# 参数名称: xavier_init, 形状: torch.Size([5, 5]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
# 参数名称: kaiming_init, 形状: torch.Size([5, 5]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
# 参数名称: scalar_param, 形状: torch.Size([]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
# 参数名称: frozen_param, 形状: torch.Size([]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: False
# 参数名称: dynamic_param, 形状: torch.Size([4]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
# 参数名称: test_multiple_param, 形状: torch.Size([]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
# 参数名称: test_multiple_buffer, 形状: torch.Size([]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
# 参数名称: test_multiple_buffer_param, 形状: torch.Size([]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
# 参数名称: test_multiple_param_buffer, 形状: torch.Size([]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
# 参数名称: dynamic_param2, 形状: torch.Size([1]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
# 参数名称: linear.weight, 形状: torch.Size([4, 8192]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
# 参数名称: linear.bias, 形状: torch.Size([4]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
# 参数名称: conv.weight, 形状: torch.Size([8, 3, 3, 3]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
# 参数名称: conv.bias, 形状: torch.Size([8]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
# 参数名称: bn.weight, 形状: torch.Size([8]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
# 参数名称: bn.bias, 形状: torch.Size([8]), 类型: <class 'torch.nn.parameter.Parameter'>, 是否可训练: True
#
# === Buffers ===
# 缓冲区名称: custom_buffer, 形状: torch.Size([1]), 类型: <class 'torch.Tensor'>
# 缓冲区名称: dynamic_buffer, 形状: torch.Size([5]), 类型: <class 'torch.Tensor'>
# 缓冲区名称: bn.running_mean, 形状: torch.Size([8]), 类型: <class 'torch.Tensor'>
# 缓冲区名称: bn.running_var, 形状: torch.Size([8]), 类型: <class 'torch.Tensor'>
# 缓冲区名称: bn.num_batches_tracked, 形状: torch.Size([]), 类型: <class 'torch.Tensor'>
#
# === state_dict ===
# Key: direct_param
# Key: explicit_param
# Key: xavier_init
# Key: kaiming_init
# Key: scalar_param
# Key: frozen_param
# Key: dynamic_param
# Key: test_multiple_param
# Key: test_multiple_buffer
# Key: test_multiple_buffer_param
# Key: test_multiple_param_buffer
# Key: dynamic_param2
# Key: custom_buffer
# Key: dynamic_buffer
# Key: linear.weight
# Key: linear.bias
# Key: conv.weight
# Key: conv.bias
# Key: bn.weight
# Key: bn.bias
# Key: bn.running_mean
# Key: bn.running_var
# Key: bn.num_batches_tracked
#
# 训练前 dynamic_param: tensor([-1.2286, 0.4382, 2.0483, 0.1235])
# 训练前 dynamic_param2: tensor([-0.3353])
# 训练前 scalar_param: tensor(0.1000)
# 训练前 frozen_param: tensor(0.1000)
# 训练后 dynamic_param: tensor([-1.2186, 0.4482, 2.0583, 0.1335])
# 训练前 dynamic_param2: tensor([-0.3253])
# 训练后 scalar_param: tensor(0.0900)
# 训练后 frozen_param(未发生改变): tensor(0.1000)
#
# test_multiple_param: Parameter containing:
# tensor(5., requires_grad=True)
# test_multiple_buffer: Parameter containing:
# tensor(4., requires_grad=True)
附录:loss.backward() 和 optimizer.step() 的工作流程
loss.backward()负责计算梯度并存储到参数的.grad中(若requires_grad = False则不会计算梯度)optimizer.step()负责根据梯度更新参数(.grad为None时不更新)- 如果在
loss.backward()之前执行requires_grad = False可保证.grad为None,参数不会更新 - 如果在
loss.backward()和optimizer.step()中间执行requires_grad = False,参数会更新这一次,下次不会更新- 梯度计算发生在
loss.backward()阶段 :此时参数的requires_grad为True,梯度已被计算并存储在param.grad中 - 优化器只检查
.grad是否为None:修改requires_grad = False不会清除已计算的梯度,因此优化器仍会使用已有的梯度更新参数 - 后续迭代中参数被忽略:一旦
requires_grad = False,后续的loss.backward()将不再计算该参数的梯度,优化器也会跳过它
- 梯度计算发生在