迁移学习一、基本使用

非完全原创,但遗失了原文链接,看到可更改

迁移学习

一、迁移学习方法介绍

1. 微调网络的方法

微调网络的方法实现迁移学习,更改最后一层全连接,并且微调训练网络

2. 将模型看成特征提取器

模型看成特征提取器,如果一个模型的预训练模型非常的好,那完全就把前面的层看成特征提取器冻结所有层并且更改最后一层只训练最后一层,这样我们只训练了最后一层,训练会非常的快速

两者区别:1. 前者是会在更改网络之后微调训练全部的网络参数

  1. 后者是将前面的层冻结,只训练最后一层,训练速度会很快

二、迁移基本步骤

  1. 数据的准备

  2. 选择数据增广的方式 : 扩展数据集

  3. 选择合适的模型

  4. 更换最后一层全连接

  5. 冻结层,开始训练

  6. 选择预测结果最好的模型保存

是先冻结部分层,此时已经将这些层的权重传入了,然后再修改网络的结构,最后是训练修改了的结构的权重

三、简单迁移学习案例

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情况下训练,这也是迁移学习为什么这么受欢迎的原因之一了,如果读者有兴趣可以自己试一试在不冻结层的情况下,使用方法一能否得到更好的结果

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值