----接上次的鸟的图像分类,其acc为84%。

这次依然使用此数据集,并用resenet网络进行finetune,然后进行鸟的图像分类。

1、什么是finetune?

       利用已训练好的模型进行重构(自己的理解)。 对给定的预训练模型(用数据训练好的模型)进行微调,直接利用预训练模型进行微调可以节省许多的时间,能在比较小的epoch下就达到比较好的效果。通常进行微调,1、自己构建模型效果差,所以采用一些常用的模型,别人用数据训好的。2、数据量不够大,所以采用微调。以下是模型微调的例子:

2、数据为鸟类的数据集,其一共有4个类别,如下所示:

 1、数据的类别

 2、图像数据

数据的前期处理和划分,划分可以用random.shuffule直接进行打乱,然后划分。

2、数据导入

import torch
from torch.utils.data import DataLoader
from torchvision import transforms
from PIL import Image
import numpy as np

random_seed = 1
torch.manual_seed(random_seed)

transform = transforms.Compose([
    # transforms.RandomRotation(1),
    transforms.Resize(224), #
    transforms.CenterCrop(224), #
    transforms.ToTensor(), # 将图片(Image)转成Tensor,归一化至[0, 1]
    transforms.Normalize(mean=[.5,.5,.5], std=[.5,.5,.5]),      # 标准化至[-1, 1],规定均值和标准差

])


transform1 = transforms.Compose([
    # transforms.RandomRotation(1),
    # transforms.Resize(224), #
    # transforms.CenterCrop(224), # 从图片中间切出224*224的图片
    transforms.ToTensor(), # 将图片(Image)转成Tensor,归一化至[0, 1]
    transforms.Normalize(mean=[.5,.5,.5], std=[.5,.5,.5]), # 标准化至[-1, 1],规定均值和标准差
    # transforms.Normalize(mean=[.5], std=[.5])   # 通道数为1
    #input[channel] = (input[channel] - mean[channel]) / std[channel]
])


class DogLoader(torch.utils.data.Dataset):

    def __init__(self,root,train=True, img_transform = None, target_transform=None,transform = None):  # 负责将输入的数据做成list形式
        # 这样不会调用父类中的init方法,这个是重载了子类的init,并且这里的属性都属于全局的属性

        self.root = root
        self.transform = img_transform
        self.target_transform = target_transform
        self.train = train
        self.transforms = transform

        self.data = []
        self.label = []
        if self.train:
            with open('trainImg.txt') as fr:
                fr = fr.readlines()
                for imgPath in fr:
                    img = imgPath.split('\t')[0]
                    label = imgPath.split('\t')[1]

                    self.data.append(root+img)
                    self.label.append(int(label.strip()))

        else:
            with open('testImg.txt') as fr1:
                fr1 = fr1.readlines()
                for imgPath in fr1:
                    img = imgPath.split('\t')[0]
                    label = imgPath.split('\t')[1]

                    self.data.append(root+img)
                    self.label.append(int(label.strip()))


    def __getitem__(self, index):   # 对数据进行编码,然后转换成我们想要的格式

        img,label = self.data[index],self.label[index]
        img = Image.open(img).convert('RGB')   # 将图片转为RGB图像,为了有一些图像不是RGB的
        # img = Image.open(img_path).convert('L')    # 将RGB的三通道转为一通道的数
        if self.transforms:
            img=self.transforms(img)     # 传入transforms是PIL数据

        array = np.asarray(img)
        img = torch.from_numpy(array)

        return img, label

    def __len__(self):
        return len(self.data)

train_data = DogLoader('data/',train=True,transform=transform)
train_loader = DataLoader(train_data, batch_size=16, shuffle=True, drop_last=False, num_workers=0)

test_data = DogLoader('data/',train=False,transform=transform1)   # 测试不进行数据增强,训练继续宁数据增
test_loader = DataLoader(test_data, batch_size=16, shuffle=True, drop_last=False, num_workers=0)

# for i, trainData in enumerate(train_loader):  # 将一个类的对象可以像list那样调用
#     print("第 {} 个Batch \n{}".format(i, trainData))
#
# for i, testData in enumerate(test_loader):  # 将一个类的对象可以像list那样调用
#     print("第 {} 个Batch \n{}".format(i, testData))

__init__() :类的属性形式,这里主要获取图像的地址和图像的标签。

__getitem__():  当使用这个函数时,它的实例对象(假设为P)就可以以P[key]形式取值,当实例对象做P[key]运算时,就会调用类中的__getitem__()方法。此处使用__getitem__()函数主要是通过后面训练时for循环得到图像的数据和标签。

2、预训练模型

# -*- coding: utf-8 -*-
from torchvision import models
from torch import nn
from global_config import *


def fine_tune_resnet18(): # 这里表示为

    model_ft = models.resnet18(pretrained=True)
    '''
    这里写为True,会自动下载模型的参数,并加载到模型中。
    当然也可以手动下载模型的参数,然后将模型的参数加载到模型中
    '''
    # 把前面的特征进行了拼接
    print('num_features', model_ft)
    num_features = model_ft.fc.in_features
    # fine tune we change original fc layer into classes num of our own
    model_ft.fc = nn.Linear(num_features, 4)

    if USE_GPU:
        model_ft = model_ft.cuda()
    return model_ft


def fine_tune_vgg16():

    model_ft = models.vgg16(pretrained=True)

    print('fine_tune_vgg16() = ',model_ft)
    num_features = model_ft.classifier[6].in_features
    model_ft.classifier[6] = nn.Linear(num_features, 4)

    if USE_GPU:
        model_ft = model_ft.cuda()
    return model_ft


def fine_tune_resnet18_():
    """
     'resnet18': 'https://download.pytorch.org/models/resnet18-5c106cde.pth',
     'resnet34': 'https://download.pytorch.org/models/resnet34-333f7ec4.pth',
     'resnet50': 'https://download.pytorch.org/models/resnet50-19c8e357.pth',
     'resnet101': 'https://download.pytorch.org/models/resnet101-5d3b4d8f.pth',
     'resnet152': 'https://download.pytorch.org/models/resnet152-b121ed2d.pth',
    """
    model_ft = models.resnet18(pretrained=False)
    model_ft.load_state_dict(torch.load('resnet18-5c106cde.pth'))   # 加载已经下载好的模型参数
    print('model_ft: ',model_ft)
    print('resnet18-5c106cde.pth',model_ft.load_state_dict(torch.load('resnet18-5c106cde.pth')))
    num_features = model_ft.fc.in_features
    # fine tune we change original fc layer into classes num of our own
    model_ft.fc = nn.Linear(num_features, 4)

    if USE_GPU:
        model_ft = model_ft.cuda()
    return model_ft


def fine_tune_resnet50():
    # 实际任务中这个挺重要的
    resNet50 = models.resnet50(pretrained=True) # 调用的预训练网络
    ResNet50 = ResNet(Bottleneck, [3, 4, 6, 3], num_classes=2)   # 自己定义的网络

    # 读取参数
    pretrained_dict = resNet50.state_dict()   # 读取预训练网络模型的参数
    model_dict = ResNet50.state_dict()   # 读自定义模型的参数

    # 将pretained_dict里不属于model_dict的键剔除掉
    pretrained_dict = {k: v for k, v in pretrained_dict.items() if k in model_dict}   # 剔除一些不同的网络模型参数

    # 更新现有的model_dict
    model_dict.update(pretrained_dict)

    # 加载真正需要的state_dict
    ResNet50.load_state_dict(model_dict)

预训练模型的调用,models.resnet18(),表示的是调用resnet18模型,当models.resnet18(pretrained=True)的时候,则表示直接下载了模型的参数。当models.resnet18(pretrained=False)的时候,可以手动下载好模型torch.load('resnet18-5c106cde.pth')。由于不同的数据可能的类别不同所以通常对最后的一层更改。

3、训练与测试代码

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import matplotlib.pyplot as plt
from icecream import ic
from LoaderBirddata import *
from Bird_finetune_model import *

n_epochs = 10
learning_rate = 0.01
momentum = 0.09   
log_interval = 10
random_seed = 1
torch.manual_seed(random_seed)

network = fine_tune_vgg16()
optimizer = optim.SGD(network.parameters(), lr=learning_rate, momentum=momentum)   #尝试多种优化器
# scheduler = torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones=[10, 20, 30, 40,50], gamma=0.1, last_epoch=-1)

train_losses = []
train_counter = []
test_losses = []
test_counter = [i * len(train_loader.dataset) for i in range(n_epochs + 1)]


def train(epoch):
    network.train()
    corrt = 0
    corrt_batch = 0

    for batch_idx, (data, target) in enumerate(train_loader): # 这里的idx 表示的是一个批次的idx,一个for循环一个批次
        optimizer.zero_grad()  # 梯度先更0
        output = network(data)
        loss = F.cross_entropy(output, target)   # 多试试其他的loss函数,这个loss+log = 交叉熵loss 函数
        loss.backward()
        optimizer.step()

        # train_= output.max(1)[1]
        train_output = output.max(1, keepdim=True)[1]   # 列中最大值的索引值
        train_output = train_output.view(target.size())
        corrt += torch.eq(train_output,target).sum()

        corrt_batch += torch.eq(train_output,target).sum()
        if batch_idx % log_interval == 0:   # 10个批次计算一下loss函数还有
            print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}\tacc: {}'.format(epoch, batch_idx * len(data),
                                                                           len(train_loader.dataset),
                                                                           100. * batch_idx / len(train_loader),
                                                                           loss.item(),corrt_batch /(log_interval*output.shape[0])))     # 用loss。item()精度更高

            corrt_batch = 0
            train_losses.append(loss.item())    # 将得到的loss全部读append到一个list中去
            train_counter.append((batch_idx * 8) + ((epoch - 1) * len(train_loader.dataset)))
            torch.save(network.state_dict(), './model.pth')   # 为什么这样就是保存了参数?
            torch.save(optimizer.state_dict(), './optimizer.pth')

    train_cucarrcy =  corrt / len(train_loader.dataset)
    print(f'train_cucarrcy: {train_cucarrcy}')
    # scheduler.step()   # 调用学习率,能自动调整学习率。

def test():
    network.eval()
    test_loss = 0
    correct = 0
    corrt1 = 0
    with torch.no_grad():   # 所有的梯度停止
        for data, target in test_loader:   # 一次是一个batch的test data,一共1w张图片,每个batch是1k,所以10个batch为一个epoch
            output = network(data)
            test_loss += F.cross_entropy(output, target, reduction='sum').item()
            pred = output.max(1, keepdim=True)[1]   
            # pred = output.data.max(1, keepdim=True)[1] 
            correct += pred.eq(target.data.view_as(pred)).sum()
            ####
            test_output = output.max(1, keepdim=True)[1]
            tEST_output = test_output.view(target.size())
            corrt1 += torch.eq(tEST_output, target).sum()
            ####

    test_loss /= len(test_loader.dataset)    # 取平均loss
    test_losses.append(test_loss)

    test_cucarrcy = corrt1 / len(test_loader.dataset)
    print('test_cucarrcy',test_cucarrcy)
    print('\nTest set: Avg. loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
        test_loss, correct, len(test_loader.dataset),
        (100. * correct / len(test_loader.dataset))))

train(1)   # 训练一次,然后测试一下
test()  # 不加这个,后面画图就会报错:x and y must be the same size

for epoch in range(1, n_epochs + 1):
    train(epoch)
    test()

fig = plt.figure()
plt.plot(train_counter, train_losses, color='blue')
plt.scatter(test_counter, test_losses, color='red')
plt.legend(['Train Loss', 'Test Loss'], loc='upper right')
plt.xlabel('number of training examples seen')
plt.ylabel('negative log likelihood loss')

4、实验结果:

经过2个epoch就可以达到99的准确率。 

Logo

更多推荐