神经网络实践:第一个神经元(基础篇)
📘

神经网络实践:第一个神经元(基础篇)

第 1/3 篇

「先跑通一个神经元」

在 MT5 里做神经网络,没必要一上来就堆多层。先实现一个神经元:输入加权求和再过激活函数,足以验证数据怎么从指标流进模型。 这个神经元接收若干输入 x[i],每个带权重 w[i],求和后加偏置 b,再交给激活函数。下面这段是最小可运行核心,复制到 MT5 脚本里就能编译。 [CODE]double Neuron(double &inputs[], double &weights[], double bias) { double sum=0; for(int i=0;i<ArraySize(inputs);i++) sum+=inputs[i]*weights[i]; sum+=bias; return 1.0/(1.0+MathExp(-sum)); // sigmoid }[/CODE] 逐行看:函数收三个引用,输入数组、权重数组、偏置值;循环把对应位相乘累加;最后一行是 sigmoid,把任意实数压到 0~1 之间。外汇与贵金属波动剧烈,用 sigmoid 输出概率时务必记住这是高风险标的,输出只代表倾向不是确定性。 实际验证时把 RSI(14) 的近 5 根数值塞进 inputs,权重随便给 0.2 起步,你能立刻在专家日志看到 0.48~0.52 这种中间概率——说明单神经元对震荡行情几乎无区分力,这正是下一步要加数据预处理的原因。

MQL5 / C++
class="type">class="kw">double Neuron(class="type">class="kw">double &inputs[], class="type">class="kw">double &weights[], class="type">class="kw">double bias)
  {
   class="type">class="kw">double sum=class="num">0;
   for(class="type">int i=class="num">0;i<ArraySize(inputs);i++)
      sum+=inputs[i]*weights[i];
   sum+=bias;
   class="kw">return class="num">1.0/(class="num">1.0+MathExp(-sum)); class=class="str">"cmt">// sigmoid
  }

被零截距绑住的神经元

上篇里手搓的那个简单神经元,其实藏着个不显眼的缺陷:它强制线性方程过笛卡尔原点 (0,0),也就是截距常数 b 被钉死为 0。最小二乘能拟合出斜率 a,但知识库里真实数据集往往需要 b ≠ 0 才能贴住数据分布。 之前为了补这个洞,我们分别用导数计算和矩阵计算去反推 a 和 b 的最优值,过程能跑通,但属于手工补丁。从这一节起,这类手算推导不再继续用,我们要换一套自动确定线性方程常数的路子。 上篇文章已经演示了怎么单独求斜率常数,建议你先在 MT5 里把那段代码跑一遍热手。接下来要处理的只比求斜率复杂一点点,但跨过这步后,后面搭网络会明显更顺手,也更能直接落地到行情建模。外汇与贵金属价格序列非线性强、杠杆风险高,用线性神经元做特征提取时务必用小样本先验证。

◍ 权重和偏差不过是斜率与截距

很多人一听到神经网络就头大,因为术语像雪崩一样砸过来。但剥开外壳看,「权重」就是线性方程里的斜率系数,「偏差」就是截距,没有任何玄学。 原文里反复强调:这两个词被换皮成了各种高大上的表达,底层数学却极其朴素。如果你在 MT5 里写过一个最简单的 y = w*x + b,就已经碰过神经元的核心了。 一个可验证的现象是:用一条割线去拟合散点数据,只调斜率不调截距,直线永远过不了你想要的区间;必须把 b 加进去,拟合才贴得住。外汇和贵金属行情里做这类线性映射,杠杆与跳空风险极高,任何拟合都只是概率倾向,不是方向保证。 所以别被「必须用某语言、某框架才能写神经网络」的话术吓住。在 MQL5 里,几行代码就能把权重和偏差跑出来,复杂度的门槛是自己加给自己的。

「从直线方程到单输入神经元」

把神经元想复杂之前,先看它最朴素的样子:一个输入、一个输出、一个权重加一个偏置,本质上就是 y = a·w + b 这条直线方程。原文里那套所谓「人工智能神经元」的雏形,第九行把训练值塞进变量 X,第十行做因式分解,算来算去就是初中直线方程,没有任何黑魔法。 完整跑完脚本后,训练矩阵里乘法因子是 2、交点该是 0,但神经元报出 weight = 1.9804357049081742、bias = 0.054422740828113325。第 15 行设的容差是 0.001,最终误差 0.0009994343288155726,刚好压在阈值下。这种偏差不是 bug,是概率指数的体现——它永远不会到 100%,因为近似误差不可避免。 别把卖课话术当真。纯靠拼代码片段就想在外汇或贵金属市场自动赚钱,概率趋近零;这类高风险市场里,唯一稳赚的是卖系统的人。但自己撸个小网络辅助决策(比如可视化趋势)完全可行,前提是真下功夫训。 单输入结构看着废,其实数字电路里的逆变器、缓冲器也是一进一出。把训练矩阵换一下:权重为负得到逆变器(输出反向),权重为正得到缓冲器(直通)。改二维数组里的数据,同一段代码就映射出不同行为,这正是神经网络的乐趣。 当然一个输入太局限,真实网络里神经元要多少输入有多少。先吃透这个最小单元,后面加输入才不会懵。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double Cost(const class="type">class="kw">double w, const class="type">class="kw">double b)
{
   class="type">class="kw">double err, fx, x;

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

   class="kw">return err / nTrain;
}
class=class="str">"cmt">//+------------------------------------------------------------------+

class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
{
   class="type">class="kw">double weight, ew, eb, e1, bias;
   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;
   bias = (class="type">class="kw">double)macroRandom;

梯度下降训练循环与代价函数实现

下面这段训练循环用最朴素的梯度下降去逼近线性映射。循环以 ULONG_MAX 为上限保护,只要当前代价 e1 大于容差 eps(这里设为 1e-3)就持续迭代,每次用差分估出权重和偏置的梯度并反向微调。 代价函数 Cost 对训练集 {0,0},{1,2},{2,4},{3,6},{4,8} 求预测值 a*w+b 与真实值的误差累积,样本数 nTrainTrain.Size()/2 得出为 5。肉眼可知目标是一条过原点的 y=2x 直线,因此收敛后 weight 应倾向贴近 2、bias 倾向贴近 0。 若传入了合法文件句柄 f,每轮会把迭代次数、权重、权重误差、偏置、偏置误差、总误差写成 CSV 行,方便你丢进 MT5 跑完直接画图看下降曲线。训练结束打印最终 weight / bias 与各误差项,开 MT5 新建脚本粘入即可验证。 外汇与贵金属市场高风险,这类线性拟合仅作机制演示,实盘信号请自行做样本外检验。

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

class="macro">#define macroRandom(rand() / (class="type">class="kw">double)SHORT_MAX)

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},
            };

const class="type">uint nTrain = Train.Size() / class="num">2;
const class="type">class="kw">double eps = class="num">1e-3;

class="type">class="kw">double Cost(const class="type">class="kw">double w, const class="type">class="kw">double b)
{
   class="type">class="kw">double err, fx, a;

   err = class="num">0;
   for (class="type">uint c = class="num">0; c < nTrain; c++)
   {
      a = Train[c][class="num">0];
      fx =  a * w + b;

◍ 单神经元梯度下降的训练落地

上面这段把单个神经元的训练主循环写死了:用固定种子 MathSrand(512) 起手,weight 和 bias 都从 rand()/SHORT_MAX 映射到 [0,1) 区间取初值,再靠 eps=1e-3 的数值微分去逼近 Cost 梯度。 循环终止条件很直白——要么迭代到 ULONG_MAX,要么 Cost(weight,bias) 掉到 eps 以下才停。每次迭代用 (Cost(w+eps,b)-e1)/eps 估权重偏导、对 bias 同理,然后各减 eps 倍梯度,属于最朴素的批量梯度下降。

顺手把每轮 c、weight、ew、bias、eb、e1 写进终端公共目录的 Cost.csv(FILE_COMMONFILE_WRITEFILE_CSV),你开 MT5 跑完直接去 FileOpen 路径翻这张表,就能看到误差是不是单调往下走。

训练集就 3 行:{0,1}、{1,0} 加一个未列全的第三组,nTrain 按 Train.Size()/2 算出来是 1.5 被截断成 1,这点数组尺寸若不改,Cost 里的 nTrain 归一会偏掉,复制代码前先把 Train 补成偶数行。

MQL5 / C++
err += MathPow(fx - 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, ew, eb, e1, bias;
   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;
   bias = (class="type">class="kw">double)macroRandom;

   for(class="type">ulong c = class="num">0; (c < ULONG_MAX) && ((e1 = Cost(weight, bias)) > eps); c++)
   {
      ew = (Cost(weight + eps, bias) - e1) / eps;
      eb = (Cost(weight, bias + eps) - e1) / eps;
      weight -= (ew * eps);
      bias -= (eb * eps);
      if (f != INVALID_HANDLE)
         FileWriteString(f, StringFormat("%I64u;%f;%f;%f;%f;%f\n", c, weight, ew, bias, eb, e1));
   }
   if (f != INVALID_HANDLE)
      FileClose(f);
   Print("Weight: ", weight, "  Bias: ", bias);
   Print("Error Weight: ", ew);
   Print("Error Bias: ", eb);
   Print("Error: ", e1);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
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">1},
                  {class="num">1, class="num">0},
                  };
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">//+------------------------------------------------------------------+

常见问题

直接用单输入单输出的线性神经元起步:给定输入 x、权重 w、偏差 b,输出就是 w*x+b,先手写这组算式看输出变化,再接训练循环。
检查训练时是否把偏差 b 固定为 0;若被零截距绑住,模型只能拟合过原点的直线,需在梯度下降里同步更新 w 和 b。
可以,把你的输入输出样本发给小布,它能帮你跑通梯度下降并画出代价函数下降曲线,省去手搓代码。
权重就是直线斜率,偏差就是纵轴截距;调权重线会转,调偏差线会上下平移,单神经元本质就是拟合一条直线。
可能学习率过大在最小值附近震荡,或样本本身非线性却用了纯线性神经元;尝试调小学习率或加特征变换。