模型微调(finetune)
多分类的finetune。
----接上次的鸟的图像分类,其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的准确率。
更多推荐
所有评论(0)