IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 人工智能 -> Pytorch基础知识(6)图像多类别分类 -> 正文阅读

[人工智能]Pytorch基础知识(6)图像多类别分类

多类别图像分类一直是深度学习研究的核心。多类图像分类的目标是从一组固定的类别中为图像指定一个标签。
在本文中,我们将学习如何创建一个算法来识别STL-10数据集中的10类对象。我们将使用在ImageNet数据集曾经表现SOTA的模型并且在STL-10数据集上微调。
本文讲涉及以下内容:

  • 导入与与处理数据
  • 构建模型
  • 定义损失函数
  • 定义优化器
  • 迁移学习
  • 模型部署

导入与数据处理

我们将使用PyTorch的torchvision包提供的STL-10数据集。类别分别为飞机(0)、鸟(1)、轿车(2)、猫(3)、鹿(4)、狗(5)、马(6)、猴子(7)、船(8)和卡车(9)。图像为RGB颜色,尺寸为96*96。该数据集包含5000张训练图像和8000张测试图像。在训练和测试数据集中,每个类分别有500和800张图像。

#1. 导入训练数据集
from torchvision import datasets
import torchvision.transforms as transforms
import os

path2data = "./data"
if not os.path.exists(path2data):
	os.mkdirs(path2data)
data_transformer = transforms.Compose([transforms.ToTensor()])
train_ds = datasets.STL10(path2data, split="train", download=True, transform=data_transformer)
print(train_ds.data.shape)
# (5000, 3, 96, 96)

#2.获取train_ds中每类的图片数目
import collections
y_train=[y for _,y in train_ds]
counter_train = collections.Counter(y_train)
print(counter_train)
# Counter({1:500, 5:500, 6:500, 3:500, 9:500, 7:500, 4:500, 8:500, 0:500, 2:500})

#3.导入测试集数据
test0_ds = datasets.STL10(path2data, split="test", download=True, transform=data_transformer)
print(test0_ds.data.shape)
# (8000, 3, 96, 96)

#4.将test0_ds分为两个部分
from sklearn.model_selection import StratifieldShuffleSplit
sss = StratifieldShuffleSplit(n_splits=1, test_size=0.2, random_state=0)
indices=list(range(len(test0_ds)))
y_test0 = [y for _,y in test0_ds]
for test_index, val_index in sss.split(indices, y_test0):
	print("test:", test_index, "val:", val_index)
	print(len(val_index), len(test_index))
	# test:[2096 4231 2667 ... 3304 3201] val:[6843 2212 1123 ... 1198 4598]
	# 1600 6400
#5.将test0_ds划分为验证集和测试集
	from torch.utils.data import Subset
	val_ds = Subset(test0_ds, val_index)
	test_ds = Subset(test0_ds, test_index)
#6. 分别获取val_ds和test_ds中每类的图片数目
import collections
import numpy as np
y_test = [ y for _,y in test_ds]
y_val = [y for _,y in val_ds]
counter_test = collections.Counter(y_test)
counter_val = collections.Counter(y_val)
print(counter_test)
print(counter_val)

# Counter({6: 640, 0: 640, 4: 640, 5: 640, 9: 640, 2: 640, 3: 640, 1:640, 7: 640, 8: 640})
# Counter({2: 160, 8: 160, 3: 160, 6: 160, 4: 160, 1: 160, 5: 160, 9:160, 0: 160, 7: 160})
#7.显示train_ds中的一些样本
from torchvision import utils
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(0)

# 定义函数
def show(img, y=None, color=True):
	npimg = img.numpy()
	npimg_tr = np.transpose(npimg, (1, 2, 0))
	plt.imshow(npimg_tr)
	if y is not None:
		plt.title("label: " + str(y))
# 获得随机样本
grid_size=4
rnd_inds = np.random.randint(0, len(train_ds),grid_size)
print("image indices:", rnd_inds)
# image indices: [2743 2678  3245 1987]
# 为样本创建grid
x_grid = [train_ds[i][0] for i in rnd_inds]
y_grid = [train_ds[i][1] for i in rnd_inds]

x_grid = utils.make_grid(x_grid, nrow=4, padding=1)
print(x_grid.shape)
# torch.Size([3, 100, 394])
# 调用函数
plt.figure(figsize=(10, 10))
show(x_grid,y_grid)
#8. 显示val_ds数据集上的样本
np.random.seed(0)
grid_size=4
rnd_inds=np.random.randint(0,len(val_ds),grid_size)
print("image indices:",rnd_inds)
x_grid=[val_ds[i][0] for i in rnd_inds]
y_grid=[val_ds[i][1] for i in rnd_inds]
x_grid=utils.make_grid(x_grid, nrow=4, padding=2)
print(x_grid.shape)
plt.figure(figsize=(10,10))
show(x_grid,y_grid)
# image indices: [ 684 559 1216 835]
# torch.Size([3, 100, 394])

#9.计算train_ds数据集的均值与标准差
import numpy as np
meanRGB = [np.mean(x.numpy(), axis=(1,2)) for x,_ in train_ds]
stdRGB = [np.std(x.numpy(),axis=(1, 2) for x,_ in train_ds]
meanR = np.mean([m[0] for m in meanRGB])
meanG = np.mean([m[1] for m in meanRGB])
meanB = np.mean([m[2] for m in meanRGB])

stdR = np.mean([s[0] for s in stdRGB])
stdG = np.mean(s[1] for s in stdRGB])
stdB = np.mean(s[2] for s in stdRGB])
print(meanR, meanG, meanB)
print(stdR, stdG, stdB)
# 0.4467106 0.43980986 0.40664646
# 0.22414584 0.22148906 0.22389975
#10.分别定义train_ds和test0_ds的数据变换
train_transformer = 
transforms.Compose([transforms.RandomHorizontalFlip(p=0.5),
					transforms.RandomVerticalFlip(p=0.5),
					transforms.ToTensor(),
					transforms.Normalize([meanR, meanG, meanB], [stdR, stdG, stdB],
										])
#11 分别更新train_ds 和 test0_ds的transform函数
train_ds.transform = train_transformer
test0_ds.transform = test0_transformer

#12 显示变换后的train_ds中的样本
import torch
np.random.seed(0)
torch.manual_seed(0)

grid_size=4
rnd_inds = np.random.randint(0, len(train_ds), grid_size)
print("image indices:", rnd_inds)

x_grid = [train_ds[i][0] for i in rnd_inds]
y_grid = [train_ds[i][1] for i in rnd_inds]
x_grid = utils.make_grid(x_grid, nrow=4, padding=2)
print(x_grid.shape)
plt.figure(figsize=(10,10))
show(x_grid, y_grid)
#image indices:[2743 2789 1456 1243]
#torch.Size([3, 200, 394])
#13. 创建数据导入器
from torch.utils.data import DataLoader
train_dl = DataLoader(train_ds, batch_size=32, shuffle=True)
val_dl = DataLoader(val_ds, batch_size=64, shuffle=False)

#14 获取train_dl上的一个batch数据
for x, y in train_dl:
	print(x.shape)
	print(y.shape)
	break
# torch.Size([32,3,96,96])
# torch.Size([32])

#15 获取val_dl上的一个batch数据
for x, y in val_dl:
	print(x.shape)
	print(y.shape)
	break
# torch.Size([64,3,96,96])
# torch.Size([64])

代码解析:
在步骤1中,我们从torchvision.datasets加载训练数据集。训练数据集有5000张3x96x96大小的图像。变换函数转换PIL图像为张量并且归一化像素值在[0,1]范围。确保第一次运行代码时,download= True。它将自动下载数据集并将其存储在指定文件夹中。下载一次后,可以设置download= False。在步骤2中,我们计算了每个类的图像数量。在每个类中有相同数量的图像(一个平衡的数据集)。
在步骤3中,我们加载了原始测试数据集,并将其命名为test0_ds。test0_ds中有8000张图像。因为没有正式的验证数据集,所以我们将test0_ds索引分成两个组,val_index和test_index。在步骤4中,我们使用了来自sklearn.model_selection的StratifiedShuffleSplit。这个函数返回分层的随机索引。在第5步中,我们使用val_index和test_index创建两个数据集:val_ds和test_ds。val_ds中有1600张图象,test_ds中有6400张图象。我们将在训练期间使用val_ds对模型进行评估。
因为我们使用了分层随机索引,所以我们可以在步骤6中看到val_ds和test_ds都是平衡的。在步骤7和步骤8中,我们显示了来自train_ds和val_ds的示例图像。我们使用np.random.seed(0)固定了随机种子,以便每次运行中得到相同的随机索引。如果您想在每次运行中看到不同的随机图像,请注释掉这一行。在第9步中,我们计算了train_ds中像素值的均值和标准差。我们将使用这些值对数据进行归一化。
在步骤10中,我们定义了图像变换。对于train_ds,我们添加了RandomHorizontalFlip和RandomVerticalFlip来增加训练数据集。此外,我们通过使用transforms.Normalize实现零均值单位方差的归一化。这个函数的参数是第9步中计算的每个通道的平均值和标准差。对于test0_ds,我们只添加了标准化函数,因为我们不需要对验证和测试数据集进行数据扩充。
在第11步中,我们更新了train_ds和test0_ds的转换函数。注意,当我们更新test0_ds时。转换函数val_ds和test_ds都将被更新,因为它们是test0_ds的子集。在第12步中,我们使用更新的转换显示了来自train_ds的示例图像。注意,由于归一化,转换图像的像素值在显示时被剪辑。
在第13步中,我们创建了PyTorch数据加载器train_dl和val_dl,以便能够自动从每个数据集获取批量数据。在步骤14和步骤15中,我们使用数据加载器提取一批数据。正如所见,提取的数据大小取决于批处理的大小。
在torchvision包中还包含其他多分类数据集,例如,FashionMNIST。要使用该数据集,使用以下代码:

from torchvision import datasets
fashion_train = datasets.FashionMNIST(path2data, train_true, download=True)

构建模型

接下来,我们将为我们的多分类任务构建一个模型。我们将使用torchvision内置的模型,而不是构建自定义模型。torchvision包为图像多分类提供了多种深度学习模型的实现。这些包括AlexNet,VGG,ResNet,SqueezeNet,DenseNet,Inception,GoogleNet,ShuffleNet。这些模型是在ImageNet数据集上训练的结果。ImageNet数据集拥有1000个类,共计1400万张图像。我们既可以使用随机初始化权重也可以使用预先训练的权重。

# 1.采用随机初始化权重的方法导入torchvision.models中的resnet18模型
from torchvision import models
import torch
model_resnet18 = models.resnet18(pretrained=False)
#2. 打印模型结构
print(model_resnet18)
# ResNet(
# (conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2),padding=(3, 3), bias=False)
# (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True,track_running_stats=True)
# (relu): ReLU(inplace)
# (maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1,dilation=1, ceil_mode=False)
# ...
#3.修改输出层为10分类
from torch import nn
num_classes = 10
num_ftrs = model_resnet18.fc.in_features
model_resnet18.fc = nn.Linear(num_ftrs, num_classes)
device = torch.device("cuda:0")
model_resnet18.to(device)
#4.获取模型summary
from torchsummary import summary
summary(model_resnet18, input_size=(3, 224, 224), device=device.type)

# ----------------------------------------------------------------
# Layer (type)         Output Shape          Param #
# ================================================================
# Conv2d-1         [-1, 64, 112, 112]        9,408
# BatchNorm2d-2    [-1, 64, 112, 112]         128
# ReLU-3           [-1, 64, 112, 112]          0
# MaxPool2d-4      [-1, 64, 56, 56]            0
# Conv2d-5         [-1, 64, 56, 56]         36,864
# BatchNorm2d-6    [-1, 64, 56, 56]           128
# ReLU-7           [-1, 64, 56, 56]            0
# Conv2d-8         [-1, 64, 56, 56]         36,864
# BatchNorm2d-9    [-1, 64, 56, 56]           128
# ...
# ...
# ================================================================
# Total params: 11,181,642
# Trainable params: 11,181,642
# Non-trainable params: 0
# ----------------------------------------------------------------
# Input size (MB): 0.57
# Forward/backward pass size (MB): 62.79
# Params size (MB): 42.65
# Estimated Total Size (MB): 106.01
# ----------------------------------------------------------------

#5. 可视化CNN层
for w in model_resnet18.parameters():
	w = w.data.cpu()
	print(w.shape)
	# 权重归一化
	min_w = torch.min(w)
	w1 = (-1/(2*min_w))*w + 0.5
	print(torch.min(w1).item(), torch.max(w1).item())
	# 显示
	grid_size = len(w1)
	x_grid = [w1[i] for i in range(grid_size)]
	x_grid = utils.make_grid(x_grid, nrow=8, padding=1)
	print(x_grid.shape)
	plt.figure(figsize=(10, 10))
	show(x_grid)
	break
#6. 采用预训练权重的方法导入torchvision.models中的resnet18模型
from torchvision import models
import torch
resnet18_pretrained = models.resnet18(pretrained=True)
num_classes=10
num_ftrs = resnet18_pretrained.fc.in_features
resnet18_pretrained.fc = nn.Linear(num_ftrs, num_classes)
device = torch.device("cuda:0")
resnet18_pretrained.to(device)

#7. 可视化CNN层
for w in resnet18_pretrained.parameters():
	w = w.data.cpu()
	print(w.shape)
	# 权重归一化
	min_w = torch.min(w)
	w1 = (-1/(2*min_w))*w + 0.5
	print(torch.min(w1).item(), torch.max(w1).item())
	# 显示
	grid_size = len(w1)
	x_grid = [w1[i] for i in range(grid_size)]
	x_grid = utils.make_grid(x_grid, nrow=8, padding=1)
	print(x_grid.shape)
	plt.figure(figsize=(10, 10))
	show(x_grid)
	break

代码解析:
在步骤1中,我们加载了resnet18模型。这将自动下载模型并将其存储在本地以备将来使用。加载模型时的一个重要参数是预先训练。如果设置为False,模型权重将被随机初始化。
在步骤2中,我们打印了模型。最后一层是一个线性层,有1000个输出。resnet18模型是为拥有1000个类的ImageNet数据集开发的。在步骤3中,我们将最后一层更改为num_classes = 10的分类输出。当我们得到模型summary时,可以在第4步中看到这个更改。即使原始图像的大小是9696,我们需要调整它们的大小224224,与resnet18模型预训练时的尺寸相同。
在步骤5中,我们可视化了第一层的卷积滤波器。由于滤波器是随机初始化的,它们显示的是随机初始化滤波器的结果。
在步骤6中,我们用pretraining = True加载了resnet18模型。这将使用ImageNet数据集上预先训练的权重加载模型。在第7步中,我们显示了第一层的滤波器。正如所见,滤波器代表一些模式和结构,如边缘。
此外,你也可以使用torchvision.models包提供的更多模型。例如vgg19,代码如下:

num_classes=10
vgg19 = models.vgg19(pretrained=True)
# change the last layer
vgg19.classifier[6] = nn.Linear(4096, num_classes)

定义损失函数

定义损失函数的目标是优化模型。分类任务的标准损失函数是交叉熵损失或log损失。但是,在定义损失函数时,我们需要考虑模型输出的数量及其激活函数。对于多类分类任务,输出的数量被设置为类的数量。输出激活函数然后决定损失函数。
下表为不同激活函数对应的损失函数:

在构建模型部分中定义的resnet18模型,因为是没有激活函数的线性输出,因此,我们选择nn.CrossEntropyLoss 作为损失函数的。这个损失函数将nn.LogSoftmax()和nn.NLLLoss()合并在一个类中。

# 使用随机值来测试定义的损失函数
#1. 定义损失函数
loss_func = nn.CrossEntropyLoss(reduction="sum")
#2. 使用随机值来测试定义的损失函数
torch.manual_seed(0)
n,c=4,5
y = torch.randn(n, c, requires_grad=True)
print(y.shape)

loss_func = nn.CrossEntropyLoss(reduction="sum")
target = torch.randint(c, size=(n,))
print(target.shape)

loss = loss_func(y, target)
print(loss.item())
# torch.Size([4, 5])
# torch.Size([4])
# 7.312585830688477
#3. 计算梯度
loss.backup()
print(y.data)
# tensor([[-1.1258, -1.1524, -0.2506, -0.4339, 0.5988],
#        [-1.5551, -0.3414, 1.8530, 0.4681, -0.1577],
#        [ 1.4437, 0.2660, 1.3894, 1.5863, 0.9463],
#        [-0.8437, 0.9318, 1.2590, 2.0050, 0.0537]])

代码解析:
在步骤1中,我们定义了损失函数。resnet18模型使用线性输出。因此,我们使用nn.CrossEntropyLoss 作为损失函数。定义损失函数时要注意的点是reduction参数,它指定了应用于输出的reduction方式。有三个选项可供选择:none, sum, and mean。我们选择reduction= sum,将输出损失求和。由于我们将分批处理数据,这将返回每批数据的损失值之和。
在步骤2中,我们以n=4个样本,c=5个类别为例计算损失。
在步骤3中,我们为步骤2中的例子计算了梯度。稍后,我们将使用反向传播算法来计算相对于模型参数的损失梯度。

定义优化器

torch.optim包提供了通用优化器的实现。优化器将保存当前状态,并根据计算出的梯度更新参数。对于分类任务,随机梯度下降(SGD)和Adam优化器是常用的优化器。为模型选择一个优化器被认为是一个超参数。通常需要尝试多个优化器来找到性能最好的一个。Adam optimizer在速度和准确性方面通常优于SGD,所以我们在这里选择Adam optimizer。
torch.optim包中另一个不错的工具是学习率策略。学习策略是在训练过程中自动调整学习率以提高模型性能的有效工具。
在本教程中,我们将学习如何定义优化器、获取当前学习速率以及定义学习策略。

# 我们将定义优化器以及学习策略
#1. 定义学习率为1e-4的Adam优化器
from torch import optim
opt=optim.Adam(model_resnet18.parameters(), lr=1e-4)
#2. 获取当前的学习率
def get_lr(opt):
	for param_group in opt.param_groups:
		return param_group["lr"]
current_lr = get_lr(opt)
print("current lr={}".format(current_lr))
# current lr=0.0001
# 3. 使用CosineAnnealingLR学习策略
from torch.optim.lr_scheduler import CosineAnnelingLR
lr_scheduler = CosineAnnelingLR(opt, T_max=2, eta_min=1e-5)
#4. 学习策略是如何工作的
for i in range(10):
	lr_scheduler.step()
	print("epoch %s, lr: %.1e" %(i, get_lr(opt)))
# epoch 0, lr: 1.0e-04
# epoch 1, lr: 5.5e-05
# epoch 2, lr: 1.0e-05
# epoch 3, lr: 5.5e-05
# epoch 4, lr: 1.0e-04
# epoch 5, lr: 5.5e-05
# epoch 6, lr: 1.0e-05
# epoch 7, lr: 5.5e-05
# epoch 8, lr: 1.0e-04
# epoch 9, lr: 5.5e-05

代码解析:
在步骤1中,我们定义了Adam优化器。如前所述,Adam优化器在分类任务方面的性能在大多数情况下优于其他优化器。然而,这不应被视为一项规则。考虑将优化器作为超参数,并尝试几种不同的优化器,看看哪一种性能更好。优化器类的重要参数是模型参数和学习率。model_resnet18.parameters()返回传递给优化器的模型参数的迭代器。学习速率将决定更新的大小。我们为所有层设置一个学习速率。在步骤2中,我们开发了一个返回当前学习率值的辅助函数。
在步骤3中,我们使用了来自torch.optim.lr_scheduler包中的CosineAnnealingLR方法。该优化策略基于余弦退火策略调整学习率。学习速率从优化器中的设定值lr=1e-4开始。然后逐渐向eta_min=1e-5递减,在2*T_max=4次迭代后返回到原来的设定值lr=1e-4。
下面的图描绘了10个epochs的学习率值:
在这里插入图片描述
稍后,我们将看到如何在训练中调用学习率策略。

从头开始训练与迁移学习

到目前为止,我们已经创建了数据集并定义了模型、损失函数和优化器。在本教程中,我们将实现训练和验证脚本。我们首先用随机初始化的权值训练模型。然后,我们将使用预先训练的权重训练模型。这种方法也被称为迁移学习。在迁移学习中,我们尝试将从一个问题中学到的知识(权重)用于其他类似的问题。训练和验证脚本可能很长且重复。为了更好的代码可读性和避免代码重复,我们将首先构建几个辅助函数。

# 从头开始训练与迁移学习
#1. 首先实现每批次正确预测数目函数
def metrics_batch(output, target):
	pred = output.argmax(dim=1, keepdim=True)
	corrects = pred.eq(target.view_as(pred)).sum().item()
	return corrects
#2. 然后实现每批次的损失值函数
def loss_batch(loss_func, output, target, opt=True):
	loss = loss_func(output, target)
	metric_b = metrics_batch(output, target)
	if opt is not None:
		opt.zero_grad()
		loss.backward()
		opt.step()
	return loss.item(), metric_b
#3. 下一步,实现计算一个epoch下损失值和性能度量的函数
def loss_epoch(model, loss_func, dataset_dl, sanity_check=False, opt=None):
	running_loss = 0.0
	running_metric = 0.0
	len_data = len(dataset_dl.dataset)
	for xb, yb in dataset_dl:
		xb = xb.to(device)
		yb = yb.to(device)
		output = model(xb)
		loss_b, metric_b = loss_batch(loss_func, output, yb, opt)
		running_loss += loss_b
		if metric_b is not None:
			running_metric += metric_b
		if sanity_check is True:
			break
	loss = running_loss/float(len_data)
	metric = running_metric/float(len_data)
	return loss, metric
#4. 实现train_val函数
def train_val(model, params):
	num_epochs = params["num_epochs"]
	loss_func = params["loss_func"]
	opt = params["optimizer"]
	train_dl = params["train_dl"]
	val_dl = params["val_dl"]
	sanity_check = params["sanity_check"]
	lr_scheduler = params["lr_scheduler"]
	path2weights = params["path2weights"]
	# 两个字典
	loss_history = {"train":[], "val":[]}
	metric_history = {"train":[], "val":[]}
	# 定义变量来存储最好的模型参数
	best_model_wts = copy.deepcopy(model.state_dict())
	best_loss = float("inf")
	for epoch in range(num_epochs):
		current_lr = get_lr(opt)
		print("Epoch {}/{}, current lr={}".format(epoch, num_epochs-1, current_lr))
		model.train()
		train_loss,train_metric=loss_epoch(model, loss_func, train_dl, sanity_check, opt)
		loss_history["train"].append(train_loss)
		metric_history["train"].append(train_metric)
		# 模型评估
		model.eval()
		with torch.no_grad():
			val_loss, val_metric = loss_epoch(model, loss_func, val_dl, sanity_check)
			loss_history["val"].append(val_loss)
			metric_history["val"].append(val_metric)
			if val_loss < best_loss:
				best_loss = val_loss
				best_model_wts = copy.deepcopy(model.state_dict())
				torch.save(model.state_dict(), path2weights)
				print("Copied best model weights")
			lr_scheduler.step()
			print("train loss:%.6f, dev loss: %.6f, accuracy:%.2f"%(train_loss, val_loss, 100*val_metric)
	# 返回最好模型
	model.load_state_dict(best_model_wts)
	return model, loss_history, metric_history
#5. 通过调用train_val函数训练模型
import copy
loss_func = nn.CrossEntropyLoss(reduction="sum")
opt = optim.Adam(model_resnet18.parameters(), lr=1e-4)
lr_scheduler = CosineAnnelingLR(opt, T_max=5, eta_min=1e-6)
# 设置训练参数,调用train_val函数
os.makedirs("./model", exist_ok=True)
params_train = {
	"num_epochs":100,
	"optimizer":opt,
	"loss_func":loss_func,
	"train_dl":train_dl,
	"val_dl":val_dl,
	"sanity_check":False,
	"lr_scheduler":lr_scheduler,
	"path2weights":"./models/resnet18.pt",
}
model_resnet18, loss_hist, metric_hist = train_val(model_resnet18, params_train)
#6 下面,画出训练期间的损失和准确率
num_epochs = params_train["num_epochs"]
plt.title("Train-Val Loss")
plt.plot(range(1, num_epochs+1), loss_hist["train"], label="train")
plt.plot(range(1, num_epochs+1), loss_hist["val"], label="val")
plt.ylabel("Loss")
plt.xlabel("Training Epochs")
plt.legend()
plt.show()
# 画准确率
plt.title("Train-Val Accuracy")
plt.plot(range(1, num_epochs+1), metric_hist["train"], label="train")
plt.plot(range(1, num_epochs+1), metric_hist["val"], label="val")
plt.ylabel("Accuracy")
plt.xlabel("Training Epochs")
plt.legend()
plt.show()
#7, 使用预训练权重微调模型
import copy
loss_func = nn.CrossEntropyLoss(reduction="sum")
opt = optim.Adam(model_resnet18.parameters(), lr=1e-4)
lr_scheduler = CosineAnnelingLR(opt, T_max=5, eta_min=1e-6)
params_train = {
	"num_epochs":100,
	"optimizer":opt,
	"loss_func":loss_func,
	"train_dl":train_dl,
	"val_dl":val_dl,
	"sanity_check":False,
	"lr_scheduler":lr_scheduler,
	"path2weights":"./models/resnet18_pretrained.pt",
}
model_resnet18, loss_hist, metric_hist = train_val(resnet18_pretrained, params_train)	
#8. 使用#6画图		

代码解析
在step1中,定义一个获取每个batch中正确数目的函数
在step2中,我们实现loss_batch函数,这个函数输入损失函数,优化器,模型以及真值。
在step3中,loss_epoch函数输入模型对象,损失函数,数据加载器和优化器。我们使用数据加载器获取一个batch的数据,然后这个batch的数据一道GPU设备上,并获得模型输出。然后把损失以及度量值保存在之前定义好的变量里面。
在step4中,我们传入Python字典参数,可以提高代码的可读性
为了避免过拟合,在训练期间需要跟踪模型的性能。每训练一个epoch后,需要评估模型在验证集上的性能。验证集上的loss与最好的loss作比较。如果val_loss<best_loss, 将当前的权重拷贝给best_model_wts。在step5中,开始训练模型。每10个epochs,学习率策略经过一个周期。
在step6中,绘制损失和正确率的图形。可以发现,使用resnet18从头开始训练,不能得到很好的结果,那是因为训练集数据太少。
在step7中,使用预训练模型进行微调,效果显著。

部署模型

# 导入模型参数,验证模型性能,在测试集上部署模型
#1. 导入resnet18模型
from torch import nn
from torchvision import models

model_resnet18 = models.resnet18(pretrained=False)
num_ftrs = model_resnet18.fc.in_features
num_classes=10
model_resnet18.fc=nn.Linear(num_ftrs, num_classes)
#2. 导入训练好的权重到模型中
import torch
path2weights = "./models/resnet18_pretrained.pt"
model_resnet18.load_state_dict(torch.load(path2weights))
#3. 设置模型为评估模式
model_resnet18.eval()
#4. 将模型一道GPU设备上
if torch.cuda.is_available():
	device = torch.device("cuda")
	model_resnet18=model_resnet18.to(device)
#5. 在测试集上部署模型
def deploy_model(model, dataset, device, num_classes=10, sanity_check=False):
	len_data = len(dataset)
	y_out = torch.zeros(len_data, num_classes)
	y_gt = np.zeros((len_data), dtype="uint8")
	model = model.to(device)
	elapsed_time=[]
	with torch.no_grad():
		for i in range(len_data):
			x,y = dataset[i]
			y_gt[i] = y
			start = time.time()
			yy = model(x.unsqueeze(0).to(device))
			y_out[i] = torch.softmax(yy, dim=1)
			elapsed = time.time()-start
			elapsed_time.append(elapsed)
			if sanity_check is True:
				break
	inference_time = np.mean(elapsed_time)*1000
	print("average inference time per image on %s: %,2f ms" % (device, inference_time))
	return y_out.numpy(), y_gt
#6. 在验证集上部署模型
import time
import numpy as np
y_out, y_gt = deploy_model(cnn_model, val_ds, device=device, sanity_check=False)
print(y_out.shape, y_gt.shape)
# average inference tiem per image on cuda: 3.62ms
# (1600, 10) (1600,)
#7. 计算在验证集上的准确率
from sklearn.metrics import accuracy_score
y_pred = np.argmax(y_out, axis=1)
print(y_pred.shape, y_gt.shape)

acc = accuracy_score(y_pred, y_gt)
print("accuracy: %.2f" % acc)
# (1600,) (1600,)
# accuracy: 0.94
#8. 在test_ds上部署模型
y_out, y_gt = deploy_model(cnn_model, test_ds, device=device, sanity_check=False)
y_pred = np.argmax(y_out, axis=1)
acc = accuracy_score(y_pred, y_gt)
print("accuracy: %.2f" % acc)
# average inference time per image on cuda: 3.47 ms
# accuracy: 0.94

#9. 显示test_ds上的样本
from torchvision import utils
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(1)
def show(inp, title=None):
	mean = [0.447, 0.440, 0.407]
	std = [0.224, 0.221, 0.224]
	inp = inp.numpy().transpose((1, 2, 0))
	mean = np.array(mean)
	std = np.array(std)
	inp = std * inp + mean
	inp = np.clip(inp, 0, 1)
	plt.show(inp)
	if title is not None:
		plt.title(title)
	plt.pause(0.001)  # pause a bit so that plot are update
grid_size = 4
rnd_inds = np.random.randint(0, len(test_ds), grid_size)
print("image indices:", rnd_inds)
x_grid_test = [test_ds[i][0] for i in rnd_inds]
y_grid_test = [test_ds[i][1] for i in rnd_inds]

x_grid_test = utils.make_grid(x_grid_test, nrow=4, padding=2)
print(x_grid_test.shape)
plt.rcParams["figure.figsize"]=(10, 5)
imshow(x_grid_test, y_grid_test)
# image indices: [5157 235 3980 5192]
# torch.Size([3, 100, 394])
#10. 在CPU上测试性能
device_cpu = torch.device("cpu")
y_out, y_gt = deploy_model(cnn_model, val_ds, device=device, sanity_check=False)
print(y_out.shape, y_gt.shape)

# average inference tiem per image on cpu: 14.41 ms
# (1600,10) (1600,)		

代码解析:
在步骤1中,我们从torchvision.models加载了内置模型。在步骤2中,我们加载了state_dict,它包含模型中的模型权重。对于部署,需要将模型设置为评估模式。这一点很重要,因为某些层(如dropout和BatchNorm)在训练和部署模式中表现不同。如果CUDA设备可用,我们应该把模型移到CUDA设备上。模型已经准备好部署了。
在第5步中,辅助函数以numpy数组的形式返回模型输出和ground truth标签。此外,CUDA设备上每个图像的推断时间大概为3.53 ms。稍后,我们将看到CPU设备上的推断时间。
在第6步中,我们通过在验证数据集中部署保存的模型来验证它。在深度学习模型的开发过程中,很多事情都可能出错。我们建议通过在已知数据集(例如,验证数据集)上部署模型来验证存储模型的性能。
在步骤7中,我们使用scikit-learn包来计算我们的二分类模型的准确性。验证数据集的准确性为0.94。这是对我们模型的一个很好的验证。
在步骤8中,我们将模型部署在test_ds上。我们没有使用test_ds进行验证,因此可以认为它是模型的一个隐藏数据集。幸运的是,我们也有test_ds的标签,我们可以在test_ds上评估模型的性能。准确性为0.95,接近val_ds的精度。
在第9步中,我们显示了一些来自test_ds的示例图像和预测标签。
在步骤10中,我们希望看到部署在CPU上的推理时间。如果我们想要在没有GPU的设备上部署模型,这将是有用的信息。每张图像的推理时间大约是25毫秒。这仍然是一个很低的数字,可以用于许多实时应用程序。

  人工智能 最新文章
2022吴恩达机器学习课程——第二课(神经网
第十五章 规则学习
FixMatch: Simplifying Semi-Supervised Le
数据挖掘Java——Kmeans算法的实现
大脑皮层的分割方法
【翻译】GPT-3是如何工作的
论文笔记:TEACHTEXT: CrossModal Generaliz
python从零学(六)
详解Python 3.x 导入(import)
【答读者问27】backtrader不支持最新版本的
上一篇文章      下一篇文章      查看所有文章
加:2021-07-07 00:00:15  更:2021-07-07 00:00:45 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/26 17:20:48-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码