【pytorch】筛选冻结部分网络层参数同时设置有参数组的时候该怎么办?

在进行神经网络训练的时候,常常需要冻结部分网络层的参数,不想让他们回传梯度。这个其实很简单,其他博客里教程很多~
那如果,我想对不同的参数设置不同的学习率呢?这个其他博客也有,设置参数组就好啦,优化器就可以分别设置学习率了。
那么,如果我同时想冻结参数和设置不同的学习率呢?是不是把两个人给合起来就好了?好的那你试试吧看看行不行。

我最近工作中需要对two-stream的其中的一只进行冻结,并且设置不同的学习率。下面记录一下我踩的坑。
首先,我们需要筛选所需要的层。我想要把名字里含有特定符号的层给筛选出来。在这里我要强烈推荐这个利用正则表达式来进行字符串筛选的方式!

import re
str = 'assdffggggg'
word = 'a'
 a = [m.start() for m in re.finditer(word, str)]

这里的a是一个列表,它里面包含的是word在字符串str中所在的位置,这里自然就是0了。
在进行网络层参数冻结的时候,网上会有两种for循环:

for name, p in net.named_parameters():

for p in net.parameters():

这两种都行,但是对于需要对特定名称的网络层进行冻结的时候就需要选第一个啦,因为我们需要用到参数的"name"属性。
下面就是简单的筛选和冻结,和其他教程里面的一样:

word1 = 'seg'
for name, p in decode_net.named_parameters():
    str = name
    a = [m.start() for m in re.finditer(word1, str)]
    if a: #列表a不为空的话就设置回传的标识为False
        p.requires_grad = False
    else:
        p.requires_grad = True

    #if p.requires_grad:#这个判断可以打印出需要回传梯度的层的名称
        #print(name)

到这里我们就完成了网络参数的冻结。我真正想要分享的在下面这个部分!!看了四天的大坑!
冻结部分层的参数之后,我们在使用优化器的时候就需要先把不需要回传梯度的参数给过滤掉,如果不过滤就会报错,优化器就会抱怨你怎么把不需要优化的参数给放进去了balabala的。所以我们加一个:

optimizer = optim.SGD(
            filter(lambda p: p.requires_grad, net.parameters()),  # 记住一定要加上filter(),不然会报错
            lr=0.01, weight_decay=1e-5, momentum=0.9)

到这里也没有任何的问题。但是!我做分割的encode部分是pre-trained的resnet,这部分我的学习率不想和我decode的部分一样啊!不然我用pre-trained的有啥用??so,我划分了一个参数组:

base_params_id = list(map(id, net.conv1.parameters())) + list(map(id,net.bn1.parameters()))+\
	list(map(id,net.layer1.parameters())) + list(map(id,net.layer2.parameters())) \
		+ list(map(id,net.layer3.parameters())) + list(map(id,net.layer4.parameters()))

new_params = filter(lambda p: id(p) not in base_params_id , net.parameters())
base_params = filter(lambda p: id(p) in base_params_id, net.parameters())

好了,那么这个时候,如果我先不考虑过滤的话,优化器的设置应该是这样的:

optimizerG = optim.SGD([{'params': base_params, 'lr': 1e-4}, 
                        {'params': new_params}], lr = opt.lr, momentum = 0.9, weight_decay=0.0005)

那么,按照百度出来的教程,我下一步要加上过滤器的话是不是应该:

optimizerG = optim.SGD( filter(lambda p: p.requires_grad, net.parameters()),
						[{'params': base_params, 'lr': 1e-4}, 
                        {'params': new_params}], lr = opt.lr, momentum = 0.9, weight_decay=0.0005)

好的看起来没有任何的问题,但是运行的时候就开始报错:
在这里插入图片描述
就是这里!!一个刚开始用pytorch的我!什么都不懂!然后我看了四天!!最后查阅了官方文档才知道为什么报错。以后看到这种提示init函数错误的都要记得去官方doc上看说明。
【pytorch】筛选冻结部分网络层参数同时设置有参数组的时候该怎么办?_第1张图片
这里其实写的很清楚了,SGD优化器每个位置都是什么参数。到这里应该已经能看出来哪里有问题了吧?

optimizerG = optim.SGD( filter(lambda p: p.requires_grad, net.parameters()),
						[{'params': base_params, 'lr': 1e-4}, 
                        {'params': new_params}], lr = opt.lr, momentum = 0.9, weight_decay=0.0005)

看我的SGD函数每个参数的位置,第一个放的是过滤器,第二个是参数组,然后是lr,对比官方的定义:第一个参数,第二个是lr等等。
所以错误就在这里!我第一个位置放了过滤器!第二个位置是参数组!所以他把过滤器当作参数,参数组当作学习率,然后就报错说lr接受到很多个值……
仔细去看其他博客的教程,基本是只有分参数组的优化器设置和冻结层了之后优化器的设置。没有又分参数组又冻结部分层参数的设置。所以设置过滤器把不需要优化的参数给踢掉这个步骤还是要的,但是在现在这种情况下不应该放在SGD里!

new_params = filter(lambda p: id(p) not in base_params_id and p.requires_grad,\
    netG.parameters())
base_params = filter(lambda p: id(p) in base_params_id,
                     netG.parameters())     

应该在划分参数组的时候就添加过滤器,将不需要回传梯度的参数过滤掉(这里就是直接筛选p.requires_grad即可)。如此便可以顺利冻结参数并且设置参数组啦!
今天特意更新这个,纪念我四天的血泪。

你可能感兴趣的:(pytorch)