PyTorch 深度学习框架快速入门
- 深度学习框架
- 常用模块
- 数据集存取
- 图片数据处理库 —— PIL
- OS 模块
- 实例
- Tensorboard 记录机器学习的过程
- Transform 进行图像变换
- 数据集的下载
- DataLoader
- Module 自定义网络
- 前向传播
- 卷积层
- 卷积简单应用
- 最大池化
- 非线性层
- 线性层
- 简单的整合
- 基于现有网络模型修改
- 模型的保存和载入
- 完整模型训练流程
- 应用GPU对模型进行训练
- 模型验证
- Note
- Python对Windos下文件路径的三种表示方法
本文主要参考B站知识区UP 我是土堆 发布的PyTorch视频教程 和 PyTorch官方文档 整理而来。
关于环境配置教程大家可以观看视频自行解决,本文不包含环境配置过程。
本文不含深度学习理论知识,关于理论知识的入门教程推荐:
- 深度学习入门 (鱼书)
- 苹果书 (对应李宏毅老师的深度学习课程)
- 吴恩达老师机器学习和深度学习开源课程
- 李飞飞老师cs231n计算机视觉课程
- 深度学习 邱锡鹏
深度学习框架
P y T o r c h 起源 : PyTorch起源: PyTorch起源:
- 在 2002 年的时候,torch 基于 lua 这个小众语言诞生。
- 在 2011 年更新至 torch 7 。
- 2016 年时 facebook 在torch 7 的基础上开发了 pytorch。pytorch 早期的版本 (0.1 ~ 0.3)无法直接在 0.4 及以后的版本运行,需要将代码稍微调整后才可以。
- 在 18 年后,pytorch 发布了正式的版本 1.0 并与 CAFFE2 强强联合。
T e n s o r f l o w 起源 : Tensorflow起源: Tensorflow起源:
- theano 是加拿大人开发的一个框架,但现在已经停止开发了,在theano 的基础上,google 开发了 tensorflow,并收购了 keras。
- keras 是一个高级的 api 规范,本身可以不做任何的实现,可以使用底层的计算库来实现,如 tensorflow 可以通过 tf.keras 调用 keras 的一部分功能。
- keras 官方的实现也可以调用 tensorflow 的后端和 mxnet 的后端,快速完成网络的搭建。但由于是一个高级的接口,因此效率是一个很大的问题。
其他 : 其他: 其他:
- Facebook 阵营的主要是 caffe 1 和 caffe 2,目前主推的是 pytorch。
- mxnet 和 caffe 都是由华人进行开发的,mxnet 目前成为了亚马逊的官方框架。
- microsoft 阵营的是 cntk,比较小众。
- chainer 是日本的一家公司研发的框架,而 pytorch 借鉴了 chainer 的 api 规范。
区别 : 区别: 区别:
- pytorch 和 tensorflow 是目前主推的两个框架,他们的区别在于是静态图优先(tensorflow)还是动态图优先(pytorch)。
- 动态图优先符合我们的思维习惯,在每完成一个计算过程的时候都会有一个符号链接的过程,同时也会得到该过程的计算结果。我们可以在运行的时候动态改变公式。
- 静态图优先指的是先完成静态图的创建,但却并不会同时得到一个计算结果,在完成了图的创建后,需要另外的计算过程来计算输出结果。如上图,要想计算 x * y 的结果,就需要先创建一个乘法的过程,然后通过 run 代码代入数据计算结果。图的创建过程和图的运行过程是分离的。静态图中的静态体现在图一旦创建好,在 run 过程中是无法改变的。在具体的 run 的过程里,我们只能控制输入,运算过程是无法进行控制的。
常用模块
数据集存取
本部分涉及两个关键组件:
- d a t a s e t : dataset: dataset: 指的是数据集,在数据集中,会存储着数据的编号,数据本身和数据的标签。
- d a t a l o a d e r : dataloader: dataloader: 是用于载入数据集的工具,数据不一定是一个一个进入的,可以是几个数据作为一组进入到处理器中。
数据集有两种最简单的保存方式:
- 第一种:
- 第二种:
图片数据处理库 —— PIL
P I L PIL PIL 是指 P y t h o n Python Python I m a g i n g Imaging Imaging L i b r a r y Library Library,是一个用来打开、操作和保存许多不同格式图像文件的库。 P I L PIL PIL 提供了许多图像处理的功能,如图像裁剪、旋转、颜色调整、滤镜等。
需要注意的是,目前 P I L PIL PIL 已经被 P i l l o w Pillow Pillow 所取代, P i l l o w Pillow Pillow 原先是 P I L PIL PIL 的一个分支,目前仍在积极维护和开发。因此我们可以直接安装 P i l l o w Pillow Pillow,可以通过下面的代码安装:
pip install pillow
虽然安装的时候安装的是 p i l l o w pillow pillow 库,但是在实际使用的时候仍然可以使用下面的代码将 P I L PIL PIL 作为源,并导入其他模块,也就是说 P i l l o w Pillow Pillow 具有与 P I L PIL PIL 相同的功能。
from PIL import Image
这条代码的意思是从 P I L PIL PIL 库中导入 I m a g e Image Image 模块。 I m a g e Image Image 模块是 P I L PIL PIL 中最主要的模块,用于处理图像。
下面是一个简单的例子,展示如何使用 P I L PIL PIL 打开和显示一张图像:
from PIL import Image
# 打开图像
img = Image.open("test.png")
img2 = Image.open(r"C:\Users\zdh\Downloads\test.png")
# 显示图像
img.show()
img2.show()
在这个例子中, I m a g e . o p e n ( " t e s t . p n g " ) Image.open("test.png") Image.open("test.png") 用于打开名为 t e s t . p n g test.png test.png 的图像文件,然后 i m g . s h o w ( ) img.show() img.show()用于显示这张图像。
OS 模块
i m p o r t import import o s os os 语句用于在 P y t h o n Python Python 中导入 o s os os 模块。 o s os os 模块提供了非常丰富的方法,用于处理文件和目录等操作系统相关的任务。
o s os os 模块是 P y t h o n Python Python 的标准库模块之一,提供了许多与操作系统交互的功能。主要用于文件和目录操作、环境变量操作、进程管理等。以下是一些常用的功能:
- 文件和目录操作:
os.getcwd():获取当前工作目录。 # get current work directory
os.chdir(path):改变当前工作目录。 # change directory
os.listdir(path):列出指定目录中的文件和子目录。 # list directory
os.mkdir(path):创建目录。 # make directory
os.makedirs(path):递归创建目录。 # make directories
os.remove(path):删除文件。 # remove (file)
os.rmdir(path):删除目录。 # remove directory
os.rename(src, dst):重命名文件或目录。 # rename (file)
- 路径操作:
os.path.basename(path):获取路径中的文件名部分。
os.path.dirname(path):获取路径中的目录部分。
os.path.join(path, *paths):将多个路径组合后返回。
os.path.split(path):将路径分割为目录和文件名。
os.path.exists(path):判断路径是否存在。
os.path.isabs(path):判断是否为绝对路径。
os.path.isfile(path):判断是否为文件。
os.path.isdir(path):判断是否为目录。
- 环境变量:
os.getenv(key):获取环境变量的值。
os.putenv(key, value):设置环境变量的值。
os.environ:获取所有环境变量的字典。
- 进程管理:
os.system(command):运行系统命令。
os.popen(command):执行命令并获取输出。
os.getpid():获取当前进程 ID。
os.getppid():获取父进程 ID。
下面是一些使用 o s os os 模块的示例代码:
import os
# 获取当前工作目录
current_directory = os.getcwd()
print("当前工作目录:", current_directory)
# 列出当前目录中的文件和子目录
files = os.listdir(current_directory)
print("当前目录中的文件和子目录:", files)
# 创建一个新目录
new_directory = "new_dir"
os.mkdir(new_directory)
print(f"创建目录: {new_directory}")
# 重命名目录
new_directory_renamed = "new_dir_renamed"
os.rename(new_directory, new_directory_renamed)
print(f"重命名目录: {new_directory_renamed}")
# 删除目录
os.rmdir(new_directory_renamed)
print(f"删除目录: {new_directory_renamed}")
# 获取环境变量
home_directory = os.getenv("HOME")
print("主目录:", home_directory)
实例
- 情况一: 文件夹的名称即为 label,文件夹内是数据和编号。
from torch.utils.data import Dataset
from PIL import Image
import os
class MyDataset(Dataset):
def __init__(self, root_dir, label_dir):
self.root_dir = root_dir
self.label_dir = label_dir
# 根据当前操作系统的路径分隔符规则,将多个字符串组合成一个完整的路径。
# 在 Windows 系统中,路径分隔符通常是 \,而在 Unix/Linux 系统中,路径分隔符通常是 /。
# 使用 os.path.join() 函数可以使你的代码在不同操作系统上都能正确地拼接路径,避免了硬编码路径分隔符带来的跨平台问题。
self.path = os.path.join(self.root_dir, self.label_dir)
# 返回目录下的文件名列表,便于 getitem 函数调用
self.img_path = os.listdir(self.path)
def __getitem__(self, idx): # []运算符重载,使 MyDataset[idx] 返回对应序号 idx 下的 img 和 label
img_name = self.img_path[idx]
img_item_path = os.path.join(self.path, img_name)
img = Image.open(img_item_path) # 使 img 包含该图像的所有数据,如 长宽、图片显示数据
label = self.label_dir # 图片所在目录名即图片对应Label
return img, label
def __len__(self):
return len(self.img_path) # 返回数据集长度
root_dir = 'dataset/train'
ants_label_dir = 'ants'
bees_label_dir = 'bees'
ants_dataset = MyDataset(root_dir, ants_label_dir)
bees_dataset = MyDataset(root_dir, bees_label_dir)
# Dataset 类中对+运算符进行了重载,因此,在这里使用 + 相当于是将两个数据集进行拼接
train_dataset = ants_dataset + bees_dataset
print(len(train_dataset))
print(len(ants_dataset))
print(len(bees_dataset))
ant_img, ant_label = ants_dataset[0]
bee_img, bee_label = bees_dataset[0]
ant_img.show()
bee_img.show()
- 情况二: 通过不同的文件夹将数据的标签和数据本身分开储存。
from torch.utils.data import Dataset
from PIL import Image
import os
class MyDataset(Dataset):
def __init__(self, root_dir, img_dir, label_dir):
self.root_dir = root_dir
self.img_dir = img_dir
self.label_dir = label_dir
self.img_path = os.path.join(self.root_dir, self.img_dir)
self.label_path = os.path.join(self.root_dir, self.label_dir)
# 图片名列表
self.img_list = os.listdir(self.img_path)
# 图片标签txt文件名列表
self.label_list = os.listdir(self.label_path)
def __getitem__(self, idx):
img_item_path = os.path.join(self.img_path, self.img_list[idx])
# with类似Java JDK8之后推出的try(...) catch语法糖,作用大体都是可以在代码执行最后自动调用资源对象的close方法
with open(os.path.join(self.root_dir, self.label_dir, self.label_list[idx]), 'r') as f:
# 读取图片对应标签txt文件中保存的label名
label = f.readlines()
img = Image.open(img_item_path)
return img, label
root_dir = "test/train"
img_dir = "ants_image"
label_dir = "ants_label"
ants_dataset = MyDataset(root_dir, img_dir, label_dir)
ant_img, ant_label = ants_dataset[0]
ant_img.show()
小 T i p s : 小Tips: 小Tips:
# 如果希望显示两幅图像,可以采用 matplotlib 库
import matplotlib.pyplot as plt
fig1, ax1 = plt.subplots(figsize=(5, 5))
ax1.imshow(bee_img)
ax1.set_title("Bee")
ax1.axis("off")
# Create the second figure for the ant image
fig2, ax2 = plt.subplots(figsize=(5, 5))
ax2.imshow(ant_img)
ax2.set_title("Ant")
ax2.axis("off")
# Show both figures
plt.show()
Tensorboard 记录机器学习的过程
T e n s o r B o a r d TensorBoard TensorBoard 是 T e n s o r F l o w TensorFlow TensorFlow 的一个可视化工具,用于帮助开发者理解、调试和优化 T e n s o r F l o w TensorFlow TensorFlow 程序。它可以将训练过程中的各种信息以可视化的方式展示出来,包括但不限于:
- 损失函数和评估指标的变化趋势。
- 模型的计算图结构。
- 训练过程中的权重、偏置等参数的分布和直方图。
- 图像、音频和文本数据的可视化。
常用的简单的方法介绍:
add_scalar(tag, scalar_value, global_step=None, walltime=None)
- t a g tag tag(必需): 一个字符串,作为这个标量数据的唯一标识符。它将作为标签出现在 TensorBoard 的界面中,用于区分不同的标量数据序列。例如,可以使用 “training_loss” 表示训练损失,“validation_loss” 表示验证损失等。
- s c a l a r scalar scalar_ v a l u e value value(必需):要记录的标量数据的值。可以是 Python 的内置数据类型,如 int 或 float,也可以是 PyTorch 的标量张量(torch.Tensor 且 tensor.numel() == 1)。例如,在训练深度学习模型时,可以是损失函数的值、准确率等。
- g l o b a l global global_ s t e p step step(可选):一个整数,表示当前的步骤或迭代次数。通常在训练过程中会随着训练的进行而递增,用于在时间序列上对 scalar_value 进行排序。如果未提供,默认为 None。在 TensorBoard 中,不同 global_step 下的 scalar_value 会形成一条曲线,显示该标量随时间的变化。
- w a l l t i m e walltime walltime(可选):一个浮点数,表示记录数据的时间戳,默认为 None。如果未提供,将使用当前时间。
1. 代码中进行打点记录 1.代码中进行打点记录 1.代码中进行打点记录
from torch.utils.tensorboard import SummaryWriter
# SummaryWriter 主要用于将训练过程中的各种信息记录下来,以便后续使用 TensorBoard 进行可视化
# 'logs' 是一个字符串参数,指定了存储日志文件的目录路径。
# 当你创建了 SummaryWriter 对象后,在后续的代码中,可以使用该对象将训练过程中的标量、图像、直方图等信息写入到这个目录下的日志文件中。
writer = SummaryWriter('logs')
for i in range(100):
writer.add_scalar("y = x", i, i) # add_scalar 表示增加标量 第一个 i 表示 y,第二个 i 表示 x
writer.close()
2. 启动 T e n s o r B o a r d 显示打点记录 2. 启动TensorBoard显示打点记录 2.启动TensorBoard显示打点记录
在终端使用tensorboard --logdir=logs --port=6007 命令打开日志文件,端口可以设置为其他的。
add_image(tag, img_tensor, global_step=None, walltime=None, dataformats='CHW')
- t a g tag tag(必需):一个字符串,作为这个图像数据的唯一标识符。它将作为标签出现在 TensorBoard 的界面中,用于区分不同的图像数据。例如,可以使用 “input_image” 表示输入的图像,“output_image” 表示输出的图像等。
- i m g img img_ t e n s o r tensor tensor(必需):一个表示图像的张量。张量的形状和 dataformats 参数有关,不同的数据格式对张量的形状有不同的要求。
- g l o b a l global global_ s t e p step step(可选):一个整数,表示当前的步骤或迭代次数。通常在训练过程中会随着训练的进行而递增,用于在时间序列上对图像进行排序。如果未提供,默认为 None。
- w a l l t i m e walltime walltime(可选):一个浮点数,表示记录数据的时间戳,默认为 None。如果未提供,将使用当前时间。
- d a t a f o r m a t s dataformats dataformats(可选):一个字符串,用于指定图像张量的格式。默认为 ‘CHW’,表示图像张量的维度顺序是 (C, H, W),即通道、高度、宽度。也可以设置为 ‘HWC’,表示维度顺序是 (H, W, C)。
# 除了通过记录点绘制上述的图像外,还可用于图像的显示。
import numpy as np # 导入 numpy 库是用于图像数据的转换。
img_path = "test/train/bees_image/16838648_415acd9e3f.jpg" # 此处使用的是相对地址
img = Image.open(img_path)
img_array = np.array(img) # 将图像数据转换为数组进行储存
# 第一个参数是日志文件中图像区间的 名字,第二个参数是 图像数据(必须是 array型、tensor型等),第三个参数是 步长
# 每个步长存储相应步长下的数据,最后一个参数是设置图像数据的格式,H → 高,W → 宽,C → 通道
# 当使用相同名字的图来记录数据时,步长是有用的,当使用不同名字时,可以不设置步长
writer.add_image("test", img_array, 0, dataformats = "HWC")
T i p s : Tips: Tips:
- 在计算机中,图像可以以 HWC、CHW 等格式存储,H 和 W 分别表示图像的高和宽, C 表示图像的通道,如果图像以 RGB 的形式存储,那么通道数有 3 个,RGB 分别表示 红色、绿色和蓝色; 如果是灰度图像,那么通道数就只有 1 个。
- 一般来说,图像的分辨率指的就是图像的 W*H,图像 W 表示图像的宽度是多少个像素点,H 表示图像的高度是多少个像素点。比如图像的分辨率是 512 * 300,那么图像的宽就是 512 个像素,高是 300 个像素。
- 图像的 H 和 W 可以表示所有的像素点的个数,每个像素点都有三个通道的值,三个通道的值都确定后,R、G、B 的强度才能够知道。
- 对于灰度图像而言,像素值的大小表示的是该像素点亮度的大小,0 表示是黑色,255 表示白色。
Transform 进行图像变换
T r a n s f o r m Transform Transform 主要是用于图像变换的操作,可以对图像进行裁剪、标准化等,在使用前需要先将图像转换成张量的形式。
T r a n s f o r m Transform Transform 属于 t o r c h v i s i o n torchvision torchvision 这个库, t o r c h v i s i o n torchvision torchvision 是专门用于计算机视觉的库,所谓的计算机视觉就是让计算机能够读取图像和视频数据并进行相关的处理,如图像识别、图像分类等。
from PIL import Image
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms
img_path = "test/train/bees_image/16838648_415acd9e3f.jpg"
img = Image.open(img_path)
# 0. ToTensor -- PIL转换为张量类型
trans_tensor = transforms.ToTensor()
# tensor 类型的数据和 PIL image的区别在于 前者是 (C, H, W) 的形式储存的,后者则是 (H, W, C) 的格式储存的,
# 前者像素值是在 0.0 ~ 1.0 之间,后者则是在 0 ~ 255 之间。转换成 tensor 类型的数据更适合用于图像处理的操作。
img_tensor = trans_tensor(img) # 能够这样使用的原因是 ToTensor 这个类已经重载了 __call__ 方法。
writer = SummaryWriter("logs")
writer.add_image("Tensor", img_tensor, 0) # 0 表示从第 0 个步长开始记录数据
# 1. Normalization -- 归一化
tensor_norm = transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
# 用于对张量进行归一化的操作,第一个参数表示标准化的均值列表,表示 RGB 三个通道的均值。每个通道上的值会减去均值。
# 第二个参数是标准化的标准差列表,表示 RGB 三个通道的标准差。每个通道上的值会除以对应通道的标准差。
img_norm = tensor_norm(img_tensor) # 能够这样使用的原因是 Normalize 这个类已经改写了 __call__ 方法。
writer.add_image("Normalization", img_norm, 0)
# 2. Resize -- 转换图片大小
trans_resize = transforms.Resize((512, 512)) # 裁剪图像
img_resize = trans_resize(img_tensor)
writer.add_image("Resize", img_resize)
# 3. Compose -- 类似过滤器链模式,可以组合多个Transforms
trans_resize_2 = transforms.Resize((512, 512)) # 将裁剪图像和将图像转换成张量的步骤合并了
trans_comp = transforms.Compose([trans_resize_2, trans_tensor])
# 在最新版的 PyTorch 中,transforms.Resize 进行了优化,可以根据传入的输入类型自动进行适当的处理。
# 它可以直接处理 PIL 图像和 Tensor 图像,无需手动在 ToPILImage 和 ToTensor 之间进行转换。
img_comp = trans_comp(img)
writer.add_image("Resize", img_comp, 1)
# 4. RandomCrop -- 随机裁剪
trans_crop = transforms.RandomCrop((100,100), 50)
# RandomCrop 类可以随机从图像数据中截取一个 100*100 的数据,50 表示的是 padding,即图像周围的填充,默认填充 0(黑色)
for i in range(10):
img_crop = trans_crop(img_tensor)
writer.add_image("RandomCrop", img_crop, i)
writer.close()
数据集的下载
t o r c h v i s i o n . d a t a s e t s . C I F A R 10 torchvision.datasets.CIFAR10 torchvision.datasets.CIFAR10 是 P y T o r c h PyTorch PyTorch 中方便使用 C I F A R − 10 CIFAR-10 CIFAR−10 数据集的工具,通过不同的参数可以灵活地指定数据集存储位置、加载训练集还是测试集、进行数据转换操作以及是否下载数据集。
核心参数解释:
- r o o t root root(必需):字符串类型,表示数据集的存储根目录。如果数据集不存在于该目录下,会根据 download 参数决定是否下载数据集到此目录。例如,如果 root=“./data”,那么数据集将存储在当前目录下的 data 文件夹中。
-
t
r
a
i
n
train
train(可选,默认为 True):布尔类型,用于指定是加载训练集还是测试集。
- 当 train=True 时,加载 CIFAR-10 的训练集,包含 50,000 张图像。
- 当 train=False 时,加载 CIFAR-10 的测试集,包含 10,000 张图像。
-
t
r
a
n
s
f
o
r
m
transform
transform(可选,默认为 None):可调用对象(通常是 torchvision.transforms 中的函数或自定义函数),用于对数据集中的图像进行转换操作。
- 例如,可以使用 transform=torchvision.transforms.ToTensor() 将图像转换为 PyTorch 的 Tensor 类型,将图像从 PIL Image 的 (H, W, C) 格式转换为 (C, H, W) 格式,并将像素值从 [0, 255] 范围转换为 [0, 1] 范围。
-
t
a
r
g
e
t
t
r
a
n
s
f
o
r
m
target_transform
targettransform(可选,默认为 None):可调用对象,用于对数据集中的标签进行转换操作。
- 例如,可以使用 target_transform=lambda x: torch.tensor(x) 将标签转换为 PyTorch 的 Tensor 类型。
-
d
o
w
n
l
o
a
d
download
download(可选,默认为 False):布尔类型,用于决定是否从网络下载数据集。
- 当 download=True 且数据集不存在于 root 目录下时,会自动从网络下载数据集;
- 当 download=False 且数据集不存在时,会引发异常。
PyCharm编码下载CIFAR10训练集和测试集:
C I F A R − 10 CIFAR-10 CIFAR−10 是一个广泛使用的图像分类数据集,它包含 60,000 张 32x32 像素的彩色图像,分为 10 个不同的类别,每个类别有 6,000 张图像。
- 这 10个类别包括:飞机(airplane)、汽车(automobile)、鸟(bird)、猫(cat)、鹿(deer)、狗(dog)、青蛙(frog)、马(horse)、船(ship)和卡车(truck)。
import torchvision
from torch.utils.tensorboard import SummaryWriter
# 定义图像转换操作,将图像转换为 Tensor 类型
dataset_transforms = torchvision.transforms.Compose([torchvision.transforms.ToTensor()])
# 加载 CIFAR-10 训练集
dataset_train = torchvision.datasets.CIFAR10(root="./Datasets", train=True, transform=dataset_transforms, download=True)
# 加载 CIFAR-10 测试集
dataset_test = torchvision.datasets.CIFAR10(root="./Datasets", train=False, transform=dataset_transforms, download=True)
# 创建一个 SummaryWriter 对象,将日志存储在 "dataset" 目录下
writer = SummaryWriter("dataset")
# 从测试集中选取前 10 张图像添加到 TensorBoard 中
for i in range(10):
# 返回值包括图像和标签,图像是 torch.Tensor 类型,标签是整数类型。
# 对于 CIFAR-10 数据集,标签的范围是 0 到 9,分别对应 10 个类别:
# 0: airplane
# 1: automobile
# 2: bird
# 3: cat
# 4: deer
# 5: dog
# 6: frog
# 7: horse
# 8: ship
# 9: truck
img, target = dataset_test[i]
writer.add_image("dataset", img, i)
# 关闭 SummaryWriter
writer.close()
DataLoader
D a t a L o a d e r DataLoader DataLoader 是 P y T o r c h PyTorch PyTorch 中 t o r c h . u t i l s . d a t a torch.utils.data torch.utils.data 模块里的一个类,主要用于将数据集包装成一个可迭代的数据加载器,它提供了对数据集的多进程迭代器,可以实现批量加载、数据打乱、并行加载等功能,是深度学习中数据处理的重要工具。
D a t a L o a d e r DataLoader DataLoader具有以下优势:
- 批量加载:
- 可以方便地将数据以批次的形式提供给模型,符合深度学习中基于批次的训练方式,有助于利用硬件的并行性,提高训练效率。
- 数据打乱:
- 通过设置 shuffle=True,可以在每个 epoch 开始时打乱数据,避免模型对数据顺序产生依赖,提高模型的泛化能力。
- 多进程加载:
- 使用 num_workers 参数可以开启多进程数据加载,提高数据加载的速度,尤其是对于大规模数据集和复杂的数据预处理操作。
D a t a L o a d e r DataLoader DataLoader参数详解:
-
d
a
t
a
s
e
t
dataset
dataset(必需):
- 要加载的数据集对象,可以是 torch.utils.data.Dataset 的子类,如 torchvision.datasets.MNIST 或自定义数据集。
-
b
a
t
c
h
batch
batch_
s
i
z
e
size
size(常用):
- 每个批次的数据量,决定了每次迭代从数据集中获取多少个样本。在训练深度学习模型时,合适的 batch_size 可以影响训练的速度和稳定性。
-
s
h
u
f
f
l
e
shuffle
shuffle(常用):
- 是否在每个 epoch 开始时打乱数据。在训练过程中,通常设置为 True 以增加训练的随机性和模型的泛化能力;在测试和验证时,通常设置为 False。
-
n
u
m
num
num_
w
o
r
k
e
r
s
workers
workers:
- 用于数据加载的子进程数量,设置为 0 表示在主进程中加载数据,大于 0 表示使用多个子进程进行数据加载,可以加速数据加载过程,但会消耗更多的系统资源。
-
d
r
o
p
drop
drop_
l
a
s
t
last
last:
- 当数据集的样本数量不能被 batch_size 整除时,是否丢弃最后一个不完整的批次。如果 drop_last=True,则丢弃;如果 drop_last=False,则保留最后一个不完整的批次。
-
p
i
n
pin
pin_
m
e
m
o
r
y
memory
memory:
- 若为 True,会将加载的数据 Tensor 存储在固定内存(pinned memory)中,加快数据传输到 GPU 的速度,对于 GPU 训练非常有用。
代码的简单使用演示:
import torchvision
from torch.utils.tensorboard import SummaryWriter
from torch.utils.data import DataLoader
test_set = torchvision.datasets.CIFAR10("./Datasets", train=False, transform=torchvision.transforms.ToTensor(), download=False)
test_load = DataLoader(dataset=test_set, batch_size=64, shuffle=True, num_workers=0, drop_last=False)
# 这里测试集的载入规则是每个批次载入 64 个图片,shuffle 为 True 表示每次重新使用这个测试集时都打乱顺序,
# num_workers 指定用于数据加载的工作线程数,Windows 中通常设为 0。drop_last 表示如果最后批次的图像数据不能凑齐 64 个,就舍弃
writer = SummaryWriter("dataloader")
for epoch in range(2): # 进行两个周期(epoch) 的训练 -- 每个epoch会分批次看完一轮所有训练集的数据
step = 0 # step 表示的是每个周期中训练的轮数,与 batch_size 的值有关。
for i in test_load:
imgs, targets = i # 参数的分配参照的是 CIFAR10 中的 __getitem__ 方法。
writer.add_images(f"Epoch:{epoch}", imgs, step) # 注意,这里是 add_images,适用于每个批次不止一张图片的情况
# 如果每个 step 只有一张图片显示,那么使用 add_image。
step = step + 1
writer.close()
Module 自定义网络
n n . M o d u l e nn.Module nn.Module 模块作用:
-
模型的模块化组织:
- nn.Module 为构建神经网络提供了一个基类,通过继承它,可以将网络中的不同组件(如层、激活函数、池化层等)封装成一个逻辑单元。这种模块化的组织方式使得代码结构清晰,易于理解和维护。
- 例如,在构建一个卷积神经网络 (CNN) 时,可以将卷积层、池化层、全连接层等作为不同的模块,然后在
__init__
方法中进行初始化,在 forward 方法中定义它们之间的连接和数据的传递路径。
-
参数管理:
- 自动管理网络中的可训练参数(如权重和偏置)。当你在 nn.Module 的子类中定义层(如 nn.Linear、nn.Conv2d 等)时,这些层的参数会自动添加到模块的参数列表中。
- 可以使用 parameters() 或 named_parameters() 方法来访问这些参数,方便将它们传递给优化器进行训练。例如:
-
前向传播的定义:
- 要求在继承 nn.Module 的子类中实现 forward 方法,该方法定义了数据在网络中的流动方式,即前向传播的逻辑。
- 当调用 net(input_tensor) 时,实际上是调用 net.forward(input_tensor),实现了输入数据在网络中的处理过程。
-
设备迁移:
- nn.Module 提供了 to 方法,方便将整个网络及其参数移动到不同的设备(如 CPU 或 GPU)。
- 这个功能对于在 GPU 上加速深度学习计算非常重要,因为 GPU 可以显著加快训练和推理的速度。
-
训练和评估模式切换:
- nn.Module 提供了 train() 和 eval() 方法,用于将网络切换到训练模式或评估模式。
- 一些层(如 nn.Dropout 和 nn.BatchNorm2d)在训练和评估模式下的行为不同。例如,nn.Dropout 在训练时会随机丢弃一些神经元,而在评估时不进行丢弃;nn.BatchNorm2d 在训练时使用当前批次的统计信息更新均值和方差,在评估时使用之前保存的统计信息。
-
模型保存和加载:
- 可以使用 state_dict() 方法获取网络的状态字典,它存储了网络的参数信息,方便保存和加载模型。
前向传播
import torch
import torch.nn as nn
class SimpleNet(nn.Module):
def __init__(self):
super(SimpleNet, self).__init__()
# 定义网络的层
self.fc1 = nn.Linear(10, 5) # 全连接层,输入维度为 10,输出维度为 5
self.relu = nn.ReLU() # ReLU 激活函数
self.fc2 = nn.Linear(5, 2) # 全连接层,输入维度为 5,输出维度为 2
def forward(self, x):
# 前向传播过程
x = self.fc1(x) # 输入 x 通过第一个全连接层
x = self.relu(x) # 使用 ReLU 激活函数
x = self.fc2(x) # 通过第二个全连接层
return x
# 创建一个 SimpleNet 实例
net = SimpleNet()
# 生成一个随机输入张量,形状为 (batch_size, input_size),这里 batch_size = 3,input_size = 10
input_tensor = torch.randn(3, 10)
# 进行前向传播 -- net()会调用__call__方法,__call__方法会调用forward
output_tensor = net(input_tensor)
print(f"Input tensor shape: {input_tensor.shape}")
print(f"Output tensor shape: {output_tensor.shape}")
输出:
Input tensor shape: torch.Size([3, 10])
Output tensor shape: torch.Size([3, 2])
卷积层
核心函数 c o n v 2 d conv2d conv2d介绍:
torch.nn.functional.conv2d
c o n v 2 d conv2d conv2d 函数的输入通常是一个四维张量,其形状为 (batch_size, in_channels, height, width)。这四个维度分别代表:
- b a t c h batch batch_ s i z e size size:表示输入数据的批次大小,即同时处理的样本数量。在深度学习中,通常会同时处理多个样本,以提高计算效率。例如,在训练过程中,会一次性将多个样本输入到模型中进行前向传播。
-
i
n
in
in_
c
h
a
n
n
e
l
s
channels
channels:表示输入数据的通道数。对于彩色图像,通道数通常为 3(RGB);对于灰度图像,通道数为 1。对于更复杂的数据,可能有更多的通道,如医学图像中的不同模态或多光谱图像等。
- 对于卷积核,也需要考虑输入通道数,因为卷积操作会对每个输入通道进行卷积,然后将结果相加得到最终结果。例如,对于一个 in_channels = 3 的输入,卷积核也需要有 3 个通道,以匹配输入的通道结构。
- h e i g h t height height 和 w i d t h width width:表示输入数据的高度和宽度,也就是图像或特征图的空间维度。
import torch
import torch.nn.functional as F
# 定义输入张量,形状为 (5, 5)
input = torch.tensor([
[1, 2, 0, 3, 1],
[0, 1, 2, 3, 1],
[1, 2, 1, 0, 0],
[5, 2, 3, 1, 1],
[2, 1, 0, 1, 1]
])
# 定义卷积核张量,形状为 (3, 3)
kernel = torch.tensor([
[1, 2, 1],
[0, 1, 0],
[2, 1, 0]
])
# 将输入张量重塑为 (1, 1, 5, 5),符合 conv2d 函数的输入格式
# 1 表示批次数,1 表示输入通道数,5 表示高度,5 表示宽度
input = torch.reshape(input, [1, 1, 5, 5])
# 将卷积核张量重塑为 (1, 1, 3, 3)
# 1 表示输入通道数,1 表示输出通道数,3 表示卷积核高度,3 表示卷积核宽度
kernel = torch.reshape(kernel, [1, 1, 3, 3])
# 使用 conv2d 函数进行卷积操作,步长为 1
output = F.conv2d(input, kernel, stride=1)
# 打印卷积结果
print(output)
输出:
tensor([[[[10, 12, 12],
[18, 16, 16],
[13, 9, 3]]]])
c o n v 2 d conv2d conv2d 函数相关参数作用如下动图所示:
动态图所在 github 页面链接
T i p s : R e s h a p e 方法介绍 Tips: Reshape方法介绍 Tips:Reshape方法介绍
- 函数功能: reshape 函数的主要功能是将输入的张量 input 重新调整为指定的 shape 形状。当可能的时候,返回的张量将是输入张量的一个视图(view),否则将是一个拷贝。
- 参数:
- input (Tensor): 需要重新调整形状的张量。
- shape (tuple of int): 新的形状。形状可以包含一个维度为 1,此时该维度的大小将从剩余的维度和元素数量推断出来。
- 视图与拷贝:
- 视图:如果输入张量是连续的(contiguous)且其步幅(strides)是兼容的,reshape 可以返回一个视图,这意味着数据不会被复制,输出的张量和输入的张量共享数据。
- 拷贝:在某些情况下,reshape 需要复制数据以实现新的形状。
- 什么是“步幅是兼容的“?
- 步幅兼容:如果一个张量在内存中的排列方式(即步幅)允许它被重新解释为另一个形状,而不需要重新排列数据,这样的步幅就是兼容的。在这种情况下,重塑后的张量可以是原张量的一个视图(view),这意味着没有实际的数据拷贝,重塑操作是非常高效的。
- 人性化的理解方式:假设通过积木搭建了一个正方形的相框,从正面看,它是正方形的,但是从另一个角度看,它就是一条直线,也就是说,不同视角下看待数据会有不同的表现形式。数据在内存条中的存放方式是连续的,如果我定义了一个 3x3 的矩阵,那么在内存条中的长度就是 9 个字节,因此我也可以将它看作是 1 x 9 的或是 9 x 1 的矩阵,因为它们在内存条中的存在形式相同。因此,reshape 就是在保证数据在内存条中的存在形式不变的情况下,改变我们观察的视角。
卷积简单应用
import torchvision
import torch.nn as nn
import torch
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
# 加载 CIFAR-10 测试数据集,并将其转换为 Tensor 类型
dataset = torchvision.datasets.CIFAR10("./Datasets", train=False, transform=torchvision.transforms.ToTensor(), download=False)
# 使用 DataLoader 包装数据集,设置批次大小为 64
dataloader = DataLoader(dataset=dataset, batch_size=64)
# 定义一个自定义的卷积神经网络 Mynn,继承自 nn.Module
class Mynn(nn.Module):
def __init__(self):
super(Mynn, self).__init__()
# 定义一个卷积层,输入通道为 3,输出通道为 6,卷积核大小为 3x3,步长为 1
# 每个卷积核会对输入张量的所有通道进行卷积操作,并将结果相加得到一个输出通道。
# 因此,有多少个输出通道,就有多少个卷积核。
self.conv1 = nn.Conv2d(in_channels=3, out_channels=6, kernel_size=3, stride=1)
def forward(self, x):
# 前向传播过程,将输入通过卷积层
output = self.conv1(x)
return output
# 创建 Mynn 网络实例
mynn = Mynn()
# 创建 SummaryWriter,将日志存储在 "./logs" 目录下
writer = SummaryWriter("./logs")
# 初始化步骤计数器
step = 0
# 遍历数据加载器
for data in dataloader:
# 从数据加载器中获取一个批次的数据,包括图像和标签
imgs, targets = data
# 将一个批次的图像输入到网络中进行前向传播
output = mynn(imgs)
# 打印输入和输出的形状
print(imgs.shape)
print(output.shape)
# 将输入图像添加到 TensorBoard 中
writer.add_images("input", imgs, step)
# 将输出的特征图进行形状重塑,以便添加到 TensorBoard 中
output = torch.reshape(output, (-1, 3, 30, 30))
# 将重塑后的输出添加到 TensorBoard 中
writer.add_images("output", output, step)
# 增加步骤计数器
step = step + 1
# 关闭 SummaryWriter
writer.close()
输出:
torch.Size([16, 3, 32, 32])
torch.Size([16, 6, 30, 30])
最大池化
最大池化( M a x P o o l i n g Max Pooling MaxPooling)是一种常用的下采样( s u b s a m p l i n g subsampling subsampling)或降采样( d o w n s a m p l i n g downsampling downsampling)操作,主要用于卷积神经网络( C o n v o l u t i o n a l N e u r a l N e t w o r k s Convolutional Neural Networks ConvolutionalNeuralNetworks, C N N s CNNs CNNs)中。它通过取局部区域内的最大值来减少特征图的尺寸,同时保留重要的特征,从而达到降低计算量和防止过拟合的目的。
与卷积操作类似,池化操作也是通过一个核来扫描数据的,这个核称为 池化核,只不过,在扫描的过程中,不是在池化核内部做相乘求和的操作,而是在池化核的内部取出数据的最大值作为该区域的主要特征。
最大池化主要用到的类是 M a x P o o l 2 d MaxPool2d MaxPool2d(→针对二维图像数据),其中需要着重注意一下 ceil_mode 参数和 dilation 参数。
关于 c e i l ceil ceil_ m o d e mode mode 参数:
- ceil_mode=False(默认):使用向下取整(floor)来计算输出特征图的尺寸。这意味着如果池化窗口在输入特征图上滑动时,最后一部分窗口可能会覆盖不完全的区域,直接忽略这部分数据。
- ceil_mode=True:使用向上取整(ceil)来计算输出特征图的尺寸。这意味着即使池化窗口在输入特征图上滑动时,最后一部分窗口覆盖的区域不完整,也会将其考虑在内,并进行池化操作。
关于
d
i
l
a
t
i
o
n
dilation
dilation 参数:
d
i
l
a
t
i
o
n
dilation
dilation 表示的是池化核内部各个元素之间的
s
t
r
i
d
e
stride
stride,也就是 步幅。
import torchvision
import torch.nn as nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
# 加载 CIFAR-10 测试数据集,并将其转换为 Tensor 类型
dataset = torchvision.datasets.CIFAR10("./Datasets", train=False, transform=torchvision.transforms.ToTensor())
# 使用 DataLoader 包装数据集,设置批次大小为 64
dataloader = DataLoader(dataset=dataset, batch_size=64)
# 创建 SummaryWriter,将日志存储在 "logs" 目录下
writer = SummaryWriter("logs")
# 初始化步骤计数器
step = 0
# 定义一个自定义的神经网络模块,包含一个最大池化层
class My_nn(nn.Module):
def __init__(self):
super(My_nn, self).__init__()
# 定义一个最大池化层,核大小为 3x3,步长为 3,无填充,ceil_mode=True
self.maxpool1 = nn.MaxPool2d(kernel_size=3, stride=3, padding=0, ceil_mode=True)
def forward(self, x):
# 前向传播过程,将输入通过最大池化层
output = self.maxpool1(x)
return output
# 创建 My_nn 网络实例
my_nn = My_nn()
# 遍历数据加载器
for data in dataloader:
# 从数据加载器中获取一个批次的数据,包括图像和标签
imgs, targets = data
# 将一个批次的图像输入到网络中进行前向传播
output = my_nn(imgs)
# 将输入图像添加到 TensorBoard 中
writer.add_images("maxpool_input", imgs, step)
# 将最大池化层的输出添加到 TensorBoard 中
writer.add_images("maxpool_output", output, step)
# 增加步骤计数器
step = step + 1
# 关闭 SummaryWriter
writer.close()
输出:
非线性层
R e L U ReLU ReLU,全称为 R e c t i f i e d Rectified Rectified L i n e a r Linear Linear U n i t Unit Unit(修正线性单元),是一种在神经网络中常用的激活函数。 R e L U ReLU ReLU 的数学表达式是 f ( x ) = m a x ( 0 , x ) f(x) = max(0,x) f(x)=max(0,x)
即当输入值小于 0 时输出为 0,当输入值大于等于0时输出为输入值本身。
R e L U ReLU ReLU 的名字来源于它的数学表达式中的“修正”操作( R e c t i f i e d Rectified Rectified)和它是一个线性单元( L i n e a r U n i t Linear Unit LinearUnit)。“修正”指的是输入值小于0时被修正为 0,而“线性单元”指的是输入值大于等于 0 时保持线性关系。
在神经网络中, R e L U ReLU ReLU 的主要作用是引入非线性。非线性激活函数使神经网络能够学习和表示复杂的模式和特征。如果没有非线性激活函数,神经网络无论有多少层,最终都会退化为一个线性模型,无法解决复杂的任务。
R e L U ReLU ReLU 具有以下优点:
- 计算简单: R e L U ReLU ReLU 的计算非常简单,只需要判断输入是否大于0。
- 缓解梯度消失问题:相比于传统的 S i g m o i d Sigmoid Sigmoid 和 T a n h Tanh Tanh 激活函数, R e L U ReLU ReLU 能有效缓解梯度消失问题,因为它的梯度在正区间是常数1。
R e L U ReLU ReLU 与卷积和池化的先后关系 , 在卷积神经网络( C N N CNN CNN)中,通常的操作顺序是:
- 卷积层( C o n v o l u t i o n a l L a y e r Convolutional Layer ConvolutionalLayer):应用卷积核对输入进行卷积操作,提取特征。
- 激活层( A c t i v a t i o n L a y e r Activation Layer ActivationLayer):在卷积操作之后,通常会应用 R e L U ReLU ReLU 激活函数对卷积层的输出进行非线性变换。
- 池化层( P o o l i n g L a y e r Pooling Layer PoolingLayer):对激活后的输出进行下采样,减少数据量,提取主要特征,常见的池化方法有最大池化( M a x P o o l i n g Max Pooling MaxPooling)和平均池化( A v e r a g e P o o l i n g Average Pooling AveragePooling)。
这个顺序通常是卷积 -> 激活(ReLU)-> 池化
。这种结构可以重复多次,形成深层的卷积神经网络。在具体实现时,也可以根据需求调整顺序或组合方式。
通过这种结构,卷积层负责提取特征,激活层引入非线性,池化层则负责下采样,减少计算量和模型复杂度,使得网络能够在复杂的任务中表现良好。
import torchvision
from torch.utils.tensorboard import SummaryWriter
import torch.nn as nn
from torch.utils.data import DataLoader
# 加载 CIFAR-10 数据集,仅使用测试集,同时将数据转换为张量
dataset = torchvision.datasets.CIFAR10("./Datasets", train=False, transform=torchvision.transforms.ToTensor())
# 创建数据加载器,将数据集按批次加载,每个批次包含 64 个样本
dataloader = DataLoader(dataset=dataset, batch_size=64)
# 自定义神经网络类,继承自 nn.Module
class My_nn(nn.Module):
def __init__(self):
super(My_nn, self).__init__()
# 定义 ReLU 激活函数
self.relu1 = nn.ReLU()
# 定义 Sigmoid 激活函数
self.sigmoid = nn.Sigmoid()
def forward(self, x):
# 前向传播过程
# 将输入 x 通过 ReLU 激活函数得到输出 output_1
output_1 = self.relu1(x)
# 将输入 x 通过 Sigmoid 激活函数得到输出 output_2
output_2 = self.sigmoid(x)
# 返回两个激活函数的输出结果
return output_1, output_2
# 实例化自定义的神经网络
my_nn = My_nn()
# 创建 SummaryWriter 对象,用于将数据记录到 "./ReLU_logs" 目录下,以便使用 TensorBoard 进行可视化
writer = SummaryWriter("./ReLU_logs")
# 初始化步骤计数器
step = 0
# 遍历数据加载器,按批次处理数据
for data in dataloader:
# 从批次数据中分离出图像数据 imgs 和目标标签 targets
imgs, targets = data
# 将图像数据 imgs 输入到自定义神经网络中,得到 ReLU 和 Sigmoid 激活函数的输出
output1, output2 = my_nn(imgs)
# 将 ReLU 激活函数的输出添加到 TensorBoard 的 "ReLU" 部分,并使用 step 作为当前步骤的标识
writer.add_images("ReLU", output1, step)
# 将 Sigmoid 激活函数的输出添加到 TensorBoard 的 "Sigmoid" 部分,并使用 step 作为当前步骤的标识
writer.add_images("Sigmoid", output2, step)
# 步骤计数器加 1
step += 1
writer.close()
输出:
线性层
在神经网络中,线性层(
L
i
n
e
a
r
L
a
y
e
r
Linear Layer
LinearLayer)通常是指全连接层(
F
u
l
l
y
C
o
n
n
e
c
t
e
d
L
a
y
e
r
Fully Connected Layer
FullyConnectedLayer)。其主要作用是对输入数据进行线性变换,即执行矩阵乘法和加法操作。具体来说,对于输入向量
x
x
x 和权重矩阵
W
W
W,线性层的输出是
W
∗
x
+
b
W*x+b
W∗x+b,其中
b
b
b 是偏置向量。
线性层的作用
- 特征变换: 线性层可以将输入特征映射到一个新的特征空间,这对特征提取和表示学习非常重要。
- 维度变化: 通过指定不同的输入和输出维度,线性层可以调整数据的维度。
- 参数学习: 线性层中的权重和偏置是可训练参数,可以通过反向传播算法进行优化。
卷积神经网络( C N N CNN CNN)中线性层和其他层的关系:
- 卷积层 ( C o n v o l u t i o n a l L a y e r Convolutional Layer ConvolutionalLayer): 对输入进行卷积操作,提取局部特征。例如:torch.nn.Conv2d
- 非线性激活函数 ( N o n − l i n e a r A c t i v a t i o n F u n c t i o n Non-linear Activation Function Non−linearActivationFunction): 引入非线性,使得网络能够表示更复杂的函数。例如:torch.nn.ReLU
- 池化层 ( P o o l i n g L a y e r Pooling Layer PoolingLayer): 对特征图进行降采样,减少尺寸和计算量,防止过拟合。例如:torch.nn.MaxPool2d
重复上述三步多次,逐步提取高层次特征。
- 展平层 ( F l a t t e n L a y e r Flatten Layer FlattenLayer): 将多维的特征图展开为一维向量,以便输入到全连接层。例如:torch.flatten()
- 线性层 ( L i n e a r L a y e r Linear Layer LinearLayer):对特征进行线性变换,通常用于分类或回归任务的最后几层。例如:torch.nn.Linear
- 输出层 ( O u t p u t L a y e r Output Layer OutputLayer):最后一层通常是一个线性层,后接适当的激活函数(如 softmax 用于分类)。例如:torch.nn.Softmax
import torch
import torchvision
from torch.utils.data import DataLoader
import torch.nn as nn
# 加载 CIFAR-10 数据集,使用测试集,将数据转换为张量
datasets = torchvision.datasets.CIFAR10("Datasets", train=False, transform=torchvision.transforms.ToTensor())
# 使用 DataLoader 将数据集按批次加载,批次大小为 64
dataloader = DataLoader(dataset=datasets, batch_size=64)
# 自定义神经网络类,继承自 nn.Module
class My_nn(nn.Module):
def __init__(self):
super(My_nn, self).__init__()
# 定义一个线性层,输入维度为 196608,输出维度为 10
self.linear = nn.Linear(196608, 10)
def forward(self, x):
# 前向传播过程,将输入通过线性层
output = self.linear(x)
return output
# 实例化自定义的神经网络
my_nn = My_nn()
# 遍历数据加载器
for data in dataloader:
# 获取一个批次的数据,包括图像和目标标签
imgs, targets = data
# 打印原始图像数据的形状
print(imgs.shape)
# 将图像数据展平为一维张量
output = torch.flatten(imgs)
# 打印展平后的张量形状
print(output.shape)
# 将展平后的张量输入到神经网络中
output = my_nn(output)
# 打印经过神经网络后的输出形状
print(output.shape)
输出:
torch.Size([64, 3, 32, 32])
torch.Size([196608])
torch.Size([10])
简单的整合
训练
C
I
F
A
R
10
CIFAR10
CIFAR10 数据集采用的网络结构如下图所示:
图片来源
conv2d 的 padding 参数值计算公式如下:
公式对应官网文档链接
import torch
from torch import nn
from torch.nn import Sequential, Conv2d, MaxPool2d, Flatten, Linear
from torch.utils.tensorboard import SummaryWriter
class my_nn(nn.Module):
def __init__(self):
super(my_nn, self).__init__()
# 使用 Sequential 容器组合多个层
self.model = Sequential(
# 第一个卷积层:输入通道为 3,输出通道为 32,卷积核大小为 5,填充为 2
Conv2d(3, 32, 5, padding=2),
# 第一个最大池化层:核大小为 2
MaxPool2d(2),
# 第二个卷积层:输入通道为 32,输出通道为 32,卷积核大小为 5,填充为 2
Conv2d(32, 32, 5, padding=2),
# 第二个最大池化层:核大小为 2
MaxPool2d(2),
# 第三个卷积层:输入通道为 32,输出通道为 64,卷积核大小为 5,填充为 2
Conv2d(32, 64, 5, padding=2),
# 第三个最大池化层:核大小为 2
MaxPool2d(2),
# 将特征图展平
Flatten(),
# 第一个线性层:输入维度为 1024,输出维度为 64
Linear(1024, 64),
# 第二个线性层:输入维度为 64,输出维度为 10
Linear(64, 10)
)
def forward(self, x):
# 前向传播:将输入通过 Sequential 容器中的层
output = self.model(x)
return output
# 实例化自定义的神经网络
my = my_nn()
# 打印网络结构
print(my)
# 创建一个测试输入张量,形状为 (64, 3, 32, 32) , 值全部为1
input = torch.ones((64, 3, 32, 32))
# 将输入张量传入网络,得到输出
output = my(input)
# 打印输出形状
print(output.shape)
# 创建 SummaryWriter 对象,将日志存储在 "logs_seq" 目录下
writer = SummaryWriter("logs_seq")
# 将网络结构添加到 TensorBoard 中
writer.add_graph(my, input)
# 关闭 SummaryWriter
writer.close()
输出:
my_nn(
(model): Sequential(
(0): Conv2d(3, 32, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
(1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(2): Conv2d(32, 32, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
(3): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(4): Conv2d(32, 64, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
(5): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(6): Flatten(start_dim=1, end_dim=-1)
(7): Linear(in_features=1024, out_features=64, bias=True)
(8): Linear(in_features=64, out_features=10, bias=True)
)
)
torch.Size([64, 10])
引入反向传播和损失函数: 反向传播算法就是利用损失函数反向更新训练的参数,由于与原来的训练过程是相反的,因此称为反向传播。
# 实例化神经网络
my = my_nn()
# 定义交叉熵损失函数
loss = nn.CrossEntropyLoss()
# 遍历数据加载器
for data in dataloader:
# 获取图像和标签
imgs, targets = data
# 前向传播
output = my(imgs)
# 计算损失
result_loss = loss(output, targets)
# 反向传播
result_loss.backward()
引入优化器: 与反向传播算法结合起来优化每一步的梯度值。基本过程是先定义一个优化器,然后在循环的内部对上一步的梯度值清零,利用反向传播算法计算参数,利用优化器对下一步的参数进行优化。
# 实例化神经网络
my = my_nn()
# 定义交叉熵损失函数,用于分类任务
loss = nn.CrossEntropyLoss()
# 定义随机梯度下降(SGD)优化器,学习率为 0.01
optim = torch.optim.SGD(my.parameters(), lr=0.01)
# 训练 20 个 epoch
for epoch in range(20):
# 初始化每轮的累积损失为 0.0
running_loss = 0.0
# 遍历数据加载器中的每个批次
for data in dataloader:
# 获取图像和目标标签
imgs, targets = data
# 前向传播,将图像输入网络得到输出
output = my(imgs)
# 计算损失
result_loss = loss(output, targets)
# 清空梯度,避免梯度累积
optim.zero_grad()
# 反向传播,计算梯度
result_loss.backward()
# 更新参数,根据梯度更新网络参数
optim.step()
# 累积每批次的损失
running_loss = running_loss + result_loss.item()
# 打印每轮的累积损失
print(running_loss)
T i p s : Tips: Tips:
- optim.zero_grad() 是 PyTorch 优化器(如 torch.optim.SGD、torch.optim.Adam 等)中的一个方法,其主要作用是将优化器管理的所有参数的梯度清零。
- 在 PyTorch 中,当进行反向传播(backward())操作时,梯度会累加到每个参数的 .grad 属性中。在每次进行新的反向传播之前,需要将这些梯度清零,否则梯度会累积,导致错误的梯度更新。
- 例如,在多次调用 backward() 而不调用 zero_grad() 的情况下,每次梯度计算的结果会不断累加到 .grad 属性上,最终导致梯度更新的步长过大,使网络无法正常训练。
基于现有网络模型修改
import torchvision.models
from torch import nn
# 创建不带预训练权重的 VGG16 模型
vgg16_false = torchvision.models.vgg16(weights=None)
# 创建带预训练权重的 VGG16 模型
vgg16_true = torchvision.models.vgg16(weights='DEFAULT')
# 打印带预训练权重的 VGG16 模型结构
print(vgg16_true)
# 对带预训练权重的 VGG16 模型添加一个线性层,将输入特征从 4096 改为 1000,输出特征从 1000 改为 10
vgg16_true.add_module("add_Linear", nn.Linear(1000, 10))
# 再次打印修改后的 VGG16 模型结构
print(vgg16_true)
模型的保存和载入
- 模型的保存
import torch
import torchvision.models
vgg16 = torchvision.models.vgg16()
# 保存方式 1,模型结构和模型参数
torch.save(vgg16, "vgg16_method1.pth") # 引号内部是保存的文件名,最好使用 pth 结尾
# 保存方式 2, 只保存模型参数(官方推荐)
torch.save(vgg16.state_dict(), "vgg16_method2.pth")
- 模型的载入
# 由于 vgg16 是一个标准的 PyTorch 预训练模型,且这个模型类已经定义在 torchvision.models 模块中,
# 因此不需要再载入模型的 py 文件中重新定义类
# 但是对于自己定义的类,在其他 py 文件中导入时,一定要重新定义类,类似于 class My_nn(nn.Module):...
import torch
import torchvision.models
# 保存方式 1 载入模型的方式
model = torch.load("vgg16_method1.pth")
print(model)
# 保存方式 2 载入模型的方式
vgg16 = torchvision.models.vgg16()
vgg16.load_state_dict(torch.load("vgg16_method2.pth"))
print(vgg16)
# 如果方式 2 通过 方式 1 载入模型的代码载入,只会导入一堆模型参数的字典。
完整模型训练流程
还是基于
C
I
F
A
R
10
CIFAR10
CIFAR10 训练集采用的网络结构,下面给出完整的模型训练流程代码:
- model.py 文件
import torch
from torch import nn
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear
class My_nn(nn.Module):
def __init__(self):
super(My_nn, self).__init__()
self.model = nn.Sequential(
# 第一个卷积层:输入通道为 3,输出通道为 32,卷积核大小为 5,步长为 1,填充为 2
Conv2d(3, 32, 5, 1, 2),
# 最大池化层,池化核大小为 2
MaxPool2d(2),
# 第二个卷积层:输入通道为 32,输出通道为 32,卷积核大小为 5,步长为 1,填充为 2
Conv2d(32, 32, 5, 1, 2),
# 最大池化层,池化核大小为 2
MaxPool2d(2),
# 第三个卷积层:输入通道为 32,输出通道为 64,卷积核大小为 5,步长为 1,填充为 2
Conv2d(32, 64, 5, 1, 2),
# 最大池化层,池化核大小为 2
MaxPool2d(2),
# 展平层,将特征图展平为一维向量
Flatten(),
# 第一个线性层:输入维度为 64*4*4,输出维度为 64
Linear(64*4*4, 64),
# 第二个线性层:输入维度为 64,输出维度为 10
Linear(64, 10)
)
def forward(self, x):
# 前向传播函数,将输入 x 通过模型
output = self.model(x)
return output
if __name__ == '__main__':
# 实例化自定义的神经网络
my_nn = My_nn()
# 创建一个形状为 (64, 3, 32, 32) 的输入张量
input = torch.ones((64, 3, 32, 32))
# 将输入张量传递给网络进行前向传播
output = my_nn(input)
# 打印输出张量的形状
print(output.shape)
T i p s : Tips: Tips:
if __name__ == '__main__'
: 是 Python 中的一个条件语句,它的主要作用是将一段代码块限定为仅在该 Python 文件作为脚本直接运行时执行,而在作为模块被导入到其他 Python 文件时不执行。- 当 Python 解释器开始运行一个 .py 文件时,会将该文件中定义的模块的
__name__
属性设置为__main__
。 - 而当一个 .py 文件作为模块被导入到另一个文件时,其
__name__
属性会被设置为该模块的名称(即文件名,不包含 .py 后缀)。
- 当 Python 解释器开始运行一个 .py 文件时,会将该文件中定义的模块的
- train.py 文件
import torchvision
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
from model import * # model.py 文件 与 train.py 文件要在同一个文件夹下
# 导入数据集
dataset_train = torchvision.datasets.CIFAR10("Datasets", train=True, transform=torchvision.transforms.ToTensor())
dataset_test = torchvision.datasets.CIFAR10("Datasets", train=True, transform=torchvision.transforms.ToTensor())
# 加载数据集
dataloader_train = DataLoader(dataset=dataset_train, batch_size=64, shuffle=True)
dataloader_test = DataLoader(dataset=dataset_test, batch_size=64, shuffle=True)
# 训练集的长度
len_train = len(dataset_train)
print(f"训练集的长度为 {len_train}")
# 测试集的长度
len_test = len(dataset_test)
print(f"测试集的长度为 {len_test}")
# 构造模型
model = My_nn()
# 设置训练和测试次数
train_step = 0
test_step = 0
# 设置训练轮数
epoch = 10
# 设置学习率
learning_rate = 1e-2
# 定义优化器
optimise = torch.optim.SGD(model.parameters(), lr=learning_rate)
# 定义损失函数
my_loss = nn.CrossEntropyLoss()
# 构造日志文件
writer = SummaryWriter("Train_Logs")
for i in range(epoch):
print(f"----开始第 {i+1} 轮训练----")
# 训练步骤开始
# 这一行并不是对于所有的网络都是必须的,
# 只有当网络中含有 dropout 层、BatchNorm或是其他一些特殊的层的时候需要使用这一层激活
# 关于这一点说明参见
# https://pytorch.org/docs/stable/generated/torch.nn.Module.html#torch.nn.Module:~:text=in%20evaluation%20mode.-,This%20has%20any%20effect%20only%20on%20certain%20modules.%20See%20documentations%20of%20particular%20modules%20for%20details%20of%20their%20behaviors%20in%20training/evaluation%20mode%2C%20if%20they%20are%20affected%2C%20e.g.%20Dropout%2C%20BatchNorm%2C%20etc.,-This%20is%20equivalent
model.train()
for data in dataloader_train:
imgs, targets = data
outputs = model(imgs)
loss = my_loss(outputs, targets)
optimise.zero_grad()
loss.backward()
optimise.step()
train_step += 1
if train_step % 100 == 0:
print(f"第 {train_step} 步 损失率:{loss.item()}")
writer.add_scalar("train_loss", loss.item(), train_step)
# 测试步骤开始
# 这一行也不是对所有网络都是必须的
model.eval()
total_test_loss = 0
total_accuracy = 0
with torch.no_grad():
for data in dataloader_test:
imgs, targets = data
outputs = model(imgs)
loss = my_loss(outputs, targets)
total_test_loss += loss.item()
# outputs.argmax(1) 表示的是在outputs每一行的所有类别预测中,找到
# 概率最大的一个并返回,返回的结果就是该 img 的标签,由于 out_feature = 10
# 因此标签值是 0 ~ 9 中的一个。outputs.argmax(1) 具有 batch_size 个标签值
# 将该标签列表中与 targets 进行元素级别的比较,若元素相同会返回 1,否则为 0
# 将所有返回 1 的值相加即得到正确的样本个数,除以测试集大小就是正确率
accuracy = (outputs.argmax(1) == targets).sum()
total_accuracy += accuracy
print(f"整体测试集的损失 Loss 为 {total_test_loss}")
print(f"整体测试集上的正确率为 {total_accuracy/len_test}")
writer.add_scalar("test_loss", total_test_loss, test_step)
writer.add_scalar("test_accuracy", total_accuracy/len_test, test_step)
test_step += 1
torch.save(model, "torch_{}.pth".format(i))
# 也可以使用官方推荐的方式进行保存,即 torch.save(model.state_dict(), "torch_{}.pth".format(i))
print("模型已保存")
writer.close()
T i p s : p y t h o n 中的 w i t h 关键字 Tips: python中的with关键字 Tips:python中的with关键字
with open("1.txt") as file:
data = file.read()
- 紧跟with后面的语句被求值后,返回对象的
–enter–()
方法被调用,这个方法的返回值将被赋值给as后面的变量; - 当with后面的代码块全部被执行完之后,将调用前面返回对象的
–exit–()
方法。
T i p s : Tips: Tips:
with torch.no_grad()
- 在使用深度学习框架进行训练或者推理时,我们通常需要计算各个模型参数的梯度,以便进行反向传播和参数更新。然而,在某些情况下,我们并不需要计算梯度,例如在模型预测或评估时,只需要进行前向传播并获得输出结果。
- 在这些情况下,使用 “with torch.no_grad()” 可以帮助我们关闭梯度计算,从而同时减少内存的消耗,并提高程序的运行速度。因为不计算梯度,所以无需保存相应的中间结果,这可以显著减少内存的使用。
应用GPU对模型进行训练
- GPU 训练模式一:利用 . c u d a ( ) .cuda() .cuda() 方法
- 想要使用 GPU 对网络进行训练,需要利用 . c u d a ( ) .cuda() .cuda() 方法,需要将 网络模型、数据(输入,标注)和损失函数 都转换为GPU 训练的模式。
import torch
import torchvision
from torch import nn
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
import time
# 导入数据集
dataset_train = torchvision.datasets.CIFAR10("Datasets", train=True, transform=torchvision.transforms.ToTensor())
dataset_test = torchvision.datasets.CIFAR10("Datasets", train=False, transform=torchvision.transforms.ToTensor())
# 加载数据集
dataloader_train = DataLoader(dataset=dataset_train, batch_size=64, shuffle=True)
dataloader_test = DataLoader(dataset=dataset_test, batch_size=64, shuffle=True)
# 训练集的长度
len_train = len(dataset_train)
print(f"训练集的长度为 {len_train}")
# 测试集的长度
len_test = len(dataset_test)
print(f"测试集的长度为 {len_test}")
# 模型创建
class My_nn(nn.Module):
def __init__(self):
super(My_nn, self).__init__()
self.model = nn.Sequential(
Conv2d(3, 32, 5, 1, 2),
MaxPool2d(2),
Conv2d(32, 32, 5, 1, 2),
MaxPool2d(2),
Conv2d(32, 64, 5, 1, 2),
MaxPool2d(2),
Flatten(),
Linear(64 * 4 * 4, 64),
Linear(64, 10)
)
def forward(self, x):
output = self.model(x)
return output
# 构造模型
model = My_nn()
if torch.cuda.is_available():
model = model.cuda()
# 设置训练和测试次数
train_step = 0
test_step = 0
# 设置训练轮数
epoch = 10
# 设置学习率
learning_rate = 1e-2
# 定义优化器
optimise = torch.optim.SGD(model.parameters(), lr=learning_rate)
# 定义损失函数
my_loss = nn.CrossEntropyLoss()
if torch.cuda.is_available():
my_loss = my_loss.cuda()
# 构造日志文件
writer = SummaryWriter("Train_Logs")
# 计时开始
start_time = time.process_time()
for i in range(epoch):
print(f"----开始第 {i + 1} 轮训练----")
# 训练步骤开始
# 这一行并不是对于所有的网络都是必须的,只有当网络中含有 dropout 层或是其他一些特殊的层的时候需要使用这一层激活
model.train()
for data in dataloader_train:
imgs, targets = data
if torch.cuda.is_available():
imgs = imgs.cuda()
targets = targets.cuda()
outputs = model(imgs)
loss = my_loss(outputs, targets)
optimise.zero_grad()
loss.backward()
optimise.step()
train_step += 1
if train_step % 100 == 0:
end_time = time.process_time()
print(f"第 {train_step} 消耗的时间为 {end_time - start_time} s")
print(f"第 {train_step} 步 损失率:{loss.item()}")
writer.add_scalar("train_loss", loss.item(), train_step)
# 测试步骤开始
# 这一行也不是对所有网络都是必须的
model.eval()
total_test_loss = 0
total_accuracy = 0
with torch.no_grad():
for data in dataloader_test:
imgs, targets = data
if torch.cuda.is_available():
imgs = imgs.cuda()
targets = targets.cuda()
outputs = model(imgs)
loss = my_loss(outputs, targets)
total_test_loss += loss.item()
# outputs.argmax(1) 表示的是在 outputs 每一行的所有类别预测中,找到
# 概率最大的一个并返回,返回的结果就是该 img 的标签,由于 out_feature = 10
# 因此标签值是 0 ~ 9 中的一个。outputs.argmax(1) 具有 batch_size 个标签值
# 将该标签列表中与 targets 进行元素级别的比较,若元素相同会返回 1,否则为 0
# 将所有返回 1 的值相加即得到正确的样本个数,除以测试集大小就是正确率
accuracy = (outputs.argmax(1) == targets).sum()
total_accuracy += accuracy
print(f"整体测试集的损失 Loss 为 {total_test_loss}")
print(f"整体测试集上的正确率为 {total_accuracy / len_test}")
writer.add_scalar("test_loss", total_test_loss, test_step)
writer.add_scalar("test_accuracy", total_accuracy / len_test, test_step)
test_step += 1
torch.save(model, "torch_{}.pth".format(i))
# 也可以使用官方推荐的方式进行保存,即 torch.save(model.state_dict(), "torch_{}.pth".format(i))
print("模型已保存")
writer.close()
- GPU 训练模式二:利用 . t o ( d e v i c e ) .to(device) .to(device) 方法
- 首先需要定义设备,之后也需要将 网络模型、数据(输入,标注)和损失函数 连接到设备上。
import torch
import torchvision
from torch import nn
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
import time
# 导入数据集
dataset_train = torchvision.datasets.CIFAR10("Datasets", train=True, transform=torchvision.transforms.ToTensor())
dataset_test = torchvision.datasets.CIFAR10("Datasets", train=False, transform=torchvision.transforms.ToTensor())
# 加载数据集
dataloader_train = DataLoader(dataset=dataset_train, batch_size=64, shuffle=True)
dataloader_test = DataLoader(dataset=dataset_test, batch_size=64, shuffle=True)
# 训练集的长度
len_train = len(dataset_train)
print(f"训练集的长度为 {len_train}")
# 测试集的长度
len_test = len(dataset_test)
print(f"测试集的长度为 {len_test}")
# 定义训练设备
device = torch.device("cpu")
# 如果需要使用 gpu 进行训练那么就定义为 device = torch.device("cuda") ,
# 或者使用 device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 模型创建
class My_nn(nn.Module):
def __init__(self):
super(My_nn, self).__init__()
self.model = nn.Sequential(
Conv2d(3, 32, 5, 1, 2),
MaxPool2d(2),
Conv2d(32, 32, 5, 1, 2),
MaxPool2d(2),
Conv2d(32, 64, 5, 1, 2),
MaxPool2d(2),
Flatten(),
Linear(64*4*4, 64),
Linear(64, 10)
)
def forward(self, x):
output = self.model(x)
return output
# 构造模型
model = My_nn()
model = model.to(device)
# 设置训练和测试次数
train_step = 0
test_step = 0
# 设置训练轮数
epoch = 10
# 设置学习率
learning_rate = 1e-2
# 定义优化器
optimise = torch.optim.SGD(model.parameters(), lr=learning_rate)
# 定义损失函数
my_loss = nn.CrossEntropyLoss()
my_loss = my_loss.to(device)
# 构造日志文件
writer = SummaryWriter("Train_Logs")
# 计时开始
start_time = time.process_time()
for i in range(epoch):
print(f"----开始第 {i+1} 轮训练----")
# 训练步骤开始
# 这一行并不是对于所有的网络都是必须的,只有当网络中含有 dropout 层或是其他一些特殊的层的时候需要使用这一层激活
model.train()
for data in dataloader_train:
imgs, targets = data
imgs = imgs.to(device)
targets = targets.to(device)
outputs = model(imgs)
loss = my_loss(outputs, targets)
optimise.zero_grad()
loss.backward()
optimise.step()
train_step += 1
if train_step % 100 == 0:
end_time = time.process_time()
print(f"第 {train_step} 消耗的时间为 {end_time - start_time} s")
print(f"第 {train_step} 步 损失率:{loss.item()}")
writer.add_scalar("train_loss", loss.item(), train_step)
# 测试步骤开始
# 这一行也不是对所有网络都是必须的
model.eval()
total_test_loss = 0
total_accuracy = 0
with torch.no_grad():
for data in dataloader_test:
imgs, targets = data
imgs = imgs.to(device)
targets = targets.to(device)
outputs = model(imgs)
loss = my_loss(outputs, targets)
total_test_loss += loss.item()
# outputs.argmax(1) 表示的是在outputs每一行的所有类别预测中,找到
# 概率最大的一个并返回,返回的结果就是该 img 的标签,由于 out_feature = 10
# 因此标签值是 0 ~ 9 中的一个。outputs.argmax(1) 具有 batch_size 个标签值
# 将该标签列表中与 targets 进行元素级别的比较,若元素相同会返回 1,否则为 0
# 将所有返回 1 的值相加即得到正确的样本个数,除以测试集大小就是正确率
accuracy = (outputs.argmax(1) == targets).sum()
total_accuracy += accuracy
print(f"整体测试集的损失 Loss 为 {total_test_loss}")
print(f"整体测试集上的正确率为 {total_accuracy/len_test}")
writer.add_scalar("test_loss", total_test_loss, test_step)
writer.add_scalar("test_accuracy", total_accuracy/len_test, test_step)
test_step += 1
torch.save(model, "torch_{}.pth".format(i))
# 也可以使用官方推荐的方式进行保存,即 torch.save(model.state_dict(), "torch_{}.pth".format(i))
print("模型已保存")
writer.close()
模型验证
准备一张狗狗的图片:
下面加载保存的模型尝试进行识别:
import torch
from PIL import Image
from torch import nn
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear
from torchvision import transforms
# 图片路径
image_path = r"C:\Users\zdh\Downloads\dog.png"
# 打开图片并转换为 RGB 模式
image = Image.open(image_path)
image = image.convert(mode="RGB")
# 定义图片的预处理操作
transform = transforms.Compose([
transforms.Resize((32, 32)),
# 调整图片大小为 32x32,以匹配网络输入
transforms.ToTensor()
])
# 对图片进行预处理
image = transform(image)
class My_nn(nn.Module):
def __init__(self):
super(My_nn, self).__init__()
self.model = nn.Sequential(
# 第一个卷积层:输入通道 3,输出通道 32,卷积核 5,步长 1,填充 2
Conv2d(3, 32, 5, 1, 2),
# 最大池化层,池化核 2
MaxPool2d(2),
# 第二个卷积层:输入通道 32,输出通道 32,卷积核 5,步长 1,填充 2
Conv2d(32, 32, 5, 1, 2),
# 最大池化层,池化核 2
MaxPool2d(2),
# 第三个卷积层:输入通道 32,输出通道 64,卷积核 5,步长 1,填充 2
Conv2d(32, 64, 5, 1, 2),
# 最大池化层,池化核 2
MaxPool2d(2),
# 展平特征图
Flatten(),
# 第一个线性层:输入维度 64*4*4,输出维度 64
Linear(64 * 4 * 4, 64),
# 第二个线性层:输入维度 64,输出维度 10
Linear(64, 10)
)
def forward(self, x):
# 前向传播函数
output = self.model(x)
return output
# 加载预训练的模型
model = torch.load("torch_8.pth")
# 将图像重塑为 (1, 3, 32, 32) 的形状,以匹配模型输入
image = torch.reshape(image, (1, 3, 32, 32))
# 将图像输入模型进行前向传播
output = model(image)
# 打印输出结果
print(output)
# 打印预测的类别索引
print(output.argmax(1))
输出:
tensor([[-3.6053, -4.3679, 4.0489, 3.7961, -0.3184, 7.2941, -2.1973, 3.5614,
-7.7688, -1.5362]], grad_fn=<AddmmBackward0>)
tensor([5])
让我们回顾一下CIFAR10训练集中各个标签值对应的类型:
# 对于 CIFAR-10 数据集,标签的范围是 0 到 9,分别对应 10 个类别:
# 0: airplane
# 1: automobile
# 2: bird
# 3: cat
# 4: deer
# 5: dog
# 6: frog
# 7: horse
# 8: ship
# 9: truck
Note
Python对Windos下文件路径的三种表示方法
1. 1. 1. 使用双反斜杠: 在路径中使用 ∖ ∖ \setminus \setminus ∖∖来表示反斜杠,Python 会识别为单个反斜杠。
path = "C:\\Users\\Username\\Documents"
2. 2. 2. 使用原始字符串:通过在字符串前加 r r r,Python 会将字符串当作“原始”文本,不进行转义处理。
path = r"C:\Users\Username\Documents"
3. 3. 3. 使用正斜杠 / / /:Python 支持在路径中使用正斜杠,即使在 Windows 上也可以正确处理。
path = "C:/Users/Username/Documents"
综上: 使用正斜杠 / / / 或原始字符串 r r r 通常是推荐的做法,能避免转义字符带来的潜在问题。