深度神经网络 (第六部分)。 神经网络分类器的融合: 引导聚合·进阶篇
(2/3)·单体 DNN 再深也扛不住样本扰动,bagging 融合才是进阶玩家的方差剪刀
单线程跑融合训练与贝叶斯超参搜索
执行训练前先把 MKL 数学库切到单线程模式,再用随机数序列 rng 初始化各常量,foreach() 每次迭代都会重置随机数发生器。迭代结束务必把 MKL 设回多线程——单线程下计算结果会略差,但能保证每次返回的融合分类器权重完全一致。 计算顺序固定为 train / predict / best / test_averaging / test_voting。无论重复多少遍(同参数),结果不变,这正是我们要优化的神经网络融合超参数。影响单体分类器品质的四个旋钮:输入预测器数量、训练样本规模、隐藏层神经元数、激活函数。 超参范围这样定:numFeature 取 3~12;r 为训练集比例,计算前除以 10;nh 隐藏层神经元 1~50;fact 为激活函数向量 Fact 的索引。适应度函数 fitnes() 接收这 4 个参数,内部训练融合、算 predict、挑 bestNN,最后平均合并最佳预测并返回 Score = mean(F1)。目标变量 Ytrain/Ytest/Ytest1 移出函数外,因为搜索中不变。 先验证函数可跑:全部计算约 9 秒出结果。接着用 10 个随机初始点、20 次迭代跑贝叶斯优化,按 Value 排历史取前 10。最优解显示预测器 8 个、样本比例 0.3、隐藏层神经元 3、激活函数 radbas——这种组合靠直觉很难凑出,说明贝叶斯搜索确实能覆盖广域模型空间。 用上述最优参数在测试集训练并测融合:挑 7 个最佳神经网络平均合并,度量明显优于任意单体网络,且可与前作最优 DNN 参数结果比肩。外汇与贵金属行情高波动,此类模型仅降低过拟合概率,实盘前请在 MT5 用历史数据复算一遍。
#----准备------------- class="macro">#library(anytime) class="macro">#library(rowr) class="macro">#library(elmNN) class="macro">#library(rBayesianOptimization) class="macro">#library(foreach) class="macro">#library(magrittr) class="macro">#library(clusterSim) #class="macro">#source(file = "FunPrepareData.R") #class="macro">#source(file = "FUN_Ensemble.R") ##---准备---- evalq({ dt <- PrepareData(Data, Open, High, Low, Close, Volume) DT <- SplitData(dt, class="num">4000, class="num">1000, class="num">500, class="num">250, start = class="num">1) pre.outl <- PreOutlier(DT$pretrain) DTcap <- CappingData(DT, impute = T, fill = T, dither = F, pre.outl = pre.outl) preproc <- PreNorm(DTcap, meth = meth) DTcap.n <- NormData(DTcap, preproc = preproc) }, env) #---数据 X------------- evalq({ list( pretrain = list( x = DTcap.n$pretrain %>% dplyr::select(-c(Data, Class)) %>% as.data.frame(), y = DTcap.n$pretrain$Class %>% as.numeric() %>% subtract(class="num">1) ), train = list( x = DTcap.n$train %>% dplyr::select(-c(Data, Class)) %>% as.data.frame(), y = DTcap.n$train$Class %>% as.numeric() %>% subtract(class="num">1) ), test = list( x = DTcap.n$val %>% dplyr::select(-c(Data, Class)) %>% as.data.frame(), y = DTcap.n$val$Class %>% as.numeric() %>% subtract(class="num">1) ), test1 = list( x = DTcap.n$test %>% dplyr::select(-c(Data, Class)) %>% as.data.frame(), y = DTcap.n$test$Class %>% as.numeric() %>% subtract(class="num">1) ) ) -> X }, env) require(clusterSim) evalq({ numFeature <- class="num">10 HINoV.Mod(x = X$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",
「特征筛选后极限学习机的集成训练与验证」
先做特征重要性排序,用随机索引跑聚类后取头部特征。输出矩阵里第5号特征得分0.600、第12号仅0.101,差距近6倍,说明 v.fatl、v.rbci、v.ftlm、rbci 等前几个指标对方向判别的贡献显著占优,而末位特征基本是噪声。 接着用极限学习机(ELM)做集成:设 n=500 个基模型、隐藏层 nh=5、激活函数 sin,按 7:3 随机切分预训练集。测试集上对每个模型做 0/1 预测后算 F1,排序取前 7 个(numEns*2+1)最优网络,头部三个 F1 分别为 0.720、0.718、0.718,证明集成里挑优比全量平均更稳。 最后在独立测试集对优选网络做等权平均,阈值 0.5 转信号。结果 Accuracy 0.75、Precision 0.723、Recall 0.739、F1 0.731——外汇与贵金属行情高杠杆高风险,该精度仅代表样本内统计倾向,实盘需防过拟合与滑点侵蚀。
evalq({ n <- class="num">500 r <- class="num">7 nh <- class="num">5 Xtrain <- X$pretrain$x[ , bestF] Ytrain <- X$pretrain$y Ens <- foreach(i = class="num">1:n, .packages = "elmNN") %do% { idx <- rminer::holdout(Ytrain, ratio = r/class="num">10, mode = "random")$tr elmtrain(x = Xtrain[idx, ], y = Ytrain[idx], nhid = nh, actfun = "sin") } }, env) evalq({ Xtest <- X$train$x[ , bestF] Ytest <- X$train$y foreach(i = class="num">1:n, .packages = "elmNN", .combine = "cbind") %do% { predict(Ens[[i]], newdata = Xtest) } -> y.pr }, env) evalq({ numEns <- class="num">3 foreach(i = class="num">1:n, .combine = "c") %do% { ifelse(y.pr[ ,i] > class="num">0.5, class="num">1, class="num">0) -> Ypred Evaluate(actual = Ytest, predicted = Ypred)$Metrics$F1 %>% mean() } -> Score Score %>% order(decreasing = TRUE) %>% head((numEns*class="num">2 + class="num">1)) -> bestNN Score[bestNN] %>% round(class="num">3) }, env) evalq({ n <- len(Ens) Xtest <- X$test$x[ , bestF] Ytest <- X$test$y foreach(i = class="num">1:n, .packages = "elmNN", .combine = "+") %:% when(i %in% bestNN) %do% { predict(Ens[[i]], newdata = Xtest)} %>% divide_by(length(bestNN)) -> ensPred ifelse(ensPred > class="num">0.5, class="num">1, class="num">0) -> ensPred Evaluate(actual = Ytest, predicted = ensPred)$Metrics[ ,class="num">2:class="num">5] %>% round(class="num">3) }, env)
◍ 集成模型在测试集上的两类投票对比
把多个 ELM 子网络的预测做集成,常用两种办法:直接对概率取平均(averaging),或让每个网络投正负票再汇总(voting)。在 test1 子集上,averaging 对类别 1 的 F1 为 0.761,而 voting 把类别 0 的 F1 推到 0.781,说明投票机制在少数类识别上倾向更稳。 具体跑法上,averaging 段先筛出 bestNN 对应的模型,用 foreach 并行 predict 后除以模型数得到 ensPred,再以 0.5 为阈值转成 0/1 标签。voting 段则把每个预测先映射成 1/-1,按样本行求和,得票数大于 0 才判为 1。 两个测试集(test 与 test1)结果不完全一致:test 上 voting 与 averaging 的各类指标几乎重合(Accuracy 均 0.745),但 test1 上 voting 的类别 0 精度从 0.716 升到 0.787。外汇与贵金属行情里这种泛化差异很常见,实盘使用前建议在 MT5 导出的多段样本上复测,高风险品种尤需警惕过拟合。
切分窗口与极端值封顶的实操细节
这段 R 脚本把原始行情序列按 4000/1000/500/250 四条长度切成 pretrain、train、val、test 四段,起点偏移设为 1。PreOutlier 先对 pretrain 做离群预检,CappingData 再对全段做缩尾封顶,impute 和 fill 都开、dither 关,避免把外汇跳空造成的极端值喂进模型。 归一化用 PreNorm 记下 meth 对应的参数,NormData 套用到所有段。整理 X 时丢弃 Data 和 Class 两列,Class 转数值再减 1,使标签从 0 起步——MT5 导出的 CSV 若带字符串类名,这一步会直接报错,先确认因子已编码。 特征筛选写死取前 10 个:HINoV.Mod 用 metric 距离、kmeans 聚类、cRAND 指数排特征重要性,head(10) 截出 bestF。若你把 numFeature 调到 20,训练耗时大致翻倍但过拟合概率可能上升,贵金属跨品种测试时尤其要手动看 stopri 分布。 训练侧起了 500 个 ELM 基模型,隐藏层 nh=5、激活函数 sin、holdout 比例 0.7,随机种子锁 12345 保证可复现。预测阶段只对 X$train 跑 bestF 子集,foreach 并行 cbind 出 500 列预测矩阵 y.pr,后续投票或阈值切割都从这张表走。
DT <- SplitData(dt, class="num">4000, class="num">1000, class="num">500, class="num">250, start = class="num">1) pre.outl <- PreOutlier(DT$pretrain) DTcap <- CappingData(DT, impute = T, fill = T, dither = F, pre.outl = pre.outl) preproc <- PreNorm(DTcap, meth = meth) DTcap.n <- NormData(DTcap, preproc = preproc) #--class="num">1-Data X------------- list( pretrain = list( x = DTcap.n$pretrain %>% dplyr::select(-c(Data, Class)) %>% as.data.frame(), y = DTcap.n$pretrain$Class %>% as.numeric() %>% subtract(class="num">1) ), train = list( x = DTcap.n$train %>% dplyr::select(-c(Data, Class)) %>% as.data.frame(), y = DTcap.n$train$Class %>% as.numeric() %>% subtract(class="num">1) ), test = list( x = DTcap.n$val %>% dplyr::select(-c(Data, Class)) %>% as.data.frame(), y = DTcap.n$val$Class %>% as.numeric() %>% subtract(class="num">1) ), test1 = list( x = DTcap.n$test %>% dplyr::select(-c(Data, Class)) %>% as.data.frame(), y = DTcap.n$test$Class %>% as.numeric() %>% subtract(class="num">1) ) ) -> X #---class="num">2--bestF----------------------------------- class="macro">#require(clusterSim) numFeature <- class="num">10 HINoV.Mod(x = X$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] -> orderX orderX %>% head(numFeature) -> bestF #---class="num">3-----Train---------------------------- evalq({ Xtrain <- X$pretrain$x[ , bestF] Ytrain <- X$pretrain$y setMKLthreads(class="num">1) n <- class="num">500 r <- class="num">7 nh <- class="num">5 k <- class="num">1 rng <- RNGseq(n, class="num">12345) Ens <- foreach(i = class="num">1:n, .packages = "elmNN") %do% { rngtools::setRNG(rng[[k]]) k <- k + class="num">1 idx <- rminer::holdout(Ytrain, ratio = r/class="num">10, mode = "random")$tr elmtrain(x = Xtrain[idx, ], y = Ytrain[idx], nhid = nh, actfun = "sin") } setMKLthreads(class="num">2) }, env) #---class="num">4-----predict------------------- evalq({ Xtest <- X$train$x[ , bestF] Ytest <- X$train$y foreach(i = class="num">1:n, .packages = "elmNN", .combine = "cbind") %do% { predict(Ens[[i]], newdata = Xtest) } -> y.pr #[ ,n] }, env) #---class="num">5-----best---------------------- evalq({
「集成模型在测试集上的表现与参数边界」
把单模型按 F1 排序取前 7 个(numEns*2+1)做集成,单模型最高 F1 落在 0.723,随后 0.722、0.722、0.719 递减,说明顶部模型性能非常接近,靠堆数量边际增益有限。 平均法集成在测试集两类上 Accuracy 均为 0.75,类 1 的 F1 到 0.761;投票法 Accuracy 0.749,类 1 的 F1 0.760。两者差距极小,平均法略占优,但都没有突破 0.75 准确率天花板,外汇与贵金属行情用这类信号下单仍属高风险。 激活函数可选 sig、sin、radbas、hardlim 等 10 种,参数搜索边界定为 numFeature 3–12、r 1–10、nh 1–50、fact 1–10,进化训练样本量 n=500。直接把这些边界丢进 MT5 的 R 桥接或 Python 脚本里跑一遍,能验证极限配置下集成是否还会卡在 0.75 附近。
numEns <- class="num">3 foreach(i = class="num">1:n, .combine = "c") %do% { ifelse(y.pr[ ,i] > class="num">0.5, class="num">1, class="num">0) -> Ypred Evaluate(actual = Ytest, predicted = Ypred)$Metrics$F1 %>% mean() } -> Score Score %>% order(decreasing = TRUE) %>% head((numEns*class="num">2 + class="num">1)) -> bestNN Score[bestNN] %>% round(class="num">3) # [class="num">1] class="num">0.723 class="num">0.722 class="num">0.722 class="num">0.719 class="num">0.716 class="num">0.714 class="num">0.713 n <- len(Ens) Xtest <- X$test$x[ , bestF] Ytest <- X$test$y foreach(i = class="num">1:n, .packages = "elmNN", .combine = "+") %:% when(i %in% bestNN) %do% { predict(Ens[[i]], newdata = Xtest)} %>% divide_by(length(bestNN)) -> ensPred ifelse(ensPred > class="num">0.5, class="num">1, class="num">0) -> ensPred Evaluate(actual = Ytest, predicted = ensPred)$Metrics[ ,class="num">2:class="num">5] %>% round(class="num">3) # Accuracy Precision Recall F1 # class="num">0 class="num">0.75 class="num">0.711 class="num">0.770 class="num">0.739 # class="num">1 class="num">0.75 class="num">0.790 class="num">0.734 class="num">0.761 n <- len(Ens) Xtest <- X$test$x[ , bestF] Ytest <- X$test$y foreach(i = class="num">1:n, .packages = "elmNN", .combine = "cbind") %:% when(i %in% bestNN) %do% { predict(Ens[[i]], newdata = Xtest) } %>% apply(class="num">2, function(x) ifelse(x > class="num">0.5, class="num">1, -class="num">1)) %>% apply(class="num">1, function(x) sum(x)) -> vot ifelse(vot > class="num">0, class="num">1, class="num">0) -> ClVot Evaluate(actual = Ytest, predicted = ClVot)$Metrics[ ,class="num">2:class="num">5] %>% round(class="num">3) # Accuracy Precision Recall F1 # class="num">0 class="num">0.749 class="num">0.711 class="num">0.761 class="num">0.735 # class="num">1 class="num">0.749 class="num">0.784 class="num">0.738 class="num">0.760 Fact <- c("sig", "sin", "radbas", "hardlim", "hardlims", "satlins", "tansig", "tribas", "poslin", "purelin") bonds <- list( numFeature = c(3L, 12L), r = c(1L, 10L), nh <- c(1L, 50L), fact = c(1L, 10L) ) Ytrain <- X$pretrain$y Ytest <- X$train$y Ytest1 <- X$test$y n <- class="num">500 numEns <- class="num">3 fitnes <- function(numFeature, r, nh, fact){ bestF <- orderX %>% head(numFeature) Xtrain <- X$pretrain$x[ , bestF] setMKLthreads(class="num">1) k <- class="num">1 rng <- RNGseq(n, class="num">12345) }