神经网络初步
神经网络各层详解
我们用一个极简例子贯穿全文:
输入: 28×28 灰度图
结构: Conv(1→8通道, 3×3核) → ReLU → MaxPool(2×2) → FC(→10类)
输出: 数字 0-9 的识别结果
2. 什么是算子
算子是计算图上的一个节点——确定输入输出形状的计算单元。
输入 Tensor ──→ [ 算子 ] ──→ 输出 Tensor
↑
固定的数学公式 + 参数(可选)
类比 C 编程:算子是函数,算子组合是调用链,ONNX 是这些函数的静态调用图。
本文涉及的算子:
| 算子 | 有参数? | 参数 | 作用 |
|---|---|---|---|
| Conv | ✅ | weight (72), bias (8) | 提取局部特征 |
| BN | ✅ | γ (8), β (8) | 归一化数值分布 |
| ReLU | ❌ | 无 | 引入非线性 |
| MaxPool | ❌ | 无 | 降采样、扩大感受野 |
| FC | ✅ | weight (13520), bias (10) | 综合全局做决策 |
3. Conv2d(1→8, 3×3):卷积层
3.1 参数组成
输入: 1 通道(灰度图)
输出: 8 通道
核: 3×3
权重: [out=8] × [in=1] × [h=3] × [w=3] = 72 个 float32
偏置: 8 个 float32
3.2 卷积怎么算
一个 3×3 窗口在输入图上滑动,每次右移 1 格(stride=1):
3×3 输入窗口 3×3 卷积核
┌──────────┐ ┌─────────────┐
│ a b c │ │ w₀ w₁ w₂ │
│ d e f │ ⊙ │ w₃ w₄ w₅ │ = a·w₀ + b·w₁ + ... + i·w₈ + bias
│ g h i │ │ w₆ w₇ w₈ │
└──────────┘ └─────────────┘
↑
这 9 个数构成的核,在整张图上共享
不管窗口滑到哪,用的都是同一组 w
权值共享:72 个 float 就描述了一个完整的卷积层。不是每个输出位置存一组权重,而是每个通道存一组。
28×28 输入, k=3, p=0 → 输出 26×26×8
总计算量: 26 × 26 × 8 × 9 = 48,672 次乘加 (MAC)
4. Stride 与 Padding:Conv 的两个超参数
这两个超参数控制核的滑动行为——不在权重文件里,但在 ONNX 里显式记录。
4.1 Stride(步幅)
核每次移动的间距。
S=1: 输出 ≈ 输入尺寸 S=2: 输出长宽减半(降采样)
输出 = floor((N - K) / S + 1)
4.2 Padding(填充)
在输入图边缘外围补 0。
P=0: 越卷越小 P=1 (k=3): 输出等大 ("same padding")
输出 = floor((N + 2×P - K) / S + 1)
经典组合:
| 配置 | 效果 |
|---|---|
| P=0, S=1 | 越卷越小,边缘信息丢失 |
| P=1, S=1 (k=3) | "same padding",输出等大 |
| P=0, S=2 | 长宽减半(经典下采样) |
4.3 ONNX 里 strides 二元组、pads 四元组的原因
- strides: [H, W]:只有空间维度,从头到尾一致
- pads: [H_begin, W_begin, H_end, W_end]:不对称场景需要补不同数量
例如 7×7 输入 + k=3 + S=2 → 需要总 padding=1,只能上补 1 下补 0:pads: [1, 0, 0, 0]。
5. Pool:用空间换语义
5.1 本质
在固定窗口内做聚合操作,用一个统计量替代窗口内所有值。Pool 没有可学习参数。
4×4 输入, 2×2 窗口, S=2:
原始: MaxPool (取最大): AveragePool (取平均):
┌───────────┐ ┌───────────┐ ┌───────────┐
│ 3 1 4 2│ │ 7 8 │ │ 1.8 2.7 │
│ 7 2 5 8│ → │ │ 或 │ │
│ 9 6 2 1│ │ 9 6 │ │ 3.8 2.6 │
│ 5 3 8 6│ └───────────┘ └───────────┘
└───────────┘
本质:用 1 个数概括 n×n 个数的信息,顺势扔掉空间精度。
5.2 降采样
窗口按 stride 跳着走 → 输出长宽减半。副作用是平移不变性——图像平移 1 像素,MaxPool 输出可能不变。对分类是好事(鲁棒),对定位是坏事。
6. 感受野
输出特征图上一个像素对应回输入图上的区域大小。
输入 28×28 ──Conv3×3──→ 26×26 ──MaxPool2×2──→ 13×13
输出[0,0] ← 2×2 Pool窗口 ← 每个来自 Conv 的 3×3 感受野
→ 对应输入 4×4 的区域
层越深,感受野越大:
浅层 Conv (k3): 看到 3×3 → 边角
+ Pool (2×2, S2): 翻倍
中层 Conv (k3): 看到 10×10 → 形状
+ Pool (2×2, S2): 再翻倍
深层 Conv (k3): 看到 22×22 → 语义("这像眼睛")
不 Pool,3 层 k3 Conv 只有 7×7 感受野;加 Pool,轻松到 22×22。
7. FC:全连接层
PyTorch: nn.Linear(in, out),ONNX: Gemm。
7.1 本质:矩阵乘法 + 偏置
y₁ = x₁·w₁₁ + x₂·w₂₁ + x₃·w₃₁ + x₄·w₄₁ + b₁
y₂ = x₁·w₁₂ + x₂·w₂₂ + x₃·w₃₂ + x₄·w₄₂ + b₂
y₃ = x₁·w₁₃ + x₂·w₂₃ + x₃·w₃₃ + x₄·w₄₃ + b₃
矩阵: y = xW + b
7.2 FC vs Conv
| Conv | FC | |
|---|---|---|
| 连接方式 | 局部(每个输出连 9 个输入) | 全连接(每个输出连所有输入) |
| 参数共享 | 权重空间复用 | 每个连接独立参数 |
| 参数数量 | Conv(1→8,k3): 72 | FC(1352→10): 13520 |
| 空间结构 | 保留 | 丢弃(输入展平为 1D) |
7.3 在网络里的角色
Conv + Pool → 提取局部特征,保留空间结构
Flatten → 展平为一维向量
FC → 综合全局做最终决策
8. BatchNorm:夹在 Conv 和 ReLU 之间
8.1 为什么需要 BN
Conv 输出的值域不可控,可能很大或很小,导致训练不稳定。BN 把每个通道的值拉到均值≈0、方差≈1 的分布。
Conv → BN → ReLU
x - mean
x̂ = ────────────── ← 标准化
√(var² + ε)
y = γ × x̂ + β ← 缩放+平移(γ、β 可学习)
8.2 四个参数
| 参数 | 个数 (8通道) | 怎么来的 |
|---|---|---|
| γ (weight) | 8 | 训练学习(与 Conv.weight 同理) |
| β (bias) | 8 | 训练学习 |
| μ (running_mean) | 8 | 训练累积统计量 |
| σ² (running_var) | 8 | 训练累积统计量 |
8.3 推理时的算子融合
BN 公式展开后就是一个乘加:y = αx + b(α、b 是推理时已知常量),可吸收进 Conv 的 weight 和 bias。Conv+BN+ReLU → 一个融合算子,省两次 DDR 读写。
9. Conv + Pool 组合的意义
分工
Conv: 提取特征,"这里有一条竖线"
Pool: 丢弃精度,换取视野,"大概左上角有笔画"
每经过一组 Conv+Pool:
| Conv | Pool | |
|---|---|---|
| 参数 | 学习"看什么" | 无参数 |
| 空间尺寸 | 略减 | 减半 |
| 感受野 | 略增 (k-1) | 翻倍 |
| 通道数 | 变深 | 不变 |
| 计算量 | 大 | ≈零 |
最终效果
输入 28×28 (像素)
→ Conv: 纹理、边缘
→ Pool: 尺寸减半,感受野翻倍
→ Conv: 曲线、拐角组合
→ Pool: 再减半
→ Conv: 完整形状、语义
→ FC: 综合全局 → 输出决策
本质是用空间精度换语义抽象:像素 → 边角 → 形状 → 概念。

浙公网安备 33010602011771号