重要|Pytorch 冻结网络层

Pytorch 冻结网络层 参考文献:
pytorch如何冻结某层参数的实现
pytorch 两种冻结层的方式
【pytorch】筛选冻结部分网络层参数同时设置有参数组的时候该怎么办?
方法一:设置requires_grad为False 这种方法的性质是:
被冻结的层可以前向传播,也可以反向传播,只是自己这一层的参数不更新,其他未冻结层的参数正常更新。
还需要注意的是优化器要加上filter:

optimizer = torch.optim.Adam(filter(lambda p: p.requires_grad, perception.parameters()), lr=learning_rate)

还要注意的是: 构造optimizer对象的位置要放在冻结图层操作之后。
此外,设置requires_grad为False有两种方法:
①: x.requires_grad_(False)
②:x.requires_grad = False
两种效果相同。
直接看例子:
import torch from torch import nn# 首先建立一个全连接的子module,继承nn.Module class Linear(nn.Module): def __init__(self, in_dim, out_dim, requires_grad): super(Linear, self).__init__()# 调用nn.Module构造函数 # 使用nn.Parameter来构造需要学习的参数 self.w = nn.Parameter(torch.randn(in_dim, out_dim), requires_grad=requires_grad) self.b = nn.Parameter(torch.randn(out_dim), requires_grad=requires_grad)# 在forward中实现向前传播过程 def forward(self, x): x = x.matmul(self.w)# 使用Tensor.matmul实现矩阵相乘 y = x + self.b.expand_as(x)# 使用Tensor.expand_as()来保证矩阵形状一致 return yclass Perception(nn.Module): def __init__(self, in_dim, hid_dim1, hid_dim2, out_dim): super(Perception, self).__init__() self.layer1 = Linear(in_dim, hid_dim1, True) self.layer2 = Linear(hid_dim1, hid_dim2, True) self.layer3 = Linear(hid_dim2, out_dim, True)def forward(self, x): x = self.layer1(x) y = torch.sigmoid(x)# 使用torch中的sigmoid作为激活函数 y = self.layer2(y) y = torch.sigmoid(y) y = self.layer3(y) y = torch.sigmoid(y) return y# 实例化一个网络,并赋值全连接中的维数,最终输出二维代表了二分类 perception = Perception(2, 3, 3, 2)# N是训练的batch size; D_in 是input输入数据的维度; # H是隐藏层的节点数; D_out 输出的维度,即输出节点数. N, D_in, D_out = 64, 2, 2# 创建输入、输出数据 x = torch.randn(N, D_in) y = torch.randn(N, D_out)# 定义损失函数 loss_fn = torch.nn.MSELoss(reduction='mean')learning_rate = 1e-2for t in range(4): # 第一步:数据的前向传播,计算预测值p_predif t <= 1: for k, v in perception.named_parameters(): if any(x in k.split('.') for x in ['layer1']): print('freezing %s' % k) v.requires_grad_(False) if any(x in k.split('.') for x in ['layer2']): print('unfreezing %s' % k) v.requires_grad_(True) if any(x in k.split('.') for x in ['layer3']): print(f'unfreezing %s' % k) v.requires_grad_(True)else: for k, v in perception.named_parameters(): if any(x in k.split('.') for x in ['layer1']): print('unfreezing %s' % k) v.requires_grad_(True)if any(x in k.split('.') for x in ['layer2']): print('freezing %s' % k) v.requires_grad_(False)if any(x in k.split('.') for x in ['layer3']): print(f'freezing %s' % k) v.requires_grad_(False)print()y_pred = perception(x)# 第二步:计算计算预测值p_pred与真实值的误差 loss = loss_fn(y_pred, y) # print(t, loss.item()) # 构造一个optimizer对象 optimizer = torch.optim.Adam(filter(lambda p: p.requires_grad, perception.parameters()), lr=learning_rate)# 在反向传播之前,将模型的梯度归零,这 optimizer.zero_grad()# 第三步:反向传播误差 loss.backward()print(f'第{t}次迭代:~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~') print(f'迭代前:') for k, v in perception.named_parameters(): print(f'{k}:\n{v}\n') print(f'{k}.requires_grad:\n{v.requires_grad}\n')# 直接通过梯度一步到位,更新完整个网络的训练参数,一句话优化所有的参数,是不是很牛逼 optimizer.step()print(f'第{t}次迭代:~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~') print(f'迭代后:') for k, v in perception.named_parameters(): print(f'{k}:\n{v}\n') print(f'{k}.requires_grad:\n{v.requires_grad}\n')

在第0和1次迭代,self.layer1被冻结,self.layer2和self.layer3没被冻结,
在第2和3次迭代,self.layer1没被冻结,self.layer2和self.layer3被冻结,
因此,在第0和1次迭代,self.layer1的参数不更新(不参与训练),self.layer2和self.layer3的参数更新;在第2和3次迭代,self.layer1的参数更新(参与训练),self.layer2和self.layer3的参数不更新。
输出结果可以验证:
重要|Pytorch 冻结网络层
文章图片

重要|Pytorch 冻结网络层
文章图片

如果放在循环之前,则无法实现正确的冻结图层。
import torch from torch import nn# 首先建立一个全连接的子module,继承nn.Module class Linear(nn.Module): def __init__(self, in_dim, out_dim, requires_grad): super(Linear, self).__init__()# 调用nn.Module构造函数 # 使用nn.Parameter来构造需要学习的参数 self.w = nn.Parameter(torch.randn(in_dim, out_dim), requires_grad=requires_grad) self.b = nn.Parameter(torch.randn(out_dim), requires_grad=requires_grad)# 在forward中实现向前传播过程 def forward(self, x): x = x.matmul(self.w)# 使用Tensor.matmul实现矩阵相乘 y = x + self.b.expand_as(x)# 使用Tensor.expand_as()来保证矩阵形状一致 return yclass Perception(nn.Module): def __init__(self, in_dim, hid_dim1, hid_dim2, out_dim): super(Perception, self).__init__() self.layer1 = Linear(in_dim, hid_dim1, True) self.layer2 = Linear(hid_dim1, hid_dim2, True) self.layer3 = Linear(hid_dim2, out_dim, True)def forward(self, x): x = self.layer1(x) x = self.layer2(x) x = self.layer3(x) return x# 实例化一个网络,并赋值全连接中的维数,最终输出二维代表了二分类 perception = Perception(2, 3, 3, 2)# N是训练的batch size; D_in 是input输入数据的维度; # H是隐藏层的节点数; D_out 输出的维度,即输出节点数. N, D_in, D_out = 64, 2, 2# 创建输入、输出数据 x = torch.randn(N, D_in) y = torch.randn(N, D_out)# 定义损失函数 loss_fn = torch.nn.MSELoss(reduction='mean')learning_rate = 1e-2# 构造一个optimizer对象 optimizer = torch.optim.Adam(filter(lambda p: p.requires_grad, perception.parameters()), lr=learning_rate)for t in range(20): # 第一步:数据的前向传播,计算预测值p_predif t <= 10: for k, v in perception.named_parameters(): if any(x in k.split('.') for x in ['layer1']): print('freezing %s' % k) v.requires_grad_(False) if any(x in k.split('.') for x in ['layer2']): print('unfreezing %s' % k) v.requires_grad_(True) if any(x in k.split('.') for x in ['layer3']): print(f'unfreezing %s' % k) v.requires_grad_(True)else: for k, v in perception.named_parameters(): if any(x in k.split('.') for x in ['layer1']): print('unfreezing %s' % k) v.requires_grad_(True)if any(x in k.split('.') for x in ['layer2']): print('freezing %s' % k) v.requires_grad_(False)if any(x in k.split('.') for x in ['layer3']): print(f'freezing %s' % k) v.requires_grad_(False)print()y_pred = perception(x)# 第二步:计算计算预测值p_pred与真实值的误差 loss = loss_fn(y_pred, y) # print(t, loss.item())# 在反向传播之前,将模型的梯度归零,这 optimizer.zero_grad()# 第三步:反向传播误差 loss.backward()print(f'第{t}次迭代:~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~') print(f'迭代前:') for k, v in perception.named_parameters(): print(f'{k}:\n{v}') print(f'{k}.requires_grad:{v.requires_grad}')# 直接通过梯度一步到位,更新完整个网络的训练参数,一句话优化所有的参数,是不是很牛逼 optimizer.step()print(f'第{t}次迭代:~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~') print(f'迭代后:') for k, v in perception.named_parameters(): print(f'{k}:\n{v}') print(f'{k}.requires_grad:{v.requires_grad}')

方法二:使用 with torch.no_grad() 该方法的性质:
这种方式只需要在网络定义中的forward方法中,将需要冻结的层放在 with torch.no_grad()下。
放入with torch.no_grad()中的网络层,可以前向传播,但反向传播被阻断,自己这层(如self.layer2)和前面的所有与之相关的层(如self.layer1)的参数都会被冻结,不会被更新。但如果前面的层除了和self.layer2相关外,还与其他层有联系,则与其他层联系的部分正常更新。
【重要|Pytorch 冻结网络层】直接看例子:
import torch from torch import nn# 首先建立一个全连接的子module,继承nn.Module class Linear(nn.Module): def __init__(self, in_dim, out_dim, requires_grad): super(Linear, self).__init__()# 调用nn.Module构造函数 # 使用nn.Parameter来构造需要学习的参数 self.w = nn.Parameter(torch.randn(in_dim, out_dim), requires_grad=requires_grad) self.b = nn.Parameter(torch.randn(out_dim), requires_grad=requires_grad)# 在forward中实现向前传播过程 def forward(self, x): x = x.matmul(self.w)# 使用Tensor.matmul实现矩阵相乘 y = x + self.b.expand_as(x)# 使用Tensor.expand_as()来保证矩阵形状一致 return yclass Perception(nn.Module): def __init__(self, in_dim, hid_dim1, hid_dim2, out_dim): super(Perception, self).__init__() self.layer1 = Linear(in_dim, hid_dim1, True) self.layer2 = Linear(hid_dim1, hid_dim2, True) self.layer3 = Linear(hid_dim2, out_dim, True)def forward(self, x): x = self.layer1(x) x = torch.sigmoid(x)# 使用torch中的sigmoid作为激活函数 with torch.no_grad(): x = self.layer2(x) x = torch.sigmoid(x) x = self.layer3(x) x = torch.sigmoid(x) return x# 实例化一个网络,并赋值全连接中的维数,最终输出二维代表了二分类 perception = Perception(2, 3, 3, 2)# N是训练的batch size; D_in 是input输入数据的维度; # H是隐藏层的节点数; D_out 输出的维度,即输出节点数. N, D_in, D_out = 64, 2, 2# 创建输入、输出数据 x = torch.randn(N, D_in) y = torch.randn(N, D_out)# 定义损失函数 loss_fn = torch.nn.MSELoss(reduction='mean')learning_rate = 1e-2 # 构造一个optimizer对象for t in range(5): # 第一步:数据的前向传播,计算预测值p_predy_pred = perception(x)# 第二步:计算计算预测值p_pred与真实值的误差 loss = loss_fn(y_pred, y) # print(t, loss.item())optimizer = torch.optim.Adam(perception.parameters(), lr=learning_rate)# 在反向传播之前,将模型的梯度归零,这 optimizer.zero_grad()# 第三步:反向传播误差 loss.backward()print(f'第{t}次迭代:~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~') print(f'迭代前:') for k, v in perception.named_parameters(): print(f'{k}:\n{v}\n') print(f'{k}.requires_grad:\n{v.requires_grad}\n')# 直接通过梯度一步到位,更新完整个网络的训练参数,一句话优化所有的参数,是不是很牛逼 optimizer.step()print(f'第{t}次迭代:~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~') print(f'迭代后:') for k, v in perception.named_parameters(): print(f'{k}:\n{v}\n') print(f'{k}.requires_grad:\n{v.requires_grad}\n')

Perception总共有3个层,其中self.layer2放在 with torch.no_grad()中,并且self.layer1和self.layer2存在直接关系,因此self.layer2和self.layer1的层都将被冻结,因为反向传播在self.layer2层就被阻断了,不再向前传播。因此只有self.layer3正常更新。
结果可验证:
重要|Pytorch 冻结网络层
文章图片

重要|Pytorch 冻结网络层
文章图片

    推荐阅读