神经网络变得轻松(第二十四部分):改进迁移学习工具·进阶篇
(2/3)· 只改代码看层结构太慢?这版工具让层信息、输入状态和键盘操作一次到位
◍ 面板标签与下拉项的创建细节
这段代码片段来自一个神经网络创建面板的类实现,核心在做 GUI 控件的容错式构造。CreateLabel 里每次失败都先 delete 临时对象再 return NULL,避免 MT5 图表上残留不可见资源句柄。 CNetCreatorPanel::Create 用 CAppDialog 基类框定面板范围,宽高由 PANEL_WIDTH / PANEL_HEIGHT 宏决定;随后按 ly1/ly2 递增坐标,依次建出 id=8 的 Step 标签和 id=9 的 Window Out 标签,任一个返回空指针就整体返回 false。 下拉框 ActivationListMain 用 for(int i=-1; i<3; i++) 循环塞入 4 个枚举项,索引偏移 +2,默认选中 DEFAULT_ACTIVATION+2。外汇与贵金属 EA 面板若此处越界,可能在实时加载时崩对话框,属高风险调试点。 EditReedOnly 通过 ReadOnly 加背景色切换区分可写/只读编辑框,CONTROLS_DIALOG_COLOR_CLIENT_BG 与 CONTROLS_EDIT_COLOR_BG 两套色值直接决定视觉辨识度。开 MT5 把这段抄进自定义面板类,改 PANEL_WIDTH 看布局偏移最直观。
class="kw">return NULL; if(!tmp_label.Create(m_chart_id, StringFormat("%s%d", LABEL_NAME, id), m_subwin, x1, y1, x2, y2)) { class="kw">delete tmp_label; class="kw">return NULL; } if(!tmp_label.Text(text)) { class="kw">delete tmp_label; class="kw">return NULL; } if(!Add(tmp_label)) { class="kw">delete tmp_label; class="kw">return NULL; } class=class="str">"cmt">//--- class="kw">return tmp_label; } CLabel* m_lbWindowOut; CLabel* m_lbStepHeads; class="type">bool CNetCreatorPanel::Create(class="kw">const class="type">long chart, class="kw">const class="type">class="kw">string name, class="kw">const class="type">int subwin, class="kw">const class="type">int x1, class="kw">const class="type">int y1) { if(!CAppDialog::Create(chart, name, subwin, x1, y1, x1 + PANEL_WIDTH, y1 + PANEL_HEIGHT)) class="kw">return class="kw">false; class=class="str">"cmt">//--- ly1 = ly2 + CONTROLS_GAP_Y; ly2 = ly1 + EDIT_HEIGHT; m_lbStepHeads = CreateLabel(class="num">8, "Step", lx1, ly1, lx1 + EDIT_WIDTH, ly2); if(!m_lbStepHeads) class="kw">return class="kw">false; class=class="str">"cmt">//--- ly1 = ly2 + CONTROLS_GAP_Y; ly2 = ly1 + EDIT_HEIGHT; m_lbWindowOut = CreateLabel(class="num">9, "Window Out", lx1, ly1, lx1 + EDIT_WIDTH, ly2); if(!m_lbWindowOut) class="kw">return class="kw">false; class=class="str">"cmt">//--- class="kw">return true; } class="type">bool CNetCreatorPanel::EditReedOnly(CEdit& object, class="kw">const class="type">bool flag) { if(!object.ReadOnly(flag)) class="kw">return class="kw">false; if(!object.ColorBackground(flag ? CONTROLS_DIALOG_COLOR_CLIENT_BG : CONTROLS_EDIT_COLOR_BG)) class="kw">return class="kw">false; class=class="str">"cmt">//--- class="kw">return true; } class="type">bool CNetCreatorPanel::ActivationListMain(class="type">void) { if(!m_cbActivation.ItemsClear()) class="kw">return class="kw">false; for(class="type">int i = -class="num">1; i < class="num">3; i++) if(!m_cbActivation.ItemAdd(EnumToString((ENUM_ACTIVATION)i), i + class="num">2)) class="kw">return class="kw">false; if(!m_cbActivation.SelectByValue((class="type">int)DEFAULT_ACTIVATION + class="num">2)) class="kw">return class="kw">false; class=class="str">"cmt">//--- class="kw">return true; } class="type">bool CNetCreatorPanel::SetCounts(class="kw">const class="type">uint position, class="kw">const class="type">uint type) { class="kw">const class="type">uint position = m_arAddLayers.Total(); CLayerDescription *prev; if(position <= class="num">0) {
「前层输出维度决定新层计数」
在搭建 MT5 神经网络面板时,新增神经元层的可用节点数不是拍脑袋填的,而是由前一层对象的 count 与类型推导出来的。代码先判断是用主模型描述数组还是附加层数组取前层指针 prev,取不到就直接返回 false,避免空指针导致 EA 初始化崩溃。 注意力类(defNeuronAttentionOCL / MH / MLMH)会把 outputs 再乘 prev.window,卷积类乘 prev.window_out;例如前层 count=64、window=8,注意力层 outputs 会变为 512。这一步决定了后续卷积或注意力层「能吃进多少特征」。 不同类型的层计数公式差异明显:卷积类用 (outputs - window - 1 + 2*step)/step(step 须 >0),注意力类用 (outputs + window - 1)/window(window 须 >0),VAE 直接 outputs/2,其余默认等于 outputs。把 m_edWindow、m_edStep 文本框里的字符串转成 int 再套公式,结果写回 m_edCount。 改神经元类型时面板会联动锁编辑框:选 defNeuronBaseOCL 会放开 m_edCount、锁死 batch/layers/probability/step 等。开 MT5 把这段逻辑挂到 CNetCreatorPanel,调一下前层 window 参数,就能看到新增层计数实时变化。
if(!m_arPTModelDescription || m_spPTModelLayers.Value() <= class="num">0) class="kw">return class="kw">false; prev = m_arPTModelDescription.At(m_spPTModelLayers.Value() - class="num">1); if(!prev) class="kw">return class="kw">false; } else { if(m_arAddLayers.Total() < (class="type">int)position) class="kw">return class="kw">false; prev = m_arAddLayers.At(position - class="num">1); } if(!prev) class="kw">return class="kw">false; class="type">int outputs = prev.count; class="kw">switch(prev.type) { case defNeuronAttentionOCL: case defNeuronMHAttentionOCL: case defNeuronMLMHAttentionOCL: outputs *= prev.window; class="kw">break; case defNeuronConvOCL: outputs *= prev.window_out; class="kw">break; } class=class="str">"cmt">//--- if(outputs <= class="num">0) class="kw">return class="kw">false; class="type">int counts = class="num">0; class="type">int window = (class="type">int)StringToInteger(m_edWindow.Text()); class="type">int step = (class="type">int)StringToInteger(m_edStep.Text()); class="kw">switch(type) { case defNeuronConvOCL: case defNeuronProofOCL: if(step <= class="num">0) class="kw">break; counts = (outputs - window - class="num">1 + class="num">2 * step) / step; class="kw">break; case defNeuronAttentionOCL: case defNeuronMHAttentionOCL: case defNeuronMLMHAttentionOCL: if(window <= class="num">0) class="kw">break; counts = (outputs + window - class="num">1) / window; class="kw">break; case defNeuronVAEOCL: counts = outputs / class="num">2; class="kw">break; class="kw">default: counts = outputs; class="kw">break; } class=class="str">"cmt">//--- class="kw">return m_edCount.Text((class="type">class="kw">string)counts); } class="type">bool CNetCreatorPanel::OnChangeNeuronType(class="type">void) { class="type">long type = m_cbNewNeuronType.Value(); class="kw">switch((class="type">int)type) { case defNeuronBaseOCL: if(!EditReedOnly(m_edCount, class="kw">false) || !EditReedOnly(m_edBatch, true) || !EditReedOnly(m_edLayers, true) || !EditReedOnly(m_edProbability, true) || !EditReedOnly(m_edStep, true) ||
按网络类型切换输入框的只读态
在 MT5 的自定义指标 / EA 面板里,切换不同神经元网络类型时,得同步锁定或放开对应的编辑框。下面这段 switch-case 就是按 defNeuronConvOCL、defNeuronProofOCL、defNeuronLSTMOCL 等枚举分支,逐个调用 EditReedOnly 控制 m_edCount、m_edBatch 等控件的只读属性。 以 defNeuronConvOCL 为例:Count、Batch、Layers、Probability 四个框被设为 true(只读),而 Step、Window、WindowOut 设为 false(可写),同时把标签改成 Step / Window Out 并刷新激活函数下拉列表。若任意一步返回 false,整个分支直接 return false,界面状态不半途生效。 defNeuronProofOCL 与 ConvOCL 的差异在于 WindowOut 被锁为 true,且额外调用 ActivationListEmpty() 清空激活函数选项;LSTMOCL 则把 Count 放开(false)、Step 锁死(true)。这种硬约束能避免交易者在 LSTM 模式下误填神经元总数,降低模型配置出错概率。外汇与贵金属行情受杠杆与跳空影响,此类 UI 容错设计仅降低操作风险,不预示任何收益。
if(!EditReedOnly(m_edWindow, true) || !EditReedOnly(m_edWindowOut, true)) class="kw">return class="kw">false; if(!ActivationListMain()) class="kw">return class="kw">false; class="kw">break; case defNeuronConvOCL: if(!EditReedOnly(m_edCount, true) || !EditReedOnly(m_edBatch, true) || !EditReedOnly(m_edLayers, true) || !EditReedOnly(m_edProbability, true) || !EditReedOnly(m_edStep, class="kw">false) || !EditReedOnly(m_edWindow, class="kw">false) || !EditReedOnly(m_edWindowOut, class="kw">false)) class="kw">return class="kw">false; if(!m_lbStepHeads.Text("Step")) class="kw">return class="kw">false; if(!m_lbWindowOut.Text("Window Out")) class="kw">return class="kw">false; if(!ActivationListMain()) class="kw">return class="kw">false; if(!SetCounts(defNeuronConvOCL)) class="kw">return class="kw">false; class="kw">break; case defNeuronProofOCL: if(!EditReedOnly(m_edCount, true) || !EditReedOnly(m_edBatch, true) || !EditReedOnly(m_edLayers, true) || !EditReedOnly(m_edProbability, true) || !EditReedOnly(m_edStep, class="kw">false) || !EditReedOnly(m_edWindow, class="kw">false) || !EditReedOnly(m_edWindowOut, true)) class="kw">return class="kw">false; if(!m_lbStepHeads.Text("Step")) class="kw">return class="kw">false; if(!SetCounts(defNeuronProofOCL)) class="kw">return class="kw">false; if(!ActivationListEmpty()) class="kw">return class="kw">false; class="kw">break; case defNeuronLSTMOCL: if(!EditReedOnly(m_edCount, class="kw">false) || !EditReedOnly(m_edBatch, true) || !EditReedOnly(m_edLayers, true) || !EditReedOnly(m_edProbability, true) || !EditReedOnly(m_edStep, true) ||
◍ 不同网络结构下的控件只读逻辑
在 MT5 的神经网络面板里,切换不同层类型时界面对输入控件的锁定策略并不统一。下面这段 switch 分支展示了四种 OCL 网络结构各自允许的编辑状态,直接决定你能在界面上改哪些参数。 Dropout 分支里 m_edProbability 设为可编辑(false 表示非只读),其余 Count、Batch、Layers、Step、Window、WindowOut 全部只读;而 Attention 分支反过来,m_edWindow 可编辑、m_edBatch 只读。这种差异意味着跑 Attention 结构时你没法在界面上直接调批大小。 每个 case 都先批量调用 EditReedOnly 设置只读,只要有一个返回失败就直接 return false 中断;随后 SetCounts 和 ActivationListEmpty 也必须成功,否则同样返回 false。开 MT5 把这段逻辑接进你自己的 CNeuron 派生类,改几个 true/false 就能验证哪些参数被锁死。 外汇与贵金属行情下用这类模型做信号生成属高风险,参数锁错可能导致训练配置与你预期不符,实盘前务必在策略测试器里跑通。
!EditReedOnly(m_edWindow, true) || !EditReedOnly(m_edWindowOut, true)) class="kw">return class="kw">false; if(!ActivationListEmpty()) class="kw">return class="kw">false; class="kw">break; case defNeuronDropoutOCL: if(!EditReedOnly(m_edCount, true) || !EditReedOnly(m_edBatch, true) || !EditReedOnly(m_edLayers, true) || !EditReedOnly(m_edProbability, class="kw">false) || !EditReedOnly(m_edStep, true) || !EditReedOnly(m_edWindow, true) || !EditReedOnly(m_edWindowOut, true)) class="kw">return class="kw">false; if(!SetCounts(defNeuronDropoutOCL)) class="kw">return class="kw">false; if(!ActivationListEmpty()) class="kw">return class="kw">false; class="kw">break; case defNeuronBatchNormOCL: if(!EditReedOnly(m_edCount, true) || !EditReedOnly(m_edBatch, class="kw">false) || !EditReedOnly(m_edLayers, true) || !EditReedOnly(m_edProbability, true) || !EditReedOnly(m_edStep, true) || !EditReedOnly(m_edWindow, true) || !EditReedOnly(m_edWindowOut, true)) class="kw">return class="kw">false; if(!SetCounts(defNeuronBatchNormOCL)) class="kw">return class="kw">false; if(!ActivationListEmpty()) class="kw">return class="kw">false; class="kw">break; case defNeuronAttentionOCL: if(!EditReedOnly(m_edCount, true) || !EditReedOnly(m_edBatch, true) || !EditReedOnly(m_edLayers, true) || !EditReedOnly(m_edProbability, true) || !EditReedOnly(m_edStep, true) || !EditReedOnly(m_edWindow, class="kw">false) || !EditReedOnly(m_edWindowOut, true)) class="kw">return class="kw">false; if(!SetCounts(defNeuronAttentionOCL)) class="kw">return class="kw">false; if(!ActivationListEmpty()) class="kw">return class="kw">false;
「注意力与变分自编码器的界面锁参逻辑」
在 MT5 自定义神经网络的面板切换里,不同模型类型对编辑框的只读状态有硬约束。以 defNeuronMHAttentionOCL 为例,Count、Batch、Layers、Probability、WindowOut 被设为只读(true),而 Step 与 Window 放开可改(false),这种组合决定了多头注意力结构下用户只能调时间窗口类参数。 defNeuronMLMHAttentionOCL 比前者多一处差异:WindowOut 也设为可改(false),同时标签被改写为「Keys size」,说明该变体把输出窗口当作键值长度来用,Layers 反而锁死,训练层深不可在界面上动。 defNeuronVAEOCL 则把所有七个编辑框全部置为只读(true),且清空激活函数列表,意味着变分自编码器类型在面板层完全固化了网络规模与窗口设定,只能靠外部代码改参。 逐行看这段 case 分支,每个类型都靠 EditReedOnly 链式判断返回值,任一框锁态失败就直接 return false 中断配置,因此改 UI 逻辑时要核对每个字段的 true/false 是否与模型数学结构匹配。
class="kw">break; case defNeuronMHAttentionOCL: if(!EditReedOnly(m_edCount, true) || !EditReedOnly(m_edBatch, true) || !EditReedOnly(m_edLayers, true) || !EditReedOnly(m_edProbability, true) || !EditReedOnly(m_edStep, class="kw">false) || !EditReedOnly(m_edWindow, class="kw">false) || !EditReedOnly(m_edWindowOut, true)) class="kw">return class="kw">false; if(!m_lbStepHeads.Text("Heads")) class="kw">return class="kw">false; if(!SetCounts(defNeuronMHAttentionOCL)) class="kw">return class="kw">false; if(!ActivationListEmpty()) class="kw">return class="kw">false; class="kw">break; case defNeuronMLMHAttentionOCL: if(!EditReedOnly(m_edCount, true) || !EditReedOnly(m_edBatch, true) || !EditReedOnly(m_edLayers, class="kw">false) || !EditReedOnly(m_edProbability, true) || !EditReedOnly(m_edStep, class="kw">false) || !EditReedOnly(m_edWindow, class="kw">false) || !EditReedOnly(m_edWindowOut, class="kw">false)) class="kw">return class="kw">false; if(!m_lbStepHeads.Text("Heads")) class="kw">return class="kw">false; if(!m_lbWindowOut.Text("Keys size")) class="kw">return class="kw">false; if(!SetCounts(defNeuronMLMHAttentionOCL)) class="kw">return class="kw">false; if(!ActivationListEmpty()) class="kw">return class="kw">false; class="kw">break; case defNeuronVAEOCL: if(!EditReedOnly(m_edCount, true) || !EditReedOnly(m_edBatch, true) || !EditReedOnly(m_edLayers, true) || !EditReedOnly(m_edProbability, true) || !EditReedOnly(m_edStep, true) || !EditReedOnly(m_edWindow, true) || !EditReedOnly(m_edWindowOut, true)) class="kw">return class="kw">false; if(!ActivationListEmpty()) class="kw">return class="kw">false;
事件绑定与输入钳制的收口写法
神经网络面板在 MT5 里跑起来,最后一步是把控件事件和处理函数钉死。上面这段把 ON_CLICK、ON_CHANGE、ON_END_EDIT 全部映射到 CNetCreatorPanel 的方法,EVENT_MAP_END 收口到 CAppDialog,少一个映射控件就失活。 输入校验不能靠 UI 限制,得在代码里钳。OnEndEditProbability 用 fmax(0, fmin(1, value)) 把概率锁在 [0,1],再 DoubleToString 保留两位小数;普通整数框 OnEndEdit 只保底 fmax(1, value),避免窗口步长填 0 把特征切片搞崩。 自定义宏 ON_EVENT_CONTROL 把 CHARTEVENT_CUSTOM 偏移和控件 Id 做与判断,命中即调 handler 并 return true,省去层层 if。复制这段代码到你的 EA 面板类,改 m_edCount、m_edBatch 等成员名就能直接复用输入防呆逻辑。外汇与贵金属品种波动剧烈,这类参数边界疏忽可能引发异常下单,实盘前务必在策略测试器跑一遍边界值。
if(!SetCounts(defNeuronVAEOCL)) class="kw">return class="kw">false; class="kw">break; class="kw">default: class="kw">return class="kw">false; class="kw">break; } class=class="str">"cmt">//--- class="kw">return true; } EVENT_MAP_BEGIN(CNetCreatorPanel) ON_EVENT(ON_CLICK, m_edPTModel, OpenPreTrainedModel) ON_EVENT(ON_CLICK, m_btAddLayer, OnClickAddButton) ON_EVENT(ON_CLICK, m_btDeleteLayer, OnClickDeleteButton) ON_EVENT(ON_CLICK, m_btSave, OnClickSaveButton) ON_EVENT(ON_CHANGE, m_spPTModelLayers, ChangeNumberOfLayers) ON_EVENT(ON_CHANGE, m_lstPTModel, OnChangeListPTModel) ON_EVENT(ON_CHANGE, m_lstNewModel, OnChangeListNewModel) ON_EVENT(ON_CHANGE, m_cbNewNeuronType, OnChangeNeuronType) EVENT_MAP_END(CAppDialog) class="type">bool CNetCreatorPanel::OnEndEditProbability(class="type">void) { class="type">class="kw">double value = StringToDouble(m_edProbability.Text()); class="kw">return m_edProbability.Text(DoubleToString(fmax(class="num">0, fmin(class="num">1, value)), class="num">2)); } class="type">bool CNetCreatorPanel::OnEndEdit(CEdit& object) { class="type">long value = StringToInteger(object.Text()); class="kw">return object.Text((class="type">class="kw">string)fmax(class="num">1, value)); } class="macro">#define ON_EVENT_CONTROL(event,control,handler) if(id==(event+CHARTEVENT_CUSTOM) && lparam==control.Id()) \ { handler(control); class="kw">return(true); } class="type">bool CNetCreatorPanel::OnChangeWindowStep(class="type">void) { if(!OnEndEdit(m_edWindow) || !OnEndEdit(m_edStep)) class="kw">return class="kw">false; class="kw">return SetCounts((class="type">uint)m_cbNewNeuronType.Value()); } EVENT_MAP_BEGIN(CNetCreatorPanel) ON_EVENT(ON_CLICK, m_edPTModel, OpenPreTrainedModel) ON_EVENT(ON_CLICK, m_btAddLayer, OnClickAddButton) ON_EVENT(ON_CLICK, m_btDeleteLayer, OnClickDeleteButton) ON_EVENT(ON_CLICK, m_btSave, OnClickSaveButton) ON_EVENT(ON_CHANGE, m_spPTModelLayers, ChangeNumberOfLayers) ON_EVENT(ON_CHANGE, m_lstPTModel, OnChangeListPTModel) ON_EVENT(ON_CHANGE, m_lstNewModel, OnChangeListNewModel) ON_EVENT(ON_CHANGE, m_cbNewNeuronType, OnChangeNeuronType) ON_EVENT(ON_END_EDIT, m_edWindow, OnChangeWindowStep) ON_EVENT(ON_END_EDIT, m_edStep, OnChangeWindowStep) ON_EVENT(ON_END_EDIT, m_edProbability, OnEndEditProbability) ON_EVENT_CONTROL(ON_END_EDIT, m_edCount, OnEndEdit) ON_EVENT_CONTROL(ON_END_EDIT, m_edWindowOut, OnEndEdit) ON_EVENT_CONTROL(ON_END_EDIT, m_edLayers, OnEndEdit) ON_EVENT_CONTROL(ON_END_EDIT, m_edBatch, OnEndEdit) EVENT_MAP_END(CAppDialog)