Pytorch分步训练(只训练部分参数)

我现在的问题是,我的模型由两部分组成,bert+gat,bert只需要3~5轮就能收敛,而gat需要几十次,

我期望的目标是训练5轮过后,就只训练gat,bert不被更新

总体上有两种思路,一种是将不想被训练的参数修改为requires_grad=False,另一种是只将要训练的参数放到优化器中

第一种:设置requires_grad=Fasle

点击查看代码
import torch
import torch.nn as nn
from torch.nn.modules import loss
from torch_geometric.nn import models
import ipdb

data = torch.randn(4,10)

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(1,10)
        self.fc2 = nn.Linear(10,1)
        self.relu = nn.ReLU()
        self.sigmoid = nn.Sigmoid()
    def forward(self, x):
        # ipdb.set_trace()
        x = self.relu(self.fc1(x))
        x = self.sigmoid(self.fc2(x))
        return x

def print_gram(model):
    for name, param in model.named_parameters():
        if 'fc1' in name:
            print(name, param.data, param.grad.norm(), param.requires_grad)


model = Net()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001,betas=(0.5, 0.999))
criterion = nn.MSELoss()

data = torch.tensor([[1.0],[3.0],[5.0],[7.0]])
label = torch.tensor([[1.0],[9.0],[25.0],[49.0]])
for i in range(20):
    a = model(data)
    loss = criterion(a,label)

    print(i, loss)

    if i < 10:
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    else:
        optimizer.zero_grad()
        model.fc1.requires_grad_(False)
        loss.backward()
        optimizer.step()    

    print_gram(model)

你会发现,fc1的梯度的确为0,且requires_grad==False,但是weight变了!!!

可见 关于pytorch中使用detach并不能阻止参数更新这档子事儿

(1)backward之前,grad=None

(2)backward之后,grad变成具体值

(3)执行step,weight得到更新

(4)执行zero_grad,grad=0

除了逐一设置,也能直接将一个模块设为False,nn.Module.requires_grad_()

因此,梯度为0,因为有历史信息,权重也能更新!!

感谢这个问题,让我明白了pytorch为什么不在每个step自动清零grad而是非要让人主动去做zero_grad。

第二种:只优化要更新的参数

点击查看代码
import torch
import torch.nn as nn
from torch.nn.modules import loss
from torch_geometric.nn import models
import ipdb

data = torch.randn(4,10)

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(1,10)
        self.fc2 = nn.Linear(10,1)
        self.relu = nn.ReLU()
        self.sigmoid = nn.Sigmoid()
    def forward(self, x):
        # ipdb.set_trace()
        x = self.relu(self.fc1(x))
        x = self.sigmoid(self.fc2(x))
        return x

model = Net()

def get_parameters():
    for name, param in model.named_parameters():
        params = []
        if 'fc1' not in name:
           params.append(param)
    
    print(len(params))
    return params

optimizer = torch.optim.Adam(model.parameters(), lr=0.001,betas=(0.5, 0.999))
optimizer2 = torch.optim.Adam(get_parameters(), lr=0.001,betas=(0.5, 0.999))

def print_gram(model):
    for name, param in model.named_parameters():
        if 'fc1' in name:
            print(name, param.data, param.grad.norm(), param.requires_grad)


criterion = nn.MSELoss()
data = torch.tensor([[1.0],[3.0],[5.0],[7.0]])
label = torch.tensor([[1.0],[9.0],[25.0],[49.0]])

for i in range(20):
    a = model(data)
    loss = criterion(a,label)

    print(i, loss)

    if i < 10:
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    else:
        optimizer2.zero_grad()
        loss.backward()
        optimizer2.step()    

    print_gram(model)

虽然fc1会有梯度,但权重不会被更新,感觉还不错

总结:

使用requires_grad可以对参数或模块进行设置;使用detach用法简单,例如冻结bert,x = pooled_output.detach()  这句简单,只要把bert的输出加个detach,而且这两种方法只能用于冻结上游参数,不能是中间或后面,而且只有之前没有进行过BP才不会有历史信息,才会完全不影响梯度

使用优化器方法,只设置优化器还是会进行整个BP,不会更新参数值,但并不会节省算力。参考bert模型的微调,如何固定住BERT预训练模型参数,只训练下游任务的模型参数? - ZJU某小白的回答 - 知乎 https://www.zhihu.com/question/317708730/answer/634068499

 

参考链接:

https://www.cxyzjd.com/article/Answer3664/108493753

https://blog.csdn.net/jinxin521125/article/details/83621268

https://pytorch.org/docs/stable/notes/autograd.html

posted @ 2021-11-15 20:56  Rogn  阅读(2778)  评论(0编辑  收藏  举报