PyTorch03-PyTorch的主要组成模块
三、PyTorch的主要组成模块
3.1 深度学习流程及与机器学习差异
一项机器学习任务的整体流程如下:
- 数据准备与划分:对原始数据进行清洗、格式统一与特征变换,并划分为训练集、验证集和测试集(如采用随机切分或 K 折交叉,可以使用sklearn带的test_train_split函数、kfold来实现)。
- 模型与算法配置:选择合适的网络架构,指定衡量误差的损失函数、更新参数的优化器以及相关超参数(可以使用sklearn这样的机器学习库中模型自带的损失函数和优化器)。
- 模型训练(拟合):将训练集输入模型,通过反向传播算法迭代更新权重以拟合数据。
- 性能评估与调优:在验证集和测试集上评估模型的泛化能力,并根据表现进行超参数微调或最终效果验证。
深度学习与机器学习的差异如下:
| 对比维度 | 传统机器学习 | 深度学习 |
|---|---|---|
| 本质定义 | 数据驱动让程序从经验中提升性能,包含多种算法范式 | 机器学习的一个分支,特指用多层神经网络自动提取特征 |
| 特征提取方式 | 人工设计特征。依赖专家经验从原始数据中提取有用特征(如TF-IDF、HOG、SIFT等),特征的好坏直接影响模型上限 | 自动学习特征。网络底层自动学习边缘、纹理等低级特征,高层组合成语义特征,实现端到端学习,无需人工设计 |
| 数据需求量 | 较小。几百到几万样本即可取得不错效果,在小数据集上表现稳定,不易过拟合 | 较大。因参数量巨大,通常需要十万级以上样本才能充分训练,否则容易过拟合;可通过迁移学习/数据增强缓解 |
| 硬件依赖 | CPU即可胜任。计算量相对较小,普通笔记本电脑即可运行大部分算法 | 重度依赖GPU。矩阵运算密集,需要GPU加速才能在大规模数据和深层网络上合理时间内完成训练 |
| 数据加载方式 | 通常可全量加载。数据量小,可一次性读入内存,训练过程简单直接 | 需mini-batch分批加载。数据量和模型参数量远超显存容量,必须分批次送入GPU训练,配合DataLoader、预读取等机制;批大小(batch size)本身也是影响模型收敛的重要超参数 |
| 模型构建方式 | 调用现成算法库。通常直接调用sklearn等库中的完整算法类,传入参数即可,无需逐层搭建网络结构 | 逐层搭建或模块组装。需手动定义网络各层(卷积层、池化层、批正则化层、LSTM层等),或预定义功能模块再组装,灵活性强但代码实现要求更高 |
| 损失函数与优化器 | 与深度学习类似,均需设定损失函数和优化器;但传统模型多为凸优化,调参相对简单 | 损失函数和优化器需保证反向传播能在用户自定义的复杂网络结构上正确实现;非凸优化对学习率、动量等超参数更敏感 |
| GPU配置与操作 | 通常不需要。大部分传统ML算法在CPU上运行即可满足需求 | 必须显式管理。需将模型和数据手动“放到”GPU(.cuda()或.to(device)),损失函数和优化器也需在GPU上工作;多卡训练还需考虑模型并行/数据并行、梯度同步、结果汇总等问题;验证/评测时常需将数据“放回”CPU |
| 训练与验证流程 | 全量数据一次性训练。通常一轮训练即可收敛(如线性回归闭式解),或少量迭代(如随机森林建树) | 按批循环训练。每个epoch遍历所有batch,每批数据前向传播→计算损失→反向传播→优化器更新参数;需协调DataLoader、模型、损失函数、优化器、GPU之间的配合;验证时同样按批计算指标 |
| 可解释性 | 较强。树模型可直接输出特征重要性排序,线性模型系数有明确含义,易于理解和调试 | 较弱。黑盒特性显著,难以直观解释内部决策逻辑,需借助Grad-CAM、SHAP、注意力可视化等事后工具辅助理解 |
| 适用数据类型 | 结构化表格数据为主。在金融风控、电商推荐等表格数据场景中表现优异 | 非结构化数据为主。图像、语音、文本、视频等原始高维数据是深度学习的主场 |
| 表格数据表现 | 常胜。XGBoost、LightGBM等梯度提升树在表格数据上至今是工业界首选 | 未必占优。纯深度模型在表格数据上通常打不过调优后的GBDT,需结合TabNet等专门设计才有竞争力 |
| 图像/语音/NLP | 基本退场。传统方法在这些领域已被深度学习全面超越 | 绝对主场。CNN统治图像,RNN/Transformer统治序列数据,大语言模型引领NLP前沿 |
| 典型算法举例 | 线性回归、逻辑回归、支持向量机(SVM)、决策树、随机森林、XGBoost、K近邻(KNN)、K均值聚类 | 卷积神经网络(CNN)、循环神经网络(RNN/LSTM)、Transformer、生成对抗网络(GAN)、扩散模型(Diffusion)、BERT/GPT |
其中需要特别提到的是,深度学习所需的样本量一般更大,所以相关数据(训练集/验证集/测试集)一般无法一次性被加载到内存或显存中,此时一般分批输入数据。因此深度学习在数据加载上需要有专门的设计。
3.2 基本配置
一些常用的包
在使用PyTorch中,需要导入一些常用的包来使用其提供的特定功能。常见的包有如下:os、sys、torch、torch.nn、torch.utils.data.Dataset、torch.utils.data.DataLoader、torch.optimizer、numpy、matplotlib、seaborn,如果涉及到表格读取与处理一般会用到pandas,下游分析和指标计算一般会用到sklearn。
1 | |
一些常用的超参数:
可以通过变量的形式设置超参数,然后在代码中使用:
1 | |
也可以使用yaml、json,dict等文件来存储超参数,这样可以方便后续的调试和修改,常见的深度学习库(mmdetection,Paddledetection,detectron2)和一些AI
Lab里面比较常用这种方式。
指定GPU
如果未指明要使用GPU,程序中默认使用的是CPU。如果指明了使用GPU但未指定GPU编号,那么默认就一直使用第0个GPU,容易造成此GPU资源如显存耗尽,所以最好明确要使用的GPU的编号。一般有如下两个方法
1 | |
3.3 数据读入
此小节学习如下内容:PyTorch常见的数据读取方式、构建自己的数据读取流程。
在PyTorch中,通过Dataset与DataLoader的配合使用,完成对的数据集的加载与批量读入。其中Dataset告诉 PyTorch 数据在哪里、怎么读取某一条具体的数据(比如读取一张图片、一段文本或一行表格数据)、读取出来后要做什么预处理(数据变换/Augmentation),DataLoader本身就是一个可迭代对象(Iterable)、以迭代的方式不断按批次(就是一次读入固定的N个单数据,最后一个批次可能小于N个单数据)读入数据。
总结起来这二者的作用如下:
Dataset:解决“单条数据的提取与转换”问题(个体逻辑)。
DataLoader:解决“批量化、打乱、多线程加速与内存流式供给”问题(工程效率)。
不管处理的是图像、语音还是文本,DataLoader 的批处理逻辑是通用的,只需要编写对应数据类型的 Dataset 即可。PyTorch官方也提供了一些现成的具体Dataset类实现。
可以自定义类来实现灵活的数据读取,自定义类需要继承PyTorch自身的torch.utils.data.Dataset类。一般只需要重写几个核心方法:
__init__:初始化,传入外部参数、配置数据集运行所需的基础资源与参数。__getitem__:用于逐个读取样本集合中的元素,可以进行一定的变换,并将返回训练/验证所需的数据。比如输入一个索引idx,返回第idx条处理好的数据(比如:读取第 50 张图,裁剪放缩后,转换为 Tensor 并与标签一起返回)。__len__:用于返回数据集的样本数。即告诉 PyTorch 一共有多少条数据(比如数据集一共 10000 张图)。
PyTorch 官方提供的一些现成 Dataset
具体实现类,可以直接使用。比如下面的datasets.ImageFolder及大致使用:
1 | |
此示例代码使用了PyTorch中自带的ImageFolder类读取按一定结构存储的图片数据。ImageFolder
会自动扫描 train_path
下的所有子文件夹名称作为类别(Class),并自动为每个类别分配一个整数标签(0, 1, 2...)。
1 | |
其中data_transform可以对图像进行一定的变换,如翻转、裁剪、缩放等操作,可自定义。
如下是一个自定义Dataset类的示例:
1 | |
构建好Dataset后,就可以使用DataLoader来按批次读入数据了,实现代码如下:
1 | |
其中相关参数的作用:
train_data/val_data:输入的数据集实例(即继承并实现了Dataset的对象),告诉 DataLoader 从哪里提取数据。batch_size:样本是按批次(Batch)读入的,指定每个批次包含的样本数量。num_workers:指定用于数据加载的多进程 CPU 线程/进程数。数值越大,后台并行读取数据的速度越快,可有效防止 GPU 等待数据(0 表示仅用主进程)。shuffle:指定是否在每个 Epoch 开始时打乱数据顺序。训练集设置为True可提高模型泛化能力;验证集通常设置为False以保持评估指标的可比性。drop_last:指定当总样本数不能被batch_size整除时,是否丢弃最后一个样本量不足的 Batch(设置为True可避免最后一个小批次影响 Batch Normalization 或计算图的形状)。
可以通过如下方式查看加载的数据(又用到了matplotlib):
1 | |
1 | |
3.4 模型构建
本节学习内容:PyTorch中神经网络的构造方法、PyTorch中特殊层的构建、LeNet的PyTorch实现。
人工智能的第三次浪潮主要受益于深度学习的突破,其成功得益于海量数据、GPU 算力飞跃以及深度神经网络算法(如结合了 BP 算法和 GPU 加速的卷积神经网络 AlexNet)的融合应用。
PyTorch中,自定义模型一般是基于继承nn.Module类来完成的,我们只需要重写其中一些方法(如__init__、forward)即可。这给灵活构造自定义模型提供方便捷。
3.4.1 神经网络的构造
PyTorch
中,torch.nn.Module是一个基本构造类,所有神经网络层(如
nn.Linear、nn.Conv2d)以及自定义的大模型,本质上都是它的子类。
以下示例通过继承 Module 类构造多层感知机。这里定义的 MLP 类重载了
Module 类的 __init__ 函数和
forward
函数。它们分别用于创建模型参数和定义前向计算(正向传播)。下面的 MLP
类定义了一个具有两个隐藏层的多层感知机。
1 | |
上述执行过程大致如下:
- 这个MLP的__init__方法中定义了两个线性函数,一个激活函数。然后在forward方法中使用了它们
self.hidden(x):输入特征数是 784(例如将 \(28 \times 28\) 的图像展平后的向量),通过矩阵乘法 \(xW^T + b\) 转换后,输出 256 个特征数。self.act(...):ReLU 是一个激活函数,计算公式为 \(\max(0, x)\)。它不会改变张量的形状或特征数量,只是把这 256 个特征值中小于 0 的部分变成 0,大于 0 的保持不变,因此最终输出是 256 个激活后的特征值。self.output(o):将这 256 个特征值传递到输出层,最终映射转换为 10 个特征值(比如对应 10 个类别的预测 logits)。
以上的 MLP 类中⽆须定义反向传播函数。系统将通过⾃动求梯度⽽自动⽣成反向传播所需的 backward 函数。
nn.Module类中包含__call__属性且是一个可执行方法,按照Python中的
__call__
协议,nn.Module类实例就可以当成一个方法来调用,且调用时实际上执行的就是__call__属性对应的方法。以下代码首先将MLP实例化为net,通过调用net(x)最终将导致调用MLP类中重写的forward方法(而这也反过来要求我们在继承nn.Module类自定义子类时,一定要重写实现forward方法)。
1 | |
1 | |
注意,此示例中nn.Module的命名特征,翻译过来叫“模块”,而不是叫Layer层或Model模型,因为该类被定义成一个可供⾃由组建的部件。Module
里可以包含 Module,任意多个 Module 可以组合成更大、更复杂的 Module。
继承nn.Module的子类即可以是某某层(如池化层、线性化层等),也可以是一个模型(如上述MLP),甚至一个模型的一部分(在中大型复杂模型如
ResNet、Transformer、YOLO 等中,“模型的一个部分”被经常单独封装为一个
nn.Module )。
3.4.2 神经网络中常见的层
深度学习中提供了或可实现神经网络中各式各样的层,如全连接层、卷积层、池化层与循环层等等。虽然PyTorch中已经预置了大量的层,但我们在实际开发中仍可能需要自定义层,此时就是一个继承
nn.Module类实现子类的过程。
不含模型参数的层
先介绍如何定义一个不含模型参数的自定义层。以下示例中定义了一个层:将输入减掉均值后输出。
1 | |
__init__
只负责“搭建舞台”(初始化网络结构与权重),forward
才是真正的“演出过程”(数据流转与计算)。
含模型参数的层
还可以自定义包含模型参数的层。其中的模型参数可以通过多轮训练反向传播更新学习。
torch.nn.Parameter类是 torch.Tensor
的子类。当我们在自定义层或模型时,如果某个张量是实际上是torch.nn.Parameter实例,那么它将被自动添加到模型的参数列表中,以便于后续跟踪、求导。所以在⾃定义含模型参数的层时,应该将这些模型参数定义成
Parameter类型。除了直接定义成 Parameter
类外,还可以使⽤ ParameterList 和
ParameterDict 分别定义参数的列表和字典。
1 | |
1 | |
二维卷积层
虽然在深度学习中我们习惯叫“卷积”运算,但严格从数学定义上讲,神经网络里的卷积层实际上执行的是“互相关运算(Cross-correlation)”
- 互相关运算:用一个较小的矩阵(卷积核 \(K\))在较大的输入矩阵(输入 \(X\))上从左到右、从上到下按步长滑动。每移动到一个位置,就将卷积核与输入对应的子区域按元素相乘再求和,得到输出矩阵 \(Y\) 中的一个点。
- 标量偏差(Bias \(b\)):一个可学习的标量,加到互相关运算结果的每一个元素上。
- 参数更新:卷积核权重 \(W\) 和偏置 \(b\) 就是模型要学习的“参数”。它们初始时是随机给出的,在训练过程中通过反向传播(Backpropagation)和梯度下降逐步优化。
卷积层的模型参数包括了卷积核和标量偏差。在训练模型的时候,通常先对卷积核随机初始化,然后不断迭代更新卷积核和偏差。
1 | |
卷积窗口形状为\(p×q\)的卷积层称为\(p×q\)卷积层。同样,\(p×q\)卷积或\(p×q\)卷积核说明卷积核的高和宽分别为p和q。一般情况下,p与q相等。
为了防止图像尺寸过快收缩(如果不加 Padding,每经过一层 \(3 \times 3\) 卷积,图像高宽就会减小 \(2\)。经过十几层卷积后,图像就会缩小为 \(1 \times 1\),无法构建很深的网络结构。)、保护边缘信息(位于输入图像边缘和角落的像素点,如果不加 Padding,被卷积核扫描到的次数远远少于中心区域的像素;加了 Padding 之后,边缘像素也能被充分提取特征),我们在实际应用中会对输入数据上下左右加上填充(padding)。
填充的元素值一般都是 0,PyTorch 中 nn.Conv2d 的
padding 参数默认采用的就是
零填充。加上填充后,我们可以使输入数据与输出数据有相同高度、宽度。
1 | |
还可以为卷积核窗口设置不同的高与宽。如果卷积核的高和宽不同时,可以通过在高和宽上设置不同的填充数,最后使得输出和输入具有相同的高和宽。
1 | |
填充可以增加输出的高和宽。这常用来使输出与输入具有相同的高和宽。
做卷积过程中,卷积窗口在输入数据上按照从上到下、从左到右的规则滑动,每次滑动的行数或列数称为步幅(stride),行步幅与列步幅可以不同。
1 | |
步幅可以减小输出的高和宽,例如输出的高和宽变为指定的大小。