大数跨境
0
0

vtreat 数据集拆分-10

vtreat 数据集拆分-10 数据分析学习与实践
2021-03-02
1
导读:vtreat数据集拆分1. 动机vtreat提供了许多数据集拆分或交叉验证的工具。隐式的:有些服务是隐含的、

vtreat数据集拆分

1. 动机

vtreat提供了许多数据集拆分或交叉验证的工具。

  • 隐式的:有些服务是隐含的、不可见的,如高自由度衍生变量的样本得分的模拟输出(例如catB,catN,catD,和catP)。

  • 显式的:一些服务是显式的、可见的,例如vtreat::mkCrossFrameCExperiment和vtreat::mkCrossFrameNExperiment(请参见上篇文章)。甚至还有一个面向用户的交叉验证计划器:vtreat::buildEvalSets(请尝试help(buildEvalSets)获取详细信息)。

结构化交叉验证已经有了很多文章,但“随机测试/训练拆分”并不能满足所有需求。关键是回顾历史,随机测试/训练拆分核心目的是如何模型的效果最好。它本质上不是像随机对照试验中那样的实际实验设计。为了成为有效的仿真,您在未来预测中,必须尽量保留原有数据内在结构。

总体思路是:更好的拆分方式目的在于建立在实践中实际表现更好的模型。并将这种拆分方式移植回您的评估程序中,可以使您更好地估计此模型的性能。

随机测试/训练拆分尝试保留以下内容:(未来应用数据、测试数据、训练数据)

  1. 未来应用数据可以与训练数据交换(在模型构建之前)。

  2. 未来应用数据仍然可以与测试数据交换(可以在模型构建之后,因为在模型构建中不使用测试数据)

请注意,如果概念发生了变化(也称为非平稳性问题),则未来应用数据已经无法与训练数据进行统计交换(因此无法保留您从未拥有的属性)。但是,即使您的未来数据开始可以与训练数据进行交换,训练数据与未来应用数据之间也至少存在是否已经建模的差异:

  1. 未来应用数据倾向于在训练数据之后形成;

这通常是问题解决计划的未声明结构:使用过去的注释数据为将来的未注释数据构建监督模型。

2. 例子

通过以上讨论,我们回到了当下的问题。在创建适当的测试/训练组时,我们必须考虑以下问题:

  • 分层:分层保留结果变量(或任何其他变量,但vtreat仅对y进行分层)的分布或普遍性。例如,对于目标类别患病率为15%的分类问题,对y进行分层可确保训练集和测试集的目标类别患病率均精确为15%(或尽可能接近),而不仅仅是“大约” 15%,就像简单的随机测试/训练拆分一样。这对于建模稀有事件尤其重要。

  • 分组:“分组”是指不将密切相关的事件分为测试和训练:如果一组row构成一个“组”,那么我们希望所有这些行作为一个组进入测试或训练,不切分。典型示例:



    • 来自单个客户的多个事件(因为您确实希望模型预测新客户的行为)

    • 记录在时间上紧密联系在一起(因为后一个应用数据记录在时间上不会接近原始培训记录)

    • <

  • 结构化反向测试:结构化反向测试保留了按时间排序的事件的顺序。在金融领域,使用周一和周三的数据来建立介于周二之间的价格模型被认为是荒谬的,但是如果使用简单的随机分割对训练和评估数据进行分区,则可能会犯这种错误。

我们的目标是vtreat成为一个与域无关的针对y感知数据调节器。因此,vtreat对y分层其数据拆分。版本之前0.5.26 vtreat使用简单的随机分割。现在,版本0.5.26(当前可从Github获得)vtreat默认默认为分层抽样。但记录分组的本地性或时间顺序之类的事情是领域问题,应由分析人员处理。

任何拆分或分层计划都需要领域知识,并且应该代表以下其中某两个相互竞争的目标之间对领域敏感的权衡:

  • 随机拆分

  • 结果变量在各个拆分中分布的稳定性

  • 不切入“原子”记录组

  • 不使用未来的数据来预测过去

  • 每个分组中都有大量数据

  • 具有打散的训练和测试数据

由于版本0.5.26 vtreat通过允许用户指定的数据分割功能,其中所述分析师可以编码其所需的域不变量支持。用户实现的拆分功能应具有签名

function(nRows,nSplits,dframe,y) 其中:

  • nRows 是您要拆分的行数

  • nSplits 是您想要的分组数量

  • dframe 是原始数据框(可能包含所需的分组或顺序列),

  • y 结果变量是否转换为数值

该函数应返回list of list。第i个元素应该有个插槽,其中包含train与app,其中[[i]]$train指定用于拟合模型的训练数据,用[[i]]$app的数据评估模型的准确性。

通过示例最容易显示:

 
  1. vtreat::kWayStratifiedY(3,2,NULL,NULL)


  2. ## [[1]]

  3. ## [[1]]$train

  4. ## [1] 2 3

  5. ##

  6. ## [[1]]$app

  7. ## [1] 1

  8. ##

  9. ##

  10. ## [[2]]

  11. ## [[2]]$train

  12. ## [1] 1

  13. ##

  14. ## [[2]]$app

  15. ## [1] 3 2

  16. ##

  17. ##

  18. ## attr(,"splitmethod")

  19. ## [1] "kwaycross"

如我们所见,vtreat::oneWayHoldout构建了三个拆分集,其中每个集中的“应用数据”是单个行索引,而对应的训练行是互补行索引。这是一劳永逸的交叉验证plan。

vtreat 提供了许多交叉验证拆分/计划实施:

  • kWayStratifiedY:k-way y分层交叉验证。这是vtreat默认的拆分计划。

  • makekWayCrossValidationGroupedByColumn:k-way y分层交叉验证,保留分组(例如,与单个客户或患者相对应的所有行,等等)。这是一个复杂的拆分计划,仅在绝对需要时才建议使用。

  • kWayCrossValidation:k-way非分层交叉验证

  • oneWayHoldout:一劳永逸的交叉验证。注意,一种支持方法可能会泄漏目标期望值,因此对于嵌套模型情况不是首选。

函数buildEvalSets将上述拆分函数之一作为输入,并输出一个交叉验证计划,该计划将实例化所需的拆分,同时还防止出现极端情况。在使用designTreatments[N\C]或设计vtreat变量treat计划时,还可以显式指定拆分计划mkCrossFrame[N\C]Experiment。

对于超出分层的问题,用户可能希望执行自己的拆分计划。然后,这样的功能可以被传递到任何vtreat操作,需要一个splitFunction参数(如mkCrossFrameNExperiment,designTreatmentsN,以及更多)。例如,我们可以通过定义的用户splitFn到vtreat::buildEvalSets如下:

例如,要使用用户提供的拆分功能,我们将编写以下功能定义。

 
  1. # 这种方法不是一个好主意,因为数据可能具有与该拆分相同的模式跨步。在技术上任何拆分都是可行的,但是我们通常使用伪随机结构(在许多潜在的拆分调用中并不相同) ),以使此类结构不太可能匹配。

  2. modularSplit <- function(nRows,nSplits,dframe,y) {

  3. group <- seq_len(nRows) %% nSplits

  4. lapply(unique(group),

  5. function(gi) {

  6. list(train=which(group!=gi),

  7. app=which(group==gi))

  8. })

  9. }

然后,该函数可被传递到任何vtreat操作,需要一个splitFunction参数(如mkCrossFrameNExperiment,designTreatmentsN,以及更多)。例如,我们可以通过定义的用户splitFn到vtreat::buildEvalSets如下:

 
  1. vtreat::buildEvalSets(nRows=25,nSplits=3,splitFunction=modularSplit)


  2. ## [[1]]

  3. ## [[1]]$train

  4. ## [1] 2 3 5 6 8 9 11 12 14 15 17 18 20 21 23 24

  5. ##

  6. ## [[1]]$app

  7. ## [1] 1 4 7 10 13 16 19 22 25

  8. ##

  9. ##

  10. ## [[2]]

  11. ## [[2]]$train

  12. ## [1] 1 3 4 6 7 9 10 12 13 15 16 18 19 21 22 24 25

  13. ##

  14. ## [[2]]$app

  15. ## [1] 2 5 8 11 14 17 20 23

  16. ##

  17. ##

  18. ## [[3]]

  19. ## [[3]]$train

  20. ## [1] 1 2 4 5 7 8 10 11 13 14 16 17 19 20 22 23 25

  21. ##

  22. ## [[3]]$app

  23. ## [1] 3 6 9 12 15 18 21 24

  24. ##

  25. ##

  26. ## attr(,"splitmethod")

  27. ## [1] "userfunction"

如上所述,vtreat库代码将尝试使用用户函数进行拆分,但是在用户函数可能无法处理的特殊情况下(例如,行太少,组太少以及依此类推)。因此,用户代码可以认为它处于合理的情况下(如果无法处理给出的情况,甚至可以安全地返回NULL)。例如,检测并纠正了以下不良拆分:

 
  1. badSplit <- function(nRows,nSplits,dframe,y) {

  2. list(list(train=seq_len(nRows),app=seq_len(nRows)))

  3. }

  4. vtreat::buildEvalSets(nRows=5,nSplits=3,splitFunction=badSplit)


  5. ## Warning in doTryCatch(return(expr), name, parentenv, handler):

  6. ## vtreat::buildEvalSets user carve-up rejected: train and application slots

  7. ## overlap


  8. ## [[1]]

  9. ## [[1]]$train

  10. ## [1] 2 3 4 5

  11. ##

  12. ## [[1]]$app

  13. ## [1] 1

  14. ##

  15. ##

  16. ## [[2]]

  17. ## [[2]]$train

  18. ## [1] 1 3 4

  19. ##

  20. ## [[2]]$app

  21. ## [1] 5 2

  22. ##

  23. ##

  24. ## [[3]]

  25. ## [[3]]$train

  26. ## [1] 1 2 5

  27. ##

  28. ## [[3]]$app

  29. ## [1] 4 3

  30. ##

  31. ##

  32. ## attr(,"splitmethod")

  33. ## [1] "kwaycross"

请注意,上面返回的拆分不满足所有原始需求,但可以保证是有用的数据分区。

3. 实操

文件outOfSample.R包含有效的示例。特别是,我们建议您在键入以下任意一项时运行显示的代码:

  • help(kWayCrossValidation)

  • help(kWayStratifiedY)

  • help(makekWayCrossValidationGroupedByColumn)

  • help(oneWayHoldout)

例如,从help(kWayStratifiedY)我们可以看到,y分层时,每个分布比不折时的相似得多:

 
  1. library('vtreat')


  2. set.seed(23255)

  3. d <- data.frame(y=sin(1:100))


  4. # stratified 5-fold cross validation

  5. pStrat <- kWayStratifiedY(nrow(d),5,d,d$y)

  6. # check if the split is a good partition

  7. check = vtreat::problemAppPlan(nrow(d),5,pStrat,TRUE)

  8. if(is.null(check)) {

  9. print("Plan is good")

  10. } else {

  11. print(paste0("Problem with plan: ", check))

  12. }

 
  1. d$stratGroup <- vtreat::getSplitPlanAppLabels(nrow(d),pStrat)


  2. # unstratified 5-fold cross validation

  3. pSimple <- kWayCrossValidation(nrow(d),5,d,d$y)

  4. # check if the split is a good partition; return null if so

  5. check = vtreat::problemAppPlan(nrow(d),5,pSimple,TRUE)

  6. if(is.null(check)) {

  7. print("Plan is good")

  8. } else {

  9. print(paste0("Problem with plan: ", check))

  10. }

 
  1. d$simpleGroup <- vtreat::getSplitPlanAppLabels(nrow(d),pSimple)


  2. # mean(y) for each fold, unstratified

  3. tapply(d$y,d$simpleGroup,mean)


  4. ## 1 2 3 4 5

  5. ## -0.059622525 0.068139081 -0.007774052 0.099774019 -0.106875074

 
  1. # standard error of mean(y)

  2. sd(tapply(d$y,d$simpleGroup,mean))


  3. ## 1 2 3 4 5

  4. ## 0.008797500 -0.011530915 -0.010448401 0.009648950 -0.002825685

 
  1. # standard error of mean(y)

  2. sd(tapply(d$y,d$stratGroup,mean))


  3. ## [1] 0.01015539

总结:控制交叉验证中数据的分割方式(保留y分布,组甚至顺序)可以提高在此类数据上训练的模型的真实性能。显然,这增加了一些复杂性和“出错的地方”,但这是一个值得学习的话题。


【声明】内容源于网络
0
0
数据分析学习与实践
数据分析,数据科学,线性代数,统计学,AI,python,可视化,excel
内容 343
粉丝 0
数据分析学习与实践 数据分析,数据科学,线性代数,统计学,AI,python,可视化,excel
总阅读14
粉丝0
内容343