非完全原创,但遗失了原文链接,看到可更改
迁移学习
一、迁移学习方法介绍
1. 微调网络的方法
微调网络的方法实现迁移学习,更改最后一层全连接,并且微调训练网络
2. 将模型看成特征提取器
将模型看成特征提取器,如果一个模型的预训练模型非常的好,那完全就把前面的层看成特征提取器,冻结所有层并且更改最后一层,只训练最后一层,这样我们只训练了最后一层,训练会非常的快速
两者区别:1. 前者是会在更改网络之后微调训练全部的网络参数
后者是将前面的层冻结,只训练最后一层,训练速度会很快
二、迁移基本步骤
-
数据的准备
-
选择数据增广的方式 : 扩展数据集
-
选择合适的模型
-
更换最后一层全连接
-
冻结层,开始训练
-
选择预测结果最好的模型保存
是先冻结部分层,此时已经将这些层的权重传入了,然后再修改网络的结构,最后是训练修改了的结构的权重
三、简单迁移学习案例
3.1 数据准备
# 解压数据到指定文件 def unzip(filename, dst_dir): z = zipfile.ZipFile(filename) z.extractall(dst_dir) unzip('./data/hymenoptera_data.zip', './data/') # 实现自己的Dataset方法,主要实现两个方法__len__和__getitem__ class MyDataset(Dataset): def __init__(self, dirname, transform=None): super(MyDataset, self).__init__() self.classes = os.listdir(dirname) self.images = [] self.transform = transform for i, classes in enumerate(self.classes): classes_path = os.path.join(dirname, classes) for image_name in os.listdir(classes_path): self.images.append((os.path.join(classes_path, image_name), i)) def __len__(self): return len(self.images) def __getitem__(self, idx): image_name, classes = self.images[idx] image = Image.open(image_name) if self.transform: image = self.transform(image) return image, classes def get_claesses(self): return self.classes # 分布实现训练和预测的transform train_transform = transforms.Compose([ transforms.Grayscale(3), transforms.RandomResizedCrop(224), #随机裁剪一个area然后再resize transforms.RandomHorizontalFlip(), #随机水平翻转 transforms.Resize(size=(256, 256)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) val_transform = transforms.Compose([ transforms.Grayscale(3), transforms.Resize(size=(256, 256)), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) # 分别实现loader train_dataset = MyDataset('./data/hymenoptera_data/train/', train_transform) train_loader = DataLoader(train_dataset, shuffle=True, batch_size=32) val_dataset = MyDataset('./data/hymenoptera_data/val/', val_transform) val_loader = DataLoader(val_dataset, shuffle=True, batch_size=32)
3.2 选择预训练的模型
这里我们选择了resnet18在ImageNet 1000类上进行了预训练的
model = models.resnet18(pretrained=True) # 使用预训练
使用model.buffers
查看网络基本结构
然后修改网络结构
from torchvision.models.resnet import * def get_net(): model = resnet18(pretrained=True) model.avgpool = nn.AdaptiveAvgPool2d((1, 1)) model.fc = nn.Sequential( nn.BatchNorm1d(512*1), nn.Linear(512*1, 你的分类类别数), ) return model
代码简单解读一下:
首先,通过torchvision导入相关的函数
通过resnet18( )实例化一个模型,并使用imagenet预训练权重
将平均池化修改为自适应全局平均池化,避免输入特征尺寸不匹配
修改全连接层,主要是修改分类类别数,并加入BN1d
这样子,不仅可以根据自己的需求改造网络,还能最大限度的使用现成的预训练权重。需要注意的是,这里的nn.BatchNorm1d(512*1)是很必要的,初学者可以尝试删除这个部件感受一下区别。
总结两点:
1. 由于pytorch框架是先创建网络中会使用到的模块,然后一个个拼接(在forward方法中),init方法是我们定义的一个个模块(包括Conv2、pool池化等),所以当我们创建一个对象时,还能够通过修改对象里面的属性来修改网络的结构,然后在实际训练的时候调用forward()方法,此时用的已经是修改过的网络了。
2. 相对于tensorflow,tf是将网络结构直接定义死的,不容易在现成的网络结构中进行修改。
替换最后一层网络结构
only_train_fc = True if only_train_fc: for param in model.parameters(): param.requires_grad_(False) fc_in_features = model.fc.in_features model.fc = torch.nn.Linear(fc_in_features, 2, bias=True)
注释
:only_train_fc
如果我们设置为True那么就只训练最后的fc层 现在观察一下可导的参数有那些(在只训练最后一层的情况下)
for i in model.parameters(): if i.requires_grad: print(i)
3.3 训练主体实现
epochs = 50 loss_fn = torch.nn.CrossEntropyLoss() opt = torch.optim.SGD(lr=0.01, params=model.parameters()) device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') # device = torch.device('cpu') model.to(device) opt_step = torch.optim.lr_scheduler.StepLR(opt, step_size=20, gamma=0.1) max_acc = 0 epoch_acc = [] epoch_loss = [] for epoch in range(epochs): for type_id, loader in enumerate([train_loader, val_loader]): mean_loss = [] mean_acc = [] for images, labels in loader: if type_id == 0: # opt_step.step() model.train() else: model.eval() images = images.to(device) labels = labels.to(device).long() opt.zero_grad() with torch.set_grad_enabled(type_id==0): outputs = model(images) _, pre_labels = torch.max(outputs, 1) loss = loss_fn(outputs, labels) if type_id == 0: loss.backward() opt.step() acc = torch.sum(pre_labels==labels) / torch.tensor(labels.shape[0], dtype=torch.float32) mean_loss.append(loss.cpu().detach().numpy()) mean_acc.append(acc.cpu().detach().numpy()) if type_id == 1: epoch_acc.append(np.mean(mean_acc)) epoch_loss.append(np.mean(mean_loss)) if max_acc < np.mean(mean_acc): max_acc = np.mean(mean_acc) print(type_id, np.mean(mean_loss),np.mean(mean_acc)) print(max_acc)
四、总结
使用了预训练模型,发现大概10个epoch就可以很快的得到较好的结果了,即使在使用cpu情况下训练,这也是迁移学习为什么这么受欢迎的原因之一了,如果读者有兴趣可以自己试一试在不冻结层的情况下,使用方法一能否得到更好的结果