深度学习自学地图:从数学基础到模型压缩与推理部署 📅 发布时间:2026/9/11 15:48:01 👁 浏览次数: 简介深度学习资料合集覆盖从数学基础到工程落地的完整学习链路适合具备一定Python基础、正在入门或进阶深度学习的学生、算法工程师及竞赛选手。包内系统梳理线性代数、微积分、概率论等数学知识详解卷积、循环、注意力等神经网络基础部件并提供模型训练调参的“炼丹策略”与剪枝、量化等压缩算法说明还包含深度学习推理框架的实战实现思路。资源共497个文件以md笔记、png与jpg图解为主辅以svg流程图、gif动态演示、xmind思维导图及py/cpp示例代码配套思维导图可快速搭建知识框架动态图解直观展示卷积、池化等运算过程便于对照阅读与动手练习整体压缩包约118MB目录按主题划分查找方便。目前已有269人学习浏览既可作为课程设计、期末大作业或毕业设计的参考资料也适合希望在模型部署与优化方向深入钻研的开发者按需取用。1. 一份能快速落地的深度学习自学地图训练一个模型loss 曲线在某个 epoch 突然变成 NaN或者在验证集上准确率怎么都上不去这种问题几乎每个做深度学习的人都会遇到。搜教程时你会发现网上信息很分散数学推导、网络结构、调参技巧、模型压缩散落在不同的博客和视频里经常是这块学完了下一块又得重新找资料。这份深度学习资料合集的价值在于它把数学基础、神经网络部件、训练策略、模型压缩算法以及推理框架实现串成了一条完整的链路。它不是某一框架的官方文档也不是某个单一模型的复现而是一份偏工程视角的知识地图。对于计算机、数学、电子信息专业的课程设计或者毕设这份合集能帮你少走很多弯路对于已经入行但想系统补齐知识盲区的从业者它也能提供不少值得细读的细节。2. 深度学习数学基础从梯度推导到矩阵运算的工程视角2.1 为什么要回头补数学一个具体的反向传播例子深度学习框架的自动微分功能太方便了以至于很多开发者可能从来没有手推过反向传播。但遇到自定义算子、梯度异常或者需要把模型移植到特定硬件时数学功底就会显露出价值。这份资料里的数学基础部分核心覆盖了线性代数、概率论和微积分在神经网络中的应用重点不是完整数学课程而是把和模型训练直接相关的部分挑了出来。以反向传播的链式法则为例很多资料只给公式但工程里真正要理解的是计算图上的数据流动。假设一个只有一层的网络输入 x权重 w损失函数用均方误差前向计算为h w * x loss (h - y)^2反向传播时梯度要从 loss 往 w 方向流动。先求 loss 对 h 的偏导 ∂loss/∂h 2 * (h - y)然后求 h 对 w 的偏导 ∂h/∂w x根据链式法则 ∂loss/∂w ∂loss/∂h * ∂h/∂w 2 * (h - y) * x这个推导看似简单但实际工程里矩阵维度一变很多人就会搞混。比如当 x 是形状为 (batch_size, input_dim) 的矩阵w 是形状为 (input_dim, output_dim) 的矩阵时梯度的形状必须和 w 一致也就是 (input_dim, output_dim)。这个形状对齐规则是写自定义训练循环时最常踩的坑。import numpy as np # 模拟一个线性层的反向传播 def linear_backward(dout, x, w): 参数: dout: 上游梯度, 形状 (batch_size, output_dim) x: 输入数据, 形状 (batch_size, input_dim) w: 权重, 形状 (input_dim, output_dim) 返回: dx: 对输入的梯度, 形状 (batch_size, input_dim) dw: 对权重的梯度, 形状 (input_dim, output_dim) dx dout w.T dw x.T dout return dx, dw # 验证维度 batch_size, input_dim, output_dim 32, 128, 64 x np.random.randn(batch_size, input_dim) w np.random.randn(input_dim, output_dim) dout np.random.randn(batch_size, output_dim) dx, dw linear_backward(dout, x, w) print(dx shape:, dx.shape) # 期望 (32, 128) print(dw shape:, dw.shape) # 期望 (128, 64)这段代码的核心是维度对齐dx 的形状必须与 x 相同dw 的形状必须与 w 相同。之前有同行在自定义损失函数时梯度形状没对齐结果 loss.backward() 报错后排查了很久才发现是矩阵乘法顺序写反了。2.2 矩阵乘法与内存布局理解 row-major 和列访问数学基础部分的另一个重点是矩阵乘法的高效实现。很多人在 Python 里用 numpy 做矩阵乘法很顺手但到了 C 部署环境矩阵内存布局会影响性能数倍。资料里用 row-major 存储解释了一个关键问题访问连续内存比跳跃访问快得多。// 以 row-major 存储的矩阵乘法内层循环直接访问连续内存 void matmul_row_major(float* A, float* B, float* C, int M, int N, int K) { for (int i 0; i M; i) { for (int j 0; j N; j) { float sum 0.0f; for (int k 0; k K; k) { // A[i * K k] 是连续递增的 // B[k * N j] 的步长是 N不连续 sum A[i * K k] * B[k * N j]; } C[i * N j] sum; } } }这段实现里C 矩阵的每个元素计算都要完整扫描 A 的一行和 B 的一列。B 的列访问是不连续的cache miss 会很多。性能更好的做法是先把 B 转置成 row-major或者做分块矩阵乘法这些在资料的模型压缩和推理框架部分都有体现。2.3 激活函数的梯度特性为什么 tanh 容易梯度消失数学基础部分对激活函数的讨论很细。比如 tanh 的导数是1 - tanh(x)^2当输入绝对值偏大时输出接近 ±1导数接近 0梯度就会消失。很多初学者只知道 ReLU 能缓解梯度消失但不知道为什么。tanh 的梯度曲线在 0 附近最大向两侧迅速衰减。这就是深度学习数学基础中梯度稳定性的经典问题。理解这一点对炼丹很有帮助如果网络层数深且使用 tanh梯度传几层就没了参数几乎不更新。提示深度学习数学基础的核心不是会做难题而是能看懂 loss 为什么震荡、梯度为什么爆炸、网络为什么收敛慢。这份资料里的数学部分把这三类问题的数学解释都覆盖了。3. 神经网络基础部件详解从卷积的 padding 到 BN 层的训练/推理差异3.1 卷积层的参数计算与感受野变化卷积神经网络是深度学习中最常用的结构但卷积层的参数数量计算、输出特征图尺寸变化、感受野变化这些基础概念反而容易被忽视。资料里对卷积的讲解配有动态可视化图conv_dynamic_visual.gif 和 conv_visual.gif 展示了卷积核滑动和 maxpool 降采样的完整过程。卷积输出尺寸的计算公式是output_size (input_size - kernel_size 2 * padding) / stride 1这个公式本身不难但实际使用时有几个容易出错的细节。stride 不能整除时有的框架是向下取整有的框架是向上取整得确认实现的行为。之后计算模型 FLOPs 时需要理解参数和计算量的区别。import torch import torch.nn as nn # 验证卷积输出的尺寸变化 conv nn.Conv2d(in_channels3, out_channels64, kernel_size3, stride2, padding1) input_tensor torch.randn(1, 3, 224, 224) output_tensor conv(input_tensor) print(输入尺寸:, input_tensor.shape) print(输出尺寸:, output_tensor.shape) # 计算参数量 params sum(p.numel() for p in conv.parameters()) print(卷积层参数量:, params)输出尺寸会从 (1, 3, 224, 224) 变成 (1, 64, 112, 112)因为 stride2 做了下采样。参数量是3 * 64 * 3 * 3 64 1792其中最后的 64 是偏置参数数量和输入输出的空间尺寸无关只和通道数、卷积核大小有关。3.2 BatchNorm 在训练和推理时的不同行为BatchNorm 是神经网络基础部件中比较特殊的一个。训练时它用当前 batch 的均值和方差做归一化同时维护全局的 moving_mean 和 moving_var。推理时直接用保存的 moving_mean 和 moving_var而不是重新计算当前输入的统计量。这个差异如果不理解模型从训练切换到推理时很容易出现精度剧降的问题。import torch import torch.nn as nn # 手动模拟 BatchNorm 的训练和推理差异 bn nn.BatchNorm2d(num_features16) bn.train() # 训练模式 x_train torch.randn(8, 16, 32, 32) with torch.no_grad(): y_train bn(x_train) # 切换到推理模式 bn.eval() x_test torch.randn(8, 16, 32, 32) with torch.no_grad(): y_test bn(x_test)在 script 模式或静态图部署时BatchNorm 会被融合进前面的卷积层以消除推理时的额外计算开销。资料里提到的模型压缩算法详解就涉及 BatchNorm 融合的算子融合策略这是一个值得展开的细节。3.3 池化层的作用maxpool 为什么能带来平移不变性maxpool.gif 动态图直观展示了 max pooling 的行为。池化层的核心作用有两个下采样减少计算量提特征平移不变性。每隔几个像素取最大值使得微小的位置偏移不会显著影响输出。池化层和 stride1 的卷积层都能下采样但语义不同。卷积下采样是可学习的池化没有可学习参数。某些现代网络设计中会用 stride2 的卷积替代池化效果通常更好。资料中同时保留了卷积可视化动图和池化动图方便对比理解这一点挺有意思。提示神经网络基础部件部分的内容适合和 PyTorch 官方文档对照看。先看动图理解空间操作再读公式确认维度变化最后动手写一个简单 CNN 在 CIFAR-10 上验证。4. 深度学习炼丹策略优化器选择、学习率调度与 loss 震荡排查4.1 Adam 和 SGD 的适用边界炼丹策略的价值在实操中体现。发现模型不收敛时先检查数据预处理和标签是否正确再考虑优化器选择的问题。资料里对不同优化器的对比很实用SGD Momentum 的泛化能力通常比 Adam 好但 Adam 的收敛速度和对超参的鲁棒性更适合做初始探索。import torch import torch.nn as nn import torch.optim as optim # 优化器选择和参数配置 model nn.Sequential( nn.Linear(784, 256), nn.ReLU(), nn.Linear(256, 128), nn.ReLU(), nn.Linear(128, 10) ) # Adam: 初始学习率可以设大一点beta2 保持默认 0.999 optimizer_adam optim.Adam(model.parameters(), lr1e-3, betas(0.9, 0.999)) # SGD Momentum: 学习率要小momentum 一般取 0.9 optimizer_sgd optim.SGD(model.parameters(), lr1e-2, momentum0.9)用 Adam 时把学习率设置成 1e-3用 SGD 时设置成 1e-2包括 weight decay 的默认值也不一样。PyTorch 中 AdamW 这种优化器在 Transformer 类模型上更常用因为 Adam 的 L2 正则实现方式会导致权重衰减不彻底。4.2 学习率调度策略余弦退火与 warmup学习率调度的意义在于训练前期希望快速接近最优区域训练后期则要用小的学习率帮助精细收敛。资料里提到的余弦退火是目前比较常用的一种风格。专项说明下学习率调度器的进阶内容比如余弦退火和 warmup 的组合。import torch.optim as optim from torch.optim.lr_scheduler import CosineAnnealingLR, LinearLR, SequentialLR optimizer optim.Adam(model.parameters(), lr1e-3) # warmup: 前 10 个 epoch 线性上升 warmup_scheduler LinearLR( optimizer, start_factor0.01, end_factor1.0, total_iters10 ) # 余弦退火从当前值衰减到最小值 cosine_scheduler CosineAnnealingLR( optimizer, T_max90, eta_min1e-5 ) # 串联组合 scheduler SequentialLR( optimizer, schedulers[warmup_scheduler, cosine_scheduler], milestones[10] )这段代码中 start_factor0.01 表示初始学习率是设定的 1%经过 10 个 epoch 后线性升到 100%。eta_min1e-5 是余弦退火的最低学习率。这种组合策略在训练 Transformer 或深层 CNN 时比较常用能有效避免前期 loss 震荡。4.3 loss 出现 NaN 或震荡时的排查顺序炼丹和 debug 很相似模型训练中发现 loss 不下降或直接变成 NaN需要系统性排查。资料里给出的排查顺序很实用总结一下大概的排查逻辑输入数据里有没有 NaN 或 inf标签是否越界网络输出的数值范围是否合理尝试用固定初始化的权重跑一次损失函数中是否有除零操作比如 softmax 前的 logits 太大导致 exp 溢出学习率是否过大把学习率降到 1e-5 验证梯度截断gradient clipping只解决爆炸问题不能解决 NaN 根源import torch # 梯度诊断检查每个参数的梯度范数 total_norm 0.0 for p in model.parameters(): if p.grad is not None: param_norm p.grad.data.norm(2) total_norm param_norm.item() ** 2 total_norm total_norm ** 0.5 print(f梯度范数: {total_norm:.4f}) if total_norm 10.0: print(梯度爆炸建议降低学习率或添加梯度裁剪)4.4 数据增强和正则化手段的取舍炼丹策略中有一块内容专门讲正则化。Dropout 在 Transformer 类模型中和卷积网络的效果不一样需要看具体实现。比如 Dropout 在 Transformer 中通常作用于 attention 输出和 FFN 输出但在卷积网络中一般作用于全连接层。class SimpleCNN(nn.Module): def __init__(self): super().__init__() self.conv1 nn.Conv2d(3, 32, kernel_size3, padding1) self.bn1 nn.BatchNorm2d(32) self.conv2 nn.Conv2d(32, 64, kernel_size3, padding1) self.bn2 nn.BatchNorm2d(64) self.fc nn.Linear(64 * 8 * 8, 10) self.dropout nn.Dropout(0.5) self.relu nn.ReLU() def forward(self, x): x self.relu(self.bn1(self.conv1(x))) x self.relu(self.bn2(self.conv2(x))) x x.view(x.size(0), -1) # Dropout 放在全连接层前 x self.dropout(x) x self.fc(x) return x注意代码中 dropout 放在了全连接层的前面这是一个常见的插入位置。在测试阶段需要把网络切换到 eval 模式以关闭 Dropout或者用 model.eval() 方法。5. 模型压缩算法详解剪枝、量化与知识蒸馏的路数5.1 结构化剪枝与非结构化剪枝的工程取舍模型压缩和实际部署相关。深度学习模型训练好后体积偏大、推理速度偏慢尤其要部署到边缘设备或嵌入式的场景压缩几乎是必须做的步骤。资料里把修剪、量化、蒸馏三种主流算法都讲清楚了其中剪枝部分重点区分了结构化剪枝和非结构化剪枝。非结构化剪枝会产生稀疏权重矩阵加速效果依赖硬件对稀疏运算的支持。结构化剪枝则直接移除整个 channel 或者 filter不改变网络的基础结构可以直接在常规硬件上加速。工程上结构化剪枝的可落地性更高。import torch import torch.nn as nn def channel_prune(model, prune_ratio): 简单的通道剪枝示例对 Conv2d 层按权重 L2 范数排序剪枝 for name, module in model.named_modules(): if isinstance(module, nn.Conv2d): # 计算每个卷积核的 L2 范数 weight module.weight.data # shape: (out_channels, in_channels, kh, kw) l2_norm torch.norm(weight.view(weight.size(0), -1), dim1) # 确定保留的通道数 keep_num int(l2_norm.size(0) * (1 - prune_ratio)) # 按 L2 范数从大到小排序保留前面的索引 _, indices torch.sort(l2_norm, descendingTrue) keep_indices indices[:keep_num].sort()[0] # 用索引更新权重这里仅示意未处理 BN 和后续层的对齐 module.weight.data module.weight.data[keep_indices]这段代码演示了最原始的剪枝思路真实实现需要同时处理 BN 层以及下一层卷积的输入通道对齐。资料中对这部分的完整流程做了详细的讲解。简单来说剪枝后还需要做微调fine-tune因为剪掉通道后模型精度会下降需要用训练集继续训练几个 epoch 恢复精度而不是剪完直接部署。5.2 INT8 量化PTQ 与 QAT 的不同路径量化是压缩算法中效果最明显的手段。从 FP32 到 INT8模型体积可以缩小到原来的四分之一推理速度会有显著提升。量化又分为训练后量化PTQ和量化感知训练QAT两种路径PTQ 不用重新训练实现简单但当模型精度敏感时会有明显的掉点情况。import torch # 计算 activation 的 min/max用于对称量化 def symmetric_quantize(tensor, bits8): 对称量化示例将浮点张量量化为定点整数 qmax 2 ** (bits - 1) - 1 # INT8 对称量化上限为 127 scale torch.max(torch.abs(tensor)) / qmax quantized torch.round(tensor / scale).clamp(-qmax, qmax) return quantized, scale # 测试一个随机张量 x torch.randn(16, 16) * 0.5 q_x, scale symmetric_quantize(x) dequantized q_x * scale print(原始范围: [{:.4f}, {:.4f}].format(x.min(), x.max())) print(量化范围: [{}, {}].format(q_x.min().item(), q_x.max().item())) print(反量化误差: {:.6f}.format((x - dequantized).abs().mean().item()))对称量的计算过程中scale 是整个张量最大绝对值除以量化上限然后每个数除以 scale 取整。但量化误差较大时说明这个分布不适合直接做 PTQ得考虑 QAT 或对权重做 per-channel 量化。5.3 知识蒸馏用大模型教小模型知识蒸馏的逻辑是大模型学到的知识更丰富小模型单独训练很难达到大模型的精度但可以用大模型的输出软标签去指导小模型。软标签中包含了类别间的相似关系比如一只狗的图像在猫类别上也有一定概率这种信息是 one-hot 标签不具备的。import torch import torch.nn.functional as F def distillation_loss(student_logits, teacher_logits, labels, T3.0, alpha0.7): 知识蒸馏损失 alpha * KD 损失 (1-alpha) * CE 损失 # 软化后的 logits 用温度 T 缩放 kd_loss F.kl_div( F.log_softmax(student_logits / T, dim1), F.softmax(teacher_logits / T, dim1), reductionbatchmean ) * (T * T) ce_loss F.cross_entropy(student_logits, labels) return alpha * kd_loss (1 - alpha) * ce_loss蒸馏时温度 T 的取值需要尝试。T 过高会导致类别分布过于平滑信息被稀释一般取 3 到 7 之间比较常见。alpha 是 KD 损失和 CE 损失的权重偏大就意味着更依赖教师模型的信息具体值视任务而定。6. 推理框架实战把 PyTorch 模型搬到 C 环境的几个关键步骤6.1 libtorch 部署的基本流程资料最后一部分把模型压缩和推理框架实战串了起来这一节是工程落地最关心的问题模型训练完并且压缩完成后怎么集成到生产环境里。常见做法是用 libtorchPyTorch 的 C 接口加载模型完成部署而不必在 C 里重新实现整个网络。#include torch/script.h #include iostream int main() { // 加载 TorchScript 模型 torch::jit::script::Module module; try { module torch::jit::load(resnet18_quantized.pt); } catch (const c10::Error e) { std::cerr 模型加载失败: e.what() std::endl; return -1; } // 切换到推理模式 module.eval(); // 构造输入shape 为 (1, 3, 224, 224) std::vectortorch::jit::IValue inputs; inputs.push_back(torch::ones({1, 3, 224, 224})); // 推理 torch::NoGradGuard no_grad; auto output module.forward(inputs).toTensor(); std::cout 输出 shape: output.sizes() std::endl; return 0; }PyTorch 模型要先导出为 TorchScript 格式用torch.jit.trace或torch.jit.script即可导出。导出时需要注意模型中的控制流如果模型中存在依赖于张量数据的 if-else 分支trace 方式会固定住执行路径需要使用 script 方式保存。6.2 量化模型导出时的常见问题部署过量化模型的话会有体会INT8 量化模型在导出时容易遇到算子不支持的问题。PyTorch 的量化模型导出为 TorchScript 时某些自定义算子或特定版本的算子无法被正确序列化。比较稳妥的方式是先用torch.jit.trace导出再在 C 端验证精度和速度是否达到预期。import torch import torchvision.models as models model models.resnet18(pretrainedTrue) model.eval() # 使用 trace 导出为 TorchScript输入尺寸要与真实部署一致 dummy_input torch.randn(1, 3, 224, 224) traced_model torch.jit.trace(model, dummy_input) traced_model.save(resnet18.pt) # 也可以打包传参导出的 Input 信息方便 C 端查看输入要求 print(导出完成请检查 resnet18.pt 文件大小)从代码可以看到 trace 需要真实的输入张量这一步决定了模型的输入维度会固定住后续部署时如果输入尺寸变化就会与模型预期不符。如果模型需要支持任意尺寸输入建议在模型定义时使用固定尺寸或用 script 方式动态构建更灵活。6.3 算子融合把压缩算法的收益真正落到推理速度上推理框架实战中算子融合本身就是重要手段需要单独拿出一节说。模型压缩做完量化和剪枝并不代表推理速度就一定变快。层数深了以后逐算子执行每次都要读写显存或内存这个开销会非常显著。算子融合把多个连续操作合并成一个减少 kernel 启动开销和显存访问这属于常规但很关键的优化手段。最经典的融合例子是 Conv BN ReLU 融合。训练时 BN 是对卷积输出做归一化推理时 BN 的参数是固定的可以展开成对卷积输出的逐元素线性变换再与 ReLU 合并y ReLU(BN(Conv(x)))卷积是一个线性操作BN 在推理时也是线性变换ReLU 是逐元素操作三者可以合成一个算子。展平后的计算可以并入卷积层的权重和偏置实施时需要注意卷积层输出的每个通道对应不同的 BN 参数融合时要做通道维度的对齐。提示算子融合的收益通常在小模型上更加明显因为小模型的单次算子计算时间短kernel 启动和内存访问的相对开销更大。这也是为什么 MobileNet 这类轻量网络基本都做了算子融合。6.4 内存复用与输入尺寸固定的取舍C 推理框架的另一个关键点是内存复用。训练框架中张量会随计算图动态申请和释放内存但推理场景输入尺寸不变时可以提前分配好所有中间张量的内存并复用。这样网络在推理时几乎不触发内存分配操作从而提升时间表现的稳定性。实现思路是在模型加载完成后遍历一次计算图统计所有中间张量的生命周期然后分配一块大内存池按生命周期给张量分配池中的偏移地址。框架是 TensorRT 做法简单落地的方案就是预先分配一个足够大的缓冲区推理时反复使用// 简单的内存池示例 class InferenceMemoryPool { private: std::vectorfloat buffer; size_t offset 0; public: explicit InferenceMemoryPool(size_t size) : buffer(size, 0.0f) {} float* allocate(size_t count) { if (offset count buffer.size()) { throw std::runtime_error(内存池空间不足); } float* ptr buffer.data() offset; offset count; return ptr; } void reset() { offset 0; // 每轮推理结束后重置偏移 } };注意这个池子的实现没有考虑内存释放工程上要按生命周期来管理。如果反复推理每轮结束后调用 reset 就能复用整块内存完全避免内存分配。这个技巧在 CPU 推理和高频场景下提升会更加明显理解起来也更容易。部署时还有一些值得留意的附加问题比如模型格式版本兼容性。PyTorch 的 libtorch 版本有严格的对应关系用 2.x 版本导出的模型C 端也得用 2.x 对应的 libtorch 版本版本不匹配会出现加载直接崩溃的情况这也是部署时经常遇到的问题。如果对这方面有兴趣建议多刷一些实战经验帖和深度学习的案例积累这些边界问题的处理方法。本文还有配套的精品资源点击获取