PyTorch:模型训练-parameters和optimizer

模型参数的查看

model.named_parameters()

[named_parameters(prefix: str = ''recurse: bool = True) → Iterator[Tuple[str, torch.Tensor]]]

迭代打印model.named_parameters()将会打印每一次迭代元素的名字和param(元素是 torch.nn.parameter.Parameter 类型)

for name, param in model.named_parameters():
    print(name,param.requires_grad)
    param.requires_grad=False  # 顺便改下属性

model.parameters()

[parameters(recurse: bool = True) → Iterator[torch.nn.parameter.Parameter]]

迭代打印model.parameters()将会打印每一次迭代元素的param而不会打印名字,这是他和named_parameters的区别,两者都可以用来改变requires_grad的属性

for  param in model.parameters():
    print(param.requires_grad)
    param.requires_grad=False

model.state_dict().items()

每次迭代打印该选项的话,会打印所有的name和param,但是这里的所有的param都是requires_grad=False,没有办法改变requires_grad的属性,所以改变requires_grad的属性只能通过上面的两种方式。

for name, param in model.state_dict().items():
    print(name,param.requires_grad=True)

获取参数个数torch.numel()

torch.numel(input) → int     Returns the total number of elements in the input tensor.

def get_parameter_number(net):
    total_num = sum(p.numel() for p in net.parameters())
    trainable_num = sum(p.numel() for p in net.parameters() if p.requires_grad)
    return {'Total': total_num, 'Trainable': trainable_num}


def get_parameter_number_details(net):
    trainable_num_details = {name: p.numel() for name, p in net.named_parameters() if p.requires_grad}
    return {'Trainable': trainable_num_details}

model = DCN(...)
print(get_parameter_number(model))
print(get_parameter_number_details(model))

模型参数初始化

神经网络的初始化是训练流程的重要基础环节,会对模型的性能、收敛性、收敛速度等产生重要的影响。

初始化方法

高斯初始化[TORCH.RANDN]

pytorch内置的torch.nn.init方法,常用的初始化操作,例如正态分布、均匀分布、xavier初始化、kaiming初始化等都已经实现,可以直接使用。
nn.init.xavier_uniform(net1[0].weight)

xavier初始化[nn.init.xavier_uniform_(tensorgain=1.0)]

参数声明及初始化

方式1

self.kernel = nn.Parameter(torch.Tensor(num_pairs, 1))
nn.init.xavier_uniform_(self.kernel)

方式2

route_weights = nn.Parameter(torch.randn(num_capsules, num_route_nodes, in_channels, out_channels))

两种常用的初始化操作

使用pytorch内置的torch.nn.init方法

修改nn.Linear默认的均匀分布初始化为正态分布:

        self.linear_layers = nn.ModuleList(
            [nn.Linear(hidden_units[i], hidden_units[i + 1]) for i in range(len(hidden_units) - 1)])
        for name, tensor in self.linear_layers.named_parameters():
            if 'weight' in name:
                nn.init.normal_(tensor, mean=0, std=init_std)   #init_std=0.0001

[torch.nn.init — PyTorch 2.0 documentation]

更加灵活的初始化借助numpy

对于自定义的初始化方法,有时tensor的功能不如numpy强大灵活,故可以借助numpy实现初始化方法,再转换到tensor上使用。
for layer in net1.modules():
    if isinstance(layer, nn.Linear): # 判断是否是线性层
        param_shape = layer.weight.shape
        layer.weight.data = torch.from_numpy(np.random.normal(0, 0.5, size=param_shape)) 
        # 定义为均值为 0,方差为 0.5 的正态分布

参数优化torch.optim

[pytorch源码阅读(二)optimizer原理 - 知乎]

示例

optimizer = torch.optim.SGD(net.parameters(), lr=0.1)

optimizer = torch.optim.Adam(lr=learning_rate, params=params)

optimizer = torch.optim.Adadelta(lr=config.learning_rate, rho=config.adadelta_decay_rate, eps=config.adadelta_epsilon, params=params)

optimizer.state_dict()

        优化器在训练过程中的状态字典,包含了优化器当前的超参数值、内部缓存(如动量、梯度累积等)以及其他与优化器状态相关的信息。 优化器在训练神经网络模型时,根据损失函数的梯度来更新模型的参数。 这个过程中,优化器会根据自己的算法来动态地调整超参数,并且记录下来当前的状态。

        torch保存模型时,如果还想保存某一次训练采用的优化器、epochs等信息,可将这些信息组合起来构成一个字典,然后将字典保存起来。这时就需要optimizer.state_dict()了。

[自定义保存内容(推荐)]

可学习的参数

即params参数对应的对象。

如果是通过net.parameters()入参的,net.parameters()中哪些生效可参考下面的[哪些参数可以在模型训练时被更新?]

自定义可学习的params

当然也可以自定义可学习的params并输入到optim参数中:
params = list()
params.append({'params': self.token_embedding.parameters(), 'is_embedding': True})
params.append({'params': self.linear.parameters()})

torch.optim.Adam(lr=learning_rate, params=params)

学习率调参optimizer.param_groups

自定义可学习参数列表时,可以通过代码控制embedding层过多少步才可以学习:

def update_lr(self, optimizer, epoch):
    if epoch > self.config.num_epochs_static_embedding:
        for param_group in optimizer.param_groups[:2]:
            param_group["lr"] = self.config.learning_rate
    else:
        for param_group in optimizer.param_groups[:2]:
            param_group["lr"] = 0

定义好optimizer后可以通过optimizer.param_groups来修改不同参数组的学习率等参数
for param_group in optimizer.param_groups:
    param_group["lr"] = self.config.learning_rate
Note: 这时的param_groups对应的就是params = list()的顺序[不同层使用不同学习率]。

冻结参数

在加载预训练模型的时候,想冻结前面几层,使其参数在训练过程中不发生变化。

首先从requires_grad讲起:在pytorch中,tensor有一个requires_grad参数,如果设置为True,则反向传播时,该tensor就会自动求导。tensor的requires_grad的属性默认为False,若一个节点(叶子变量:自己创建的tensor)requires_grad被设置为True,那么所有依赖它的节点requires_grad都为True(即使其他相依赖的tensor的requires_grad = False)。当requires_grad设置为False时,反向传播时就不会自动求导了,因此大大节约了显存或者说内存。

方式1:torch.no_grad no_grad — PyTorch 2.0 documentation

with torch.no_grad或者@torch.no_grad()的作用
在该模块下,所有计算得出的tensor的requires_grad都自动设置为False。
即使一个tensor(命名为x)的requires_grad = True,在with torch.no_grad计算,由x得到的新tensor(命名为w-标量)requires_grad也为False,且grad_fn也为None,即不会对w求导。

x = torch.tensor([1.], requires_grad=True)
with torch.no_grad():
    y = x * 2
y.requires_grad
@torch.no_grad()
def doubler(x):
    return x * 2
z = doubler(x)
z.requires_grad

参考sentencetransfomer中的anaconda3/Lib/site-packages/sentence_transformers/SentenceTransformer.py:164


方式2:

需要先知道每一层的名字:

net = Network()
for name, value in net.named_parameters():
    print('name: {0},\t grad: {1}'.format(name, value.requires_grad))
name: cnn.VGG_16.convolution1_1.weight, grad: True
name: cnn.VGG_16.convolution1_1.bias, grad: True
name: cnn.VGG_16.convolution1_2.weight, grad: True
name: cnn.VGG_16.convolution1_2.bias, grad: True
name: cnn.VGG_16.convolution2_1.weight, grad: True
name: cnn.VGG_16.convolution2_1.bias, grad: True
name: cnn.VGG_16.convolution2_2.weight, grad: True
name: cnn.VGG_16.convolution2_2.bias, grad: True

后面的True表示该层的参数可训练。

我们定义一个要冻结的层的列表:
no_grad = [
    'cnn.VGG_16.convolution1_1.weight',
    'cnn.VGG_16.convolution1_1.bias',
    'cnn.VGG_16.convolution1_2.weight',
    'cnn.VGG_16.convolution1_2.bias'
]

冻结方法如下:
for name, value in net.named_parameters():
    if name in no_grad:
        value.requires_grad = False
    else:
        value.requires_grad = True

冻结后再打印每层的信息:
name: cnn.VGG_16.convolution1_1.weight, grad: False
name: cnn.VGG_16.convolution1_1.bias, grad: False
name: cnn.VGG_16.convolution1_2.weight, grad: False
name: cnn.VGG_16.convolution1_2.bias, grad: False
name: cnn.VGG_16.convolution2_1.weight, grad: True
name: cnn.VGG_16.convolution2_1.bias, grad: True
name: cnn.VGG_16.convolution2_2.weight, grad: True
name: cnn.VGG_16.convolution2_2.bias, grad: True

可以看到前两层的weight和bias的requires_grad都为False,表示它们不可训练。
最后在定义优化器时,只对requires_grad为True的层的参数进行更新。

optimizer = optim.Adam(filter(lambda p: p.requires_grad, net.parameters()), lr=0.01)

[https://www.zhihu.com/question/311095447/answer/589307812]

[13个算法工程师必须掌握的PyTorch Tricks]

注册参数

torch.nn.Module.register_parameter(name, param)

Adds a parameter to the module.The parameter can be accessed as an attribute using given name. 

注册一个可训练更新的参数。

主要作用是:将一个不可训练的类型Tensor转换成可以训练的类型parameter,并将这个parameter绑定到这个module里面,相当于变成了模型的一部分,成为了模型中可以根据训练进行变化的参数。

[pytorch中的register_parameter()和parameter()_模糊包的博客-CSDN博客]

torch.nn.Module.register_buffer(name, tensor, persistent=True)

Adds a buffer to the module. 该方法的作用是定义一组参数,该组参数在模型训练时不会更新(即调用 optimizer.step() 后该组参数不会变化,只可人为地改变它们的值),但是保存模型时,该组参数又作为模型参数不可或缺的一部分被保存。

示例参考

[/transformers/blob/main/src/transformers/models/bert/modeling_bert.py#L195C9-L195C13]

哪些参数可以在模型训练时被更新?

如果定义优化器时是这样的:

optimizer = torch.optim.SGD(net.parameters(), lr=0.1)

可以通过net.named_parameters()查看。

分别以

(1)常见的nn.Module类形式

(2)self.register_buffer()形式

(3)self.register_parameter()形式

(4)python类的属性形式

定义了4组参数。

import torch 
import torch.nn as nn
from collections import OrderedDict

class Model(nn.Module):
    def __init__(self):
        super(Model, self).__init__()
        # (1)常见定义模型时的操作
        self.param_nn = nn.Sequential(OrderedDict([
            ('conv', nn.Conv2d(1, 1, 3, bias=False)),
            ('fc', nn.Linear(1, 2, bias=False))
        ]))

        # (2)使用register_buffer()定义一组参数
        self.register_buffer('param_buf', torch.randn(1, 2))

        # (3)使用形式类似的register_parameter()定义一组参数
        self.register_parameter('param_reg', nn.Parameter(torch.randn(1, 2)))

        # (4)按照类的属性形式定义一组变量
        self.param_attr = torch.randn(1, 2) 

    def forward(self, x):
        return x

net = Model()

只有(1)和(3)定义的参数可以被更新,而self.register_buffer()和以python类的属性形式定义的参数都不能被更新。也就是说,modules和parameters可以被更新,而buffers和普通类属性不行。

哪些才算是模型的参数呢?

模型的所有参数都装在 state_dict 中,因为保存模型参数时直接保存 net.state_dict()。

In [9]: net.state_dict()
Out[9]:
OrderedDict([('param_reg', tensor([[-0.0617, -0.8984]])),
             ('param_buf', tensor([[-1.0517,  0.7663]])),
             ('param_nn.conv.weight',
              tensor([[[[-0.3183, -0.0426, -0.2984],
                        [-0.1451,  0.2686,  0.0556],
                        [-0.3155,  0.0451,  0.0702]]]])),
             ('param_nn.fc.weight',
              tensor([[-0.4647],
                      [ 0.7753]]))])

可以看到,通过 nn.Module 类、self.register_buffer() 以及 self.register_parameter() 定义的参数都在 state-dict 中,只有用python类的属性形式定义的参数不包含其中。也就是说,保存模型时,buffers,modules和parameters都可以被保存,但普通属性不行。

[PyTorch nn.Module中的self.register_buffer()解析 - 简书]

from: -柚子皮-

ref: 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值