机器学习模型的变量评估和选择·进阶篇
🔬

机器学习模型的变量评估和选择·进阶篇

(2/3)· 很多模型跑崩不是算法不行,而是喂进去的变量早就该清场和重排了

实战向进阶 第 2/3 篇
把原始OHLC直接丢进模型当输入,是新手最常踩的坑。报价不是数值变量,先取对数差或比值再谈训练。变量里有NA或近零方差异常值,模型轻则抖动重则崩溃,这些在训练前就该亲手处理掉。

◍ 因子不是越多越好:过滤与封装两条路

实测把输入因子堆上去并不总能改善模型,反而可能拖累表现。多数情况下,真正起作用的预测因子只有 3–5 个,这一点在 rminer、caret、SuperLearner、mlr 这些聚合包的内置变量重要性函数里都能复现。 按 John、Kohavi 和 Pfleger 1994 年的分法,缩减因子基本就两类。过滤法在模型之外先算因子相关性,只留满足阈值的进模型,比如分类任务里逐个检查因子和标签的模糊关系,明显相关的才用;封装法则把因子当搜索输入、把模型效率当输出,靠递归增减、遗传算法或模拟退火去凑最优组合。 过滤更快,但挑选标准不直接挂钩模型效率;封装能盯着效率找组合,代价是反复训模型、调参,计算时间陡增。本文不碰封装,只拆一种我看能避开上述短板的过滤思路。外汇与贵金属预测因子建模波动剧烈,高杠杆下误判风险极高,相关结论仅作概率性参考。

因子筛选的三种务实切法

预测因子跑完外部评估后,会得到一个信息容量排序——也就是每个变量对模型质量的贡献权重。这一步不是装饰,是直接决定后续训练噪声量的关口。 实际落地通常只有三条路:固定抓重要度最高的 N 个;按全集合的百分比截 top 段;或者设一道重要度阈值,高于它的才进池子。 三种方式里的数量、百分比、阈值本身都能丢进优化器里扫参。外汇与贵金属行情高杠杆、跳空频繁,因子冗余会放大数据过拟合概率,筛完建议先在小布盯盘里回测一轮再上实盘。 下面把输入和输出数据集按这三种切法分别备好,才能跑后续的对照试验。

「把11个振荡器压成17维输入矩阵」

建模用的样本取自 EURUSD 的 M30 周期,最近 4000 根 K 线,不区分指标优先级,统一塞进输入集。原始计划是 11 个常见振荡器,再从中抽变量,最终拼出 17 维特征矩阵供后续训练。 以 MACD 为例,用 magrittr 的管道 %>% 可以把「算指标→转数据帧→加衍生列→抽列→回矩阵」写成一眼能读完的链式调用:先取 macd 与 signal 两列,给数据帧加 vsig = signal 一阶差分后头部补 NA 再乘 10,最后只留 signal 和 vsig。 代码里 In() 函数把 ADX、aroon、ATR、CCI、chaikinVolatility、CMO、MACD、RSI、stoch、SMI、volatility 全部按 n=16 算完 cbind 起来。直接在 MT5 导出的 M30 EURUSD csv 上跑 x <- In(p = 16) 能看到 summary:DX 中位数 16.64、ADX 均值 20.72、oscDX 均值 4.23,且各列有 16~31 个 NA 头尾填充。 外汇与贵金属属高杠杆品种,这套输入构造只解决特征来源,不预示任何方向;NA 行数会随参数 p 变化,调大 p 头部空值只会更多。

MQL5 / C++
In <- function(p = class="num">16){
  require(TTR)
  require(dplyr)
  require(magrittr)
  adx <- ADX(price, n = p) %>% as.data.frame %>%
        mutate(.,oscDX = DIp -DIn) %>%
        transmute(.,DX, ADX, oscDX) %>% as.matrix()
  ar <- aroon(price[ ,c(&class="macro">#x27;High&class="macro">#x27;, &class="macro">#x27;Low&class="macro">#x27;)], n = p)%>%
        extract(,class="num">3)
  atr <- ATR(price, n = p, maType = "EMA") %>%
        extract(,class="num">1:class="num">2)
  cci <- CCI(price[ ,class="num">2:class="num">4], n = p)
  chv <- chaikinVolatility(price[ ,class="num">2:class="num">4], n = p)
  cmo <- CMO(price[ ,&class="macro">#x27;Med&class="macro">#x27;], n = p)
  macd <- MACD(price[ ,&class="macro">#x27;Med&class="macro">#x27;], class="num">12, class="num">26, class="num">9) %>%
        as.data.frame() %>%
        mutate(., vsig = signal %>%
        diff %>% c(NA,.) %>% multiply_by(class="num">10)) %>%
        transmute(., sign = signal, vsig) %>%
        as.matrix()
  rsi <- RSI(price[ ,&class="macro">#x27;Med&class="macro">#x27;], n = p)
  stoh <- stoch(price[ ,class="num">2:class="num">4], nFastK = p, nFastD =class="num">3, nSlowD = class="num">3, maType = "EMA")%
        as.data.frame() %>% mutate(., oscK = fastK - fastD)%
        transmute(.,slowD, oscK)%>% as.matrix()
  smi <- SMI(price[ ,class="num">2:class="num">4],n = p, nFast = class="num">2, nSlow = class="num">25, nSig = class="num">9)
  vol <- volatility(price[ ,class="num">1:class="num">4], n = p, calc = "yang.zhang", N = class="num">144)
  In <- cbind(adx, ar, atr, cci, chv, cmo, macd, rsi, stoh, smi, vol)
  class="kw">return(In)
}
x <- In(p = class="num">16)
> summary(x)
       DX                 ADX            oscDX  
Min.   : class="num">0.02685   Min.   : class="num">5.291   Min.   :-class="num">93.889  
1st Qu.: class="num">8.11788   1st Qu.:class="num">14.268   1st Qu.: -class="num">9.486  
Median :class="num">16.63550   Median :class="num">18.586   Median :  class="num">5.889  
Mean   :class="num">20.70162   Mean   :class="num">20.716   Mean   :  class="num">4.227  
3rd Qu.:class="num">29.90428   3rd Qu.:class="num">24.885   3rd Qu.: class="num">19.693  
Max.   :class="num">79.80812   Max.   :class="num">59.488   Max.   : class="num">64.764  
NA&class="macro">#x27;s   :class="num">16         NA&class="macro">#x27;s   :class="num">31       NA&class="macro">#x27;s   :class="num">16       
       ar                tr                atr  
Min.   :-class="num">100.0000   Min.   :class="num">0.0000000   Min.   :class="num">0.000224  
1st Qu.: -class="num">50.0000   1st Qu.:class="num">0.0002500   1st Qu.:class="num">0.000553  
Median :  -class="num">6.2500   Median :class="num">0.0005600   Median :class="num">0.000724  
Mean   :  -class="num">0.8064   Mean   :class="num">0.0008031   Mean   :class="num">0.000800  
3rd Qu.:  class="num">50.0000   3rd Qu.:class="num">0.0010400   3rd Qu.:class="num">0.000970  
Max.   : class="num">100.0000   Max.   :class="num">0.0150300   Max.   :class="num">0.003104  
NA&class="macro">#x27;s   :class="num">16          NA&class="macro">#x27;s   :class="num">1          NA&class="macro">#x27;s   :class="num">16        
       cci              chv              cmo  

◍ 多指标样本分布的统计切面

把一段行情切片后跑描述统计,能看到各指标的真实散布而不是肉眼印象。下面这组汇总覆盖了 sign、vsig、rsi、slowD、oscK、SMI、signal、vol 八个序列,每个都带 NA 计数,说明取样窗口里有断点。 rsi 的 Median 落在 49.40、Mean 49.56,基本围绕 50 中轴,但 Min 12.59 / Max 89.42 提示极端超卖超买都出现过;slowD 的 Median 0.4720、Mean 0.4859,3rd Qu. 到 0.7124,整体偏多头排列的概率略高。 oscK 的 Mean 是 -0.000017,几乎为零,但 Min -0.415723 / Max 0.448486,摆动对称;SMI 的 Median -5.238、Mean -4.089,轻微偏负,Max 75.079 对比 Min -74.122 接近对称。vol 的 Mean 0.012337、Max 0.048948,是 Min 0.003516 的约 14 倍,波动聚类特征明显。 NA 数量值得注意:vsig 缺 34 个、signal 缺 33 个、SMI 缺 25 个,回测前要先决定是向前填充还是直接剔除,否则均值会被样本数不同的序列拉偏。外汇与贵金属杠杆高,这类统计仅描述历史切片,对后续走势只具参考倾向。

MQL5 / C++
Min.   :-class="num">515.375   Min.   :-class="num">0.67428   Min.   :-class="num">88.5697   
1st Qu.: -class="num">84.417   1st Qu.:-class="num">0.33704   1st Qu.:-class="num">29.9447   
Median :  -class="num">5.674   Median : class="num">0.03057   Median : -class="num">2.4055   
Mean   :  -class="num">1.831   Mean   : class="num">0.11572   Mean   : -class="num">0.6737   
3rd Qu.:  class="num">83.517   3rd Qu.: class="num">0.44393   3rd Qu.: class="num">28.0323   
Max.   : class="num">387.814   Max.   : class="num">3.25326   Max.   : class="num">94.0649   
NA&class="macro">#x27;s   :class="num">15         NA&class="macro">#x27;s   :class="num">31         NA&class="macro">#x27;s   :class="num">16         
         sign              vsig              rsi       
Min.   :-class="num">0.38844   Min.   :-class="num">0.43815   Min.   :class="num">12.59   
1st Qu.:-class="num">0.07124   1st Qu.:-class="num">0.05054   1st Qu.:class="num">39.89   
Median :-class="num">0.00770   Median : class="num">0.00009   Median :class="num">49.40   
Mean   :-class="num">0.00383   Mean   :-class="num">0.00013   Mean   :class="num">49.56   
3rd Qu.: class="num">0.05075   3rd Qu.: class="num">0.05203   3rd Qu.:class="num">58.87   
Max.   : class="num">0.38630   Max.   : class="num">0.34871   Max.   :class="num">89.42   
NA&class="macro">#x27;s   :class="num">33         NA&class="macro">#x27;s   :class="num">34         NA&class="macro">#x27;s   :class="num">16       
         slowD             oscK                SMI       
Min.   :class="num">0.0499   Min.   :-class="num">0.415723   Min.   :-class="num">74.122   
1st Qu.:class="num">0.2523   1st Qu.:-class="num">0.043000   1st Qu.:-class="num">33.002   
Median :class="num">0.4720   Median : class="num">0.000294   Median : -class="num">5.238   
Mean   :class="num">0.4859   Mean   :-class="num">0.000017   Mean   : -class="num">4.089   
3rd Qu.:class="num">0.7124   3rd Qu.: class="num">0.045448   3rd Qu.: class="num">22.156   
Max.   :class="num">0.9448   Max.   : class="num">0.448486   Max.   : class="num">75.079   
NA&class="macro">#x27;s   :class="num">19       NA&class="macro">#x27;s   :class="num">17           NA&class="macro">#x27;s   :class="num">25       
         signal            vol       
Min.   :-class="num">71.539   Min.   :class="num">0.003516   
1st Qu.:-class="num">31.749   1st Qu.:class="num">0.008204   
Median : -class="num">5.319   Median :class="num">0.011274   
Mean   : -class="num">4.071   Mean   :class="num">0.012337   
3rd Qu.: class="num">19.128   3rd Qu.:class="num">0.015312   
Max.   : class="num">71.695   Max.   :class="num">0.048948   
NA&class="macro">#x27;s   :class="num">33       NA&class="macro">#x27;s   :class="num">16

用随机均匀森林拆解预测因子权重

把 ZigZag 信号当目标变量,用最小折线长度 25/75 点算两条 ZZ,取短 leg 那条做输入拼表,删掉 condition="0" 的未定义行和目标的 "0" 类,类型分布基本均衡,可直接进模型。 两个输入变量相关性超 90% 的是 rsi 和 SMI,剔掉后留剩余因子做评估。用 Random Uniform Forests 包算变量重要度,它把 Importance 拆成 contribution(降预测误差的整体贡献)+ interaction(对其他变量的影响)。 按 2:3 切训练/测试集,spatialSign 规范化到 [-1,1],doParallel 自动并行。训练内部误差 1%、方差 21.26%;Breiman 边界给预期误差 18.42%、上限 27.76%,测试外部误差 19.97%,低于上限且不大于内部误差,大概率不用重训。 全局重要度看出现频次,但前十名和「含相互影响」的排名对不上:tr 全局排末尾本该丢,却因强相互影响升到第 6。用前十变量重跑,测试误差降到 17.55%,比上限 28.18% 低,模型质量明显改善。 部分依赖图能直接看分割效果:cci 分割还行、signal 两类几乎全覆盖、tr 分割合理、chv 很差。分类视角下 tr 对类型 1 远重于 -1,oscK 反之。互相影响图里一阶最重要是 cci、二阶是 atr,热力图确认两者联合作用最强。 外汇/贵金属行情高噪,这套评估只降维度提概率,不保证信号对。开 MT5 把 ZZ leg 设 25/75 点导出,喂进 R 的 Random Uniform Forests,先删 rsi+SMI 再看 tr 的相互影响排名,比盲用全局重要度更稳。

「从离散因子里淘出能用的规则」

预测因子先被离散化:slowD 和 sign 完全没被分离,直接从数据集剔除;signal、vsig、cci、oscDX 只切了两段,其余变量分了 3 到 6 类。用 induction rules 包跑出来的规则,会把预测因子和目标绑定,比如一条规则里 idx 指向 6(atr)、4(ar)、11(vsig),当 atr 在 (85.1, Inf]、ar 在 (0.00137, Inf]、vsig 在 (0.0374, Inf] 时,consequent 判 class=1,laplace 字段给出该规则的置信区间。 测试集上做验证时,约 300 个样本被标为无足轻重可丢弃。拿精简后的集解析规则,预测质量反而比保留全集时更高一些。要注意 RandomUniformForests 这种算法每次重新加载都会给出略微不同的结果,重复试验拿不到可复现结论,外汇和贵金属市场本身高风险,这类概率结论只能当倾向性参考。 规则计算相当耗时,下面这段 R 侧函数负责把价格序列转成 ZigZag 及其转向信号,供上游离散化使用。ch 参数若大于 1 会按点位精度缩放,mode 控制取中价、高低价或收盘价;循环里把 NA 往前填充,再用 diff 算方向符号。

MQL5 / C++
ZZ <- function(pr = price, ch = ch , mode="m") {
  require(TTR)
  if(ch > class="num">1) ch <- ch/(class="num">10 ^ (Dig - class="num">1))
  if(mode == "m"){pr <- pr[ ,&class="macro">#x27;Med&class="macro">#x27;]}
  if(mode == "hl") {pr <- pr[ ,c("High", "Low")]}
  if(mode == "cl") {pr <- pr[ ,c("Close")]}
  zz <- ZigZag(pr, change = ch, percent = F, retrace = F, lastExtreme = T)
  n <- class="num">1:length(zz)
  for(i in n) { if(is.na(zz[i])) zz[i] = zz[i-class="num">1]}
  dz <- zz %>% diff %>% c(class="num">0,.)
  sig <- sign(dz)
  class="kw">return(cbind(zz, sig))
}
out1 <- ZZ(ch = class="num">25)
out2 <- ZZ(ch = class="num">50)
> matplot(tail(cbind(out1[ ,class="num">1], out2[ ,class="num">1]), class="num">500), t="l")
> data <- cbind(as.data.frame(x) , Class = factor(out1[ ,class="num">2])) %>%
+        na.omit
> data <- data[data$Class != class="num">0, ]
> data$Class <- rminer::delevels(data$Class, c("class="num">0", "class="num">1"), "class="num">1")
> table(data$Class)
   -class="num">1    class="num">1
class="num">1980 class="num">1985
> descCor <- cor(data[ ,-ncol(data)])
> summary(descCor[upper.tri(descCor)])
    Min.  1st Qu.   Median     Mean  3rd Qu.     Max.
-class="num">0.20170  class="num">0.03803  class="num">0.26310  class="num">0.31750  class="num">0.57240  class="num">0.95730
> highCor <- caret::findCorrelation(descCor, cutoff = .class="num">90)
> highCor
[class="num">1] class="num">12 class="num">15
> data.f <- data[ ,-highCor]
> descCor <- cor(data.f[ ,-ncol(data.f)])
> summary(descCor[upper.tri(descCor)])
    Min.  1st Qu.   Median     Mean  3rd Qu.     Max.
-class="num">0.20170  class="num">0.03219  class="num">0.21610  class="num">0.27060  class="num">0.47820  class="num">0.89880
> idx <- rminer::holdout(y = data.f$Class)
> prep <- caret::preProcess(x = data.f[idx$tr, -ncol(data.f)],
+           method = c("spatialSign"))
> x.train <- predict(prep, data.f[idx$tr, -ncol(data.f)])
> x.test <- predict(prep, data.f[idx$ts, -ncol(data.f)])
> y.train <- data.f[idx$tr, ncol(data.f)]
> y.test <- data.f[idx$ts, ncol(data.f)]
> ruf <- randomUniformForest( X = x.train,
+            Y = y.train,
+            xtest = x.test,
+            ytest = y.test,
+            mtry = class="num">1, ntree = class="num">300,
+            threads = class="num">2,
+            nodesize = class="num">2
+            )
为了易于计算,将标记 -class="num">1 class="num">1 已经转换成 class="num">1 class="num">2 ,并用于内部替换。
> print(ruf)
Call:
把变量筛查交给小布盯盘
这些诊断小布盯盘的AIGC已内置,打开对应品种页即可看到哪些因子信息量低、哪些组合冗余,你只需专注决策而非跑脚本。

常见问题

有序因子如小时数需显式声明顺序关系,无序因子如城市分区各类别等权,错误声明会让模型学到虚假梯度。
可以。小布内置的AIGC会对品种页输入集做信息量与近零方差筛查,并标记可能冗余的因子组合,省去手写R脚本。
OHLC量纲与分布不适合直接进模型,通常取差异对数或报价比对数,或用指标组合构造规整矩阵。
前者基于变量重要性评分与交互评估,后者从粗糙集角度优化因子集甚至训练样本集,角度互补。
当缺失行占比小且时序连续时插补保样本,缺失随机且少可删,但需自己确认而非依赖默认。