PyTorch 深度学习框架快速入门 (小土堆)
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 osclass MyDataset(Dataset):def __init__(self, root_dir, label_dir):self.root_dir = root_dirself.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 和 labelimg_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 # 图片所在目录名即图片对应Labelreturn img, labeldef __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_datasetprint(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 osclass MyDataset(Dataset):def __init__(self, root_dir, img_dir, label_dir):self.root_dir = root_dirself.img_dir = img_dirself.label_dir = label_dirself.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, labelroot_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 pltfig1, 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 表示 xwriter.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 transformsimg_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: truckimg, 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 DataLoadertest_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 + 1writer.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 nnclass SimpleNet(nn.Module):def __init__(self):super(SimpleNet, self).__init__()# 定义网络的层self.fc1 = nn.Linear(10, 5) # 全连接层,输入维度为 10,输出维度为 5self.relu = nn.ReLU() # ReLU 激活函数self.fc2 = nn.Linear(5, 2) # 全连接层,输入维度为 5,输出维度为 2def 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=Trueself.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_1output_1 = self.relu1(x)# 将输入 x 通过 Sigmoid 激活函数得到输出 output_2output_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 和目标标签 targetsimgs, 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)# 步骤计数器加 1step += 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,输出维度为 10self.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 SummaryWriterclass my_nn(nn.Module):def __init__(self):super(my_nn, self).__init__()# 使用 Sequential 容器组合多个层self.model = Sequential(# 第一个卷积层:输入通道为 3,输出通道为 32,卷积核大小为 5,填充为 2Conv2d(3, 32, 5, padding=2),# 第一个最大池化层:核大小为 2MaxPool2d(2),# 第二个卷积层:输入通道为 32,输出通道为 32,卷积核大小为 5,填充为 2Conv2d(32, 32, 5, padding=2),# 第二个最大池化层:核大小为 2MaxPool2d(2),# 第三个卷积层:输入通道为 32,输出通道为 64,卷积核大小为 5,填充为 2Conv2d(32, 64, 5, padding=2),# 第三个最大池化层:核大小为 2MaxPool2d(2),# 将特征图展平Flatten(),# 第一个线性层:输入维度为 1024,输出维度为 64Linear(1024, 64),# 第二个线性层:输入维度为 64,输出维度为 10Linear(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.0running_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.modelsvgg16 = 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, Linearclass My_nn(nn.Module):def __init__(self):super(My_nn, self).__init__()self.model = nn.Sequential(# 第一个卷积层:输入通道为 3,输出通道为 32,卷积核大小为 5,步长为 1,填充为 2Conv2d(3, 32, 5, 1, 2),# 最大池化层,池化核大小为 2MaxPool2d(2),# 第二个卷积层:输入通道为 32,输出通道为 32,卷积核大小为 5,步长为 1,填充为 2Conv2d(32, 32, 5, 1, 2),# 最大池化层,池化核大小为 2MaxPool2d(2),# 第三个卷积层:输入通道为 32,输出通道为 64,卷积核大小为 5,步长为 1,填充为 2Conv2d(32, 64, 5, 1, 2),# 最大池化层,池化核大小为 2MaxPool2d(2),# 展平层,将特征图展平为一维向量Flatten(),# 第一个线性层:输入维度为 64*4*4,输出维度为 64Linear(64*4*4, 64),# 第二个线性层:输入维度为 64,输出维度为 10Linear(64, 10))def forward(self, x):# 前向传播函数,将输入 x 通过模型output = self.model(x)return outputif __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 SummaryWriterfrom 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%20equivalentmodel.train()for data in dataloader_train:imgs, targets = dataoutputs = model(imgs)loss = my_loss(outputs, targets)optimise.zero_grad()loss.backward()optimise.step()train_step += 1if train_step % 100 == 0:print(f"第 {train_step} 步 损失率:{loss.item()}")writer.add_scalar("train_loss", loss.item(), train_step)# 测试步骤开始# 这一行也不是对所有网络都是必须的model.eval()total_test_loss = 0total_accuracy = 0with torch.no_grad():for data in dataloader_test:imgs, targets = dataoutputs = 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 += accuracyprint(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 += 1torch.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 = dataif 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 += 1if 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 = 0total_accuracy = 0with torch.no_grad():for data in dataloader_test:imgs, targets = dataif 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 += accuracyprint(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 += 1torch.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 = dataimgs = imgs.to(device)targets = targets.to(device)outputs = model(imgs)loss = my_loss(outputs, targets)optimise.zero_grad()loss.backward()optimise.step()train_step += 1if 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 = 0total_accuracy = 0with torch.no_grad():for data in dataloader_test:imgs, targets = dataimgs = 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 += accuracyprint(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 += 1torch.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,填充 2Conv2d(3, 32, 5, 1, 2),# 最大池化层,池化核 2MaxPool2d(2),# 第二个卷积层:输入通道 32,输出通道 32,卷积核 5,步长 1,填充 2Conv2d(32, 32, 5, 1, 2),# 最大池化层,池化核 2MaxPool2d(2),# 第三个卷积层:输入通道 32,输出通道 64,卷积核 5,步长 1,填充 2Conv2d(32, 64, 5, 1, 2),# 最大池化层,池化核 2MaxPool2d(2),# 展平特征图Flatten(),# 第一个线性层:输入维度 64*4*4,输出维度 64Linear(64 * 4 * 4, 64),# 第二个线性层:输入维度 64,输出维度 10Linear(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 通常是推荐的做法,能避免转义字符带来的潜在问题。
相关文章:
PyTorch 深度学习框架快速入门 (小土堆)
PyTorch 深度学习框架快速入门 深度学习框架常用模块数据集存取图片数据处理库 —— PILOS 模块实例 Tensorboard 记录机器学习的过程Transform 进行图像变换数据集的下载DataLoaderModule 自定义网络前向传播卷积层卷积简单应用 最大池化非线性层线性层 简单的整合基于现有网络…...
React Native Hooks开发指南
一、什么是Hooks Hooks 是 React 16.8 的新增特性。在不编写 class 的情况下使用 state 以及其他的 React 特性。Hooks 是一种在函数式组件中使用有状态函数的方法。 二、类组件 componentDidMount、componentDidUpdate 和 componentWillUnmount 这三个函数的组合。 三、常用…...
Java内存与缓存
Java内存管理和缓存机制是构建高性能应用程序的关键要素。它们之间既有联系又有区别,理解这两者对于优化Java应用至关重要。 Java 内存模型 Java内存模型(JMM)定义了线程如何以及何时可以看到其他线程修改过的共享变量的值,并且规…...
两分钟解决 :![rejected] master -> master (fetch first) , 无法正常push到远端库
目录 分析问题的原因解决 分析问题的原因 在git push的时候莫名遇到这种情况 若你在git上修改了如README.md的文件。由于本地是没有README.md文件的,所以导致 远端仓库git和本地不同步。 将远端、本地进行合并就可以很好的解决这个问题 注意:直接git pu…...
React中ElementFiber对象、WorkInProgress双缓存、ReconcileRenderCommit、第一次挂载过程详解
基础概念 Element对象与Fiber对象 Element对象与Fiber对象 Element 对象 定义 React 的 Element 对象是一个描述用户界面(UI)的普通 JavaScript 对象,通常由 React.createElement 或 JSX 语法生成。 作用 它是 React 应用中的一种描述 …...
【论文阅读】Workload Dependent Performance Evaluation of the Linux 2.6 I/O Schedulers
文章目录 某些背景知识的科普(依赖GPT)GPT简短总结摘要-Abstract引言-Introduction1 I/O Scheduling and the BIO LayerThe 2.6 Deadline I/O Scheduler2.1 The 2.6 Anticipatory I/O scheduler2.2 The 2.6 CFQ Scheduler2.3 The 2.6 noop I/O scheduler…...
macOS 版本对应 Xcode 版本,以及 Xcode 历史版本下载
注:当前页面的所有Xcode下载链接均为苹果官方下载链接 ,点击将直接转至苹果官网下载。❤️❤️❤️ Xcode官网:Xcode Releases | xcodereleases.com Xcode版本Xcode发布时间对应macOS版本macOS SDKsiOS SDKswatchOS SDKstvOS SDKs下载Xcode发…...
量子计算:从薛定谔的猫到你的生活
文章背景 说到量子计算,不少人觉得它神秘又遥不可及。其实,它只是量子物理学的一个“应用小分支”。它的核心在于量子比特的“叠加”和“纠缠”,这些听上去像科幻小说的概念,却为计算世界开辟了一片全新的天地。如果经典计算是“…...
R4-LSTM学习笔记
🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊 LSTM-火灾温度预测 导入数据数据可视化设置X、y构建模型调用模型个人总结LSTM 的基本结构细胞状态(Cell State)LSTM 的优点 导入数据 i…...
Facebook 隐私变革之路:回顾与展望
在数字时代,个人隐私的保护一直是社交平台面临的重大挑战之一。作为全球最大的社交网络平台,Facebook(现为Meta)在处理用户隐私方面的变革,历经了多次调整与完善。本文将回顾Facebook在隐私保护方面的历程,…...
计算机网络 (40)域名系统DNS
前言 计算机网络域名系统DNS(Domain Name System)是互联网的基础技术之一,它负责将人类可读的域名转换为计算机用来通信的数字IP地址。 一、基本概念 DNS的主要目的是将域名解析或翻译为IP地址,使得用户可以通过简单易记的域名来访…...
LabVIEW驱动电机实现样品自动搜索
利用LabVIEW控制电机驱动相机在XY平面上进行扫描,以检测样品位置。样品最初可能位于相机视野范围之外,需要实现自动搜索样品位置并完成精确定位扫描的功能。该系统需具有以下特点: 高效搜索:能够快速确定样品位置,缩短…...
程序员独立开发竞品分析:确定网站使用什么建站系统
要确定一个网站使用的建站系统,可以通过以下几种方法尝试分析: 查看页面源代码: 打开网站,右键点击页面并选择“查看页面源代码”。在代码中查找一些常见的建站系统标志,例如: WordPress 的迹象:…...
计算机网络 笔记 网络层1
网络层功能概述 主要的任务是把分组从源端传输到目的端,为分组交换网上的不同主句提供通信服务,网络层的传输单位是数据报。 主要的功能; 1,路由选择:路由选择指网络层根据特定算法,为数据包从源节点到目…...
推理规则库的构建与应用
目录 前言1. 推理规则库概述1.1 推理规则的基本构成1.2 推理规则库的作用与意义 2. 推理规则库的构建2.1 知识获取与表示2.2 推理规则的提取2.3 规则的优化与整合 3. 推理规则库的推理机制3.1 前向推理3.2 后向推理3.3 混合推理 4. 推理规则库的应用4.1 医学领域4.2 金融领域4.…...
深度学习|表示学习|一个神经元可以干什么|02
如是我闻: 如果我们只有一个神经元(即一个单一的线性或非线性函数),仍然可以完成一些简单的任务。以下是一个神经元可以实现的功能和应用: 1. 实现简单的线性分类 输入:一组特征向量 x x x 输出ÿ…...
2 XDMA IP中断
三种中断 1. Legacy 定义:Legacy 中断是传统的中断处理方式,使用物理中断线(例如 IRQ)来传递中断信号。缺点: 中断线数量有限,通常为 16 条,限制了可连接设备的数量。中断处理可能会导致中断风…...
什么是负载均衡?NGINX是如何实现负载均衡的?
大家好,我是锋哥。今天分享关于【什么是负载均衡?NGINX是如何实现负载均衡的?】面试题。希望对大家有帮助; 什么是负载均衡?NGINX是如何实现负载均衡的? 1000道 互联网大厂Java工程师 精选面试题-Java资源…...
AWS简介
AWS 一,AWS是什么? AWS的全称是 Amazon Web Services 的缩写,是亚马逊公司提供的一套广泛且应用广泛的云端服务。 AWS提供了超过200项全功能的服务,来自数据中心数据中心遍布全球多个地理位置,这些服务包括计算能力&…...
旅游网站设计与实现
文末附有完整项目代码 在当今数字化时代,旅游网站成为人们获取旅游信息的重要途径。本文将详细介绍旅游网站的设计与实现,让你轻松了解其中的技术奥秘! 一、项目背景 随着社会经济的发展,人们对精神消费愈发重视,旅游…...
vscode 扩展Cline、Continue的差别?
Cline和Continue都是VSCode的AI编程插件,它们在功能、用户体验、性能、适用场景以及配置和使用步骤等方面存在一些差别: 一、功能差异 编辑功能 Cline:能够分析项目的文件结构和源代码抽象语法树(AST),通…...
用 HTML5 Canvas 和 JavaScript 实现雪花飘落特效
这篇文章将带您深入解析使用 HTML5 Canvas 和 JavaScript 实现动态雪花特效的代码原理。 1,效果展示 该效果模拟了雪花从天而降的动态场景,具有以下特点: 雪花数量、大小、透明度和下落速度随机。雪花会在屏幕底部重置到顶部,形成循环效果。随窗口大小动态调整,始终覆盖…...
电梯系统的UML文档01
Lu Luo 著,王君 译 1 简介 这是一份Carnegie Mellon 大学博士课程(分布式嵌入系统)项目报告。整个课程完成了一个分布式实时系统的设计、搭建和模拟。设计时用到了OOA 和OOD,特别是UML。 系统的大多数类省略了很多细节。现在看…...
浅谈云计算04 | 云基础设施机制
探秘云基础设施机制:云计算的基石 一、云基础设施 —— 云计算的根基二、核心机制之网络:连接云的桥梁(一)虚拟网络边界ÿ…...
数据库知识
存储引擎层面 数据存储格式 不同的存储引擎有不同的数据存储格式。以 InnoDB 和 MyISAM 为例,InnoDB 是面向行的存储引擎,它将数据行存储在聚集索引(如果没有显式定义主键,会自动生成一个隐藏的主键)中。数据行和索引…...
2025宝塔API一键建站系统PHP源码
源码介绍 2025宝塔API一键建站系统PHP源码,对接自己的支付,虚拟主机也能搭建,小白式建站系统,基于宝塔面板搭建的建站系统,功能丰富,多款模板,每日更新 上传源码到服务器,浏览器访问…...
第三十八章 Spring之假如让你来写MVC——适配器篇
Spring源码阅读目录 第一部分——IOC篇 第一章 Spring之最熟悉的陌生人——IOC 第二章 Spring之假如让你来写IOC容器——加载资源篇 第三章 Spring之假如让你来写IOC容器——解析配置文件篇 第四章 Spring之假如让你来写IOC容器——XML配置文件篇 第五章 Spring之假如让你来写…...
客户端渲染和服务端渲染
二者本质的区别:是在哪完成了 HTML 的拼接,服务端渲染是在服务端拼接,客户端渲染是在客户端拼接。 服务端渲染的优缺点 优点 SEO 友好,服务端渲染更有利于爬虫爬取信息。 更快的首屏渲染,因为 HTML 已经在服务端生…...
《盘古大模型——鸿蒙NEXT的智慧引擎》
在当今科技飞速发展的时代,华为HarmonyOS NEXT的发布无疑是操作系统领域的一颗重磅炸弹,其将人工智能与操作系统深度融合,开启了智能新时代。而盘古大模型在其中发挥着至关重要的核心作用。 赋予小艺智能助手超强能力 在鸿蒙NEXT中…...
软件架构考试基础知识 004:死锁问题
死锁的定义 死锁(Deadlock)是指在多进程系统中,一组进程相互等待对方持有的资源,导致所有相关进程都无法继续执行的状态。这种状态是僵持的,无法自动解除,必须通过外部干预(如重启系统…...
AI学习路线图-邱锡鹏-神经网络与深度学习
1 需求 神经网络与深度学习 2 接口 3 示例 4 参考资料...
Pytorch通信算子组合测试
Pytorch通信算子组合测试 一.背景二.相关链接三.遇到的问题四.操作步骤1.登录服务器2.查看拓扑3.准备测试用例A.准备目录B.用例代码 4.创建docker容器5.查看当前pytorch版本6.运行测试程序 一.背景 测试pytorch通信算子不同配置下的功能及性能测试不同的group组合测试不同的te…...
Android Dex VMP 动态加载加密指令流
版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/ 上一篇【详解如何自定义 Android Dex VMP 保护壳】实现了 VMP 保护壳。 为了进一步加强对 dex 指令的保护,实现指令流加密和动态加载,…...
深度学习blog-剪枝和知识蒸馏
深度学习网络模型从卷积层到全连接层存在着大量冗余的参数,大量神经元激活值趋近于0,将这些神经元去除后可以表现出同样的模型表达能力,这种情况被称为过参数化。因此需要一些技术手段减少模型的复杂性,去除一些不重要的参数和连接…...
13:00面试,13:08就出来了,问的问题有点变态。。。
从小厂出来,没想到在另一家公司又寄了。 到这家公司开始上班,加班是每天必不可少的,看在钱给的比较多的份上,就不太计较了。没想到9月一纸通知,所有人不准加班,加班费不仅没有了,薪资还要降40%…...
机器学习笔记合集
大家好,这里是好评笔记,公主 号:Goodnote。本笔记的任务是解读机器学习实践/面试过程中可能会用到的知识点,内容通俗易懂,入门、实习和校招轻松搞定。 笔记介绍 本笔记的任务是解读机器学习实践/面试过程中可能会用到…...
七 rk3568 android 11 ec20 4G驱动移植
一 内核驱动集成 参考:Quectel_LTE&5G_Linux_USB_Driver_V1.0.zip EC20 内核驱动有两个版本 ,一个是 qmi_wwan, 一个是 GOBNet , 这里用的是 qmi_wwan版本 1.1 添加 USBNET 驱动文件 将驱动包里的 qmi_wwan_q.c 拷到 kernel/driver/net/usb/ 下 修改 kernel/dr…...
【Elasticsearch7.11】postman批量导入少量数据
JSON 文件内的数据格式,json文件数据条数不要过多,会请求参数过大,最好控制再10000以内。 {"index":{"_id":"baec07466732902d22a24ba01ff09751"}} {"uuid":"baec07466732902d22a24ba01ff0975…...
NLP三大特征抽取器:CNN、RNN与Transformer全面解析
引言 自然语言处理(NLP)领域的快速发展离不开深度学习技术的推动。随着应用需求的不断增加,如何高效地从文本中抽取特征成为NLP研究中的核心问题。深度学习中三大主要特征抽取器——卷积神经网络(Convolutional Neural Network, …...
45_Lua模块与包
Lua中的模块系统是该语言的一个重要特性,它允许开发者将代码分割成更小、更易于管理的部分。通过使用模块,你可以创建可重用的代码片段,并且可以降低代码间的耦合度。下面我将详细介绍Lua模块的基本概念、语法以及一些实际案例。 1.Lua模块 1.1 模块的基本概念 从Lua 5.1…...
软定时器的原理与创建
目录 问题概述 设计原理 设计实现 一个任务来管理所有在指定的时间、以特定的周期触发某种操作的定时需求。 问题概述 在实际应用中,常常需要周期性或者在指定时间做一件事情。 周期性:在指定的延时开始做某件事情,然后周期性重复执行 一次性…...
【自动化测试】—— Appium安装配置保姆教程(图文详解)
目录 一. 环境准备 二. JDK安装 1. 下载JDK 2. 安装JDK 3. 配置环境 4. 验证安装 三. Android SDK安装 1. 下载Android SDK 2. 安装Android SDK 3. 安装工具 4. 配置环境 5. 验证安装 四. NodeJS安装 1. 下载NodeJS 2. 安装NodeJS 3. 验证安装 4. 安装淘宝镜像…...
穿越火线怀旧服预约网页vue3版本
源码下载地址: https://github.com/superBiuBiuMan/crossfire-old-vue3版权来自穿越火线,项目仅供参考学习!!! 效果 源码下载地址: https://github.com/superBiuBiuMan/crossfire-old-vue3预览地址: https://crossfire.123916.xyz/官网效果: https://www.cfhuodong.com/2025-…...
《Keras3从头开始的图像分类》
Keras3从头开始的图像分类 作者:fchollet创建日期:2020/04/27最后修改时间:2023/11/09描述:在 Kaggle Cats vs Dogs 数据集上从头开始训练图像分类器。 (i) 此示例使用 Keras 3 在 Colab 中查看 • GitHub…...
Apache Hop从入门到精通 第三课 Apache Hop下载安装
1、下载 官方下载地址:https://hop.apache.org/download/,本教程是基于apache-hop-client-2.11.0.zip进行解压,需要jdk17,小伙伴们可以根据自己的需求下载相应的版本。如下图所示 2、下载jdk17(https://www.microsoft…...
Vue.js组件开发-图片剪裁性能优化最佳方案实例
在Vue.js组件开发中,优化图片剪裁性能的最佳方案通常涉及多个方面的综合考虑。以下是一个结合多个优化策略的图片剪裁组件性能优化实例: 1. 组件设计 首先,设计一个简洁且高效的图片剪裁组件,确保其功能明确且易于使用。组件应包…...
React - router的使用 结合react-redux的路由守卫
web端使用路由安装的是 react-router-dom "react-router-dom": "^5.2.0"在组件中使用路由,我们先设置2个路由,分别是首页、关于 // src/components/RouteSample.jsimport React from react; // 引入路由需要的基础模块 import {Bro…...
day09_kafka高级
文章目录 kafka高级今日课程内容核心概念整理Kafka的数据位移offset**为什么 Kafka 的 offset 就像是“书签”?****实际意义** Kafka的基准/压力测试测试生产的效率测试消费的效率 Kafka的分片与副本机制kafka如何保证数据不丢失生产者端Broker端消费者端相关参数 K…...
【MT32F006】MT32F006之通信协议
本文最后修改时间:2025年01月09日 一、本节简介 本文介绍如何使用MT32F006写一个通信协议。 二、实验平台 库版本:V1.0.0 编译软件:MDK5.37 硬件平台:MT32F006开发板(主芯片MT32F006) 仿真器ÿ…...
CMake学习笔记(2)
1. 嵌套的CMake 如果项目很大,或者项目中有很多的源码目录,在通过CMake管理项目的时候如果只使用一个CMakeLists.txt,那么这个文件相对会比较复杂,有一种化繁为简的方式就是给每个源码目录都添加一个CMakeLists.txt文件ÿ…...