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 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的参数不更新。
输出结果可以验证:
Pytorch 冻结网络层_第1张图片
Pytorch 冻结网络层_第2张图片

如果放在循环之前,则无法实现正确的冻结图层。

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}')

方法二:使用 with torch.no_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正常更新。
结果可验证:
Pytorch 冻结网络层_第3张图片
Pytorch 冻结网络层_第4张图片

你可能感兴趣的:(重要,pytorch,深度学习)