一、 环境安装与配置
💡 核心逻辑
推荐使用 Conda 进行环境管理。通过创建“虚拟环境”,可以在同一台机器上隔离不同版本的 Python 和 PyTorch,避免不同项目间的依赖冲突。
1.1 第一步:安装 Conda
版本选择:
Miniconda:轻量级,仅包含 Conda 和基础包,适合开发者。
Anaconda:全家桶版,包含大量科学计算包,占用空间较大。
安装步骤:前往 Miniconda 官网或 Anaconda 官网。
Mac (Apple Silicon): 必须选择 macOS Apple M1/M2/M3 (M-series) 64-bit 版。
Windows: 选择 Windows 64-bit 版。
1.2 第二步:(仅限 Windows NVIDIA 用户) 安装 CUDA
Windows 若要使用 GPU 加速,必须满足硬件是 NVIDIA 显卡。
检查显卡:在任务管理器中确认是否有 NVIDIA GPU。
安装驱动:前往 NVIDIA 官网更新最新的显卡驱动。
CUDA 版本:通常 PyTorch 会自带简版 CUDA,但建议手动安装 CUDA Toolkit(如 11.8 或 12.1)以获得最佳兼容性。
1.3 第三步:安装 Python 及 PyTorch
打开终端(Mac 使用 Terminal,Windows 使用 Anaconda Prompt 或 PowerShell)。
1. 创建并激活虚拟环境
| |
torch_env:为所创建的虚拟环境名称。python=3.10:为该虚拟环境中安装的 python 版本。-y:表示安装过程中的一切询问都默认 yes。
2. 安装 PyTorch 核心库
根据硬件平台选择安装命令。请确保已提前激活虚拟环境。
| 运行平台 | 安装命令 | 硬件说明 |
|---|---|---|
| Mac (M系列) | pip install torch torchvision torchaudio | 原生支持 MPS 硬件加速 |
| Windows (NVIDIA) | pip install torch torchvision torchaudio --index-url [https://download.pytorch.org/whl/cu121](https://download.pytorch.org/whl/cu121) | 需要 CUDA 驱动支持 (示例为 12.1 版) |
| 通用 (仅 CPU) | pip install torch torchvision torchaudio | 无显卡加速,适合基础逻辑测试 |
💡 核心参数详解:
--index-url
为什么要手动指定源地址? pip 默认从 PyPI 下载,但 PyPI 仓库通常只存放通用版。PyTorch 官方源提供的包是针对不同 CUDA 版本专门编译优化的。
作用:显式指定下载服务器的地址,引导 pip 去特定的仓库“提货”。
平台差异:
Windows/Linux (GPU):必须添加。不加此参数会导致 pip 默认安装 CPU 版本,无法调用 NVIDIA 显卡算力。
Mac (M系列):通常不加。苹果的 MPS 加速代码已直接集成在 PyTorch 的标准版本中。
1.4 第四步:跨平台验证脚本
| |
二、 PyTorch 张量基础 (Tensor)
2.1 什么是张量?
张量是神经网络中最基础的数据结构,可以看作是标量、向量、矩阵向更高维度的推广。
0 维张量:标量 (Scalar),即单个数字。
1 维张量:向量 (Vector),类似于列表。
2 维张量:矩阵 (Matrix),二维数组。
3 维张量及以上:多维数组。
2.2 创建 Tensor
1. 常用初始化方法
| |
2. 基于现有 Tensor 创建
| 方法 | 说明 | 继承属性 |
|---|---|---|
x.new_ones(shape) | 需要手动指定新形状 | 类型 (dtype), 设备 (device), 自动微分 (requires_grad) |
torch.randn_like(x) | 无需指定形状 | 形状, 类型, 设备 |
3. 查看属性
数据类型:
x.dtype(同质数据类型)。形状/大小:
x.shape或x.size()(互为别名)。所在设备:
x.device(CPU/CUDA/MPS)。
2.3 张量的运算操作
1. 三种加法实现方式
运算符重载:
print(x + y)函数调用:
torch.add(x, y)原地操作 (In-place):
y.add_(x)(注意:PyTorch 中以
_结尾的方法会直接修改调用者。)
2. 索引与内存共享 (重要)
PyTorch 索引遵循 视图 (View) 机制。
| |
核心原理:索引结果与原数据共享内存。
优点:节省显存,避免拷贝。
风险:修改子集会意外改变原张量。需独立副本请使用
y = x[:, 0].clone()。
三、 Autograd 自动求导系统
核心定义:Autograd 是 PyTorch 的自动求导引擎,通过构建 动态计算图 (Dynamic Computational Graph) 记录操作历史,计算反向传播梯度。
核心组件:
grad_fn(算子记录) 与grad(梯度存储)。
3.1 计算图的基石
1.1 开启梯度追踪
requires_grad默认为 False。设为 True 后开启追踪。传递性:参与运算的张量只要有一个为 True,输出也为 True。
1.2 理解 grad_fn (计算记录器)
叶子节点 (Leaf):通过
torch.tensor()等原始创建的张量,grad_fn为 None。非叶子节点:运算生成的张量,
grad_fn指向具体的 Function 对象(如AddBackward)。
| |
3.2 反向传播:backward()
2.1 梯度计算逻辑
调用 loss.backward(),Autograd 逆流计算导数并存入叶子节点的 .grad。
2.2 约束与内存分配
标量触发:通常对单一数值(如 Loss)调用
backward()。非标量触发:非标量需传入形状相同的权重参数。
内存分配:默认只保留叶子节点的
.grad。中间变量欲保留需调用z.retain_grad()。
| |
3.3 梯度脱离:阻止追踪
with torch.no_grad():全局屏蔽梯度计算,常用于模型预测/验证。.detach()方法:分离张量,返回requires_grad=False的新视图,共享内存。
| |
3.4 💡 进阶补充:陷阱与技巧
梯度累加:
.grad不会自动清零,反向传播时会累加。训练迭代前需手动调用optimizer.zero_grad()。In-place 操作:可能破坏反向传播所需数据。建议使用
z = x + y替代x.add_(y)以保安全。
| |
四、 使用 CUDA 加速与并行计算
💡 核心逻辑
PyTorch 提供了简单的方法将模型和数据从 CPU 移动到 GPU。当单块显卡无法满足需求时,可以使用并行计算技术:DP(简单但不均衡)或 DDP(官方推荐,高性能)。
4.1 CUDA 核心概念介绍
CUDA(Compute Unified Device Architecture,统一计算设备架构)是 NVIDIA 推出的并行计算平台和编程模型。它允许开发者利用 NVIDIA GPU(图形处理器)强大的并行计算能力来处理复杂的计算任务,而不仅仅局限于图形渲染。
1. 为什么需要 CUDA?(CPU vs GPU)
在深度学习和科学计算中,GPU 比 CPU 更具优势,其核心差异在于设计理念:
CPU(中央处理器):设计目标是处理复杂的逻辑控制和串行任务。它拥有强大的算术逻辑单元(ALU),但数量较少,擅长“一个老师处理一个复杂的数学难题”。
GPU(图形处理器):设计目标是处理大规模并行任务。它拥有数以千计的简单核心,擅长“一千个小学生同时做简单的加法题”。
CUDA 正是连接这种强大硬件能力的桥梁。
2. CUDA 的核心架构:软件与硬件的映射
CUDA 的编程模型将任务拆解为多层级结构,以便于在硬件上高效调度:
| 逻辑层级 (Software) | 硬件实现 (Hardware) | 描述 |
|---|---|---|
| Thread (线程) | CUDA Core | 计算的最小单位。 |
| Warp (线程束) | - | CUDA 调度的基本单位,通常包含 32 个线程,执行相同的指令(SIMT 架构)。 |
| Block (线程块) | Streaming Multiprocessor (SM) | 一组线程的集合,共享同一块“共享内存”(Shared Memory)。 |
| Grid (网格) | Entire GPU | 一个核函数(Kernel)启动时产生的所有线程集合。 |
3. CUDA 的工作流程:Heterogeneous Computing
CUDA 采用“异构计算”模式,即 Host (CPU) 负责逻辑控制,Device (GPU) 负责大规模数值计算。典型的执行步骤如下:
分配内存:在 GPU 显存上分配空间。
数据拷贝 (H2D):将数据从 CPU 内存拷贝到 GPU 显存。
启动核函数 (Kernel):CPU 指挥 GPU 启动成千上万个线程进行并行运算。
数据回传 (D2H):将计算结果从 GPU 显存拷贝回 CPU 内存。
释放资源:清理显存。
4. CUDA 在 PyTorch 中的角色
在 PyTorch 中,CUDA 是实现张量加速的底层后端。当你执行 tensor.to("cuda") 时,实际上发生了以下动作:
PyTorch 调用 CUDA API 在显存中开辟空间。
数据通过 PCIe 总线传输至显卡。
随后的算子(如矩阵乘法)会调用 NVIDIA 提供的 cuBLAS 或 cuDNN 等高度优化的加速库。
4.2 常用的并行方式
在深度学习中,常用的并行方法主要分为 数据并行、模型并行 和 混合并行。理解它们的关键在于:任务(数据或参数)是如何被拆分并分发到不同设备上的。
1. 数据并行 (Data Parallelism, DP)
这是最常用、最基础的并行方式。它的核心思想是 “模型不动,数据拆分”。
拆分方式:
模型副本:每个 GPU 上都完整地拷贝一份相同的模型参数。
数据切分:将一个大的 Batch(批次)数据平均拆分成 N 份(N 为 GPU 数量)。例如,Batch Size 为 64,有 4 张显卡,则每张显卡分到 16 个样本。
工作流程:
分发:主设备将不同的数据子集发给各 GPU。
并行计算:每个 GPU 独立进行前向传播和反向传播,计算出各自的梯度。
梯度同步:各 GPU 将计算出的梯度进行汇总(All-Reduce),取平均值。
参数更新:所有 GPU 使用相同的平均梯度同步更新自己的模型参数,确保下一轮迭代时模型依然一致。
2. 模型并行 (Model Parallelism, MP)
当模型极其巨大(如千亿级参数量),单张显卡的显存连模型本身都装不下时,就需要模型并行。它的核心思想是 “数据不动,模型拆分”。
模型并行又细分为 张量并行 和 流水线并行:
2.1 张量并行 (Tensor Parallelism, TP)
拆分方式:将模型中的某个层(如大型矩阵乘法层)拆开。
并行逻辑:将一个巨大的权重矩阵 W 拆分为 W_1 和 W_2,分别放在 GPU 0 和 GPU 1 上。计算时,数据会同时进入两张显卡进行局部计算,最后通过通信合并结果。
特点:通信非常频繁,通常要求显卡之间有极高的带宽(如 NVLink)。
2.2 流水线并行 (Pipeline Parallelism, PP)
拆分方式:按模型的层(Layer)进行纵向切割。
并行逻辑:将模型的前一半层放在 GPU 0,后一半层放在 GPU 1。
工作流程:数据像流水线一样,先在 GPU 0 处理,结果传给 GPU 1 继续处理。
优化:为了避免 GPU 空闲(泡泡/Bubble),通常会将数据进一步切分为 Micro-batches(微批次),让不同 GPU 同时处理不同微批次的任务。
3. 混合并行 (Mixed Parallelism)
在训练超大规模模型(如 GPT-4, Llama 3)时,通常会将上述方法结合使用,构建成一个 3D 并行 体系:
数据并行:扩大训练规模,增加吞吐量。
张量并行:解决单个超大层(如 Attention 层)的显存瓶颈。
流水线并行:跨节点连接不同的模型切片。
4. 总结与对比
| 方法 | 拆分对象 | 解决的问题 | 通信开销 |
|---|---|---|---|
| 数据并行 (DP/DDP) | 数据 (Samples) | 提高训练速度,增加 Batch Size | 中等(同步梯度) |
| 张量并行 (TP) | 层内参数 (Tensors) | 单层参数量过大,显存溢出 | 极高(同步神经元输出) |
| 流水线并行 (PP) | 网络层数 (Layers) | 模型层数过多,单卡装不下整个模型 | 较低(同步层间激活值) |
在实际操作中,PyTorch 提供的 DDP (Distributed Data Parallel) 是最推荐的通用数据并行工具;而对于大模型训练,则通常使用 DeepSpeed 或 Megatron-LM 这种高度集成的混合并行框架。
4.3 单卡训练 (Single GPU)
要让 GPU 运行,必须确保模型和数据都在同一块显存中。
| |
4.4 DP vs DDP
1. DataParallel (DP)
| |
运转方式:单机多卡
DP 的核心是“单进程多线程”模式。
内存空间的限制:在单机上,所有的线程都运行在同一个进程的内存空间内,主线程可以非常方便地通过 Python 内部指令去指挥不同的显卡线程。
通信协议的缺失:DP 并没有设计一套能够跨越物理机器、通过网络(如以太网或 InfiniBand)进行数据交换的协议。它只能利用单机内部的 PCIe 总线进行极速通信。
在 DataParallel (DP) 模式下,存在一个“主卡”(通常是 device_id 序列中的第一块卡,默认是 cuda:0),它扮演着“调度中枢”的角色。
深度理解:单进程多线程
单进程:当你运行 Python 脚本时,操作系统只启动了一个 Python 进程。这意味着所有的显卡控制逻辑都挤在这个进程里。
多线程:为了同时操作多张显卡,该进程会启动多个线程,每个线程负责控制一块显卡。
瓶颈:由于 Python 存在 GIL(全局解释器锁),在同一时间只能有一个线程执行 Python 字节码。这导致 DP 在控制多块显卡时,线程切换的开销很大,无法发挥显卡的最高效率。
主卡(Master Node)的具体职责
在 DP 模式下,主卡不仅要参与计算,还要承担繁重的“管理工作”:
分发 (Scatter):主卡负责将从磁盘读取的一个 Mini-Batch 数据,切分成更小的块(Sub-batches)。主卡通过 PCIe 总线将这些数据块分发给其他的从卡(Slave Nodes)。
模型同步 (Replicate):主卡将自己的模型参数(Weights)完整地拷贝到每一块从卡上,确保大家算的是同一个模型。
计算 (Parallel Apply):所有卡(包括主卡)并行进行前向传播(Forward Pass)。
汇总 (Gather & Reduce):关键点:所有从卡计算出的输出(Outputs)必须传回主卡。主卡负责收集所有的输出,计算总的 Loss,并分发给各卡进行反向传播计算梯度。最后,主卡再次收集所有卡算出的梯度,进行平均(Reduce),并在主卡上更新参数。
为什么会“负载不均衡”?
由于上述的分发和汇总全部由主卡完成,你会观察到以下现象:
显存占用不均:主卡因为要暂存所有卡的输出和梯度,其显存占用(Memory Usage)通常比其他卡高出很多。
计算浪费:当显卡数量增多时,主卡忙于通信和汇总,会导致其他显卡进入短暂的“空转”等待状态。
笔记补充:Linux 与 Mac 的差异
| 维度 | Linux/Windows (DP) | macOS (M 系列) |
|---|---|---|
| 进程模型 | 单进程多线程 | 单进程单线程 |
| 主从结构 | 存在明确的主卡(Master) | 不存在,因为通常只有一块集成 GPU |
| 内存逻辑 | 显存与内存独立,存在 H2D/D2H 拷贝 | 统一内存架构 (UMA),无需跨设备拷贝数据 |
总结
DataParallel 就像是一个中央集权的组织:主卡是决策者,负责分发任务、收集结果并总结。而 DistributedDataParallel (DDP) 则像是分布式联邦:每个 GPU 都是独立的进程,通过彼此交换信息(Ring-AllReduce)达成共识,因此效率更高、负载更稳。这种“单进程”与“多进程”的区别,正是 DP 逐渐被 DDP 取代的核心原因。
2. DistributedDataParallel (DDP)
DistributedDataParallel (DDP) 是 PyTorch 官方强烈推荐的并行训练方案。与传统的 DataParallel (DP) 相比,它在底层设计上有着本质的飞跃,是目前工业界处理大规模模型训练的标准工具。
| |
运转方式:多机多卡
DDP 的核心设计:多进程模式
DDP 的精髓在于 “多进程”。
进程分配:DDP 会为每一块显卡启动一个独立的 Python 进程。例如,如果你有 8 张显卡,系统就会运行 8 个独立的程序副本。
消除 GIL 锁瓶颈:由于每个进程都有自己独立的 Python 解释器,因此彻底避开了 Python 全局解释器锁(GIL)的限制,让每块显卡都能满载运行。
去中心化:不同于 DP 需要一个“主卡”来汇总所有数据,DDP 中的每个进程地位平等,它们通过相互通信来同步梯度。
核心技术:Ring-AllReduce 算法
DDP 高效的关键在于梯度同步的方式。它不采用“汇总到中心再分发”的模式,而是采用 Ring-AllReduce 算法:
环形通信:所有显卡连接成一个逻辑上的环。
切片交换:每个显卡将自己的梯度切成小块,只传递给环中的下一个邻居。
带宽利用率高:无论显卡数量如何增加,每块显卡承担的通信量基本恒定。这使得 DDP 在显卡数量极多时(如百卡、千卡集群)依然能保持极高的效率。
DDP 的工作流程
初始化:启动 N 个进程,通过网络建立连接(使用 nccl 或 gloo 后端)。
数据分发:使用
DistributedSampler确保每个进程读取的数据子集是不重叠的。前向传播:每个进程在自己的显卡上运行模型,计算输出。
梯度计算与同步:在反向传播期间,各进程通过 AllReduce 异步交换梯度并取平均值。
更新参数:所有进程使用完全相同的平均梯度更新本地模型参数,确保下一轮训练开始前,所有显卡上的模型依然是镜像一致的。
跨平台适配建议 (Linux vs Mac)
| 特性 | Linux/Windows (NVIDIA) | macOS (Apple Silicon) |
|---|---|---|
| 通信后端 | NCCL (NVIDIA 专用,性能最强) | Gloo (Apple M系列目前仅支持此后端) |
| 启动方式 | 推荐使用 torchrun 工具 | 推荐使用 torchrun 或多进程脚本 |
| 优势 | 极高的吞吐量,支持多机多卡 | 主要用于分布式代码本地调试 |
进阶知识点:DistributedSampler
在 DDP 模式下,必须配合使用
DistributedSampler。- 原理:如果直接使用普通 DataLoader,每个进程都会从第一条数据开始读,导致所有 GPU 都在跑同样的数据,白白浪费算力。
DistributedSampler会根据当前的 rank(进程编号)和 world_size(总进程数)对数据集进行切分,确保“各跑各的”。
- 原理:如果直接使用普通 DataLoader,每个进程都会从第一条数据开始读,导致所有 GPU 都在跑同样的数据,白白浪费算力。
保存模型的注意事项
在使用 DDP 后,模型会被包装在 DistributedDataParallel 对象中。
保存:必须使用
torch.save(model.module.state_dict(), PATH),加上.module才能剥离分布式外壳,保存纯净的模型参数。加载:加载时也要注意,如果是在单卡环境加载 DDP 保存的模型,可能需要处理键名中的
module.前缀。
2.3.1 DDP 核心概念:进程组(Process Group)
在分布式计算中,DDP 通过“进程组”来管理多个计算单元。
GROUP: 进程组,默认为全局组(World)。可以通过
new_group创建子集进行精细通信。WORLD_SIZE: 全局总进程数。
单机多卡:GPU 总数。
多机多卡:机器数 × 每台机器的 GPU 数。
RANK: 全局进程序号(0 到 WORLD_SIZE-1)。Rank 0 通常作为 Master 节点。
LOCAL_RANK: 单个节点(机器)内的 GPU 编号。由启动工具自动分配。
2.3.2 DDP 代码编写流程
在使用 DDP 时,代码的逻辑结构需要进行“分布式改造”:
1. 自动获取 Local Rank
不再手动指定 GPU ID,而是通过环境变量或命令行参数接收启动工具传入的编号。
| |
2. 初始化进程组(后端选择)
选择合适的通信后端(Backend)是性能的关键:
| 后端 | 硬件环境 | 建议场景 |
|---|---|---|
| NCCL | NVIDIA GPU | 最佳选择。支持 InfiniBand 和 Ethernet,性能最高。 |
| GLOO | CPU / Mac | CPU 分布式首选。在 Mac 上调试 DDP 时使用。 |
| MPI | 高性能计算集群 | 仅在有特殊 MPI 需求的环境下使用。 |
| |
3. 数据集划分:DistributedSampler
为了确保 N 个进程不跑重复的数据,必须对数据进行“切片”。
| |
4. 包装模型
| |
2.3.3 如何启动 DDP 训练
DDP 不能直接用 python main.py 启动,需要使用专门的启动器来同步开启多个进程。
A. Linux/Windows (单机多卡)
使用 torchrun(PyTorch 官方目前推荐,取代了旧版的 launch):
| |
B. macOS (本地逻辑调试)
由于 Mac 只有一块集成 GPU,多进程并行的意义在于调试分布式代码:
| |
💡 经验之谈 (Obsidian Tips)
网络接口设置
如果服务器有多块网卡(如同时有以太网和 InfiniBand),NCCL 可能会找错接口导致挂起。
可以在代码中手动指定:
1 2# 找到 ifconfig 中对应的网卡名,如 eth0 os.environ['NCCL_SOCKET_IFNAME'] = 'eth0'DDP vs DP 的性能
即使在单机多卡环境下,也优先选择 DDP。
DDP 没有单进程的 GIL 锁限制。
DDP 的梯度同步(Ring-AllReduce)比 DP 的主卡汇聚快得多。
DDP 支持跨机器扩展,而 DP 只能死守单机。
4.5 💡 并行计算对比表
| 方案 | 易用性 | 性能 | 场景建议 |
|---|---|---|---|
| DP | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | 快速实验、双卡轻量训练 |
| DDP | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 工业界标准、大型模型训练 |
五、 AI 硬件加速设备
💡 核心逻辑
深度学习对算力的渴求推动了硬件从 通用(CPU) 向 并行(GPU) 再向 专用(ASIC/TPU/NPU) 的演进。
CPU:全能管家,逻辑控制强,计算效率低。
GPU:并行专家,浮点运算强,功耗高,依赖 CPU。
ASIC:定制工匠,为特定算法设计,性能功耗比极高。
5.1 通用处理器:CPU vs GPU
| 特性 | CPU (中央处理器) | GPU (图形处理器) |
|---|---|---|
| 设计目标 | 处理复杂指令、分支跳转、逻辑控制 | 处理海量、简单的数据并行运算 |
| 架构特点 | 控制单元大,晶体管主要用于缓存和逻辑 | 大量晶体管组成专用电路和流水线 |
| 计算瓶颈 | 冯·诺依曼结构导致频繁读取指令/存储 | 功耗高,无法独立工作,需 CPU 调用 |
5.2 TPU (Tensor Processing Unit, 张量处理器)
TPU 是谷歌为优化 TensorFlow 框架而定制的 ASIC 芯片,专为神经网络计算设计。
1. 核心架构:脉动阵列 (Systolic Array)
矩阵乘法单元 (MMU):TPU 的心脏。包含 $256 \times 256$ 个 MAC 部件,每个周期可执行 65,536 次 8 位乘加操作。
计算逻辑:数据像血液脉动一样流过芯片。多个运算逻辑单元(ALU)串联,复用一个寄存器的读取结果,极大降低了内存带宽压力。
性能指标:以 700MHz 运行,每秒可执行约 92 万亿次矩阵运算。
2. 技术特点
AI 加速专用:特定领域架构 (DSA),指令集精简,深度学习效率极高。
确定性功能:舍弃了缓存、分支预测等复杂逻辑。运行时间可精准预测,使芯片能以接近峰值的吞吐量运行。
大规模片上内存:一代 TPU 拥有占芯片面积 35% 的内存(24MB 局部内存 + 4MB 累加器内存),节约了访存能耗。
5.3 NPU (Neural-network Processing Unit, 神经网络处理器)
NPU 采用“数据驱动并行计算”架构,旨在解决冯·诺依曼结构中存储与处理分离导致的效率瓶颈。
1. 寒武纪 DianNao (电脑) 系列演进
| 型号 | 定位与特点 | 核心组件 (NFU) |
|---|---|---|
| DianNao | 基础加速器,模拟神经元工作 | NFU-1: 256个乘法器 NFU-2: 加法树 NFU-3: 激活函数单元 |
| DaDianNao | 多核升级版,专攻训练任务 | $16 \times 16$ 尺寸 NFU,数据通路更灵活 |
| ShiDianNao | 机器视觉专用,2D 格点结构 | 唯一考虑运算单元级数据重用的 2D 阵列 |
| PuDianNao | 异构加速器,支持多种机器学习算法 | MLU (机器学习单元) + ALU (处理通用计算) |
2. NPU 的运算逻辑 (以 DianNao 为例)
向量/卷积运算:NFU-1 完成元素相乘,NFU-2 完成结果累加,NFU-3 完成激活映射。
池化运算:利用 NFU-2 完成最大值或平均值提取。
PuDianNao 的多算法支持:支持神经网络、SVM、朴素贝叶斯、K-Means 等 7 种算法,是该系列中功能最灵活的单元。
💡 核心知识点总结表
硬件类型 核心优势 主要缺点 适用场景 CPU 极高的灵活性、逻辑处理 算力密度低、访存延迟高 逻辑控制、前处理 GPU 通用并行计算、成熟生态 功耗巨大、数据吞吐效率上限 通用深度学习训练 TPU 极速矩阵运算、低延迟 仅限特定框架、灵活性差 云端大规模推理/训练 NPU 存储处理一体化、低功耗 硬件逻辑固定、开发门槛高 手机/监控等移动端 AI
六、 PyTorch 各模块的使用
6.1 从机器学习到深度学习
1.1 什么是机器学习 (ML)?
机器学习是人工智能的一个子集,其核心在于“不通过显式编程来赋予计算机学习能力”。它通过算法从数据中寻找模式,并利用这些模式对未知数据进行预测。
- 特征工程:传统机器学习(如 SVM、随机森林)极其依赖人工设计的特征。如果特征选得不好,模型性能上限会很低。
1.2 什么是深度学习 (DL)?
深度学习是机器学习的一种特殊形式,利用多层人工神经网络来学习数据的高阶抽象表示。
- 端到端学习:它最大的优势是自动特征提取。你只需输入原始数据(如像素),神经网络会自动学习从简单线条到复杂轮廓的特征。
1.3 核心对比总结
| 维度 | 机器学习 | 深度学习 |
|---|---|---|
| 数据量 | 少量数据即可起步 | 极度依赖海量数据(大数据) |
| 硬件 | 普通 CPU 即可 | 依赖高性能 GPU/TPU |
| 特征工程 | 人工干预多,需专家知识 | 自动化程度高,端到端学习 |
| 可解释性 | 较强(如决策树) | 较弱(“黑盒”模型) |
6.2 深度学习项目标准流程 (PyTorch 视角)
在安装 PyTorch 时,官方通常建议通过一行命令同时安装 torch、torchvision 和 torchaudio。这三者构成了 PyTorch 开发的“三剑客”,分别负责底层核心、视觉任务和音频任务。
以下是它们的详细解析及用途:
torch (核心引擎)
这是 PyTorch 的主体库,也是所有操作的基础。
核心功能:
张量计算 (Tensors):提供类似于 NumPy 但支持 GPU 加速的多维数组运算。
自动求导 (Autograd):深度学习的灵魂,自动计算梯度以实现反向传播。
神经网络模块 (nn.Module):定义层、损失函数和各种架构。
用途:它是所有 AI 模型运行的底座,负责处理数学运算和硬件调度(如前文提到的 CPU、GPU、TPU 资源分配)。
torchvision (计算机视觉专家)
这是一个专门为图像和视频处理设计的扩展库,极大地简化了视觉任务的开发。
核心功能:
标准数据集 (datasets):内置了如 MNIST、CIFAR10 等常用数据集,并提供了 ImageFolder 等工具来读取按目录存放的图片数据。
图像变换 (transforms):提供缩放、裁剪、归一化等预处理功能。
底层 IO:如
read_image函数,可直接将图片文件读取为 PyTorch 张量。预训练模型:内置了 ResNet、VGG、YOLO 等经典的成熟模型,可以实现“拎包入住”式的迁移学习。
用途:只要你的任务涉及图片识别、目标检测或视频分析,就必然会用到它。
torchaudio (音频处理专家)
类似于 torchvision,它是专门针对音频和信号处理的扩展库。
核心功能:
音频加载:支持 wav、mp3 等格式的加载,并自动转换为张量。
信号变换:可以轻松将声波转换为梅尔频谱 (Mel-spectrogram) 等模型更易理解的形式。
专业算法:内置了重采样、滤波、波形增强等音频专用算法。
用途:适用于语音识别、音乐生成、声纹识别等任务。
总结对比
| 模块名称 | 专注领域 | 核心作用 |
|---|---|---|
| torch | 通用深度学习 | 提供张量运算、自动求导和硬件加速。 |
| torchvision | 图像/视频处理 | 图片数据、执行数据增强、调用预训练视觉模型。 |
| torchaudio | 音频/语音 | 加载音频文件、转换频谱、处理音频信号。 |
💡 关于 Dataset 与 DataLoader 的“户口”归属
这是一个非常关键的逻辑点,决定了你代码的组织方式:
核心规则在 torch:
Dataset基类与DataLoader工具的源代码其实都在 torch 核心库中(位于torch.utils.data)。它们定义了 PyTorch 处理数据的通用游戏规则:无论你处理的是图片、文字还是音频,都必须遵循“定义仓库 (Dataset)”和“架设传送带 (DataLoader)”的流程。现成素材在 torchvision / torchaudio:这两个模块是根据上述规则,针对特定领域提供的样板间。例如,
torchvision.datasets.ImageFolder就是一个已经按照 torch 的规范写好的 Dataset 子类,专门用来读图片。深度总结:数据如何流转?
我们可以用下面这个流程图来理解它们是如何协同工作的:
现实世界:原始的 .jpg 图片或 .wav 音频文件。
翻译阶段 (torchvision / torchaudio):利用这些模块中的工具(如
read_image或自定义的MyDataset)将原始文件读取、裁剪、缩放。标准化阶段 (Dataset):数据被包装成统一的格式,并明确“一共有多少”和“怎么拿一个”。
分发阶段 (DataLoader):利用 torch 自带的传送带,将数据打包成 Batch,并决定是否打乱顺序。
计算阶段 (torch):数据最终以 Tensor(张量) 的形式进入显卡,交给 torch 进行疯狂的矩阵运算。
核心结论:
torch负责计算,不挑食;torchvision/torchaudio负责洗菜切菜,把原始食材加工成 torch 爱的张量。 虽然我们在做视觉任务时经常引用 torchvision,但请记住,DataLoader永远是你从 torch 主库里请来的“搬运工”。
2.1 基本配置 (Basic Configuration)
这是项目的“地基”。
环境设置:包括导入 torch 库,设置随机种子以确保结果可复现。
设备分配:检测并指定运行设备(cpu、cuda 或 mps)。
超参数定义:统一管理学习率 (LR)、Batch Size、训练轮数 (Epochs) 等关键参数。
2.2 数据读入 (Data Loading)
PyTorch 的数据读入是通过 Dataset 与 DataLoader 协作完成的。
Dataset:定义了数据的“仓库规范”。它规定了数据在哪、有多少、每一份长什么样。
DataLoader:定义了数据的“分发规则”。它负责按批次(Batch)抓取数据、打乱顺序(Shuffle)以及多进程加速。
1. 构建自己的数据读取流程
要实现灵活的数据读取,我们需要定义一个继承自 torch.utils.data.Dataset 的类。这个自定义类必须包含以下三个核心函数:
__init__:用于向类中传入外部参数(如文件路径、变换操作),并定义样本集。__len__:返回数据集中的样本总数。__getitem__:用于逐个读取样本集合中的元素。在这里可以进行数据变换,并返回训练或验证所需的(data, label)对。
2. 常见的数据读取方式
根据数据的复杂程度,通常有以下三种处理方案:
方案一:简单情况 —— 使用 PyTorch 内置数据集
如果你处于学习阶段,正在练习经典模型,torchvision.datasets 已经为你封装好了现成的类。
适用场景:MNIST、CIFAR-10、COCO 等标准学术数据集。
优点:一行代码即可实现自动下载、解析和加载。
| |
参数详解
| 参数名 | 作用 | 深度解析 |
|---|---|---|
root | 存储路径 | 指定数据集下载后存放在本地的哪个文件夹。如果文件夹不存在,它会自动创建。建议统一放在 ./data 下方便管理。 |
train | 模式选择 | True:加载训练集(通常 60,000 张);False:加载测试集(通常 10,000 张)。你需要分别创建这两个对象。 |
download | 自动下载 | True:如果 root 路径下没找到数据,就去官网下载。如果已经有了,它会跳过下载直接加载,非常智能。 |
transform | 数据变换 | 必考点。原始下载的数据通常是 PIL 图片格式,模型看不懂。ToTensor() 将其转为 Tensor 并将像素值归一化到 [0, 1] 之间。 |
为什么
transform这一步必不可少?你可能会问:“我不设置 transform 行吗?” 答案是:不行。
格式不兼容:如果不加 transform,Dataset 返回的是 PIL 图片,当你把它喂给 DataLoader 时,程序会因为无法把图片打包成“张量批次”而报错。
维度重排(Dimension Reordering):原始图片(PIL 或 NumPy)通常是 HWC 格式(高度、宽度、通道)。PyTorch Tensor 要求必须是 CHW 格式(通道、高度、宽度)。
ToTensor()会自动把通道维度(如 RGB 的 3 层)挪到最前面。数据类型转换(Type Conversion):将原本是整数类型(uint8,范围 0-255)的像素点,转化为浮点数张量(FloatTensor)。
归一化缩放(Scaling/Normalization):它会将像素值从原本的 [0, 255] 自动缩放到 [0.0, 1.0] 之间。这对于防止神经网络在训练初期发生梯度爆炸至关重要。
“全自动”与“纯手工”的对比
使用
datasets.MNIST:你不需要关心数据集是从哪个 URL 下载的,不需要关心图片是怎么存在二进制文件里的,甚至不需要关心标签是怎么对齐的。PyTorch 全帮你做了。自定义
MyDataset:当你面对公司内部的私有数据(比如 CSV 里的医疗影像)时,你就需要手动实现__init__和__getitem__来告诉 PyTorch 如何读取。
方案二:标准情况 —— 使用 ImageFolder
如果你的图片数据已经按照“一个类别一个文件夹”的格式整齐排好了,PyTorch 提供了“懒人神器”。
逻辑:它会自动扫描目录,将子文件夹名直接映射为标签(Label)。
代码实现:
| |
在 PyTorch 中,原始图像(通常是不同尺寸、不同光照的图片文件)在喂给神经网络之前,必须经过统一的清洗和格式转换。这个“清洗和转换”的过程,就被统称为 Transform(变换)。
data_transform 是如何定义的?
通常我们会使用 torchvision.transforms.Compose 将一系列操作像“串糖葫芦”一样串起来。
| |
它在整个流程中起什么作用?
在你的代码 datasets.ImageFolder(root=..., transform=data_transform) 中,一旦你绑定了这个变量,就会触发以下连锁反应:
按需加工:每当 DataLoader 准备去抓取一张图时,这张图会先经过
data_transform里的所有步骤。数据增强(Data Augmentation):通过随机翻转、裁剪、旋转等操作,同一张图在每一轮训练中看起来可能略有不同,这能有效防止模型“死记硬背”,提升泛化能力。
格式对齐:神经网络对输入非常挑剔。
data_transform确保了进入模型的所有数据都是同样的大小、同样的数值范围、同样的 Tensor 格式。
方案三:真实/复杂情况 —— 必须自定义 Dataset
在实际项目或科研中,数据往往是碎片化的(如标签在 CSV 里、输入是图文混排、或者是音频/点云等)。此时必须通过继承 Dataset 类来自定义逻辑。
核心“函数”:
你需要在一个类中实现三个关键动作:
__init__:初始化仓库(读取路径、读取 CSV 标签)。__len__:告诉程序一共有多少条数据。__getitem__:根据索引 idx 拿出一条数据(并在这里做数据清洗/变换)。
代码模板:
假设你的标签存储在一个 CSV 文件中:
| |
3. 最终投喂:DataLoader
不论你用哪种方案生成的 train_data,最后都要交给 DataLoader 这个“传送带”:
| |
一句话总结
torch制定规则(Dataset/DataLoader),torchvision/audio提供素材处理。 掌握了自定义 Dataset,你就拥有了处理任何异构数据的能力。
4. 验证数据加载情况
可以通过 iter 和 next 手动查看加载的数据形状及内容。
| |
经验之谈:在使用 matplotlib 绘图时,一定要记得使用
.transpose(1, 2, 0)。这是因为 PyTorch 默认的通道顺序是 (C, H, W),而常用的绘图库要求 (H, W, C)。
6.3 模型构建 (Model Construction)
神经网络的兴起受益于卷积神经网络(CNN)和反向传播算法(BP)的实现。在 PyTorch 中,所有的模型构造几乎都是基于 nn.Module 类完成的,它提供了极高的灵活性。
1. 神经网络的构造基础
Module 类是 torch.nn 模块提供的模型构造基类。创建一个模型通常需要重载两个关键函数:
__init__:创建模型参数(如定义各种层)。forward:定义前向计算逻辑(数据怎么流转)。
示例:多层感知机 (MLP)
| |
关键点:你不需要定义
backward函数。由于 PyTorch 的 Autograd 机制,系统会自动生成反向传播逻辑。
2. 自定义参数
在自定义层时,如果你只是定义一个普通的 torch.Tensor,模型在训练时是看不见它的。你必须告诉 PyTorch:“这是一个需要优化的权重。”
什么是
nn.Parameter?本质:它是 Tensor 的子类。
自动注册:只要一个 Tensor 被定义为 Parameter,它就会被自动添加到模型的参数列表里。
训练标记:这意味着当你调用
net.parameters()时,PyTorch 能找到它;当你运行优化器时,它会被更新。
为什么需要
ParameterList和ParameterDict?如果你有很多参数,想用列表或字典存起来,不能直接用 Python 原生的 list 或 dict。
普通 List/Dict:存放在里面的 Parameter 不会被模型识别,训练时梯度不会更新。
专用容器:必须使用
nn.ParameterList或nn.ParameterDict。它们像“带登记功能的抽屉”,确保放在里面的每一个参数都完成了“入职登记”。
代码示例:自定义含参层
| |
3. 神经网络中常见的层
深度学习的魅力在于各式各样的层(全连接、卷积、池化等)。在深度学习中,神经网络的每一层都各司其职,像一条精密工业流水线。在写代码之前,理解这些层的逻辑分工至关重要。
宏观架构:输入、隐藏与输出
无论神经网络多复杂,都可以划分为这三大主要区域:
输入层 (Input Layer):
定义:数据的入口。在代码中,它通常不是一个具体的类(如
nn.Linear),而是你传入forward(self, x)中的张量x的形状 (Shape)。作用:确定模型能接收多大的图片或多少个特征。
隐藏层 (Hidden Layer):
定义:模型中用于“学习”和“提取特征”的所有中间层。
成员:卷积层、池化层以及中间的全连接层都属于隐藏层。
作用:负责把原始像素转化为高级语义特征(例如从像素点识别出“猫耳朵”)。
输出层 (Output Layer):
定义:模型的最后一层。
成员:通常是一个全连接层 (Linear),其输出神经元个数等于你的分类数量。
作用:给出最终的预测结果(例如:这张图 90% 的概率是猫)。
卷积层 (Convolutional Layer) —— “特征提取专家”
逻辑归属:隐藏层。
卷积层是视觉任务的核心。它通过一组可学习的“过滤网”(卷积核)在图像上滑动,提取局部特征。
作用:提取局部特征(如边缘、纹理、形状)。
核心参数:
卷积核 (Kernel):进行二维互相关运算的矩阵。
填充 (Padding):在输入边缘补 0,常用于保持输出与输入形状一致。
步幅 (Stride):滑动的快慢。步幅越大,输出维度减小越快。
池化层 (Pooling Layer) —— “数据压缩专家”
逻辑归属:隐藏层。
池化层不包含任何可学习的参数,它的计算是固定的。
作用:压缩特征图尺寸,减少计算量,并提高模型对平移的鲁棒性(即物体挪动一点也能识别出来)。
常见类型:
最大池化 (MaxPool):取窗口内的最大值,保留最明显的特征。
平均池化 (AvgPool):取窗口内的平均值。
全连接层 (Fully Connected / Linear Layer) —— “分类决策专家”
逻辑归属:隐藏层 或 输出层。
全连接层中的每一个神经元都与前一层的所有神经元相连,进行全局的信息汇总。
作用:将之前通过卷积和池化提取到的特征进行组合,映射到最终的分类空间。
数学本质:一个仿射变换(Affine operation),公式为 $y = xW^T + b$。
输出层应用:在模型的最后,全连接层的输出神经元个数通常等于类别数。
4. nn.Sequential
在 PyTorch 的神经网络构建中,如果说 nn.Module 是你的大地基,那么 nn.Sequential 就是一个预装好的“自动化流水线”或“逻辑胶囊”。
它是一个有序的容器,神经网络模块将按照在构造函数中传递的顺序依次被添加到计算图中,并按顺序执行。
为什么需要
nn.Sequential?在不使用它的情况下,你需要在
__init__里定义每一个层,然后在forward里手动写出数据流转的过程(如x = self.layer1(x),x = self.layer2(x)等)。使用nn.Sequential的核心优势在于:代码简洁:将功能相关的层打包在一起,大大缩短了
forward函数的长度。逻辑清晰:可以将模型划分为明显的块(例如:特征提取块、分类块),让结构一目了然。
自动前向传播:你只需要把输入丢给这个容器,它会自动按照你定义的顺序把数据传给内部的每一个层。
现代代码示例:AlexNet 中的应用
参考你提供的 AlexNet 实现,我们可以看到它使用了两个
nn.Sequential容器,分别管理“卷积特征提取”和“全连接分类”:
| |
注意事项与限制
虽然
nn.Sequential非常好用,但它也不是万能的:单一输入输出:它只支持数据按照直线运行(单一输入 $\rightarrow$ 单一输出)。 如果你的模型需要复杂的“跳跃连接”(如 ResNet 的残差结构)或多输入多输出,则不能单纯依赖 Sequential,必须在
forward中手动控制流转。调试粒度:因为层被打包了,如果你想在中间某一层停下来查看张量的形状,Sequential 会显得不够灵活(虽然可以通过
register_forward_hook解决,但操作较复杂)。
5. 经典模型示例
核心模型 A:LeNet
LeNet 是一个简单的前馈神经网络,经典的“卷积+池化+全连接”结构。
| |
核心模型 B:AlexNet
相比 LeNet,AlexNet 使用了更深的网络、更小的卷积核、以及
nn.Sequential容器,并引入了 Dropout 来缓解过拟合。
| |
💡 避坑与进阶 Tips
批量处理限制:
torch.nn只支持小批量 (Mini-batches) 输入。如果只有一个样本,请使用input.unsqueeze(0)增加一个“假”的批大小维度。梯度清零:在进行反向传播前,务必调用
net.zero_grad()清空之前的梯度缓存。权重更新公式:$weight = weight - learning_rate \times gradient$
Module vs Layer:在 PyTorch 中,Module 既可以是一个单独的层,也可以是一个庞大的模型,甚至可以是模型的一个子部件。
6.4 模型初始化 (Model Initialization)
在神经网络训练中,权重的初始值直接决定了模型的收敛速度和最终精度。
1. 为什么不能全初始化为 0?
虽然 nn.Linear 等层默认会有随机初始化,但我们通常会手动干预。
规避对称性:如果权重全为 0,网络中每个神经元的表现将完全一致,导致梯度消失或模型无法学习复杂的特征。
加速收敛:合理的初始化能让损失函数从一个更靠近“底谷”的地方开始下降。
2. PyTorch 的初始化工具箱:torch.nn.init
PyTorch 在 torch.nn.init 模块中提供了丰富的原地操作(In-place)函数。
注意:函数名后缀带有下划线
_的,表示会直接修改传入的张量。
常见的初始化方法:
基础型:
uniform_(均匀分布)、normal_(正态分布)、constant_(常数)。极简型:
ones_(全1)、zeros_(全0)。进阶型(最常用):
Xavier 初始化:适用于 Sigmoid 或 Tanh 激活函数,保持输入输出的方差一致。
Kaiming (He) 初始化:专为 ReLU 及其变体设计,是目前视觉模型的主流选择。
增益值 (Gain) 参考表:
不同的激活函数对梯度的影响不同,我们需要通过增益值进行修正:
| 激活函数 | 增益值 (Gain) |
|---|---|
| Linear / Identity | $1$ |
| Sigmoid | $1$ |
| Tanh | $5/3$ |
| ReLU | $\sqrt{2}$ |
| Leaky ReLU | $\sqrt{\frac{2}{1 + \text{negative_slope}^2}}$ |
3. 实战:如何优雅地初始化模型
在实际开发中,我们不会给每一个层手动写初始化代码,而是通过 isinstance() 判断层的类型,并使用 model.apply() 进行一键处理。
初始化函数封装:
| |
在 PyTorch 中,apply 函数是 nn.Module 提供的一个非常强大的递归工具。如果把你的神经网络比作一棵大树,apply 就像是一个高效的“巡检员”,它会顺着树干(主模型)一直走到每一片叶子(子模块/层)上执行你指定的动作。
核心定义:
apply(fn)函数的作用是将函数fn递归地应用到模型的所有子模块(Submodules)上。这里的子模块包括模型本身、定义的各个层、以及nn.Sequential里的每一个部件。为什么要用
apply?在处理复杂模型(如 AlexNet 或 ResNet)时,模型内部嵌套了很多层。如果你想给每一层做初始化,手动去写
self.conv1,self.fc2会非常繁琐且容易遗漏。一键操作:通过
model.apply(fn),你只需要写一个针对单层的处理函数,它会自动帮你跑遍全场。解耦逻辑:你可以将“构建模型”的代码和“初始化模型”的代码分开,让程序结构更清晰。
💡 关键 Tips
原地修改:
m.weight.data配合带下划线的函数(如normal_)是修改参数的正确姿势。不要“贪零”:除非是偏置(Bias),否则权重尽量不要初始化为 0。使用一个小正数(如 0.01)或 Kaiming 初始化效果会好得多。
现代框架的默认值:PyTorch 的内置层其实自带了不错的默认初始化(通常是 LeCun 或 Xavier 的变体),但在复现特定论文或遇到训练不收敛时,手动初始化是你的第一件武器。
深度思考:初始化其实是在帮模型“打破僵局”。既然你已经给模型设置好了完美的起跑姿势,接下来我们要不要看看损失函数,学习如何衡量模型在跑道上到底有没有跑偏?
6.5 损失函数 (Loss Function)
损失函数是模型的“负反馈”来源,它衡量预测值与真实标签之间的差距。
1. 分类任务 (Classification)
这类函数用于衡量概率分布之间的差异。
BCELoss (二分类交叉熵)
计算二分类任务的交叉熵,要求输入必须是概率形式(通常经过 Sigmoid)。
- 关键参数:
weight(类别权重)、reduction(计算模式)。
- 关键参数:
| |
CrossEntropyLoss (多分类交叉熵)
最常用的分类损失,它在内部整合了 LogSoftmax 和 NLLLoss。
- 关键参数:
ignore_index(忽略某个类别的计算)、weight。
- 关键参数:
| |
2. 回归任务 (Regression)
用于预测具体的连续数值。
L1 & MSE & SmoothL1 (距离度量)
L1Loss:计算绝对值差。
MSELoss:计算平方差。
SmoothL1Loss:误差小时用平方,大时用绝对值,减轻离群点影响。
| |
3. 相似度与特殊任务
KLDivLoss (KL 散度)
计算相对熵,用于衡量两个概率分布的接近程度。
- 关键参数:
reduction='batchmean'(在 Batch 维度求平均)。
- 关键参数:
| |
TripletMarginLoss (三元组损失)
用于拉近正样本距离,推开负样本距离。
- 关键参数:
margin(边界距离值)、p(范数阶数)。
- 关键参数:
| |
CTCLoss (连接时序分类)
用于解决如语音识别、OCR 等时序数据对齐问题。
| |
💡 核心参数
reduction总结所有的损失函数几乎都有这个参数,它决定了返回值的形式:
'mean'(默认):计算 Batch 的平均损失,返回标量。
'sum':计算 Batch 的总损失,返回标量。
'none':不合并,返回每个样本独立的 Loss,形状与输入一致。
6.6 训练和评估 (Training and Evaluation)
在 PyTorch 中,训练和评估的逻辑框架非常相似,核心区别在于是否更新参数以及是否记录梯度。
1. 核心开关:模型状态切换
模型中有一些特殊的层(如 Dropout 和 BatchNorm)在训练和测试时的表现是完全不同的。因此,在开始逻辑前必须先设置状态:
model.train():训练模式。开启 Dropout 随机失活,开启 BatchNorm 的均值方差统计更新。model.eval():评估/测试模式。关闭 Dropout,固定 BatchNorm。
2. 标准流程对比
训练和评估就像是“开卷考试”和“闭卷考试”的区别:
| 步骤 | 训练阶段 (Training) | 验证/测试阶段 (Validation) |
|---|---|---|
| 模式设置 | model.train() | model.eval() |
| 梯度计算 | 必须计算 | 关闭 (with torch.no_grad():) |
| 梯度清零 | optimizer.zero_grad() | 不需要 |
| 前向传播 | output = model(data) | output = model(data) |
| 计算损失 | loss = criterion(output, label) | loss = criterion(output, label) |
| 反向传播 | loss.backward() | 跳过 |
| 更新权重 | optimizer.step() | 跳过 |
3. 训练与验证代码
A. 训练逻辑封装
| |
B. 验证逻辑封装
| |
4. 模型效果的“体检报告”:Metrics
除了简单的 Loss 和 Accuracy,我们通常使用 sklearn 来生成更详细的报告(精确率、召回率、F1值)。
| |
💡 避坑与进阶 Tips
loss.item()的妙用:在累加 Loss 时,一定要用.item()提取数值。如果直接累加 loss 张量,由于它带着计算图,会导致显存逐渐堆积直至 OOM(内存溢出)。
.to(device)vs.cuda():推荐使用.to(device)。你可以在代码开头写一句device = torch.device("cuda" if torch.cuda.is_available() else "cpu"),这样代码在有无 GPU 的机器上都能跑。
torch.no_grad():在验证环节千万别漏了它!它不仅能提速,还能防止验证集的数据“污染”了模型的梯度,保证评估的客观性。
6.7 可视化 (Visualization)
在 PyTorch 的世界里,可视化工具主要分为基础绘图、实时监控和云端实验管理三大类。
6.7.1 常用可视化工具介绍
| 工具名称 | 类型 | 适用场景 | 核心特点 |
|---|---|---|---|
| Matplotlib | 基础绘图库 | 静态图表、实验总结 | Python 绘图的鼻祖,最通用,适合把训练好的结果画成论文插图。 |
| TensorBoard | 实时监控 | 监控训练过程 | 原本是 TensorFlow 的工具,现在已成为 PyTorch 的官方标配。适合看实时 Loss 曲线。 |
| Weights & Biases (W&B) | 云端管理 | 团队协作、大规模实验 | 现代化的“炼丹记录仪”。自动同步实验数据到云端,支持多人协作。 |
| Visdom | 实时监控 | 轻量级监控 | Facebook 团队开发的工具,支持多种数据窗口展示,适合较老或轻量级的项目。 |
6.7.2 深度解析:必备双剑
1. Matplotlib:你的“多功能画板”
它是你笔记里最常见的工具。无论是在 Jupyter Notebook 里看一张图片,还是对比两个损失函数的数学曲线,它都是首选。
优点:不需要额外开启服务器,随写随画。
局限性:难以处理实时动态数据,如果训练跑了好几天,你很难用它一直盯着曲线。Matplotlib 最适合在训练结束后,对收集到的数据进行统一汇总和对比。
代码示例:对比训练与验证 Loss
| |
关键说明:
适用性:适合在本地环境或 Jupyter Notebook 中快速查看结果。
操作模式:它是“非交互式”的。如果你想更新图像,必须重新运行代码生成新的图表。
2. TensorBoard:你的“实时监视器”
在 PyTorch 中,通过 from torch.utils.tensorboard import SummaryWriter 即可调用。
核心逻辑:
在代码中创建一个 Writer。
每训练一步,用
add_scalar把 Loss 或 Accuracy 写进日志文件。在终端输入
tensorboard --logdir=logs,打开浏览器就能看到跳动的曲线。
优点:可以一边训一边看,还能查看网络结构图、直方图。TensorBoard 的精髓在于它能一边训练一边写日志,你只需要刷新浏览器就能看到最新的曲线。
代码示例:在训练循环中集成 TensorBoard
| |
如何查看:
在你的项目目录下打开终端(Terminal)。
输入命令:
tensorboard --logdir=logs。点击终端给出的链接(通常是
http://localhost:6006/),在浏览器中查看动态曲线。
关键说明:
层级管理:在标签名中使用斜杠(如
Loss/train),TensorBoard 会自动帮你把同类指标归纳到同一个面板下。实时性:即便训练还在跑,你也可以随时打开网页看曲线的变化趋势。
6.8 PyTorch 优化器 (PyTorch Optimizer)
深度学习模型的训练本质上是一个寻找最优解的过程。面对拥有数千万参数的复杂模型(如 ResNet-50),我们无法暴力穷举,而是依赖反向传播(BP)与优化器(Optimizer)来逐步逼近最优参数。
优化器根据网络反向传播得到的梯度信息来更新参数,其核心目标是降低损失函数(Loss)的值,使模型输出不断接近真实标签。
6.8.1 优化器的基石:torch.optim
PyTorch 的 torch.optim 库提供了多种现成的优化算法(如 SGD, Adam, RMSprop, AdamW 等),它们全都继承自基类 Optimizer。
优化器的三大核心属性:
defaults:存储优化器的默认超参数(如lr学习率、momentum动量等)。state:存储参数的缓存信息(如动量缓冲区momentum_buffer),用于记录训练过程中的状态。param_groups:管理参数组的列表。每一组都是一个字典,可以为不同的层设置不同的学习率或超参数。
6.8.2 优化器的五大核心方法
| 方法 | 作用 | 备注 |
|---|---|---|
zero_grad() | 清空所管理参数的梯度 | PyTorch 梯度会累加,每次更新前必须手动清零。 |
step() | 执行一步梯度更新 | 根据当前梯度和算法逻辑修改参数数值。 |
add_param_group() | 动态添加参数组 | 可用于微调模型或给新层设置特定优化策略。 |
state_dict() | 获取当前状态字典 | 包含参数和超参数,用于模型保存。 |
load_state_dict() | 加载状态字典 | 用于断点续训,恢复上次训练的完整状态。 |
6.8.3 现代实战:标准训练逻辑与技巧
1. 基础训练循环
| |
2. 进阶技巧:差异化学习率
你可以为不同的网络层配置不同的优化强度,这在迁移学习中非常有用:
| |
6.8.4 优化器参数说明
1. 通用参数(几乎所有优化器都有)
在深入具体的优化器之前,这两个参数是你的“必修课”:
lr (Learning Rate, 学习率):最重要的参数。决定了每一步走的距离。
太大:模型会在最优解附近反复横跳,甚至直接“起飞”(梯度爆炸)。
太小:模型走得太慢,可能还没走到终点训练就结束了。
weight_decay (权重衰减):正则化技术(通常对应 L2 正则)。
- 作用:像给模型戴上“紧箍咒”,防止权重数值变得过大,从而有效缓解过拟合。
2. SGD (随机梯度下降)
SGD 是最经典的优化器,它的参数非常像物理世界的运动。
momentum (动量):模拟物体的惯性。原理:它会积累之前的运动方向。如果之前一直在下坡,它会越滚越快,帮助模型冲出“平坦区域”或“局部最小值”。通常设为 0.9。
nesterov (牛顿动量):布尔值。原理:开启后,它会先根据惯性往前“看一眼”,然后再计算梯度。这让它在弯道处更加灵敏,不容易冲出赛道。
3. Adam (自适应矩估计)
Adam 是目前的“万金油”,它的参数主要用于控制如何自动调整步长。
betas (平滑常数):通常是一个元组 (0.9, 0.999)。
$\beta_1$ (0.9):控制一阶矩(类似动量),决定了梯度的平滑程度。
$\beta_2$ (0.999):控制二阶矩(梯度的平方),决定了它是如何针对每个参数调整步长的。通常不需要改动。
eps (Epsilon):一个极小的数(默认 1e-8)。作用:防止在数学运算中除以 0。除非你遇到了数值不稳定的问题,否则永远别动它。
4. AdamW (Adam 的改进版)
- 核心区别:它是目前在 Transformer 和 现代视觉模型 中最推荐的优化器。它修改了
weight_decay的作用方式,让权重衰减真正独立于梯度更新,能获得更好的泛化效果。
5. RMSprop (均方根传播)
常用于循环神经网络(RNN)。
- alpha:平滑常数(默认 0.99)。类似于 Adam 中的 $\beta_2$,决定了对近期梯度的“记忆深度”。
核心对比表
| 优化器 | 建议学习率 (lr) | 必调参数 | 形象比喻 |
|---|---|---|---|
| SGD | 1e-1 或 1e-2 | momentum | 像一个滚下山的铁球,靠惯性冲过小坑。 |
| Adam | 1e-3 或 3e-4 | lr | 像一个带导航的赛车,自动在弯道减速、直道加速。 |
| AdamW | 1e-3 | weight_decay | Adam 的进化版,不仅跑得快,还不容犯错。 |
💡 怎么调最有效?
先用 Adam:如果你是新手,或者在测试一个新模型,先用
optim.Adam(lr=1e-3)。它通常能给你一个不错的结果。后期切 SGD:如果你在参加竞赛或追求极致的精度,可以在训练后期换成带
momentum=0.9的 SGD,配合细致的学习率调整,往往能磨出更高的分数。大模型必选 AdamW:如果你在玩 ViT、ResNet 变体或 Transformer,请直接使用
AdamW。
6.8.5 如何选择优化器?
优化器的选择会显著影响模型的收敛速度和最终效果。
SGD (随机梯度下降):虽然收敛可能较慢,但通常能找到更优的泛化解,适合调优精细的模型。
Adam:结合了动量和自适应学习率,收敛极快,是绝大多数任务的默认首选。
AdamW:修复了 Adam 在权重衰减(Weight Decay)上的问题,在计算机视觉任务中表现优异。
核心心得
优化器的性能高度依赖于模型和数据。没有绝对最好的优化器,只有最合适的组合。通常建议从 Adam 开始快速验证,后期再考虑使用带动量的 SGD 进行极限精度的打磨。