前言
学习 PyTorch 深度学习框架之前先学会深度学习和卷积神经网络 CNN ,这样学习起来会更香嗷。
Windows系统下PyTorch的环境配置
Anaconda是什么:
Anaconda是一个开源的Python发行版本,专注于数据分析领域。它包含了conda、Python等190多个科学包及其依赖项,如NumPy、Pandas、Matplotlib等,使得安装比常规Python安装要容易。
此外,Anaconda还提供了一个强大的环境管理工具conda,它可以实现包的安装与版本管理,创建虚拟环境,从而将不同的Python包甚至是不同版本的Python隔离开来,避免环境混乱。通过Anaconda,用户可以方便地安装、管理和更新各种Python库和工具,提高科学计算和数据分析的效率。
总的来说,Anaconda是一个集成了Python、常用科学计算库和环境管理工具的发行版,旨在为用户提供便捷、高效的数据分析体验。
如果没有 Anaconda,我们平常在使用 Python 时会涉及到很多的工具包的安装卸载,是非常麻烦的事情,因此使用了 Anaconda 将能避免这种繁琐的安装卸载环节。
下载anaconda包管理器
下载地址链接:anaconda网址
跳转链接以后:
点击红框位置,跳转到下载链接列表:
选择一个自己喜欢且合适的版本下载即可(我选的版本是Anaconda3-2023.07-1-Windows-x86_64),然后安装,安装过程和 up 的一样,不再赘述。
PyTorch下载
有序的管理环境
Anaconda提供了一个有序的管理环境。
因为在以后的工作学习中避免不了会遇到不同的项目,需要使用到不同版本的环境,比如一个版本要用到 PyTorch 0.4,另一个要用到 1.0 ,为了避免反复卸载安装,Anaconda提供了 conda 包来解决这个问题。
类似于,我们可以将其想象成其能创建多个屋子,相互隔离,一个屋子放 0.4 版本,而另一个屋子放 1.0 版本,这样在需要切换版本的时候要用到哪个就切换到哪个即可,非常方便。
1、conda 创建环境(即一个屋子)指令:
除了 Anaconda 的版本不一样,剩下的步骤全是一样的啦。
conda create -n pytorch python=3.6
conda 是指调用conda 包,create是创建的意思,-n指的是后面的名字是屋子的名字,pytorch是屋子的名字(可以随便命名),python=3.6 是指创建的屋子所使用的python是3.6。
2、激活环境(或者叫切换环境)
可以使用下面的指令:
conda activate pytorch
从上图中可以看到最左边的标识当前环境的名字变成了我们所需要的环境了。
3、展示当前环境所具有的工具包
使用命令(关于 pip 命令可以查看本小节最后一节所补充关于 pip 的内容):
pip list
可以看见当前的包中缺乏我们需要的 pytorch包,因此我们要进行下载,在这之前先简单介绍一下 pip 命令。
补充:pip 命令
pip是一个以Python计算机程序语言写成的软件包管理系统,它可以安装和管理软件包,另外不少的软件包也可以在“Python软件包索引”(英语:Python Package Index,简称PyPI)中找到。以下是一些pip的常见命令和选项:
安装包:pip install package_name。例如,要安装numpy包,可以运行命令“pip install numpy”。
卸载包:pip uninstall package_name。如果要卸载已安装的numpy包,可以使用“pip uninstall numpy”。
列出已安装包:pip list。这个命令用于列出当前环境中已安装的所有Python包。
搜索包:pip search package_name。例如,要搜索numpy包,可以运行“pip search numpy”。
查看包详细信息:pip show package_name。此命令用于查看指定包的详细信息,包括版本号、安装路径等。
导出已安装包:pip freeze > requirements.txt。这可以将已安装的Python包信息保存到requirements.txt文件中,以便在另一台电脑或服务器上部署同样的环境。
从文件安装包:pip install -r requirements.txt。这可以从requirements.txt文件中安装所有的Python包。
批量安装:pip install -r requirements.txt。这个命令同样用于从文件中批量安装多个Python包。
检查升级包:pip list --outdated。这个命令用于列出需要升级的所有包。
更新指定包:pip install --upgrade package_name。例如,要更新xlwt包,可以运行“pip install --upgrade xlwt”。
显示pip版本:pip --version。
显示pip帮助信息:pip --help。
此外,pip还有一些常用的选项,如-h或–help用于显示帮助信息,-v或–verbose用于显示详细的输出信息,–quiet或-q用于静默模式,–proxy 用于设置代理服务器,–no-cache-dir用于禁用缓存目录,–no-color用于禁用彩色输出,–user用于将包安装到用户目录而不是系统目录,–index-url 或-i 用于指定包的索引URL,–trusted-host 用于指定信任的主机名,以便安装不受信任的包等。
注意一点,pip命令和选项可能会随着Python和pip的更新而有所变化。因此,建议查阅最新的pip文档或运行pip --help命令以获取最准确和最新的信息。
下载链接:pytorch下载链接
跳转到该链接,然后下拉到上图页面,选好自己的选项(windows的话就照我上面的选),然后直接复制Run this Command中的命令即可下载安装,会有点慢,但我的方法是使用魔法上网(因为是国外的网站嘛,下载就很慢,如果有梯子就会很快了)就可以解决啦。
问题: (我这里的问题是显卡驱动太老)torch.cuda.is_available()返回false
我因为太久没更新过显卡驱动了,所以我通过报错信息是能够知道这里torch.cuda.is_available()返回false的原因是驱动太老,更新驱动即可,下载最新驱动的网站:英伟达驱动网站
这里参照任务管理器里面的GPU显卡型号搜索对应的驱动更新即可返回true。
Python编辑器选择、安装与配置
安装PyCharm
略,这个太简单了教程也很多。
Jupyter
Jupyter是一个开源的交互式计算环境,为数据科学家、研究人员和开发者提供了一个灵活且强大的平台,用于创建、共享和展示计算性工作。它最初由Fernando Perez于2014年推出,并得到了全球广泛的应用和认可。Jupyter的名称源自三个核心编程语言:Julia、Python和R,但它现在支持超过40种编程语言,包括Scala、Ruby、C#、Lua等,这使得Jupyter不仅限于数据科学领域,还可以应用于其他领域。
Jupyter Notebook是Jupyter项目中的一个组件,它是一个Web应用程序,允许用户创建和共享包含代码、方程式、可视化和文本的文档。这些文档支持实时代码、数学方程、可视化和Markdown,具有数据清理和转换、数值模拟、统计建模、数据可视化、机器学习等多种用途。
Jupyter的主要特性包括交互式计算,用户可以直接在浏览器中交互式地编写代码、运行代码和显示结果。此外,Jupyter还提供了丰富的插件和扩展功能,以满足不同用户的需求。用户可以使用电子邮件、Dropbox、GitHub和Jupyter Notebook Viewer与他人共享笔记本。代码也可以生成丰富的交互式输出,包括HTML、图像、视频等。
问题1:PyCharm2022.3使用Anaconda解释器却找不到该解释器的问题
参考这篇文章解决:解决问题
问题2:PyTorch环境安装jupyter notebook失败
在pytorch虚拟环境中配置jupyter notebook,不要跟视频上使用一样的指令,用下面的:
conda install jupyter notebook
然后可能会出现问题如下:
对于这种问题,我CSDN了一下相关文章:解决问题
使用这篇文章中的解决办法,直接pip uninstall isoduration,然后再pip install isoduration即可,此时就可以正常跳转网页(和视频中的一样)。
Python中查询帮助的两大函数
PyTorch本身可以看作是个工具箱,里面封装了各种各样的库供我们使用。
查看这些库工具,可以使用下面两个函数 dir() 和 help(),具体使用方法如下:
也就是使用 dir() 函数(dir就是目录的开头三个字母缩写,也即展示目录的意思嘛)打开一个工具目录,然后再使用 help() 来查看对应库程序的更详细的说明文档(使用 dir 查看工具放置的位置,然后用 help 查看该工具作用,组合使用效果更佳)。
简单在 PyCharm 中使用一下:
以后遇到不懂的工具时就可以使用这两个函数来进行查询。
三种常用编辑器的对比
因为三者各有特点,所以经常可能穿插使用。
PyTorch加载数据初识
PyTorch读取数据主要涉及两个类:
DataSet 和 Dataloader。
DataSet类解释:
在PyTorch中,Dataset是一个抽象类,用于表示数据集。它是所有数据集类的基类,提供了一种统一的方式来访问数据。我们可以通过继承Dataset类并实现特定的方法来创建自己的数据集类。
DataLoader类解释:
PyTorch中的DataLoader类是一个用于加载和处理数据集的工具类,它可以将数据集转换成可以进行遍历的对象,每次迭代可以从数据集中返回一组数据。在模型训练时,使用DataLoader可以批量读取数据,提高训练效率。
其中DataSet类是需要我们自己去写的(因为要继承嘛),它的功能是提供一种方式去获取数据及其label,以供后面的网络来使用。
比如如何获取每一个数据及其label,还有告诉系统总共有多少的数据。
而Dataloader类则是将DataSet类提供的数据以某种方式进行打包加载到后面的流程中以供网络使用。
这样说会有点抽象,我们将用一个实例来简单说明:
首先提供数据集:
train为训练数据集,val为验证数据集。
训练数据集中是蜜蜂的图片以及蚂蚁的图片:
我们大致能够判断出这是一个用于区分或者说识别蚂蚁和蜜蜂的对这俩进行一个二分类的数据集,然后这个文件夹的名称就是它们的label,比如蚂蚁的label就是ants。
Dataset的使用
基于上述基础,先来使用一下DataSet类,先通过jupyter来查看一下Dataset的内容以及如何使用:
在进行正式代码的编写前,还有一个准备工作,因为要进行图片识别的内容,所以我们还要安装一个opencv的库:
上面如果没有该命令行,点击上图中向下的箭头然后点击Command Prompt即可打开命令行。
然后输入上图中的命令,等待下载安装完即可。
(服了,up后面没用这个,那就当知道了咋安装吧)
通过上述描述,我们就可以来进行一个自己的Dataset类的编写:
# 引入Dataset抽象类,其位于 touch包中的utils包中的data包下
from torch.utils.data import Dataset
# 从PIL(Python Imaging Library,即Python图像处理库)中导入Image模块
# PIL 是Python Imaging Library的缩写,是一个强大的图像处理库。它提供了许多用于图像处理的函数和方法。
# Image 是PIL库中的一个模块,它提供了许多用于打开、操作和保存图像文件的函数和方法。
from PIL import Image
# 导入os模块。os模块提供了与操作系统交互的功能,比如读取或写入文件、操作目录、获取环境变量等。
import os
# 创建自定义数据集MyData ,它继承于 PyTorch 中的抽象类 Dataset
class MyData(Dataset):
# 初始化构造器
# root_dir 是数据集的地址,label_dir是数据标签的地址
def __init__(self, root_dir, label_dir):
# 获取数据集当中的所有图片
# 使用 self.value_name 相当于这个value_name被声明为了这个类当中的一个成员变量
self.root_dir = root_dir
self.label_dir = label_dir
# join()方法是 Python 中 os.path 模块的一个常见用法,用于连接一个或多个路径组件。
# 在这个例子中,它将 self.root_dir 和 self.label_dir 这两个路径组件连接起来,形成一个完整的文件或目录路径。
self.path = os.path.join(self.root_dir, self.label_dir)
# os.listdir(self.path): 使用 os 模块的 listdir 函数列出 self.path 指定的目录中的所有文件和子目录的名称。这些名称以列表的形式返回。
# self.img_path = ...: 将这个列表赋值给对象的 img_path 属性。
self.img_path = os.listdir(self.path)
# 重写 Dataset抽象类中的 __getitem__ 方法
# 方法作用是:获取数据集中的每一个图片,idx是索引下标(这个参数是默认重写的时候就会有的)
def __getitem__(self, idx):
img_name = self.img_path[idx]
img_item_path = os.path.join(self.root_dir, self.label_dir, img_name)
# Image.open() 函数会打开这个路径指向的图像文件,并返回一个 Image 对象,这个对象可以被用来进行各种图像处理操作。
img = Image.open(img_item_path)
label = self.label_dir
return img, label
# 返回数据集长度,这个也是 Dataset 抽象类当中的一个函数,需要进行重写
def __len__(self):
# len函数直接返回 img_path 这个列表的长度
return len(self.img_path)
# 测试代码
root_dir = "dataset/train"
ants_label_dir = "ants"
bees_label_dir = "bees"
bees_dataset = MyData(root_dir, bees_label_dir)
ants_dataset = MyData(root_dir, ants_label_dir)
# 训练集连接,就两个训练集加起来即可
train_data = ants_dataset + bees_dataset
通过这个类我们就可以挨个拿到数据集中的每一个数据啦,并且还可以定义更多的对于数据集中数据的操作。
Tensorboard
而 transform 在我们上一小节中的 Dataset 类中会非常常用,主要用于对我们 input 的数据(即图像)进行一个变换,比如要将所有图像统一到同一个尺寸,或者对图像中每一个数据进行一个类的转换。
但是因为演示 transform 的结果时会涉及到运行完一个对应方法之后图像的展示,而这会使用到 TensorBoard,因此我们先介绍TensorBoard。
在PyTorch中,TensorBoard 是一个强大的可视化工具,主要用于记录训练数据、评估数据、网络结构、图像等,并且可以在web上展示。它允许研究者和开发者在训练神经网络时观察和理解各种数据。通过TensorBoard,你可以跟踪模型的各种指标,如损失、准确率、学习率等,并且还可以可视化模型的结构、权重和梯度。
比如查看损失函数 loss 或者查看输入的图片数据:
接下来我们来浅浅进行一个介绍和使用。
SummaryWriter类介绍
- 实例化SummaryWriter对象
首先,你需要创建一个SummaryWriter对象,并指定一个日志目录。
from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter('runs/experiment-1')
- 记录标量数据
使用add_scalar方法记录单个数值,比如损失或准确率。
writer.add_scalar('Loss/train', loss.item(), global_step)
writer.add_scalar('Accuracy/train', accuracy.item(), global_step)
- 记录图像数据
使用add_image方法记录图像。
writer.add_image('Images/train', img_tensor, global_step)
- 记录直方图数据
使用add_histogram方法记录张量的分布直方图。
writer.add_histogram('Weights/layer1', model.layer1.weight.data, global_step)
- 记录文本数据
使用add_text方法记录文本信息。
writer.add_text('Model Architecture', str(model), global_step)
- 记录模型结构图
使用add_graph方法记录模型的结构图。
writer.add_graph(model, input_tensor)
- 关闭SummaryWriter
训练完成后,确保调用close方法关闭SummaryWriter。
writer.close()
注意事项
- 日志目录管理
确保指定的日志目录是存在的或者程序有权限创建它。
避免日志目录名称冲突,以免覆盖之前的实验结果。
如果不需要保留旧的日志数据,可以在开始新的实验前手动删除旧的日志目录。
- 全局步长(global_step)
global_step参数在add_*方法中非常重要,它用于在TensorBoard中正确排序和显示数据。通常,它可以是训练批次数、epoch数或其他表示训练进度的数值。
确保global_step是递增的,否则TensorBoard中的数据显示可能会出现问题。
- 数据类型与形状
记录的数据类型(如标量、图像、直方图等)应与使用的add_*方法相匹配。
对于图像数据,通常需要将其转换为PyTorch张量,并确保其形状正确(例如,对于RGB图像,通常是(C, H, W))。
- 及时关闭SummaryWriter
在训练结束后,记得调用close方法关闭SummaryWriter,以确保所有数据都被正确写入日志文件。
- TensorBoard启动
使用tensorboard --logdir=your_log_directory命令启动TensorBoard,并确保你的浏览器可以访问TensorBoard提供的端口。
- 内存与性能
SummaryWriter在记录大量数据时可能会占用较多的内存和磁盘空间。请确保你的系统有足够的资源来处理这些数据。
在某些情况下,为了减少内存使用,可以考虑减少记录数据的频率或调整记录的数据类型。
SummaryWriter类使用
在我们之前的 PyTorch 项目中新建一个 py 文件,写上下面的代码:
# SummaryWriter是一个关键类,它用于向TensorBoard记录信息。
# SummaryWriter对象提供了各种方法来记录模型训练或评估过程中的不同信息,
# 比如标量、图片、文本、直方图等,以及模型的结构图。
# 这些信息可以在TensorBoard的web界面上可视化,帮助用户更好地理解和调试模型。
from torch.utils.tensorboard import SummaryWriter
# Python编程语言中用于导入NumPy库的语句
# NumPy(Numerical Python的简称)是Python的一个开源数值计算扩展程序库,
# 它支持大量的维度数组与矩阵运算,此外也针对数组运算提供大量的数学函数库。
import numpy as np
from PIL import Image
# writer = SummaryWriter("logs") 这行代码用于创建一个SummaryWriter对象,并将日志数据保存到名为"logs"的目录中。
# 当你创建一个SummaryWriter对象时,你需要指定一个目录名(在这个例子中是"logs"),TensorBoard会从这个目录中读取日志数据。
# 每次你调用SummaryWriter的add_*方法(比如add_scalar、add_image等)来记录数据时,这些数据都会被写入到这个目录中。
writer = SummaryWriter("logs")
image_path = "dataset/train/ants/0013035.jpg"
img_PIL = Image.open(image_path)
# 将一个使用PIL(Python Imaging Library,现在更常用的库名是Pillow)库加载的图像(img_PIL)转换为一个NumPy数组(img_array)。
# 当你需要将图像数据用于数值计算或机器学习任务时,将其转换为NumPy数组是很有用的。
# NumPy是一个用于处理大型多维数组和矩阵的库,它提供了大量的数学函数库来操作这些数组
img_array = np.array(img_PIL)
print(type(img_array))
# 这里打印了发现我们输入的图片数据的规格为:H(Height)、W(Width)、C(Channel)
# 因此我们需要指定add_image的数据格式为 dataformats='HWC' 否则会报错,因为该函数默认是 CHW 的
print(img_array.shape)
# add_image方法用于将图像数据记录到TensorBoard中,以便在训练过程中可视化图像。
# "train":这是标签名,用于在TensorBoard中标识这个图像。你可以在TensorBoard的界面中通过这个标签来找到和查看这个图像。
# img_array:这是你想要记录的图像数据,通常是一个NumPy数组。
# 数组的形状应该是(height, width, channels),
# 其中height是图像的高度,width是图像的宽度,channels是图像的通道数(例如,对于RGB图像,通道数通常为3)。
# 1:这是全局步长(global step),用于在TensorBoard中标识这个图像是在哪个训练步骤或epoch中记录的。
# 确保这个值是递增的,以便TensorBoard能够正确地对图像进行排序和显示。
# dataformats='HWC':这个参数指定了图像数据的格式。'HWC'表示图像数据的形状是(height, width, channels),
# 这是最常见的格式,特别是在使用Pillow库加载图像时。
# 如果你的图像数据是以其他格式(例如,(channels, height, width),即'CHW'格式)存储的,你需要相应地调整这个参数。
# add_image方法通常用于记录单个图像。如果你想要记录一批图像,你可能需要遍历这批图像,并为每个图像分别调用add_image方法。
writer.add_image("train", img_array, 1, dataformats='HWC')
# writer.add_image()
for i in range(100):
# add_scalar方法用于记录单个数值,通常用于在TensorBoard中绘制标量(scalar)随训练步骤变化的曲线。
# 在下面代码writer.add_scalar("y=2x", 3*i, i)中:
# "y=2x":这是标签名,用于在TensorBoard中标识这个标量。它会作为图表上的标题或者图例的一部分显示。
# 注意,这里的"y=2x"实际上是一个文本描述,并不会影响TensorBoard中曲线的绘制方式;它只是用来标识这条曲线。
# 3*i:这是要记录的标量的值。在这个例子中,它似乎是随着变量i(可能是一个循环的迭代次数或者训练步骤数)的增加而线性增长的。
# i:这是全局步长(global step),用于在TensorBoard中标识这个标量是在哪个训练步骤或迭代中记录的。
# 这个值应该随着你的训练过程而递增,以确保TensorBoard能够正确地对标量进行排序和显示。
writer.add_scalar("y=2x", 3*i, i)
# 关闭 tensorboard
writer.close()
注意运行时如果出现下面问题:
说明环境中并没有安装 TensorBoard 工具包,使用下面的命令进行安装即可(注意要在你需要的环境当中装嗷,比如我们现在使用的环境是 pytorch 而非 base):
pip install tensorboard
运行结果如下:
此时在我们项目目录中就多了一个log:
而 logs 文件夹下的 events 文件就是事件的日志文件。
接下来我们就可以在命令行窗口中输入启动命令以在 web 中进行结果的查看:
有时候跑模型时会是多个用户一起跑,那就有可能大家的数据都会在这个 web 的默认端口上展现,就可能会非常混乱,因此 tensorboard 也允许我们自己指定自己项目的 web 端口 号,只需要将上面的命令后面跟个 port 参数即可:
tensorboard --logdir=logs --port=6007
注意:–logdir= 后面跟的是我们刚刚代码中写入的日志文件夹名称嗷。
然后我们就可以访问该 web 了,即图中蓝色部分的 http 地址:
补充:epoch的概念
“Epoch”在机器学习和深度学习的上下文中,通常指的是整个数据集上的一次完整的前向和反向传播过程。更具体地说,当模型遍历了所有的训练数据一次,并更新了其权重和偏差时,我们就说它完成了一个“epoch”。
Transform
在PyTorch中,Transform通常指的是用于预处理数据的一系列操作。特别是在处理图像数据时,Transform对象非常有用,因为它们允许你以一致的方式对图像进行预处理,比如裁剪、缩放、归一化等。
torchvision.transforms模块提供了许多预定义的Transform操作,你可以将它们组合起来以创建自定义的数据预处理流程。
下面是一些常用的Transform操作:
注意上面所谓的 Transform 操作指的都是函数嗷,一会儿我们可以在代码中观察到。
Transform使用方法
导入必要的模块:
首先,你需要导入torchvision.transforms模块,这个模块包含了大多数常用的图像转换操作。
import torchvision.transforms as transforms
定义转换操作:
你可以定义一系列的转换操作,比如调整图像大小、裁剪、转换为张量、归一化等。
transform = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])
对于 transforms 中的 Compose 方法的介绍:
在PyTorch的torchvision.transforms模块中,Compose方法的作用是将多个图像转换操作组合成一个单一的转换操作。当你有一系列的图像预处理步骤需要按照特定顺序应用时,Compose方法非常有用。
Compose方法接受一个转换操作列表作为输入,并返回一个新的转换对象。这个新的转换对象在调用时,会按照列表中转换操作的顺序依次应用到输入图像上。
使用Compose方法的主要好处是简化了代码,并确保了预处理步骤的一致性。你可以一次性定义所有需要的预处理步骤,并在加载图像数据时一次性应用它们,而不是在每次需要预处理图像时都手动调用每个单独的转换操作。
应用转换操作:
当你读取图像数据时,可以直接应用这个transform对象,它会自动按照定义的顺序对图像进行预处理。
from torchvision import datasets
train_dataset = datasets.ImageFolder(root='path_to_train_data', transform=transform)
自定义转换操作:
如果你需要实现一些特殊的转换操作,可以通过继承torchvision.transforms.Transform类并实现__call__方法来创建自定义的转换操作。
python
class CustomTransform(transforms.Transform):
def __init__(self, some_parameter):
super().__init__()
self.some_parameter = some_parameter
def __call__(self, img):
# 在这里实现你的转换逻辑
return transformed_img
注意事项:
输入和输出:
transform方法通常只接受一个参数(如一张图像)并返回一个处理后的结果。因此,在设计自定义的transform时,需要确保你的方法遵循这个规则。
参数传递:
如果你需要为你的转换操作传递额外的参数(比如概率、阈值等),可以在定义transform时通过构造函数(init)传递这些参数,并在__call__方法中使用它们。
组合转换:
使用transforms.Compose可以方便地组合多个转换操作,确保它们按照你期望的顺序执行。
数据类型和大小:
确保你的转换操作能够处理输入数据的类型和大小。例如,某些操作可能要求输入是PIL图像或NumPy数组,而输出是PyTorch张量。
性能考虑:
转换操作可能会影响数据加载的性能,特别是在处理大型数据集时。因此,尽量使用高效的转换操作,并在必要时进行性能优化。
保持一致性:
确保在训练和测试阶段使用相同的转换操作,以保持数据的一致性。这有助于模型更好地泛化到未见过的数据。
transform 其实具体指的就是 transforms.py 这个python文件(也就是transform的源代码,在PyCharm中使用ctrl+鼠标单击可以查看库源码),它可以被当作成一个工具箱,其内置了非常多的类,这些类包含了处理图片的很多方法,我们数据集中的图片通过这些工具的加工处理之后就能够得到我们想要的图片结果:
Transform使用实战
在项目中新建一个 py 文件 Transform.py :
from PIL import Image
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms
# 通过 transform.ToTensor去看两个问题
# 第一个问题:为什么我们需要Tensor数据类型
#
# 将图像数据转换为Tensor类型在深度学习和PyTorch框架中是非常重要的,原因主要有以下几点:
#
# 1、框架兼容性:PyTorch是一个基于Tensor的深度学习框架。Tensor是PyTorch中的基本数据结构,用于存储模型参数、输入数据和中间计算结果。
# 因此,为了与PyTorch框架兼容并充分利用其提供的各种功能和优化,需要将图像数据转换为Tensor类型。
#
# 2、自动微分:PyTorch的Tensor支持自动微分,这是深度学习模型训练过程中的关键步骤。
# 通过计算损失函数关于模型参数的梯度,可以更新模型参数以优化模型的性能。只有Tensor类型的数据才能利用PyTorch的自动微分引擎进行梯度计算。
#
# 3、GPU加速:Tensor可以方便地转移到GPU上进行计算,从而利用GPU的并行计算能力加速深度学习模型的训练和推理过程。
# 这对于处理大规模图像数据集和复杂模型尤为重要。
#
# 4、数据标准化:将图像数据转换为Tensor类型时,通常会进行像素值的标准化(例如,将像素值范围从0-255缩放到0.0-1.0)。
# 这种标准化有助于模型更好地学习图像的特征,并提高模型的性能。
#
# 5、维度调整:在深度学习中,卷积神经网络通常期望输入数据的维度为(C, H, W)(通道数、高度、宽度)。
# 将图像转换为Tensor类型时,可以方便地调整数据的维度以满足模型的要求。
img_path = "dataset/train/ants/0013035.jpg"
img = Image.open(img_path)
# 打印img图片可以发现其是 JpegImageFile
print(img)
writer = SummaryWriter("logs")
# 第二个问题:transforms该如何使用(在python中)
# 创建一个 ToTensor 转换对象,并将其赋值给变量 tensor_trans。
# ToTensor() 本身是一个类的构造方法,它返回的是一个 ToTensor 类的实例,而不是直接返回一个转换后的张量。
# ToTensor 类是一个预处理转换类,用于将PIL图像或NumPy ndarray转换为PyTorch张量。
# 当你使用这个类的实例(即 tensor_trans)去调用一个图像时(例如 tensor_trans(image)),它会对图像执行转换操作,并返回转换后的张量。
# 换句话说,ToTensor() 并不直接返回任何东西;它只是初始化一个转换对象。真正的转换操作是在你调用这个对象(作为一个函数)并传入图像数据时发生的。
tensor_trans = transforms.ToTensor()
# __call__ 是Python中对象的特殊方法,它允许对象像函数一样被调用。
# 当你尝试调用一个对象(例如 tensor_trans(img))时,Python实际上是在调用该对象的 __call__ 方法。
# 因此,tensor_trans.__call__(img) 与 tensor_trans(img) 是等价的。
# 这行代码的作用是调用 tensor_trans 对象(即 ToTensor 类的实例)来转换传入的图像 img。
# 具体来说,它将 img(一个PIL图像或NumPy ndarray)转换为PyTorch张量,并返回这个张量。
# 转换过程包括:
# 1、将图像数据从PIL图像或NumPy ndarray格式转换为PyTorch张量格式。
# 2、将图像的像素值从0-255的整数范围缩放到0.0-1.0的浮点数范围。
# 3、将图像的维度从 (H, W, C) 调整为 (C, H, W),以适应PyTorch卷积神经网络的输入要求。
# 最后,转换后的张量(tensor_img)会被赋值给变量 tensor_img,以便后续在PyTorch中使用。
tensor_img = tensor_trans.__call__(img)
# 打印tensor_img可以发现其是 tensor 类型
print(tensor_img)
writer.add_image("Tensor_img", tensor_img)
writer.close()
运行结果如下:
web 浏览器中可以查看:
除了上述注释中的区别外,我们还可以通过查看各个变量的内容来理解为什么我们需要将数据转换为 Tensor 张量类型。
先来看上述代码中的 img 图片变量的内容都有些什么:
再来看被转换成 Tensor 类型的 tensor_img 变量里有什么:
不难看出,在Tensor 张量类型中存储了很多在神经网络中需要使用到的内容,比如 _backward_hooks反向传播、神经网络训练使用的设备 device = cpu 等等,因此将输入数据转换为 Tensor 张量是必须的。
常见Transform方法的使用
在Transform方法的使用上,主要是要注意下面一个问题,即每个方法需要什么要的输入以及会有什么样的输出的问题:
关于作用应该是都比较清楚的(就是向神经网络输入数据嘛),关键是输入与输出的操作上容易出问题,因此重点来讲一下这两个部分。
图片数据一般都会有不同的格式,比如 PIL 就是由 Image.open() 获得的图片类型;tensor 就是由 ToTensor() 的方式获得的图片类型;还有 narrays 是通过 opencv(python版本)的cv.imread() 获得的图片类型。
不管是使用 PIL 还是 opencv 获得的数据格式都需要被转换为 Tensor 的张量形式才行,转换为张量形式之后就可以应用更多的 Transform 的类与函数,重点关注使用不同的方法会需要输入什么样的数据类型然后还会输出什么样的类型即可,接下来结合几个案例来看一下哪些是比较常用的:
from PIL import Image
from torch.utils.tensorboard import SummaryWriter
from torchvision.transforms import transforms
writer = SummaryWriter("logs")
# 1、python自带的 PIL 方法所获得的图片类型
img = Image.open("images/img.png")
# 注意上面的代码会有问题,png图片色彩是四通道的,比jpg多一个Alpha通道,因此会报错
# 解决办法是进行色彩通道的转换,将其转成三通道的即可
img = img.convert("RGB")
# 打印 PIL 的文件类型:<PIL.PngImagePlugin.PngImageFile image mode=RGB size=1148x750 at 0x2D4DB273D10>
print(img)
# 2、ToTensor方式获得张量形式的 tensor 的图片类型
trans_tensor = transforms.ToTensor()
img_tensor = trans_tensor(img)
# 打印 ToTensor 的文件类型:tensor([[[0.6510, 0.6588, 0.6667, ..., 0.4902, 0.4902, 0.4902],
# ...[1.0000, 1.0000, 1.0000, ..., 1.0000, 1.0000, 1.0000]]])
print(img_tensor)
# 可视化显示
writer.add_image("ToTensor", img_tensor)
# 3、在张量类型基础上,使用transform中的归一化函数,即Normalize
# Normalize() 是 PyTorch 中 torchvision.transforms 模块下的一个函数,主要用于对图像数据进行标准化处理。
# 标准化是图像预处理中的一个重要步骤,通常用于将数据调整到一定的范围内,同时消除不同维度之间的量纲影响,使模型更容易收敛。
# 参数解释:
# mean (sequence可变列表 or tuple不可变列表 of float): 每个通道的均值。
# std (sequence or tuple of float): 每个通道的标准差。
# transforms.Normalize() 的作用是对输入图像的每个通道进行标准化,
# 标准化公式如下:normalized[channel]或者叫input[channel] = (input[channel]-mean[channel])/std[channel];
# 其中,input 是输入的图像数据,mean 和 std 是指定的均值和标准差。这个公式会将输入数据转换成一个以 0 为中心,标准差为 1 的分布。
trans_norm = transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
img_norm = trans_norm(img_tensor)
writer.add_image("Normalize", img_norm, 2)
# 4、在张量类型基础上,使用transform中的Resize函数
# Resize 函数通常用于调整图像或其他张量的大小。这个函数是torchvision.transforms模块的一部分,通常用于数据预处理和增强。
# Resize 可以帮助你将图像或张量调整为特定的尺寸,这对于模型训练和评估中的一致性以及性能优化都非常重要。
# 注意:在源码解释中Resize函数是可以对张量tensor使用的,但是要注意格式,但是很多资料貌似不推荐这样做或者说
# 是之前的PyTorch版本不支持Resize对张量的做法,建议还是只对 PIL 类型的图像数据使用Resize吧,
# 毕竟Resize函数主要是为PIL图像设计的,因为它依赖于PIL库的内部插值方法。
# 参数解释:
# size (tuple or int): 输出图像的大小。如果输入是一个整数,则输出图像将具有该大小的高度和宽度。如果输入是一个元组,则元组的第一个元素是高度,第二个元素是宽度。
# interpolation (int, optional): 插值方法。默认为 PIL.Image.BILINEAR。如果输入大小小于原始图像大小,建议使用 PIL.Image.NEAREST。
# 先打印一下原来的大小:1148*750
print(img.size)
trans_resize = transforms.Resize((512, 512))
img_resize = trans_resize(img)
# 输出img_resize = <PIL.Image.Image image mode=RGB size=512x512 at 0x2482356FDD0>
# 看得出来图片大小变成了 512*512 大小了捏
print(img_resize)
# 因为此时的img_resize 图片类型只是大小切换了但类型没换,如果想通过tensorboard进行展示
# 就需要将 PIL 转换成 tensor 张量嗷
img_resize = trans_tensor(img_resize)
writer.add_image("Resize", img_resize, 0)
# 5、使用Compose方法来将上面这个转换过程给流水线化
trans_resize_2 = transforms.Resize(512)
# 对于下面这行代码的输入是 PIL 图片,经过 PIL -> PIL -> Tensor,最后变成 Tensor 类型图片输出
# 注意类型的连贯性,即列表中后一个元素的输入和前一个元素的输出数据必须是相同的数据类型,不然会报错
trans_compose = transforms.Compose([trans_resize_2, trans_tensor])
img_resize_2 = trans_compose(img)
writer.add_image("Resize", img_resize_2, 1)
# 6、使用RandomCrop进行数据的随机裁剪,注意其输入也是 PIL 类型的图片,其参数也和上面的Resize函数类似
trans_random = transforms.RandomCrop(512)
trans_compose_2 = transforms.Compose([trans_random, trans_tensor])
# 随机裁剪十个
for i in range(10):
img_crop = trans_compose_2(img)
writer.add_image("RandomCrop", img_crop, i)
writer.close()
运行程序后,归一化后的图片效果:
Resize变换大小以后的图片效果:
对区域进行随机裁剪后的图片效果:
剩下的都是不太常用的函数了,如果有特殊需要用的函数,可以按照下面总结的方式进行查看。
总结一下,注意看官方文档的意思就是直接点进框架源文件中看源码,里面注释都写的很清楚:
补充:归一化
在PyTorch中,归一化(Normalization)是一种预处理技术,主要用于将输入数据调整到适当的范围或分布,以便模型能够更好地进行训练。归一化的主要作用包括:
1、加快训练速度:
归一化可以将数据变换到接近标准正态分布(均值为0,标准差为1),这有助于梯度下降算法更快地收敛。因为当数据分布较为均匀时,模型的权重更新会更加稳定,训练过程也会更加平滑。
2、提高模型性能:
通过归一化,不同特征之间的尺度差异被消除,模型可以更容易地学习到各个特征之间的相对重要性。此外,归一化还可以减少过拟合的风险,因为模型不再需要适应不同尺度上的数据变化。
3、简化超参数调整:
归一化可以减少对学习率等超参数的依赖,因为数据分布变得更加稳定,模型对超参数的变化不再那么敏感。
补充:PyCharm 中查看文件结构的技巧
在 PyCharm 中的左下角的位置,可以打开我们想要查看的任一文件的文件结构:
这有助于帮助我们查找并理解需要的函数或者类。
torchvision中的数据集使用
官网 torchvision 中关于数据集的介绍
如果需要实现一些自己的需求任务的话,需要自己搞一些自定义的数据集。
我们前文说的都是单个图片在 transform 中的使用,但实际训练过程中 transform 都是要和批量的图片进行结合使用的,因此这个小节专门来说如何把数据集和 transform 结合在一起,同时介绍一些在科研当中标准的数据集需要如何下载如何组织如何去查看和使用的方法。
打开 pytorch 的官网,然后可以看到 torchvision 模块:
点进该模块可以发现有一大堆数据集相关的 API 文档,从这些数据集里面选出我们需要的,然后只要写代码的时候参考一下该文档,从中获得一些关于这份数据集的参数,然后设置在代码中就可以了,这样我们的工程就能够自己去下载和使用这些在科研当中的数据集或者是标准的数据集,会很方便:
点进上图中的Datasets,可以发现有诸多数据集:
包括我们之前提到的非常经典的 CIFAR 数据集等都在上面是存在的。
注意上上图中的 Model 模块:
在这个模块中提供了很多常见的一些神经网络,有些神经网络已经预训练好了,这个部分和上面数据集的部分是我们工作学习中会比较常用的模块。
接下来我们来动手实现一下如何将 torchvision 中的数据集和 transform 结合起来使用,先在官网上选择一个数据集:
注意上图的左侧和右侧是不一样的嗷,左边这种引入方式(如CIFAR10(root[,train,transform,…]))意思是使用 PyTorch 框架设置代码然后通过网络拉取数据集,这样数据集不会存储在本地上,而右侧这种方式(CIFAR10 Dataset)则是可以直接下载数据集到本地,另外其也对该数据集进行了详细的介绍,可以简单来看一下:
简单介绍一下 CIFAR-10 数据集:
从上图可以知道,CIFAR-10数据集收集了六万张32*32像素大小的彩色图片,然后这个图片共有十个类别。
每个类别有六千张图像,其中有五万张图片是训练图片,有一万张图像是测试图片。
那我们肯定是用左边这种方式啦,比如选择最经典的 CIFAR10 :
从上图可以看到我们的代码应该怎么写,只要设置好对应的参数即可,解释一下几个 Parameters :
root:
设置数据集的位置。
train:
如果其为 true ,那么创建的数据集就是一个训练集,如果为 false 的话,那创建的就是一个测试集。
transform:
如果想对数据集当中的所有数据进行一个什么样的变化,我们把 transform 写到这个参数里面即可。
target_transform:
这是用来对 目标 进行 transform 变化的。
download:
设置为 true 的话就自动下载这个数据集,如果为 false 那么就不会下载。
从 torchvision 中下载我们需要的数据集
import torchvision
# 引入数据集,在PyCharm中使用 ctrl+p 可以查看当前函数所需参数
# 这个函数显然只需要一个root,其它的都有默认值
# 将root设置为./dataset,这意味着程序会在当前文件目录下创建一个文件夹dataset2,同时把文件保存进去
# 创建训练数据集
train_set = torchvision.datasets.CIFAR10(root="./dataset2", train=True, download=True)
# 创建测试数据集
test_set = torchvision.datasets.CIFAR10(root="./dataset2", train=False, download=True)
下的很慢,不过俺是下完啦,下完了就能看见我们项目的目录结构中多了一个 cifar 的数据集,以 tar.gz 结尾的是压缩包,然后上面的 cifar-10-batches-py 是其解压出来的文件:
可以看见用这种方式获取数据集是非常方便的(就是下载比较墨迹)。
打印看看数据集:
# 测试一下,查看测试集中的第一个数据
print(test_set[0])
运行结果如下:
可以看见和我们之前学习的内容是一样的。
我们可以看一下每个图片里面有些什么属性,给上面的打印语句打上断点:
从变量列表里面可以看到其内部包含了很多属性:
我们来打印其中的一些:
运行结果:
其目标类别为3,从 classes 属性列表中我们不难发现,这第一张图片是一只猫。
将torchvision的数据集与transform与tensorboard相结合
若要使用 transform,那么势必要先将我们的 PIL 格式的图片数据转换为 PyTorch 的张量形式嗷:
运行结果如下:
可以发现所有数据类型已经被转换成为张量了。
接下来我们再举例简单说下其在 tensorboard 中的使用:
运行结果如下:
可以看到我们的数据集已经可以在 tensorboard 中进行使用了。
DataLoader的使用
DataLoader类以及其参数介绍
DataLoader是一个加载器,所做的事情就是将我们的数据加载到我们的一个神经网络当中。
DataLoader每次都会从我们的Dataset当中去取数据,每次取多少怎么取这个过程是由DataLoader当中的参数进行设置的。
以平常打牌为例子,牌堆中的牌就是我们的Dataset,而DataLoader就是我们的手,每次取多少(取多少牌)怎么取(一只手还是两只手,左手还是右手)由手说了算,比如上图我们取了五张牌,便是从Dataset中每次加载五个数据到神经网络(对局)当中,差不多理解就行。
接下来我们还是从 PyTorch 的官网中查看 DataLoader,然后再写个例子来深刻理解一下 DataLoader 的使用。
在首页的 Docs 文档中找到 PyTorch 官方文档:
点击进入后,搜索 Dataloader:
搜索结果展示在了上图的右侧,可以发现 Dataloader 出现在 torch.utils.data 包下,点击进入红色箭头的部分(因为我们要查看的是这个 DataLoader 的 Python 类,别误入了上面那个关于模块的链接中嗷):
这个文档对于 DataLoader 进行了非常详尽的解释。
从上图中可以发现这个类的参数非常多,但大部分其实都是有默认值的,只有 dataset 这个参数没有默认值是需要我们指定的,即我们之前写的 Dataset。
实际上使用也很简单,就把我们之前自定义的 dataset 实例化,然后放到这个 Dataloader 当中即可。
同时因为大部分参数都是有默认值的,因此我们实际在训练过程中也只是需要手动设置少量的参数即可。
这一节我们就讲解一下常见的一些参数设置。
dataset:
这个参数指定了 DataLoader 要加载的数据集,假如是 test_data。那么 test_data 应该是一个实现了 __len__ 和 __getitem__ 方法的对象,这样 DataLoader 才能迭代它。
batch_size:
这个参数指定了每个批次(batch)中样本的数量。
shuffle:
这个参数指定是否在每个训练周期(epoch)开始时打乱数据。设置为 True 意味着在每个周期开始时,test_data 中的样本顺序会被随机打乱。这在测试时通常不是必需的,因为测试集通常不需要进行多次遍历或打乱。但在某些特定场景下,例如当你想要多次评估模型性能并确保每次评估时样本顺序都不同时,可以使用这个设置。该参数默认情况下是false,我们一般喜欢设置为true。
sampler与 batch_sampler放在后面再说。
num_workers:
这个参数指定了用于数据加载的子进程数。设置为 0 意味着数据将在主进程中同步加载,这通常适用于调试和单 GPU 训练。设置为大于 0 的值会使用多个子进程异步加载数据,这可以加速数据加载,特别是在使用多个 GPU 或大型数据集时。
drop_last:
这个参数决定了当数据集的大小不能被 batch_size 整除时,是否丢弃最后一个不完整的批次。
其主要是用来标识每次取数据时如果有余数是不是需要舍去,比如数据是一百张图片,假设每次取三张图片数据,那么一百除以三的话最后会余1,那么这一张牌是否需要舍去。
代码实战
编写如下测试代码:
运行结果如下:
按照代码中的逻辑,可以看见每一次循环所返回的 imgs 和 targets 都是四个四个的,torch.Size([4, 3, 32, 32]) 标识共有四张图片,其规格大小为 32*32 且是RGB三通道的。
其输出后面紧跟着 tensor([1, 2, 4, 8]) 的意思则是对应的 targets 列表,因为是 shuffle 过的,因此是随机抓取的四张图片所对应的target。
我们也可以在 tensorboard 中进行一个测试,将 batch_size 改成 64 来看一下效果:
运行结束后,在 tensorboard 中的运行结果为:
step的意思也就是第几次取数据,从上面的图中可以看到,每一次取数据都是 64 张图片,并且每一次取的都不一样,只有最后一次取数据的时候不一样:
之所以会这样就是因为我们的 drop_last 参数设置为了 false,因为最后这 16 张不够 64 张,但我们也将其保留下来不舍去。
神经网络
依然是打开 PyTorch 的官网,点击下图中的 PyTorch 模块:
进入后在左侧不难发现有很多基于 Python 语言的 PyTorch API :
而关于神经网络的工具都位于 torch.nn 这个包里:
torch.nn 是 PyTorch 深度学习框架中用于构建神经网络的关键模块。它包含了多个子模块,每个子模块都提供了特定的功能或组件。以下是 torch.nn 下一些常见的子模块及其解释:
Containers:
提供了用于组织网络层的容器类,如 nn.Sequential 和 nn.ModuleList。nn.Sequential 允许你按顺序堆叠多个层,而 nn.ModuleList 则是一个可以包含多个模块的列表。(相当于是给我们的神经网络定义了骨架)
Convolution Layers
Convolution Layers(卷积层)是torch.nn模块中的一个重要部分,用于实现图像的卷积操作。其中,torch.nn.Conv2d是最常用的二维卷积层类。
torch.nn.Conv2d的主要参数包括:
in_channels:输入图像的通道数,也就是输入层神经元的个数。对于彩色图像,这通常是3(红、绿、蓝三个通道)。
out_channels:通过卷积后产生的输出图像的通道数,也是输出层神经元的个数。这个参数决定了卷积层将有多少个卷积核(也称为滤波器或特征检测器)。
kernel_size:单个卷积核的尺寸。可以是一个整数,表示卷积核的高度和宽度相同;也可以是一个元组,表示卷积核的高度和宽度可能不同。
stride:卷积核在输入图像上滑动的步长。如果步长大于1,则卷积核在滑动时会跳过一些像素。
padding:在输入图像的边缘添加零填充的层数。这可以控制输出特征图的大小,并防止信息在卷积过程中丢失。
神经网络基本骨架——nn.Module
之前说过,Containers 模块就是用来完成神经网络骨架的定义的,那么我们依然是查看官网,发现其由六个部分组成:
在这六个部分中,毫无疑问 Module 部分是我们最最常用的,其作用是Base class for all neural network modules.(所有神经网络模块的基类)
当我们想要定义一个神经网络层或整个神经网络时,通常会通过继承torch.nn.Module类来实现。
这个基类提供了一些基本的方法和属性,如.parameters()(返回模型中的可学习参数)、.zero_grad()(将模型中所有参数的梯度清零)以及.forward(input)(定义前向传播的行为)等。通过继承这个基类并实现特定的功能,我们可以创建自定义的神经网络层或整个网络模型。
点进去可以看到官网给出了比较详细的介绍和示例:
接下来我们就按照官网给出的代码来写一个例子:
运行结果如下:
为什么调用example实例(即example_module模型)并将x作为输入而这会自动调用forward方法并返回结果呢?
在 PyTorch 中,当你创建一个继承自 nn.Module 的类的实例(比如 example 这个实例,它是 example_module 类的实例),这个实例(模型)就被设计成了可以直接接受输入数据并返回输出数据的对象。
这种设计方式背后的原理是 PyTorch 的 nn.Module 类内部实现了一个 call 方法,这个方法会在你尝试“调用”模型实例(即使用括号和输入参数)时被自动触发。在 nn.Module 的 call 方法内部,它首先会处理输入数据(例如,将其移至正确的设备,如 CPU 或 GPU ),然后调用模型的forward方法来计算输出,最后返回这个输出。
因此,当你写下 output = example(x) 这样的代码时,实际上发生了以下几步:
1、Python解释器看到example(x)这样的调用,于是尝试在example对象上调用__call__方法。
因为example是example_module类(继承自nn.Module)的实例,所以它会使用nn.Module中定义的__call__方法。
nn.Module的__call__方法首先处理输入数据x(在这个例子中,可能只是简单地将其保留原样,因为没有指定任何特殊的设备或数据类型转换)。
2、接着,__call__方法调用example实例的forward方法,并将处理过的输入数据(在这个例子中是x)作为参数传入。
forward方法执行其定义的计算(在这个例子中是input + 1),并返回结果。
__call__方法捕获forward方法的返回值,并将其作为整个调用example(x)的结果。
3、最后,这个结果被赋值给变量output。
这种设计方式使得PyTorch模型的使用非常直观和简洁,你只需像调用函数一样调用模型实例,而无需显式地调用forward方法。这大大提高了代码的可读性和易用性。
神经网络——卷积层
理论讲解
依然是先看官方文档:
可以看到官方文档中的 nn 下的 Convolution Layers(卷积层)类型有很多,其中最常用的就是 Conv1d 和 Conv2d,其他的都不常用,如果要用到的话用法是类似的。
我们重点讲解 Conv2d 这个函数,其中 1d 表示输入数据是一维的,那么 2d 就表示输入数据是二维的,而我们的图片数据就是二维的。
点进 Conv2d ,可以发现其用法:
其参数列表解释如下:
对于参数将其翻译成中文更好看一些:
常用参数的一点补充说明:
in_channels:这个比较好理解,就是我们输入图片的通道数。
out_channels:那么卷积结果的通道数是什么意思?
其实就是卷积神经网络中最后得到的特征图的维度,也就是这里所谓的通道数:
可以看到输入通道数是 1 ,因为是一个二维平面图,此时我们的out_channel也设置为了1,这表示卷积结果特征图也就是一维的,那么卷积核只会用到一个。
如果out_channel设置为了2呢?那就表示用到了两个卷积核进行卷积操作,得到的卷积图就是二维的:
这个概念可以参考本专栏的另外一篇文章(《卷积神经网络CNN入门》),这里不再赘述。
kernel_size:这个参数定义了卷积核的大小,在搭建卷积层的时候我们只需要定义其大小即可,其中的数并不需要我们进行设置。那么卷积核中的数据从哪里来呢?其实是从一些概率论的公式理论分布中计算得到的,这个过程是自动的,值取多少都无所谓,因为经过不断的前向、反向传播后这些值都会被逐步调整为一个更优秀的值。
stride 和 padding 在下文中会进行详细解释,这里不再赘述。
dilation 参数简单用个图来示意:
接下来我们画图来简单解释一下卷积的一般流程,然后再在代码当中实操一遍,但其实卷积操作在本专栏的另外一篇文章中说的很清楚(《卷积神经网络CNN入门》),这里就再简单说一下也让自己加深一点印象。
从上图中可以看到,有一个二维的 5x5 大小的输入图像,然后右侧有一个 3x3 大小的卷积核。
卷积核会在输入图像中一个部分一个部分的按照卷积核大小的范围进行扫描,像下面这样:
然后进行卷积计算,卷积计算非常简单,就是对应位相乘,然后加起来即可:
这样我们就可以算出特征图(卷积之后的结果叫特征图)中的第一个特征值,然后卷积核会向右进行移动,移动的距离也叫步长或者步径我们是可以控制的,通过 stride 参数可以控制。这里我们默认为 1 ,那么卷积核向右移动一个单位的距离:
一样的计算方法,我们就拿到了特征图中的第二个值,然后依次类推从左到右遍历完,向下进行移动的时候也是可以通过 stride 参数控制的,因为这里默认为 1 ,因此其向下走时也为 1 个单位距离:
这样从左往右,从上到下遍历结束之后就可以拿到我们的卷积结果——特征图了捏:
按照这样的方法,那么当 stride 也就是步长为 2 的时候应该也很好理解了,会得到一个 2x2 大小的特征图,推理过程不再赘述。
这里还要补充一个常用的参数,也就是 padding 填充参数的概念,关于这个概念也在我本专栏中的另一篇文章《卷积神经网络CNN入门》中有提过,这里不再赘述,就简单举个例子说明一下,以 padding 参数设置为 1 为例:
padding 为 1 时,可以看见在输入数据的图像中,其大小扩大了一个圈,变成了 7x7 大小,而这最外围的一圈的值将被设置为 1,虽然一般习惯上我们都设置为 0。
代码实战
我们先用代码来验证一下上面所说的理论,然后再搭建一个完整一点的简单的神经网络进行代码实战。
注意代码中我们写了的参数都是一般情况下最常用的参数,也就是 Conv2d 函数的前五个参数(in_channels, out_channels, kernel_size, stride 和 padding),剩下的除非特殊情况,否则一般默认的就足够了,无需特殊设置。
运行结果如下:
可以对理论讲解部分中的内容进行参考,看是否结论一致。
接下来我们简单搭建一个稍微完整一点的卷积神经网络的代码示例:
运行结果如下:
从上图可以看到,输入时数据的 size 是:[64, 3, 32, 32] 的,其中 64 是我们设定的 batch_size 大小,3 是因为图片数据是 3 通道的因此我们在设置卷积的时候 in_channels 设置为了 3 ,32 则是我们的数据集的图片大小,其为 32x32 像素。
然后输出数据的 size 则变成了:[64, 6, 30, 30],开头 64 意义不变,但可以看到 out_channels 通道数变成了 6,这是因为我们设置了输出通道数为 6,即使用六个卷积核来进行卷积操作,因此最后得到特征图就为 6 维的了,30 则表示在卷积过程中得到的特征图都会缩小(从前文的理论讲解中不难得知),因此最后得到的特征图大小为 30x30 .
除了上述这种数值型的结果,其实我们还可以使用 tensorboard 来看一下卷积之后的图片究竟会变成什么样,代码就不写了,卷积之后的效果如下:
可与看到卷积之后图片的特征大致都能被提取出来,依稀是能够可以看出原图是啥的。
神经网络——池化层
理论讲解
依然是看官方文档:
最最常用的是最大池化函数:MaxPool2d,其参数说明如下:
参数中文翻译如下:
最大池化的思想依然是老样子,在我的《卷积神经网络CNN入门》一文中有详细解释,这里不再过多赘述,简单贴几张图示例一下,并提醒下几个点吧:
从上图中不难发现池化核 kernel_size 大小为 3,而按照官方文档的说法 stride 参数值和 kernel_szie 值是一样的,因此也为 3,那么池化核向右池化时直接跨越三个单位长度,成了如下情况:
可以看到有一侧边缘是没有值的,此时池化结果中需不需要保留含有一部分空值的池化核中的值呢(按最大池化的思想这部分的值也就是 3 )?
这就和 Ceil_mode 参数值有关了,如果其为 true,说明就要保留,为 false 则不保留:
最后池化结果得到的特征图就是:
因此一般使用最大池化 MaxPool2d 函数时,就只需要设置 kernel_size 参数即可,其他的默认就足矣。
代码实战
当 ceil_mode 参数被设置为 True 的时候:
运行结果:
当 ceil_mode 被设置为 False 的时候:
可以看到,和我们预期的结果是一模一样的。
这里同样可以使用 tensorboard 来查看池化的效果是什么样的,代码不写了,看效果图:
由于池化的作用就是下采样,也就是给特征图瘦身,因此可以明显看到输出的特征图都很糊。
神经网络——非线性激活
理论讲解
对于非线性激活这一层,我们一般使用 RELU 和 Sigmoid 比较多,但 RELU更主流一些,因此我们以 RELU 为例进行讲解,可以从官方文档中找到下面的信息:
ReLU(Rectified Linear Unit)是一种在神经网络中广泛使用的激活函数(activation function)。ReLU 函数的定义非常简单,它对于输入的每一个元素应用以下公式:
f(x) = max(0, x)
换句话说,ReLU 函数将所有负的输入值都设置为0,而正的输入值保持不变。这种操作被称为“整流”或“修正”,因此 ReLU 的名称由此而来。
上图中可以看到 ReLU 函数需要一个参数 inplace 替换的意思,什么意思呢?
其实就是设置是否需要返回值的意思,从上图中是比较好理解的,一般这个参数我们设置为 False。
因此 ReLU 是比较简单的,所以我们直接上代码演示。
代码实战
运行结果如下:
不难看出负数都为零了,而正数则还是正数。
神经网络——线性层及其它层的介绍
神经网络的骨架中,我们已经学习了 Containers 容器,Convolution Layers卷积层,Pooling Layers池化层,Padding Layers扩展层(这一层用的非常少,因为其功能在之前介绍卷积层的函数参数中都是提供了的,所以就当讲解过了),Non-linear Activations(非线性激活)等等。
接下来再简单介绍一下其他层,没有被介绍到的层那就说明并不重要或者说是极少极少情况才会使用到的,有需要的话就看文档即可,毕竟本文只是用来入门 PyTorch 的并非精通。
Normalization Layers(正则化层)
正则化层用的不是很多,主要是有一篇论文中提到如果使用了正则化的话会提高我们模型的训练速度。
如果要用到的话其中 BatchNorm2d 这个函数是使用的最多的:
基本上使用这个函数就只要将 num_features 设置一下,并且知道这个值来自输入数据中的 C 参数,其余的参数默认即可,因此不再赘述。
Recurrent Layers(循环神经网络层)
PyTorch中的Recurrent Layers(循环层)模块主要用于构建循环神经网络(RNN)及其变种,如长短时记忆网络(LSTM)和门控循环单元(GRU)。这些网络结构特别适用于处理序列数据,因为它们能够记住之前的输入信息,并将其用于影响当前的输出。
在PyTorch中,你可以使用nn.RNN、nn.LSTM和nn.GRU等类来定义循环层。这些类都提供了多种参数和选项,以便你能够根据自己的需求定制网络结构。
此外,nn.RNNCell、nn.LSTMCell和nn.GRUCell等类提供了单个循环单元的实现,这些单元可以在更复杂的网络结构中作为组件使用。这些类允许你更精细地控制循环层的内部机制,包括如何计算隐藏状态和更新权重等。
对于具体的实现细节,你可以参考PyTorch的官方文档和相关教程。这些资源将为你提供关于循环层模块的更多信息,包括如何定义网络结构、设置参数以及进行训练等。
这个对于我们来说用的不多,如果确实有这方面的需求那么再来查询官方文档进行使用就可以了,知道有这么个东西即可。
Transformer Layers(PyTorch中用于实现Transformer模型的层或组件。)
PyTorch中的Transformer Layers是用于实现Transformer模型的模块。Transformer模型是一种深度学习模型,特别适用于自然语言处理任务。它通过使用自注意力机制和位置编码来实现对输入序列的编码和解码,可以应用于机器翻译、文本生成、文本分类等多种场景。
具体来说,Transformer模型由多个编码器和解码器组成。编码器负责将输入序列中的每个元素转换为一个连续的向量表示,而解码器则将这些向量转换为目标序列中的每个元素。每个编码器和解码器都由多个Transformer层组成,每个层包括自注意力机制、前馈神经网络等组件。
在PyTorch中,你可以使用nn.Transformer、nn.TransformerEncoder、nn.TransformerDecoder等类来定义Transformer模型的不同部分。这些类提供了Transformer模型的各个组件,包括Transformer编码器、Transformer解码器以及整个Transformer模型的实现。通过组合这些组件,你可以构建出具有不同参数和配置的Transformer模型,以适应不同的任务需求。
此外,PyTorch还提供了一些工具和函数,如nn.MultiheadAttention、nn.PositionalEncoding等,以帮助你更方便地实现Transformer模型的各个部分。这些工具和函数可以简化代码并提高开发效率。
总的来说,PyTorch中的Transformer Layers是一个强大的模块,它为你提供了构建Transformer模型所需的所有组件和工具,使你能够轻松地实现各种基于Transformer的自然语言处理任务。
这个对于我们来说用的不多,如果确实有这方面的需求那么再来查询官方文档进行使用就可以了,知道有这么个东西即可。
重点:Linear Layers(线性层)
理论讲解
这个就用的很多啦,需要重点讲。
上图是官方文档的介绍,接下来我们结合下图一起学习,看看这个线性层到底是什么东西:
上图的层与层之间就是一个典型的线性连接的关系,参数中的 in_features 就是上面左侧输入的部分,比如 x1、x2…xd,那么对应上图的话 in_features 的值就为 d,这些值经过线性连接之后会输出一些值,上图中就是 g1、g2…gL,那么此时的 out_features 就是 L。
那么线性连接如何连接?从官方文档可以看到,是 K*x + b;
其中这个 b 就是一个偏置项,如果 Linear 函数参数中的 bias 设置为 true,那么这个 b 就会被加上,否则就不会被加上。
而 其中的 K 就是一个权重参数,也就是官方文档中的 Weight :
这样我们就可以得到一次线性连接的结果了。
代码实战
我们要完成一件什么事情呢?
假设我们有一张图片,其为 5x5 大小,我们要现在要将这张二维图片给转换成一行,那就应该有 25 个单位这么长。
然后我们要使用线性层将 25 个单位长的这个向量给转换成 3 个单位长度的向量,就做这个事情:
那么代码示例如下:
运行结果如下:
可以看到我们的输入数据一开始是三维的,然后我们先将其拉成一条直线的向量的,总共长度为 196608 这么长,然后使用了线性层将其转换为了 10 个单位这么长的数据,这就是线性层的作用。
Dropout Layers(失活层)
Dropout Layers在深度学习中指的是一种防止模型过拟合的技术,也称为“丢弃层”或“失活层”。它的主要作用是在训练过程中随机地“关闭”或“丢弃”神经网络中的一部分神经元,即让这些神经元的输出在训练时临时为0。
Dropout层的主要目的是通过减少神经元之间的共适应性(co-adaptation),来增强模型的泛化能力。在训练过程中,由于部分神经元被随机丢弃,模型无法过度依赖任何特定的神经元或特征,从而迫使它学习更加鲁棒和通用的特征表示。
具体来说,Dropout层会在每次训练迭代中随机选择一部分神经元,并将其输出置为0。这些被丢弃的神经元的权重在训练过程中不会被更新。通过这种方式,Dropout层可以有效地减少模型的复杂度,降低过拟合的风险,并提高模型在未知数据上的性能。
需要注意的是,Dropout层只在训练过程中使用,而在测试或实际应用时,所有神经元都会被激活并使用,以充分利用整个模型的性能。此外,Dropout层通常与其他正则化技术(如L1或L2正则化)结合使用,以进一步提高模型的泛化能力。
在PyTorch等深度学习框架中,Dropout层可以通过相应的API(如torch.nn.Dropout)方便地实现和应用。
这个比较简单,有用的时候查文档即可。
Sparse Layers(稀疏连接层)
在深度学习和神经网络中,“Sparse Layers” 通常指的是具有稀疏连接或稀疏激活的层。这里的“稀疏”意味着网络中的连接或激活是稀少的,即大部分连接是未激活的或权重接近于零。
稀疏连接:
在神经网络中,稀疏连接意味着神经元之间不是全连接的,而是只有一部分神经元之间有连接。这可以减少参数的数量,从而降低模型的复杂度和过拟合的风险。例如,卷积神经网络(CNN)中的卷积层就是稀疏连接的,因为每个神经元只与前一层的一个局部区域(感受野)相连。
稀疏激活:
稀疏激活指的是神经元的输出大部分时候是接近于零的,只有少数神经元在给定输入下被激活。这可以通过使用激活函数(如ReLU)和正则化技术(如L1正则化)来实现。稀疏激活有助于网络学习更加鲁棒和高效的特征表示。
Sparse Layers 的实现:
稀疏连接:在卷积神经网络中,稀疏连接是通过卷积操作自然实现的。在其他类型的神经网络中,可以通过设计特定的连接模式或使用稀疏矩阵来实现稀疏连接。
稀疏激活:在PyTorch中,稀疏激活可以通过选择合适的激活函数(如ReLU)来实现。此外,还可以使用正则化技术(如L1正则化)来鼓励模型学习稀疏的权重,从而间接实现稀疏激活。
需要注意的是,尽管稀疏连接和稀疏激活有助于降低模型的复杂度和提高泛化能力,但它们也可能导致模型在训练过程中更难以优化。因此,在使用稀疏层时需要仔细调整超参数和优化策略,以确保模型能够达到良好的性能。
这个模块对于做自然语言处理的会用到的比较多,这里不再赘述,毕竟我是做图像的。
Distance Functions(距离函数)
在PyTorch中,Distance Functions 模块包含了一系列用于计算向量之间距离的函数和类。这些函数和类在机器学习和深度学习中非常有用,特别是在处理与距离相关的任务时,如聚类、相似度计算等。
其中,torch.nn.functional.pairwise_distance 是一个常用的函数,用于计算两组向量之间的成对距离。这个函数可以度量向量之间的相似性或差异性,广泛应用于各种机器学习算法中,如k-最近邻(k-NN)和聚类等。
另一个重要的类是 torch.nn.CosineSimilarity,它用于计算两个输入之间的余弦相似度。余弦相似度是一种常用的相似度度量方式,特别适用于高维空间中的向量。在自然语言处理、推荐系统等领域中,余弦相似度常用于比较文档或用户偏好的相似性。
这些Distance Functions模块在PyTorch的nn(neural networks)子模块中,它们为开发者提供了方便的工具来度量向量之间的距离和相似度,从而帮助实现各种基于距离的机器学习算法。
Loss Functions(损失函数)
在PyTorch中,Loss Functions模块提供了一系列用于计算损失函数的类和函数。这些损失函数在训练神经网络时至关重要,因为它们定义了模型预测与真实标签之间的差异或误差,从而指导模型在训练过程中的优化方向。
PyTorch的Loss Functions模块包括多种不同类型的损失函数,以适应不同的任务和模型架构。以下是一些常见的损失函数及其解释:
此外,PyTorch还提供了许多其他类型的损失函数,如HingeEmbeddingLoss、MultiLabelMarginLoss、SmoothL1Loss等,以满足各种特定的需求和任务。
这些损失函数都可以直接通过PyTorch的nn模块进行调用,如nn.MSELoss(), nn.CrossEntropyLoss()等。在使用这些损失函数时,需要将模型的预测值和真实标签作为输入,然后计算损失值,最后通过优化器来更新模型的参数。
神经网络——搭建小实战和Sequential的使用
在搭建网络模型实战之前,先来介绍一个之前没有讲到的在 torch.nn 的 Containers 中的 Sequential 的一个使用:
可以看到上图中有一个 Sequential 模块:
这个模块的作用和之前讲的 transform 中的 Compose 方法差不多,作用是一样的,就是将一组操作合在一起,这样子代码写起来会更简洁易懂。
那么接下来我们来搭建一个简单的网络模型,也就是实现对数据集 CIFAR10 进行分类的这样一个网络的实战。
先设计一个基于CIFAR10的模型结构:
注意:上图中的最后一部分是原图漏写了,补上去了两个线性层。
现在我们就来实现这个网络模型,从上图可以知道,在进行第一次卷积之后得到的特征图的尺寸是 32 维 32x32 大小,那么其 padding 和 stride 参数要如何进行设置才能得到这样的输出呢?
这个可以根据 Conv2d 函数的官方文档中的输入输出数据公式进行推算:
因为一般情况下 padding 值肯定不会设置的过大,过大的话那就失真了(也就是欠拟合),那么我们可以从推测 stride 值入手,假设 stride 为 1 时(因为这个是很常用的推测值),正好可以得知 padding 值为 2,这是可以接受的。
但是如果 stride 为 2的话,那么padding值就会有点大了嗷,那么我们就可以直接使用 stride = 1,padding= 2的参数来进行代码的编写:
完整的代码逻辑如下:
import torch
from torch import nn
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear
# 定义网络结构
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
# 因为stride 默认值就是1,因此无需显式设置,这样就设置完了第一层的卷积
self.conv1 = Conv2d(3, 32, 5, padding=2)
# 第一个池化层,池化核大小为 2x2
self.maxpool1 = MaxPool2d(2)
# 第二层卷积,这依然可以根据第一层的示例方法进行推算得到
self.conv2 = Conv2d(32, 32, 5, padding=2)
# 第二层池化
self.maxpool2 = MaxPool2d(2)
# 第三层卷积
self.conv3 = Conv2d(32, 64, 5, padding=2)
# 第三层池化
self.maxpool3 = MaxPool2d(2)
# flatten 对数据进行展平,否则无法进行概率统计,对应结构图上其被拉伸为一个 64x4x4=1024 大小的向量
self.flatten = Flatten()
# 第一层线性层
self.linear1 = Linear(1024, 64)
# 第二层线性层,输出为10,这是由于被划分成了十个类别的原因
self.linear2 = Linear(64, 10)
def forward(self, x):
# 常规写法
x = self.conv1(x)
x = self.maxpool1(x)
x = self.conv2(x)
x = self.maxpool2(x)
x = self.conv3(x)
x = self.maxpool3(x)
x = self.flatten(x)
x = self.linear1(x)
x = self.linear2(x)
return x
# 测试网络
net = Net()
print(net)
input = torch.ones((64, 3, 32, 32))
output = net(input)
print(output.shape)
运行结果如下:
可以看到最后的输出确实是 10 ,而前面的 64 表示的batch_size,就当成有 64 张图片即可,无需关心。
上面的写法可能看起来有点不太雅观,那么我们就可以使用本节开始时使用的 Sequential 方法来重写一下啦:
可以看见代码简洁优雅了很多,运行结果是一样的,这里就不再赘述。
另外我们也可以通过 tensorboard 工具来进一步的查看这个转换的细节,加上如下代码即可:
运行结果后查看 web :
我们可以不断的点开我们想查看的地方进行细节的观察:
这里不再赘述,多玩玩就好了。
损失函数与反向传播
Loss Functions(损失函数)详解
之前有简单提过一嘴,这里再着重讲一下这个 Loss Functions 的使用。
首先要知道这个 Loss Functions 的最主要作用:
1、计算实际输出和目标输出之间的差距
2、为我们更新输出提供一定的依据(反向传播)
然后继续看官方文档,位于 torch.nn 下面的 Loss Functions 模块;
比较常用到的有下面几个,挨个介绍一下。
nn.L1Loss (平均绝对误差函数)
官方文档如下:
看文档感觉写的非常的绕,其实很简单,就是将输入的 x 和 输出的 y 进行求差操作,然后要么将这些差值进行求和要么进行求平均值,具体是哪种行为可以根据参数 reduction 来指定,如下图所示:
如果是求和的话,那 L1loss 的结果就应该为 2.
不难看出,不管是哪种方式,肯定都是尽量减小 loss 的值是最好的,因为 loss 值越小,说明其网络最后输入的数据和期望得到的目标数据越相近。
接下来我们写代码来验证一下。
运行结果如下:
nn.MSELoss (均方误差函数)
这个其实和上面平均绝对误差函数差不多,唯一的区别就是多了个求平方差的操作:
就是求对应值的平方差值,最后再求平均值。
代码验证:
运行结果如下:
nn.CrossEntropyLoss (交叉熵函数)
这个就比较复杂了,因此这里不再详述这个交叉熵是咋算的(想知道的话可以找一些其他的参考资料),只要从文档中了解到其对于训练一个具有 C 种类别的分类问题时是十分有用的就可以了。
直接上代码实战:
运行结果如下;
损失函数如何与神经网络结合
也比较简单,直接看代码即可。
import torchvision.datasets
from torch import nn
from torch.nn import Sequential, Conv2d, MaxPool2d, Flatten, Linear
from torch.utils.data import DataLoader
# 准备数据集
dataset = torchvision.datasets.CIFAR10(root="./dataset2", train=False, transform=torchvision.transforms.ToTensor(), download=True)
# 加载数据集
dataloader = DataLoader(dataset, batch_size=1)
# 定义网络
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.model1 = Sequential(
Conv2d(3, 32, 5, padding=2),
MaxPool2d(2),
Conv2d(32, 32, 5, padding=2),
MaxPool2d(2),
Conv2d(32, 64, 5, padding=2),
MaxPool2d(2),
Flatten(),
Linear(1024, 64),
Linear(64, 10)
)
def forward(self, x):
x = self.model1(x)
return x
# 使用交叉熵损失函数来判断预测结果的误差大小
loss = nn.CrossEntropyLoss()
# 测试网络
net = Net()
for data in dataloader:
imgs, targets = data
outputs = net(imgs)
# 先打印看一下outputs和targets长什么样,再看选择什么样的损失函数
# 输出结果;tensor([[-0.1219, -0.0085, 0.0734, -0.0379, -0.1445, -0.0993, 0.0247, -0.0432,
# 0.1162, 0.0955]], grad_fn=<AddmmBackward0>)
# tensor([1])
# 可以看到其中共有十个输出,每一个输出代表预测是这个类别的一个概率值,而其真实的 target值为 1
# 不难看出这就很适合使用交叉熵损失函数来计算损失
# print(outputs)
# print(targets)
result_loss = loss(outputs, targets)
print(result_loss)
运行结果如下:
现在输出的数据就是我们的神经网络经过计算之后与目标 targets 之间的误差值了,也就正好佐证了我们一开始说的损失函数的第一个作用:计算实际输出和目标输出之间的差距。
那之前也说过,损失函数的第二个作用就是为我们更新输出提供一定的依据,也就是为反向传播更新数据提供依据,那么是如何提供的呢?
在神经网络当中的每一个需要调优的参数(卷积核中的参数)都被设置了一个梯度 grad,当我们采用反向传播的时候每一个节点或者说每一个需要更新的参数都会自动求出来一个对应的梯度,然后在优化的过程中神经网络就会对这个梯度参数进行一个调整进而实现最终使得整个 Loss 的值降低的目的。
因此我们在上面的代码中加上反向传播的内容进行优化,代码也很简单,其实加上一句代码即可:
如上图所示,我们打上断点来调试一下:
此时程序运行到断点处停下,我们可以查看到此时网络中的各个参数状态:
我们继续查看上图中光标选中的卷积层中的各个参数此时的内容:
不难发现此时的 grad 梯度参数值为 NULL,因为我们还没有进行反向传播。
此时我们让代码运行到下一步,即进行反向传播的那一行代码处:
可以看到经过反向传播之后,我们的梯度 grad 值发生了变化,算出来了一个梯度。
但这还不足够,我们等一下会使用合适的优化器进行激化,优化器会利用这些梯度值来对我们神经网络中的比如卷积核中的各个参数进行调整更新。
优化器
前面提到,反向传播可以求出神经网络中每一个需要调节的参数的梯度,有了梯度之后还需要结合上优化器使用。
优化器可以根据反向传播所求出来的梯度值对需要调节的参数进行调整更新,以达到整体误差降低的目的。
PyTorch中所有的优化器都被放置在 torch.optim 中:
上图是官方文档中给出的如何使用优化器的步骤,往下翻可以看到 Algorithms 算法模块:
这个模块中官方文档讲解了各个优化器算法的设计思想和实现,如果有想深入学习的话可以看这部分文档,但本文只是介绍入门,因此这里只介绍怎么用就可以了。
仿照着官方文档的示例,我们也来写代码测试一下(基于上一小节的代码嗷,下图只展示了发生改动的代码片段):
运行结果如下:
可以看见每一轮的总 loss 值都在不断降低,说明优化器在不停的优化我们神经网络中的各个节点参数。
现有网络模型的使用及修改
这一节我们来简单介绍一下如何使用和修改 PyTorch 提供的现有已封装好的各类网络模型。
官网的 Docs 下有关于语音的、文字的、视觉的等等的工具包,因为我是做视觉的,因此我们以视觉包下的图像分类相关的模型为例:
上图就是所有的和图像分类相关的模型了,我们以 VGG 为例:
其使用的数据集是 ImageNet,整整 140 多个G,因此就不下载下来进行完整的模型训练了,简单了解如何使用一下 VGG16 网络模型即可。
直接上代码:
简单的通过这几行代码就可以引入 VGG16 的网络模型了,运行后首先会进行 VGG16 网络模型的下载,下载的内容主要是已经预训练好的各类权重参数以及整个网络架构的代码等等:
等待下载完成之后,可以得到下面的内容:
上面两张图就是整个 VGG16 网络模型的内容了。
接下来我们在 VGG16 的基础上,进行一些定制化的操作,比如我们现在想给它添加一层线性层,让其 in_features 变成1000,out_features 变成 10,这样就变成了我们自己的网络模型啦:
运行结果如下:
可以看见对网络模型进行添加或者修改都是可以的。
网络模型的保存与读取
第一种方式:
运行结果如下:
不难发现在运行结束之后我们的项目中出现了一个名为 vgg16_method1.pth 的文件,这就是我们保存下来的网络模型。
使用这种方式保存的模型,我们需要使用对应的读取方式:
运行结果如下:
可以发现是正常读取的。
第二种保存方式如下:
运行结果如下:
可以发现 vgg16_method2.pth 正常被保存下来了。
模型保存方式二所对应的读取加载方式如下:
运行结果如下:
不难发现其加载出来的都是一个一个字典形式的数据,也是可以被正常读取的。
但是光加载了参数状态还不足够,还需要将这些参数加载到我们对应的模型中:
运行结果如下:
可以发现和我们之前的方式一加载后的效果一模一样,可以被正常读取。
注意:
在PyTorch中,torch.save() 函数用于保存模型的状态字典(state_dict)或者整个模型(如果模型是继承自 torch.nn.Module 并且已经在其上定义了 state_dict)。然而,直接保存一个未经特定处理的模型(如我们自定义的模型)可能会引发错误,特别是如果我们的模型是一个预训练的模型实例,并且它包含了一些不可序列化的属性(如文件句柄或设备特定的属性)。
因此最好是使用第二种方式,这也是官方最推荐的一种方式捏。
至此基本上 PyTorch 框架的内容就讲完了,不过新手在一开始时就掌握一种比较优雅的模型训练和模型验证的套路是很重要的,这可以多参考一些别人的套路写法。