pytorch可以自动求导梯度,不过要对需要求梯度的tensor设置为“requires grad = TRUE"。同时,pytorch可以对神经网络进行灵活训练,主要同通过Optimazer的设计来实现的。

以下针对不同的应用场景需要对optimizer的使用进行总结。

1. 多个神经网络联合训练

应用场景

(1)Autoencoder网络分为encoder与decoder两部分组成,中间加入了AWGN噪声。首先两部分进行联合训练,得到压缩编码,然后再单独将接收端进行训练,观看性能是否有所提升。

(2)GAN网络的discriminator需要单独进行training,generator需要经过discriminator后,再进行参数更新,同时,discriminator锁死,不进行参数更新,只进行梯度反传。

具体实现

(1)Autoencoder只进行接收端的训练时,网络结构如下,定义一个encoder的网络参数,再定义一个decoder的网络参数

super(AutoEncoder, self).__init__() 
        self.encoder = nn.Sequential(
            nn.Linear(InputNum, 128),
            nn.LeakyReLU(),
            nn.Linear(128,OutputBit),
        )
 
        self.decoder = nn.Sequential(
            nn.Linear(OutputBit,128),
            nn.LeakyReLU(),
            nn.Linear(128, InputNum),
          
        )

Optimizer定义为:

net = AutoEncoder()
net.apply(weight_init)
optimizer = optim.Adam(net.parameters(), lr = LR)
decoder_optimizer = optim.Adam(net.decoder.parameters(), lr=LR)  
criterion = nn.CrossEntropyLoss()

第一个optimaizer为整个net的parameters,第二个decoder_optimizer的参数只传入了net.decoder.parameters(),所以

decoder_optimizer.step()

只更新decoder的参数。

(2)GAN在训练网络时如下图:

神经网络的flops 怎么算 神经网络optimizer_pytorch

pytorch网络结构实现为:

class discriminator(nn.Module):
    def __init__(self):
        super(discriminator,self).__init__()
        self.dis=nn.Sequential(
            nn.Linear(784,256),#输入特征数为784,输出为256
            nn.LeakyReLU(0.2),#进行非线性映射
            nn.Linear(256,256),#进行一个线性映射
            nn.LeakyReLU(0.2),
            nn.Linear(256,1),
            nn.Sigmoid()#也是一个激活函数,二分类问题中,
            # sigmoid可以班实数映射到【0,1】,作为概率值,
            # 多分类用softmax函数
        )
    def forward(self, x):
        x=self.dis(x)
        return x
class generator(nn.Module):
    def __init__(self):
        super(generator,self).__init__()
        self.gen=nn.Sequential(
            nn.Linear(100,256),#用线性变换将输入映射到256维
            nn.ReLU(True),#relu激活
            nn.Linear(256,256),#线性变换
            nn.ReLU(True),#relu激活
            nn.Linear(256,784),#线性变换
            nn.Tanh()#Tanh激活使得生成数据分布在【-1,1】之间
        )
 
    def forward(self, x):
        x=self.gen(x)
        return x

discriminator的优化器设置如下: 

#创建对象
D=discriminator()
G=generator()

criterion = nn.BCELoss() #是单目标二分类交叉熵函数
d_optimizer=torch.optim.Adam(D.parameters(),lr=0.0003)
g_optimizer=torch.optim.Adam(G.parameters(),lr=0.0003)

 # 损失函数和优化
d_loss = d_loss_real + d_loss_fake #损失包括判真损失和判假损失
d_optimizer.zero_grad()  # 在反向传播之前,先将梯度归0
d_loss.backward()  # 将误差反向传播
d_optimizer.step()  # 更新参数

generator优化器为:

# 计算假的图片的损失
 
z = Variable(torch.randn(num_img, z_dimension)).cuda()  # 得到随机噪声
fake_img = G(z) #随机噪声输入到生成器中,得到一副假的图片
output = D(fake_img)  # 经过判别器得到的结果
g_loss = criterion(output, real_label)  # 得到的假的图片与真实的图片的label的loss

g_optimizer.zero_grad()  # 梯度归0
g_loss.backward()  # 进行反向传播
g_optimizer.step()  # .step()一般用在反向传播后面,用于更新生成网络的参数

由于两个网络是分开设置的,所以两个网络的参数也是分开更新的。在更新G的参数时,使用D网络进行传输,所以D网络的梯度进行计算,但是在step时,D的参数并不更新。

2.对网络中固定部分参数进行训练

方法一:首先将网络层中不进行更新的参数的requires_grad属性设置为False,然后再optimizer中加入过滤器即可。

具体参考:


optimizer.SGD(filter(lambda p: p.requires_grad, model.parameters()), lr=1e-3)

对于网络层中的设置方法比较多,如:features是构建NN中的一个模组。 

for i, para in enumerate(self._net.module.features.parameters()):
            if i < 16:
                para.requires_grad = False
            else:
                para.requires_grad = True

 在加载预训练模型之后,在原来的基础上添加一部分的网络,我们可以固定原来的参数,然后只训练我们添加的这部分网络,完了之后再全部训练.

class RESNET_attention(nn.Module):
    def __init__(self, model, pretrained):
        super(RESNET_attetnion, self).__init__()
        self.resnet = model(pretrained)
        for p in self.parameters():
            p.requires_grad = False
        self.f = nn.Conv2d(2048, 512, 1)
        self.g = nn.Conv2d(2048, 512, 1)
        self.h = nn.Conv2d(2048, 2048, 1)
        self.softmax = nn.Softmax(-1)
        self.gamma = nn.Parameter(torch.FloatTensor([0.0]))
        self.avgpool = nn.AvgPool2d(7, stride=1)
        self.resnet.fc = nn.Linear(2048, 10)
optimizer = optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr=0.0001, betas=(0.9, 0.999), eps=1e-08, weight_decay=1e-5)

如果是Variable,则可以初始化时指定

j = Variable(torch.randn(5,5), requires_grad=True)

但是如果是

m = nn.Linear(10,10)

是没有requires_grad传入的 
m.requires_grad也没有 
需要用for循环

for i in m.parameters():
    i.requires_grad=False

另外一个小技巧就是在nn.Module里,可以在中间插入这个

for p in self.parameters():
    p.requires_grad=False

这样前面的参数就是False,而后面的不变

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 6, 5)
        self.conv2 = nn.Conv2d(6, 16, 5)

        for p in self.parameters():
            p.requires_grad=False

        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

 在fine tuning一个预训练的网络时,要增加一个参数组,还可以用下面方法:

add_param_group(param_group)

加载optimizer的状态

load_state_dict(state_dict)

获取一个optimizer的状态(一个dict)。

state_dict()
model=resnet()#自己构建的模型,以resnet为例
model_dict = model.state_dict()
pretrained_dict = torch.load('xxx.pkl')
pretrained_dict = {k: v for k, v in pretrained_dict.items() if k in model_dict}
model_dict.update(pretrained_dict)
model.load_state_dict(model_dict)

for k,v in model.named_parameters():

    if k!='xxx.weight' and k!='xxx.bias' :

        v.requires_grad=False#固定参数

optimizer2=torch.optim.Adam(params=[model.xxx.weight,model.xxx.bias],lr=learning_rate,betas=(0.9,0.999),weight_decay=1e-5)

3.Optimizer不同的参数设置不同的学习速率

参考:

为不同的子网络参数不同的学习率,finetune常用,使分类器学习率参数更高,学习速度更快(理论上).

网络结构:

import torch as t
 
 
class LeNet(t.nn.Module):
    def __init__(self):
        super(LeNet, self).__init__()
        self.features = t.nn.Sequential(
            t.nn.Conv2d(3, 6, 5),
            t.nn.ReLU(),
            t.nn.MaxPool2d(2, 2),
            t.nn.Conv2d(6, 16, 5),
            t.nn.ReLU(),
            t.nn.MaxPool2d(2, 2)
        )
        # 由于调整shape并不是一个class层,
        # 所以在涉及这种操作(非nn.Module操作)需要拆分为多个模型
        self.classifiter = t.nn.Sequential(
            t.nn.Linear(16*5*5, 120),
            t.nn.ReLU(),
            t.nn.Linear(120, 84),
            t.nn.ReLU(),
            t.nn.Linear(84, 10)
        )
 
    def forward(self, x):
        x = self.features(x)
        x = x.view(-1, 16*5*5)
        x = self.classifiter(x)
        return x
 
net = LeNet()

(1)经由构建网络时划分好的模组进行学习率设定:(粗粒度设定)

optimizer = optim.SGD([{'params': net.features.parameters()}, # 默认lr是1e-5
                       {'params': net.classifiter.parameters(), 'lr': 1e-2}], lr=1e-5)

(2)以网络层对象为单位进行分组,并设定学习率:(细粒度)着重学习nn.ModuleList是如何工作的。

# # 以层为单位,为不同层指定不同的学习率
# ## 提取指定层对象
special_layers = t.nn.ModuleList([net.classifiter[0], net.classifiter[3]]) #第一层与第四层
# ## 获取指定层参数id
special_layers_params = list(map(id, special_layers.parameters()))
print(special_layers_params)
# ## 获取非指定层的参数id
base_params = filter(lambda p: id(p) not in special_layers_params, net.parameters())
optimizer = t.optim.SGD([{'params': base_params},
                         {'params': special_layers.parameters(), 'lr': 0.01}], lr=0.001)

(3)在训练中动态的调整学习率

'''调整学习率'''
# 新建optimizer或者修改optimizer.params_groups对应的学习率
# # 新建optimizer更简单也更推荐,optimizer十分轻量级,所以开销很小
# # 但是新的优化器会初始化动量等状态信息,这对于使用动量的优化器(momentum参数的sgd)可能会造成收敛中的震荡
# ## optimizer.param_groups:长度2的list,optimizer.param_groups[0]:长度6的字典
print(optimizer.param_groups[0]['lr'])
old_lr = 0.1
optimizer = optim.SGD([{'params': net.features.parameters()},
                       {'params': net.classifiter.parameters(), 'lr': old_lr*0.1}], lr=1e-5)

可以看到optimizer.param_groups结构,[{'params','lr', 'momentum', 'dampening', 'weight_decay', 'nesterov'},{……}],集合了优化器的各项参数。

辅助理解:

法1(比较推荐):新建一个optimizer。对于使用动量的优化器(如Adam),会丢失动量等状态信息,可能会造成损失函数的收敛出现震荡等情况


old_lr = 0.1
optimizer1 =optim.SGD([
                {'params': net.features.parameters()},
                {'params': net.classifier.parameters(), 'lr': old_lr*0.1}
            ], lr=1e-5)
optimizer1


法2:一种是修改optimizer.param_groups中对应的学习率


# 方法2: 调整学习率, 手动decay, 保存动量
for param_group in optimizer.param_groups:
    param_group['lr'] *= 0.1 # 学习率为之前的0.1倍
optimizer