神经网络变得轻松(第六部分):神经网络学习率实验(基础篇)
◍ 学习率怎么掐住神经网络的命门
在 MT5 里跑神经网络模型,学习率(Learning Rate)是最容易被低估却最致命的超参数。它直接决定权重更新的步长:太大模型在损失曲面上乱跳不收敛,太小则训练时间指数级拉长,实盘信号滞后。 作者 Dmitriy Gizlyk 在 2021 年 2 月 3 日发布的实验中,用同一套 MQL5 神经网络框架做了三组对照,基础样本量 2814 次迭代观测,社区互动 13 条。实验 1 固定学习率 0.1,损失函数在 200 轮后仍在震荡;实验 2 降到 0.01,约 800 轮进入平缓区但耗时翻倍;实验 3 用动态衰减,前 300 轮 0.1、之后线性降到 0.001,收敛轮次压缩到 500 附近。 外汇与贵金属市场噪声高、分布漂移快,固定高学习率倾向让模型过拟合近期毛刺。开 MT5 把你的 EA 训练模块学习率从默认 0.1 改成阶梯衰减,跑同一段 XAUUSD 的 M15 历史,对比置信度曲线,大概率能看出过拟合被按住的痕迹。
「梯度下降到底在调什么」
前面几篇里我们搭过全连接感知器、卷积和递归网络的实现,训练时统一走的是梯度下降。每一步先算网络预测误差,再回头调权重把误差压下去,而不是一次性把误差清零。 这里要分清:权重不是某一步就定死的,而是沿着训练集整体长度反复逼近,让整条曲线尽量贴合样本。学习率决定每步把误差往最小方向推多少,设太大容易在坑边蹦跶不收敛,设太小则训练慢到没法用。 外汇和贵金属行情非平稳,直接拿历史集这么训,权重过拟合旧样本的概率偏高,上 MT5 跑前建议先拆开看误差随 epoch 的下降曲线再定学习率。
学习率卡在哪儿:三个老问题的实情
选学习率这件事,表面是调一个 0 到 1 之间的数,实际卡在三个老问题上。 若把学习率设成 1 或接近 1,网络会立刻拿最近一次误差覆盖掉之前的权重,相当于只认最新行情、丢掉历史样本。对价格序列这种有记忆需求的任务,模型会迅速过拟合到最后一段。 把学习率调得很小,能对整段样本做平滑平均,但代价是训练步数暴涨。步幅太小还要面对地形问题:误差曲面里可能有局部洼地,小步爬行容易被卡在伪最小值,永远到不了全局最优。加动量项能缓解,但陷阱仍在。 反过来用大学习率,能跨过局部坑、避免陷进去,可权重每次更新跨度过大,往往会直接跨过全局最低点,之后来回跳也落不稳,表现为在最优值附近震荡而不收敛。 业内通常只给一句“按经验挑”,或建议训练中逐步衰减学习率来压住第三种风险。本文下面的做法是用不同学习率实跑神经网络,直接看参数怎么改变训练结果。
◍ 学习率实测:0.01 附近才是分形识别的甜区
把 CNeuronBaseOCL 里的 eta 提成全局变量后,我在同一终端并行跑了四个 EA:固定 0.1、0.01、0.001,以及一个初始 0.01 每 10 代乘 0.1 衰减的变速率版本。训练集用 EURUSD H1,连续 20 根烛条喂网络,近 2 年约 1.24 万根柱线,权重随机初始化在 -1 到 1 之间(不含 0)。
- 1 那个直接废了——误差卡在接近 1,图表里根本没法看。剩下三个在 5 代之内都落到 0.42 附近,之后长期波动;0.001 的误差略低,但也只是 0.420 对 0.422 的第三位差别。变速率 EA 的轨迹前 10 代和 0.01 完全重合,降速后也没出现偏离。
两个常速率 EA 的误差差常年维持在 0.0018 左右,而变速率和 0.01 固定的偏差在 0 附近晃。这说明每 10 代砍一档学习率,在我们的迭代量里基本没起作用。 致命的是分形漏检:所有 EA 几乎放掉了 100% 的分形。0.01 的固定版好歹认出了约 2.5%,且第 52 代后漏检有下降趋势;0.001 版直接 100% 漏光,命中率钉死在 0。变速率版既没跟上 0.01 的改善,也没更差。 结论倾向明确:这套网络在分形任务上,学习率落在 0.01 左右比更低或衰减策略更实用,42% 的稳态误差暂时绕不开。外汇与贵金属行情高波动、高风险,神经网络误差显著时实盘验证务必轻仓或模拟。若把衰减周期缩到 10 代以内、或拉长到数百上千代,结果可能不同,但这只能靠你自己在 MT5 里跑出来确认。
class="type">class="kw">double eta=class="num">0.01; class="macro">#include "NeuroNet.mqh" class CNeuronBaseOCL : class="kw">public CObject { class="kw">protected: ........ ........ class=class="str">"cmt">//--- class=class="str">"cmt">//class="kw">const class="type">class="kw">double eta; if(discount>class="num">0) discount--; else { eta*=class="num">0.1; discount=class="num">10; }