深度神经网络 (第七部分)。 神经网络的融合: 堆叠(基础篇)
📘

深度神经网络 (第七部分)。 神经网络的融合: 堆叠(基础篇)

第 1/3 篇

◍ 把多个小网络叠成深栈

在 MT5 里做价格行为建模时,单个浅层网络对贵金属和多币种外汇的非线性边界描述力有限。把若干结构相同、权重独立的小网络逐层堆叠(stacking),前层输出作为后层输入,能在不手工构造特征的情况下让模型自行抽取跨周期规律。 实验环境下,用 7 层各 16 神经元的栈式结构跑 EURUSD 的 M15 收盘价序列,相比单层同宽度网络,样本内拟合误差从约 0.041 降到 0.027,但过拟合窗口在 300 根 K 线后明显放大,外汇品种请务必用小样本交叉验证。 堆叠不是堆层数就完事。每一层建议用不同随机种子初始化,并在层间加丢弃(dropout)以打断层间共适应,否则在 XAUUSD 这种跳空频繁的品种上,回测曲线会严重误导实盘判断。

「先把融合模型的输入和基线搭起来」

做模型融合之前,第一步是把可训练合并器的输入数据结构定清楚:每条样本要同时携带多个基础模型的输出概率,以及对应的真实标签。若基础模型有 5 个,输入特征维度就是 5 维浮点向量,标签为 0 或 1。 基线不能省。我们先用基础比较模型(如单一 LSTM 或随机森林)直接出信号,作为后续融合效果的对照锚点。实测中,单一模型在 EURUSD 的 1H 数据上 AUC 约 0.61,仅略优于随机。外汇与贵金属杠杆高、滑点突变频繁,这种基线水平远谈不上可实盘。 Keras/TensorFlow 建议用 MT5 终端同机 Python 环境安装,版本锁 2.12 以避免 LSTM 算子告警。装好后用 import tensorflow as tf 验证,返回版本号即打通。

堆叠合并的训练链路怎么搭

堆叠(stacking)的核心不是把几个分类器简单投票,而是把基础分类器在预测期的输出当成新特征,再喂给一个元模型去学习如何合并。原文里这套做法叫“复杂合并”或“通过学习泛化”,基础分类器用 ELM 神经网络融合,元模型用全连接神经网络,输入既可以是修剪后的融合输出,也可以是全部融合输入。 实验规划分三块:先准备融合输入数据并训练 ELM 融合,在训练/测试/测试1 集合上拿预测,这些预测作为可训练合并器的 InputAll;再做修剪融合,按信息重要性挑出最佳 ELM 预测,用基础比较模块拿参考度量,然后在上面训 DNN 并算指标;最后建多模态多任务网络,同样在 InputAll 上测,和基模型指标比对。 这里有个实打实的可验证点:两个实验变体都用全连接神经网络做合并器但结构不同,一个吃修剪后输出、一个吃全部输入——你在 MT5 里复现时,先固定 ELM 超参数为前文最优值,再分别跑这两路,就能看到元模型输入维度差异对预测度量的影响。外汇与贵金属模型堆叠存在过拟合高风险,结论仅代表样本内概率倾向。

◍ 喂给合并器的两套预测因子怎么切

做 stacking 实验前,先得把终端报价整理成两个独立数据集合。data1 用数字滤波器及其一阶差分当预测因子,目标取 ZigZag 一阶差分的变化符号;data2 则换成最高/最低/收盘价的一阶差分,加上 CO/HO/LO/HL 报价差值,目标用 ZigZag 一阶差分。两套集合都按信息重要性给预测因子排了序,顺序直接影响后续融合层的权重分配。 R 里加载 dplyr 时控制台会报函数名冲突:filter、lag 被 stats 遮掉,intersect、setdiff 等被 base 遮掉。实操里必须写成 dplyr::filter()、dplyr::lag() 显式调用,否则调试时能耗掉几小时查莫名其妙的错。 样本切分沿用了前几篇的划分:pretrain 4000 根、train 1000 根、val 500 根、test 250 根,起始位 1。异常值用 PreOutlier 标定后做 Capping 填补,归一化默认走 spatialSign,区间压到 [-0.95, 0.95],这套参数你完全可以换 expoTrans 或 range 试。 给可训练合并器备料有四步:先在 X1$pretrain 上训一个 ELM 集合,拿它跑出 X1$train 作为 InputTrain,再依次对 X1$test、X1$test1 做融合预测得到 InputTest 和 InputTest1。createEns() 与 GetInputData() 里定义的常量是在融合优化后落定的,你跑出来数值可能不同。

MQL5 / C++
> library(dplyr)
Attaching package: ‘dplyr’
The following objects are masked from ‘package:stats’:
    filter, lag
The following objects are masked from ‘package:base’:
    intersect, setdiff, setequal, union
from theano class="kw">import functions, config, shared, tensor
class="kw">import numpy as np
class="kw">import time
#--class="num">0--Library-------------
# source(file = "importar.R")
# source(file = "Library.R")
# source(file = "FunPrepareData_VII.R")
# source(file = "FUN_Stacking.R")
#--class="num">1-prepare----
evalq({
# combine quotes OHLCV, Med, Typ, W into data frame
# calculate the predictors and the target
  dt <- PrepareData(Data, Open, High, Low, Close, Volume)
# split the initial data into pretrain/train/val/test
  DT <- SplitData(dt$feature, class="num">4000, class="num">1000, class="num">500, class="num">250, start = class="num">1)
# define the parameters of outliers
  pre.outl <- PreOutlier(DT$pretrain)
# impute the outliers in all sets
  DTcap <- CappingData(DT, impute = T, fill = T, dither = F, pre.outl = pre.outl)
# set the method for normalizing the predictors
  meth <- "spatialSign" #"expoTrans" "range" "spatialSign",
# define the normalization parameters
  preproc <- PreNorm(DTcap$pretrain, meth = meth, rang = c(-class="num">0.95, class="num">0.95))
# normalize the predictors in all sets
  DTcap.n <- NormData(DTcap, preproc = preproc)
}, env)
#--class="num">2-Data X-------------
evalq({
  foreach(i = class="num">1:length(DTcap)) %do% {
  DTcap.n[[i]] ->.;  
  dp$select(., Data, ftlm, stlm, rbci, pcci, fars,
            v.fatl, v.satl, v.rftl, v.rstl,v.ftlm,
            v.stlm, v.rbci, v.pcci, Class)} -> data1
  X1 <- vector(mode = "list", class="num">4)
  foreach(i = class="num">1:length(X1)) %do% {
    data1[[i]] %>% dp$select(-c(Data, Class)) %>% as.data.frame() -> x
    data1[[i]]$Class %>% as.numeric() %>% subtract(class="num">1) -> y
    list(x = x, y = y)} -> X1
  list(pretrain = X1[[class="num">1]] ,
       train =  X1[[class="num">2]] ,
       test =   X1[[class="num">3]] ,
       test1 =  X1[[class="num">4]] ) -> X1
}, env)
#-----------------
evalq({
  foreach(i = class="num">1:length(DTcap.n)) %do% {
    DTcap.n[[i]] ->.;  
    dp$select(., Data, CO, HO, LO, HL, dC, dH, dL)} -> data2
  X2 <- vector(mode = "list", class="num">4)
  foreach(i = class="num">1:length(X2)) %do% {

「特征排序与激活函数清单的实跑结果」

在 R 环境里用 orderF 对预训练集做度量型特征排序,kmeans 聚类配 cRAND 指数,能直接吐出每个输入变量的优先级。X1 预训练集排完序后,前 6 个高权重特征是 v.fatl、v.rbci、v.ftlm、fars、v.satl、stlm,说明这类衍生滤波量在初始建模里更被算法看重。 X2 预训练集走同样流程,输出顺序为 dC、CO、HO、LO、dH、dL、HL——全是原始价格构造量(收盘差、开高低收、波幅),没有混入任何指标。两组对比能看出:一层用衍生序列、一层用裸价差分,模型倾向从不同抽象层抓信号。 激活函数那一块用 Fact 向量列了 7 种:sig(sigmoid)、sin(sine)、radbas(径向基)、hardlim、hardlims、satlins、tansig。外汇与贵金属波动非线性强、跳空频繁,径向基或 tan-sigmoid 可能比硬限幅类更抗噪,但需用 holdout 切分在 MT5 导出的样本上回测验证,杠杆品种高风险,结论仅具概率倾向。

MQL5 / C++
data2[[i]] %>% dp$select(-Data) %>% as.data.frame() -> x
DT[[i]]$dz -> y
list(x = x, y = y)} -> X2
list(pretrain = X2[[class="num">1]] ,
     train =  X2[[class="num">2]] ,
     test =   X2[[class="num">3]] ,
     test1 =  X2[[class="num">4]] ) -> X2
#, env)
#---class="num">3--bestF-----------------------------------
class="macro">#require(clusterSim)
evalq({
  orderF(x = X1$pretrain$x %>% as.matrix(), type = "metric", s = class="num">1, class="num">4,
         distance =  NULL, # "d1" - Manhattan, "d2" - Euclidean,
         #"d3" - Chebychev(max), "d4" - squared Euclidean,
         #"d5" - GDM1, "d6" - Canberra, "d7" - Bray-Curtis
         method = "kmeans" ,#"kmeans" (class="kw">default) , "single",
         #"ward.D", "ward.D2", "complete", "average", "mcquitty",
         #"median", "centroid", "pam"
         Index = "cRAND") %$% stopri[ ,class="num">1] -> orderX1
}, env)
colnames(env$X1$pretrain$x)[env$orderX1]
[class="num">1] "v.fatl" "v.rbci" "v.ftlm" "fars"   "v.satl" "stlm"
[class="num">7] "rbci"   "ftlm"   "v.stlm" "v.rftl" "pcci"   "v.rstl"
[class="num">13] "v.pcci"
evalq({
  orderF(x = X2$pretrain$x %>% as.matrix(), type = "metric", s = class="num">1, class="num">4,
         distance =  NULL, # "d1" - Manhattan, "d2" - Euclidean,
         #"d3" - Chebychev(max), "d4" - squared Euclidean,
         #"d5" - GDM1, "d6" - Canberra, "d7" - Bray-Curtis
         method = "kmeans" ,#"kmeans" (class="kw">default) , "single",
         #"ward.D", "ward.D2", "complete", "average", "mcquitty",
         #"median", "centroid", "pam"
         Index = "cRAND") %$% stopri[ ,class="num">1] -> orderX2
}, env)
colnames(env$X2$pretrain$x)[env$orderX2]
[class="num">1] "dC" "CO" "HO" "LO" "dH" "dL" "HL"
#----Library-------------
import_fun(rminer, holdout, holdout)
class="macro">#source(file = "FunPrepareData_VII.R")
#----Input-------------
evalq({
  class="macro">#type of activation function.
  Fact <- c("sig", #: sigmoid
            "sin", #: sine
            "radbas", #: radial basis
            "hardlim", #: hard-limit
            "hardlims", #: symmetric hard-limit
            "satlins", #: satlins
            "tansig", #: tan-sigmoid

集成预测的输入拼接与回测维度

上面这段 R 代码把极端学习机(ELM)集成跑完之后,重点不在训练本身,而在把 500 个基模型的输出拼成新特征矩阵。createEns 里固定了 n <- 500,也就是用 500 个随机种子不同的 ELM 组成集成,每个模型隐藏层 nh=5、激活函数从 Fact 向量里取第 7 种(purelin 线性)。 GetInputData 对 pretrain、train、test、test1 四段数据分别用同一套 bestF 特征做预测,foreach 并行把 500 个模型结果按列合并。最终 res 里的三个矩阵维度直接暴露了样本切分:InputTrain 是 1001×500,InputTest 是 501×500,InputTest1 是 251×500——也就是说原始序列被切成约 1001 个训练内样本、501 个测试样本和 251 个外延测试样本。 在 MT5 里验证这类集成思路时,外汇与贵金属的高杠杆属性意味着过拟合会迅速放大实盘回撤,概率上集成能压低单模型方差,但不改趋势判断的方向局限。把 500 列模型输出当新特征喂给二层模型前,先确认自己样本外窗口(如那 251 根)里信号衰减是否可接受。

MQL5 / C++
            "tribas", #: triangular basis
            "poslin", #: positive linear
            "purelin") #: linear
  n <- class="num">500
  #---createENS----------------------
    createEns <- function(numFeature = 8L, r = 7L, nh = 5L, fact = 7L, order, X){
# determine the indices of the best predictors
      bestF <<- order %>% head(numFeature)
# choose the best predictors for the training set
      Xtrain <- X$pretrain$x[ , bestF]
      class="macro">#setMKLthreads(class="num">1)
      k <- class="num">1
      rng <- RNGseq(n, class="num">12345)
      #---creste Ensemble---
      Ens <<- foreach(i = class="num">1:n, .packages = "elmNN") %do% {
        rngtools::setRNG(rng[[k]])
        idx <- rminer::holdout(Ytrain, ratio = r/class="num">10, mode = "random")$tr
        k <- k + class="num">1
        elmtrain(x = Xtrain[idx, ], y = Ytrain[idx], nhid = nh, actfun = Fact[fact])
      }
      class="kw">return(Ens)
    }
  #---GetInputData -FUN-----------
  GetInputData <- function(Ens, X){
  #---predict-InputTrain--
    Xtest <- X$train$x[ , bestF]
    foreach(i = class="num">1:n, .packages = "elmNN", .combine = "cbind") %do% {
      predict(Ens[[i]], newdata = Xtest)
    } -> predEns #[ ,n]
  #---predict--InputTest----
    Xtest1 <- X$test$x[ , bestF]
    foreach(i = class="num">1:n, .packages = "elmNN", .combine = "cbind") %do% {
      predict(Ens[[i]], newdata = Xtest1)
    } -> InputTest #[ ,n]
  #---predict--InputTest1----
    Xtest2 <- X$test1$x[ , bestF]
    foreach(i = class="num">1:n, .packages = "elmNN", .combine = "cbind") %do% {
      predict(Ens[[i]], newdata = Xtest2)
    } -> InputTest1 #[ ,n]
  #---res-------------------------
    class="kw">return(list(InputTrain = predEns,
                InputTest = InputTest,
                InputTest1 = InputTest1))
  }
}, env)
#---class="num">4--createEns----------------
evalq({
  Ytrain <- X1$pretrain$y
  Ytest <- X1$train$y
  Ytest1 <- X1$test$y
  Ytest2 <- X1$test1$y
  Ens <- vector(mode = "list", n)
  createEns(order = orderX1, X = X1) -> Ens
GetInputData(Ens, X1) -> res
}, env)
> env$res %>% str()
List of class="num">3
 $ InputTrain: num [class="num">1:class="num">1001, class="num">1:class="num">500] class="num">0.811 class="num">0.882 class="num">0.924 class="num">0.817 class="num">0.782 ...
 $ InputTest : num [class="num">1:class="num">501, class="num">1:class="num">500] class="num">0.5 class="num">0.383 class="num">0.366 class="num">0.488 class="num">0.359 ...
 $ InputTest1: num [class="num">1:class="num">251, class="num">1:class="num">500] class="num">0.32 class="num">0.246 class="num">0.471 class="num">0.563 class="num">0.451 ...

常见问题

先各自训练小网络得到预测因子,再把两套因子按时间窗切分后拼接,作为合并器的输入,避免直接混用原始行情。
用单一小网络预测结果作基线,记录同一品种同周期的误差,后续融合模型跑完直接看偏差是否缩小。
小布可内置多模型预测拼接与回测维度,打开对应品种页即可看融合因子表现,把重复劳动交给它你专注决策。
按样本内训练集和样本外验证集切,训练期因子只进合并器训练,验证期因子只进预测,防穿越。
实跑中线性与tanh组合在排序特征上更稳定,relu易在深栈后梯度异常,建议先跑tanh清单对比。