一个做Agent的朋友前几天跟我吐槽说他的导航智能体在仿真环境里跑得好好的一换到带干扰的真实感场景就全线崩溃策略网络仿佛失忆。我问他输入是什么他说直接把摄像机原始图像压扁了喂给网络。我说问题大概率就出在这——你从来没有教过模型“怎么看世界”。这就是我今天想聊透的话题AI Agent的自监督表示学习。很多做智能体的开发者一开始都把精力砸在策略算法、奖励设计上结果真正卡住进度的往往是“表示”这一关。高维像素、多模态输入、连续决策带来的时序依赖随便一个都能把训练拖垮。自监督表示学习的作用就是让Agent不依赖昂贵的人工标注从自己的交互数据里学会提取干净、紧凑、对决策有用的特征。这篇文章我会从原理、方案选型、实操代码到踩坑记录把整套东西摊开讲。如果你正在做机器人控制、游戏AI、仿真导航或者任何一个“输入是高维观测、输出是动作序列”的系统这篇文章应该能帮你少走不少弯路。1. 内容整体设计与思路拆解1.1 AI Agent的表示困境原始观测为何难用先还原一个很典型的场景。假设你在做一个室内导航Agent输入是一路720P的RGB视频流。一帧图像就是约92万个浮点数策略网络第一层就要吃下这么大一个向量。问题是这92万个数值里真正和“该往哪走”有关的信息可能只占极小一部分剩下的是光照变化、墙面纹理、摄像头噪点、远处无关物体的抖动。把这些原始观测直接送进策略网络网络要同时做两件事一是从海量冗余里硬挑出决策相关特征二是基于这些特征学动作映射。第一个任务没人帮它它就只能靠反复试错去摸索数据效率低得吓人。我在一个项目里试过用原始像素直接训PPO跑了三百万步曲线还在原地打转换到带干扰的位置就彻底崩盘。后来我把同样的算力放在“学表示”上再做下游策略训练效果完全不一样。这就是表示学习要解决的第一个问题把高维原始观测压缩成低维、语义清晰、时间可预测的特征向量。就好比你翻译一部长篇报告不会逐字逐句对着翻一定是先提炼出核心脉络再组织语言。Agent的表示网络就扮演这个“提炼主干”的角色它把原始观测浓缩成状态向量或者状态序列供策略网络做决策。但对Agent来说这个“提炼”还有特殊要求。图像分类模型学到的特征能告诉你“这是椅子”“这是桌子”Agent却需要知道“我离椅子还有多远”“绕过桌子应该左转还是右转”。也就是说Agent的表示必须和行为、位姿、时序变化强相关而不是仅仅停留在语义标签层面。这是整个设计思路上最关键的分叉点。1.2 自监督范式在Agent场景中的定位表达完问题来说为什么偏偏是“自监督”。三个字标注贵。给图像打分类标签很贵给Agent的动作轨迹打行为标签更贵。你可能需要逐帧标注“此刻智能体正在躲避障碍”“此刻正在朝目标移动”这种标注的主观性还特别强不同的人标出来的结果可能都不一样。监督学习在这条路上走不通成本和质量双重失控。强化学习看起来是另一条路但同样有坑。奖励信号天生稀疏智能体走十步可能才收到一个“1”大部分时间它根本不知道当前状态是好是坏。指望靠这种极度稀疏的信号反推“什么样的表示是好的”样本效率低到无法接受。我在训练早期就看到过这种情况策略网络疯狂尝试各种动作但状态表示乱成一团学习系统整体进入无效震荡。自监督学习的思路完全不同不用外部标注不使用奖励信号直接从数据自身的结构里构造监督信号。对Agent来说最大的优势就是它本身就能产生大量交互数据——在模拟环境里随机探索、跟环境互动、采集轨迹这些数据天然带有时序结构先发生什么、后发生什么、采取什么动作后变成什么状态。这些结构本身就是免费且海量的监督信息。打个比方小孩认识世界不需要有人逐帧标注“这是碗这是杯子”。他反复观察、触摸、不小心打翻几次就慢慢建立了“物体稳定性”“容器属性”这些表征。Agent也一样它完全可以通过观察和交互来构建对环境的理解再把这些理解迁移到具体任务上。自监督表示学习就是把这套“先理解再行动”的机制工程化。1.3 方案设计要点任务目标决定表示形态做方案设计的时候我最深的体会是别把ImageNet预训练特征直接搬过来当Agent的表示用。静态图像的表示学习追求的是“语义判别性”它学到的特征擅长区分“猫和狗”但未必擅长区分“左边有障碍和右边有障碍”。Agent需要的是“行为导向的表示”特征里必须编码空间位置、位姿关系、时序变化趋势。我在方案设计阶段确定了表示学习的三个核心属性后续所有技术选型都围绕这三个属性来做时间平滑性相邻时刻的观测其表示应该连续变化不能剧烈跳变这样才能支撑稳定的策略学习。动作条件可预测性给定当前状态表示和动作模型应该能预测下一个状态的表示这是Agent做规划的基础能力。跨模态对齐如果系统涉及视觉、语言指令、力觉等多模态输入表示空间需要统一让信息可以在不同模态之间流通。这三个属性决定了技术路线。单纯做图像级的对比学习不够必须引入时序结构、动作条件和多模态对齐机制。这也是为什么到了实践环节很多看似成熟的自监督框架需要改造才能用在Agent上。我最终定的设计思路是用自监督对比学习作为主干构造“时序正样本对”和“动作条件预测”两个辅助任务配合跨模态对齐损失让表示同时具备语义判别性、时间连贯性和行为预测能力。这套方案在下文会逐步拆解。2. 核心细节解析与实操要点2.1 三大自监督范式的选型对比明确了需求下一步是选技术路线。现阶段自监督表示学习主流的做法可以粗分成三大类我分别说说它们的特点、优缺点和适配场景。第一类是对比式学习。核心思想很简单把同一个输入的不同视角看作“正样本对”把不同输入看作“负样本”训练网络让正样本的表示靠近、负样本的表示远离。经典做法有双分支结构把同一张图做两次随机增强得到两个视图分别通过编码器再拉近它们的一致性。这类方法的优点是表示判别性强收敛快实现相对容易缺点是强依赖负样本的数量和质量负样本太少或太相似都会导致学习效果下降。第二类是非对称结构方法。它针对对比学习的负样本依赖做了改造引入了“预测头”和“动量编码器”的概念一个分支用在线编码器另一个分支用动量编码器参数缓慢更新在线分支需要额外用一个预测器去拟合目标分支的输出。这个设计有意思的地方在于它从结构上天然防止了“表示崩塌”——即使没有负样本网络也不会把所有输入映射到同一个点。训练更稳定负样本需求量也大幅下降。第三类是重建/预测式方法。思路是把输入的一部分遮住让网络去预测缺失部分。比如遮住图像的一块区域去重建像素或者更高级一点在特征空间里预测未来帧的表示。这类方法对空间结构和时序动态的建模能力特别强很适合Agent场景因为Agent本质上就是在不断预测“下一步会发生什么”。缺点是训练成本通常比较高收敛速度偏慢。选择哪一类取决于你的场景和算力如果只想要一个通用视觉表示快速验证想法对比式最省事。如果发现训练不稳定、经常表示崩塌非对称结构是更稳的底子。如果Agent任务强依赖时序预测比如导航、操控、环境建模重建/预测式路线潜力更大值得多花算力。我最后的方案是把对比式和非对称结构结合起来对比损失保证判别性动量编码器保证稳定性在这个底子上额外加一层动作条件预测让表示具备行为相关性。2.2 损失函数中的关键细节与直觉很多人写代码的时候把损失函数一抄就过了但里面的门道值得抠一抠。以最常用的InfoNCE损失为例它的公式长这样[ \mathcal{L} -\log \frac{\exp(\text{sim}(z_i, z_j) / \tau)}{\sum_{k1}^{N} \exp(\text{sim}(z_i, z_k) / \tau)} ]分子是正样本对之间的相似度用余弦相似度分母是正样本对加上所有负样本对的相似度之和。训练目标就是让分子变大、分母变小。这个形式本质上是一个温度系数为τ的softmax分类器模型被要求从N个样本里“找出那个正样本”。温度系数τ是最值得调试的超参数之一。τ越小损失越关注相似度最高的那些负样本训练越“严厉”但太小容易让训练不稳定甚至崩溃τ越大分布越平滑所有负样本被一视同仁训练稳定但可能学不到精细结构。我自己的经验是在Agent场景里τ设到0.07到0.1之间效果都不错但如果你用非对称结构、负样本特别多可以稍微放宽到0.2。负样本的作用也值得展开说。对比学习的本质是在“推开”和“拉近”之间找平衡。负样本太少模型只需要把所有东西拉近就能把损失降下去表示会退化成一个点负样本太多模型把精力花在区分无关样本上可能忽略真正重要的语义结构。实际操作里负样本数量在4096到65536之间比较常见取决于你的显存和队列长度。非对称结构里还有一个隐藏的关键点预测器和梯度隔离。在线分支预测目标分支的输出时目标分支的梯度必须被截断否则网络会找到“两边互相配合着摆烂”的捷径。这个细节在实现上只有一两行代码但漏掉它整个结构就失去了防崩塌能力。2.3 面向智能体场景的表示学习改造静态图像里做对比学习正样本对是“同一张图的两个随机增强版本”。但这个逻辑直接搬到Agent场景会出问题——智能体的观测是连续帧相邻两帧本身就极度相似如果直接拿它们当正样本对模型很容易走捷径什么都不学只做一个时间平滑损失就能降得很低。我采用的策略是用“时间距离”来控制正样本难度。相邻帧太简单就拉大时间间隔比如隔3到5帧取一对强迫模型去理解“在这段时间里世界发生了什么变化”。更进阶一点用动作来条件化给定当前帧和智能体执行的动作让模型预测若干帧之后的表示。这比单纯对比相邻帧信息量高得多因为模型必须理解“动作如何改变状态”。跨模态对齐也是一样。如果Agent同时接收视觉和语言指令你需要让这两个模态在表示空间里“对齐”。方法可以是在训练数据里成对采样视觉观测和对应的指令描述拉近它们的表示距离。实际操作中挑战在于两种模态的信息粒度不同——语言是离散的语义标签视觉是连续的物理信号。我一般会把语言指令过一层编码器投影到与视觉相同的维度再在表示空间里做对齐。这里还有一个容易被忽略的陷阱增强策略不能照搬静态图像。我之前用过比较激进的随机裁剪增强结果模型学到了“位置不变性”把物体在图像里的空间位置信息给“增强”掉了。但Agent的核心能力恰恰是空间感知。后来我把随机裁剪的幅度大幅调小保住了位置信息表示质量立刻上了一个台阶。3. 实操过程与核心环节实现3.1 交互数据采集与增强策略配置说完了原理进入真正动手的部分。整个流程的第一步是采集数据。我用的是一个开源仿真环境Agent在里面随机探索记录下每一帧观测、执行的动作、帧间间隔和最终反馈。数据不要求“聪明”但要求覆盖度足够广如果只在初始位置附近随机游走学到的表示会严重偏向局部空间。我会在环境里随机初始化Agent的位置让它尽可能跑遍整个场景。伪代码大概是这个逻辑for episode in range(total_episodes): obs env.reset() trajectory [] for step in range(max_steps): action policy.sample() # 均匀随机采样动作 next_obs, reward, done, info env.step(action) trajectory.append({ obs: obs, action: action, next_obs: next_obs, time_step: step }) obs next_obs if done: break每一条轨迹都天然带有时间戳。训练时我会按“时间距离d”采样正样本对比如间隔4帧这样模型必须学会“经过一系列动作后状态发生了怎样的变化”而不是简单地做相邻帧平滑。数据增强方面我的经验是“适度但不过激”。在视觉通道上我通常保留颜色抖动幅度控制在0.3以下避免模型学不到颜色线索。高斯噪声增强鲁棒性但噪声方差不能太大否则掩盖真实信号。随机裁剪只做较浅的裁剪保留空间位置信息这个我在前文说过了是踩过坑之后的修正。3.2 模型结构搭建与关键模块代码模型结构上我沿用“主干编码器 投影头 动量编码器”这个经典骨架。主干编码器负责把高维观测压成特征向量投影头进一步把特征投影到对比空间。关键代码如下import torch import torch.nn as nn import torch.nn.functional as F class Encoder(nn.Module): def __init__(self, input_dim3, hidden_dim512, latent_dim256): super().__init__() # 视觉编码器输入图像/特征图输出latent self.cnn nn.Sequential( nn.Conv2d(input_dim, 32, 3, stride2, padding1), nn.ReLU(), nn.Conv2d(32, 64, 3, stride2, padding1), nn.ReLU(), nn.Conv2d(64, 128, 3, stride2, padding1), nn.ReLU(), nn.AdaptiveAvgPool2d(1), ) self.fc nn.Linear(128, hidden_dim) self.latent nn.Linear(hidden_dim, latent_dim) def forward(self, obs): feat self.cnn(obs) feat feat.flatten(1) return self.latent(F.relu(self.fc(feat)))投影头我习惯用一个两层MLP先升维到256再输出128维表示class ProjectionHead(nn.Module): def __init__(self, in_dim256, hidden_dim256, out_dim128): super().__init__() self.fc1 nn.Linear(in_dim, hidden_dim) self.fc2 nn.Linear(hidden_dim, out_dim) def forward(self, x): return F.normalize(self.fc2(F.relu(self.fc1(x))), dim-1)动量编码器的实现要特别注意参数更新方式。它不是用梯度更新而是用在线编码器的参数做指数滑动平均EMAclass MomentumEncoder(nn.Module): def __init__(self, encoder, momentum0.99): super().__init__() self.encoder encoder self.momentum momentum torch.no_grad() def update(self, online_encoder): for p_ema, p_online in zip(self.encoder.parameters(), online_encoder.parameters()): p_ema.data.mul_(self.momentum).add_((1 - self.momentum) * p_online.data)这个EMA机制最大的价值是给目标分支提供一个稳定、缓慢变化的监督来源。在线分支更新很快目标分支更新慢网络需要在“跟上老师”和“保持稳定”之间找到一个平衡点这种拉扯恰恰是防止表示崩塌的关键。3.3 训练流程配置与超参数调整训练配置方面我习惯的基准参数是批量大小256队列长度65536温度系数0.1EMA动量0.995学习率1e-4。这里有一个容易被忽视的关系EMA动量是跟训练步数挂钩的。训练越长动量应该越大让目标分支更稳定。如果你训练几百万步0.99的动量可能不够建议逐步推到0.999。对比损失的计算我用一个内存队列来维护负样本每个step把当前batch的特征推进队列同时弹出最旧的特征queue torch.randn(65536, 128).cuda() queue F.normalize(queue, dim-1) def contrastive_loss(q_online, k_momentum): # q_online: (B, D), k_momentum: (B, D) logits_pos torch.sum(q_online * k_momentum, dim-1, keepdimTrue) / tau logits_neg torch.matmul(q_online, queue.T) / tau logits torch.cat([logits_pos, logits_neg], dim1) labels torch.zeros(q_online.size(0), dtypetorch.long) return F.cross_entropy(logits, labels)训练时我除了监控对比损失本身还会额外监控一个指标表示空间的秩。做法是每500步把当前batch的表示矩阵做SVD看奇异值分布。如果只有一个大奇异值其余都趋近于零说明表示正在崩塌需要立刻干预。这个检查比单纯看loss曲线可靠得多。训练时长上一个小型仿真场景单个GPU跑6到8小时就能看到一个初步可用的表示。真正想让表示在复杂场景里稳定我建议训练量翻倍并且别急着终止——表示学习的收益往往在训练后期才充分释放前期看起来“已经收敛”的loss后面还会出现二次下降。3.4 下游策略学习的验证方法表示学得好不好光看表示本身说不清最可靠的验证方法是接到下游策略上跑一遍。我的做法是取学好的表示层把参数冻结只在表示之上训练一个PPO策略网络。这一步的目的很纯粹检验表示是否真的为策略提供了干净的决策信号。对比实验我一般做三组基线A原始像素输入不做表示学习直接训练PPO。基线B用ImageNet预训练特征输入训练PPO。实验组用自监督表示输入训练PPO。在仿真导航任务里我实测到的差距非常明显。原始像素组收敛慢且不彻底后期在复杂关卡里频繁失败ImageNet预训练组在标准场景尚可但一旦加视觉干扰性能下滑很快自监督表示组收敛速度最快最终回报也最高而且对光照变化、背景干扰的抵抗力明显更强。这里插一句我在做评估时特意加入了“干扰压力测试”改变光照、加入随机遮挡物、改变地板纹理。自监督表示的抗干扰优势正是在这种测试里体现出来的——因为它学的是环境的结构性和行为相关性特征而不是依赖表面的像素统计特征。4. 常见问题与排查技巧实录4.1 表示崩塌症状、原因与对策训练自监督表示时最臭名昭著的问题就是表示崩塌。症状很典型训练的对比损失一路下降看起来一切正常但把学到的表示拿出来可视化发现所有输入都被映射到同一个点附近不同样本的表示几乎重合完全丧失了判别性。我排查过一次崩塌问题最后定位到原因是目标分支更新太快。当时我把EMA动量设成了0.9等于目标分支基本跟着在线分支实时变化网络找到了“两边同步输出同一个常量”的捷径。后来把动量改回0.995以上的时候问题立刻缓解。如果你的项目遇到同样问题从这几个方向依次排查检查EMA动量设置不要低于0.99。检查目标分支的梯度是否被正确截断预测器如果和梯度计算搅在一起很容易造成崩塌。检查负样本数量数量过少时对比信号不足崩塌概率显著上升。4.2 增强强度与任务信息缺失的失衡自监督学习经常出现另一种“静默失败”表示不崩塌但学到的东西不对。症状是下游任务性能提升不明显甚至不如直接用原始像素。问题往往出在增强策略上。我踩过的坑是随机裁剪过强。因为Agent任务里的空间位置信息至关重要强裁剪会把“物体在视野左侧”这个关键信息直接消除模型被迫依赖“整体场景不变”来凑合倒是学会了稳定的表示但把最有用的空间线索搞丢了。诊断这个问题的方法是用一个下游探针任务用学到的表示去预测几个辅助标签比如Agent当前的位置、朝向、是否接近目标。如果表示里这些关键信息严重缺失说明增强策略或者预测任务设计有问题。一般来说增强强度应该控制在“去除噪声但不丢核心任务线索”的范围内每种增强都要单独做消融测试。4.3 与强化学习联动时的分布漂移问题表示学习一般是离线阶段先做一轮然后接在线策略训练。但这里藏着一个隐蔽的坑策略更新会改变观测分布。离线阶段采集的数据来自随机策略观测分布比较分散在线训练时策略慢慢变“聪明”Agent开始更频繁地访问某些关键区域访问分布发生偏移。表示网络是在旧分布上训练的面对新分布力不从心下游性能就会悄悄下滑。我在一个项目里就遇到过这个情况表示学习阶段验证时效果很好在线策略跑了50万步后性能反而下降了。排查后发现是分布漂移导致表示退化。现在常用的解决方案是策略训练开始后把表示层的训练关闭仅让策略网络在固定表示上适应这样至少保证表示稳定。如果还想进一步提升上限可以隔一段时间用新采集的轨迹对表示层做一轮“在线微调”但学习率要降到原来的十分之一并且只微调高层特征底层保留离线训练的成果。4.4 算力受限时的工程处理手段自监督表示学习尤其是我上面这套时序对比动作预测的组合对算力的要求不低。不是每个人都有充足的GPU集群但算力受限不等于不能做有几个工程手段可以显著降低训练开销。降低输入分辨率。对Agent来说把输入从64x64降到48x48甚至32x32表示质量下降有限但计算量大幅缩减。时序对比学习本质上不依赖超高像素细节效果差异不大。冻结Encoder只训练Projection Head。如果你已有预训练模型可以只训练投影层做对比适配训练量可能只有完整训练的十分之一。混合精度训练。在确保不漏掉梯度的情况下能省不少显存和算力。缩短轨迹长度。采样窗口从16帧降到8帧也能节省显存。我甚至试过在一张消费级显卡上跑完整流程做法是缩小batch、减小队列把训练周期拉长最终效果虽然不如大算力版本但已经足够支撑下游策略训练。自监督表示学习是典型的吃数据、吃算力、出效果的路线但工程优化空间非常充裕别被算力门槛吓退。写在最后的实操体会做表示学习做到后面我最大的感受是它不是一个“预训练完就结束”的独立阶段而是Agent整个学习闭环里的基础设施。表示的质量决定了策略能走多远也决定了系统面对新场景的泛化能力。如果让我给刚入坑的朋友一个最实在的建议我会说先别急着堆花哨的模型结构把正样本对的采样逻辑、增强强度、温度系数、EMA动量这几个基础配置调明白效果比换模型快得多。这些细节看起来不起眼每一个都直接决定表示是“学到东西”还是“白跑一趟”。希望这篇文章能帮你把这些基础扎稳少走点弯路。
阅读完成 · 觉得有帮助?