神经网络实践:绘制神经元·进阶篇
◍ 单个神经元的权重随机初始化与线性拟合验证
这段 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,但那是运气,不是收敛。
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 当模型上限:它只是单神经元、无反向传播的裸前向结果,后续接梯度修正才谈得上拟合。
};
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 行没直接用原始误差去调权重点,否则程序会沿曲线来回乱蹦。用了一个因式分解后,前期收敛快、临近最优值衰减平滑,像倒对数曲线,不必每次判断误差升降。 外汇与贵金属波动有高风险,这类小神经元演示只在可控离线数据上做验证,别直接拿去跑实盘信号。
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,说明数值梯度在这类线性问题上够用;外汇与贵金属行情非线性极强,直接套用有高概率失效,仅作底层机制验证。
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,能看到误差从高位逐轮塌缩——外汇与贵金属行情非线性更强,这种单神经元模型直接套用风险很高,仅适合验证优化器本身。
{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 行,神经元就从「抄作业」变成「自己长」。 外汇与贵金属市场高波动、高杠杆,用这类小模型做特征实验前先想清楚风险边界。下一篇会把单个神经元扩成更有意思的结构,但这之前,先把这套代码敲熟、跑崩几次,比看十篇说明都管用。
weight = (class="type">class="kw">double)macroRandom * class="num">50; MathSrand(GetTickCount());