【译】使用R和Caret包处理类别不平衡分类问题。
原文是Handling Class Imbalance with R and Caret – An Introduction。该文章使用R的Caret包非常简洁的处理非平衡分类问题。这类题在笔者的工作中十分常见,所以记录下来,方便后面回顾。
现实世界的分类任务中,处理高度不平衡的分类问题非常具有挑战性。本文由两部分组成,主要介绍一些R和caret相关技巧。这些技巧可以在高度不平衡的分类场景下提高模型预测性能。本文是第一部分,以通用视角介绍如何在实战中使用这些技巧。第二部分着重介绍这些技巧相关的“坑”,这些坑十分容易在实战中遇见,需要谨记。
分类评估指标
当训练完一个分类器后,你需要确定这个分类器是否有效。现存有很多指标用来评估分类器性能,可以大致将它们分类为两类:
- 阈值相关:准确率(Accuracy),精度(Precision),召回(Recall)和F1分(F1 score)全部属于此类。此类方法需要使用特定阈值,计算混淆矩阵,然后根据此矩阵计算上面所有的指标。这类指标在非平衡的问题上无法有效的评估模型性能,因为统计软件一般使用默认0.5作为阈值,这种不适当的设定会导致大多数样本被归纳到主要的类别中。
- 阈值无关:ROC曲线下的面积(AUC)属于此类。它将真阳性看做是假阳性的函数,它们会随着切分阈值变化而变化。从概率的角度来看,此指标描述了随机从正样本和负样本各取一个样本,然后使用模型对其打分,正样本得分大于负样本得分的概率。(译者注:详细数学推导,可以参考笔者博文AUC的数学解释)
提高非平衡数据下模型性能的方法
下面会介绍一些常用技巧处理非平衡分类问题的技巧,但是这个列表并不会详细描述所有细节。简短起见,本文仅提供简短的概要。如果希望了解这些技术背后的细节,作者强烈建议阅读这篇博文。
- 样本加权:将更多的权重放到更少的类别的样本上,这样当模型在少量类别上犯错后,会得到更多惩罚
- 向下采样:在主要类别的样本中,随机移除一些样本。
- 向上采样:在较少类别的样本中,随机重复一些样本。
- SMOTE:向下采样,同时在较少的样本中,使用插值的方法随机生成新的样本。
需要着重指出的是,这类加权和抽样技巧对阈值依赖的指标有非常显著的影响,因为他们人工的将阈值朝着最优的地方移动。阈值无关的指标仍然会被这类方法改进,但是效果并没有声称的那样显著。
模拟设置
使用caret包中的twoClassSim函数模拟类别不平衡。我们将训练集和预测集分开模拟,每个数据集有5000个样本。除此之外,我们添加了20个有意义的变量,以及10个噪音变量。参数intercept控制整体不平衡的程度,最终数据集的正负样本比例为50:1。
library(dplyr) # for data manipulation
library(caret) # for model-building
library(DMwR) # for smote implementation
library(purrr) # for functional programming (map)
library(pROC) # for AUC calculations
set.seed(2969)
imbal_train <- twoClassSim(5000,
intercept = -25,
linearVars = 20,
noiseVars = 10)
imbal_test <- twoClassSim(5000,
intercept = -25,
linearVars = 20,
noiseVars = 10)
prop.table(table(imbal_train$Class))
##
## Class1 Class2
## 0.9796 0.0204
初始结果
gbm模型用于给这些数据建模,因为它可以处理模拟数据中潜在的交互和非线性部分。模型超参数,在训练数据上使用5折交叉验证调节。为了避免确定最后的阈值,AUC被用于评估模型的效果。因为需要交叉验证,下面的代码可能需要一点执行时间,所以可以减少参数repeats来加速实验过程,或者使用函数trainControl中的参数verboseIter=TRUE来追踪整个过程。
# Set up control function for training
ctrl <- trainControl(method = "repeatedcv",
number = 10,
repeats = 5,
summaryFunction = twoClassSummary,
classProbs = TRUE)
# Build a standard classifier using a gradient boosted machine
set.seed(5627)
orig_fit <- train(Class ~ .,
data = imbal_train,
method = "gbm",
verbose = FALSE,
metric = "ROC",
trControl = ctrl)
# Build custom AUC function to extract AUC
# from the caret model object
test_roc <- function(model, data) {
roc(data$Class,
predict(model, data, type = "prob")[, "Class2"])
}
orig_fit %>%
test_roc(data = imbal_test) %>%
auc()
## Area under the curve: 0.9575
整体而言,此模型的AUC为0.96,效果还不错。我们可以使用上面提到的技巧提高AUC吗?
使用加权或采样方法处理非平衡分类
加权和采样方法在caret包中使用非常简单。使用train函数中的weights参数,即可将权重加到建模过程中(此方法必须支持weights参数,完整列表参考这里)。采样方法可以使用trainControl函数中的sampling参数设置。为了得到一致的结果,seeds必须保持不变。
需要注意,使用采样方法时,只有训练数据需要采样,测试数据不能采样。这意味着需要在交叉验证过层中使用采样技巧。caret的作者Max Kuhn给出了一个非常好的例子,描述如果不注意这个细节将会发生什么,具体可以参考这里。下面代码演示如何正确使用采样技巧。
# Create model weights (they sum to one)
model_weights <- ifelse(imbal_train$Class == "Class1",
(1/table(imbal_train$Class)[1]) * 0.5,
(1/table(imbal_train$Class)[2]) * 0.5)
# Use the same seed to ensure same cross-validation splits
ctrl$seeds <- orig_fit$control$seeds
# Build weighted model
weighted_fit <- train(Class ~ .,
data = imbal_train,
method = "gbm",
verbose = FALSE,
weights = model_weights,
metric = "ROC",
trControl = ctrl)
# Build down-sampled model
ctrl$sampling <- "down"
down_fit <- train(Class ~ .,
data = imbal_train,
method = "gbm",
verbose = FALSE,
metric = "ROC",
trControl = ctrl)
# Build up-sampled model
ctrl$sampling <- "up"
up_fit <- train(Class ~ .,
data = imbal_train,
method = "gbm",
verbose = FALSE,
metric = "ROC",
trControl = ctrl)
# Build smote model
ctrl$sampling <- "smote"
smote_fit <- train(Class ~ .,
data = imbal_train,
method = "gbm",
verbose = FALSE,
metric = "ROC",
trControl = ctrl)
测试集的AUC表明,相比于原始方法,权重方法或采样方法有明显的提升。
# Examine results for test set
model_list <- list(original = orig_fit,
weighted = weighted_fit,
down = down_fit,
up = up_fit,
SMOTE = smote_fit)
model_list_roc <- model_list %>%
map(test_roc, data = imbal_test)
model_list_roc %>%
map(auc)
## $original
## Area under the curve: 0.9575
##
## $weighted
## Area under the curve: 0.9804
##
## $down
## Area under the curve: 0.9705
##
## $up
## Area under the curve: 0.9759
##
## $SMOTE
## Area under the curve: 0.976
我们可以通过ROC曲线来观察,加权和采样方法在哪些地方优于原始方法。我们可以看出加权模型在整个ROC曲线上都优于其他方法,同时原始方法的假阳率在0~0.25之间明显比其他方法差。这表明其他模型在早期有更好的检索数字。即当样本被模型计算为较少类型的高概率样本时,其他算法可以更准确的确定真阳样本。
results_list_roc <- list(NA)
num_mod <- 1
for(the_roc in model_list_roc){
results_list_roc[[num_mod]] <-
data_frame(tpr = the_roc$sensitivities,
fpr = 1 - the_roc$specificities,
model = names(model_list)[num_mod])
num_mod <- num_mod + 1
}
results_df_roc <- bind_rows(results_list_roc)
# Plot ROC curve for all 5 models
custom_col <- c("#000000", "#009E73", "#0072B2", "#D55E00", "#CC79A7")
ggplot(aes(x = fpr, y = tpr, group = model), data = results_df_roc) +
geom_line(aes(color = model), size = 1) +
scale_color_manual(values = custom_col) +
geom_abline(intercept = 0, slope = 1, color = "gray", size = 1) +
theme_bw(base_size = 18)
最后的思考
上面总结出了一些改进非平衡分类问题性能的技巧。虽然在模拟数据上,加权方法优于采样方法,但是这并不是在所有数据下都是这样。因此,在你的数据集上,必须尝试不同的方法确认最优的方案。笔者发现在很多不同的非平衡数据集上,采样或加权方法并没有显著的AUC提升。下偏博文,笔者将介绍一些使用AUC的“坑”,以及其他分类器指标。请继续收看!
您的打赏是对我最大的鼓励!