🤖 roboto_origin_03 Wiki
首页 / RSL-RL / CNN 观测编码与特征提取

在强化学习任务中,当智能体的观测包含图像、高度图或其他空间结构数据时,全连接网络难以有效捕获局部空间特征与平移不变性。rsl_rl 通过 ActorCriticCNN 模块为 Actor-Critic 架构引入卷积神经网络(CNN)编码器,支持混合观测输入——即同时处理一维向量观测与二维空间观测。CNN 分支将二维观测压缩为紧凑的特征向量,再与一维观测拼接后送入 MLP 策略头与价值头,从而在保持策略网络表达能力的同时,高效利用空间信息。本章聚焦于 CNN 编码器的内部结构、混合观测的数据流,以及配置与集成方式。

Sources: modules/actor_critic_cnn.py, networks/cnn.py

CNN 网络结构与设计哲学

CNN 类位于 networks/cnn.py,继承自 nn.Sequential,其核心设计目标是通过声明式配置堆叠多层卷积运算,自动处理张量维度推演,最终输出可用于 MLP 的扁平特征向量。网络支持逐层独立配置卷积核、步长、膨胀率、归一化、激活与池化,并在末尾提供可选的全局池化与展平操作。这种设计允许用户通过配置文件定义从原始图像到特征向量的完整变换,而无需手动推导每一层的张量形状。

每一层的构建遵循固定的运算顺序:卷积 → 归一化(可选)→ 激活 → 最大池化(可选)。在层间推进过程中,模块会调用 _compute_padding 自动计算保持空间对齐所需的填充量,并调用 _compute_output_dim 追踪输出高宽,确保后续层接收到正确的输入尺寸。若启用全局池化(maxavg),输出将被进一步压缩为 (B, C, 1, 1) 的形状;若启用 flatten,则最终输出为 (B, C×H×W) 的二维张量,可直接与一维观测拼接。

Sources: networks/cnn.py, networks/cnn.py

CNN 配置参数详解

下表汇总了 CNN 构造函数的全部参数及其作用。其中 kernel_sizestridedilationnormmax_pool 既可以是单个标量/布尔值(表示所有层共享同一配置),也可以是列表/元组(表示逐层独立配置)。

参数 类型 默认值 说明
input_dim tuple[int, int] 输入张量的高与宽 (H, W)
input_channels int 输入通道数,如 RGB 图像为 3
output_channels tuple/list[int] 每一层卷积的输出通道数,长度决定层数
kernel_size int / tuple/list 卷积核尺寸,可为逐层列表
stride int / tuple/list 1 卷积步长
dilation int / tuple/list 1 空洞卷积的膨胀率
padding str "none" 填充类型:nonezerosreflectreplicatecircular
norm str / tuple/list "none" 归一化类型:nonebatchlayer
activation str "elu" 激活函数名称,由 resolve_nn_activation 解析
max_pool bool / tuple/list False 每层后是否接 MaxPool2d(kernel=3, stride=2, padding=1)
global_pool str "none" 末端全局池化:nonemaxavg
flatten bool True 是否将最终特征图展平为向量

padding 设为 none 时不使用填充;设为其他模式时,_compute_padding 会依据 PyTorch 的 Conv2d 文档公式计算对称填充量,使输出尺寸保持合理的对齐关系。ActorCriticCNN 要求 CNN 输出必须为展平状态(flatten=True),否则会在初始化时抛出异常,因为 MLP 头无法接受保留空间维度的张量。

Sources: networks/cnn.py, networks/cnn.py

权重初始化策略

CNN 类提供 init_weights 方法,对所有 nn.Conv2d 层执行 Kaiming Normal 初始化kaiming_normal_),并将偏置项置零。这种初始化方式与 ReLU 及其变体(如 ELU)配合良好,有助于缓解深层卷积网络的前向传播方差爆炸问题。需要注意的是,当前 ActorCriticCNN 并未显式调用 CNN 的 init_weights,因此卷积层默认使用 PyTorch 的标准初始化;若用户在训练中发现收敛困难,可在模型构建后手动调用该接口。

Sources: networks/cnn.py

ActorCriticCNN 混合观测架构

ActorCriticCNN 继承自基础的 ActorCritic,并非重写整个策略网络,而是在基类能力之上扩展了二维观测分支。它通过 obs_groups 配置区分策略网络(policy)与价值网络(critic)所使用的观测集合,并在每个集合内部进一步识别哪些观测是二维的(形状为 B, C, H, W),哪些是一维的(形状为 B, C)。这种分层解析使得同一套代码能够同时适应纯图像输入、纯向量输入,以及两者混合的复杂场景。

在初始化阶段,ActorCriticCNN 会遍历 obs_groups["policy"]obs_groups["critic"],分别构建 actor_obs_groups_2d / actor_obs_groups_1d 与对应的 critic 分组。对于每一个二维观测组,模块会实例化一个独立的 CNN 编码器,所有编码器的扁平输出拼接后,与一维观测的拼接向量组合,形成最终进入 MLP 的联合特征表示。

Sources: modules/actor_critic_cnn.py

数据流架构图

以下 Mermaid 图展示了 ActorCriticCNN 的前向传播数据流。以策略网络为例,价值网络遵循完全对称的逻辑,但使用 critic_cnnscritic MLP。

flowchart LR
    subgraph ObsInput[观测输入 TensorDict]
        O1[1D观测组<br/>B x C]
        O2[2D观测组A<br/>B x C x H x W]
        O3[2D观测组B<br/>B x C x H x W]
    end

    subgraph ActorEncoder[Actor编码分支]
        C1[CNN_A<br/>flatten输出]
        C2[CNN_B<br/>flatten输出]
        CAT1[Concat<br/>1D + CNN特征]
        MLP[Actor MLP<br/>hidden_dims]
    end

    O1 --> CAT1
    O2 --> C1 --> CAT1
    O3 --> C2 --> CAT1
    CAT1 --> MLP --> Mean[动作均值]

actact_inference 阶段,_update_distribution 首先调用 get_actor_obs 分离出一维与二维观测,随后用 actor_cnns 对二维观测进行编码,将编码结果与一维观测在特征维度(dim=-1)上拼接,最后调用基类的 _update_distribution 计算动作分布的均值与标准差。evaluate 方法对 critic 侧执行同样的拼接逻辑,只是目标变为状态价值估计。

Sources: modules/actor_critic_cnn.py

与 ActorCritic 基类的关键差异

特性 ActorCritic(基类) ActorCriticCNN
观测维度支持 B x C 一维观测 一维 B x C 与二维 B x C x H x W 混合
观测获取方式 get_actor_obs 返回单一张量 get_actor_obs 返回 (1d_tensor, 2d_dict) 元组
归一化范围 对全部 actor/critic 观测归一化 仅对一维观测执行 EmpiricalNormalization
CNN 配置 通过 actor_cnn_cfg / critic_cnn_cfg 独立配置
多 2D 观测组 不支持 每个 2D 组拥有独立 CNN,输出拼接

基类 ActorCriticget_actor_obs 中直接按 obs_groups["policy"] 顺序拼接所有观测,并通过 actor_obs_normalizer 对整个拼接向量进行经验归一化。而 ActorCriticCNN 的重写版本将归一化限制在一维观测子集上,原因在于二维图像数据通常已通过环境侧预处理(如除以 255 或直方图归一化)达到合理数值范围,额外的逐元素经验归一化可能破坏空间结构的相对关系。

Sources: modules/actor_critic.py, modules/actor_critic_cnn.py, modules/actor_critic_cnn.py

观测分组与 CNN 配置解析

ActorCriticCNN 的灵活性建立在 obs_groups 与 CNN 配置字典的协同解析之上。obs_groups 定义了策略网络与价值网络各自关注哪些观测组,例如:

obs_groups = {
    "policy": ["proprioception", "depth_camera", "height_scan"],
    "critic": ["proprioception", "depth_camera", "height_scan", "command"]
}

在初始化时,ActorCriticCNN 检查每个观测组的张量形状:若 len(shape) == 4,则归类为二维观测;若 len(shape) == 2,则归类为一维观测。如果策略侧或价值侧不存在任何二维观测,模块会断言失败并提示用户改用 ActorCritic,这避免了不必要的 CNN 计算开销。

CNN 配置 actor_cnn_cfgcritic_cnn_cfg 支持两种写法。第一种是为每个 2D 观测组提供独立的配置字典:

actor_cnn_cfg = {
    "depth_camera": {
        "output_channels": [32, 64, 128],
        "kernel_size": [8, 4, 3],
        "stride": [4, 2, 1],
        "activation": "elu"
    },
    "height_scan": {
        "output_channels": [16, 32],
        "kernel_size": 3,
        "stride": 2
    }
}

第二种是当所有 2D 观测组共享相同结构时,直接传入单个字典,ActorCriticCNN 会自动将其复制到每个组:

actor_cnn_cfg = {
    "output_channels": [32, 64],
    "kernel_size": 3,
    "stride": 2
}
# 内部自动转换为 {"depth_camera": {...}, "height_scan": {...}}

Sources: modules/actor_critic_cnn.py, modules/actor_critic_cnn.py

配置示例与训练集成

在实际训练流程中,OnPolicyRunner 通过配置文件中的 class_name 字段解析策略类。若将 policy.class_name 设为 ActorCriticCNNOnPolicyRunner._construct_algorithm 会在构建算法时把观测样本 obs、解析后的 obs_groups 以及所有 policy_cfg 参数传入构造函数。以下为一个完整的策略配置示例:

policy:
  class_name: ActorCriticCNN
  actor_hidden_dims: [256, 256, 256]
  critic_hidden_dims: [256, 256, 256]
  activation: elu
  actor_obs_normalization: true
  critic_obs_normalization: true
  actor_cnn_cfg:
    output_channels: [32, 64, 32]
    kernel_size: [8, 4, 3]
    stride: [4, 2, 1]
    norm: none
    activation: elu
    flatten: true
  critic_cnn_cfg:
    output_channels: [32, 64, 32]
    kernel_size: [8, 4, 3]
    stride: [4, 2, 1]
    norm: none
    activation: elu
    flatten: true

需要注意的是,actor_cnn_cfgcritic_cnn_cfg 中的参数直接对应 CNN.__init__ 的命名,但无需传入 input_diminput_channels,因为这两个值由环境返回的观测张量形状自动推导。若策略侧和价值侧的二维观测组不同(例如 critic 额外接收一张全局地图),则必须为两侧分别提供完整的分组配置字典,且字典的键必须与对应 obs_groups 中的组名完全匹配。

Sources: runners/on_policy_runner.py, modules/actor_critic_cnn.py

与系统其他模块的交互

ActorCriticCNN 并非孤立组件,它与训练系统的交互关系如下: