不平衡的数据集是否存在问题,以及(如何)过采样(声称)有帮助?

机器算法验证 不平衡类 过采样
2022-01-22 02:18:37

TL;博士

见标题。


动机

我希望得到一个符合“(1)否,(2)不适用,因为(1)”的规范答案,我们可以用它来解决许多关于不平衡数据集和过采样的错误问题。如果我的先入之见被证明是错误的,我会很高兴。神话般的赏金等待着勇敢的回答者。


我的论点

我对我们收到的许多问题感到困惑标签。不平衡的班级似乎很糟糕不言而喻,少数群体被视为有助于解决不言而喻的问题。许多带有两个标签的问题都会继续询问如何在某些特定情况下执行过采样。

我既不了解不平衡类会带来什么问题,也不了​​解过采样应该如何解决这些问题。

在我看来,不平衡的数据根本不会造成问题。应该对类成员概率进行建模,这些概率可能很小。只要它们是正确的,就没有问题。当然,不应将准确率用作在分类问题中最大化的 KPI。或者计算分类阈值相反,应该使用适当的方法评估整个预测分布的质量. Tetlock 的Superforecasting是预测不平衡类的精彩且非常易读的介绍,即使书中没有明确提到这一点。


有关的

评论中的讨论引发了许多相关的话题。

IcannotFixThis 的答案似乎假定 (1) 我们试图最大化的 KPI 是准确度,以及 (2) 准确度是分类模型评估的合适 KPI。它不是。这可能是整个讨论的关键之一。

AdamO 的回答侧重于不平衡因素的估计精度低。这当然是一个有效的担忧,可能是我名义问题的答案。但是过采样在这里没有帮助,就像我们可以通过简单地将每个观察值复制十次来在任何普通回归中获得更精确的估计一样。


概括

上面的线索显然可以总结如下。

  • 稀有类(在结果和预测变量中)是一个问题,因为参数估计和预测具有高方差/低精度。这不能通过过采样来解决。(从某种意义上说,获得更多代表总体的数据总是更好的,选择性抽样会根据我和其他人的模拟产生偏差。)
  • 如果我们通过准确性评估我们的模型,稀有类是一个“问题”。准确度并​​不是评估分类模型的好方法(我确实考虑过在我的模拟中包括准确性,但是我需要设置一个分类阈值,这是一个密切相关的错误问题,而且问题已经足够长了。)

一个例子

让我们模拟一个插图。具体来说,我们将模拟十个预测变量,其中只有一个实际上对罕见结果有影响。我们将研究两种可用于概率分类的算法:.

在每种情况下,我们将模型应用于完整数据集或过采样平衡数据集,其中包含稀有类的所有实例和来自多数类的相同数量的样本(因此过采样数据集小于完整数据集)。

对于逻辑回归,我们将评估每个模型是否真正恢复了用于生成数据的原始系数。此外,对于这两种方法,我们将计算概率类成员预测,并在使用与原始训练数据相同的数据生成过程生成的保留数据上评估这些预测。预测是否真正匹配结果将使用最常见的正确评分规则之一Brier 评分进行评估。

我们将运行 100 次模拟。(加速这个只会使 beanplots 更加狭窄,并使模拟运行时间超过一杯咖啡。)每个模拟都包含n=10,000样品。预测器形成一个10,000×10具有条目均匀分布的矩阵[0,1]. 只有第一个预测变量实际上有影响;真正的 DGP 是

logit(pi)=7+5xi1.

这使得少数 TRUE 类的模拟发生率在 2% 到 3% 之间:

培训_发病率

让我们运行模拟。将完整的数据集输入逻辑回归,我们(不出所料)得到无偏的参数估计(真实的参数值由红色菱形表示):

逻辑系数

但是,如果我们将过采样数据集提供给逻辑回归,则截距参数会出现严重偏差

logistic_coefficients_oversampled

对于逻辑回归和随机森林,让我们比较适合“原始”数据集和过采样数据集的模型之间的 Brier 分数。请记住,越小越好:

logistic_regression_Brier_scores

Random_Forest_Brier_score

在每种情况下,从完整数据集派生的预测分布都比从过采样数据集派生的预测分布要好得多。

我的结论是,不平衡的类不是问题,过采样并不能缓解这个非问题,而是无缘无故地引入了偏差和更糟糕的预测。

我的错误在哪里?


一个警告

我很乐意承认过采样有一个应用:如果

  1. 我们正在处理一个罕见的结果,并且
  2. 评估结果很容易或便宜,但是
  3. 评估预测变量是困难的或昂贵的

一个典型的例子是罕见疾病的全基因组关联研究(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")
1个回答

我想首先附议问题中的一个声明:

...我的观点是,CV 中关于不平衡数据集的问题并没有提到这种权衡,而是将不平衡类视为不言而喻的邪恶,完全不考虑样本收集的任何成本。

我也有同样的担忧,我在这里这里的问题旨在提出反证据,即这一个“不言而喻的邪恶”,缺乏答案(即使有赏金)表明它不是。许多博客文章和学术论文也没有说明这一点。分类器可能会遇到数据集不平衡的问题,但仅限于数据集非常小的情况,因此我的回答与例外情况有关,通常不证明对数据集重新采样是合理的。

一个类不平衡问题,但它不是由不平衡本身引起的,而是因为少数类的例子太少,无法充分描述它的统计分布。如问题中所述,这意味着参数估计值可能具有高方差,这是真的,但这可能会导致偏向于多数类别(而不是平等地影响两个类别)。在逻辑回归的情况下,King 和 Zeng 对此进行了讨论,

3加里·金和曾朗彻。2001. “罕见事件数据中的逻辑回归”。政治分析,9,页。137–163。https://j.mp/2oSEnmf

[在我的实验中,我发现有时可能存在偏向少数类的偏见,但这是由于过度拟合造成的,其中类重叠由于随机抽样而消失,所以这并不算数和(贝叶斯)正则化应该解决这个问题]

好消息是 MLE 是渐近无偏见的,因此我们可以预期,随着数据集整体规模的增加,这种针对少数类的偏见会消失,而不管不平衡如何。

由于这是一个估计问题,任何使估计更加困难的事情(例如高维)似乎都可能使类不平衡问题变得更糟。

请注意,概率分类器(例如逻辑回归)和适当的评分规则不会解决这个问题,因为“流行的统计程序,例如逻辑回归,可能会严重低估罕见事件的概率” 3这意味着您的概率估计不会得到很好的校准,因此您将不得不调整阈值(相当于重新采样或重新加权数据)之类的事情。

因此,如果我们查看具有 10,000 个样本的逻辑回归模型,我们不应期望看到不平衡问题,因为添加更多数据往往会解决大多数估计问题。

因此,如果您有一个极端的不平衡并且数据集很小(和/或高维等),那么不平衡可能是有问题的,但是在这种情况下,可能很难做很多事情(因为您没有足够的数据估计需要对抽样进行多大的修正才能纠正偏差)。如果您有大量数据,重新采样的唯一原因是因为操作类频率与训练集中的频率不同或错误分类成本不同等(如果未知或可变,您真的应该使用概率分类器)。

这主要是一个存根,我希望以后能够添加更多。