一、预处理与数据增强模块
transforms
transforms通常指torchvision.transforms
1.调整尺寸
transforms.Resize():将输入图像的尺寸调整为指定的大小
2.裁剪
transforms.CenterCrop():从图像中心进行裁剪,剪裁出指定大小的区域
transforms.RandomCrop():随机从图像中裁剪出指定大小的区域,常用于数据增强
3.翻转
transforms.RandomHorizontalFlip():以一定概率对图像进行水平翻转,这是一种简单有效的数据增强方式,能增加数据集的多样性
transforms.RandomVerticalFlip():以一定概率对图像进行垂直翻转
4.数据归一化
transforms.Normalize():对图像数据进行归一化处理,使其符合特定的均值和标准差分布,这有助于模型的训练收敛。
通常对于RGB图像,常见的归一化参数如下:
normalize_transform = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
5.类型转换
transforms.ToTensor():将PIL图像或numpy.ndarray 转换为torch.Tensor,并将像素值从[0,255](如果是8位图像)归一化到[0.0,1.0]
6.组合变换
transforms.Compose():可以将多个变换操作组合在一起,按顺序依次对图像进行处理。比如先调整大小,再随机裁剪,然后转换为张量,最后归一化
transform = transforms.Compose([
transforms.Resize(256),
transforms.RandomCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
二、模型选择
推导式
1.列表推导式
[expression for item in iterable if condition],其中exporession是对item进行处理后生成的结果,iterable是可迭代对象,if condition是可选的过滤条件
2.集合推导式
{expression for item in iterable if condition},与列表推导式类似,但使用花括号{},生成的是集合,集合中的元素是唯一的
3.字典推导式
{key_expression:value_expression for item in iterable if condition},用于创建字典,冒号:分隔键表达式和值表达式
ImageFolder
ImageFolder要求数据集的目录结构遵循特定的格式,即每个类别对应一个子文件夹,子文件夹的名称即为该类别的标签,子文件夹中存放属于该类别的图像文件
以下是一个使用ImageFolder加载图像数据集的基本示例:
python
import torchvision
from torchvision import transforms
# 定义图像预处理操作
transform = transforms.Compose([
transforms.Resize((224, 224)), # 调整图像大小为 224x224
transforms.ToTensor(), # 将图像转换为张量
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) # 归一化处理
])
# 使用 ImageFolder 加载数据集
dataset = torchvision.datasets.ImageFolder(root='data', transform=transform)
# 打印数据集的类别信息
print(dataset.classes) # 类别名称列表
print(dataset.class_to_idx) # 类别名称到索引的映射
# 访问数据集中的样本
image, label = dataset[0] # 获取第一个样本及其标签
print(image.shape) # 打印图像张量的形状
print(label) # 打印标签的索引
参数说明:
ImageFolder类的构造函数定义如下:
python
torchvision.datasets.ImageFolder(root, transform=None, target_transform=None, loader=<function default_loader>, is_valid_file=None)
root:数据集的根目录,即包含各个类别子文件夹的目录路径
transform:对加载的图像进行预处理的操作,通常使用torchvision.transforms中的变换组合
target_transform
:对图像对应的标签进行预处理的操作,可选参数。
loader
:用于加载图像文件的函数,默认为 default_loader
,它可以处理常见的图像文件格式(如 JPEG、PNG 等)。
is_valid_file
:一个可选的函数,用于过滤哪些文件应该被加载,只有返回 True
的文件才会被包含在数据集中。
在实际训练模型时,通常会使用torch.utils.data.DataLoader来批量加载数据
python
from torch.utils.data import DataLoader
# 创建数据加载器
dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
# 遍历数据加载器
for images, labels in dataloader:
print(images.shape) # 打印批量图像的形状
print(labels) # 打印批量标签
break # 仅打印一个批次的数据,可根据需要修改
三、迁移学习方法解读
迁移学习(Transfer Learning)是一种机器学习技术,旨在将从一个或多个源任务中学习到的知识应用到不同但相关的目标任务中,从而提升目标任务的学习效果和效率。
原理:
传统的机器学习和深度学习方法通常是针对特定任务进行数据收集、模型训练,要求每个任务都有大量的标注数据。但在实际应用中,很多任务可能缺乏足够的数据。迁移学习的核心思想是利用在大数据集上已经训练好的模型所学到的通用特征表示,将这些知识迁移到目标任务中,因为不同任务的数据往往在底层特征(如边缘、纹理等)上具有一定的相似性。
常见方法:
①基于特征的迁移学习
-
预训练模型特征提取:使用在大规模数据集(如 ImageNet)上预训练好的深度神经网络(如 ResNet、VGG 等)作为特征提取器。具体做法是去掉模型的最后几层(通常是全连接层),保留前面的卷积层,将目标任务的数据输入到预训练模型中,提取中间层的特征,然后将这些特征输入到一个新的简单分类器(如全连接层)中进行训练。
-
示例代码(使用 PyTorch):
python
import torch
import torchvision.models as models
import torch.nn as nn
# 加载预训练的 ResNet18 模型
resnet = models.resnet18(pretrained=True)
# 去掉最后一层全连接层
feature_extractor = nn.Sequential(*list(resnet.children())[:-1])
# 冻结特征提取器的参数
for param in feature_extractor.parameters():
param.requires_grad = False
# 定义新的分类器
num_classes = 10
classifier = nn.Sequential(
nn.Flatten(),
nn.Linear(512, num_classes)
)
# 组合特征提取器和分类器
model = nn.Sequential(
feature_extractor,
classifier
)
注:
1)*
是 Python 中的解包运算符。它将列表中的元素解包,作为独立的参数传递给函数。在这里,nn.Sequential
函数期望接收多个子模块作为参数,而不是一个包含子模块的列表,所以使用 *
解包列表,将列表中的每个子模块作为独立的参数传递给 nn.Sequential
。
2)nn.Sequential
是 PyTorch 中的一个容器类,它可以按顺序将多个子模块组合成一个新的模型。通过 nn.Sequential
,将去掉最后一层的 ResNet
子模块按顺序组合成一个新的特征提取器 feature_extractor
。
②基于模型的迁移学习
-
微调(Fine - Tuning):在预训练模型的基础上,对模型的部分或全部参数进行微调。通常先使用预训练模型的参数初始化模型,然后在目标任务的数据集上继续训练。可以选择只微调模型的最后几层(如全连接层),也可以微调整个模型。微调时学习率通常设置得较小,以避免过度拟合。
-
示例代码(接上上面的代码):
python
import torch.optim as optim
import torch.nn as nn
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)
# 假设已经有了训练数据和标签 train_loader
for epoch in range(10):
running_loss = 0.0
for i, data in enumerate(train_loader, 0):
inputs, labels = data
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
print(f'Epoch {epoch + 1}, Loss: {running_loss / len(train_loader)}')
四、修改全连接层
①加载预训练模型并修改全连接层
python
# 加载预训练的 ResNet18 模型
model = models.resnet18(pretrained=True)
# 获取原始全连接层的输入特征数量
num_ftrs = model.fc.in_features
# 修改全连接层
# 假设目标任务有 10 个类别
num_classes = 10
model.fc = nn.Linear(num_ftrs, num_classes)
model.fc
表示 ResNet18 模型的全连接层。首先通过 model.fc.in_features
获取原始全连接层的
输入特征数量,然后使用 nn.Linear
重新定义全连接层,将输出维度设置为目标任务的类别数。
②冻结部分层的参数
如果希望只训练新的全连接层,而保持预训练模型的其他层参数不变,可以冻结这些层的参数。
python
# 冻结除全连接层外的所有参数
for param in model.parameters():
param.requires_grad = False
for param in model.fc.parameters():
param.requires_grad = True
项目实践
1.导包
python
import os
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
import torch
from torch import nn
import torch.optim as optim
import torchvision
#pip install torchvision
from torchvision import transforms, models, datasets
#https://pytorch.org/docs/stable/torchvision/index.html
import imageio
import time
import warnings
warnings.filterwarnings("ignore")
import random
import sys
import copy
import json
from PIL import Image
2.制作数据源
data_transforms中指定了所有图像预处理操作
ImageFolder假设所有的文件按文件夹保存好,每个文件夹下面存储同一类别的图片,文件夹的名字为分类的名字
python
data_transforms = {
"train":
transforms.Compose([
transforms.Resize([96,96]),
transforms.RandomRotation(45),
transforms.CenterCrop(64),
transforms.RandomHorizontalFlip(p=0.5),
transforms.RandomVerticalFlip(p=0.5),
transforms.ToTensor(),
transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225]) #均值,标准差
]),
"valid":
transforms.Compose([
transforms.Resize([64,64]),
transforms.ToTensor(),
transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])
])
}
transforms.Compose
是一个用于组合多个数据预处理操作的函数,它会按照列表中的顺序依次对输入数据进行处理。
-
transforms.Resize([96,96])
:将输入图像的大小调整为 96x96 像素。这一步可以确保所有输入图像具有相同的尺寸,方便后续处理。 -
transforms.RandomRotation(45)
:随机将图像旋转 -45 到 45 度之间的任意角度。这是一种数据增强技术,可以增加训练数据的多样性,提高模型的泛化能力。 -
transforms.CenterCrop(64)
:从图像的中心裁剪出一个 64x64 像素的区域。这可以去除图像的边缘信息,聚焦于图像的中心部分。 -
transforms.RandomHorizontalFlip(p=0.5)
:以 0.5 的概率对图像进行水平翻转。这也是一种数据增强技术,可以增加训练数据的多样性。 -
transforms.RandomVerticalFlip(p=0.5)
:以 0.5 的概率对图像进行垂直翻转。同样是为了增加训练数据的多样性。 -
transforms.ToTensor()
:将图像数据转换为 PyTorch 的张量(Tensor)格式。同时,将图像的像素值从 [0, 255] 范围缩放到 [0, 1] 范围。 -
transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])
:对图像进行归一化处理。这里使用的均值和标准差是在 ImageNet 数据集上预计算得到的。归一化可以加速模型的训练过程,提高模型的稳定性。
python
data_dir = "./flower_data/"
train_dir = data_dir + "/train"
valid_dir = data_dir + "/valid"
python
batch_size = 128
image_datasets = {x:datasets.ImageFolder(os.path.join(data_dir,x),data_transforms[x]) for x in ["train","valid"]}
dataloaders = {x:torch.utils.data.DataLoader(image_datasets[x],batch_size=batch_size,shuffle=True) for x in ["train","valid"]}
dataset_sizes = {x:len(image_datasets[x]) for x in ["train","valid"]}
class_names = image_datasets["train"].classes
python
# 读取标签对应的实际名字
with open("cat_to_name.json","r") as f:
cat_to_name = json.load(f)
3.加载models中提供的模型,并直接使用训练好的权重当做初始化参数
python
model_name = "resnet"
# 使用训练好的特征
feature_extract = True
选择GPU/CPU
python
train_on_gpu = torch.cuda.is_available()
if not train_on_gpu:
print("CUDA is not available.Training on CPU ...")
else:
print("CUDA is available.Training on GPU...")
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
-
torch.device
是 PyTorch 中用于表示计算设备的类,它可以指定张量和模型应该在哪个设备上进行计算。 -
"cuda:0"
表示第一个可用的 CUDA 设备(即第一块 GPU),"cpu"
表示使用 CPU 进行计算。
python
# 判断模型参数是否需要更新
def set_parameter_requires_grad(model,feature_extracting):
if feature_extracting:
for param in model.parameters():
param.requires_grad = False
python
# 修改模型输出层
def initialize_model(model_name,num_classes,feature_extract,use_pretrained=True):
model_ft = models.resnet18(pretrained=use_pretrained)
set_parameter_requires_grad(model_ft,feature_extract)
num_ftrs = model_ft.fc.in_features
model_ft.fc = nn.Linear(num_ftrs,num_classes)
input_size=64
return model_ft,input_size
python
model_ft,input_size = initialize_model(model_name,102,feature_extract,use_pretrained=True)
# 设置CPU还是GPU用于计算
model_ft = model_ft.to(device)
# 模型保存
filename = "best.pt"
# 是否训练所有层
params_to_update = model_ft.parameters()
print("Params to learn:")
if feature_extract:
params_to_update = []
for name,param in model_ft.named_parameters():
if param.requires_grad == True:
params_to_update.append(param)
print("\t",name)
else:
for name,param in model_ft.named_parameters():
if param.requires_grad == True:
print("\t",name)
-
feature_extract
是一个布尔值,用于决定是否只对模型的部分层进行微调。 -
use_pretrained=True
表示使用预训练的模型权重。当use_pretrained
为True
时,代码会尝试从互联网上下载预训练的模型权重文件。
4.优化器设置
python
# 优化器设置
optimizer_ft = optim.Adam(params_to_update,lr=1e-2)
scheduler = optim.lr_scheduler.StepLR(optimizer_ft,step_size=10,gamma=0.01)
criterion = nn.CrossEntropyLoss()
第二行代码定义了一个学习率调度器,用于在训练过程中动态调整学习率。
-
详细解释:
-
optim.lr_scheduler.StepLR
是 PyTorch 中实现步长学习率调度策略的类。该策略会在每经过一定的训练轮数(step_size
)后,将学习率乘以一个固定的衰减因子(gamma
)。 -
optimizer_ft
是前面定义的 Adam 优化器,学习率调度器会根据这个优化器来调整学习率。 -
step_size=10
表示每经过 10 个训练轮次,学习率会进行一次调整。 -
gamma=0.01
是学习率的衰减因子,每次调整时,学习率会乘以 0.01。例如,如果初始学习率为 0.01,经过 10 个轮次后,学习率会变为 0.01 * 0.01 = 0.0001。
-
5.定义训练模块
python
def train_model(model,dataloaders,criterion,optimizer,num_epochs=25,filename="best.pt"):
since = time.time()
best_acc = 0
model.to(device)
val_acc_history = []
train_acc_history = []
train_losses = []
valid_losses = []
# 学习率
LRs = [optimizer.param_groups[0]["lr"]]
best_model_wts = copy.deepcopy(model.state_dict())
for epoch in range(num_epochs):
print("Epoch {}/{}".format(epoch,num_epochs-1))
print("-"*10)
# 训练和验证
for phase in ["train","valid"]:
if phase == "train":
model.train()
else:
model.eval()
running_loss = 0.0
running_corrects = 0
for inputs,labels in dataloaders[phase]:
inputs = inputs.to(device)
labels = labels.to(device)
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs,labels)
_,preds = torch.max(outputs,1)
# 训练阶段更新权重
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 / len(dataloaders[phase].dataset)
epoch_acc = running_corrects.double()/len(dataloaders[phase].dataset)
time_elapsed = time.time() - since
print("Time elapsed {:.0f}m {:.0f}s".format(time_elapsed//60,time_elapsed%60))
print("{} Loss:{:.4f} Acc:{:.4f}".format(phase,epoch_loss,epoch_acc))
# 得到最好的那次模型
if phase == "valid" and epoch_acc > best_acc:
best_acc = epoch_acc
best_model_wts = copy.deepcopy(model.state_dict())
state = {
"state_dict":model.state_dict(),
"best_acc":best_acc,
"optimizer":optimizer.state_dict()
}
torch.save(state,filename)
if phase=="valid":
val_acc_history.append(epoch_acc)
valid_losses.append(epoch_loss)
if phase=="train":
train_acc_history.append(epoch_acc)
train_losses.append(epoch_loss)
print("Optimizer learning rate: {:.7f}".format(optimizer.param_groups[0]["lr"]))
LRs.append(optimizer.param_groups[0]["lr"])
print()
scheduler.step() #学习率衰减
time_elapsed = time.time()-since
print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60))
print('Best val Acc: {:4f}'.format(best_acc))
# 将训练中最后的一次当做模型最终结果
model.load_state_dict(best_model_wts)
return model,val_acc_history,train_acc_history,valid_losses,train_losses,LRs
datasets.ImageFolder
的作用
torchvision.datasets.ImageFolder
是 PyTorch 提供的一个方便的数据集类,用于从文件夹结构中加载图像数据。它的工作方式如下:
- 文件夹结构要求:数据目录下的每个子文件夹代表一个类别,子文件夹中的所有图像都属于该类别。例如,数据目录结构可能如下:
- 标签生成 :
ImageFolder
会根据子文件夹的名称自动为每个图像分配一个类别标签,标签是从 0 开始的整数索引。例如,class1
的标签为 0,class2
的标签为 1,依此类推。
综上所述,虽然代码中没有显式地指定标签,但 datasets.ImageFolder
会根据文件夹结构自动为图像分配标签,torch.utils.data.DataLoader
会将数据和标签一起批量返回。
6.开始训练模型
python
model_ft,val_acc_history,train_acc_history,valid_losses,train_losses,LRs = train_model(model_ft,dataloaders,criterion,optimizer_ft,num_epochs=20)
训练结果如下:
python
Epoch 0/19
----------
Time elapsed 0m 57s
train Loss:2.3975 Acc:0.4710
Time elapsed 1m 3s
valid Loss:3.4273 Acc:0.3215
Optimizer learning rate: 0.0100000
Epoch 1/19
----------
Time elapsed 1m 59s
train Loss:2.2989 Acc:0.4902
Time elapsed 2m 6s
valid Loss:3.4469 Acc:0.3509
Optimizer learning rate: 0.0100000
Epoch 2/19
----------
Time elapsed 2m 60s
train Loss:2.2143 Acc:0.5176
Time elapsed 3m 7s
valid Loss:3.9898 Acc:0.3227
Optimizer learning rate: 0.0100000
Epoch 3/19
----------
Time elapsed 4m 3s
train Loss:2.2419 Acc:0.5266
Time elapsed 4m 9s
valid Loss:4.2700 Acc:0.2983
Optimizer learning rate: 0.0100000
Epoch 4/19
----------
Time elapsed 5m 3s
train Loss:2.2196 Acc:0.5325
Time elapsed 5m 9s
valid Loss:4.2233 Acc:0.3142
Optimizer learning rate: 0.0100000
Epoch 5/19
----------
Time elapsed 6m 2s
train Loss:2.1590 Acc:0.5453
Time elapsed 6m 9s
valid Loss:4.2198 Acc:0.3313
Optimizer learning rate: 0.0100000
Epoch 6/19
----------
Time elapsed 7m 3s
train Loss:2.3123 Acc:0.5432
Time elapsed 7m 10s
valid Loss:4.1263 Acc:0.3325
Optimizer learning rate: 0.0100000
Epoch 7/19
----------
Time elapsed 8m 5s
train Loss:2.1808 Acc:0.5476
Time elapsed 8m 11s
valid Loss:4.1894 Acc:0.3276
Optimizer learning rate: 0.0100000
Epoch 8/19
----------
Time elapsed 9m 7s
train Loss:2.2034 Acc:0.5553
Time elapsed 9m 14s
valid Loss:4.1895 Acc:0.3496
Optimizer learning rate: 0.0100000
Epoch 9/19
----------
Time elapsed 10m 9s
train Loss:2.2178 Acc:0.5592
Time elapsed 10m 15s
valid Loss:4.4034 Acc:0.3166
Optimizer learning rate: 0.0100000
Epoch 10/19
----------
Time elapsed 11m 9s
train Loss:2.0092 Acc:0.5920
Time elapsed 11m 15s
valid Loss:4.1064 Acc:0.3399
Optimizer learning rate: 0.0001000
Epoch 11/19
----------
Time elapsed 12m 8s
train Loss:1.8037 Acc:0.6145
Time elapsed 12m 15s
valid Loss:3.9882 Acc:0.3594
Optimizer learning rate: 0.0001000
Epoch 12/19
----------
Time elapsed 13m 8s
train Loss:1.7063 Acc:0.6209
Time elapsed 13m 14s
valid Loss:3.8820 Acc:0.3619
Optimizer learning rate: 0.0001000
Epoch 13/19
----------
Time elapsed 14m 8s
train Loss:1.6577 Acc:0.6302
Time elapsed 14m 14s
valid Loss:3.8769 Acc:0.3631
Optimizer learning rate: 0.0001000
Epoch 14/19
----------
Time elapsed 15m 11s
train Loss:1.6208 Acc:0.6378
Time elapsed 15m 18s
valid Loss:3.9119 Acc:0.3545
Optimizer learning rate: 0.0001000
Epoch 15/19
----------
Time elapsed 16m 12s
train Loss:1.5684 Acc:0.6467
Time elapsed 16m 18s
valid Loss:3.8372 Acc:0.3545
Optimizer learning rate: 0.0001000
Epoch 16/19
----------
Time elapsed 17m 11s
train Loss:1.6428 Acc:0.6346
Time elapsed 17m 18s
valid Loss:3.9027 Acc:0.3716
Optimizer learning rate: 0.0001000
Epoch 17/19
----------
Time elapsed 18m 11s
train Loss:1.5844 Acc:0.6427
Time elapsed 18m 17s
valid Loss:3.8490 Acc:0.3729
Optimizer learning rate: 0.0001000
Epoch 18/19
----------
Time elapsed 19m 11s
train Loss:1.5653 Acc:0.6505
Time elapsed 19m 17s
valid Loss:3.7844 Acc:0.3692
Optimizer learning rate: 0.0001000
Epoch 19/19
----------
Time elapsed 20m 13s
train Loss:1.5774 Acc:0.6404
Time elapsed 20m 19s
valid Loss:3.7797 Acc:0.3692
Optimizer learning rate: 0.0001000
Training complete in 20m 19s
Best val Acc: 0.372861
7.再训练所有层
python
for param in model_ft.parameters():
param.requires_grad = True
# 再继续训练所有的参数,学习率最好调小一点
optimizer = optim.Adam(model_ft.parameters(),lr=1e-3)
scheduler = optim.lr_scheduler.StepLR(optimizer_ft,step_size=7,gamma=0.1)
# 损失函数
criterion = nn.CrossEntropyLoss()
python
# 加载之前训练好的权重参数
checkpoint = torch.load(filename)
best_acc = checkpoint['best_acc']
model_ft.load_state_dict(checkpoint['state_dict'])
python
model_ft, val_acc_history, train_acc_history, valid_losses, train_losses, LRs = train_model(model_ft, dataloaders, criterion, optimizer, num_epochs=10)
训练数据如下:
python
Epoch 0/9
----------
Time elapsed 1m 52s
train Loss:2.6279 Acc:0.4615
Time elapsed 1m 59s
valid Loss:3.2914 Acc:0.4034
Optimizer learning rate: 0.0010000
Epoch 1/9
----------
Time elapsed 3m 51s
train Loss:1.1428 Acc:0.6879
Time elapsed 3m 58s
valid Loss:1.8249 Acc:0.5342
Optimizer learning rate: 0.0010000
Epoch 2/9
----------
Time elapsed 5m 47s
train Loss:0.9859 Acc:0.7213
Time elapsed 5m 53s
valid Loss:1.6096 Acc:0.5758
Optimizer learning rate: 0.0010000
Epoch 3/9
----------
Time elapsed 7m 42s
train Loss:0.7332 Acc:0.7915
Time elapsed 7m 48s
valid Loss:1.7855 Acc:0.5758
Optimizer learning rate: 0.0010000
Epoch 4/9
----------
Time elapsed 9m 39s
train Loss:0.7189 Acc:0.7999
Time elapsed 9m 45s
valid Loss:1.5474 Acc:0.6015
Optimizer learning rate: 0.0010000
Epoch 5/9
----------
Time elapsed 11m 36s
train Loss:0.5291 Acc:0.8371
Time elapsed 11m 42s
valid Loss:1.5273 Acc:0.6540
Optimizer learning rate: 0.0010000
Epoch 6/9
----------
Time elapsed 13m 34s
train Loss:0.5759 Acc:0.8385
Time elapsed 13m 40s
valid Loss:1.6264 Acc:0.5978
Optimizer learning rate: 0.0010000
Epoch 7/9
----------
Time elapsed 15m 32s
train Loss:0.4117 Acc:0.8716
Time elapsed 15m 38s
valid Loss:1.6492 Acc:0.6100
Optimizer learning rate: 0.0010000
Epoch 8/9
----------
Time elapsed 17m 29s
train Loss:0.3964 Acc:0.8770
Time elapsed 17m 36s
valid Loss:1.5607 Acc:0.6540
Optimizer learning rate: 0.0010000
Epoch 9/9
----------
Time elapsed 19m 24s
train Loss:0.4744 Acc:0.8651
Time elapsed 19m 31s
valid Loss:2.1448 Acc:0.6015
Optimizer learning rate: 0.0010000
Training complete in 19m 31s
Best val Acc: 0.654034
8.加载训练好的模型并进行测试
①加载训练好的模型
python
model_ft,input_size = initialize_model(model_name,102,feature_extract,use_pretrained=True)
model_ft=model_ft.to(device)
# 保存文件的名字
filename="best.pt"
# 加载模型
checkpoint = torch.load(filename)
best_acc=checkpoint["best_acc"]
model_ft.load_state_dict(checkpoint["state_dict"])
②获取一个测试数据
python
dataiter = iter(dataloaders["valid"])
images,labels = dataiter.next()
model_ft.eval()
if train_on_gpu:
output = model_ft(images.cuda())
else:
output = model_ft(images)
③得到概率最大的标签
python
_,preds_tensor = torch.max(output,1)
preds = np.squeeze(preds_tensor.numpy()) if not train_on_gpu else np.squeeze(preds_tensor.cpu().numpy())
④展示数据
python
def im_convert(tensor):
image = tensor.to("cpu").clone().detach()
image = image.numpy().squeeze()
image = image.transpose(1,2,0)
image = image * np.array((0.229,0.224,0.225)) + np.array((0.485,0.456,0.406))
image = image.clip(0,1)
return image
a)
-
tensor.to("cpu")
:将输入的张量tensor
从当前设备(可能是 GPU)移动到 CPU 上。因为后续操作如转换为 NumPy 数组等在 CPU 上进行更为方便。 -
clone()
:创建张量的一个副本。这是为了避免在后续操作中修改原始张量,确保数据的独立性。 -
detach()
:将张量从计算图中分离出来,切断其与计算图的连接。这样做可以防止在后续操作中对原始张量进行反向传播,节省计算资源。
b)
-
image.numpy()
:将 PyTorch 张量转换为 NumPy 数组,以便使用 NumPy 的相关操作。 -
squeeze()
:去除数组中维度大小为 1 的维度。例如,如果输入的张量形状是(1, 3, 224, 224)
,经过squeeze()
后,会将形状变为(3, 224, 224)
,这样更符合图像数据的常见表示形式。
c)
transpose(1, 2, 0)
:对数组的维度进行转置操作。在 PyTorch 中,图像张量的形状通常是(channels, height, width)
,而在 NumPy 中,图像数组的常见表示形式是(height, width, channels)
。因此,通过transpose(1, 2, 0)
可以将维度顺序从(channels, height, width)
转换为(height, width, channels)
。
d)
-
这一步是对图像数据进行反归一化操作。在图像预处理过程中,通常会对图像进行归一化,即减去均值并除以标准差。这里进行反归一化,将图像数据还原到原始的像素值范围。
-
np.array((0.229, 0.224, 0.225))
是预处理时使用的标准差,np.array((0.485, 0.456, 0.406))
是预处理时使用的均值。通过乘以标准差并加上均值,实现反归一化。
e)
clip(0, 1)
:将数组中的所有元素限制在 0 到 1 的范围内。由于反归一化过程可能会导致某些像素值超出 0 到 1 的范围,使用clip
函数可以确保所有像素值都在有效的范围内,避免出现异常的像素值。
python
fig=plt.figure(figsize=(20, 20))
columns =4
rows = 2
for idx in range (columns*rows):
ax = fig.add_subplot(rows, columns, idx+1, xticks=[], yticks=[])
plt.imshow(im_convert(images[idx]))
ax.set_title("{} ({})".format(cat_to_name[str(preds[idx])], cat_to_name[str(labels[idx].item())]),
color=("green" if cat_to_name[str(preds[idx])]==cat_to_name[str(labels[idx].item())] else "red"))
plt.show()
a)
-
plt.figure(figsize=(20, 20))
:使用matplotlib
库创建一个新的图形对象fig
,并设置图形的大小为 20x20 英寸。 -
columns = 4
和rows = 2
:分别定义了子图网格的列数和行数,这里表示要创建一个 2 行 4 列的子图布局,总共可以显示 8 个子图。
b)
-
fig.add_subplot(rows, columns, idx + 1)
:在图形fig
中添加一个子图,rows
和columns
分别指定子图网格的行数和列数,idx + 1
表示当前子图在网格中的位置(从 1 开始计数)。 -
xticks=[]
和yticks=[]
:设置子图的 x 轴和 y 轴刻度为空,即不显示坐标轴刻度,使图像更加简洁。
c)
-
cat_to_name
:是一个字典,用于将类别索引映射到对应的类别名称。 -
preds[idx]
:是模型对第idx
张图像的预测类别索引,labels[idx].item()
是第idx
张图像的真实类别索引。 -
cat_to_name[str(preds[idx])]
和cat_to_name[str(labels[idx].item())]
:分别获取预测类别和真实类别的名称。 -
ax.set_title()
:为当前子图设置标题,标题格式为预测类别名称 (真实类别名称)
。 -
color=("green" if cat_to_name[str(preds[idx])] == cat_to_name[str(labels[idx].item())] else "red")
:根据预测类别和真实类别是否一致,设置标题的颜色。如果一致,标题颜色为绿色;否则,标题颜色为红色。
展示结果如下:
至此结束。