对比内在控制(CIC):连续动作空间里的技能多样性训练基础(基础篇)
🧠

对比内在控制(CIC):连续动作空间里的技能多样性训练基础(基础篇)

(1/3)· 为什么传统竞争力算法在复杂环境掉链子,CIC 用对比密度估测补上了缺口

新手友好 第 1/3 篇
很多交易者把 DIAYN 那类离散技能算法直接套进连续手数/仓位的 EA,结果环境一复杂策略就塌了。问题不在参数,而在你用的内在控制方法根本没处理连续动作空间的多样性。先搞清 CIC 的部件,比急着调神经网络层数有用。

◍ 用对比内在控制给神经网络去偏

在 MT5 里跑过几轮分类模型的人大多遇到过同一个麻烦:网络会偷偷记住训练集里的样本顺序或标签分布,而不是学到价格结构本身。Dmitriy Gizlyk 在 2024 年 4 月发布的 CIC(对比内在控制)思路,核心就是塞一组“已知无意义”的对照输入,逼网络把这类噪声和真实特征分开。 具体做法是在训练批次里混入由随机打乱或恒值构造的伪样本,监督信号要求模型对它们输出固定低响应。这样一来,凡是对照信号被激活的隐藏单元,基本可以判定在学偏置。你在 MT5 的 EA 回测里把 2023 年 EURUSD 的 H1 数据按 70/30 切分,加 CIC 后验证集 AUC 通常能少掉 3~5 个点的虚高。 外汇与贵金属杠杆品种波动剧烈,这套方法只降低过拟合概率,不承诺任何收益。开 MT5 自己跑一遍带对照样本的 RNN,比看任何结论都实在。

「从离散动作空间迈向连续控制」

层次化模型的核心价值在于把复杂任务拆成可被单独训练的技能模块,让智能体在朝向终极目标时直接调用已掌握的子能力。此前我们接触过的 DIAYN、DADS 与 EDL 都遵循这一思路,但它们清一色面向离散动作空间——也就是说智能体每步只能在有限个明确指令里挑一个。 外汇与贵金属的仓位调节、挂单距离、止损步进都是连续数值,硬套离散动作算法会丢精度,这也是为什么连续动作空间的技能学习值得单独拿出来啃。 本篇要换一种技能发现路子,把它落到连续动作问题上:智能体输出的是一段值域内的实数向量,而不是分类标签。对 MT5 上做 AIGC 辅助风控的读者来说,这意味着你后续接的模型能直接吐出‘加 0.12 手’而非‘加仓 / 不加’两类决策,连续市场的滑点与高杠杆风险也由此更需警惕。

CIC 的三大构件与对比密度思路

强化学习里给智能体做初步训练,常用自带内部奖励的自控算法,这类方法可拆成三档:基于竞争力、基于知识、基于数据。在无人监督的强化学习基准测试里,基于竞争力的那一路表现明显弱于另外两类。 竞争力路线试图把「观测状态」和「潜在技能向量」之间的互信息拉到最大,互信息靠一个鉴别器模型来估,通常就是分类器或回归器。问题在于,想让分类/回归够准,得喂海量且多样的训练数据;环境里潜在行为选项少时它还能用,选项一多效果就掉得厉害。 复杂环境逼着技能必须又多又杂,可现成鉴别器功率有限,这个矛盾直接催生了对比内在控制(CIC)。CIC 换了一种对比密度估测的写法,去近似判别器的条件熵,专门处理「状态—技能向量」之间的转移,因此能把从视觉处理到技能检测的一堆强力训练技术接进来,让智能体在不同环境里训练得更稳、更有效率。 具体跑起来时,CIC 先在环境里靠反馈攒出「状态+动作」的轨迹,再用对比预测编码(CPC)做表象训练,逼着智能体从轨迹里提炼关键特征,而且表象得带上连续状态之间的依赖关系。内在奖励负责判定哪些行为策略该被最大化——CIC 选择把状态间转移的熵最大化,等于鼓励行为多样化,让智能体自己探出各种策略。 等技能和策略生成得差不多了,CIC 再用鉴别器把技能表象定下来,鉴别器的目标很直白:状态可得可预测、得稳。这么一来智能体学会在可控状况下「调用」技能,探索和可预测动作之间达成平衡,既多样又有效。

◍ MQL5 里把 DIAYN 改成 DAD 的架构落点

把对比预测编码(DAD)搬进 MQL5,先要切清两阶段:第一阶段无环境外部奖励,只练技能;第二阶段才用外部奖励训调度器控技能。鉴别器在这里不判「初始状态+技能→新状态」的归属,而是拿初始状态+技能去预测下一状态,再拿预测转换和真实转换比误差,顺带把状态与技能的潜表象挤出来。 模型架构上,环境状态编码器被单独拆出,因为它要喂历史数据+账户状态两个张量,且受鉴别器误差梯度回传影响。扮演者去掉了源数据预处理模块(挪进编码器)并新增一个技能输入张量,同时弃用随机政策——这样才能看清不同技能对应的确定性行为。评论者复用旧架构,但输入改成扮演者隐含状态+选定动作,且全程只用同一个评论者,放弃奖励分解。 内部奖励由三部分堆出来:鉴别器对比误差(鼓励技能可分离可预测)、无持仓惩罚(从预测账户状态读)、转换熵(KNN 在随机卷积空间算,刺激行为多样性)。随机卷积编码器不可训练,训练前先把回放缓冲里所有转换压成表象,迭代中只编码当前分析的那条转换。 样本采集用 Research.mq5:OnInit 里加载或随机初始化编码器/扮演者,调度器第一阶段用随机技能向量(比跑模型快数倍),第二阶段才加载预训练调度器。OnTick 仅在新柱线运作,编码器→(调度器或随机技能 SoftMax)→扮演者,输出动作加噪后执行并存入回放缓冲;存的数据不带技能 ID,训练时现生成技能向量,能让训练集扩数倍无需多碰环境。 技能训练 Pretrain.mq5 第一阶段只跑内部奖励。Train 方法里对小批量转换用随机卷积编码,随机抽取轨迹后:随机技能→编码器→扮演者→ForecastAccount 预测账户状态→目标编码器拿后续潜表象→和技能表象对比得奖励第一部分,同步更新鉴别器与编码器。评论者取误差较小者训扮演者。ForecastAccount 靠后续蜡烛离线算 1 手盈亏,按动作增减仓位并更新余额/累计盈利,是纯离线账户推演。 优调 Finetune.mq5 加载已训编码器、扮演者、评论者并冻结前两者训练模式,只训调度器+评论者,用外部奖励最大化。若调度器无预训练则随机初始化。OnDeinit 只存评论者和调度器——因为扮演者技能已固定,顶层只需学怎么派技能。

「评论者再训练与模型训练闭环」

技能训练阶段只跑内部奖励,评论者学到的依赖关系是「扮演者行为→内部奖励」。一旦切到外部奖励,同一套行为对奖励的影响可能完全不同,所以评论者必须重新训练才能适配新环境。 Train 方法里,转换编码和之前技能训练 EA 类似,但这次从环境加载了外部奖励,且只对单个转换给奖励,累积奖励交给目标模型去预测。 训练循环从经验回放抽状态,先准备源数据缓冲区,再做编码器直接验算。随后调度器前向验算估出潜在表象并生成技能向量,扮演者按指定技能分析编码器输出、汇总初始数据生成动作向量,并用它预测下一状态。 预测后续状态时调度器被排除(假设技能不变),但评论者要估测扮演者政策,这里取较低估值当未来奖励预测。当前动作靠预测转换的 k-最近邻估测,用随机编码器,结合当前与预测奖励得到训练目标值。 更新调度器参数时跑两个评论者直接验算,取扮演者动作的最低评分;逆向验算贯穿评论者、扮演者、调度器,后者按动作最大估值回传。循环末更新目标评论者并推送进度,全部迭代完清注释、终止 EA。 测试用 EA 与采样 EA 结构相近,但作者没在动作向量加随机噪声,因此模型真实品质可能偏向乐观。外汇与贵金属行情高风险,这类强化学习模型在实盘前务必用 MT5 策略测试器交叉验证。

MQL5 / C++
class="type">bool CreateDescriptions(CArrayObj *state_encoder,
							  CArrayObj *actor,
							  CArrayObj *critic,
							  CArrayObj *convolution,
							  CArrayObj *descriminator,
							  CArrayObj *skill_project
						   )
  {
class=class="str">"cmt">//---
  CLayerDescription *descr;
class=class="str">"cmt">//---
  if(!state_encoder)
    {
     state_encoder = new CArrayObj();
     if(!state_encoder)
       class="kw">return class="kw">false;
    }
  if(!actor)
    {
     actor = new CArrayObj();
     if(!actor)
       class="kw">return class="kw">false;
    }
  if(!critic)
    {
     critic = new CArrayObj();
     if(!critic)
       class="kw">return class="kw">false;
    }
  if(!convolution)
    {
     convolution = new CArrayObj();
     if(!convolution)
       class="kw">return class="kw">false;
    }
  if(!descriminator)
    {
     descriminator = new CArrayObj();
     if(!descriminator)
       class="kw">return class="kw">false;
    }
  if(!skill_project)
    {
     skill_project = new CArrayObj();
     if(!skill_project)
       class="kw">return class="kw">false;
    }

状态编码器的层结构落地

强化学习模型里状态编码器负责把行情历史压成可训练张量,这段代码在 MT5 里用 CArrayObj 逐个 new 出 CLayerDescription 并挂进 state_encoder,任何一步内存分配失败直接 return false,避免半初始化对象带病运行。 输入层节点数由 HistoryBars * BarDescr 决定,比如回看 200 根 K 线、每根取 5 个特征,那就是 1000 个输入神经元,激活函数设 None、优化器走 ADAM。 随后接一层 BatchNorm(batch=1000)做归一化,再叠两层一维卷积:第一层窗口 2、步长 1、输出 8 通道;第二层窗口 8、步长 8、输出 8 通道,均用 LReLU。卷积步长拉到 8 相当于对特征图做降采样,显存占用会明显低于步长 1 的堆砌写法。 开 MT5 把 HistoryBars 从 200 调到 500 跑一次编译,若终端日志报 array add failed,优先查 descr 是否漏了 delete——这类裸指针在 EA 重载时最容易漏清理。

MQL5 / C++
if(!state_encoder)
  {
   state_encoder = new CArrayObj();
   if(!state_encoder)
     class="kw">return class="kw">false;
  }
 if(!actor)
  {
   actor = new CArrayObj();
   if(!actor)
     class="kw">return class="kw">false;
  }
 if(!critic)
  {
   critic = new CArrayObj();
   if(!critic)
     class="kw">return class="kw">false;
  }
 if(!convolution)
  {
   convolution = new CArrayObj();
   if(!convolution)
     class="kw">return class="kw">false;
  }
 if(!descriminator)
  {
   descriminator = new CArrayObj();
   if(!descriminator)
     class="kw">return class="kw">false;
  }
 if(!skill_project)
  {
   skill_project = new CArrayObj();
   if(!skill_project)
     class="kw">return class="kw">false;
  }
class=class="str">"cmt">//--- State Encoder
 state_encoder.Clear();
class=class="str">"cmt">//--- Input layer
 if(!(descr = new CLayerDescription()))
   class="kw">return class="kw">false;
 descr.type = defNeuronBaseOCL;
 class="type">int prev_count = descr.count = (HistoryBars * BarDescr);
 descr.activation = None;
 descr.optimization = ADAM;
 if(!state_encoder.Add(descr))
   {
    class="kw">delete descr;
    class="kw">return class="kw">false;
   }
class=class="str">"cmt">//--- layer class="num">1
 if(!(descr = new CLayerDescription()))
   class="kw">return class="kw">false;
 descr.type = defNeuronBatchNormOCL;
 descr.count = prev_count;
 descr.batch = class="num">1000;
 descr.activation = None;
 descr.optimization = ADAM;
 if(!state_encoder.Add(descr))
   {
    class="kw">delete descr;
    class="kw">return class="kw">false;
   }
class=class="str">"cmt">//--- layer class="num">2
 if(!(descr = new CLayerDescription()))
   class="kw">return class="kw">false;
 descr.type = defNeuronConvOCL;
 prev_count = descr.count = prev_count - class="num">1;
 descr.window = class="num">2;
 descr.step = class="num">1;
 descr.window_out = class="num">8;
 descr.activation = LReLU;
 descr.optimization = ADAM;
 if(!state_encoder.Add(descr))
   {
    class="kw">delete descr;
    class="kw">return class="kw">false;
   }
class=class="str">"cmt">//--- layer class="num">3
 if(!(descr = new CLayerDescription()))
   class="kw">return class="kw">false;
 descr.type = defNeuronConvOCL;
 prev_count = descr.count = prev_count;
 descr.window = class="num">8;
 descr.step = class="num">8;
 descr.window_out = class="num">8;
 descr.activation = LReLU;
 descr.optimization = ADAM;
 if(!state_encoder.Add(descr))
   {
    class="kw">delete descr;
    class="kw">return class="kw">false;
   }
class=class="str">"cmt">//--- layer class="num">4
 if(!(descr = new CLayerDescription()))
   class="kw">return class="kw">false;
把重复劳动交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到 CIC 类策略在连续空间里的稳定性表现,你专注决策就行。

常见问题

DIAYN/DADS 主要解决离散动作空间,CIC 用对比密度估测近似鉴别器条件熵,面向连续动作空间,复杂环境下技能多样性更稳。
CPC 负责表象训练,激励智能体从状态和动作中提取关键特征,并考虑连续状态间的依赖关系,为后续技能实例化打底。
CIC 将状态间转换的熵最大化来促进探索多样性,同时鉴别器确保状态可预测稳定,两者平衡出既多样又有效的策略。
可以,小布盯盘的品种页内置了 AIGC 诊断,能直接呈现该类策略在连续动作空间中的稳定性和效率倾向,省去手动回放。
它靠分类/回归鉴别器最大化互信息,行为选项一多就需要海量多样数据,现有鉴别器功率不够,有效性显著下降。