资讯动态

深度学习入门:从神经网络到Transformer的PyTorch实践主线

发布时间:2026/8/29 15:14:54 来源:尧图企业网站定制
深度学习入门最困难的地方往往不是某个公式看不懂而是不知道这些模型到底在解决什么问题。神经网络、CNN、RNN、Transformer 这四个关键词几乎是所有深度学习课程都会覆盖的节点但它们并不是四个孤立的模型清单而是一条从数据形状到模型设计的演化主线先有全连接神经网络再用卷积网络处理图像用循环网络处理序列最后用 Transformer 把并行能力和长距离建模同时解决。这篇笔记按这条主线展开带你从神经网络的基础概念开始逐步用 Python 和 PyTorch 跑通全连接网络、CNN、RNN 和 Transformer 的最小示例并补充深度学习训练与部署阶段最常用的精度取舍。学完之后你可以把相同的思路迁移到图像分类、文本分类、时序预测和简单 NLP 任务中。1. 先建立主线神经网络、CNN、RNN、Transformer 解决不同问题1.1 从规则编程到深度学习核心差别在哪里传统编程的核心是“人工设计规则”。比如判断一张图片是不是猫传统做法是先写清楚猫的耳朵形状、眼睛颜色、毛发纹理等特征再通过 if / else 组合成规则。这个方式在小规模问题里有效但一旦图片背景复杂、光线变化、拍摄角度不同人工规则就会变得难以维护。深度学习的核心思路是“从数据中直接学习映射关系”。它不再追求人写出所有规则而是提供大量“输入-输出”样本让模型自己调整内部参数使得输入经过计算后得到的输出尽量接近真实标签。以图像分类为例输入是像素矩阵输出是类别概率中间过程就是神经网络。这两个思路的差别决定了学习方式的不同。传统编程里每一步逻辑都要人负责深度学习里人更需要负责的是数据质量、模型结构、损失函数、训练过程和验证方式。这也是入门者最需要转过来的一个观念不是把模型“写出来”就能用而是要把训练流程“跑起来”并持续观察指标。1.2 神经网络的最小单元神经元、权重、偏置和激活函数神经网络的基本单元是神经元。一个神经元做的事情可以写成z w1 * x1 w2 * x2 ... wn * xn b a activation(z)这里的权重 w 表示每个输入的重要性偏置 b 可以理解为神经元被激活的阈值。如果没有偏置所有输入为 0 时神经元永远输出 0表达能力会很受限制。activation 是激活函数常见的有 ReLU、Sigmoid、Tanh。激活函数的作用是让网络不再是纯线性组合。纯线性组合有一个致命问题任意多层线性变换都能等价压缩成一层线性变换。只有加入非线性激活函数深层网络才能表达复杂函数。用最简单的话说激活函数让神经网络“弯得起来”否则网络再深也只是线性回归。在 PyTorch 中一个线性层已经封装了权重和偏置import torch.nn as nn layer nn.Linear(in_features64, out_features128) print(layer.weight.shape) # torch.Size([128, 64]) print(layer.bias.shape) # torch.Size([128])权重 shape 是[out_features, in_features]偏置 shape 是[out_features]。做矩阵乘法时输入形状需要满足[batch_size, in_features]输出形状会自动变成[batch_size, out_features]。1.3 一条主线理解模型演化把四个模型放在一张表里可以快速看到它们解决的核心问题。模型核心假设更适合的数据形状核心操作典型任务全连接网络特征之间无序、相互独立固定长度的特征向量线性变换 激活函数表格数据分类、回归CNN局部区域特征相关图像、时序信号卷积、池化图像分类、目标检测RNN数据存在时间先后关系长度可变的序列循环共享权重文本分类、时间序列预测Transformer任意位置都存在依赖序列、图像、多模态自注意力机制NLP、图像分类、大模型全连接网络是最基础的形态它把所有输入看作一个平铺向量。但当输入是图片时相邻像素本来就有空间关系全连接网络却把它们当成无差别的一维特征这既浪费参数也容易忽略局部结构。CNN 通过卷积核共享权重来保留局部空间关系。当输入是文本或语音这种有明显顺序的数据时CNN 又无法直接记录“前一个词影响后一个词”于是 RNN 用循环结构处理时间依赖。RNN 虽然能处理序列但逐时间步计算太慢长距离信息容易丢失Transformer 的自注意力机制让所有位置可以直接互相作用。这条主线理解了后面每个模型的代码都只是在实现同一个训练范式定义模型、计算损失、反向传播、更新参数、验证效果。模型结构不同但训练骨架不变。2. 环境准备用 Python 和 PyTorch 搭出可运行的学习环境2.1 Python 环境先建虚拟环境再装依赖刚入门深度学习时最忌讳在全局 Python 环境里直接安装一堆包。不同项目的 PyTorch 版本、Python 版本可能互相冲突建议先创建虚拟环境。在 Ubuntu 或 macOS 的终端中常用命令如下python3 -m venv dl_env source dl_env/bin/activate python -m pip install --upgrade pipWindows 的命令略有不同python -m venv dl_env dl_env\Scripts\activate python -m pip install --upgrade pip激活虚拟环境后终端前面会出现(dl_env)标记。后面安装的所有包都只存在于当前环境里不会污染系统 Python。如果担心本机 Python 版本较低也可以先使用 pyenv 或 conda 管理 Python 版本再创建虚拟环境。主要依赖建议先用表格确认依赖作用建议说明Python解释器建议 3.9 或更高版本PyTorch深度学习框架提供自动求导、模型组件torchvision图像数据工具做 CNN 和图像分类时使用numpy数值计算大量数据预处理依赖它scikit-learn轻量数据集与评估指标入门时可以借 digit 数据集练习jupyter / notebook交互式环境适合边写边看输出其中 scikit-learn 不是深度学习必需但它的内置数据集非常适合入门练习。后面示例会用到它的load_digits这样可以避开下载大型图片数据集的麻烦。2.2 安装 PyTorchCPU 版用于学习GPU 版用于训练深度学习课程入门阶段CPU 环境足以跑通小规模示例。真正进入训练任务后GPU 会明显更快。安装 PyTorch 时不推荐直接执行最普通的pip install torch因为默认版本可能会拉取不适合当前机器的 CUDA 依赖。CPU 版安装命令示例pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpuGPU 版安装命令需要根据本机 CUDA 版本确认。以 CUDA 12.1 为例可以这样安装pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121版本变化很快落地前先到 PyTorch 官网查看当前稳定版本对应的安装命令。安装后不要直接开始训练先用一段代码确认本机是否能识别 GPUimport torch print(torch.__version__) print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0) if torch.cuda.is_available() else CPU only)如果输出True说明当前环境可以调用 GPU。如果输出False不一定是安装错误也可能是没有 GPU 驱动、CUDA 版本不匹配或安装的是 CPU 版。入门阶段使用 CPU 版继续学习没有影响。2.3 验证 PyTorch 的核心能力张量和自动求导PyTorch 最核心的能力有两个张量计算和自动求导。自动求导是深度学习训练的基础模型参数更新全靠它计算梯度。下面代码展示了最小验证流程import torch x torch.tensor([1.0, 2.0, 3.0], requires_gradTrue) y (x ** 2).sum() y.backward() print(x.grad)输出结果是tensor([2., 4., 6.])。原因很简单y 对每个 x 的偏导数是 2 倍 x。这里必须注意requires_gradTrue意味着这个张量会参与计算图构建。训练结束后如果不再需要梯度要使用torch.no_grad()包裹验证代码否则内存会被计算图持续占用。这个习惯会在后面的训练循环中体现出来。到这里环境已经准备好可以进入第一个最小模型。3. 从零实现一个全连接网络把“学习”跑通3.1 准备一个不用下载的数据集深度学习入门做图像任务时最常用的是 MNIST 手写数字数据集。但初学者经常卡在数据集下载和路径配置上。这里先用 scikit-learn 自带的 digits 数据集它包含 1797 张 8x8 的手写数字图片输入是 64 维特征输出是 0 到 9 的类别。准备数据集的代码from sklearn.datasets import load_digits from sklearn.model_selection import train_test_split import torch from torch.utils.data import TensorDataset, DataLoader digits load_digits() X digits.data / 16.0 # 像素范围是 0-16归一化到 0-1 y digits.target X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) X_train torch.tensor(X_train, dtypetorch.float32) y_train torch.tensor(y_train, dtypetorch.long) X_test torch.tensor(X_test, dtypetorch.float32) y_test torch.tensor(y_test, dtypetorch.long) train_dataset TensorDataset(X_train, y_train) test_dataset TensorDataset(X_test, y_test) train_loader DataLoader(train_dataset, batch_size32, shuffleTrue) test_loader DataLoader(test_dataset, batch_size32, shuffleFalse)这里把输入除以 16是为了让特征值落在 0 到 1 之间。归一化在深度学习里非常重要。如果输入数据范围差异过大模型训练时梯度容易震荡收敛也会变慢。DataLoader的batch_size决定每次更新参数时用多少个样本shuffleTrue是为了打乱顺序避免模型学到样本顺序带来的假规律。3.2 定义多层感知机模型全连接网络也叫多层感知机。下面定义了一个三层 MLP输入 64 维隐藏层分别是 128 和 64输出 10 类。import torch.nn as nn class MLP(nn.Module): def __init__(self): super().__init__() self.fc1 nn.Linear(64, 128) self.fc2 nn.Linear(128, 64) self.fc3 nn.Linear(64, 10) def forward(self, x): x torch.relu(self.fc1(x)) x torch.relu(self.fc2(x)) x self.fc3(x) return xnn.Module是 PyTorch 中所有模型的基础类。__init__里定义层结构forward里定义数据流向。最后一层没有接 ReLU因为后面要使用CrossEntropyLoss这个损失函数内部会计算 softmax不需要在最后一层提前做激活。隐藏层使用 ReLU 而不是 Sigmoid主要原因是 ReLU 在正区间梯度恒为 1能有效缓解梯度消失。Sigmoid 在输入较大或较小时梯度接近 0深层网络训练起来会很慢。3.3 训练循环前向、反向、更新PyTorch 训练循环的骨架非常固定先定义损失函数和优化器然后在每个 batch 上重复四件事前向计算、计算损失、反向传播、更新参数。import torch.optim as optim model MLP() criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr1e-3) for epoch in range(20): model.train() total_loss 0.0 for xb, yb in train_loader: optimizer.zero_grad() logits model(xb) loss criterion(logits, yb) loss.backward() optimizer.step() total_loss loss.item() print(fepoch {epoch 1}, loss: {total_loss / len(train_loader):.4f})optimizer.zero_grad()必须在每个 batch 前调用。PyTorch 默认会累加梯度如果不手动清零上一个 batch 的梯度会叠加到当前 batch 上导致参数更新方向错误。loss.item()的作用是把 Tensor 转成普通 Python 数字。这里不要直接打印 loss因为loss仍然保留计算图直接参与日志输出会造成无意义的内存占用。训练循环里如果看到 loss 持续下降说明模型正在学习。3.4 验证模型在测试集上计算准确率训练结束后需要切换到验证模式。验证时不需要梯度因此用torch.no_grad()包住计算过程同时把模型设置为eval()模式。model.eval() correct 0 total 0 with torch.no_grad(): for xb, yb in test_loader: logits model(xb) preds logits.argmax(dim1) correct (preds yb).sum().item() total yb.size(0) print(ftest accuracy: {correct / total:.4f})model.eval()对当前这个 MLP 没有直接影响但对包含 Dropout、BatchNorm 的模型影响很大。Dropout 在训练时会随机丢弃神经元在验证时必须关闭BatchNorm 在训练时会使用当前 batch 的统计量验证时会使用累计统计量。因此养成训练和验证之间切换模式的习惯很重要。如果测试准确率明显低于训练准确率说明模型过拟合。常见处理方式包括减少模型层数、增加 dropout、增加数据量、降低训练轮数。入门阶段不需要追求很高精度先把训练流程跑通最重要。4. 卷积神经网络 CNN图像任务如何做到“局部感知”4.1 为什么全连接网络处理图像很吃力全连接网络需要把图像展开成一维向量。比如一张 64x64 的彩色图片输入是64 * 64 * 3 12288个数值。如果隐藏层有 4096 个神经元第一层的参数就接近 5000 万。这个参数规模在小数据集上很容易过拟合。更关键的问题是结构上的浪费。图像中相邻像素往往相关而远处像素相关性较低。全连接网络却把所有像素放在同一层级没有任何空间局部性的假设。它既不知道“左边”和“右边”的关系也不能理解“上下相邻”意味着什么。CNN 的思路是让模型自己学习“局部特征检测器”。一个卷积核只关注一个小区域比如 3x3 或 5x5在同一层中共享同一个卷积核。这样参数大大减少同时天然考虑了局部空间结构。4.2 卷积核、通道与特征图的含义卷积操作可以理解为用一个固定大小的窗口在输入上滑动每次做逐元素相乘再求和。以 3x3 卷积核为例每次覆盖输入的一个 3x3 小区域输出一个新值。滑过整个图像后就得到一张特征图。这里有三个关键概念kernel_size卷积核大小决定每次观察的局部范围。stride滑动步长步长越大输出尺寸越小计算量越低。padding在输入边缘补零用来控制输出尺寸常取 kernel_size 的一半实现“尺寸不变”。输出尺寸可以按下面公式估计output_size (input_size - kernel_size 2 * padding) / stride 1池化层则负责下采样。最常用的是 MaxPooling它取一个小区域的最大值。池化能缩小特征图尺寸、减少计算量也带来一定的平移不变性。4.3 用 PyTorch 搭一个 CNN 识别手写数字仍然使用 digits 数据集。原始数据是 64 维向量进入 CNN 前先 reshape 成1x8x8表示单通道、高 8、宽 8 的图像。import torch.nn as nn class SimpleCNN(nn.Module): def __init__(self): super().__init__() self.conv1 nn.Conv2d(1, 16, kernel_size3, padding1) self.conv2 nn.Conv2d(16, 32, kernel_size3, padding1) self.pool nn.MaxPool2d(2) self.fc nn.Linear(32 * 2 * 2, 10) def forward(self, x): x x.view(-1, 1, 8, 8) x self.pool(torch.relu(self.conv1(x))) # 8x8 - 4x4 x self.pool(torch.relu(self.conv2(x))) # 4x4 - 2x2 x x.view(x.size(0), -1) x self.fc(x) return xview(-1, 1, 8, 8)的作用是把[batch_size, 64]变成[batch_size, 1, 8, 8]。-1表示自动推断 batch 大小。第一次卷积后通道数从 1 变成 16宽高仍保持 8池化后变成 4。第二次卷积后通道数变成 32池化后宽高变成 2。最后的fc输入维度就是32 * 2 * 2 128。训练代码和第 3 节几乎一样只需要把模型换成SimpleCNN并且在每个 batch 里把输入 reshape 的逻辑放在模型内部。这样训练循环可以保持稳定。model SimpleCNN() criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr1e-3) for epoch in range(20): model.train() total_loss 0.0 for xb, yb in train_loader: optimizer.zero_grad() logits model(xb) loss criterion(logits, yb) loss.backward() optimizer.step() total_loss loss.item() print(fepoch {epoch 1}, loss: {total_loss / len(train_loader):.4f})4.4 参数和维度变化速查表很多初学者写 CNN 时卡在“最后一个全连接层应该输入多少维”。核心原则是手算每一层输出尺寸并打印验证。层输出通道输出尺寸参数量说明Conv2d(1, 16, 3, padding1)168x81163*3 16 160MaxPool2d(2)164x40Conv2d(16, 32, 3, padding1)324x416323*3 32 4640MaxPool2d(2)322x20Linear(128, 10)-10128*10 10 1290如果换用更大尺寸的输入图片最后的线性层输入维度必须重新计算。一个通用做法是在写完模型后用随机张量打印每一层输出fake_input torch.randn(4, 64) output model(fake_input) print(output.shape) # torch.Size([4, 10])这里不能凭空写维度必须让代码告诉你结果。遇到维度报错时把x.shape打印在 forward 里是最直接的排查方式。5. 循环神经网络 RNN处理序列任务时的循环共享思想5.1 序列数据和全连接网络的矛盾文本、语音、股票价格、传感器信号都有一个共同特征它们是按顺序排列的。前面出现的内容会影响后面的理解。“我 喜欢 猫”和“猫 喜欢 我”虽然包含同样三个词含义却完全不同。全连接网络把输入当成一个向量无法表达“顺序”这个信息因为交换两个输入维度对网络来说只是换了一下特征下标。RNN 的设计目标就是处理这种时序依赖。它会用一个隐藏状态保存“到目前为止看到的信息”并且在不同时间步之间共享同一套权重。这里要特别注意 RNN 的“共享”含义不是每个时间步都有一个独立的网络而是同一个网络被重复使用。因此无论序列长度是多少模型参数数量都不会随序列长度增长。5.2 RNN 的隐藏状态与时间步展开RNN 在时间步 t 的计算可以写成h_t tanh(W_h * h_{t-1} W_x * x_t b)其中 h_{t-1} 是上一个时间步的隐藏状态x_t 是当前时间步的输入W_h 和 W_x 是共享权重。最终输出可以基于最后一个隐藏状态 h_T也可以在每个时间步都输出一个结果。反向传播时梯度需要沿着时间方向从后往前传播这个过程称为 BPTT。当序列很长时梯度经过多次连乘后会指数级缩小或放大分别造成梯度消失和梯度爆炸。在 PyTorch 中nn.RNN封装了这些计算。下面用随机数据验证输入输出形状。import torch import torch.nn as nn batch_size 2 seq_len 5 input_size 4 hidden_size 8 rnn nn.RNN(input_size, hidden_size, batch_firstTrue) x torch.randn(batch_size, seq_len, input_size) out, h_n rnn(x) print(out.shape) # torch.Size([2, 5, 8]) print(h_n.shape) # torch.Size([1, 2, 8])batch_firstTrue表示输入格式是[batch_size, seq_len, input_size]。out保存每个时间步的隐藏状态h_n只保存最后一个时间步的隐藏状态。做文本分类时常用h_n作为整个序列的表示。5.3 用一个简单 RNN 模型完成序列分类以“序列中每个位置的数字累加后是否大于阈值”为例可以构造一个非常小的分类任务。这里重点关注模型结构。class SimpleRNN(nn.Module): def __init__(self, input_size, hidden_size, num_classes): super().__init__() self.rnn nn.RNN(input_size, hidden_size, batch_firstTrue) self.fc nn.Linear(hidden_size, num_classes) def forward(self, x): _, h_n self.rnn(x) out self.fc(h_n[-1]) return out训练时输入形状是[batch_size, seq_len, input_size]标签是类别索引。RNN 的优点是可以接收不同长度的序列但实际训练时为了组成 batch通常会把相同 batch 内的序列 padding 到同一长度。这也是文本处理中一个重要的工程问题。5.4 RNN 的梯度消失、梯度爆炸和改进方向RNN 最常见的两个问题梯度消失序列太长时前面的信息很难传到后面模型只会记住比较近的内容。梯度爆炸梯度值突增导致 loss 变成 NaN参数更新异常。应对梯度爆炸可以直接做梯度裁剪clip_grad_norm_(model.parameters(), max_norm1.0)应对梯度消失则需要改结构。LSTM 加入输入门、遗忘门和输出门让信息可以选择性保留。GRU 是 LSTM 的简化版本参数更少在很多任务上效果接近。PyTorch 中只需要把nn.RNN换成nn.LSTM或nn.GRU接口结构基本一致。lstm nn.LSTM(input_size, hidden_size, batch_firstTrue) gru nn.GRU(input_size, hidden_size, batch_firstTrue)入门阶段不必把 LSTM 内部公式全部背下来但一定要理解它为什么比 RNN 更擅长处理长序列门控机制让模型可以学习“哪些信息需要保留哪些信息需要遗忘”。6. Transformer注意力机制如何取代循环结构6.1 RNN 的两个硬伤串行计算和长距离遗忘RNN 在处理序列时必须从左到右逐个时间步计算。h_2 要等 h_1 计算结束h_3 要等 h_2 计算结束。这种串行方式导致训练速度很慢尤其是数据量大的时候。另一个问题是长距离依赖。如果一句话前面 20 个词才对最后的结果产生影响RNN 很难记住这么远的信息。LSTM 和 GRU 缓解了这个问题但没有彻底解决因为它们仍然依赖“一步步传递信息”的路径。Transformer 的做法完全不同。它不再按时间顺序逐步处理而是让序列中任意两个位置直接建立联系。这个机制就是 Self-Attention中文常称为自注意力机制。6.2 Self-Attention 的计算过程Self-Attention 的核心是三个向量Query、Key、Value。可以把它们理解成检索过程。Query 是当前词要查找的内容Key 是其他词能提供的信息类型Value 是其他词实际携带的信息。对于序列中的任意两个位置 i 和 j先计算 Query_i 和 Key_j 的相似度再用相似度加权 Value_j。公式如下Attention(Q, K, V) softmax(Q * K^T / sqrt(d_k)) * V除以 sqrt(d_k) 是为了避免点积结果过大导致 softmax 后梯度太小。d_k 是 Query 和 Key 的维度。这段计算在 PyTorch 中可以通过nn.MultiheadAttention直接使用。理解公式的关键不是手算而是记住它的效果每个位置的输出都融合了全序列的信息且融合权重由数据学习出来。6.3 Multi-Head Attention 与位置编码的作用一个自注意力头只能学习一种注意力模式。Transformer 使用 Multi-Head Attention把 Q、K、V 分别投影到多个子空间每个头学习不同关系。比如一个头关注语法关系另一个头关注语义相关性。由于自注意力没有顺序概念即使把句子中所有词顺序打乱模型看到的输入也完全一样。为了保留位置信息Transformer 需要额外加入位置编码。常见的做法是使用不同频率的正弦余弦函数也可以让位置编码作为可学习参数。位置编码和词向量相加后再送入 Transformer 层。这样模型既能知道“谁和谁相关”也能知道“它们各自在什么位置”。6.4 用 PyTorch 快速体验 TransformerEncoderPyTorch 提供了完整的 Transformer 模块。下面用一个随机输入验证基本流程import torch import torch.nn as nn d_model 32 nhead 4 num_layers 2 encoder_layer nn.TransformerEncoderLayer( d_modeld_model, nheadnhead, dim_feedforward64, batch_firstTrue ) transformer_encoder nn.TransformerEncoder(encoder_layer, num_layersnum_layers) x torch.randn(2, 10, d_model) # batch2, seq_len10, feature32 out transformer_encoder(x) print(out.shape) # torch.Size([2, 10, 32])输入[batch_size, seq_len, d_model]输出保持相同形状。区别在于输出中的每个位置都聚合了整条序列的信息。如果要在真实文本任务中使用还需要把 token 转成 embedding再加入位置编码。位置编码可以手动实现class PositionalEncoding(nn.Module): def __init__(self, d_model, max_len512): super().__init__() pe torch.zeros(max_len, d_model) position torch.arange(0, max_len).unsqueeze(1).float() div_term torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model)) pe[:, 0::2] torch.sin(position * div_term) pe[:, 1::2] torch.cos(position * div_term) pe pe.unsqueeze(0) self.register_buffer(pe, pe) def forward(self, x): return x self.pe[:, :x.size(1), :]Transformer 是目前大模型、文本生成、视觉 Transformer 的基础。入门阶段不一定要从零实现全部细节但至少要理解自注意力和位置编码这两个核心概念否则阅读后续论文或开源代码时会非常吃力。7. 训练和部署中的浮点数格式fp32、fp16、bf16、tf32 怎么选7.1 为什么浮点精度会影响训练和部署深度学习模型训练和推理离不开浮点数运算。默认情况下PyTorch 模型参数和中间张量使用 fp32也就是单精度浮点数。fp32 精度高、范围大但占用显存多、计算速度不是最优。当模型变大、数据变多时显存和算力成为瓶颈。fp16、bf16、tf32 等格式可以在不同程度上节省显存或加快计算但也会引入精度损失。选错格式时模型可能出现 loss 不降、指标异常、甚至训练直接发散。这里不把浮点数当纯理论知识点而是从工程选型的角度说明。7.2 四种浮点格式对比格式位宽指数位尾数位表示范围精度特点典型用途fp3232 位8 位23 位范围大精度高默认训练、数值敏感任务fp1616 位5 位10 位范围小易溢出或下溢混合精度训练、部分推理bf1616 位8 位7 位与 fp32 相近尾数少精度低大模型训练、A100/H100 等 GPUtf32约 19 位8 位10 位接近 fp32介于 fp32 和 fp16 之间Tensor Core 矩阵乘加速需要说明的是tf32 不是一种独立存储格式而是 NVIDIA Ampere 架构之后 GPU 在 Tensor Core 上执行 fp32 矩阵乘法时的加速模式。它在输入时将 fp32 尾数截断以此换取更高吞吐。fp16 的主要风险是数值溢出。因为它的指数位只有 5 位表示范围比 fp32 小很多梯度或损失值稍大就可能变成 inf 或 NaN。bf16 保留了 8 位指数因此表示范围和 fp32 几乎一样代价是尾数更短精度更低。7.3 实战选型建议入门阶段CPU 或普通 GPU 上建议直接用 fp32。这样能避免精度问题干扰对模型本身的理解。当显存不足或训练速度过慢且 GPU 支持混合精度时可以使用 PyTorch 的自动混合精度。下面是一段典型训练片段scaler torch.cuda.amp.GradScaler() for xb, yb in train_loader: optimizer.zero_grad() with torch.autocast(device_typecuda, dtypetorch.float16): logits model(xb) loss criterion(logits, yb) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()混合精度的核心思想是前向和反向传播时用 fp16 加速但梯度更新前用 scaler 放大更新后再缩小从而避免梯度值过小而被丢弃。如果是在支持 bf16 的 GPU 上训练大模型可以把dtype换成torch.bfloat16。bf16 不需要GradScaler也能保持训练稳定因为它表示范围够大。不过 bf16 是否可用要看当前 GPU 架构不能用一张普通显卡假设支持。推理阶段的选型更偏速度和显存。通常先用 fp32 跑出正确结果再尝试 fp16最后对比精度和速度差异。如果精度下降很小可以选择低精度如果指标明显变差就要回到更高精度。7.4 显存不足时的处理顺序深度学习实践中最常见的硬件问题就是显存不足英文报错通常是CUDA out of memory。出现时不要下意识调低模型层数而应按照代价从低到高的顺序处理调低 batch_size直到能跑通一次前向计算。使用梯度累积模拟更大 batch 的效果。开启混合精度训练。缩小输入图片或序列长度。检查是否有数据加载过程保留了过多缓存。最后再考虑减少模型参数量。其中梯度累积的写法是在多个 batch 上累加梯度但不立刻更新参数accumulation_steps 4 for step, (xb, yb) in enumerate(train_loader): loss criterion(model(xb), yb) / accumulation_steps loss.backward() if (step 1) % accumulation_steps 0: optimizer.step() optimizer.zero_grad()注意这里没有调用model.train()和model.eval()的切换逻辑实际项目中要补上。显存不足时先用短代码定位是模型本身占用了显存还是中间张量、优化器状态、数据加载缓存占用了显存。nvidia-smi只能看到整体情况更准确的方式是在训练代码里逐步打印torch.cuda.memory_allocated()。8. 新手最容易踩的坑和一条排查链路8.1 学习环境与生产环境的差异课程入门阶段代码通常在小数据集、单机环境中运行。只要能把 loss 降下来、测试集指标过得去就算完成了学习目标。生产环境则完全不同。生产环境需要考虑以下问题数据版本管理训练数据分布变化后模型需要重新评估。训练任务记录保存超参数、代码版本、数据集版本和评估指标。模型监控线上推理时指标是否漂移、延迟是否稳定。回滚方案新模型上线异常时能快速回到旧模型。权限和安全模型文件和训练数据不能随意访问。资源治理GPU 显存、CPU、内存、磁盘都要有监控。这些内容不会在一门入门课里全部涉及但入门阶段就建立区分意识后面做工程时会少踩很多坑。8.2 六个高频问题问题现象可能原因快速检查方式处理建议loss 不下降学习率过大或过小打印 loss观察是否震荡从 1e-3 开始调使用学习率搜索训练精度高、测试精度低过拟合对比训练集和测试集指标增加 dropout、数据增强、减小模型loss 出现 NaN学习率过大、fp16 溢出、数据有 NaN检查输入数据、梯度均值降低学习率使用 GradScaler清洗数据GPU 显存不足batch_size 过大、中间张量过多查看报错栈和显存占用降低 batch_size梯度累积混合精度维度报错输入 shape 和模型定义不一致打印 forward 中每一步 shape按实际输出重新计算全连接输入维度验证阶段忘记关梯度没有使用 torch.no_grad()观察显存持续增长验证时用 no_grad 包裹并调用 model.eval()高频问题的共同规律是先看数据再看模型再看训练流程最后看环境。不要一上来就改网络结构很多时候只是归一化没做、学习率不合理或标签类型传错。8.3 一条从现象到根因的排查链路遇到问题时建议按下面的顺序排查第一步确认输入数据。检查数据 shape、dtype 和数值范围。把训练数据打印出来确认标签是否从 0 开始。类别数应该和模型输出维度一致。第二步确认模型前向输出。用一个随机批次跑一次model(xb)打印输出 shape。如果这一步报错问题出在模型定义常见原因是全连接层输入维度不对。第三步确认损失计算。CrossEntropyLoss要求输入是[batch_size, num_classes]标签是[batch_size]且标签是torch.long类型。如果标签是torch.float会出现类型不匹配。第四步确认反向传播梯度。在loss.backward()后打印每个参数梯度的绝对值均值for name, param in model.named_parameters(): if param.grad is not None: grad_norm param.grad.abs().mean().item() print(name, grad_norm)如果梯度全是 0说明信息没有传回如果梯度极大说明需要梯度裁剪或降低学习率。第五步确认优化器更新。检查optimizer.step()前后参数是否发生变化。如果模型处于eval()模式某些层不会更新如果忘了zero_grad()更新方向也会出错。第六步确认运行环境。用print(torch.__version__)和print(torch.cuda.is_available())确认当前解释器、框架和 GPU 状态。很多时候用户安装了新环境却忘了激活虚拟环境导致代码使用的是另一个 Python 路径。8.4 给入门者的可复用检查清单每次写完一个模型可以按下面清单自检虚拟环境已激活依赖版本与示例对齐。数据已完成归一化标签类型正确。模型 forward 的输入输出 shape 已经打印验证。训练阶段调用model.train()验证阶段调用model.eval()。每个 batch 前调用optimizer.zero_grad()。计算 loss 后使用.item()记录不保留计算图。验证阶段使用torch.no_grad()包裹。训练结束后用torch.save(model.state_dict(), model.pt)保存权重。每次改动只调一个变量并记录改动前后指标。生产环境额外配置日志、监控和回滚方案。深度学习入门不是把模型背下来而是把每一类模型为什么存在、解决了什么问题、怎样用最小代码跑通理解清楚。全连接网络是基础CNN 解决局部空间特征RNN 处理时间顺序Transformer 用注意力机制突破串行和长距离限制。先在小数据集上把训练循环跑顺再逐步接触真实数据、分布式训练、混合精度和模型部署这是比“收集大量课程”更有效的学习路径。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价