更改原始模型

举例,砍vgg16的全连接。需要注意的是avgpoolclassifier是要根据pytorch中vgg16的层命名的,直接在jupytor里print一下model就看得到。如果要大改只能改一下pytorch的网络定义和forward函数这种。

  1. model.avgpool = nn.Sequential(
  2. nn.Conv2d(512, 2048, kernel_size=7, bias=False),
  3. nn.BatchNorm2d(2048),
  4. nn.ReLU(inplace=True)
  5. )
  6. model.classifier = nn.Sequential(
  7. nn.Linear(2048, 128),
  8. nn.ReLU(),
  9. nn.Dropout(p=0.5, inplace=False),
  10. nn.Linear(128, 1)
  11. )

再举例,在我异想天开的颜值评分系统的,要为原始的VGG16加一个网络层,该层为了能够训练输入图像只做了将输入的Tensor转为Parameter的工作。

  1. class tensor2para(nn.Module):
  2. def __init__(self, img):
  3. super(tensor2para, self).__init__()
  4. self.x_para = nn.parameter.Parameter(img)
  5. def forward(self):
  6. return self.x_para
  7. class Net(nn.Module):
  8. def __init__(self, img):
  9. super(Net, self).__init__()
  10. self.t2p = tensor2para(img)
  11. self.features = torchvision.models.vgg16().features
  12. self.avgpool = nn.AvgPool2d(kernel_size=7)
  13. self.classifier = nn.Linear(512, 1)
  14. def forward(self, res):
  15. res = self.t2p()
  16. res = self.features(res)
  17. res = self.avgpool(res)
  18. res = res.view(res.size(0), -1)
  19. res = self.classifier(res)
  20. return res

冻结部分网络层

冻结浅层让浅层不要参与训练。
百度上有pytorch的fine-tune教程。这里给出一个简单的例子,冻结VGG16的第一个阶段,可以看到我们需要冻结features中的0~6层。
image.png

  1. model = torchvision.models.vgg16_bn()
  2. #################以下是错误样例##################
  3. for para in list(model.features.parameters())[0:6]:
  4. para.requires_grad = False
  5. #################################################
  6. #一个层可能包含多个parameter,譬如BN
  7. for i in range(6):
  8. for para in list(model.features)[i].parameters():
  9. para.requires_grad = False

parameterrequires_grad置为False后,将不再计算梯度并反传。
神奇的问题:如果需要冻结1、2、4层,但希望第三层的参数能够学习,该怎么办?
答:把3、4层的requires_grad置为True,在定义优化器optimizer时只将第三层的parameter作为参数传入。

为不同层指派不同学习率

迁移学习的时候可能会遇到的一个问题,不同的层需要不同的学习率等等。

一个相当简单的例子

  1. #id函数用于取地址,map用于将id函数映射到所有conv5.parameters()的对象
  2. conv5_params = list(map(id, net.conv5.parameters()))
  3. #取出所有conv5层之外的参数
  4. base_params = filter(lambda p: id(p) not in conv5_params,
  5. net.parameters())
  6. #将conv5层的学习率设为10倍
  7. optimizer = torch.optim.SGD([
  8. {'params': base_params},
  9. {'params': net.conv5.parameters(), 'lr': lr * 10}],
  10. , lr=lr, momentum=0.9)

一个复杂一些的例子

碰到更复杂一些的模型,则会变得棘手一些,譬如MobileNet:
1.png

  1. #取出第一个ConvBNReLU中卷积层的参数
  2. p0 = list(list(model.features)[0])[0].parameters()
  3. #取出整个第一个ConvBNReLU块中的参数
  4. p1 = list(model.features)[0].parameters()

其中p0的长度为1(用了BN后卷积层就没bias了,只剩weight);
p1的长度为3(虽然BN有weightbias、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为粒度指派学习率。

  1. paraBN = []
  2. paraBase = list(range(5))
  3. listBN = []
  4. listBN.extend(list(list(model.features)[0])[1].parameters())
  5. for block in range(1, 18):
  6. list_conv = list(list(model.features)[block].conv)
  7. for layer in range(0, len(list_conv) - 2):
  8. listBN.extend(list(list_conv[layer])[1].parameters())
  9. listBN.extend(list_conv[-1].parameters())
  10. listBN.extend(list(list(model.features)[18])[1].parameters())
  11. adr_paraBN = list(map(id, listBN))
  12. paraBN = filter(lambda p: id(p) in adr_paraBN, model.parameters())
  13. layer_begin = [1, 3, 5, 8, 15, 18]
  14. for stage in range(5):
  15. listBase = []
  16. for block in range(layer_begin[stage], layer_begin[stage+1]):
  17. listBase.extend(list(list(model.features)[block].parameters()))
  18. paraBase[stage] = filter(lambda p: id(p) not in adr_paraBN, listBase)
  19. paraClr = itertools.chain(list(list(model.features)[18])[0].parameters(), model.classifier.parameters())
  20. optimizer = optim.SGD([
  21. {'params': paraBN, 'lr': learning_rate * 1.0, 'weight_decay': 0.0},
  22. {'params': paraBase[0], 'lr': learning_rate * 0.09},
  23. {'params': paraBase[1], 'lr': learning_rate * 0.15},
  24. {'params': paraBase[2], 'lr': learning_rate * 0.25},
  25. {'params': paraBase[3], 'lr': learning_rate * 0.36},
  26. {'params': paraBase[4], 'lr': learning_rate * 0.60},
  27. {'params': paraClr, 'lr': learning_rate * 1.00},]
  28. , lr=learning_rate, weight_decay=0.0001, momentum=0.9)
  29. sche = optim.lr_scheduler.MultiStepLR(optimizer, [140, 185], gamma=0.1, last_epoch=-1)
  30. model.train()
  31. for epoch in range(0, 200):
  32. train(epoch)
  33. sche.step()

然后是喜闻乐见的结果,就是不管我怎么调怎么搞都是原来的最好

MobileNet-Finetune.xlsx