深度神经网络(第八部分)。 提高袋封融合的分类品质·进阶篇
🧠

深度神经网络(第八部分)。 提高袋封融合的分类品质·进阶篇

(2/3)· 前篇搭好 ELM 分类器群,本篇用 R 环境实测噪声过滤与超参数优化能否真正改写预测边界

偏理论 第 2/3 篇
不少交易者把神经网络融合当黑箱,直接喂入含异常报价的原始数据,却忽略了噪声样本会系统性拉偏类标签边界。本篇用实验拆开看:降噪与阈值选择到底在哪一环改变了分类度量。把数据准备和后期处理当两道工序分开调,比盲目堆网络层数更见效。

◍ pretrain 子集里的噪声样本怎么清

做分类训练时,预测因子集合的内部结构往往比目标变量复杂得多。pretrain 子集里一部分样本和目标对得上,另一部分完全错位,混进去训练会拉低模型分类品质。处理这类噪声样本通常有三招:纠正标签、直接删掉、或者单独归为一类。 用 NoiseFiltersR::GE() 跑一遍就能定位噪声。它默认 k=5、kk=3 做近邻判定,能修标签的就改,改不了的删。输出里 out$repIdx 是重标样本的索引,out$remIdx 是删除索引,out$repLab 是新标签,拿这些就能拼出四份子集。 实测在 4001 条样本的 pretrain 上跑 GE,移除 0 条、修复 819 条,占比 20.47%。也就是说每五条里就有一条标签被改过,原始集合的噪声密度不低。 删完样本后预测因子重要性排序会变,这点训练融合模型时不能忽略。最终把 origin、repaired、removed、relabeled 四个子集塞进 denoiseX1pretrain 结构,预测因子 x 相同但目标 y 各异,后续直接喂给 ELM 融合。

MQL5 / C++
#---------------------------
import_fun(NoiseFiltersR, GE, noise)
#-----------------------
evalq({
  out <- noise(x = data1[[class="num">1]] %>% dp$select(-Data))
  summary(out, explicit = TRUE)
}, env)
Filter GE applied to dataset
Call:
GE(x = data1[[class="num">1]] %>% dp$select(-Data))
Parameters:
k: class="num">5
kk: class="num">3
Results:
Number of removed instances: class="num">0 (class="num">0 %)
Number of repaired instances: class="num">819 (class="num">20.46988 %)
Explicit indexes for removed instances:
.......
> str(env$out)
List of class="num">7
$ cleanData :&class="macro">#x27;data.frame&class="macro">#x27;:    class="num">4001 obs. of  class="num">14 variables:
  ..$ ftlm  : num [class="num">1:class="num">4001] class="num">0.293 class="num">0.492 class="num">0.47 class="num">0.518 class="num">0.395 ...
  ..$ stlm  : num [class="num">1:class="num">4001] class="num">0.204 class="num">0.185 class="num">0.161 class="num">0.153 class="num">0.142 ...
  ..$ rbci  : num [class="num">1:class="num">4001] -class="num">0.0434 class="num">0.1156 class="num">0.1501 class="num">0.25 class="num">0.248 ...
  ..$ pcci  : num [class="num">1:class="num">4001] -class="num">0.0196 -class="num">0.0964 -class="num">0.4455 class="num">0.2685 -class="num">0.0349 ...
  ..$ fars  : num [class="num">1:class="num">4001] class="num">0.208 class="num">0.255 class="num">0.246 class="num">0.279 class="num">0.267 ...
  ..$ v.fatl: num [class="num">1:class="num">4001] class="num">0.4963 class="num">0.4635 class="num">0.0842 class="num">0.3707 class="num">0.0542 ...
  ..$ v.satl: num [class="num">1:class="num">4001] -class="num">0.0146 class="num">0.0248 -class="num">0.0353 class="num">0.1797 class="num">0.1205 ...
  ..$ v.rftl: num [class="num">1:class="num">4001] -class="num">0.2695 -class="num">0.0809 class="num">0.1752 class="num">0.3637 class="num">0.5305 ...
  ..$ v.rstl: num [class="num">1:class="num">4001] class="num">0.398 class="num">0.362 class="num">0.386 class="num">0.374 class="num">0.357 ...
  ..$ v.ftlm: num [class="num">1:class="num">4001] class="num">0.5244 class="num">0.4039 -class="num">0.0296 class="num">0.1088 -class="num">0.2299 ...
  ..$ v.stlm: num [class="num">1:class="num">4001] -class="num">0.275 -class="num">0.226 -class="num">0.285 -class="num">0.11 -class="num">0.148 ...
  ..$ v.rbci: num [class="num">1:class="num">4001] class="num">0.5374 class="num">0.4811 class="num">0.0978 class="num">0.2992 -class="num">0.0141 ...
  ..$ v.pcci: num [class="num">1:class="num">4001] -class="num">0.8779 -class="num">0.0706 -class="num">0.3125 class="num">0.6311 -class="num">0.2712 ...

清洗后样本结构与标签分布

对 pretrain 集做噪声滤除后,repaired 子集保留 4001 条观测、13 个特征变量(ftlm、stlm、rbci、pcci、fars 及各自的 v. 前缀衍生量),特征矩阵与原始 x 完全一致,仅 y 被重写为 0/1 二值。 repIdx 长度为 819,说明噪声过滤器标出 819 个疑似错标样本;removed 子集按该索引剔除了对应行,其 y 分布为 0 类 1509 个、1 类 1673 个。 repaired 的 y 经 table() 得到 0 类 1888、1 类 2113,relabeled 则在 removed 基础上把 819 个样本 y 置为 2(即重标而非删除),三类计数分别为 1509 / 1673 / 819。 用 orderF 对 removed$x 做度量型排序时,distance 留 NULL 会走默认 d1(曼哈顿),method 选 kmeans;若想换欧氏距离需显式传 "d2",否则聚类半径会偏保守。外汇与贵金属样本带杠杆,噪声标签误删可能放大过拟合,实盘前建议在 MT5 用同周期数据复算这 819 个 repIdx 是否落在极端波动段。

MQL5 / C++
evalq({
out <- noise(x = data1$pretrain %>% dp$select(-Data))
Yrelab <- X1$pretrain$y
Yrelab[out$repIdx] <- 2L
X1rem <- data1$pretrain[-out$repIdx, ] %>% dp$select(-Data)
denoiseX1pretrain <- list(origin = list(x = X1$pretrain$x,  y = X1$pretrain$y),
repaired = list(x = X1$pretrain$x, y = out$cleanData$Class %>%
as.numeric() %>% subtract(class="num">1)),
removed = list(x = X1rem %>% dp$select(-Class),
 y = X1rem$Class %>% as.numeric() %>% subtract(class="num">1)),
relabeled = list(x = X1$pretrain$x, y = Yrelab))
rm(out, Yrelab, X1rem)
}, env)

「用 cRAND 指标筛掉冗余特征」

上面的 R 代码段在集群环境里跑了一轮层次聚类与 PAM 的对比,用 cRAND 作为聚类一致性指数来给 13 个候选特征排座次。输出矩阵第一列是特征序号,第二列是 cRAND 值,数值越高代表该特征在多种聚类方法下越稳定。 从打印结果看,第 6 号特征 cRAND 达到 1.0790642,第 12 号也有 1.0320772,而第 13 号只剩 0.2170575,衰减非常明显。代码里用 rx1rem$stopri[,2] > 0.5 做了一道硬过滤,只保留 cRAND 大于 0.5 的特征,最终留下的 11 个特征名包括 v.fatl、v.rbci、v.satl、v.ftlm、fars、ftlm、rbci、v.stlm、v.rftl、stlm、pcci。 这套筛选对外汇与贵金属信号建模有直接用处:cRAND 低于 0.5 的特征(如第 13 号)在不同聚类算法下分歧大,拿来训练 EA 可能引入噪声。贵金属与外汇杠杆高、价格跳空频繁,低稳定度特征容易在实盘失效,建议先按 0.5 阈值砍一遍再进 MT5 回测。

MQL5 / C++
#"ward.D", "ward.D2", "complete", "average", "mcquitty",
#"median", "centroid", "pam"
Index = "cRAND") -> rx1rem
rx1rem$stopri[ ,class="num">1] -> orderX1rem
featureX1rem <- dp$filter(rx1rem$stopri %>% as.data.frame(),
                           rx1rem$stopri[ ,class="num">2] > class="num">0.5) %%%
  dp$select(V1) %>% unlist() %>% unname()
}, env)
print(env$rx1rem$stopri)
     [,class="num">1]      [,class="num">2]
[class="num">1,]    class="num">6 class="num">1.0790642
[class="num">2,]   class="num">12 class="num">1.0320772
[class="num">3,]    class="num">7 class="num">0.9629750
[class="num">4,]   class="num">10 class="num">0.9515987
[class="num">5,]    class="num">5 class="num">0.8426669
[class="num">6,]    class="num">1 class="num">0.8138830
[class="num">7,]    class="num">3 class="num">0.7934568
[class="num">8,]   class="num">11 class="num">0.7682185
[class="num">9,]    class="num">8 class="num">0.6720211
[class="num">10,]    class="num">2 class="num">0.6355753
[class="num">11,]    class="num">4 class="num">0.5159589
[class="num">12,]    class="num">9 class="num">0.3670544
[class="num">13,]   class="num">13 class="num">0.2170575
colnames(env$X1$pretrain$x)[env$featureX1rem]
[class="num">1] "v.fatl" "v.rbci" "v.satl" "v.ftlm" "fars"  "ftlm"  "rbci"  "v.stlm" "v.rftl"
[class="num">10] "stlm"  "pcci"

◍ 用四个去噪融合生成堆叠输入预测

把去噪后的初始数据喂给神经网络融合,本质是为后面的堆叠组合器准备输入因子。我们已经有 denoiseX1pretrain 集合,里面含四组训练数据:原始、校正标签、移除噪声、重新标记噪声,对应训出四个融合。 用 GetInputData() 对 X1 的 train/test/test1 三个子集分别跑预测,每个融合产出一组连续输出,最终得到 res.origin、res.repaired、res.removed、res.relab 四份分布(图例2—4)。这些预测的分布,和此前用 SpatialSign 常规化数据得到的预测明显不同,你自己换规范化方法也能看出差别。 随机数种子锁在 SEED=12345、r=7、n=500,保证融合可复现。下面这段 R 脚本就是融合创建与预测提取的核心,注意 actfun 用了 Fact[7] 即 tansig。 别把四个融合当同质模型。它们训练集噪声处理不同,连续预测落在不同区间,直接拼进组合器前最好先各自做类标签转换看分类品质。

MQL5 / C++
#--class="num">1--Input-------------
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
          "tribas", #: triangular basis
          "poslin", #: positive linear
          "purelin") #: linear
n <- class="num">500
r = 7L
SEED <- class="num">12345
#--class="num">2-createENS----------------------
createEns <- function(r = 7L, nh = 5L, fact = 7L, X, Y){
  Xtrain <- X[ , featureX1]
  k <- class="num">1
  rng <- RNGseq(n, SEED)
  #---creste Ensemble---
  Ens <- foreach(i = class="num">1:n, .packages = "elmNN") %do% {
    rngtools::setRNG(rng[[k]])
    idx <- rminer::holdout(Y, ratio = r/class="num">10, mode = "random")$tr
    k <- k + class="num">1
    elmtrain(x = Xtrain[idx, ], y = Y[idx], nhid = nh, actfun = Fact[fact])
  }
  class="kw">return(Ens)
}
#--class="num">3-GetInputData -FUN-----------
GetInputData <- function(Ens, X, Y){
  #---predict-InputPretrain--------------
  Xtrain <- X[ ,featureX1]
  k <- class="num">1
  rng <- RNGseq(n, SEED)
  #---create Ensemble---
  foreach(i = class="num">1:n, .packages = "elmNN", .combine = "cbind") %do% {
    rngtools::setRNG(rng[[k]])
    idx <- rminer::holdout(Y, ratio = r/class="num">10, mode = "random")$tr
    k <- k + class="num">1
    predict(Ens[[i]], newdata = Xtrain[-idx, ])
  } %>% unname() -> InputPretrain
  #---predict-InputTrain--
  Xtest <- X1$train$x[ , featureX1]
  foreach(i = class="num">1:n, .packages = "elmNN", .combine = "cbind") %do% {
    predict(Ens[[i]], newdata = Xtest)
  } -> InputTrain #[ ,n]
  #---predict--InputTest----
  Xtest1 <- X1$test$x[ , featureX1]

极端学习机集成的数据拼装与四套对照

这段脚本在做一件事:把极端学习机(ELM)集成对四类清洗后样本的推断结果,统一整理成后续元学习器能直接吃的输入矩阵。GetInputData 里用 foreach 并行跑 n 个基模型,把测试集 Xtest1、Xtest2 的预测值按列拼起来,分别存进 InputTest 和 InputTest1,维度都是 [样本数, n]。 随后在 env 环境里建了四套集成:origin、repaired、removed、relabeled,参数完全一致——r=7L、nh=5L、fact=7L。区别只在于喂进去的 x/y 来自 denoiseX1pretrain 里不同的预处理分支,用来对比噪声修复、样本剔除、标签重标各自对集成输出的影响。 跑完 origin 那套,str() 打印出的 InputPretrain 是 1201×500 的数值矩阵,首值 0.747;InputTest 是 501×500,首值 0.466;InputTest1 缩到 251×500,首值仅 0.093。而 repaired 分支的 InputPretrain 首值升到 0.815,前几个值普遍高于 origin 同位置,说明修复后的预训练样本让基模型输出整体抬了档。外汇与贵金属行情受噪声干扰大,这类集成对比只能在历史样本上提示倾向,实盘仍属高风险。 开 R 把这段 evalq 块塞进你自己的 env,改 nh=5L 为 nh=10L 重跑一次,看 InputTest 首值是否还落在 0.4~0.5 区间,就能快速验证隐层宽度对这套 ELM 集成的敏感度。

MQL5 / C++
foreach(i = class="num">1:n, .packages = "elmNN", .combine = "cbind") %do% {
  predict(Ens[[i]], newdata = Xtest1)
} -> InputTest #[ ,n]
#---predict--InputTest1----
Xtest2 <- X1$test1$x[ , featureX1]
foreach(i = class="num">1:n, .packages = "elmNN", .combine = "cbind") %do% {
  predict(Ens[[i]], newdata = Xtest2)
} -> InputTest1 #[ ,n]
#---res-------------------------
class="kw">return(list(InputPretrain = InputPretrain,
            InputTrain = InputTrain,
            InputTest = InputTest,
            InputTest1 = InputTest1))
}
}, env)
#---class="num">4--createEns--origin--------------
evalq({
  Ens.origin <- vector(mode = "list", n)
  res.origin <- vector("list", class="num">4)
  x <- denoiseX1pretrain$origin$x %>% as.matrix()
  y <- denoiseX1pretrain$origin$y
  createEns(r = 7L, nh = 5L, fact = 7L, X = x, Y = y) -> Ens.origin
  GetInputData(Ens = Ens.origin, X = x, Y = y) -> res.origin
}, env)
#---class="num">4--createEns--repaired--------------
evalq({
  Ens.repaired <- vector(mode = "list", n)
  res.repaired <- vector("list", class="num">4)
  x <- denoiseX1pretrain$repaired$x %>% as.matrix()
  y <- denoiseX1pretrain$repaired$y
  createEns(r = 7L, nh = 5L, fact = 7L,  X = x, Y = y) -> Ens.repaired
  GetInputData(Ens = Ens.repaired, X = x, Y = y) -> res.repaired
}, env)
#---class="num">4--createEns--removed--------------
evalq({
  Ens.removed <- vector(mode = "list", n)
  res.removed <- vector("list", class="num">4)
  x <- denoiseX1pretrain$removed$x %>% as.matrix()
  y <- denoiseX1pretrain$removed$y
  createEns(r = 7L, nh = 5L, fact = 7L, X = x, Y = y) -> Ens.removed
  GetInputData(Ens = Ens.removed,  X = x, Y = y) -> res.removed
}, env)
#---class="num">4--createEns--relabeled--------------
evalq({
  Ens.relab <- vector(mode = "list", n)
  res.relab <- vector("list", class="num">4)
  x <- denoiseX1pretrain$relabeled$x %>% as.matrix()
  y <- denoiseX1pretrain$relabeled$y
  createEns(r = 7L, nh = 5L, fact = 7L, X = x, Y = y) -> Ens.relab
  GetInputData(Ens = Ens.relab,  X = x, Y = y) -> res.relab
}, env)

「四种清洗策略下的特征分布对照」

跑完 origin / repaired / removed / relab 四套预处理后,直接 str() 各自的 InputTrain 就能看出样本矩阵规模没变:都是 1001 行 × 500 列,但首列均值从 origin 的 0.871 漂到 removed 的 0.864,relab 则拉到 1.043,说明重标注策略对特征尺度扰动最明显。 InputTest 的 501×500 矩阵里,origin 首列均值 0.551,removed 变成 0.755,relab 进一步到 0.76;InputTest1(251×500)在 origin 下首列出现 -0.00444 的负向离群,relab 后抬到 0.176,异常值被压回正区间。外汇与贵金属特征工程高风险,这些偏移只代表历史样本统计,实盘可能失效。 用 par(mfrow=c(2,2)) 把四个 InputTrain[,1:10] 的箱线图并排,再 abline 标 0/0.5/1.0 三条红线,肉眼就能比对哪种预处理把特征挤回了 [0,1] 的合理带。relab 的箱体明显上移,removed 的中位更贴近 0.5,选哪个进 MT5 信号模型前建议先跑这段。 最后补一张 InputTest 的 2×2 箱线图(las=1 横轴标签横排),和 Train 对照看泛化缝隙:origin 的 Test 首列均值掉到 0.551,而 relab 的 Test 仍有 0.76,过拟合概率相对低一些,但具体阈值得你自己在终端回测。

MQL5 / C++
par(mfrow = c(class="num">2, class="num">2), mai = c(class="num">0.3, class="num">0.3, class="num">0.4, class="num">0.2))
boxplot(env$res.origin$InputTrain[ ,class="num">1:class="num">10], horizontal = T, main = "res.origin$InputTrain[ ,class="num">1:class="num">10]")
abline(v = c(class="num">0, class="num">0.5, class="num">1.0), col = class="num">2)
boxplot(env$res.repaired$InputTrain[ ,class="num">1:class="num">10], horizontal = T, main = "res.repaired$InputTrain[ ,class="num">1:class="num">10]")
abline(v = c(class="num">0, class="num">0.5, class="num">1.0), col = class="num">2)
boxplot(env$res.removed$InputTrain[ ,class="num">1:class="num">10], horizontal = T, main = "res.removed$InputTrain[ ,class="num">1:class="num">10]")
abline(v = c(class="num">0, class="num">0.5, class="num">1.0), col = class="num">2)
boxplot(env$res.relab$InputTrain[ ,class="num">1:class="num">10], horizontal = T, main = "res.relab$InputTrain[ ,class="num">1:class="num">10]")
abline(v = c(class="num">0, class="num">0.5, class="num">1.0), col = class="num">2)
par(mfrow = c(class="num">1, class="num">1))
par(mfrow = c(class="num">2, class="num">2), mai = c(class="num">0.3, class="num">0.3, class="num">0.4, class="num">0.2), las = class="num">1)
boxplot(env$res.origin$InputTest[ ,class="num">1:class="num">10], horizontal = T, main = "res.origin$InputTest[ ,class="num">1:class="num">10]")
把重复劳动交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到噪声样本标记与阈值切换后的类分布,你只需专注判断信号是否值得跟。

常见问题

未处理的异常值会在训练阶段污染权重初始化,使连续预测偏移,后续阈值转换类标签时假阳性概率倾向升高。
不是。0.5 只是本实验的初筛切点,实际可依样本量与过拟合风险下调,重点看交叉验证后的度量稳定性。
多数表决对单个优化失败的子网络容错更强,分类边界倾向更平滑,但计算成本随组合数量线性上升。
小布内置了噪声标记与阈值切换视图,可对照本篇的 R 实验结果快速核对某品种的信号质量,不必自己搭环境。
主要是连续预测转类标签的阈值类型与融合权重衰减系数,优化目标多为 F1 或 AUC 而非单纯准确率。