【CV】06_迁移学习

【一】简介
  • 表现:参数重用
  • 本质:特征重用
  • 特点:任务 相似度
  • 示意图
【二】方法
  • 【数据量小】 全连接层训练,或者靠近全连接层的 卷积层训练,由于参数过多,而数据量少,容易出现 过拟合 现象

  • 【数据量小】FineTuning 精细调节,有选择性的 训练卷积层,学习速率要低,避免破坏之前学好的参数

  • 【数据量大】作为模型参数预训练的 大规模参数重训练,舍弃网络的后面部分 (这部分太特殊了),保留网络前面的通用的特征提取结构

【三】实例 - 猫狗大战(数据预处理)
  • 导入包
import os
import sys
import math
import torch
import torchvision

import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
import torch.nn.functional as F
import torchvision.models as models
import torchvision.datasets as dset
import torchvision.transforms as transforms

from PIL import Image
from torch.utils import data
from torch.nn import DataParallel
from torch.nn import init
from torch.autograd import Variable
from torch.utils.data import DataLoader

import numpy as np
import warnings
  • 加载图像
# 加载图片数据
class ImageFolder(data.Dataset):

    # path 是主路径,'E:\Programme Learning\machine learning\kaggle猫狗大战数据集\train'
    def __init__(self, path, transform=None):
        self._path = path
        self._image_paths = os.listdir(path)
        self._transform = transform

    # 获取一张图片
    def __getitem__(self, index):
        image = Image.open(os.path.join(self._path, self._image_paths[index]))
        label = os.path.basename(self._image_paths[index]).split('.')[0]
        label = 1 if label == 'cat' else 0
        if self._transform:
            image = self._transform(image)
        return image, label

    # 返回主路径
    def __len__(self):
        return len(self._image_paths)
  • 图像裁剪
# 常用的图片变换方式,可以通过 compose 将各个变换串联起来
transform = transforms.Compose([
    # transforms.CenterCrop(300), # 剪切并返回PIL图片上中心区域,会留下黑框
    transforms.Resize((256, 256)), # 缩放的值在 a<=scale<=b 随机采样,将图像大小都规范成 256*256
    transforms.ToTensor()
    # transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)), # 用均值和标准差对张量图像进行标准化处理
])
  • 训练集 and 测试集
# 训练集
trainset = ImageFolder('E:/Programme Learning/machine learning/kaggle猫狗大战数据集/train', transform)
# 测试集
testset = ImageFolder('E:/Programme Learning/machine learning/kaggle猫狗大战数据集/test1', transform)

# 在训练集上取 20 张图片做训练,50 张图片做测试
Train_data = []
Test_data = []
for i in range(20):
    Train_data.append(trainset[i])
for i in range(50):
    Test_data.append(trainset[12480 + i])
#  DataLoader(dataset, batch_size:每次输入数据的行数, num_workers:使用多少个子进程来导入数据)
trainLoader = torch.utils.data.DataLoader(Train_data, batch_size=2, num_workers=0)
testLoader = torch.utils.data.DataLoader(Test_data, batch_size=2, num_workers=0)

【四】实例 - 猫狗大战(CNN)
  • CNN 框架
# 卷积网络框架
class Conv(nn.Module):

    def __init__(self, shape):
        # 通过init进行初始化是必要的
        super(Conv, self).__init__()

        # self.conv1 = nn.Conv2d(1,1,3)
        # 第一层卷积
        self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1)
        self.bn1 = nn.BatchNorm2d(64)
        self.mx1 = nn.MaxPool2d(kernel_size=2)
        self.drop1 = nn.Dropout2d(p=0.2)
        # 第二层卷积
        self.conv2 = nn.Conv2d(64, 64, kernel_size=3, stride=1, padding=1)
        self.bn2 = nn.BatchNorm2d(64)
        self.mx2 = nn.MaxPool2d(kernel_size=2)
        self.drop2 = nn.Dropout2d(p=0.2)
        # 全连接层
        self.fc = nn.Linear(64 * shape * shape, 1)

        # 权重初始化,xavier_normal,最常用的神经网络初始化方法,高斯方差为 1
        init.xavier_normal_(self.conv1.weight)
        # 偏置量初始化
        init.constant_(self.conv1.bias, 0)
        init.xavier_normal_(self.conv2.weight)
        init.constant_(self.conv2.bias, 0)
        init.xavier_normal_(self.fc.weight)
        init.constant_(self.fc.bias, 0)

    # 前向传播:Convolution - BN - Pooling - Relu - Dropout
    def forward(self, x):
        # The step is absolutely necessary because we need to transform feature to flat in
        # order to pass to any functinal
        # x = x.view(-1 , self.num_flat_features(x))
        # define recursion
        x = F.relu(self.bn1(self.conv1(x)))
        x = self.drop1(self.mx1(x))
        x = F.relu(self.bn2(self.conv2(x)))
        x = self.drop2(self.mx2(x))
        x = x.view(-1, self.num_flat_features(x))
        x = self.fc(x)
        # 对应猫和狗的二分类
        return torch.sigmoid(x)

    # 这个函数进行基础的变形
    def num_flat_features(self, x):
        size = x.size()[1:]
        num_features = 1
        for s in size:
            num_features *= s
        return num_features

MyNet = Conv(64) # 实例化
  • 模型训练
# 优化器,使用随机梯度下降
optimizer = torch.optim.SGD(MyNet.parameters(), lr=0.001)

# 开始训练
for i in range(10):
    k = 0
    for batch_idx, (data, target) in enumerate(trainLoader):

        # 转成 Pytorch 视图,用 Variable,view(-1, 1) 将 target 转换成 tensor 需要的维度
        data, target = Variable(data), Variable(target).type(torch.FloatTensor).view(-1, 1)
        # 优化器清零
        optimizer.zero_grad()
        # 将数据输入卷积网络
        output = MyNet(data)
        # 交叉熵损失函数,反向传播的梯度不再与 sigmoid 函数相关,防止梯度下降
        bceloss = nn.BCELoss()
        # 传入 output 和 target
        loss = bceloss(output, target)
        # 开始计算,求导数
        loss.backward()
        # 优化器开始优化,更新网络的参数
        optimizer.step()

        # 预测的准确率
        pred = [0 if a < 0.5 else 1 for a in output.data.numpy()]
        incorrect = abs(pred - target.data.numpy()).sum()
        err = 100. * incorrect / len(data) # 这个地方有点错误

        # 打印
        k += 1
        if k % 10 == 0:
            print('Loss: {:.6f}\tError: {:.6f}'.format(loss.data, err))
  • 错误率函数,由于训练的数据量太少,严重过拟合
def Error(net, Data):
    incorrect = 0
    for data, target in Data:
        # data.size() = [3, 256, 256]
        data, target = Variable(data).resize(1, data.size()[0], data.size()[1], data.size()[2]), Variable(torch.FloatTensor([target]))
        # if gpu == True:
        #     data = data.cuda()
        #     target = target.cuda()
        output = net(data)
		# print (target, output)
        pred = [0 if a < 0.5 else 1 for a in output.cpu().data.numpy()]
        incorrect += abs(pred - target.cpu().data.numpy()).sum()
    error = incorrect / len(Data)
    return error

【五】实例 - 猫狗大战(迁移学习 resnet50)
  • 加载预训练模型
from torchvision.models.resnet import resnet50
# resnet50,pretrained=True,利用 ImageNet 进行的预训练
res_net = resnet50(pretrained=True)
  • 建立 特征网络
# FeatureNet 是提取特征的网络,建立一个 sequential container
FeatureNet = nn.Sequential()

# add_module 添加模块,为每个模块起个新的名字
k = 0
for module in res_net.children():
    FeatureNet.add_module('res{}'.format(k), module)
    k += 1
    # k == 9 的时候是全连接层
    if k == 9:
        break
  • 建立 决策网络(即最后的全连接层)
# 创建 Faltten 类 将二维图像压平,形成一维的特征向量
class Flatten(nn.Module):
    def forward(self, input):
        return input.view(input.size(0), -1)

# 决策网络(分类)
classifer = nn.Sequential(Flatten(), nn.Linear(2048, 1), nn.Sigmoid())
  • 特征网络 + 决策网络 = 迁移学习框架
from collections import OrderedDict
# OrderedDict 按顺序拼接
ResNet = nn.Sequential(OrderedDict([
          ('feature', FeatureNet),
          ('classifier', classifer),
        ]))

 

  • 重点冻结 一部分变量(FeatureNet), 训练 一部分变量(classifer)
  1. 【全连接层】 方法一,通过 optimizer 控制需要优化的变量
def train(net, num_epochs):
    # 训练最后的全连接层,用了 Adam 梯度下降算法
    optimizer = torch.optim.Adam(net.classifier.parameters(), lr=0.001)
    k = 0
    # 开始训练
    for epoch in range(num_epochs):
    	# ImageNet 上训练的图片的大小为 224 * 224
        for batch_idx, (data, target) in enumerate(trainLoader):
            k += 1
            data, target = Variable(data), Variable(target).type(torch.FloatTensor).view(len(data), -1)
            # 优化器归零
            optimizer.zero_grad() 
            # 将数据扔进框架
            output = net(data)
            # 交叉熵损失函数
            bceloss = nn.BCELoss() 
            # 将数据扔进损失函数内
            loss = bceloss(output, target) 
            # 开始求导计算损失值
            loss.backward() 
            # 优化器迭代求解
            optimizer.step() 
            # 预测值
            pred = [0 if a < 0.5 else 1 for a in output.cpu().data.numpy()]
            # 计算准确率
            incorrect = abs(pred - target.cpu().view(-1).data.numpy()).sum()
            err = incorrect / len(data)
            if k % 10 == 0:
                print('Epoch {}/{}\tLoss: {:.6f}\tError: {:.3f}'.format(
                    epoch, num_epochs, loss.data, err))
    torch.save(net.state_dict(), 'resnet')
  1. 【全连接层】 方法二,detach(),把不需要训练的计算图 detach 掉
def train(net, num_epochs):
    # 全局网络(将 feature 特征网络截断),用了 Adam 梯度下降算法
    optimizer = torch.optim.Adam(net.parameters(), lr=0.01)
    k = 0
    for epoch in range(num_epochs):
        for batch_idx, (data, target) in enumerate(trainLoader):
            k += 1
            data, target = Variable(data), Variable(target).type(torch.FloatTensor).view(len(data), -1)
            # 优化器归零
            optimizer.zero_grad()
            # 将数据扔进框架
            # detach() 截断了 feature 特征网络,相当于不训练这块了,只训练 classifier 决策网络
            features = net.feature(data).detach()
            # 将 detach 掉的特征网络结构 和 决策网络结构 结合
            output = net.classifier(features)
            # 交叉熵损失函数
            bceloss = nn.BCELoss()
            # 将数据扔进损失函数内
            loss = bceloss(output, target)
            # 开始求导计算损失值
            loss.backward()
            # 优化器迭代求解
            optimizer.step()
            # 预测值
            pred = [0 if a<0.5 else 1 for a in output.cpu().data.numpy()]
            # 计算准确率
            incorrect = abs(pred - target.cpu().view(-1).data.numpy()).sum()
            err = incorrect/len(data)
            if k%10 == 0:
                print('Epoch {}/{}\tLoss: {:.6f}\tError: {:.3f}'.format(
                    epoch, num_epochs, loss.data[0], err))
    torch.save(net.state_dict(), 'resnet')
  1. 【中间层】 方法三,FineTuning,只优化指定的模块,因为网络已经训练的很好了,要使用更低的学习速率。
    最后需训练的内容:从 指定的模块最后
def finetune(net, num_epochs):
    # 只优化指定的模块(这个例子里是 res7 模块)
    optimizer = torch.optim.Adam([{'params': net.feature.res7.parameters()}], lr=1e-4)
    k = 0
    for epoch in range(num_epochs):
        for batch_idx, (data, target) in enumerate(trainLoader):
            k += 1
            data, target = Variable(data), Variable(target).type(torch.FloatTensor).view(len(data), -1)
            # 优化器归零
            optimizer.zero_grad()
            # 将数据扔进框架
            output = net(data)
            # 交叉熵损失函数
            bceloss = nn.BCELoss()
            # 将数据扔进损失函数内
            loss = bceloss(output, target)
            # 开始求导计算损失值
            loss.backward()
            # 优化器迭代求解
            optimizer.step()
            # 预测值
            pred = [0 if a < 0.5 else 1 for a in output.cpu().data.numpy()]
            incorrect = abs(pred - target.cpu().view(-1).data.numpy()).sum()
            err = incorrect / len(data)
            if k % 10 == 0:
                print('Epoch {}/{}\tLoss: {:.6f}\tError: {:.3f}'.format(
                    epoch, num_epochs, loss.data[0], err))
    torch.save(net.state_dict(), 'resnet')

  • 迁移学习 另类玩法直接提取特征向量
# 特征提取函数
def feature_extract(x):
    x = Variable(x)
    x = x.resize(1, x.size()[0], x.size()[1], x.size()[2])
    net = nn.Sequential()
    k = 0
    for module in res_net.children():
        net.add_module('res{}'.format(k), module)
        # 调整 k,可在任意一层停下,前向传播获取特征向量 feature
        if k == 8:
            # net.add_module('conv11', nn.Conv2d(in_channels=2048, out_channels=512, kernel_size=1))
            net.add_module('flattern', Flatten())
            feature = net(x)
            break
        k += 1
    return feature

# 训练集的特征
features_map_train = []
for idx, (img, target) in enumerate(Train_data):
        features_map_train.append((feature_extract(img).data, target))

# 测试集的特征
features_map_test = []
for idx, (img, target) in enumerate(Test_data):
        features_map_test.append((feature_extract(img).data, target))
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值