PyTorch for Deep Learning & Machine Learning – Full Course(4) - NoTe0x009

Baobao0824

原链接:
https://www.youtube.com/watch?v=V_xro1bcAuA

写在前面

这一系列是我在学习他人的优质课程(博客文章、视频课程等)时所做的学习笔记,根据我自身的水平来进行学习,同时会进行思维的发散,补充原文中没有提到或者有错误的地方。同时也会进行经常性的更新和整理,让它和我的当下的状态更加契合。

这次带来的是视频的Chapter3部分,也就是Computer Vision,我也不知道他怎么会直接跳到计算机视觉的,总之我们先来看看吧。

准备工作

Introduction to computer vision

pytorch中的计算机视觉库有下面这些:

  • torchvision,计算机视觉的基础库。
  • torchvision.datasets,提供计算机视觉数据集以及数据加载函数。
  • torchvision.models,提供预训练好的计算机视觉模型,可以直接用于解决实际问题。
  • torchvision.transforms,用于对视觉数据进行处理的函数,使其适合机器学习模型使用。
  • torch.utils.data.Dataset,pytorch的基础数据集类。
  • torch.utils.data.DataLoader,在数据集上创建Python可迭代对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import torch
from torch import nn

import torchvision
from torchvision import datasets
from torchvision import transforms
from torchvision.transforms import ToTensor

import matplotlib.pyplot as plt

torch.__version__
# 2.10.0
torchvision.__version__
# 0.25.0

Computer vision input and outputs

在这个任务中,我们使用的是FashionMNIST数据集,一共有70000张图片,60000张数据集和10000张测试集,图片都是28*28像素的8位灰度图,同时有10个互斥的服装品类标签:0=T恤/上衣、1=裤子、2=套头毛衣、3=连衣裙、4=外套、5=凉鞋、6=衬衫、7=运动鞋、8=包、9=短靴。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# 获取数据集
from torchvision import datasets
train_data = datasets.FashionMNIST(root="data",train=True,download=True,transform=torchvision.transforms.ToTensor(),target_transform=None)
# root是数据集的根目录
# train若为True,那么从训练集创建数据集,否则从测试集创建数据集
# transform让你传可调用对象,而ToTensor类实现了__call__,可以是一个可调用对象,对样本本身进行处理
# download如果为True,那么从互联网下载数据集放入根目录(不会重复下载)
# target_transform,也是一个可调用对象,让你对标签进行处理
test_data = datasets.FashionMNIST(root="data",train=False,download=True,transform=torchvision.transforms.ToTensor(),target_transform=None)
len(train_data), len(test_data)
# (60000, 10000)
# 看一下这个数据大概长什么样子
image, label = train_data[0]
image, label
# (tensor([[[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
# 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
# 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
# 0.0000, 0.0000, 0.0000, 0.0000],
# [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
# 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
# 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
# 0.0000, 0.0000, 0.0000, 0.0000],
# [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
# 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
# 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
# 0.0000, 0.0000, 0.0000, 0.0000],
# [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
# 0.0000, 0.0000, 0.0000, 0.0000, 0.0039, 0.0000, 0.0000, 0.0510,
# 0.2863, 0.0000, 0.0000, 0.0039, 0.0157, 0.0000, 0.0000, 0.0000,
# 0.0000, 0.0039, 0.0039, 0.0000],
# [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
# 0.0000, 0.0000, 0.0000, 0.0000, 0.0118, 0.0000, 0.1412, 0.5333,
# 0.4980, 0.2431, 0.2118, 0.0000, 0.0000, 0.0000, 0.0039, 0.0118,
# 0.0157, 0.0000, 0.0000, 0.0118],
# [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
# 0.0000, 0.0000, 0.0000, 0.0000, 0.0235, 0.0000, 0.4000, 0.8000,
# 0.6902, 0.5255, 0.5647, 0.4824, 0.0902, 0.0000, 0.0000, 0.0000,
# 0.0000, 0.0471, 0.0392, 0.0000],
# [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
# ...
# [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
# 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
# 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
# 0.0000, 0.0000, 0.0000, 0.0000]]]),
# 9)
train_data.classes
# ['T-shirt/top',
# 'Trouser',
# 'Pullover',
# 'Dress',
# 'Coat',
# 'Sandal',
# 'Shirt',
# 'Sneaker',
# 'Bag',
# 'Ankle boot']
train_data.class_to_idx
# {'T-shirt/top': 0,
# 'Trouser': 1,
# 'Pullover': 2,
# 'Dress': 3,
# 'Coat': 4,
# 'Sandal': 5,
# 'Shirt': 6,
# 'Sneaker': 7,
# 'Bag': 8,
# 'Ankle boot': 9}
train_data.targets
# tensor([9, 0, 0, ..., 3, 0, 5])

这个图到底长什么样呢,我们可视化一下:

1
2
3
4
5
6
import matplotlib.pyplot as plt
image, label = train_data[0]
image.shape
# Image shape: torch.Size([1, 28, 28])
plt.imshow(image.squeeze(),cmap="gray")
plt.title(label);

这个图像也不大,也能看出来是什么商品
这个图像也不大,也能看出来是什么商品

Mini-batches

目前我们的数据仍以pytorch dataset的形式存在,DataLoader能够将数据集转换为一个Python可迭代对象。具体来说,我们希望吧数据按照batch组织起来。首先能提高计算效率,因为你可能无法一次在内存中存储这么多图像,所以我们将其拆分成每次32张(batch size为32);其次能够让神经网络在每个训练周期内获得更多更新梯度的机会。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from torch.utils.data import DataLoader
BATCH_SIZE = 32
train_dataloader = DataLoader(dataset=train_data,batch_size=BATCH_SIZE,shuffle=True)
test_dataloader = DataLoader(dataset=test_data,batch_size=BATCH_SIZE,shuffle=False)
train_dataloader, test_dataloader
# (<torch.utils.data.dataloader.DataLoader at 0x165722150>,
# <torch.utils.data.dataloader.DataLoader at 0x15906bd10>)
len(train_dataloader), len(test_dataloader)
# (1875, 313)
# 这个就是batch的数量

# 看看一个batch里都有什么
train_features_batch, train_labels_batch = next(iter(train_dataloader))
train_features_batch.shape, train_labels_batch.shape
# (torch.Size([32, 1, 28, 28]), torch.Size([32]))

Model 0: Build a basline model

这个基线模型的思路是:把输入的1乘28乘28(颜色通道、宽和高)铺平成为1乘784的张量,然后用两层神经网络来做。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from torch import nn
# 设置模型类,先铺平再传入两个线性层
class Model0(nn.Module):
def __init__(self, input_shape:int, hidden_units:int, output_shape: int):
super().__init__()
self.layers= nn.Sequential(
nn.Flatten(),
nn.Linear(in_features=input_shape, out_features=hidden_units),
nn.Linear(in_features=hidden_units, out_features=output_shape)
)
def forward(self, x):
return self.layers(x)

torch.manual_seed(42)
classes = train_data.classes
model_0 = Model0(input_shape=28*28,hidden_units=10, output_shape=len(classes))
model_0.to("cpu")
# Model0(
# (layers): Sequential(
# (0): Flatten(start_dim=1, end_dim=-1)
# (1): Linear(in_features=784, out_features=10, bias=True)
# (2): Linear(in_features=10, out_features=10, bias=True)
# )
# )

接下来就该配置损失函数、优化器与评估指标了。我们这个是多分类任务,因此损失函数选择nn.CrossEntropyLoss(),优化器采用torch.optim.SGD(),对于多分类问题来说,我们使用准确率作为评估模型性能的指标。

在运行过程中,我们不光要关注模型性能,我们还要关注模型的运行速度,所以我们再写一个计算训练时间的函数。

1
2
3
4
5
6
7
8
9
# 这个是作者写好的计算准确率的函数
from helper_functions import accuracy_fn
from timeit import default_timer as timer

loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(params=model_0.parameters(),lr=0.1)
def print_train_time(start:float, end: float, device: str):
total_time = end - start
print(f"Train {total_time:.1f} seconds on {device}")

加上了batch之后的训练循环变成了这样。由于我们是基于batch计算,那么optimizer会在每一个batch后更新一次模型参数,而不是一个epoch更新一次。

  1. 遍历epochs。
  2. 在每个epoch下遍历batchs,执行训练步骤,计算训练损失。
  3. 遍历测试的batchs,执行测试步骤,计算测试损失。
  4. 打印日志、记录整体耗时。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from tqdm.auto import tqdm

# 设置种子和开始时间的计时器
torch.manual_seed(42)
train_time_start_on_cpu = timer()
# 设置训练轮数,为了节省时间,我们设置的很小
epochs = 3
for epoch in tqdm(range(epochs)):
print(f"Epoch: {epoch} \n")
# 训练阶段
train_loss = 0
for batch ,(X,y) in enumerate(train_dataloader):
model_0.train()
# 1. 前向传播
y_pred = model_0(X)
# 2. 计算每个batch的损失
loss = loss_fn(y_pred,y)
train_loss += loss
# 3. 优化器梯度清零
optimizer.zero_grad()
# 4. 反向传播
loss.backward()
# 5. 优化器步进(每个batch步进一次)
optimizer.step()
# 隔一段时间输出
if batch % 400 ==0:
print(f"Processed {batch*len(X)} / {len(train_data)} samples\n")
# 计算平均训练损失
train_loss /= len(train_dataloader)
# 测试阶段
test_loss, test_acc = 0,0
model_0.eval()
with torch.inference_mode():
for X_test,y_test in test_dataloader:
test_pred = model_0(X_test)
test_loss += loss_fn(test_pred,y_test)
test_acc += accuracy_fn(y_true=y_test, y_pred=test_pred.argmax(dim=1))
# 计算平均测试损失和平均正确率
test_loss /=len(test_dataloader)
test_acc /= len(test_dataloader)
print(f"TrainLoss: {train_loss:.4f}, TestLoss: {test_loss:.4f}, TestAcc: {test_acc:.4f} \n")

# 计算训练时间
train_time_end_on_cpu = timer()
total_train_time_model_0 = print_train_time(start=train_time_start_on_cpu, end=train_time_end_on_cpu, device="cpu")

三个epoch下来,大概训练了6秒。最后看一下model_0的效果如何。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
torch.manual_seed(42)
def eval_model(
model: nn.Module,
data_loader:torch.utils.data.DataLoader,
loss_fn: nn.Module,
accrancy_fn = accuracy_fn):
loss, acc =0,0
model.eval()
with torch.inference_mode():
for X, y in tqdm(data_loader):
# 进行预测
y_pred = model(X)
# 计算每一个batch的累计损失和准确率
loss += loss_fn(y_pred, y)
acc+= accrancy_fn(
y_true = y,
y_pred = y_pred.argmax(dim=1)
)
# 计算每个batch的平均损失和准确率
loss /= len(data_loader)
acc /= len(data_loader)
return {
"model_name": model.__class__.__name__,
"model_loss": loss,
"model_acc": acc
}

model_0_result = eval_model(
model=model_0,
data_loader=test_dataloader,
loss_fn=loss_fn
)
model_0_result
# {'model_name': 'Model0',
# 'model_loss': tensor(0.4766),
# 'model_acc': 83.42651757188499}

Model 1: Building a better model with non-linearity

接下来,我们在模型的每一层处理之后加入一个非线性函数,看看对模型效果的提升有多大,将其命名为model_1。损失函数我们依旧选择CrossEntropyLoss,优化器也依旧是SGD。直接看整个的从定义到训练再到测试的完整代码。为了后续调用方便,我们将训练过程和测试过程独立出来作为两个函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# 创建对应的Module1类
class Model1(nn.Module):
def __init__(
self,
input_shape: int,
hidden_units: int,
output_shape: int):
super().__init__()
self.layers = nn.Sequential(
nn.Flatten(),
nn.Linear(
in_features=input_shape,
out_features=hidden_units
),
nn.ReLU(),
nn.Linear(
in_features=hidden_units,
out_features=output_shape
),
nn.ReLU()
)

def forward(self, x:torch.Tensor):
return self.layers(x)

# 实例化model_1
torch.manual_seed(42)
model_1 = Model1(
input_shape=28*28,
hidden_units=10,
output_shape= len(classes))
# 损失函数和优化器
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(params=model_1.parameters(), lr=0.1)

# 训练过程函数
def train_step(
model: nn.Module,
data_loader: torch.utils.data.DataLoader,
loss_fn,
optimizer,
accuracy_fn = accuracy_fn
):
train_loss, train_acc =0,0
model.train()
for batch, (X,y) in enumerate(data_loader):
y_pred = model(X)
loss = loss_fn(y_pred,y)
train_loss+= loss.item()
train_acc += accuracy_fn(
y_true=y,
y_pred=y_pred.argmax(dim=1)
)
optimizer.zero_grad()
loss.backward()
optimizer.step()
train_loss/=len(data_loader)
train_acc /=len(data_loader)
print(f"Train loss: {train_loss:.5f} | Train acc: {train_acc:.2f}%")

# 测试过程函数
def test_step(
model: nn.Module,
data_loader: torch.utils.data.DataLoader,
loss_fn,
accuracy_fn = accuracy_fn
):
test_loss, test_acc =0,0
model.eval()
with torch.inference_mode():
for X,y in data_loader:
test_pred = model(X)
test_loss += loss_fn(test_pred,y)
test_acc += accuracy_fn(
y_true=y,
y_pred= test_pred.argmax(dim=1)
)
test_loss /=len(data_loader)
test_acc /= len(data_loader)
print(f"Test Loss: {test_loss:.2f}, Test Accrancy: {test_acc:.2f}%\n")

# 开始训练
torch.manual_seed(42)
train_time_start_on_cpu = timer()
epochs = 3
# 训练部分
for epcoh in tqdm(range(epochs)):
print(f"Epoch {epoch} \n")
train_step(
model=model_1,
data_loader=train_dataloader,
optimizer=optimizer,
loss_fn=loss_fn
)
test_step(
model=model_1,
data_loader=test_dataloader,
loss_fn=loss_fn
)



# 计算结束时间
train_time_end_on_cpu = timer()
total_train_time_model_1 = print_train_time(
start=train_time_start_on_cpu,
end=train_time_end_on_cpu,
device="cpu"
)

这里有一个很严重的问题,如果你在函数参数里面写了默认参数,这里的默认参数是在函数定义时求值的,也就是说如果你给optimizer定义默认参数的时候,它就已经是当时的值了,你后续再去更新optimizer就不会被更新。

最后的结果是Test Loss: 0.69, Test Accrancy: 74.98%,训练了5.5s。如果是使用很小的模型,那么在数据量不大的时候,频繁的在GPU和CPU之间板运输局,会导致系统卡在运输环节,反而GPU的效率就没有CPU高了。

Model 2: Building a Convolutional Neural Network (CNN)

CNN也就是卷积神经网络,和常规的单层神经网络不一样,卷积神经网络的神经元是3d的,能够抓取更多特征,存储更多信息。在torch代码中,主要体现的是不用nn.Linear层,改用nn.Conv2d层和nn.MaxPool2d来进行模型设计。

我们首先来讲解一下nn.Conv2d层。它接收的参数列表如下:

  • in_channels,输入通道数,例如灰度图为1,RGB为3。
  • out_channels,输出通道数,即卷积核个数,卷积核个数决定你提取多少种特征。
  • kernal_size,卷积核大小,例如3乘3,卷积核尺寸越大感受域也越大,但是参数也越多。
  • stride,步长,控制输出尺寸缩小程度。
  • padding,填充,保持空间尺寸不变。
  • dilation,空洞卷积,一般不常用。
  • groups,分组卷积。
  • bias,是否使用偏置。

我们再介绍一下nn.MaxPool2d,他一般紧跟在卷积层之后,核心作用是,在不增加参数的情况下,降低特征图的空间尺寸,同时保留最显著的特征。一个nn.MaxPool2d层一般有以下接收的参数列表。

  • kernel_size,池化窗口大小,如2乘2。
  • stride,步长,默认等于kernel_size
  • padding,填充。
  • dilation,空洞池化,极少用。
  • ceil_mode,是否向上取整输出尺寸。

对于没有深度学习理论基础得我来说,这个看的非常云里雾里。所以我们直接看Model2类的写法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
class Model2(nn.Module):
def __init__(
self,
input_shape:int,
hidden_units:int,
output_shape:int
):
super().__init__()
self.block1 = nn.Sequential(
nn.Conv2d(
in_channels=input_shape,
out_channels= hidden_units,
kernel_size=3,
stride=1,
padding=1
),
nn.ReLU(),
nn.Conv2d(
in_channels=hidden_units,
out_channels=hidden_units,
kernel_size=3,
stride=1,
padding=1
),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2)
)
self.block2 = nn.Sequential(
nn.Conv2d(
in_channels=hidden_units,
out_channels= hidden_units,
kernel_size=3,
stride=1,
padding=1
),
nn.ReLU(),
nn.Conv2d(
in_channels= hidden_units,
out_channels=hidden_units,
kernel_size=3,
stride=1,
padding=1
),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2)
)
self.classifier = nn.Sequential(
nn.Flatten(),
nn.Linear(in_features=hidden_units*7*7,out_features=output_shape)
)
def forward(self,x):
x = self.block1(x)
x = self.block2(x)
x = self.classifier(x)
return x

model_2 = Model2(
input_shape=1,
hidden_units=10,
output_shape= len(classes)
)
# 这里的input_shape是通道数,因此为1,我们的输入图片都是黑白图像
model_2
# Model2(
# (block1): Sequential(
# (0): Conv2d(1, 10, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
# (1): ReLU()
# (2): Conv2d(10, 10, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
# (3): ReLU()
# (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
# )
# (block2): Sequential(
# (0): Conv2d(10, 10, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
# (1): ReLU()
# (2): Conv2d(10, 10, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
# (3): ReLU()
# (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
# )
# (classifier): Sequential(
# (0): Flatten(start_dim=1, end_dim=-1)
# (1): Linear(in_features=490, out_features=10, bias=True)
# )
# )

我们来详细解释一下代码。block1能够做到找线条和边,输出不是像素,而是边缘特征图,然后block2再把边缘拼成部件。最后用classifier做判断。经过池化操作后,每一层给出的大小都减半,最后到classifier之前的形状为[16,7,7],减小了尺寸大小,增加了通道大小。

我说实话对于这些东西我目前也看不太懂,在实践的基础上,如果能够理解里面的数学原理那就最好了,如果不理解的话那还是直接先暂缓吧,毕竟能手动开始跑深度学习我感觉就已经很强大了。

接下来,保持损失函数和优化器都不变,直接写训练过程和测试过程,利用到了刚才写的train_steptest_step两个函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
torch.manual_seed(42)
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(params=model_2.parameters(), lr=0.1)
train_time_start_on_cpu = timer()
# 开始训练和测试
epochs =3
for epoch in tqdm(range(epochs)):
print(f"Epoch{epoch}\n")
train_step(
model=model_2,
data_loader=train_dataloader,
optimizer=optimizer,
loss_fn=loss_fn
)
test_step(
model=model_2,
data_loader=test_dataloader,
loss_fn=loss_fn
)

train_time_end_on_cpu = timer()
total_train_time_model_2 = print_train_time(
start= train_time_start_on_cpu,
end= train_time_end_on_cpu,
device="cpu"
)
model_2_results = eval_model(
model=model_2,
data_loader=test_dataloader,
loss_fn=loss_fn,
accrancy_fn=accuracy_fn,
)

model_2_results
# {'model_name': 'Model2',
# 'model_loss': tensor(0.3166),
# 'model_acc': 88.50838658146965}

这个结果还是很令人满意的,准确率能达到88%。我们比较一下三者的结果看一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 比较三个模型的结果
import pandas as pd
compare_results = pd.DataFrame(
[
model_0_result,
model_1_result,
model_2_results
]
)
compare_results
# model_name model_loss model_acc
# 0 Model0 tensor(0.4766) 83.426518
# 1 Model1 tensor(0.6901) 74.810304
# 2 Model2 tensor(0.3166) 88.508387

接下来,我们取最好的model_2,看一下他的预测结果大概长什么样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def make_predictions(
model: nn.Module,
data: list,
decive: str = "cpu"
):
pred_probs = []
model.eval()
with torch.inference_mode():
for sample in data:
# 准备样本,增加batch的维度
sample = torch.unsqueeze(sample,dim=0)
# 前向传播,输出原始logits值
pred_logit: torch.Tensor = model(sample)
# 将原始logits值转换成概率
pred_prob = torch.softmax(pred_logit.squeeze(),dim=0)
pred_probs.append(pred_prob)
return torch.stack(pred_probs)

# 随机获取测试用例
import random
test_samples = []
test_labels = []
for sample,label in random.sample(list(test_data),k=9):
test_samples.append(sample)
test_labels.append(label)
plt.imshow(test_samples[0].squeeze(),cmap="gray")
plt.title(classes[test_labels[0]])

我们随机选取一个测试用例看一下到底是什么样的。

因为没设随机种子,所以和原教程中的东西是不一样的
因为没设随机种子,所以和原教程中的东西是不一样的

一个完整的预测流程,是将其

1
2
3
4
5
6
7
8
9
10
11
pred_probs = make_predictions(model=model_2,data=test_samples)
pred_probs[:2]
# tensor([[6.5902e-03, 6.4624e-04, 9.3174e-01, 1.7501e-03, 1.5836e-02, 1.0627e-05,
# 4.3039e-02, 2.0947e-05, 3.2444e-04, 3.9885e-05],
# [1.5433e-06, 4.6623e-06, 3.3769e-05, 2.5225e-05, 3.5903e-05, 1.5113e-05,
# 1.0286e-07, 6.7433e-05, 9.9981e-01, 3.2640e-06]])
pred_classes = pred_probs.argmax(dim=1)
pred_classes
# tensor([2, 8, 3, 0, 7, 6, 2, 9, 4])
test_labels
# [2, 8, 6, 0, 7, 6, 2, 9, 4]

可以看到,只有第三个预测错误,剩下的预测全部是正确的。

Evaluating model predictions with a confusion matrix

接下来,我们可以构建混淆矩阵来深入评估模型预测效果,使用混淆矩阵画图需要先安装一个库mlxtend

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
y_preds = []
model_2.eval()
with torch.inference_mode():
for X,y in tqdm(test_dataloader,desc="making predictions"):
y_logit = model_2(X)
y_pred = torch.softmax(y_logit.squeeze(),dim=0).argmax(dim=1)
y_preds.append(y_pred)
y_pred_tensor = torch.cat(y_preds)
y_pred_tensor
# tensor([9, 2, 1, ..., 8, 1, 8])
from torchmetrics import ConfusionMatrix
from mlxtend.plotting import plot_confusion_matrix
confmat = ConfusionMatrix(num_classes=len(classes), task="multiclass")
confmat_tensor = confmat(preds=y_pred_tensor, target = test_data.targets)
fig,ax = plot_confusion_matrix(conf_mat=confmat_tensor.numpy(), class_names=classes,figsize=(10,7))
# 这个函数是在helper_functions里面定义好的

可以看到到底有多少个Shirt被判断成了TShirt,这个算是重灾区了
可以看到到底有多少个Shirt被判断成了TShirt,这个算是重灾区了

总结

在这一章中,我们主要学习了如何处理图像类的数据,同时我们也可以使用CNN卷积神经网络来建立更加复杂的模型,从而学习到更多的特征了。

  • 标题: PyTorch for Deep Learning & Machine Learning – Full Course(4) - NoTe0x009
  • 作者: Baobao0824
  • 创建于 : 2026-06-12 14:39:29
  • 更新于 : 2026-06-12 14:39:29
  • 链接: https://blog.baobao0824.top//学习笔记NoTe/NT-0x009/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论