1. 完整流水线总览

核心任务:把一张 87 × 58 像素的深度图编码成一个 32D 的隐向量 depth_latent,让下游策略网络能直接用这个紧凑表征来理解前方地形,而不必处理原始的几千个像素。

输入 depth 87 × 58 5046 像素 CNN(卷积) Conv2d(1→32, k=5) MaxPool2d(2) Conv2d(32→64, k=3) 作用: 局部 pattern 提取 MLP(全连接) Flatten ↓ 62400D Linear(→128) Linear(→32) 作用: 全局信息压缩 GRU + MLP(时序) + prop(31D) combine_mlp GRU(32→512) output_mlp 作用: 多帧时序平滑 输出 depth_latent 32D → Actor 三种网络层串联,每段做不同的事 — 不是单一的"MLP"
核心观点

把 5046 像素压成 32D 不是"单一网络在算",而是三段流水线协作

  1. CNN 段:从原始像素中找"局部视觉概念"(边缘、角、平地……)
  2. MLP 段:把分散在各位置的局部信息融合压缩成全局表征
  3. GRU 段:累积过去多步深度信息,时序平滑

2. 输入:一张深度图是什么

机器人前方的相机看到了下面这样一片场景。每个像素的值 = 那条光线击中障碍物的距离(米)。

87 × 58 = 5046 个数字(每个数字 = 距离值,单位 m) 远处 (~2.5m) 障碍物 ~0.5m(近) 地面(中等距离 ~1.5m) 深色 = 近 浅色 = 远 机器人要 从这堆数 字"看出" 前方有障 碍物
机器人前方的深度图(示意)— 每个像素是个距离数值
关键挑战:网络拿到的不是这张图片,而是 5046 个原始数字。它必须自己学会"哪些数字组合在一起意味着'有障碍'"。这就是 CNN 要解决的事。

3. 第 1 层 Conv2d(1→32, k=5):找 32 种局部 pattern

Conv2d 卷积层就像一组 32 个放大镜。每个放大镜是一个 5×5 的小窗口,里面存着一个"我要找的 pattern"。这个放大镜会在整张图上从左到右、从上到下滑动一遍,每滑到一个位置就检查"这块区域是不是匹配我要找的 pattern"

3.1 单个放大镜在干什么

放大镜 #1:5×5 窗口,专门找"水平边缘"(上面远、下面近) 这个放大镜里的数值: -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 0 0 0 0 0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 滑动 × 检查 深度图上滑动 这块亮 (上远下近 ✓) 这块暗 (全远 ✗) 每个位置算: 5×5 区域里 25 个像素值 × 5×5 放大镜里 25 个权重 → 求和 → 得到一个激活强度 匹配的位置激活强度高,不匹配的位置激活强度低
放大镜定义"上方有负权重、下方有正权重" → 在"上远下近"的位置激活强

3.2 32 个放大镜并行扫一遍,产生 32 张激活图

32 个放大镜各扫一遍 → 32 张激活图 depth (1, 58, 87) 扫描 32 个放大镜 #1 水平边缘 #2 垂直边缘 #3 平地 共 32 个 每个 5×5、互不相同(网络学出来的) 32 张激活图 (32, 54, 83) #1: 水平边缘亮 #2: 垂直边缘亮 #3: 地面平地亮 ⋮ 共 32 张 每张图 54×83 像素 每张显示"该 pattern 在哪些位置匹配" 参数量 = 32 个放大镜 × 5×5 个权重 + 32 个 bias = 832 个 — 因为同一个放大镜在所有位置共享权重,这是 CNN 高效的根本原因
每个放大镜在整张图扫一遍,产生一张激活图。32 个放大镜 = 32 张激活图。
Conv2d 这一层做了什么

把"5046 个原始距离数字"翻译成"32 种基础视觉概念,在图中哪里出现"。这一步是 "原始像素 → 局部 pattern 检测"

放大镜里的具体数值(权重)是网络训出来的 —— 没人告诉它"放大镜 #1 该长成水平边缘检测器",是 PPO/DAgger 的梯度自动塑造出来的。

4. 第 2 层 MaxPool2d(2):缩小图,保留最强信号

MaxPool 比 Conv 简单得多 —— 它没有任何参数,纯粹是个"取最大值"的操作。每 2×2 块只保留最大值,于是图缩小一半

MaxPool2d(kernel=2):每个 2×2 块取最大值 激活图(4×4 示意) 1 3 2 1 0 2 1 0 4 5 0 4 1 2 3 1 每块取 max 缩小后 (2×2) 3 2 5 4 实际网络中:尺寸高宽各除以 2 没有参数 · 不学习 · 纯下采样
MaxPool 这一层做了什么
  • 省计算:图变小一半,后续运算量也减半
  • 平移容忍:障碍偏一两个像素,下采样后激活图几乎不变
  • 保留强信号:"只要 2×2 内有匹配点,就算这块匹配了"

5. 第 3 层 Conv2d(32→64, k=3):组合 pattern 找更复杂的概念

这一层和第 1 层结构一样,但作用对象不同:它不是在原始深度图上找 pattern,而是在前面 32 张激活图的组合上找更高级的 pattern

CNN 第 2 层:在 32 张激活图上找"组合 pattern" 输入: 32 张激活图 水平边缘 ⋯共 32 张 64 个新放大镜 每个 3×3×32 (高 × 宽 × 输入通道数) "看遍 32 张激活图的 同一个 3×3 区域" 输出: 64 张激活图 更高级概念 ⋯共 64 张 举例:可能学到的 新放大镜代表的概念 • "障碍物角" = 水平边缘 + 垂直边缘 同位置 • "完整障碍物" = 上下都有水平边缘 + 中间近 • "前方台阶" = 下半平地 + 上半近距离 CNN 的"层级抽象"特点 第 1 层 = 边缘 / 角 / 平地 等低级视觉特征 第 2 层 = 障碍角 / 完整物体 / 台阶 等组合的高级概念 层越深 → 表达越抽象(这是 CNN 之于视觉任务的本质优势)
第 2 层 Conv2d 做了什么

把"32 种基础视觉概念的空间分布"组合成"64 种更高级的概念"。CNN 越深的层 = 越抽象的语义。这就像人类视觉皮层的层级处理:V1 找边缘 → V2 找形状 → V4 找物体。

输出 shape: (64, 25, 39) —— 64 个通道,每个通道是 25×39 的激活图。

5.1 25 和 39 是怎么算出来的

尺寸演化追踪 输入 (1, 58, 87) Conv k=5 H−5+1, W−5+1 Conv1 后 (32, 54, 83) MaxPool ÷ 2 Pool 后 (32, 27, 41) Conv k=3 H−3+1, W−3+1 Conv2 后 (64, 25, 39) ★ Flatten 摊平 (62400,) 关键公式(无 padding,stride=1 的 Conv) H_out = H_in − kernel + 1 W_out = W_in − kernel + 1 高: 58 → 54 → 27 → 25 宽: 87 → 83 → 41 → 39 最后 Flatten: 64 × 25 × 39 = 62,400 个数字一字排开

6. 第 4 层 Flatten:从图像 → 一维向量

Flatten 是个纯 reshape 操作,没有参数、不学习。它把 (64, 25, 39) 的 3D 张量按顺序排成一根 62400 维的长向量。

Flatten: 3D 张量 → 1D 向量 (64, 25, 39) 3D 张量 25 × 39 的 激活图 叠 64 层 按顺序串起来 不做任何运算 (62400,) 1D 向量 第1张图 25×39 个数 = 前 975 个 第2张图 975~1950 ⋯共 64 段 总 62400 个 Flatten 是"接口转换器":CNN 输出图像状(3D),Linear 要向量状(1D),中间得有 Flatten 衔接

7. 第 5/6 层 Linear:全局信息压缩

到了 Linear 层,数据已经是 62400 维向量,**包含了所有空间位置上 64 种高级概念的激活强度**。Linear 做的事是把这些散布在各位置的信息融合成一个全局摘要

7.1 Linear(62400 → 128) 在干什么

Linear(62400 → 128): 把所有空间信息融合成 128 个全局特征 输入 62400D x[0] x[1] x[2] x[62399] × 权重矩阵 W 128 × 62400 ~800 万个 权重 = 输出 128D y[0] y[1] y[127] 每个 y[i] 的含义: y[i] = W[i,0]·x[0] + W[i,1]·x[1] + ⋯ + W[i,62399]·x[62399] + bias = 62400 个输入的 加权组合 每个 y[i] = "这 62400 个数应该按某种权重组合起来,代表某种全局意义" — 网络自己学这些权重

7.2 Linear(128 → 32) 最终压缩

再来一次 Linear,把 128 维压成 32 维。这一步是最后的"语义提炼":

Linear(128 → 32): 最终对接 Actor 的格式 128D 抽象表征 128 个抽象 高级特征 Linear(128→32) ~4k 参数 32D depth_feat ★ 32 个数字 分布式编码 地形语义 每个 32D 维度可能编码(举例): • 维度 1 ≈ 前方障碍物的高度 • 维度 2 ≈ 距离障碍物多远 • 维度 3 ≈ 左侧通路宽度 • 维度 4 ≈ 右侧通路宽度 • ⋮ (网络自定义的语义, 未必有这么清晰的人类可读含义)
MLP 这两层做了什么

把 CNN 提取出的"分布在各空间位置的高级视觉特征"(62400 维)融合压缩成对决策有用的紧凑表征(32 维)。这一步是从"视觉感知"过渡到"决策准备"的桥梁。

参数量主要集中在 Linear(62400→128) 这一层(~800 万),是整个 DepthEncoder 最重的部分。

8. RecurrentDepthBackbone:时序融合

到目前为止我们处理的都是单帧深度图。但机器人是连续运动的,多帧深度图含有时序信息(比如障碍物接近的速度),单帧丢失这部分。所以最后加一段 GRU 做时序融合。

8.1 RecurrentDepthBackbone 的内部结构

RecurrentDepthBackbone:CNN 输出 + 本体观测 → 时序融合 → 最终 latent depth_feat (32D) CNN 模块的输出 prop (31D) 本体观测 concat (63D) combination_mlp Linear(63→128) → Linear(→32) GRU 输入: 32D hidden state: 512D 输出: 512D hidden state 自循环(记忆上一时刻) output_mlp Linear(512→34) + Tanh depth_latent (32D) ★ 取前 32 维注入 Actor 三段协作 ① combination_mlp 把视觉特征 + 本体观测融合(机器人既看到障碍,也知道自己当前姿态) ② GRU 把当前帧和历史多帧的融合特征整合,得到平滑的时序表征;③ output_mlp 压回 32D

8.2 GRU 的核心思想:一个会记忆的盒子

GRU 在多帧上展开:每个时间步它"看一眼新输入 + 参考之前的记忆" t=0 t=1 t=2 t=3 t=4 (当前) GRU h_0 depth_t0 GRU h_1 depth_t1 GRU h_2 depth_t2 GRU h_3 depth_t3 GRU h_4 ★ depth_t4 输出 depth_latent "记忆" h 传给下一步 每步的 hidden state 包含"截至目前所有看过的信息" h_t = f(h_{t-1}, depth_t) 当前 hidden = 上一步 hidden + 当前输入 的非线性融合 这就是 GRU 实现"时序记忆"的本质 — 隐状态在时间上滚动累积
GRU 这一段做了什么
  • 时序平滑:单帧深度的瞬态噪声(某像素跳一下)被多帧融合后平均掉
  • 运动感知:通过对比当前 vs 上一帧,隐含编码出"障碍物接近速度"
  • 记忆持续:即便某一帧深度严重受干扰,GRU hidden state 还保存着前几帧的"理解",行为不会跳变

实战中:你训练完后跑 play,机器人在某些复杂地形下能展示出"提前预判"的行为 —— 这就是 GRU 时序记忆的功劳。

9. 参数量对比:为什么不能用纯 MLP 处理深度图

有人会问:MLP 理论上能逼近任何函数,为什么处理深度图不直接用 MLP?答案是参数量爆炸 + 没有空间归纳偏置

参数量对比(log 尺度) 纯 MLP 第一层 Linear(5046→128) 645,888 个参数 CNN 第 1 层 Conv2d(1→32, k=5) 832 个参数 (32 个 5×5 放大镜 + 32 bias) CNN 第 2 层 Conv2d(32→64, k=3) 18,496 个参数 (64 个 3×3×32 放大镜 + 64 bias) 实际 Linear(62400→128) 7,987,328 个参数 关键观察: CNN 用 ~20k 参数学到 64 种空间特征,纯 MLP 用 ~645k 参数才把 5046 像素压成 128D — 而且后者效果通常更差

9.1 为什么 CNN 参数少还更有效

纯 MLP 的问题CNN 的优势
每个像素位置都有独立权重,不共享 同一个 5×5 放大镜在所有位置共享权重
把 5046 个像素当 5046 个独立数字处理,不知道哪两个像素是邻居 卷积只看局部 5×5 邻域,天然利用空间结构
图像稍微平移,激活模式完全不同,必须重新学 权重共享 + 局部感受野 → translation invariance,平移后输出几乎不变
需要海量数据才能学到"边缘"这种基本概念 归纳偏置自带"局部 pattern 假设",少量数据就能学好

10. 类比:人类视觉皮层的层级处理

DepthEncoder 的"CNN + MLP + GRU"架构和人类视觉系统的处理方式高度同构。这不是巧合 —— CNN 本身就是从视觉皮层研究里得到灵感。

人类视觉系统 vs DepthEncoder 架构对照 人类视觉处理流程 视网膜 / 光感受器 接收光信号,输出像素级强度 V1(初级视觉皮层) 检测边缘、朝向、对比度 V2 / V4 把边缘组合成形状、纹理、物体 下颞叶 IT 物体识别 + 场景理解 海马 / 工作记忆 结合短时记忆,整合"刚才看到的" DepthEncoder 流程 RayCasterCamera / Depth 传感器 输出 87×58 像素的距离值 Conv2d 第 1 层 检测水平 / 垂直边缘、平地 MaxPool + Conv2d 第 2 层 组合成障碍角、台阶、整体形状 Flatten + Linear 层 融合全局信息成 32D 决策表征 GRU 时序融合 累积多帧记忆,输出最终 latent
设计哲学的同构

无论是人脑还是机器学习网络,处理高维感知信号的最有效方式都是"低级特征 → 局部组合 → 全局抽象 → 时序整合"这条流水线。这是一个被生物进化和机器学习研究双重验证的好结构。


12. 一句话回顾

做的事类比
Conv2d(1→32, k=5)在原始深度图上扫 32 种局部 patternV1 检测边缘
MaxPool2d(2)缩小图,保留强信号视觉信息粗略化
Conv2d(32→64, k=3)组合低级 pattern 成 64 种高级概念V2/V4 组合形状
Flatten把 3D 张量摊平成 1D 向量接口转换
Linear(62400→128)全局信息融合压缩高级皮层抽象
Linear(128→32)最终对接 Actor 期待格式决策准备
combination_mlp + GRU + output_mlp融合本体观测 + 时序累积 → 32D depth_latent短时记忆 + 决策

本文聚焦 DepthEncoder 的内部机制