作者:黄天元,复旦大学博士在读,热爱数据科学与开源工具(R),致力于利用数据科学迅速积累行业经验优势和科学知识发现,涉猎内容包括但不限于信息计量、机器学习、数据可视化、应用统计建模、知识图谱等,著有《R语言高效数据处理指南》(《R语言数据高效处理指南》(黄天元)【摘要 书评 试读】- 京东图书)。知乎专栏:R语言数据挖掘。邮箱:huang.tian-yuan@qq.com.欢迎合作交流。
前文提要:
数据预处理的手段非常多,包括缺失值处理、离群值去除、合并少数类等等。在mlr中,有一下一系列函数可以来做数据预处理:
-
capLargeValues()
:转换大/无限数值。 -
createDummyFeatures()
:为因子特征生成虚拟变量。 -
dropFeatures()
:删除选定的特征。 -
joinClassLevels()
:仅用于分类,将已有的类合并为新的更大的一类。 -
mergeSmallFactorLevels()
:合并出现次数较少的因子。 -
normalizeFeatures()
:通过不同的方法对特征进行归一化,例如标准化或缩放到一定范围。 -
removeConstantFeatures()
:删除常数特征。 -
subsetTask()
:从中任务中获取部分自己,如特定观测或特征。
预处理的方法与学习器的构建可以放在一起。这是因为不同的学习器(模型)往往需要不同的预处理方法。此外,这也能够有效避免不当的处理。这里我们要区分数据依赖的预处理和数据独立的预处理。距离说明,如果在没有划分训练集与验证集之前,我们就对特征的均值来插补其缺失,那么就会导致信息泄露,让后续的验证可能会出现过拟合。但是,如果我们随便选择一个常数来插入,则不会出现这种问题。
mlr中有两个主要的预处理函数:
-
makePreprocWrapperCaret()
是利用caret
的caret::preProcess()
函数来对原始数据进行一系列的预处理。 -
makePreprocWrapper()
通过定义在训练之前和预测之前要采取的操作,可以编写自己的自定义预处理方法。
由于预处理与学习器放在一起,那么就会在任务Task不变的前提下,每个预处理都是针对单个数据集的(如训练集和验证集,不会针对整体来做。如利用训练集的均值插入训练集,利用验证集的均值插入验证集),而且预处理的参数可以与模型的超参数一起进行调整(如插补方法的不同于模型超参数的交互)。
下面我们不妨先用makePreprocWrapperCaret()做示范,利用官网的例子进行解释。用一个二分类的例子,用QDA模型(二次判别分析,全称Quadratic Discriminant Analysis)做分类,并使用PCA作为预处理,让所有轴能够解释至少90%的变异性。
sonar.task
## Supervised task: Sonar-example
## Type: classif
## Target: Class
## Observations: 208
## Features:
## numerics factors ordered functionals
## 60 0 0 0
## Missings: FALSE
## Has weights: FALSE
## Has blocking: FALSE
## Has coordinates: FALSE
## Classes: 2
## M R
## 111 97
## Positive class: M
lrn = makePreprocWrapperCaret("classif.qda", ppc.pca = TRUE, ppc.thresh = 0.9)
lrn
## Learner classif.qda.preproc from package MASS
## Type: classif
## Name: ; Short name:
## Class: PreprocWrapperCaret
## Properties: twoclass,multiclass,numerics,factors,prob
## Predict-Type: response
## Hyperparameters: ppc.BoxCox=FALSE,ppc.YeoJohnson=FALSE,ppc.expoTrans=FALSE,ppc.center=TRUE,ppc.scale=TRUE,ppc.range=FALSE,ppc.knnImpute=FALSE,ppc.bagImpute=FALSE,ppc.medianImpute=FALSE,ppc.pca=TRUE,ppc.ica=FALSE,ppc.spatialSign=FALSE,ppc.corr=FALSE,ppc.zv=FALSE,ppc.nzv=FALSE,ppc.thresh=0.9,ppc.na.remove=TRUE,ppc.k=5,ppc.fudge=0.2,ppc.numUnique=3,ppc.cutoff=0.9,ppc.freqCut=19,ppc.uniqueCut=10
下面进行训练:
mod = train(lrn, sonar.task)
mod
## Model for learner.id=classif.qda.preproc; learner.class=PreprocWrapperCaret
## Trained on: task.id = Sonar-example; obs = 208; features = 60
## Hyperparameters: ppc.BoxCox=FALSE,ppc.YeoJohnson=FALSE,ppc.expoTrans=FALSE,ppc.center=TRUE,ppc.scale=TRUE,ppc.range=FALSE,ppc.knnImpute=FALSE,ppc.bagImpute=FALSE,ppc.medianImpute=FALSE,ppc.pca=TRUE,ppc.ica=FALSE,ppc.spatialSign=FALSE,ppc.corr=FALSE,ppc.zv=FALSE,ppc.nzv=FALSE,ppc.thresh=0.9,ppc.na.remove=TRUE,ppc.k=5,ppc.fudge=0.2,ppc.numUnique=3,ppc.cutoff=0.9,ppc.freqCut=19,ppc.uniqueCut=10
getLearnerModel(mod)
## Model for learner.id=classif.qda; learner.class=classif.qda
## Trained on: task.id = Sonar-example; obs = 208; features = 22
## Hyperparameters:
getLearnerModel(mod, more.unwrap = TRUE)
## Call:
## qda(f, data = getTaskData(.task, .subset, recode.target = "drop.levels"))
##
## Prior probabilities of groups:
## M R
## 0.5336538 0.4663462
##
## Group means:
## PC1 PC2 PC3 PC4 PC5 PC6
## M 0.5976122 -0.8058235 0.9773518 0.03794232 -0.04568166 -0.06721702
## R -0.6838655 0.9221279 -1.1184128 -0.04341853 0.05227489 0.07691845
## PC7 PC8 PC9 PC10 PC11 PC12
## M 0.2278162 -0.01034406 -0.2530606 -0.1793157 -0.04084466 -0.0004789888
## R -0.2606969 0.01183702 0.2895848 0.2051963 0.04673977 0.0005481212
## PC13 PC14 PC15 PC16 PC17 PC18
## M -0.06138758 -0.1057137 0.02808048 0.05215865 -0.07453265 0.03869042
## R 0.07024765 0.1209713 -0.03213333 -0.05968671 0.08528994 -0.04427460
## PC19 PC20 PC21 PC22
## M -0.01192247 0.006098658 0.01263492 -0.001224809
## R 0.01364323 -0.006978877 -0.01445851 0.001401586
后尝试比较有PCA做预处理和没有预处理,两个模型的表现:
rin = makeResampleInstance("CV", iters = 3, stratify = TRUE, task = sonar.task)
res = benchmark(list("classif.qda", lrn), sonar.task, rin, show.info = FALSE)
res
## task.id learner.id mmce.test.mean
## 1 Sonar-example classif.qda 0.2932367
## 2 Sonar-example classif.qda.preproc 0.1779848
上面的比较中,lrn是我们加了预处理的模型,而classif.qda是原始模型,sonar.task是我们的任务,而rin则是我们设置的交叉验证方法,iters代表迭代次数,stratify代表分层抽样。而交叉验证的折数没有设置,因此是默认值10。我们看到,mmce.test.mean代表的是在测试集上MMCE(Mean misclassification error)的均值,越小越好。因此有预处理的模型明显效果要更加好。
此外,还可以经过调试来获得佳的超参数和预处理参数。比如我们要留下多少个PCA主轴来做预测,与预测方法进行耦合来做模型筛选:
ps = makeParamSet(
makeIntegerParam("ppc.pcaComp", lower = 1, upper = getTaskNFeats(sonar.task)), #预处理参数网络
makeDiscreteParam("predict.method", values = c("plug-in", "debiased")) #预测模型参数网络
)
ctrl = makeTuneControlGrid(resolution = 10) # 调试粒度
res = tuneParams(lrn, sonar.task, rin, par.set = ps, control = ctrl, show.info = FALSE) # 调试
res #调试结果
## Tune result:
## Op. pars: ppc.pcaComp=21; predict.method=plug-in
## mmce.test.mean=0.1779848
as.data.frame(res$opt.path)[1:3]
## ppc.pcaComp predict.method mmce.test.mean
## 1 1 plug-in 0.5052450
## 2 8 plug-in 0.2449275
## 3 14 plug-in 0.2021394
## 4 21 plug-in 0.1779848
## 5 27 plug-in 0.2212560
## 6 34 plug-in 0.2452726
## 7 40 plug-in 0.2500345
## 8 47 plug-in 0.2452726
## 9 53 plug-in 0.2549344
## 10 60 plug-in 0.2932367
## 11 1 debiased 0.5000000
## 12 8 debiased 0.2833678
## 13 14 debiased 0.2453416
## 14 21 debiased 0.2837129
## 15 27 debiased 0.2547274
## 16 34 debiased 0.2886128
## 17 40 debiased 0.2741891
## 18 47 debiased 0.3075914
## 19 53 debiased 0.2642512
## 20 60 debiased 0.2830918
在官网中,还有对自定义预处理函数的方法进行了介绍,感兴趣的读者可以去参考链接中进行学习。
参考链接:
https://mlr.mlr-org.com/articles/tutorial/preproc.html#preprocessing-with-makepreprocwrappercaret