You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 10:58:09