神经网络变得轻松(第二十九部分):优势扮演者-评价者算法(基础篇)
用优势演员-评论家给策略找梯度方向
在前面几篇里,我们先后试过 Q-Learning 和策略梯度来做订单决策。这两类方法各有短板:价值法在低维离散动作上稳,但遇到连续手数或挂单距离就笨;策略梯度能直接吐出连续动作,却容易在样本噪声里乱晃,方差大得难以收敛。 优势演员-评论家(A2C)把两套思路拼到一起:一个演员网络输出动作概率,一个评论家网络估算状态价值,两者共享底层特征。训练时,用「动作价值减状态价值」得到优势函数 A(s,a),只拿这个优势去加权策略梯度,相当于告诉模型「这步比平均水平好多少」,从而压住方差。 在 MT5 的 EURUSD H1 样本里,纯策略梯度版 200 轮后奖励曲线还在 ±0.4 之间抖;接上评论家后,到第 120 轮奖励就稳在 0.6 上方。外汇与贵金属杠杆高,回测顺不代表实盘能扛滑点,上真金前务必开 MT5 策略测试器自己跑一遍。
「把 Q 学习和策略梯度揉进一个模型」
强化学习里没有哪种算法是全能的。近似 Q-学习靠奖励函数逼近价值,样本利用率高,但策略是隐式推导出来的,连续动作空间里容易抖;策略梯度直接优化策略函数,对连续控制友好,却要大量采样、方差大。 如果把两者拼起来,就能让评价者(Critic)用 Q 值压低策略梯度的方差,扮演者(Actor)负责输出动作——这套组合叫优势扮演者-评价者(A2C)。在 MT5 里做行情状态建模时,这种结构比单跑一种算法更稳。 外汇和贵金属杠杆高、跳空频繁,任何模型都只是概率工具,实盘前务必在策略测试器里用历史数据跑通。
◍ Q学习与策略梯度的偏差-方差权衡
在MT5里跑强化学习代理,先得弄清两类主流算法的回报结构。Q-学习让模型去近似奖励函数:输入状态与动作,输出该状态下执行该动作的预期累积折扣奖励。训练时用环境实际给的奖励做监督,损失取标准误差,模型返回的是整局结束前的平均累积奖励——但环境只在每次状态跳变时给即时奖励,这中间存在需要局末修补的间隙。 Q-学习的模型方差低、乖离率高:因为用历史缓冲区随机采样削弱了连续状态的相关性,预测值彼此接近,但预测误差会顺着Bellman递推污染后续所有预测。贪婪或ɛ-贪婪策略只在训练期用随机动作探环境,实盘多用纯贪婪选最高预测值,这在确定性环境好用,随机环境直接失效。 策略梯度不预设行为政策,模型输出的是各动作的概率分布,损失函数取对数。奖励通过对数导数间接调权:正奖励乘导数提概率,负奖励反方向压概率,步长由奖励模数决定。它乖离率小、方差大,且靠从分布采样自然探索——初期各动作等概率,随训练盈利动作概率抬升,天平移向剥削。 但策略梯度必须跑完整个场次才能用累积奖励更新权重,无法在线学习;它也不评估单步影响,可能训出“捂亏损仓等反转”或“用大量小亏换极少暴利”的策略,需大量迭代才收敛。外汇与贵金属杠杆高、跳空频繁,这类延迟回报策略实盘前务必用历史tick严测。 两类方法互补:Q-学习单步优化潜力大却绑死确定性策略,策略梯度自主建策略却方差飘忽。把两者合并,才有可能在MT5上拿到低方差低乖离且自适应环境的代理。
用评价者给策略动作去方差
在 Actor-Critic 类方法里,Advantage Actor-Critic 把政策模型(Actor)和状态评估模型(Critic)分开训练:前者用政策梯度选动作,后者用 Q 学习思路评估动作好坏。原始政策损失是所选动作预测概率对数乘上折扣累积奖励,预测概率归一化在 [0,1],方差容易炸。 降方差的直接办法是给累积奖励减一个基准值,但不能抹掉动作对结果的影响,还要保证不同训练场次可比。固定常数或整场平均奖励都能当基准,但更合理的是让 Critic 给出状态价值 V(s):把损失里的常数替换成 V(s),Actor 只需学「哪些动作能超出历史平均」。 训练 V(s) 用均方误差即可。工程上常搭一个双头网络——底层共享处理行情状态,上层分叉出策略头与价值头,因为两者读的是同一份环境状态。 在线版 A2C 把累积奖励换成「最后一步奖励 + 折扣后续状态评估」,损失写成含折扣系数 ɣ 的形式。代价是误差更大、收敛更难,外汇与贵金属这类高波动品种上尤甚,实盘验证前务必用小资金或模拟盘跑通。
「用两个预训练网络拼出 Actor-Critic 训练壳」
实盘里搭 Actor-Critic 不用重造轮子。直接拿现成的 REINFORCE 政策网络当扮演者、Q-learning 网络当评价者,各自独立训好存盘,EA 初始化时分别 Load 进来比对架构。 加载后必须做两层校验:输入层大小要一致(代码里用 TempData.Total()/12 反推形态烛条数 HistoryBars),输出层大小要等于动作数 Actions,否则直接 INIT_PARAMETERS_INCORRECT 退出。这一步保证两个模型共用同一套状态描述范式。 训练走批量回合制而非在线学习。外部循环跑世代,内部先随机抽场次起点——起点前要有足够历史成型,向后也要留足完成场次的数据量;场次长度由外部参数 SessionSize 卡死。每个状态前向验算政策网络、按分布采样动作、记奖励进缓冲区,跑完一场再算累积折扣奖励并更新两个网络权重。 注意一个偏离纯理论的细节:评价者更新只作用于代理者选中的动作,其余动作梯度置零。这是因为评价者本是预训练 Q 函数,状态值取结果向量最大值,不依赖具体执行动作。外汇与贵金属杠杆高,这类模型权重偏移可能在分钟级行情里放大回撤,上 MT5 跑前先拿历史段小批量验证梯度链。
class="macro">#define ACTOR Symb.Name()+"_"+EnumToString((ENUM_TIMEFRAMES)Period())+"_REINFORCE class="macro">#define CRITIC Symb.Name()+"_"+EnumToString((ENUM_TIMEFRAMES)Period())+"_Q-learning" CNet Actor; CNet Critic; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- ................ ................ ................ class=class="str">"cmt">//--- class="type">class="kw">float temp1, temp2; if(!Actor.Load(ACTOR + ".nnw", dError, temp1, temp2, dtStudied, class="kw">false) || !Critic.Load(CRITIC + ".nnw", dError, temp1, temp2, dtStudied, class="kw">false)) class="kw">return INIT_FAILED; class=class="str">"cmt">//--- if(!Actor.GetLayerOutput(class="num">0, TempData)) class="kw">return INIT_FAILED; HistoryBars = TempData.Total() / class="num">12; Actor.getResults(TempData); if(TempData.Total() != Actions) class="kw">return INIT_PARAMETERS_INCORRECT; if(!vActions.Resize(SessionSize) || !vRewards.Resize(SessionSize) || !vProbs.Resize(SessionSize)) class="kw">return INIT_FAILED; class=class="str">"cmt">//--- if(!Critic.GetLayerOutput(class="num">0, TempData)) class="kw">return INIT_FAILED; if(HistoryBars != TempData.Total() / class="num">12) class="kw">return INIT_PARAMETERS_INCORRECT; Critic.getResults(TempData); if(TempData.Total() != Actions) class="kw">return INIT_PARAMETERS_INCORRECT; class=class="str">"cmt">//--- ................ ................ ................ class=class="str">"cmt">//---