更改原始模型
举例,砍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_para
class Net(nn.Module):
def __init__(self, img):
super(Net, self).__init__()
self.t2p = tensor2para(img)
self.features = torchvision.models.vgg16().features
self.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,譬如BN
for 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()
然后是喜闻乐见的结果,就是不管我怎么调怎么搞都是原来的最好。