深度神经网络(第八部分)。 提高袋封融合的分类品质·进阶篇
(2/3)· 前篇搭好 ELM 分类器群,本篇用 R 环境实测噪声过滤与超参数优化能否真正改写预测边界
◍ 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 融合。
#--------------------------- 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 是否落在极端波动段。
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 回测。
#"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。 别把四个融合当同质模型。它们训练集噪声处理不同,连续预测落在不同区间,直接拼进组合器前最好先各自做类标签转换看分类品质。
#--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 集成的敏感度。
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,过拟合概率相对低一些,但具体阈值得你自己在终端回测。
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]")