TL;博士
见标题。
动机
我希望得到一个符合“(1)否,(2)不适用,因为(1)”的规范答案,我们可以用它来解决许多关于不平衡数据集和过采样的错误问题。如果我的先入之见被证明是错误的,我会很高兴。神话般的赏金等待着勇敢的回答者。
我的论点
我对我们收到的许多问题感到困惑不平衡类标签。不平衡的班级似乎很糟糕。和过采样不言而喻,少数群体被视为有助于解决不言而喻的问题。许多带有两个标签的问题都会继续询问如何在某些特定情况下执行过采样。
我既不了解不平衡类会带来什么问题,也不了解过采样应该如何解决这些问题。
在我看来,不平衡的数据根本不会造成问题。应该对类成员概率进行建模,这些概率可能很小。只要它们是正确的,就没有问题。当然,不应将准确率用作在分类问题中最大化的 KPI。或者计算分类阈值。相反,应该使用适当的方法评估整个预测分布的质量计分规则. Tetlock 的Superforecasting是预测不平衡类的精彩且非常易读的介绍,即使书中没有明确提到这一点。
有关的
评论中的讨论引发了许多相关的话题。
- 过采样、欠采样和 SMOTE 解决了什么问题?IMO,这个问题没有令人满意的答案。(根据我的怀疑,这可能是因为没有问题。)
- 什么时候不平衡数据真的是机器学习中的问题?共识似乎是“不是”。我可能会投票结束这个问题作为那个问题的副本。
IcannotFixThis 的答案似乎假定 (1) 我们试图最大化的 KPI 是准确度,以及 (2) 准确度是分类模型评估的合适 KPI。它不是。这可能是整个讨论的关键之一。
AdamO 的回答侧重于不平衡因素的估计精度低。这当然是一个有效的担忧,可能是我名义问题的答案。但是过采样在这里没有帮助,就像我们可以通过简单地将每个观察值复制十次来在任何普通回归中获得更精确的估计一样。
类不平衡问题的根本原因是什么?这里的一些评论呼应了我的怀疑,即没有问题。单一答案再次隐含地假定我们使用准确性作为 KPI,我觉得这并不令人满意。
是否存在重新平衡/重新加权明显提高准确性的不平衡学习问题?是相关的,但前提是准确性作为评估措施。(我认为这不是一个好的选择。)
概括
上面的线索显然可以总结如下。
- 稀有类(在结果和预测变量中)是一个问题,因为参数估计和预测具有高方差/低精度。这不能通过过采样来解决。(从某种意义上说,获得更多代表总体的数据总是更好的,选择性抽样会根据我和其他人的模拟产生偏差。)
- 如果我们通过准确性评估我们的模型,稀有类是一个“问题”。但准确度并不是评估分类模型的好方法。(我确实考虑过在我的模拟中包括准确性,但是我需要设置一个分类阈值,这是一个密切相关的错误问题,而且问题已经足够长了。)
一个例子
让我们模拟一个插图。具体来说,我们将模拟十个预测变量,其中只有一个实际上对罕见结果有影响。我们将研究两种可用于概率分类的算法:逻辑回归和随机森林.
在每种情况下,我们将模型应用于完整数据集或过采样平衡数据集,其中包含稀有类的所有实例和来自多数类的相同数量的样本(因此过采样数据集小于完整数据集)。
对于逻辑回归,我们将评估每个模型是否真正恢复了用于生成数据的原始系数。此外,对于这两种方法,我们将计算概率类成员预测,并在使用与原始训练数据相同的数据生成过程生成的保留数据上评估这些预测。预测是否真正匹配结果将使用最常见的正确评分规则之一Brier 评分进行评估。
我们将运行 100 次模拟。(加速这个只会使 beanplots 更加狭窄,并使模拟运行时间超过一杯咖啡。)每个模拟都包含样品。预测器形成一个具有条目均匀分布的矩阵. 只有第一个预测变量实际上有影响;真正的 DGP 是
这使得少数 TRUE 类的模拟发生率在 2% 到 3% 之间:
让我们运行模拟。将完整的数据集输入逻辑回归,我们(不出所料)得到无偏的参数估计(真实的参数值由红色菱形表示):
但是,如果我们将过采样数据集提供给逻辑回归,则截距参数会出现严重偏差:
对于逻辑回归和随机森林,让我们比较适合“原始”数据集和过采样数据集的模型之间的 Brier 分数。请记住,越小越好:
在每种情况下,从完整数据集派生的预测分布都比从过采样数据集派生的预测分布要好得多。
我的结论是,不平衡的类不是问题,过采样并不能缓解这个非问题,而是无缘无故地引入了偏差和更糟糕的预测。
我的错误在哪里?
一个警告
我很乐意承认过采样有一个应用:如果
- 我们正在处理一个罕见的结果,并且
- 评估结果很容易或便宜,但是
- 评估预测变量是困难的或昂贵的
一个典型的例子是罕见疾病的全基因组关联研究(GWAS)。检测一个人是否患有某种特定疾病比对他们的血液进行基因分型要容易得多。(我参与了一些PTSD的 GWAS 。)如果预算有限,根据结果进行筛选并确保样本中有“足够”的罕见病例可能是有意义的。
然而,人们需要在节省金钱和上述损失之间取得平衡——我的观点是,CV 中关于不平衡数据集的问题并没有提到这种权衡,而是将不平衡类视为不言而喻的邪恶,完全不同于任何样品采集费用。
R代码
library(randomForest)
library(beanplot)
nn_train <- nn_test <- 1e4
n_sims <- 1e2
true_coefficients <- c(-7, 5, rep(0, 9))
incidence_train <- rep(NA, n_sims)
model_logistic_coefficients <-
model_logistic_oversampled_coefficients <-
matrix(NA, nrow=n_sims, ncol=length(true_coefficients))
brier_score_logistic <- brier_score_logistic_oversampled <-
brier_score_randomForest <-
brier_score_randomForest_oversampled <-
rep(NA, n_sims)
pb <- winProgressBar(max=n_sims)
for ( ii in 1:n_sims ) {
setWinProgressBar(pb,ii,paste(ii,"of",n_sims))
set.seed(ii)
while ( TRUE ) { # make sure we even have the minority
# class
predictors_train <- matrix(
runif(nn_train*(length(true_coefficients) - 1)),
nrow=nn_train)
logit_train <-
cbind(1, predictors_train)%*%true_coefficients
probability_train <- 1/(1+exp(-logit_train))
outcome_train <- factor(runif(nn_train) <=
probability_train)
if ( sum(incidence_train[ii] <-
sum(outcome_train==TRUE))>0 ) break
}
dataset_train <- data.frame(outcome=outcome_train,
predictors_train)
index <- c(which(outcome_train==TRUE),
sample(which(outcome_train==FALSE),
sum(outcome_train==TRUE)))
model_logistic <- glm(outcome~., dataset_train,
family="binomial")
model_logistic_oversampled <- glm(outcome~.,
dataset_train[index, ], family="binomial")
model_logistic_coefficients[ii, ] <-
coefficients(model_logistic)
model_logistic_oversampled_coefficients[ii, ] <-
coefficients(model_logistic_oversampled)
model_randomForest <- randomForest(outcome~., dataset_train)
model_randomForest_oversampled <-
randomForest(outcome~., dataset_train, subset=index)
predictors_test <- matrix(runif(nn_test *
(length(true_coefficients) - 1)), nrow=nn_test)
logit_test <- cbind(1, predictors_test)%*%true_coefficients
probability_test <- 1/(1+exp(-logit_test))
outcome_test <- factor(runif(nn_test)<=probability_test)
dataset_test <- data.frame(outcome=outcome_test,
predictors_test)
prediction_logistic <- predict(model_logistic, dataset_test,
type="response")
brier_score_logistic[ii] <- mean((prediction_logistic -
(outcome_test==TRUE))^2)
prediction_logistic_oversampled <-
predict(model_logistic_oversampled, dataset_test,
type="response")
brier_score_logistic_oversampled[ii] <-
mean((prediction_logistic_oversampled -
(outcome_test==TRUE))^2)
prediction_randomForest <- predict(model_randomForest,
dataset_test, type="prob")
brier_score_randomForest[ii] <-
mean((prediction_randomForest[,2]-(outcome_test==TRUE))^2)
prediction_randomForest_oversampled <-
predict(model_randomForest_oversampled,
dataset_test, type="prob")
brier_score_randomForest_oversampled[ii] <-
mean((prediction_randomForest_oversampled[, 2] -
(outcome_test==TRUE))^2)
}
close(pb)
hist(incidence_train, breaks=seq(min(incidence_train)-.5,
max(incidence_train) + .5),
col="lightgray",
main=paste("Minority class incidence out of",
nn_train,"training samples"), xlab="")
ylim <- range(c(model_logistic_coefficients,
model_logistic_oversampled_coefficients))
beanplot(data.frame(model_logistic_coefficients),
what=c(0,1,0,0), col="lightgray", xaxt="n", ylim=ylim,
main="Logistic regression: estimated coefficients")
axis(1, at=seq_along(true_coefficients),
c("Intercept", paste("Predictor", 1:(length(true_coefficients)
- 1))), las=3)
points(true_coefficients, pch=23, bg="red")
beanplot(data.frame(model_logistic_oversampled_coefficients),
what=c(0, 1, 0, 0), col="lightgray", xaxt="n", ylim=ylim,
main="Logistic regression (oversampled): estimated
coefficients")
axis(1, at=seq_along(true_coefficients),
c("Intercept", paste("Predictor", 1:(length(true_coefficients)
- 1))), las=3)
points(true_coefficients, pch=23, bg="red")
beanplot(data.frame(Raw=brier_score_logistic,
Oversampled=brier_score_logistic_oversampled),
what=c(0,1,0,0), col="lightgray", main="Logistic regression:
Brier scores")
beanplot(data.frame(Raw=brier_score_randomForest,
Oversampled=brier_score_randomForest_oversampled),
what=c(0,1,0,0), col="lightgray",
main="Random Forest: Brier scores")