transfer learning tutorial

在本教程中,你将会学到如何训练你的神经网络学会迁移学习。你可以在cs231n的课程上学到更多关于迁移学习的知识。

引用它的笔记:

在实践中,很少有人使用随机的初始化来训练整个神经网络。因为拥有足够大小的数据集是相对罕见的。相反,在更大的数据集上(比如说imageNet,它包含了1000个类别共120万幅图片)预先训练ConvNet,然后使用ConvNet作为感兴趣的任务的初始化或固定特征的提取器是一种常见的做法。

这两个主要的迁移学习的场景如下:

  • 精细化调整ConvNet:比起随机的初始化,我们使用预先训练的神经网络——比如说在ImageNet上训练过的网络,来初始化神经网络。其它的训练正常进行即可。
  • 作为固定特征提取器的ConvNet:在这种场景下,我们会冻结除了最后的全连接层外的所有网络的权重。然后把全连接层置换为一个新的有着随机权重的网络层,然后只训练就好。
from __future__ import print_function,division
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np

## lr_scheduler提供了基于opoch数量的几种调整学习率的方法
## 更详细的见 https://pytorch.org/docs/stable/optim.html
from torch.optim import lr_scheduler
import torchvision

## datasets里包含了各种数据集,models中包含了各种模型
## 见 https://pytorch-cn.readthedocs.io/zh/latest/torchvision/torchvision-datasets/
from torchvision import datasets,models,transforms
import matplotlib as plt

## 包含了许多对时间进行处理的包
## 见https://www.runoob.com/python/python-date-time.html
import time
import os

## copy模块提供深拷贝和浅拷贝两种复制方法,前者复制可变(dict、列表)和不变对象
## 后者复制不变对象,引用可变对象,即对可变对象的改变也会同步改变它的值
## 见 https://www.cnblogs.com/MrFiona/p/5958911.html
import copy

plt.ion()

载入数据

我们将会使用torchvision和torch.utils.data包来载入数据。

我们今天要解决的问题是分类蚂蚁和蜜蜂。每一类我们都有大概120张图片。每个类都有75个验证图片。通常,如果从头训练,这是个非常小的数据集。但因为我们使用了迁移学习,我们应该能够很好地做到。

这个数据集是ImageNet的一个小的子集。

data_transforms = {
    'train':transforms.Compose([
        transforms.RandomResizedCrop(224),
        transforms.RandomHorizontalFlip(),
        transforms.ToTensor(),
        transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])
    ]),
    'val':transforms.Compose([
        transforms.Resize(256),
        transforms.CenterCrop(224),
        transforms.ToTensor(),
        transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])
    ])
}

data_dir = 'C:/Users/majx1/Music/hymenoptera_data'
image_datasets = {x:datasets.ImageFolder(os.path.join(data_dir,x),data_transforms[x])
                 for x in ['train','val']}
dataloaders = {x:torch.utils.data.DataLoader(image_datasets[x],batch_size=4,shuffle=True,num_workers=4)
              for x in ['train','val']}
dataset_sizes = {x:len(image_datasets[x])for x in ['train','val']}
## 用list保存类名,见 https://www.cnblogs.com/wanghui-garcia/p/10649364.html
class_names = image_datasets['train'].classes

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

可视化一些图像

让我们来可视化一些训练图像来理解数据增强。

## Visualize Train Images
def imshow(inp,title=None):
    inp = inp.numpy().transpose((1,2,0))
    mean = np.array([0.485,0.456,0.406])
    std = np.array([0.229,0.224,0.225])
    inp = std * inp + mean
    
    ## clip()将inp中小于0的数变为0,大于1的数变为1,类似Tensor的clamp()
    ## 见 https://blog.csdn.net/qq1483661204/article/details/78150203
    inp = np.clip(inp,0,1)
    
    plt.imshow(inp)
    
    if title is not None:
        plt.title(title)
    plt.pause(0.001)
    
inputs,classes = next(iter(dataloaders['train']))

out = torchvision.utils.make_grid(inputs)

imshow(out,title=[class_names[x] for x in classes])

其结果为:
在这里插入图片描述

训练模型

现在,我们可以写一个一般函数来训练模型。在这里,我们会说明:

  • 规划学习率
  • 保存最好的模型

在下面的代码里,参数scheduler是个torch.optim.lr_scheduler的一个对象。

## Training The Model
def train_model(model,criterion,optimizer,scheduler,num_epochs=25):
    ## time.time()返回距1970年1月1日的秒数
    since = time.time()
    
    best_model_wts = copy.deepcopy(model.state_dict())
    best_acc = 0.0
    
    for epoch in range(num_epochs):
        print('Epoch {}/{}'.format(epoch,num_epochs - 1))
        print('-' * 10)
        
        for phase in ['train','val']:
            if phase == 'train':
                scheduler.step()
                model.train()
            else:
                model.eval()
            
            running_loss = 0.0
            running_correct = 0
            
            for inputs,labels in dataloaders[phase]:
                inputs = inputs.to(device)
                labels = labels.to(device)
                
                optimizer.zero_grad()
                
                ## 在参数值为True时,容许记录导数;否则不能记录。是为了在测试时减少运算量
                ## 见 https://blog.csdn.net/zzzpy/article/details/88873109
                
                with torch.set_grad_enabled(phase == 'train'):
                    outputs = model(inputs)
                    _,preds = torch.max(outputs,1)
                    loss = criterion(outputs,labels)
                    
                    if phase == 'train':
                        loss.backward()
                        optimizer.step()
                    
                running_loss += loss.item() * inputs.size(0)
                running_corrects += torch.sum(preds == labels.data)
            
            epoch_loss = running_loss / dataset_sizes[phase]
            epoch_acc = running_corrects.double() / dataset_sizes[phase]
            
            print('{} loss: {:.4f} Acc: {:.4f}'.format(phase,epoch_loss,epoch_acc))
            
            if phase == 'val' and epoch_acc > best_acc:
                best_acc = epoch_acc
                best_model_wts = copy.deepcopy(model.state_dict())
        
        print()
    
    time_elapased = time.time() - since
    print('Training complete in {:.0f}m {:.0f}s'.format(time_elapased // 60,time_elapased % 60))
    print('Best val Acc:{:4f}'.format(best_acc))
    
    model.load_state_dict(best_model_wts)
    return model

可视化模型预测

用于展示一些图片的预测值的一般函数。

def visualize_model(model,num_images=6):
    was_training = model.training
    model.eval()
    images_so_far = 0
    fig = plt.figure()
    
    with torch.no_grad():
        for i,(inputs,label) in enumerate(dataloaders['val']):
            inputs = inputs.to(device)
            labels = labels.to(device)
            
            outputs = model(inputs)
            _,preds = torch.max(outputs,1)
            
            for j in range(inputs.size()[0]):
                images_so_far += 1
                ax = plt.subplot(num_images//2,2,images_so_far)
                ax.axis('off')
                ax.set_title('predicted:{}'.format(class_names[preds[j]]))
                imshow(inputs.cpu().data[j])
                
                if images_so_far == num_images:
                    model.train(mode=was_training)
                    return
        model.train(mode=was_training)

Finetuning the convnet

载入预先训练好的模型,并重制最后一层全连接层。

model_ft = models.resnet18(pretrained=True)
num_ftrs = model_ft.fc.in_features
model_ft.fc = nn.Linear(num_ftrs,2)

model_ft = model_ft.to(device)

criterion = nn.CrossEntropyLoss()

optimizer_ft = optim.SGD(model_ft.parameters(),lr=0.001,momentum=0.9)

exp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft,step_size=7,gamma=0.1)

训练及评估

这在cpu上大概要运行15-24分钟,但在GPU上,只需要运行不到1分钟。

model_ft = train_model(model_ft,criterion,optimizer_ft,exp_lr_scheduler,num_epochs=25)

其结果为:

visualize_model(model_ft)

其结果为:

作为固定特征提取器的ConvNet

在这里,我们需要冻结除了最后一层外所有的神经网络。我们需要设置requires_grad == False来冻结参数,从而使backward()时不计算梯度。

model_conv = torchvision.models.resnet18(pretrained=True)
for param in model_conv.parameters():
    param.requires_grad = False
    
num_ftrs = model_conv.fc.in_features
model_conv.fc = nn.Linear(num_ftrs,2)

model_conv = model_conv.to(device)

criterion = CrossEntropyLoss()

optimizer_conv = optim.SGD(model_conv.fc.parameters(),lr=0.001,momentum=0.9)

exp_lr_scheduler = lr_scheduler.StepLR(optimizer_conv,step_size=7,gamma=0.1)

训练和评估

在CPU上这大概会花之前一半的时间。这是预期的结果,因为大多数网络并不需要计算梯度。然而,前向传播需要计算。

model_conv = train_model(model_conv,criterion,optimizer_conv,exp_lr_scheduler,num_epochs=25)
visualize_model(model_conv)

plt.ioff()
plt.show()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值