深度学习训练
data格式转换
1 | features.ToTensor() # 将PIL图像 or NumPy数组 -> PyTorch的Tensor格式, 像素值[0, 255] ->[0.0, 1.0] |
data维度转换
张量 tensor
- storage:内存中起始元素的位置
- Shape:元素的个数(2,3,2)
- Stride :步幅(12,4,1)
- contiguous:在内存中是否连续
- offset:基于起始元素的偏移
PyTorch 的张量操作
大多数 PyTorch 模型(比如 ResNet)期望输入 shape 为 [batch_size, channels, height, width],也就是 4 个维度。
.squeeze() & .unsqueeze()
- shape改变
1 | .squeeze(dim=None) # 删除所有长度为1的维度,如果dim长度是1,可删除 |
.view()
返回 原内存的 view,仅改变stride和shape
tensor必须是 contiguous(内存连续),否则需要.contiguous()先复制数据生成新的连续 tensor
1 | pos_embed.shape # torch.Size([1, 14, 14, 768]) |
.reshape() = .contiguous().view()
- 如果张量本身连续,
reshape()不会新建内存,只改变 view(视图),即改变张量的 shape 和 strides。 - 如果张量不是连续的,
reshape()会 复制一份数据(新建内存), 再改变张量的 shape 和 strides。 - reshape 只关注总元素数一样
1 | pos_embed.shape # torch.Size([1, 196, 768]) |
.flatten()
把张量展平成一维向量,默认按行优先 flatten(C-order)
1 | x = torch.arange(12).reshape(2, 3, 2) # torch.Size([2, 3, 2]) |
.permute(dim0, dim1, dim2, …)
- 内存不连续(contiguous:False),改变张量的 shape 和 strides
- 改变张量维度的顺序
1 | pos_embed.reshape(1, w0, h0, dim).permute(0, 3, 1, 2), # → [1, dim, w0, h0] |
.transpose(dim0,dim1)
- 内存不连续(contiguous:False),改变张量的 shape 和 strides
- 仅能交换张量中的两个维度
1 | q, k, v = qkv[0] * self.scale, qkv[1], qkv[2] |
.contiguous()
- 内存连续化,但如果原 tensor 已经是连续的,不会新分配内存
1 | y = x.transpose(0,1).contiguous() |
torch.stack(tensors, dim=0) 往上放一层
- 将多个张量沿着一个新维度拼接,增加一个新的维度
- 会新建内存,stack 后 tensor 是连续的
1 | a = torch.tensor([1, 2]) #(2,) |
torch.cat(tensors, dim=0) 连接起来
- 沿已有维度拼接 tensor,不增加新维度。
- 内存可能连续也可能不连续,cat 会返回新 tensor(通常连续)
1 | a = torch.ones(2,3) |
.expand(x.shape[0], -1, -1)
x.shape[0]替换了第一个维度(原来必须是1),“复制”维度为1的数据,扩展成 batch size。-1表示保持原维度大小不变(第二和第三维不变)。- 不复制数据(共享内存),只是逻辑上重复元素。
slice / narrow()
- contiguous 可能变成 False,offset 会改变
1 | x = torch.arange(12).reshape(3,4) |
张量广播(broadcasting)
自动逻辑扩展张量维度,使操作兼容。只能广播维度为 1或相等的情况
不会复制内存,只是让操作时逻辑上看成扩展后的 shape,stride 不会增加
- 使用
.expand()可以显式广播。
1 | x = [[0], |
自动梯度(autograd)
- PyTorch 自动跟踪 tensor 操作,计算梯度。
x = torch.randn(1, requires_grad=True)时,会记录操作历史生成计算图。.backward()自动计算梯度。
注:
1、view / slice / narrow 等操作生成的新 tensor 保留计算图
2、广播也会被记录
3、.detach() 可以切断计算图
4、在 GPU / CPU 上都支持
针对ndarray
1 | # 将one-hot编码的标签y转换为类别索引标签 |
ML model input
one-hot 接受情况
| 模型类别 | 模型名称/框架 | 是否接受 One-Hot 标签 | 正确标签格式 |
|---|---|---|---|
| 传统机器学习模型 | LogisticRegression (sklearn) |
❌ 不接受 | 整数类别标签(如 0,1,2) |
SVM / SVC |
❌ 不接受 | 整数类别标签 | |
RandomForestClassifier |
❌ 不接受 | 整数类别标签 | |
KNeighborsClassifier |
❌ 不接受 | 整数类别标签 | |
DecisionTreeClassifier |
❌ 不接受 | 整数类别标签 | |
GaussianNB |
❌ 不接受 | 整数类别标签 | |
| 神经网络框架 | PyTorch(自定义分类网络) | ❌ 通常不接受 | 整数标签,用 CrossEntropyLoss |
| TensorFlow / Keras + SparseCategoricalCrossentropy | ❌ 不接受 | 整数标签(如 0,1,2) | |
| TensorFlow / Keras + CategoricalCrossentropy | ✅ 接受 | One-hot 标签 |
| 框架 | 常用损失函数 | 标签格式要求 |
|---|---|---|
| PyTorch | CrossEntropyLoss |
整数标签([1, 2, 0, ...]) |
| Keras | SparseCategoricalCrossentropy |
整数标签 |
| Scikit-learn | 所有分类模型 | 整数标签 |
浮点数精度
浮点数精度问题的本质
计算机通常使用 IEEE 754 标准表示浮点数(如 float、double):
- 十进制小数 → 二进制往往是无限循环
- 存储时必须截断或舍入 → 产生误差(rounding error)
例如:
1 | 0.1 (十进制) ≈ 0.0001100110011... (二进制无限循环) |
常见精度问题
- 表示误差(Representation Error)
1 | 0.1 + 0.2 ≠ 0.3 #实际结果0.30000000000000004 |
- 累积误差(Accumulation Error)
1 | sum = 0 |
- 大数吃小数(Catastrophic Cancellation)
1 | a = 1.0000001 |
- 比较错误(Equality Comparison Problem)
1 | if (a == b) #浮点数通常不应该直接比较相等 |
- 舍入误差(Rounding Error)
在有限精度下:向最近值舍入,不同语言/硬件策略略有差异
下溢 / 上溢(Underflow / Overflow)
上溢:数太大 → ∞
下溢:数太小 → 0 或非正规数
精度类型
| 类型 | 位宽 | 有效数字(十进制) | 数值范围 | 优点 | 缺点 | 典型用途 |
|---|---|---|---|---|---|---|
| FP16(半精度) | 16-bit | ~3–4位 | 较小 | 显存占用低、计算快 | 易下溢、数值不稳定 | 混合精度训练 |
| FP32(float) | 32-bit | ~7位 | ~10⁻³⁸ ~ 10³⁸ | 稳定、通用性强 | 比FP16慢、占内存 | 默认训练精度 |
| FP64(double) | 64-bit | ~15–16位 | 极大 | 高精度 | 非常慢、占用大 | 科学计算 |
| BF16(Brain Float) | 16-bit | ~2–3位 | ≈ FP32 | 范围大、稳定性好 | 精度较低 | 大模型训练 |
| TF32(Tensor Float) | 19-bit(存储为32) | ~6–7位 | ≈ FP32 | 速度快(GPU优化) | 精度略低于FP32 | GPU训练(如NVIDIA) |
| INT8(量化) | 8-bit | 整数(无小数) | 很小 | 极省内存、推理快 | 精度下降明显 | 模型推理/部署 |
浮点数位宽组成
| 类型 | 总位宽 | 符号位(Sign) | 指数位(Exponent)-决定“范围” | 尾数位(Fraction)-决定“精度” | 特点 |
|---|---|---|---|---|---|
| FP16 | 16 | 1 | 5 | 10 | 精度低、范围小 |
| FP32 | 32 | 1 | 8 | 23 | 平衡(默认) |
| FP64 | 64 | 1 | 11 | 52 | 高精度 |
| BF16 | 16 | 1 | 8 | 7 | 范围≈FP32,但精度低 |
| TF32 | 19(计算格式) | 1 | 8 | 10 | GPU内部格式 |
| INT8 | 8 | — | — | — | 整数,无浮点结构 |
不同阶段推荐精度
当前主流
👉 使用:混合精度训练(Mixed Precision)在 PyTorch 和 TensorFlow 中都是默认推荐方案。
前向 / 反向:FP16 或 BF16
参数更新:FP32
FP16 计算得到的梯度通常是近似值,虽然精度有限,但通过转换为 FP32 进行累加和更新,可以避免这些小梯度在后续计算中被舍弃或变为 0。
优点:显存 ↓(约一半),速度 ↑(GPU加速),精度基本不变
大模型训练(LLM / Transformer)
👉 使用:BF16
原因:指数位多 → 不容易梯度爆炸/消失,比 FP16 更稳定
GPU特定优化
👉 使用:TF32(自动)
在 NVIDIA GPU 上:FP32 计算常自动用 TF32 加速,用户通常不用手动管
模型训练相关
.detach() 作用
1 | features = encoder(x) # encoder前向,生成特征 |
hydra
配置组合(Configuration Composition)
Hydra 允许你将多个配置文件(YAML)组合起来,这种方式称为配置组合(config composition)。你可以用模块化的方式组织配置,从而提高可维护性和重用性。
1 | # config.yaml |
命令行覆盖配置
Hydra 允许你在命令行中覆盖配置参数,无需修改代码或配置文件本身。
1 | python train.py model.hidden_size=512 dataset=mnist |
动态“添加”一个新配置组
1 | python demo_forcefield.py \ |
多运行支持(Multirun)
通过 -m 选项,Hydra 可以自动运行多个配置组合,用于超参数搜索等任务。
1 | python train.py -m model.hidden_size=128,256,512 |
💡使用示例
1 | import hydra |
1 | 配置文件结构: |
loss变成Nan一般会是什么原因?
1️⃣ 学习率过大
现象:前几步正常,突然 loss = NaN 或一路飙升然后 NaN
原因:参数更新一步跨太大,直接数值溢出
解决
- 把
lr先 除以 10 或 100 - 用 warmup
- 梯度累计(缓解「小 batch + 大 lr」导致的 NaN)
- Adam/AdamW 比 SGD 稳定
1 | optimizer = AdamW(model.parameters(), lr=1e-4) |
2️⃣ 梯度爆炸
现象:loss 先变大,再 NaN(RNN / Transformer / 深层网络更常见)
解决
- 梯度裁剪(强烈推荐)
1 | torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) |
- 减小 batch size
- 用 LayerNorm / BatchNorm
3️⃣ 数值非法操作(log / div / sqrt)
典型雷点
1 | torch.log(0) |
解决
- 加
epsilon
1 | eps = 1e-8 |
- 用 PyTorch 官方 loss(更稳)
1 | nn.CrossEntropyLoss() # 比自己写 softmax + log 稳 |
4️⃣ 输入数据本身有 NaN / Inf
排查
1 | torch.isnan(x).any() |
解决
- 数据标准化前先清洗
- 避免除 0
- 缺失值填充
5️⃣ FP16 / 混合精度溢出
现象:用 AMP 时突然 NaN
解决
- 用
GradScaler - 或临时关掉 FP16 验证
1 | scaler = torch.cuda.amp.GradScaler() |
6️⃣ 初始化不当
现象:第一步 loss 就是 NaN
解决:用官方初始化
1 | nn.init.xavier_uniform_(weight) |
7️⃣ Loss 特有坑
- CrossEntropyLoss:不要自己先 softmax
1 | # ❌ 错误 |
- BCEWithLogitsLoss:输入必须是 logits,label ∈ {0,1}
8️⃣ 回归任务
- target 量级过大 → MSE 爆炸先 → normalize target
SOMESOME
- Dropout调整:合理位置添加并调整Drop Rate,可有效提升模型性能。
- Softmax温度:在注意力、交叉熵分类、对比学习等涉及Softmax的场景中引入温度参数。
- Normalization:深度学习核心是表征学习,需通过归一化(如LLM的Pre-norm/Post-norm、多模态系统设计)确保分布对齐,避免深层网络或模块组合时的表征偏移。
- Batch Size与Learning Rate同步调整:Batch Size影响梯度估计准确性(类比下山方向),LR影响更新步长(下山速度),方向准确时可加快步长;微调预训练模型时可采用极低LR(如e-7)配合多轮训练。
- 学习率策略:采用Warmup+Cosine Decay组合。
- 正则化:基础正则化如weight decay可显著提升泛化能力。
- Label Smoothing与Hinge Loss:推荐使用,OAI深度学习的weak-to-strong generation基础实现可视为进阶Label Smoothing。
- 数据采样:排序任务中调整负样本采样策略是常用手段。
- 特征融合:优先用哈达马积而非拼接/相加,其能引入更多非线性并实现滤波效果。以上均有PyTorch原生API支持,实现简单。
- 表征分布:通过triplet loss、对比学习等提升内部表征质量,高质量表征可增强模型鲁棒性与泛化性,需针对模型特点关键位置表征。
- 加权平均设计:SE Layer等加权机制在多领域(不限于CV)效果显著。
- 多头结构扩展:借鉴多头注意力经验,扩展至其他模块可有效利用算力提升性能。
- Gating操作:受RWKV中token shift启发,非关键路径的Sigmoid门控有助于建模复杂依赖关系。
总结:深度学习核心tricks聚焦于表征分布与梯度流通,二者是模型的关键。
消融实验的一些小tips分享
第一步:优先尝试轻量改动,提升效果 先从 LayerScale、Stochastic Depth 和 Warmup Cosine 这三个方法入手。它们的改动幅度小,操作起来简单,而且效果稳定显著,基本都能带来性能提升,是性价比很高的优先选项。
第二步:进阶优化,补足效果 如果第一步的优化后效果还未达到预期,可以再加入 EMA进一步提升模型稳定性。要是还有提升空间,还可以尝试分层权重衰减,针对性地优化参数更新,让模型训练更精准。
第三步:最后调整,尝试换优化器 前面的方法都试过之后,如果效果还是不理想,最后再考虑更换优化器。这种调整相对复杂,建议放在最后尝试,避免前期因改动过大导致训练不稳定
