参考文献:
pytorch如何冻结某层参数的实现
pytorch 两种冻结层的方式
【pytorch】筛选冻结部分网络层参数同时设置有参数组的时候该怎么办?
这种方法的性质是:
被冻结的层可以前向传播,也可以反向传播,只是自己这一层的参数不更新,其他未冻结层的参数正常更新。
还需要注意的是优化器要加上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 y
class 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-2
for t in range(4):
# 第一步:数据的前向传播,计算预测值p_pred
if 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的参数不更新。
输出结果可以验证:
如果放在循环之前,则无法实现正确的冻结图层。
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 y
class 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_pred
if 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}')
该方法的性质:
这种方式只需要在网络定义中的forward方法中,将需要冻结的层放在 with torch.no_grad()下。
放入with torch.no_grad()中的网络层,可以前向传播,但反向传播被阻断,自己这层(如self.layer2)和前面的所有与之相关的层(如self.layer1)的参数都会被冻结,不会被更新。但如果前面的层除了和self.layer2相关外,还与其他层有联系,则与其他层联系的部分正常更新。
直接看例子:
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 y
class 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_pred
y_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正常更新。
结果可验证: