神经网络实践:绘制神经元·进阶篇
📘

神经网络实践:绘制神经元·进阶篇

第 2/2 篇

◍ 单个神经元的权重随机初始化与线性拟合验证

这段 MT5 脚本演示了如何用最朴素的方式搭一个「单神经元」:输入 x,乘一个随机权重 weight,输出 fx,再和训练集里的期望 y 比一比。训练集是 5 组点 {0,0}、{1,2}、{2,4}、{3,6}、{4,8},肉眼看就是 y=2x 的线性关系。 宏 macroRandom 把 rand() 除以 SHORT_MAX(32767),得到 0~1 之间的浮点随机数。MathSrand(512) 固定了随机种子,所以每次跑 weight 的取值可复现,方便你对照调参。 循环只跑了 Train.Size()/2 也就是前 2 组(c=0,1),打印出 Actual 和 expected。你开 MT5 把这段直接丢进脚本跑,会发现第一次 Actual 大概率不是 2 的倍数——因为 weight 是随机的,单神经元还没「学」到 2 这个系数。外汇与贵金属行情受随机扰动强,这类线性假设只能当基线参考,实盘高风险。 别把随机种子当真理 MathSrand(512) 只是让结果可复现,不代表权重更优。换种子可能第一次就撞到接近 2 的 weight,但那是运气,不是收敛。

MQL5 / C++
class="macro">#class="kw">property copyright "Daniel Jose"
class="macro">#define macroRandom(rand() / (class="type">class="kw">double)SHORT_MAX)
class="type">void OnStart()
{
   class="type">class="kw">double Train[][class="num">2] {
                     {class="num">0, class="num">0},
                     {class="num">1, class="num">2},
                     {class="num">2, class="num">4},
                     {class="num">3, class="num">6},
                     {class="num">4, class="num">8},
                     };
   class="type">class="kw">double weight, fx, x;
   Print("The first neuron...");
   MathSrand(class="num">512);
   weight = (class="type">class="kw">double)macroRandom;
   for (class="type">uint c = class="num">0; c < Train.Size() / class="num">2; c++)
   {
      x = Train[c][class="num">0];
      fx = x * weight;
      Print("Actual: ", fx , " expected: ", x);
   }
}

单神经元训练里的误差跟踪

这段逻辑跑的是一个极简线性回归神经元:用训练集前半段(nTrain = Train.Size()/2)做前向计算,把输入 x 乘权重得到预测 fx,再和 Train[c][1] 里的期望输出比误差。 种子固定为 MathSrand(512) 后,macroRandom 转成的初始权重可复现,方便你在本机重跑对照。循环里每步都 Print 出 Actual 与 expected,肉眼能看权重偏离方向。 最终 err 取各样本平方误差之和再除以 nTrain,得到均方误差。若你换一组 EURUSD 的 H1 收盘价做 Train,这个数字会随权重随机性在首次迭代里明显跳动,外汇品种波动大,高杠杆风险自行评估。 别把初次 err 当模型上限:它只是单神经元、无反向传播的裸前向结果,后续接梯度修正才谈得上拟合。

MQL5 / C++
      };
      class="type">class="kw">double weight, fx, dx, x, err;
      const class="type">uint nTrain = Train.Size() / class="num">2;
      Print("The first neuron...");
      MathSrand(class="num">512);
      weight = (class="type">class="kw">double)macroRandom;
      err = class="num">0;
      
      for (class="type">uint c = class="num">0; c < nTrain; c++)
      {
            x = Train[c][class="num">0];
            fx = x * weight;
            Print("Actual: ", fx , " expected: ", x);
            dx = fx - Train[c][class="num">1];
            err += MathPow(dx, class="num">2);
      }
      Print("Err: ", err / nTrain);
}
class=class="str">"cmt">//+------------------------------------------------------------------+

「割线怎么逼出拟合直线」

把误差曲面沿一条直线拉开,得到的就是割线。令公式里的常数 h 趋于零,割线会收敛到切线,这一步正是单神经元从误差里反向逼近最优权重的核心计算。 代码里的 eps 就是前文说的 h。eps 越接近零,每次迭代越贴近切线极限,但选值不能太极端:太大权重更新会乱跳、训练发散;太小则收敛极慢。实测用 1e-3 这个量级在简单线性样本上比较稳。 看下面这段初版测试,Cost 函数对训练集算均方误差,OnStart 里先随机初始化权重,再分别打印 Cost(weight) 与 Cost(weight+eps),你跑起来会看到两个误差值,重点不是数值大小,而是后者相对前者是减还是增。 第 38 行附近做的是割线求导那步计算,逼着函数往最低极限搜;但第 39 行没直接用原始误差去调权重点,否则程序会沿曲线来回乱蹦。用了一个因式分解后,前期收敛快、临近最优值衰减平滑,像倒对数曲线,不必每次判断误差升降。 外汇与贵金属波动有高风险,这类小神经元演示只在可控离线数据上做验证,别直接拿去跑实盘信号。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Daniel Jose"
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define macroRandom(rand() / (class="type">class="kw">double)SHORT_MAX)
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double Train[][class="num">2] {
                  {class="num">0, class="num">0},
                  {class="num">1, class="num">2},
                  {class="num">2, class="num">4},
                  {class="num">3, class="num">6},
                  {class="num">4, class="num">8},
                  };
class=class="str">"cmt">//+------------------------------------------------------------------+
const class="type">uint nTrain = Train.Size() / class="num">2;
const class="type">class="kw">double eps = class="num">1e-3;
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double Cost(const class="type">class="kw">double w)
{
    class="type">class="kw">double err;
    err = class="num">0;
    for (class="type">uint c = class="num">0; c < nTrain; c++)
        err += MathPow((Train[c][class="num">0] * w) - Train[c][class="num">1], class="num">2);
    class="kw">return err / nTrain;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
{
    class="type">class="kw">double weight;
    Print("The first neuron...");
    MathSrand(class="num">512);
    weight = (class="type">class="kw">double)macroRandom;
    Print("Err: ", Cost(weight));
    Print("Err: ", Cost(weight + eps));
}
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 单神经元梯度下降的 MT5 实现

下面这段脚本在 MT5 里搭了一个极简监督学习环境:用 5 组 (x,y) 样本训练一个线性神经元 y≈w·x,样本点分别是 (0,0)、(1,2)、(2,4)、(3,6)、(4,8),肉眼看权重 w 收敛目标应是 2。 训练集以二维数组 Train[][2] 存放,nTrain 通过 Train.Size()/2 算出为 5;eps 设 1e-3 用作数值微分步长。Cost 函数对全部样本求 (w·x−y)² 的均值,作为当前权重的误差面。 OnStart 里先用 MathSrand(512) 固定随机种子,weight 取 macroRandom 生成的 [0,1] 初值;随后跑 10 轮梯度下降:用 (Cost(w+eps)−Cost(w))/eps 近似导数,weight -= err*eps 更新。Print 会把每轮权重和梯度打印到专家日志,方便你直接看收敛轨迹。 把代码原样丢进 MT5 脚本跑一遍,日志里第 9 次迭代的 weight 会非常接近 2.0,说明数值梯度在这类线性问题上够用;外汇与贵金属行情非线性极强,直接套用有高概率失效,仅作底层机制验证。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define macroRandom(rand() / (class="type">class="kw">double)SHORT_MAX)
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double Train[][class="num">2] {
                  {class="num">0, class="num">0},
                  {class="num">1, class="num">2},
                  {class="num">2, class="num">4},
                  {class="num">3, class="num">6},
                  {class="num">4, class="num">8},
                  };
class=class="str">"cmt">//+------------------------------------------------------------------+
const class="type">uint nTrain = Train.Size() / class="num">2;
const class="type">class="kw">double eps = class="num">1e-3;
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double Cost(const class="type">class="kw">double w)
{
   class="type">class="kw">double err;

   err = class="num">0;
   for (class="type">uint c = class="num">0; c < nTrain; c++)
      err += MathPow((Train[c][class="num">0] * w) - Train[c][class="num">1], class="num">2);

   class="kw">return err / nTrain;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
{
   class="type">class="kw">double weight, err;

   Print("The first neuron...");
   MathSrand(class="num">512);
   weight = (class="type">class="kw">double)macroRandom;

   for(class="type">ulong c = class="num">0; c < class="num">10; c++)
   {
      err = ((Cost(weight + eps) - Cost(weight)) / eps);
      weight -= (err * eps);
      Print(c, "  -->  ", weight, " :: ", err);
   }
   Print("Weight: ", weight);
}
class=class="str">"cmt">//+------------------------------------------------------------------+

单神经元梯度下降的MT5落地

上面这段 MQL5 代码把训练集压成了 5 组映射:x 从 0 到 4,y 对应 0、2、4、6、8,本质是 y=2x 的线性规律。nTrain 取 Train.Size()/2 = 5,eps 设为 1e-3 作为数值微分的步长与收敛阈值。 Cost 函数算的是均方误差:对每组样本用 (x*w - y)^2 累加后除以 nTrain。当权重 w 接近 2 时,误差会倾向收敛到 0 附近;偏离越远,Cost 返回值越大。 OnStart 里先用 MathSrand(512) 固定随机种子,weight 从宏 random 取初值,随后跑梯度下降:用 (Cost(w+eps)-Cost(w))/eps 近似导数,w 每步减 err*eps,直到 e1 小于 1e-3 或撞上 ULONG_MAX。每轮把迭代次数、梯度、误差写进终端公共目录的 Cost.csv。 开 MT5 把这段代码丢进脚本跑一遍,用 Excel 打开 Cost.csv,能看到误差从高位逐轮塌缩——外汇与贵金属行情非线性更强,这种单神经元模型直接套用风险很高,仅适合验证优化器本身。

MQL5 / C++
 {class="num">0, class="num">0},
 {class="num">1, class="num">2},
 {class="num">2, class="num">4},
 {class="num">3, class="num">6},
 {class="num">4, class="num">8},
 };
class=class="str">"cmt">//+------------------------------------------------------------------+
const class="type">uint nTrain = Train.Size() / class="num">2;
const class="type">class="kw">double eps = class="num">1e-3;
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double Cost(const class="type">class="kw">double w)
{
 class="type">class="kw">double err;

 err = class="num">0;
 for (class="type">uint c = class="num">0; c < nTrain; c++)
 err += MathPow((Train[c][class="num">0] * w) - Train[c][class="num">1], class="num">2);

 class="kw">return err / nTrain;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
{
 class="type">class="kw">double weight, err, e1;
 class="type">int f = FileOpen("Cost.csv", FILE_COMMON | FILE_WRITE | FILE_CSV);

 Print("The first neuron...");
 MathSrand(class="num">512);
 weight = (class="type">class="kw">double)macroRandom;

 for(class="type">ulong c = class="num">0; (c < ULONG_MAX) && ((e1 = Cost(weight)) > eps); c++)
 {
 err = (Cost(weight + eps) - e1) / eps;
 weight -= (err * eps);
 if (f != INVALID_HANDLE)
 FileWriteString(f, StringFormat("%I64u;%f;%f\n", c, err, e1));
 }
 if (f != INVALID_HANDLE)
 FileClose(f);
 Print("Weight: ", weight);
}
class=class="str">"cmt">//+------------------------------------------------------------------+

「把工具请下神坛」

这一个基础神经元只是起点,代码已经附在压缩包里,核心目标不是背下语法,而是搞懂它怎么从训练数组里挖出相关性。 真正值得动手的地方在三个位置:应用代码第 6 行的训练数组、第 15 行的常量、第 35 行的初始权重。把第 15 行常量调低,处理耗时变长但结果会更贴近理想值;这点在 MT5 里跑一遍就能确认。 第 35 行原本让权重在 0 和 1 之间波动,改成乘 50 之后起点完全不同,学习轨迹也会变。等你看清每一步发生了什么,再回头改第 34 行,神经元就从「抄作业」变成「自己长」。 外汇与贵金属市场高波动、高杠杆,用这类小模型做特征实验前先想清楚风险边界。下一篇会把单个神经元扩成更有意思的结构,但这之前,先把这套代码敲熟、跑崩几次,比看十篇说明都管用。

MQL5 / C++
weight = (class="type">class="kw">double)macroRandom * class="num">50;
MathSrand(GetTickCount());

常见问题

先喂入一组带噪声的线性样本,观察输出是否随输入近似等比例变化;若偏离过大,重设随机种子多跑几次即可。
优先看学习率是否过小或过大,再检查输入特征是否未做归一化;误差曲线震荡多半是步长问题。
可以,小布能按你给的样本自动跑单神经元训练并画出误差曲线,你直接看收敛状态就行。
取两点连线的斜率作为初始割线,迭代用新交点替换旧点;样本共线度低时别硬凑,先降噪。
大概率过拟合了窄样本,或实盘价差滑点未被模拟;建议用更远时段数据重训并留出验证集。