深度神经网络 (第 III 部)。样品选择和降维·进阶篇
🧹

深度神经网络 (第 III 部)。样品选择和降维·进阶篇

(2/3)·标签噪音正悄悄拖垮你的模型精度,PCA、ICA与自编码器该怎么选

新手友好 第 2/3 篇
很多交易者直接把原始行情特征丢进网络训练,没意识到标签噪音会让分类器过度拟合且预测偏差放大。错误标注的样本比缺失值更隐蔽,常在回测里好看、实盘里失效。先滤噪再降维,顺序错了后面全白搭。

PCA 怎么把高维行情压成低维信号

主分量分析(PCA)是降维里最直白的路子:在原始数据空间里找一块设定好维度的超平面,把样本投影上去,挑投影误差最小、也就是方差之和最大的那块平面。这套思路底子很老,核心不复杂,但落到 MT5 做特征压缩时很实用。 约化维度 d 一般由你自己定。做可视化(d=2 或 3)或者单纯想省内存,d 很好选;但拿去当模型输入时,d 选多少没先验答案。 有个启发式办法:PCA 生成的各阶约化空间是互相嵌套的,协方差矩阵的特征向量和特征值算一次,就能切任意 d。把特征值按从大到小排成图,右手边趋近于零的砍掉,或者定个阈值让曲线右侧面积占总量 5% 或 1%,d 就出来了。 实战里,当候选预测因子明显多于 50 个,先用 PCA 把噪声因子压一波,后续训练会更干净。外汇和贵金属波动受宏观与流动性扰动大,高维因子里混着大量噪音,这类预处理只降低维度、不保证方向,信号失效风险仍在。

◍ ICA 怎么从价格因子里拆出独立信号

独立分量分析(ICA)比 PCA 晚出场,但在信号分离上更狠:它假设观测到的 P 个变量是 p 个相互独立变量的线性混合,目标就是把原始独立源恢复出来。ICA 不要求分量正交,只要求统计独立,这比 PCA 里“只是不相关”要严格得多,对汇率、贵金属这类多因子搅在一起的高风险行情数据更有拆解价值。 caret 里的 preProcess() 同时封装了 PCA 和 ICA。ICA 分两阶段:先在训练集上算参数(含白化矩阵 K 与混合矩阵 W),之后用 predict() 把训练集和新数据统一转换。实验里对 x.cap(含异常值插补的 7906×11 数据集)指定 pcaComp=5、n.comp=3,不指定 ICA 正则化方式,跑出来的 prePCA 与 preICA 各自带 mean、std 以及 rotation / K、W 矩阵。 算法链路很直白:先居中得 X,投影到主分量方向得 PCA = X*K,再乘混合矩阵得 ICA = X*K*W。用 predict::caret 与手算参数两种方式得到的 PCA、ICA 结果一致。 别把 caret 当唯一选项。pcaPP 算 PCA 对主分量识别更稳;fastICA 包参数更灵活,建议直接用它而不是 caret 里的 ica 封装。外汇与贵金属杠杆高、波动剧烈,任何降维信号都只是概率倾向,实盘前请在 MT5 导出的 R 数据上复跑确认。

MQL5 / C++
> ls(env)
[class="num">1] "cap1"        "cap2"        "Close"       "Data"        "dataSet"     
[class="num">6] "dataSetCap"  "dataSetClean" "dataSetOut"  "High"        "i"           
[class="num">11] "k"           "k.cap"       "k.out"       "lof.x"       "lof.x.cap"  
[class="num">16] "Low"         "lower"       "med"         "Open"        "out.ftlm"    
[class="num">21] "out.ftlm1"   "pr"          "pre.outl"    "preProClean" "Rlof.x"      
[class="num">26] "Rlof.x.cap"  "sk"          "sk.cap"      "sk.out"      "test"        
[class="num">31] "test.out"    "train"       "train.out"   "upper"       "Volume"      
[class="num">36] "x"           "x.cap"       "x.out"
require(caret)
evalq({
  prePCA <- preProcess(x.cap,
                         pcaComp = class="num">5,
                         method = Hmisc::Cs(center, scale, pca))
  preICA <- preProcess(x.cap,
                         n.comp = class="num">3,
                         method = "ica")
}, env)
> str(env$prePCA)
List of class="num">20
$ dim               : class="type">int [class="num">1:class="num">2] class="num">7906 class="num">11
$ bc                : NULL
$ yj                : NULL
$ et                : NULL
$ invHyperbolicSine: NULL
$ mean              : Named num [class="num">1:class="num">11] -class="num">0.001042 -class="num">0.003567 -class="num">0.000155 -class="num">0.000104 -class="num">0.000267 ...
  ..- attr(*, "names")= chr [class="num">1:class="num">11] "ftlm" "stlm" "rbci" "pcci" ...
$ std               : Named num [class="num">1:class="num">11] class="num">0.091 class="num">0.237 class="num">0.1023 class="num">0.0377 class="num">0.0356 ...

「预处理对象里的 PCA 与 ICA 结构差异」

在 R 环境里用 str() 扫一眼 env$preProcess 和 env$preICA,能直接看出两套预处理管道的配置分叉。前者走 PCA 路线:rotation 矩阵是 11×5 的数值块,pcaComp 和 numComp 都锁在 5,thresh 设 0.95,说明用前 5 个主成分去覆盖 95% 方差倾向。 后一个 env$preICA 则是纯 ICA 结构:rotation 为 NULL,pcaComp / numComp 皆空,但在 ica 子列表里塞了 11×3 的混解矩阵 K,row.norm 为 FALSE。维度上 preICA$dim 显示 7906×11,即 7906 根样本棒、11 个原始特征(ftlm/stlm/rbci/pcci 等)。 两个对象的 mean 向量也值得核对:preICA 里 ftlm 均值约 -0.001042、std 约 0.091,stlm 均值 -0.003567、std 0.237。开 MT5 把对应指标导出到 R,跑一遍 caret::preProcess,对照这两段 str 输出,能确认自己特征工程没接错管道。外汇与贵金属数据高频噪声大,这类降维预处理仅降低过拟合概率,实盘仍属高风险。

用 R 复算确认 PCA 与 ICA 投影一致

在 MT5 之外做特征降维验证时,R 的 caret::preProcess 对象会吐出一套内部结构:W 是一个 3×3 数值矩阵,首行打印值约为 -0.587、0.77、-0.25、-0.734、-0.636,wildcards 为长度 2 的列表且含 PCA 与 ICA 两个空字符字段,k 设为 5,knnSummary 是函数,bagImp/median/data 均为 NULL,对象 class 标记为 "preProcess"。 下面这段 R 代码先把 x.cap 分别用 prePCA 和 preICA 做 predict,得到 pca 与 ica;随后手动用 scale 减均值除标准差,再乘 rotation(PCA)或 K %*% W(ICA),生成 pca1、ica1。all_equal 回测显示两者完全相同,说明 predict 内部就是标准白化后乘投影矩阵。 外汇与贵金属行情的高噪声会让 ICA 分量不稳定,建议在 MT5 里用相同均值/方差参数复算一遍,确认你自己的特征管线没有悄悄漂移。

MQL5 / C++
require(magrittr)
evalq({
  pca <- predict(prePCA, x.cap)
  ica <- predict(preICA, x.cap)
  pca1 <- ((x.cap %>% scale(prePCA$mean, prePCA$std)) %*%
           prePCA$rotation)
  ica1 <- ((x.cap %>% scale(preICA$mean, preICA$std)) %*%
           preICA$ica$K) %*% preICA$ica$W
  colnames(ica1) <- colnames(ica1, do.NULL = FALSE, prefix = &class="macro">#x27;ICA&class="macro">#x27;)
},env)
evalq(all_equal(pca, pca1), env)
# [class="num">1] TRUE
evalq(all_equal(ica, ica1), env)
# [class="num">1] TRUE

◍ PPCA 把降维变成可计算的概率题

经典 PCA 只给超平面方向,概率主分量分析(PPCA)用概率模型重写了同一件事:它恢复的是整个数据的变异模型,而不只是协方差矩阵特征向量定义的那个面。换句话说,解里既含基向量方向,也含这些基向量的长度,数据在所有方向上的散布都被描述了。 这套概率框架带来几个实打实的好处:能用 EM 算法在 d≪D 时更高效地求解;缺失值直接并进隐藏变量,EM 照跑;还能接混合物分布、贝叶斯选维、生成新样本、按类建分布做分类。likelihood 值本身就成了通用准则,不同概率模型能直接比,离群点也容易被拎出来。 真要落地,R 里的 pcaMethods 包把 PPCA 和一堆非线性变体打包好了。它的 ppca 方法能容忍 10%–15% 的 NA,bpca 靠贝叶斯+EM 容忍 >10% 缺失且不强制负载正交,nlpca 用自编码结构吃缺失值,nipals 只接 ≤5% 缺失。包里所有方法返回统一的 pcaRes 对象,用 pca() 按命名参数切类型。 下面三行是在 MT5 之外的 R 环境装包并载入的代码,跑通后你就能用上述算法评估自己导出的 tick 缺失数据。外汇与贵金属数据高频断裂常见,用前先确认缺失比例落在区间内,这类方法对样本结构敏感,结果仅代表概率倾向而非确定结论。

MQL5 / C++
source("https:class=class="str">"cmt">//bioconductor.org/biocLite.R")
biocLite("pcaMethods")
library(pcaMethods)

「用自编码器压到三维再切分样本」

自编码器(AE)是输入层与输出层神经元数相等的多层网络,核心任务是把输入数据尽可能无失真地重建。训练完之后,输入层到首个隐藏层的权重矩阵 W1 就是载荷,把原始矩阵 Xin 乘上 W1 得到的约化矩阵,本质上等同于主分量;用 predict(hidden.output=TRUE) 也能直接取出隐藏层输出。 想明显降维时可以串两个 AE:比如 12 个输入先跑 12-7-12,取隐藏层 7 维再喂给 7-3-7,最终压到 3 维(12→3)。隐藏神经元越少、n.hidden/n.in 比例越低,复原误差越大——这是调参时首先要盯的数。 下面这段 R 代码把前一篇存好的 Part_1.RData 结果拆成训练/验证/测试三份(各 1000 行:1–2000、2001–3000、3001–4000),先做去常量和缺失剔除,再用四分位上下限加 5%/95% 封顶做异常值插补,最后得到 DTcap 列表供后续建模。 [CODE] require(FCNN4R) require(deepnet) require(darch) require(tidyverse) require(magrittr) #----清理--------------------- require(caret) require(pipeR) evalq( { train = 1:2000 val = 2001:3000 test = 3001:4000 DT <- list() dataSet %>% preProcess(., method = c("zv", "nzv", "conditionalX")) %>% predict(., dataSet) %>% na.omit -> dataSetClean list(train = dataSetClean[train, ], val = dataSetClean[val, ], test = dataSetClean[test, ]) -> DT rm(dataSetClean, train, val, test) }, env) #------异常值------------- require(foreach) evalq({ DTcap <- list() foreach(i = 1:3) %do% { DT[[i]] %>% select(-c(Data, Class)) %>% as.data.frame() -> x if (i == 1) { foreach(i = 1:ncol(x), .combine = "cbind") %do% { prep.outlier(x[ ,i]) %>% unlist() } -> pre.outl colnames(pre.outl) <- colnames(x) } foreach(i = 1:ncol(x), .combine = "cbind") %do% { stopifnot(exists("pre.outl", envir = env)) lower = pre.outl['lower.25%', i] upper = pre.outl['upper.75%', i] med = pre.outl['med', i] cap1 = pre.outl['cap1.5%', i] cap2 = pre.outl['cap2.95%', i] treatOutlier(x = x[ ,i], impute = T, fill = T, lower = lower, upper = upper, med = med, cap1 = cap1, cap2 = cap2) } %>% as.data.frame() -> x.cap colnames(x.cap) <- colnames(x) return(x.cap) } -> DTcap foreach(i = 1:3) %do% { cbind(DTcap[[i]], Class = DT[[i]]$Class) } -> DTcap DTcap$train <- DTcap[[1]] DTcap$val <- DTcap[[2]] DTcap$test <- DTcap[[3]] rm(lower, upper, med, cap1, cap2, x.cap, x) }, env) #------常规化----------- evalq( 约化后画变体-协方差图,能直接看到 V1/V2/V3 被目标变量等级切开,但训练、验证、测试三集的分布是偏斜的。把噪音样本剔掉再画一次,图像大概率会更干净,这一步留给你在 MT5 之外的 R 环境里自己跑。 顺带提一句 NLPCA 里的分层变体(h-NLPCA):它不光给最优非线性子空间,还强制分量按色散等级排、彼此不相关,相当于做了非线性白化。对于后续想接 ICA 或线性分类器的交易者,这种预处理能消掉数据里的复杂非线性关系,让后续方法可以用线性假设。外汇与贵金属市场高频噪声大,这类降维白化只是概率上提升信号分离度,不保证实盘胜率,请自担高风险。

MQL5 / C++
require(FCNN4R)
require(deepnet)
require(darch)
require(tidyverse)
require(magrittr)
#----清理---------------------
require(caret)
require(pipeR)
evalq(
  {
    train = class="num">1:class="num">2000
    val = class="num">2001:class="num">3000
    test = class="num">3001:class="num">4000
    DT <- list()
    dataSet %>%
      preProcess(., method = c("zv", "nzv", "conditionalX")) %>%
      predict(., dataSet) %>%
      na.omit -> dataSetClean
    list(train = dataSetClean[train, ],
         val = dataSetClean[val, ],
         test = dataSetClean[test, ]) -> DT
    rm(dataSetClean, train, val, test)
  },
  env)
#------异常值-------------
require(foreach)
evalq({
  DTcap <- list()
  foreach(i = class="num">1:class="num">3) %do% {
    DT[[i]] %>%
      select(-c(Data, Class)) %>%
      as.data.frame() -> x
    if (i == class="num">1) {
      foreach(i = class="num">1:ncol(x), .combine = "cbind") %do% {
        prep.outlier(x[ ,i]) %>% unlist()
      } -> pre.outl
      colnames(pre.outl) <- colnames(x)
    }
    foreach(i = class="num">1:ncol(x), .combine = "cbind") %do% {
      stopifnot(exists("pre.outl", envir = env))
      lower = pre.outl[&class="macro">#x27;lower.class="num">25%&class="macro">#x27;, i]
      upper = pre.outl[&class="macro">#x27;upper.class="num">75%&class="macro">#x27;, i]
      med = pre.outl[&class="macro">#x27;med&class="macro">#x27;, i]
      cap1 = pre.outl[&class="macro">#x27;cap1.class="num">5%&class="macro">#x27;, i]
      cap2 = pre.outl[&class="macro">#x27;cap2.class="num">95%&class="macro">#x27;, i]
      treatOutlier(x = x[ ,i], impute = T, fill = T,
                   lower = lower, upper = upper,
                   med = med, cap1 = cap1, cap2 = cap2)
    } %>% as.data.frame() -> x.cap
    colnames(x.cap) <- colnames(x)
    class="kw">return(x.cap)
  } -> DTcap
  foreach(i = class="num">1:class="num">3) %do% {
    cbind(DTcap[[i]], Class = DT[[i]]$Class)
  } -> DTcap
  DTcap$train <- DTcap[[class="num">1]]
  DTcap$val <- DTcap[[class="num">2]]
  DTcap$test <- DTcap[[class="num">3]]
  rm(lower, upper, med, cap1, cap2, x.cap, x)
}, env)
#------常规化-----------
evalq(
把特征筛选交给小布盯盘
小布盯盘的 AIGC 已内置常见降维与样本诊断视图,打开对应品种页即可看到主分量贡献与异常标签提示,你只需判断保留哪几维特征。

常见问题

标签决定监督学习的正确目标,错误标签直接教错模型,导致结构复杂化与泛化下降;属性噪音多可通过平滑或补全缓解。
PCA 抓方差最大的正交方向保留线性相关性,ICA 找统计独立的分量剥离混合信号,后者更擅长分离隐藏驱动因子。
可以,品种页的 AIGC 诊断会基于分布偏移与邻近样本一致性给出疑似错标提示,但终判仍需你结合盘面确认。
瓶颈层维度通常小于输入且由重构误差拐点决定,可先用 PCA 维度做参考再网格搜索,避免过小丢信息过大过拟合。
时间序列需按时间顺序切,验证测试要覆盖不同波动 regime,比例常取 6/2/2 并单独留危机样本做压力测试。