PyTorch——Module中的Parameter和Buffer


Parameter 和 Buffer 的整体理解

  • 在 PyTorch 的 nn.Module 里,ParameterBuffer 都是张量类型(是两种类型的张量)
  • 两者的关键区别有:
    • 可训练性
      • Parameter 可训练 ,通常用于模型的权重和偏置,会在反向传播时被优化器更新
      • Buffer 不可训练 ,通常用于存储需要在训练或推理过程中保留,但不需要梯度更新的值(如 BatchNorm 的统计信息)
    • 注册方式
      • Parameter 显示定义字段:通过 nn.Parameter() 初始化,或通过 nn.Linear() 等类初始化
      • Buffer 一般是隐式定义:通过 register_buffer 或 BN 层等隐式自动定义
    • 访问方式:
      • Parameter 作为可训练的张量,会被自动添加到模型的 parameters() 迭代器中
      • Buffer 是不可训练的张量,不会被添加到 parameters() 中,也不会被优化器更新
  • 两者的共同点有:
    • 两者都会被保存在模型的 state_dict 中,因此在保存/加载模型时都会被保留
    • 当调用 model.to(device) 时,ParameterBuffer 都会被移动到指定设备
  • 最佳实践:
    • Parameter用于
      • 定义模型权重、偏置需要学习的参数
    • Buffer用于
      • 非训练状态的统计量(如 BatchNorm 的均值/方差)
      • 固定的预训练权重或常量张量
      • 以及其他需要与模型一起保存但不需要梯度的中间结果

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 和 Buffer 更新规则
      • in-place update
        • 可使用 model.x.data += 2model.x.data.fill_(2.0) 的方式修改 Buffer 或 Parameter 的值,实现 in-place update
        • 此时针对 Parameter,不需要重新初始化 优化器
      • 替换张量数据:
        • 当使用类似 model.x.data = torch.tensor(2.0) 的方式修改,或重新注册新的参数对象时,此时会替换整个 Buffer 或 Parameter 对象
        • 此时针对 Parameter,需要重新初始化 优化器 ,否则优化器无法识别到被修改后的参数的张量
    • 优化器更新规则
      • 对于被重新赋值data张量的参数,需要重新初始化 优化器 ,否则优化器无法识别到被修改后的参数的张量
      • 对于新增加的参数 ,必须重新初始化优化器 ,以保证优化器能够优化到新的参数
      • 如果一个参数没有被优化器追踪(被追踪的参数在 optimizer.param_groups() 中),该参数不会被更新(即使在 loss.backward() 阶段已经计算了梯度,参数也不会被更新)
      • 更多补充见附录
  • 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
    285
    import 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() 负责根据梯度更新参数(.gradNone时不更新)
  • 如果在 loss.backward() 之前执行 requires_grad = False 可保证 .gradNone,参数不会更新
  • 如果在 loss.backward()optimizer.step() 中间执行 requires_grad = False,参数会更新这一次,下次不会更新
    • 梯度计算发生在 loss.backward() 阶段 :此时参数的 requires_gradTrue,梯度已被计算并存储在 param.grad
    • 优化器只检查 .grad 是否为 None :修改 requires_grad = False 不会清除已计算的梯度,因此优化器仍会使用已有的梯度更新参数
    • 后续迭代中参数被忽略:一旦 requires_grad = False,后续的 loss.backward() 将不再计算该参数的梯度,优化器也会跳过它