深度神经网络 (第七部分)。 神经网络的融合: 堆叠·进阶篇
(2/3)· 基础分类器训好之后,怎么把它们的预测叠起来再训一层才不白忙
◍ 两种融合基线怎么搭
做集成对比前,先准备两个可训练的合并器:一个替换「平均融合最优神经网络输出」,另一个替换「修剪+平均」。两者都要先算每个网络的分类品质得分,否则后续没法挑。 第一选项用 ELM 融合当基线,相关函数 getBest()、testAver()、testVot() 已在 FUN_Stacking.R 里。getBest() 吐出 Score、bestNN(前 2*nb+1 个最优网络序号)、med(融合输出均值的中位数),并把 500 个网络的中值向量 th 写进环境;testAver() 给平均连续预测 Ypred 和标称预测 clAver;testVot() 给多数表决的标称预测。两个测试集上跑完,表现都还可以,后面会把分类误差拆成偏差/方差/噪声看各自占比。 第二选项直接喂 500 个输入,用 varbvs 包做贝叶斯变量筛选和系数计算,底层是变分近似,能扛百万级变量和几千样本。训练脚本在 varb.R,跑出来的度量比第一选项明显好。 下面这段代码是 R 环境里定义三个融合函数的核心,注意 th 在 getBest 里生成后,testVot 直接复用。外汇和贵金属信号若接这套,需明白模型误判概率不低,杠杆品种高风险。
evalq({ getBest <- function(Ens, x, y, nb){ n <- length(Ens) foreach(i = class="num">1:n, .packages = "elmNN", .combine = "cbind") %do% { predict(Ens[[i]], newdata = x)} -> y.pr foreach(i = class="num">1:n, .combine = "c") %do% { median(y.pr[ ,i])} ->> th foreach(i = class="num">1:n, .combine = "c") %do% { ifelse(y.pr[ ,i] > th[i], class="num">1, class="num">0) -> Ypred Evaluate(actual = y, predicted = Ypred)$Metrics$F1 %>% mean() } -> Score Score %>% order(decreasing = TRUE) %>% head(class="num">2*nb + class="num">1) -> best y.pr[ ,best] %>% apply(class="num">1, sum) %>% divide_by(length(best)) %>% median() -> med class="kw">return(list(Score = Score, bestNN = best, med = med)) } testAver <- function(Ens, x, y, best, med){ n <- length(Ens) foreach(i = class="num">1:n, .packages = "elmNN", .combine = "cbind") %:% when(i %in% best) %do% { predict(Ens[[i]], newdata = x)} %>% apply(class="num">1, sum) %>% divide_by(length(best)) -> ensPred ifelse(ensPred > med, class="num">1, class="num">0) -> clAver Evaluate(actual = y, predicted = clAver)$Metrics[ ,class="num">2:class="num">5] %>% round(class="num">3) -> Score class="kw">return(list(Score = Score, Ypred = ensPred, clAver = clAver)) } testVot <- function(Ens, x, y, best){ n <- length(Ens) foreach(i = class="num">1:n, .packages = "elmNN", .combine = "cbind") %:% when(i %in% best) %do% { predict(Ens[[i]], newdata = x)} %>% apply(class="num">2, function(x) ifelse(x > th[i], 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 = y, predicted = ClVot)$Metrics[ ,class="num">2:class="num">5] %>% round(class="num">3) -> Score class="kw">return(list(Score = Score, Ypred = ClVot)) } }, env) #--class="num">2---test---- evalq({ Ytrain <- X1$pretrain$y Ytest <- X1$train$y Ytest1 <- X1$test$y Ytest2 <- X1$test1$y Ens <- vector(mode = "list", n) Ens <- createEns(order = orderX1, X = X1) #---class="num">3------ resBest <- getBest(Ens, x = X1$train$x[ , bestF], y = Ytest, nb = class="num">3) #---class="num">4--averaging--- ScoreAver <- testAver(Ens, x = X1$test$x[ , bestF], y = Ytest1, best = resBest$bestNN, med = resBest$med) ScoreAver1 <- testAver(Ens, x = X1$test1$x[ , bestF], y = Ytest2,
「贝叶斯变量选择压过投票集成」
用 varbvs 跑二项分布贝叶斯变量选择,把 500 个输入变量压到仅 3 个入选(概率 cutoff 从 >0.10 到 >0.95 都锁定 X18、X5、X255,三者 inclusion prob 均为 1.00000)。X18 系数估计 4.529,95% 可信区间 [+3.861,+5.195],X5 为 1.955 [+1.543,+2.370],X255 为 2.097 [+1.537,+2.660],说明这三列对方向判别的贡献最稳。 对比前面的平均集成与投票集成,贝叶斯模型在测试集 1 上 Accuracy 0.78、F1(1类) 0.793,高于投票集成的 0.752 / 0.757;但在测试集 2 上 Accuracy 掉到 0.729、F1(1类) 0.707,低于投票的 0.741 / 0.692 不多,显示过拟合倾向可能偏强。 外汇与贵金属行情受宏观事件冲击,这类基于历史窗口的二分类模型仅给出概率倾向,实盘使用前请在 MT5 用对应品种复算特征列,确认 X18/X5/X255 的物理含义与采样周期一致。
library(varbvs) evalq({ vr <- varbvs(X = res$InputTrain, Z = NULL, y = Ytest, family = "binomial", optimize.eta = TRUE, logodds = seq(-class="num">6,-class="num">2, class="num">0.25), nr = class="num">250, initialize.params = TRUE, maxiter = class="num">1e5, verbose = FALSE) summary(vr, cred.class="type">int = class="num">0.95, nv = class="num">7, nr = class="num">1e5) %>% print() }, env) env$vr$pip %>% order() %>% tail(class="num">7) -> bestNN_vr evalq({ predict(vr, res$InputTest) -> pr.vr1 Evaluate(actual = Ytest1, predicted = pr.vr1)$Metrics[ ,class="num">2:class="num">5] %>% round(class="num">3) -> metr.test confus(table(Ytest1, pr.vr1)) -> cm1 predict(vr, res$InputTest1) -> pr.vr2 Evaluate(actual = Ytest2, predicted = pr.vr2)$Metrics[ ,class="num">2:class="num">5] %>% round(class="num">3) -> metr.test1 confus(table(Ytest2, pr.vr2)) -> cm2 }, env)
在 R 环境里搭起 Keras 与 TensorFlow 后端
深度神经网络的开源栈里,TensorFlow、CNTK、MXNet 都属于低层库,新手直接啃成本太高。RStudio 出的 keras 包把这套东西包成了高层 API,重点就是快速出原型、方便跑实验,而且能无缝切到 TensorFlow/CNTK/Theano 任一后端。 keras 不是牺牲灵活换易用:它底层仍接 TensorFlow,理论上底层能写的模型它都能表达。后端之间模型还能互转,比如用 TensorFlow 训完的嵌层模型,换 CNTK 加载无需改结构。硬件上除了 CPU,还支持 NVIDIA GPU、Google TPU,以及 AMD 等走 OpenCL 的卡(经 PlaidML 后端)。 Windows 下唯一受支持的安装路径是 conda,得先装 Anaconda3(文里实测用的是 Python 3.6)。之后从 CRAN 装 keras 包,调 install_keras() 默认拉 CPU 版;有 NVIDIA 卡且装了 CUDA 才考虑 gpu 参数,否则白费功夫。 实验管理有 tfruns 这套工具:自动抓每次训练的超参、度量、源码,不用改模型代码就能比多轮跑分,还能出对比报告。TensorBoard 则负责把训练过程画清楚。所有这些 R 包都靠 reticulate 做 Python 互调,R 与 Python 数据类型自动互转。 动手前先确认 R 能找到 Python。下面这段代码就是最小可验证步骤:装包、装后端、查环境。文里实测环境是 Python 3.6.5、TensorFlow v1.5.1,numpy 1.14.2,路径落在 Anaconda 的 r-tensorflow 虚拟环境里。
install.packages("keras") # class="kw">default installation library(keras) install_keras() # install with GPU version of TensorFlow # (NOTE: only do this if you have an NVIDIA GPU + CUDA!) install_keras(tensorflow = "gpu") # install a specific version of TensorFlow install_keras(tensorflow = "class="num">1.5") install_keras(tensorflow = "class="num">1.5-gpu") > library(reticulate) > py_config() python: K:\Anaconda3\envs\r-tensorflow\python.exe libpython: K:/Anaconda3/envs/r-tensorflow/python36.dll pythonhome: K:\ANACON~class="num">1\envs\R-TENS~class="num">1 version: class="num">3.6.class="num">5 | packaged by conda-forge | (class="kw">default, Apr class="num">6 class="num">2018, class="num">16:class="num">13:class="num">55) [MSC v.class="num">1900 class="num">64 bit(AMD64)] Architecture: 64bit numpy: K:\ANACON~class="num">1\envs\R-TENS~class="num">1\lib\site-packages\numpy numpy_version: class="num">1.14.class="num">2 tensorflow: K:\ANACON~class="num">1\envs\R-TENS~class="num">1\lib\site-packages\tensorflow python versions found: K:\Anaconda3\envs\r-tensorflow\python.exe K:\Anaconda3\python.exe K:\Anaconda3\python.exe > library(tensorflow) > tf_config() TensorFlow v1.class="num">5.1 (K:\ANACON~class="num">1\envs\R-TENS~class="num">1\lib\site-packages\tensorflow) Python v3.class="num">6 (K:\Anaconda3\envs\r-tensorflow\python.exe)
◍ 用 Keras 顺序模型替换融合均值
把袋型融合的 500 个输出直接喂给神经网络,而不是取平均或做修剪,是这套实验的核心改动。第一个实验仅用输入层加 softmax 输出层,结构为 NN(7,2):7 个输入神经元对应筛选后的最佳融合输出,2 个输出神经元做二分类。 代码里把训练集 20% 切给验证,优化器用 rmsprop,损失函数 binary_crossentropy,度量盯 accuracy。跑完在测试集上得到 Test loss 0.518、Test accuracy 0.754,换第二个测试集是 loss 0.55、accuracy 0.737,和融合均值几乎持平。 模型在 30 期后明显过拟合,50 期之后准确度走平。要注意神经网络初始化是随机的,每次重建训练结果都会漂,这不是 bug 是特性。 提前停止用 callback_early_stopping 盯 val_acc,patience=20、min_delta=1e-5 就停;tensorboard 能画出损失、准确度、层直方图,但每次重启要换 log_dir 否则旧图混进来。 第二个实验把 500 个输出全塞进多层网络,让 NN 同时干修剪和合并。没做超参优化时,准确度上限卡在 0.82 以下,靠直觉调参有空间但天花板令人失望,后续得走贝叶斯优化那条路。
#===========Keras=========================================== library(keras) num_classes <- 2L batch_size <- 32L epochs <- 300L #--------- bestNN <- env$resBest$bestNN x_train <- env$res$InputTrain[ ,bestNN] y_train <- env$Ytest %>% to_categorical() x_test <- env$res$InputTest[ ,bestNN] y_test <- env$Ytest1 %>% to_categorical() x_test1 <- env$res$InputTest1[ ,bestNN] y_test1 <- env$Ytest2 %>% to_categorical() ##----model--keras------------------------- # define model model <- keras_model_sequential() # add layers and compile model %>% layer_dense(units = num_classes, input_shape = dim(x_train)[class="num">2]) %>% layer_activation(activation = &class="macro">#x27;softmax&class="macro">#x27;) %>% compile( loss = &class="macro">#x27;binary_crossentropy&class="macro">#x27;, optimizer = optimizer_rmsprop(), metrics = &class="macro">#x27;accuracy&class="macro">#x27; ) ## Training & Evaluation --------------------------- # Fit model to data model %>% fit( x_train, y_train, batch_size = batch_size, epochs = epochs, verbose = class="num">0, view_metrics = FALSE, shuffle = TRUE, validation_split = class="num">0.2) -> history # Output metrics score <- model %>% evaluate(x_test, y_test, verbose = class="num">0) cat(&class="macro">#x27;Test loss:&class="macro">#x27;, score[[class="num">1]] %>% round(class="num">3), &class="macro">#x27; &class="macro">#x27;) Test loss: class="num">0.518 cat(&class="macro">#x27;Test accuracy:&class="macro">#x27;, score[[class="num">2]] %>% round(class="num">3), &class="macro">#x27; &class="macro">#x27;) Test accuracy: class="num">0.754 #--------------- score1 <- model %>% evaluate(x_test1, y_test1, verbose = class="num">0) cat(&class="macro">#x27;Test loss:&class="macro">#x27;, score1[[class="num">1]] %>% round(class="num">3), &class="macro">#x27; &class="macro">#x27;) Test loss: class="num">0.55 cat(&class="macro">#x27;Test accuracy:&class="macro">#x27;, score1[[class="num">2]] %>% round(class="num">3), &class="macro">#x27; &class="macro">#x27;) Test accuracy: class="num">0.737 #--plot------------------------ plot(history) callback_early_stopping(monitor = "val_loss", min_delta = class="num">0, patience = class="num">0, verbose = class="num">0, mode = c("auto", "min", "max")) callback_tensorboard(log_dir = NULL, histogram_freq = class="num">0, batch_size = class="num">32, write_graph = TRUE, write_grads = FALSE, write_images = FALSE, embeddings_freq = class="num">0, embeddings_layer_names = NULL, embeddings_metadata = NULL) early_stopping <- callback_early_stopping(monitor = "val_acc", min_delta = class="num">1e-5, patience = class="num">20, verbose = class="num">0,
「带早停的二分类网络实测表现」
把上一节筛出的特征列 bestNN 直接喂进 Keras 序贯模型,输入先叠一层高斯噪声(stddev=0.05)做正则,再接一个 2 单元 dense + softmax,活动正则 l2 设到 1.0,优化器用 RMSprop、学习率 0.005、decay 0.01。训练 100 轮、batch_size 32、validation_split 0.2,并挂上 early_stopping(监视 val_acc,patience=20)和 TensorBoard 回调。 在 x_test / y_test 上跑 evaluate,测试损失 0.539、测试准确率 0.756;换到另一组样本 x_test1 / y_test1,损失升到 0.571、准确率掉到 0.713。两组都只是倾向说明模型在见过的分布内尚可,遇到偏移样本泛化会明显打折。 TensorBoard 起在 http://127.0.0.1:7451,可实时看直方图与梯度。外汇与贵金属行情非平稳,这套二分类框架仅作特征验证用,实盘直接套用属高风险行为。
library(reticulate) library(keras) py_set_seed(class="num">12345) num_classes <- 2L batch_size <- 32L learning_rate <- class="num">0.005 epochs <- 100L #--------- bestNN <- env$resBest$bestNN x_train <- env$res$InputTrain[ ,bestNN] y_train <- env$Ytest %>% to_categorical() x_test <- env$res$InputTest[ ,bestNN] y_test <- env$Ytest1 %>% to_categorical() x_test1 <- env$res$InputTest1[ ,bestNN] y_test1 <- env$Ytest2 %>% to_categorical() ##----model--keras------------------------- # define model model <- keras_model_sequential() # add layers and compile model %>% layer_gaussian_noise(stddev = class="num">0.05, input_shape = dim(x_train)[class="num">2], name = "GN") %>% layer_dense(units = num_classes, name = "dense1") %>% layer_activation_softmax(name = "soft") %>% layer_activity_regularization(l2 = class="num">1.0, name = "reg") %>% compile( loss = &class="macro">#x27;binary_crossentropy&class="macro">#x27;, optimizer = optimizer_rmsprop(lr = learning_rate, decay = class="num">0.01), metrics = &class="macro">#x27;accuracy&class="macro">#x27; ) ## Training & Evaluation --------------------------- # Fit model to data model %>% fit( x_train, y_train, batch_size = batch_size, epochs = epochs, verbose = class="num">0, view_metrics = TRUE , shuffle = TRUE, validation_split = class="num">0.2, callbacks = list(early_stopping, tensboard)) -> history # Output metrics > score <- model %>% evaluate(x_test, y_test, verbose = class="num">0) > cat(&class="macro">#x27;Test loss:&class="macro">#x27;, score[[class="num">1]] %>% round(class="num">3), &class="macro">#x27;\n&class="macro">#x27;) Test loss: class="num">0.539 > cat(&class="macro">#x27;Test accuracy:&class="macro">#x27;, score[[class="num">2]] %>% round(class="num">3), &class="macro">#x27;\n&class="macro">#x27;) Test accuracy: class="num">0.756 > #--------------- > score1 <- model %>% evaluate(x_test1, y_test1, verbose = class="num">0) > cat(&class="macro">#x27;Test loss:&class="macro">#x27;, score1[[class="num">1]] %>% round(class="num">3), &class="macro">#x27;\n&class="macro">#x27;) Test loss: class="num">0.571 > cat(&class="macro">#x27;Test accuracy:&class="macro">#x27;, score1[[class="num">2]] %>% round(class="num">3), &class="macro">#x27;\n&class="macro">#x27;) Test accuracy: class="num">0.713 > tensorboard(log_dir = log_dir) TensorBoard class="num">1.7.class="num">0 at http:class=class="str">"cmt">//class="num">127.0.class="num">0.1:class="num">7451 (Press CTRL+C to quit) Started TensorBoard at http:class=class="str">"cmt">//class="num">127.0.class="num">0.1:class="num">7451
回测验证里模型到底交了什么卷
上面这套 R+Keras 流程跑完,别只看 fit 没报错就完事,真正该盯的是独立样本 x_test1 上的评估输出。env$res_mod_test1 给了一组硬数字:整体准确率 0.713,类 0 的召回 0.826、F1 0.760,类 1 的精确率 0.730、F1 0.644。 混淆矩阵里实际 0 判对 114、误判 24;实际 1 判对 65、误判 48。No Information Rate 是 0.6454,模型准确率超基准的 P 值 0.0137,Kappa 0.4092——说明不是靠瞎猜 majority class 混过去的,但优势也没到碾压级。 训练时挂了 tensorboard 回调,histogram_freq=1、write_grads=TRUE,本地起服务用 tensorboard(log_dir = c(paste0(getwd(),"/run_1"), paste0(getwd(),"/run_2"))) 就能叠两个 run 看梯度直方图。外汇贵金属行情里这类二分类信号天生高波动,实盘前建议把验证集换成不同年份切片,P-Value 若掉到 0.05 以上就得警惕过拟合。
tensboard <- callback_tensorboard(log_dir = log_dir, histogram_freq = class="num">1, batch_size = class="num">32, write_graph = TRUE, write_grads = TRUE, write_images = FALSE) ##----model--keras------------------------- # define model model <- keras_model_sequential() # add layers and compile model %>% layer_gaussian_noise(stddev = class="num">0.05, input_shape = dim(x_train)[class="num">2], name = "GN") %>% layer_dense(units = num_classes, name = "dense1") %>% layer_activation_softmax(name = "soft") %>% layer_activity_regularization(l2 = class="num">1.0, name = "reg") %>% class="macro">#l1 = class="num">0.01, compile( loss = &class="macro">#x27;binary_crossentropy&class="macro">#x27;, optimizer = optimizer_rmsprop(lr = learning_rate, decay = class="num">0.01), metrics = &class="macro">#x27;accuracy&class="macro">#x27; ) ## Training & Evaluation --------------------------- # Fit model to data model %>% fit( x_train, y_train, batch_size = batch_size, epochs = epochs, verbose = class="num">0, view_metrics = TRUE , shuffle = TRUE, validation_data = list(x_test, y_test), callbacks = list(early_stopping, tensboard)) -> history #--model--test1------------------------------------------------- predict(model, x_test1) -> Ypr.test1 Ypr.test1 %>% max.col()-class="num">1 -> y_pr_test1 class="macro">#Ypr.test1 %>% apply(class="num">1, function(x) which.max(x)) %>% subtract(class="num">1) -> y_pr_test1 evalq(res_mod_test1 <- Eval(Ytest2, y_pr_test1), env) #-----plot------------------ tensorboard(log_dir = c(paste0(getwd(),"/run_1"), paste0(getwd(),"/run_2")))