Pytorch 预训练模型加载、修改网络结构并固定某层参数训练、不同层采用不同的学习率

目录

一、Pytorch中state_dict()、named_parameters()、model.parameter()、named_modules()区别

1、model.state_dict():

2、model.named_parameters():

3、model.parameter():

4、model.named_modules() 

二、Pytorch 载入预训练模型,并修改网络结构,固定某层参数学习

1、加载预训练模型、并修改网络结构

△ 本例子以Pytorch自带模型为例:

△ 本例子以自己保存的模型参数为例:

△ 修改某层后,快速加载之前的参数:

2、固定某层参数学习

以下为加深理解:

 1)、当不冻结层时

2)、冻结 fc1 层时

3)、只训练 fc1 层

总结:

三、对不同的层采用不同的学习率


一、Pytorch中state_dict()、named_parameters()、model.parameter()、named_modules()区别

1、model.state_dict():

        是将 layer_name 与 layer_param 以键的形式存储为 dict 。包含所有层的名字和参数,所存储的模型参数 tensor 的 require_grad 属性都是 False 。输出的值不包括 require_grad 。在固定某层时不能采用 model.state_dict() 来获取参数设置 require_grad 属性。

例子:

# -*- coding: utf-8 -*-
import torch.nn as nn

class net(nn.Module):
    def __init__(self):
        super(net, self).__init__()

        self.conv1 = nn.Sequential(
            nn.Conv3d(1, 3,kernel_size=3, stride=1, padding=0, bias=False),
        )
        self.conv2 = nn.Sequential(
            nn.Conv3d(3, 6, kernel_size=3, stride=1, padding=0, bias=False),
        )
        self.conv3 = nn.Sequential(
            nn.Conv3d(6, 12,kernel_size=3, stride=1, padding=0, bias=False),
        )

    def forward(self, x):
        out = self.conv1(x)
        out = self.conv2(out)
        out = self.conv3(out)
        return out

if __name__ == '__main__':
    model = net()
    for k,v in model.state_dict().items():
        print(k,"\t",v.shape)

输出:

D:\anaconda3\python.exe ./code_.py
conv1.0.weight 	 tensor([[[[[-0.1776, -0.0824, -0.0484],
           [-0.0416, -0.1229,  0.0133],
           [-0.0035, -0.0472,  0.0650]],
            ...       ....      ....

          [[ 0.0080, -0.1849, -0.0697],
           [-0.0528, -0.0102, -0.0746],
           [ 0.1616,  0.0512,  0.0249]]]]])  # 没有 require_grad

# D:\anaconda3\python.exe G:/me-zt/code_.py
# conv1.0.weight 	 torch.Size([3, 1, 3, 3, 3])
# conv2.0.weight 	 torch.Size([6, 3, 3, 3, 3])
# conv3.0.weight 	 torch.Size([12, 6, 3, 3, 3])

# Process finished with exit code 0

2、model.named_parameters():

        是将 layer_name 与 layer_param 以打包成一个元祖然后再存到 list 当中。只保存可学习、可被更新的参数。model.named_parameters() 所存储的模型参数 tensor 的 require_grad 属性都是   True常用于固定某层的参数是否被训练,通常是通过 model.named_parameters() 来获取参数设置 require_grad 属性。

例子:

# -*- coding: utf-8 -*-
import torch.nn as nn

class net(nn.Module):
    def __init__(self):
        super(net, self).__init__()

        self.conv1 = nn.Sequential(
            nn.Conv3d(1, 3,kernel_size=3, stride=1, padding=0, bias=False),
        )
        self.conv2 = nn.Sequential(
            nn.Conv3d(3, 6, kernel_size=3, stride=1, padding=0, bias=False),
        )
        self.conv3 = nn.Sequential(
            nn.Conv3d(6, 12,kernel_size=3, stride=1, padding=0, bias=False),
        )

    def forward(self, x):
        out = self.conv1(x)
        out = self.conv2(out)
        out = self.conv3(out)
        return out

if __name__ == '__main__':
    model = net()
    for k,v in model.named_parameters():
        print(k,"\t",v)

输出:

D:\anaconda3\python.exe ./code_.py
conv1.0.weight 	 Parameter containing:
tensor([[[[[-0.0079,  0.1026,  0.1596],
           [-0.1321, -0.0128,  0.1632],
           [-0.0125, -0.0934, -0.0056]],
            ...        ...        ...

          [[ 0.0135,  0.1755,  0.0007],
           [ 0.0541,  0.1801, -0.1160],
           [-0.0295, -0.0584,  0.1403]]]]], requires_grad=True)   # 有 require_grad

3、model.parameter():

        返回的只是参数,不包括 layer_name 。返回结果包含 require_grad ,且均为 Ture ,这主要是网络在创建时,默认参数都是需要学习的,即 require_grad 都是 True。

例子:

# -*- coding: utf-8 -*-
import torch.nn as nn

class net(nn.Module):
    def __init__(self):
        super(net, self).__init__()

        self.conv1 = nn.Sequential(
            nn.Conv3d(1, 3,kernel_size=3, stride=1, padding=0, bias=False),
        )
        self.conv2 = nn.Sequential(
            nn.Conv3d(3, 6, kernel_size=3, stride=1, padding=0, bias=False),
        )
        self.conv3 = nn.Sequential(
            nn.Conv3d(6, 12,kernel_size=3, stride=1, padding=0, bias=False),
        )

    def forward(self, x):
        out = self.conv1(x)
        out = self.conv2(out)
        out = self.conv3(out)
        return out

if __name__ == '__main__':
    model = net()
    for k in model.parameters():
        print(k.shape)

输出:

D:\anaconda3\python.exe ./code_.py
Parameter containing:
tensor([[[[[-0.1761,  0.1335, -0.1649],
           [ 0.0866, -0.0375, -0.0698],
           [-0.0400, -0.1483, -0.1746]],
            ...        ...        ...

          [[ 0.1708, -0.1187,  0.1409],
           [ 0.1374,  0.0351, -0.0047],
           [ 0.0362, -0.1237,  0.0445]]]]], requires_grad=True)  # 默认为 Ture

4、model.named_modules() 

        返回每一层模型的名字和结构:

# -*- coding: utf-8 -*-
import torch.nn as nn

class net(nn.Module):
    def __init__(self):
        super(net, self).__init__()

        self.conv1 = nn.Sequential(
            nn.Conv3d(1, 3,kernel_size=3, stride=1, padding=0, bias=False),
        )
        self.conv2 = nn.Sequential(
            nn.Conv3d(3, 6, kernel_size=3, stride=1, padding=0, bias=False),
        )
        self.conv3 = nn.Sequential(
            nn.Conv3d(6, 12,kernel_size=3, stride=1, padding=0, bias=False),
        )

    def forward(self, x):
        out = self.conv1(x)
        out = self.conv2(out)
        out = self.conv3(out)
        return out

if __name__ == '__main__':
    model = net()
    for name, module in model.named_modules():
        print(name)

输出:

# D:/02.py"
# 
# conv1
# conv1.0
# conv2
# conv2.0
# conv3
# conv3.0

二、Pytorch 载入预训练模型,并修改网络结构,固定某层参数学习

        注意:需要用到提取预训练模型的哪些层,那么自己创建的网络中,这些层的名字参数都不能改变,必须与之相同。但是,需要修改的层,名字不能与原来网络的相同。

1、加载预训练模型、并修改网络结构

△ 本例子以Pytorch自带模型为例:

import torchvision.models as models

# 创建model
resnet50 = models.resnet50(pretrained=True)  # 创建预训练模型,并加载参数
net_later = net()   # 创建我们的网络

# 读取网络参数
pretrained_dict = resnet50 ().state_dict()  # 读取预训练模型参数
net_dict = net_later().state_dict()       # 读取我们的网络参数

# 将pretrained_dict里不属于net_dict的键剔除掉
pretrained_dict = {k: v for k, v in pretrained_dict.items() if k in net_dict}

# 更新修改之后的net_dict
net_dict.update(pretrained_dict)  # 将与 pretrained_dict 中 layer_name 相同的参数更新为 pretrained_dict 的

# 加载我们真正需要的state_dict
net_later.load_state_dict(net_dict)

△ 本例子以自己保存的模型参数为例:

model = net() # 创建自己的网络,该网络是修改之后的

# 读取修改之前自己模型保存的参数权重
logdir = "2021_08-24_14-56-58_"
NET_PARAMS_PATH = os.path.join(os.getcwd(), "log", logdir, "net_params(5).pkl")
net_params = torch.load(NET_PARAMS_PATH)

# 读取网络参数
model_dict = model.state_dict()  # 读取修改之后的网络参数

# 将之前保存的模型参数(net_params )里不属于 net_dict 的键剔除掉
pretrained_dict = {k: v for k, v in net_params["model_state_dict"].items() if k in net_dict}

# 更新修改之后的 model_dict
model_dict.update(pretrained_dict)

# 加载我们真正需要的 state_dict
model.load_state_dict(net_dict)

△ 修改某层后,快速加载之前的参数:

model = net() # 创建自己的网络,该网络是修改之后的

# 读取修改之前自己模型保存的参数权重
logdir = "2021_08-24_14-56-58_"
NET_PARAMS_PATH = os.path.join(os.getcwd(), "log", logdir, "net_params(5).pkl")
net_params = torch.load(NET_PARAMS_PATH)

# 加载模型参数
model.load_state_dict(net_params['model_state_dict'], strict=False) # 加载时需要设置 strict=None 为 False 属性。这样加载时就会忽略加载某层参数名与创建的网络某一层参数名字的不同而引起的报错,此时该层的参数不会使用所保存的参数,以达到修改的效果。

2、固定某层参数学习

        如果载入的这些权重参数中,有些权重参数需要被更新,即固定不变,不参与训练,需要手动设置这些参数的梯度属性为 Fasle ,并且在 Optimizer 传参时筛选掉这些参数注意顺序,不能改变)

        代码:

model = net()

# 例如冻结 fc1 层的参数
for name, param in model.named_parameters():
    if "fc1" in name:
        param.requires_grad = False

# 定义一个 fliter ,只传入 requires_grad=True 的模型参数
optimizer = optim.SGD(filter(lambda p : p.requires_grad, model.parameters()), lr=1e-2)  

        训练一段时间后如果想再打开之前冻结的层,只要 model 的 reauire_grade 设置为 True 。
同时,不要忘记将优化器再重新加载一遍,否则虽然设置为 True ,依然还是固定训练。

for k,v in model.named_parameters():
        v.requires_grad=True  # 固定层打开
optimizer = optim.Adam(model.parameters(),lr=0.01)
scheduler = torch.optim.lr_scheduler.StepLR(optimizer,step_size=10, gamma=0.5)

        或者用下面的代码更好

if epoch==50:
    for parameter in deeplabv3.classifier.parameters():
        if parameter.requires_grad==False:
            parameter.requires_grad = True
            optimizer.add_param_group({'params': parameter})

'''
冻结的是deeplabv3的classifier层中的卷积,所以只循环这一层即可。
其实也可以直接optimizer.add_param_group({'params': deeplabv3.classifier.xx.parameters()})
但是,如果有数字就只能用上面的方法
'''

        冻结时的网络层名如何查看

for name, param in deeplabv3.named_parameters():
    print(name)

# D:\Anaconda3\python.exe "D:/03.py"
# backbone.conv1.weight
# backbone.bn1.weight
# backbone.bn1.bias
# backbone.layer1.0.conv1.weight
# backbone.layer1.0.bn1.weight
# backbone.layer1.0.bn1.bias
# backbone.layer1.0.conv2.weight
# backbone.layer1.0.bn2.weight
# backbone.layer1.0.bn2.bias
# backbone.layer1.0.conv3.weight
# backbone.layer1.0.bn3.weight
# backbone.layer1.0.bn3.bias
# backbone.layer1.0.downsample.0.weight
# backbone.layer1.0.downsample.1.weight
# backbone.layer1.0.downsample.1.bias
# backbone.layer1.1.conv1.weight

以下为加深理解:

 1)、当不冻结层时

       此类情况,在训练过程中,模型的所有层参数都会被学习改变

model = net()

# 不冻结参数时
optimizer = optim.SGD(model.parameters(), lr=1e-2)  # 将所有的参数传入
loss = nn.CrossEntropyLoss()

2)、冻结 fc1 层时

        此类情况只要设置某层的 requires_grad=False ,虽然传入模型所有的参数,但仍然只更新requires_grad=True 的层的参数。

optimizer = optim.SGD(model.parameters(), lr=1e-2)  # 将所有的参数传入

# 冻结 fc1 层参数
for name, param in model.named_parameters():
    if "fc1" in name:
        param.requires_grad = False

3)、只训练 fc1 层

        此种情况,只需要将 fc1 层的参数传入优化器即可。不需要将要冻结层参数的 requires_grad 属性设置为 False 。此时,只会更新优化器传入的参数,对于没有传入的参数可以求导,但是仍然不会更新参数。

# 情况一:优化器只传入 fc2 的参数
optimizer = optim.SGD(model.fc2.parameters(), lr=1e-2)

# 情况二:优化器只传入混合层中某一层的参数(依次写即可)
optimizer = optim.SGD(model.features.denseblock3.denselayer1.conv2_2.parameters(), lr=1e-2)

总结:

        通过分析发现,应先设置冻结不更新参数的层,再在优化器里传入需要学习的参数的层。由此,可以节省显存(不将不更新的参数传入 Optimizer)和提升速度(将不更新的参数的 requires_grad 设置为 False,节省了计算这部分参数梯度的时间)。

三、对不同的层采用不同的学习率

        此时,需要知道某一块层的名字:

for k, v in model.features.denseblock3.denselayer1.named_parameters():
    print(k)

# 输出:
# D:\anaconda3\python.exe G:/me-zt/02.py
# DenseLayer.0.weight
# DenseLayer.0.bias
# DenseLayer.2.weight
# DenseLayer.3.weight
# DenseLayer.3.bias
# conv2_1.0.weight
# conv2_1.1.weight
# conv2_2.0.weight
# conv2_2.1.weight
# conv3.0.weight

# Process finished with exit code 0

        假设要求 DenseLayer 层的学习率为0.001, 其余的学习率为0.01,那么在将参数传入优化器时:

# 读取 DenseLayer 层的参数并放入列表
ignored_params = list(map(id, model.features.denseblock3.denselayer1.DenseLayer.parameters()))

# 将该 DenseLayer 层参数忽略,提取其他层参数
base_params = filter(lambda p: id(p) not in ignored_params, model.parameters())

# 按不同的层设置优化器
optimizer = torch.optim.Adam([{'params':base_params,'lr':0.01},
                              {'params':model.features.denseblock3.denselayer1.DenseLayer.parameters()}],
                              lr=0.001, momentum=0.9)

        注意:此时除 DenseLayerr 层参数的 learning_rate=0.001 外,其他参数的 learning_rate=0.01。 如果 list 中没有设置 lr 则应用 Adam 的 lr 属性。Adam的属性除了 lr, 其他参数都是共用的(例如 momentum )。

参考:

1、https://blog.csdn.net/qq_34351621/article/details/79967463

2、https://blog.csdn.net/qq_36429555/article/details/118547133

3、https://blog.csdn.net/qq_34108714/article/details/90169562

4、https://blog.csdn.net/weixin_41712499/article/details/110198423

  • 48
    点赞
  • 209
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
PyTorch 中冻结某些参数训练可以通过以下步骤实现: 1. 加载 ResNet50 预训练模型: ```python import torchvision.models as models resnet50 = models.resnet50(pretrained=True) ``` 2. 冻结指定参数: ```python for name, param in resnet50.named_parameters(): if 'layer3' not in name and 'layer4' not in name: param.requires_grad = False ``` 上述代码中,我们遍历 ResNet50 模型的所有参数,如果参数名中不包含 "layer3" 和 "layer4",则将其 requires_grad 属性设置为 False,即冻结该参数。 3. 将模型放到 GPU 上: ```python device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') resnet50 = resnet50.to(device) ``` 4. 定义优化器和损失函数: ```python import torch.optim as optim criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(filter(lambda p: p.requires_grad, resnet50.parameters()), lr=0.001, momentum=0.9) ``` 上述代码中,我们只优化 requires_grad 属性为 True 的参数,即未冻结的参数。 5. 训练模型: ```python for epoch in range(num_epochs): for i, (inputs, labels) in enumerate(train_loader): inputs = inputs.to(device) labels = labels.to(device) optimizer.zero_grad() outputs = resnet50(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() ``` 上述代码中,我们使用 DataLoader 加载数据,并将输入和标签放到 GPU 上进行训练。由于部分参数被冻结,因此反向传播时只会更新未冻结的参数

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

清纯世纪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值