更改原始模型
举例,砍vgg16的全连接。需要注意的是avgpool和classifier是要根据pytorch中vgg16的层命名的,直接在jupytor里print一下model就看得到。如果要大改只能改一下pytorch的网络定义和forward函数这种。
model.avgpool = nn.Sequential(nn.Conv2d(512, 2048, kernel_size=7, bias=False),nn.BatchNorm2d(2048),nn.ReLU(inplace=True))model.classifier = nn.Sequential(nn.Linear(2048, 128),nn.ReLU(),nn.Dropout(p=0.5, inplace=False),nn.Linear(128, 1))
再举例,在我异想天开的颜值评分系统的,要为原始的VGG16加一个网络层,该层为了能够训练输入图像只做了将输入的Tensor转为Parameter的工作。
class tensor2para(nn.Module):def __init__(self, img):super(tensor2para, self).__init__()self.x_para = nn.parameter.Parameter(img)def forward(self):return self.x_paraclass Net(nn.Module):def __init__(self, img):super(Net, self).__init__()self.t2p = tensor2para(img)self.features = torchvision.models.vgg16().featuresself.avgpool = nn.AvgPool2d(kernel_size=7)self.classifier = nn.Linear(512, 1)def forward(self, res):res = self.t2p()res = self.features(res)res = self.avgpool(res)res = res.view(res.size(0), -1)res = self.classifier(res)return res
冻结部分网络层
冻结浅层让浅层不要参与训练。
百度上有pytorch的fine-tune教程。这里给出一个简单的例子,冻结VGG16的第一个阶段,可以看到我们需要冻结features中的0~6层。
model = torchvision.models.vgg16_bn()#################以下是错误样例##################for para in list(model.features.parameters())[0:6]:para.requires_grad = False##################################################一个层可能包含多个parameter,譬如BNfor i in range(6):for para in list(model.features)[i].parameters():para.requires_grad = False
parameter的requires_grad置为False后,将不再计算梯度并反传。
神奇的问题:如果需要冻结1、2、4层,但希望第三层的参数能够学习,该怎么办?
答:把3、4层的requires_grad置为True,在定义优化器optimizer时只将第三层的parameter作为参数传入。
为不同层指派不同学习率
迁移学习的时候可能会遇到的一个问题,不同的层需要不同的学习率等等。
一个相当简单的例子
#id函数用于取地址,map用于将id函数映射到所有conv5.parameters()的对象conv5_params = list(map(id, net.conv5.parameters()))#取出所有conv5层之外的参数base_params = filter(lambda p: id(p) not in conv5_params,net.parameters())#将conv5层的学习率设为10倍optimizer = torch.optim.SGD([{'params': base_params},{'params': net.conv5.parameters(), 'lr': lr * 10}],, lr=lr, momentum=0.9)
一个复杂一些的例子
碰到更复杂一些的模型,则会变得棘手一些,譬如MobileNet:

#取出第一个ConvBNReLU中卷积层的参数p0 = list(list(model.features)[0])[0].parameters()#取出整个第一个ConvBNReLU块中的参数p1 = list(model.features)[0].parameters()
其中p0的长度为1(用了BN后卷积层就没bias了,只剩weight);
p1的长度为3(虽然BN有weight、bias、running_mean、running_var、tracked_batch,按理p1的长度 应该为6,但是后面三个都是buffer类型而非parameter类型)
模型参数Parameter类与buffer类
模型中的参数包括2种,一种是nn.Parameter类,另一种是buffer,前者每次optimizer作用时根据梯度优化,而buffer则不进行优化——譬如BN里的running_mean、running_var、tracked_batch这种统计量!不是根据梯度优化出来的!
Optimizer传进去的第一个参数类型是Parameter的generator,譬如model.parameters()就是一个generator。
连接两个generator的方法:
Itertools.chain(generator1, generator2)
魔改MobileNet各层学习率实战
实在是太麻烦了我都不想说话了,直接看代码吧。将所有BN层单独拎出来,然后以stage为粒度指派学习率。
paraBN = []paraBase = list(range(5))listBN = []listBN.extend(list(list(model.features)[0])[1].parameters())for block in range(1, 18):list_conv = list(list(model.features)[block].conv)for layer in range(0, len(list_conv) - 2):listBN.extend(list(list_conv[layer])[1].parameters())listBN.extend(list_conv[-1].parameters())listBN.extend(list(list(model.features)[18])[1].parameters())adr_paraBN = list(map(id, listBN))paraBN = filter(lambda p: id(p) in adr_paraBN, model.parameters())layer_begin = [1, 3, 5, 8, 15, 18]for stage in range(5):listBase = []for block in range(layer_begin[stage], layer_begin[stage+1]):listBase.extend(list(list(model.features)[block].parameters()))paraBase[stage] = filter(lambda p: id(p) not in adr_paraBN, listBase)paraClr = itertools.chain(list(list(model.features)[18])[0].parameters(), model.classifier.parameters())optimizer = optim.SGD([{'params': paraBN, 'lr': learning_rate * 1.0, 'weight_decay': 0.0},{'params': paraBase[0], 'lr': learning_rate * 0.09},{'params': paraBase[1], 'lr': learning_rate * 0.15},{'params': paraBase[2], 'lr': learning_rate * 0.25},{'params': paraBase[3], 'lr': learning_rate * 0.36},{'params': paraBase[4], 'lr': learning_rate * 0.60},{'params': paraClr, 'lr': learning_rate * 1.00},], lr=learning_rate, weight_decay=0.0001, momentum=0.9)sche = optim.lr_scheduler.MultiStepLR(optimizer, [140, 185], gamma=0.1, last_epoch=-1)model.train()for epoch in range(0, 200):train(epoch)sche.step()
然后是喜闻乐见的结果,就是不管我怎么调怎么搞都是原来的最好。
