如何在MLR3管道中为仅用于分层的分类变量使用SMOTE上采样?
我用MLR3管道构建二分类模型,数据集存在类别不平衡,因此采用SMOTE做过采样补偿。所有预测变量均为数值型,但有两个分类变量仅用于和目标变量一同完成训练/测试拆分、嵌套交叉验证拆分的分层操作,不会作为预测变量使用。
遇到的问题:即便SMOTE不使用这些分层变量,只要它们存在于任务中,SMOTE就无法插入新生成的数据,报错信息如下:
Error: Cannot rbind data to task 'diagnostic', missing the following mandatory columns: site, hiv
This happened PipeOp smote's $train()
Execution halted
想请教:有没有无需完全移除分层变量的解决办法?还是这是MLR3中SMOTE实现的固有局限?另外我试过在SMOTE PipeOp前仅选择预测变量,但没有效果,问题似乎出在SMOTE尝试将新数据合并回原有训练数据的环节。
示例代码
library(mlr3verse) library(data.table) library(doFuture) plan(multisession, workers = availableCores()-1) dt <- data.table( record_id = 1:100, outcome = sample(c('negative','positive'),100,T,c(0.7,0.3)), group = sample(c('A','B','C'),100,T,c(0.4,0.3,0.3)), x = rnorm(100), y = rnorm(100, 3, 1.5), z = rnorm(100, -5, 2) ) task <- as_task_classif(dt, target='outcome', id='test', positive='positive') task$set_col_roles('record_id', roles = 'name') task$set_col_roles('outcome', roles = c('target','stratum')) task$set_col_roles('group', roles = 'stratum') set.seed(682) splits <- partition(task, ratio = 0.75, stratify = T) po_scaler <- po("scale", center = TRUE, scale = TRUE) po_smote <- po("smote") lrn_glmnet <- lrn("classif.glmnet", predict_type = "prob", fallback = lrn("classif.featureless", predict_type = "prob"), encapsulate = c(train = "evaluate", predict = "evaluate"), id = "glmnet" ) graph <- po_scaler %>>% po_smote %>>% lrn_glmnet search_space <- ps( glmnet.alpha = p_dbl(0, 1), glmnet.lambda = p_dbl(0.01, 10, logscale = TRUE) ) glearner = as_learner(graph) glearner$id = "glearner" glearner$predict_type = "prob" inner_resampling = rsmp("repeated_cv", folds = 3, repeats = 10) at = auto_tuner( tuner = tnr("mbo"), learner = glearner, resampling = inner_resampling, measure = msr("classif.auc"), search_space = search_space, terminator =trm("combo", list( trm("stagnation", iters = 10, threshold = 0.0001), trm("clock_time", stop_time = Sys.time() + 60^2) ), any = TRUE)) rsmp_rcv <- rsmp("cv", folds = 5) rr = resample(task, at, rsmp_rcv)
这个问题的核心原因是:MLR3的PipeOpSmote仅基于标记为feature的列生成新样本,新样本中不会包含stratum(分层)角色的列;但这些分层列属于任务的强制列,SMOTE尝试将新样本合并回原任务时,就会因缺少这些列而报错。
无需移除分层变量的解决思路是拆分管道分支:一个分支处理SMOTE过采样,另一个分支保留分层变量,最后合并两个分支的结果,这样既保留了分层变量用于拆分,又能正常执行SMOTE。
修改后的完整管道代码如下:
library(mlr3verse) library(data.table) library(doFuture) plan(multisession, workers = availableCores()-1) dt <- data.table( record_id = 1:100, outcome = sample(c('negative','positive'),100,T,c(0.7,0.3)), group = sample(c('A','B','C'),100,T,c(0.4,0.3,0.3)), x = rnorm(100), y = rnorm(100, 3, 1.5), z = rnorm(100, -5, 2) ) task <- as_task_classif(dt, target='outcome', id='test', positive='positive') task$set_col_roles('record_id', roles = 'name') task$set_col_roles('outcome', roles = c('target','stratum')) task$set_col_roles('group', roles = 'stratum') set.seed(682) splits <- partition(task, ratio = 0.75, stratify = T) po_scaler <- po("scale", center = TRUE, scale = TRUE) po_smote <- po("smote") lrn_glmnet <- lrn("classif.glmnet", predict_type = "prob", fallback = lrn("classif.featureless", predict_type = "prob"), encapsulate = c(train = "evaluate", predict = "evaluate"), id = "glmnet" ) # -------------------------- 关键修改部分 -------------------------- # 1. 复制任务,生成两个分支 po_copy <- po("copy", outnum = 2) # 分支1:仅保留特征+目标变量,执行标准化和SMOTE branch_smote <- po("select", selector = selector_invert(c("group"))) %>>% po_scaler %>>% po_smote # 分支2:仅保留分层变量和目标变量(确保后续合并时匹配样本) branch_stratum <- po("select", selector = c("group", "outcome")) # 2. 合并两个分支的结果 po_merge <- po("cbind", param_vals = list(keep_original_cols = TRUE)) # 3. 构建完整管道 graph <- po_copy %>>% gunion(list(branch_smote, branch_stratum)) %>>% po_merge %>>% lrn_glmnet # ----------------------------------------------------------------- search_space <- ps( glmnet.alpha = p_dbl(0, 1), glmnet.lambda = p_dbl(0.01, 10, logscale = TRUE) ) glearner = as_learner(graph) glearner$id = "glearner" glearner$predict_type = "prob" inner_resampling = rsmp("repeated_cv", folds = 3, repeats = 10) at = auto_tuner( tuner = tnr("mbo"), learner = glearner, resampling = inner_resampling, measure = msr("classif.auc"), search_space = search_space, terminator =trm("combo", list( trm("stagnation", iters = 10, threshold = 0.0001), trm("clock_time", stop_time = Sys.time() + 60^2) ), any = TRUE)) rsmp_rcv <- rsmp("cv", folds = 5) rr = resample(task, at, rsmp_rcv)
逻辑说明
po_copy将输入任务复制为两份,分别进入不同分支;branch_smote分支移除分层变量,仅处理用于建模的特征列,避免SMOTE生成样本时丢失强制列的问题;branch_stratum分支保留分层变量和目标变量,确保后续合并后任务仍包含分层信息;po_merge将两个分支的结果合并,得到包含所有必要列(特征、目标、分层变量)的任务,供后续模型训练使用。
这种方式既保留了分层变量用于交叉验证的分层拆分,又能正常执行SMOTE过采样,无需移除分层变量。
内容的提问来源于stack exchange,提问作者Gian

