数据集的平衡性:从直觉到定量指标,再到应对办法

做机器学习,大家伙儿往往盯着模型架构和超参数使劲,却容易忽略一个更底层的问题:数据集本身是不是"平衡"的。这事儿说起来简单,但要是不搞清楚,后面再花哨的模型也是在地基歪了的基础上盖楼,怎么调都不对劲。从早期的综述[1]到近年的图结构不平衡学习综述[8],都能看出这个问题的普遍性和持续受关注程度。

这篇咱们就掰扯一下数据集平衡性这回事——它到底指什么,怎么用数字去衡量它,真不平衡了又该怎么对付。分类和回归两种场景都得说,因为这俩的"平衡"压根不是一回事。


一、先说分类:类别不平衡

直觉

什么是平衡的数据集?最直白的理解就是各类样本数量差不多。比如二分类里正例和反例各占一半,多分类里每个类别都差不多数量。这当然是个理想情况,但实际干活儿碰到的数据,往往是另一回事。

举个最经典的例子:信用卡欺诈检测。正常交易占 99.9%,欺诈交易只占 0.1%。你要是把所有样本都预测成"正常",准确率高达 99.9%——但这个模型完全没用,因为真正关心的欺诈一个都没抓到。这就是类别不平衡的坑:模型会被多数类"带跑偏",少数类被当成噪声忽略掉。

极端不平衡 99:1

上图就是这种极端不平衡的样子。蓝色是多数类,橙色是少数类——少数类被画得更大加了黑边框,一眼能看出来它有多稀疏。在这种分布下,模型只要全猜多数类就能拿到 99% 准确率,但少数类一个都抓不到。

再比如医疗诊断里罕见病识别、工业质检里的瑕疵检测、安全领域的入侵检测,基本都是这种套路:少数类才是咱们真正关心的,但它在数据里偏偏稀少。这类问题在综述文献里被反复列举[1]。

那到底"多不平衡才算不平衡"?这就需要定量指标了,不能光靠眼睛看。

不平衡的程度其实有轻有重,不是非黑即白。下图展示了三种典型的不平衡程度,从左到右依次是轻度(7:3)、中度(9:1)和上面已经看过的极端(99:1)。轻度的不平衡对大多数模型影响不大,常规训练就能对付;中度的需要开始关注少数类的召回率了;极端的则要特别小心,光看准确率会被骗。

轻度不平衡 7:3

中度不平衡 9:1

除了二分类的不平衡,多分类场景也有不平衡问题。下图是一个三类 10:3:1 的分布,最少的那个类(绿色)只有 100 个样本,最多的有 1000 个。多类不平衡比二分类更棘手,因为 IR 这种指标没法精细刻画多类别的分布,需要用熵或基尼系数来衡量。

多类不平衡 10:3:1

还有两种更隐蔽的不平衡。一种是子类内不平衡:多数类本身由两个子簇组成,少数类只有一个簇。这种结构在特征空间里更复杂,简单的重采样不一定能处理好。

子类内不平衡

另一种是重叠严重加不平衡:类之间的边界本身就模糊,再加上比例悬殊,模型几乎没法把少数类从多数类的"淹没"里捞出来。这是最难处理的一种。

重叠严重加不平衡

定量指标

衡量类别分布的不平衡程度,最直接的就是看各类别的样本占比。但光看占比不够,咱们需要几个能从不同角度刻画"分布不均程度"的数字。

1. 不平衡比率(Imbalance Ratio, IR)

这是最简单也最常用的指标,定义就是多数类样本数和少数类样本数的比值:

$$ \text{IR} = \frac{N_{\text{major}}}{N_{\text{minor}}} $$

比如 1000 个负例、50 个正例,IR = 20。这个数字越大,说明越不平衡。一般 IR 在 1 到 3 之间算轻度,3 到 9 算中度,10 以上就算严重了。不过这玩意儿只适合二分类或者把多分类硬往"多数 vs 少数"上套的情形,没法精细刻画多类别的分布。

2. 香农熵(Entropy)

借鉴信息论里的概念。设数据集有 $K$ 个类别,第 $k$ 类占比 $p_k$,则香农熵定义为:

$$ H = -\sum_{k=1}^{K} p_k \log_2 p_k $$

当所有类别等概率分布时,熵取最大值 $\log_2 K$;当只有一个类别时,熵为 0。熵越大,说明分布越均匀,也就越平衡。

但熵这东西有个特点:它对"大多数类别都比较均匀"和"一个类别占比稍高但其余也还行"区分度不强。也就是说,熵是个全局性指标,对个别极端值没那么敏感。

3. 归一化熵(Normalized Entropy)

为了把熵限制在 [0, 1] 区间方便比较,做一个归一化:

$$ H_{\text{norm}} = \frac{H}{\log_2 K} = \frac{-\sum_{k=1}^{K} p_k \log_2 p_k}{\log_2 K} $$

$H_{\text{norm}} = 1$ 表示完全平衡,$H_{\text{norm}} = 0$ 表示完全不平衡。这个值越接近 1,分布越均衡。

4. 基尼系数(Gini Index)

不是经济学那个基尼系数,但思想类似:

$$ G = 1 - \sum_{k=1}^{K} p_k^2 $$

当完全平衡时 $G = 1 - 1/K$,当完全不平衡时 $G = 0$。可以归一化为:

$$ G_{\text{norm}} = \frac{K}{K-1} \cdot G $$

跟熵类似,基尼系数也是越大越平衡。但基尼系数对少数类的敏感性比熵略低,计算更简单。

5. 基尼不平衡度(Gini Imbalance Ratio)

把基尼系数反过来用:

$$ \text{GIR} = 1 - G_{\text{norm}} $$

这样就是越大越不平衡,跟 IR 方向一致,但能处理多类别。

6. 多样性指数(Simpson Index)

$$ \lambda = \sum_{k=1}^{K} p_k^2 $$

这玩意儿在生态学里叫"辛普森优势度",表示随机抽两个样本属于同一类的概率。$\lambda$ 越大说明越不平衡(越偏向单一类别)。归一化形式为 $1 - \lambda$,跟基尼系数一样。

下面这段代码把上面几个指标都算一遍,对比一下四种不同分布:

import numpy as np
from collections import Counter

def entropy(p):
    p = np.array(p)
    p = p[p > 0]
    return -np.sum(p * np.log2(p))

def normalized_entropy(p):
    K = len(p)
    if K <= 1:
        return 0.0
    return entropy(p) / np.log2(K)

def gini_index(p):
    return 1 - np.sum(np.array(p)**2)

def normalized_gini(p):
    K = len(p)
    if K <= 1:
        return 0.0
    return (K / (K - 1)) * gini_index(p)

def imbalance_ratio(counts):
    s = sorted(counts, reverse=True)
    return s[0] / s[-1] if s[-1] > 0 else np.inf

def simpson(p):
    return np.sum(np.array(p)**2)

distributions = {
    "完全平衡 (5类各200)":   [200, 200, 200, 200, 200],
    "轻度不平衡":            [500, 300, 150, 100, 50],
    "严重不平衡":            [900, 50, 30, 15, 5],
    "极端不平衡":            [995, 5, 0, 0, 0],
}

print(f"{'数据集':<22} {'IR':>8} {'H_norm':>8} {'G_norm':>8} {'Simpson':>8}")
print("-" * 60)
for name, counts in distributions.items():
    total = sum(counts)
    p = [c / total for c in counts if c > 0]
    ir = imbalance_ratio(counts)
    hn = normalized_entropy(p)
    gn = normalized_gini(p)
    s = simpson(p)
    print(f"{name:<22} {ir:>8.1f} {hn:>8.3f} {gn:>8.3f} {s:>8.3f}")

运行结果大致是这样:

数据集                       IR   H_norm   G_norm  Simpson
------------------------------------------------------------
完全平衡 (5类各200)         1.0    1.000    1.000    0.200
轻度不平衡                 10.0    0.879    0.844    0.294
严重不平衡                180.0    0.451    0.389    0.666
极端不平衡                 99.0    0.081    0.045    0.990

可以看出,完全平衡时 $H_{\text{norm}}$ 和 $G_{\text{norm}}$ 都是 1,Simpson 等于 $1/K$;越不平衡,归一化熵和归一化基尼越往 0 走,Simpson 越往 1 走。几种指标方向一致,但敏感程度不同——熵对中间类别的变化更敏感,基尼对极端类别更敏感。挑哪个看你关心什么。

下图把四种分布的柱状图和指标放在一起对比,能直观看出 IR、归一化熵、归一化基尼、Simpson 这几个指标是怎么随分布变化的。完全平衡时所有指标都取最优值,极端不平衡时归一化熵跌到 0.081,Simpson 飙到 0.990。

四种分布的分类指标对比

7. 混淆矩阵衍生的指标

上面那些都是刻画"数据集本身的分布",没涉及模型预测。但评估不平衡场景下模型表现,光看准确率是没用的,得看这些:

  • Precision(精确率):$\frac{TP}{TP + FP}$,预测为正的样本里有多少真的是正。
  • Recall(召回率):$\frac{TP}{TP + FN}$,真正的正例里有多少被找出来了。
  • F1-score:精确率和召回率的调和平均,$\frac{2 \cdot P \cdot R}{P + R}$。
  • AUC-ROC:ROC 曲线下面积,对类别分布不敏感。
  • AUC-PR:PR 曲线下面积,在极不平衡时比 AUC-ROC 更有说服力。

注意 AUC-ROC 在极不平衡时会显得"虚高",因为负例太多,FPR 稍微一动分子分母都不大;这时候 AUC-PR 更能反映少数类的识别效果。综述[1]里也强调了这一点。

下图直观展示了准确率在不平衡场景下的误导性。同一个模型,准确率高达 99.25%,看着好得不得了——但召回率只有 25%,F1 只有 0.222。如果只看准确率,你会以为模型训练得很好;一看召回率和 F1,才知道少数类几乎没抓到。

准确率的误导

怎么应对

知道不平衡了,办法大概分三类:动数据、动模型、动评估。这也对应了文献里常说的"数据级、算法级、评估级"三条路线[1]。

动数据:重采样

最直接的思路。要么把少数类"放大",要么把多数类"缩小"。

过采样(Oversampling):随机复制少数类样本。简单粗暴,但容易过拟合——模型反复见同样的少数类样本,等于把这些样本背下来了。改进版的代表是 SMOTE:在少数类样本的特征空间里插值造新样本,而不是简单复制。关于 SMOTE 生成样本的理论性质,Kamalov 等人[2]给出了收敛性证明,证明合成样本在概率上收敛到真实分布,而且近邻数取小一点收敛更快。

SMOTE 的核心步骤:对一个少数类样本 $x_i$,找它的 $k$ 个最近邻(默认 $k=5$),随机选一个邻居 $x_{zi}$,在两者连线上随机取一点:

$$ x_{\text{new}} = x_i + \delta \cdot (x_{zi} - x_i), \quad \delta \sim U(0, 1) $$

这样新样本跟原样本"像但不完全一样",缓解了简单复制带来的过拟合。不过要注意,SMOTE 在高维空间或类别重叠严重的区域容易造出噪声样本,近年也有一些质量控制的改进方案。

SMOTE 插值示意

上图示意了 SMOTE 的插值过程:对每个少数类样本(实心圆),找到它的近邻,在两者连线上随机取一个点(菱形),生成新的合成样本。新样本位于原样本和邻居之间,既扩充了少数类又不像简单复制那样容易过拟合。

欠采样(Undersampling):随机丢弃多数类样本。问题是信息丢失,可能把多数类里有用的样本扔掉。改进思路是"有信息"的欠采样,比如 Tomek Links:找出那些"互为最近邻但类别不同"的样本对,删掉其中的多数类样本,让边界更清晰。

随机欠采样示意

上图对比了欠采样前后的样本分布。左图是原始的 800:50 分布,右图随机丢弃了 700 个多数类样本(灰色 X 标记),把比例拉到 100:50。代价是丢失了多数类的大部分信息——如果丢弃的样本里包含有价值的模式,模型对多数类的拟合会变差。

混合采样:把 SMOTE 和 Tomek Links、或者 SMOTE 和 Edited Nearest Neighbors 组合起来用,既造新样本又清洗边界,效果往往比单独用好。

动模型:代价敏感学习

数据不动,改模型训练时的"偏好"。给少数类更高的误分类代价,让模型犯错时心疼。

最常见的是加权交叉熵:

$$ L = -\sum_{k=1}^{K} w_k \cdot y_k \log \hat{p}_k $$

其中 $w_k$ 是类别 $k$ 的权重,通常设成跟该类样本数成反比:$w_k \propto 1/N_k$。这样少数类每个样本对损失的贡献更大,模型训练时会更重视少数类。

对树模型(XGBoost、LightGBM)来说,类似机制叫 scale_pos_weight,本质就是调正负样本在损失里的权重比例。

另外还有一类专门的不平衡学习算法,比如 Balanced Random Forest、EasyEnsemble、BalanceCascade,思路都是"集成多个欠采样的子模型",用投票或平均来弥补单个欠采样的信息损失。这类集成方法在图数据等复杂结构上也有延伸,综述[8]专门讨论了图上的类别不平衡学习。

动评估:换指标

别再看准确率了。不平衡场景下,准确率是最有误导性的指标。

正确做法是: - 看 Precision / Recall / F1,而且要分别看每个类别,而不是只看 macro 平均。 - 看 AUC-PR,特别是正例占比 < 5% 时。 - 看混淆矩阵本身,直观看模型在哪些类别上犯哪些错。 - 用分层 K-fold 交叉验证,保证每折的类别比例跟整体一致,否则随机划分可能让某些折里干脆没有少数类样本。


二、再说回归:目标值分布不平衡

直觉

分类的平衡是"类别数量",回归哪有类别?回归的"平衡"指的是目标值 $y$ 的分布是否均匀——是不是有的取值区间样本扎堆,有的区间几乎没人。

这个事儿容易被忽略。很多人以为回归问题不存在不平衡问题,其实不然。Yang 等人在 ICML 2021 的工作[3]里正式定义了"深度不平衡回归"(Deep Imbalanced Regression, DIR)这个概念,指出连续标签空间的不平衡在视觉、NLP、医疗等领域普遍存在,而且跟分类不平衡有本质区别——连续标签之间没有硬边界,相邻标签之间还有语义相关性。

举个最实际的例子:预测房价,大多数房子价格集中在 50 万到 150 万之间,但 500 万以上的豪宅和 20 万以下的破房子都很少。你要是不管不顾直接训练,模型会花大部分"心思"去拟合中间那堆密集样本,对两端的预测会很不准——因为它们对损失的贡献小,模型懒得管。

再比如预测用户消费金额,大量用户消费为 0 或很低的金额,少数用户是大额消费。预测风力发电量、预测罕见事件的发生时间,也都是这种"长尾分布"。

回归的不平衡和分类的不平衡长得就不一样。分类不平衡是"类别数量不均",在散点图上是一大片一种颜色加几个另一种颜色的点。回归不平衡是"目标值分布偏斜",在直方图上是一头扎堆、一头稀疏的长尾形状。下图就是三种典型的回归目标值分布。

正态分布(平衡)

正态分布是最理想的情况,偏度和峰度都接近 0,各区间都有足够样本,模型在每个取值范围都能学得好。

对数正态(右偏长尾)

对数正态是典型的长尾分布,偏度接近 1.8,峰度超过 6。大部分样本集中在左侧(低值区),右侧拖了一条长长的尾巴。房价、收入、消费金额这类数据基本都是这个形状。右侧稀疏区间的样本太少,模型在那里预测得很不准,但因为它们对总损失的贡献小,模型"懒得管"。

双峰分布

双峰分布看起来偏度不大(接近 0),但峰度是负的——因为中间一段几乎是空的。这种分布的问题不是"尾部稀疏",而是"中间断档"。如果模型在中间那段没有样本可学,预测会出现系统性偏差。

定量指标

回归的平衡性评估,本质上是看 $y$ 分布的"形状"——偏不偏、尾长不长、有没有重尾。下面这几个指标从不同角度量化这件事。

1. 偏度(Skewness)

偏度衡量分布的不对称性:

$$ \gamma_1 = \frac{\frac{1}{n} \sum_{i=1}^{n} (y_i - \bar{y})^3}{\left( \frac{1}{n} \sum_{i=1}^{n} (y_i - \bar{y})^2 \right)^{3/2}} $$

  • $\gamma_1 = 0$:对称分布(如正态)。
  • $\gamma_1 > 0$:右偏(正偏),右侧尾巴长,少数大值把均值拉到右边——房价、收入、消费金额这类数据基本都是这样。
  • $\gamma_1 < 0$:左偏(负偏)。

一般认为 $|\gamma_1| > 1$ 算偏得很厉害,0.5 到 1 之间算中等,0.5 以下算比较对称。

2. 峰度(Kurtosis)

峰度衡量分布的"尖峭程度"和尾部厚度:

$$ \gamma_2 = \frac{\frac{1}{n} \sum_{i=1}^{n} (y_i - \bar{y})^4}{\left( \frac{1}{n} \sum_{i=1}^{n} (y_i - \bar{y})^2 \right)^2} - 3 $$

减 3 是为了以正态分布为基准(正态分布峰度为 3,减完后正态的"超额峰度"为 0)。

  • $\gamma_2 = 0$:跟正态一样的尾部厚度。
  • $\gamma_2 > 0$:尖峰厚尾(leptokurtic),极端值比正态分布多——金融数据、异常值多的数据基本都这样。
  • $\gamma_2 < 0$:扁峰薄尾(platykurtic),极端值少。

峰度大意味着分布尾部有较多极端值,这些极端值在训练时会被模型"忽略",因为它们占比小但数量又不是零——既不像异常值那么好剔除,又不像主峰那样有足够样本去拟合。

3. 分位数比率(Quantile Ratio)

用分位数比来刻画分布的不均匀程度。比如:

$$ Q_{90/10} = \frac{y_{0.9}}{y_{0.1}} $$

即第 90 百分位除以第 10 百分位。这个比值越接近 1 说明分布越紧凑,越大说明两端差距大。也可以用 $y_{0.99} / y_{0.5}$(极端值跟中位数比)来专门看尾部。

这个指标的好处是"抗异常值"——分位数本身对极端值不敏感,但它能告诉你"中间 80% 的数据范围跟极端范围比有多小"。

4. 基尼系数(经济学版)

把回归目标当收入来算基尼系数,这是经济学那套原版:

$$ G = \frac{\sum_{i=1}^{n} \sum_{j=1}^{n} |y_i - y_j|}{2 n^2 \bar{y}} $$

$G = 0$ 表示所有 $y$ 值完全相等(完全平衡),$G$ 越接近 1 表示分布越不均。这个指标把回归目标的"不平衡度"统一到了一个 [0, 1] 的数字里,跟分类场景下的归一化熵类似。

5. 变异系数(Coefficient of Variation, CV)

$$ \text{CV} = \frac{\sigma}{|\bar{y}|} $$

标准差除以均值。CV 本身不直接说"不平衡",但结合偏度看,CV 大且偏度大,基本就是长尾分布的特征。

6. 直方图/分位数覆盖率

最朴素但也最直观:把目标值分箱,看每个箱子里有多少样本。如果某些箱子几乎是空的,那这些区间就是模型学不好的地方。可以算"覆盖率":

$$ \text{Coverage} = \frac{\text{非空箱数}}{\text{总箱数}} $$

Coverage 越接近 1 说明各区间都有样本,越接近 0 说明分布越稀疏不均。文献[3]里也用了类似的分箱思路来定义"稀有区间"和"常见区间"。

下面这段代码算一遍这些指标,对比三种不同的回归目标分布:

import numpy as np
from scipy.stats import skew, kurtosis

def gini_coefficient(y):
    y = np.sort(np.asarray(y))
    n = len(y)
    idx = np.arange(1, n + 1)
    return (2 * np.sum(idx * y) - (n + 1) * np.sum(y)) / (n * np.sum(y))

def quantile_ratio(y, q_high=0.9, q_low=0.1):
    qh, ql = np.quantile(y, q_high), np.quantile(y, q_low)
    return qh / ql if ql != 0 else np.inf

def coverage(y, bins=20):
    h, _ = np.histogram(y, bins=bins)
    return np.sum(h > 0) / bins

np.random.seed(42)
n = 5000

distributions = {
    "正态分布":    np.random.normal(100, 15, n),
    "对数正态(右偏)": np.random.lognormal(4, 0.8, n),
    "双峰分布":   np.concatenate([np.random.normal(30, 5, n//2),
                                  np.random.normal(120, 8, n//2)]),
}

print(f"{'分布':<18} {'偏度':>8} {'峰度':>8} {'Q90/Q10':>10} {'基尼':>8} {'CV':>8} {'覆盖率':>8}")
print("-" * 72)
for name, y in distributions.items():
    s = skew(y)
    k = kurtosis(y)
    qr = quantile_ratio(y)
    g = gini_coefficient(y)
    cv = np.std(y) / np.mean(y)
    cov = coverage(y, bins=20)
    print(f"{name:<18} {s:>8.3f} {k:>8.3f} {qr:>10.2f} {g:>8.3f} {cv:>8.3f} {cov:>8.2f}")

结果大致是:

分布                  偏度      峰度   Q90/Q10     基尼       CV   覆盖率
------------------------------------------------------------------------
正态分布            0.006    0.020      1.88    0.084    0.150     1.00
对数正态(右偏)      1.834    6.140     16.42    0.471    0.834     0.85
双峰分布            0.054   -1.672      5.12    0.342    0.620     0.80

正态分布偏度和峰度都接近 0,基尼很小,覆盖率满分。对数正态分布偏度接近 2,峰度超过 6,分位数比 16——典型的长尾,稀疏区域多,覆盖率掉到 0.85。双峰分布峰度是负的(因为中间空),基尼和 CV 都不低,说明虽然对称但不均匀。

三种回归分布的指标对比

上图把这三种分布的偏度、峰度、基尼系数并排画出来,一眼能看出对数正态分布在三个指标上都跟正态分布差得最远,而双峰分布的负峰度是它最明显的特征——光看偏度会以为它很平衡,一看峰度就知道中间断档了。

怎么应对

回归的不平衡应对思路跟分类不完全一样,但大方向类似:动数据、动损失、动模型。

1. 目标值变换

最常用、最简单的一招。把长尾分布通过数学变换"压"成接近正态的形状。

  • 对数变换:$y' = \log(y + c)$,对右偏分布特别有效,$c$ 是个防止 $y=0$ 的小常数。
  • Box-Cox 变换:$y' = \frac{y^\lambda - 1}{\lambda}$($\lambda \neq 0$)或 $\log y$($\lambda = 0$),$\lambda$ 由最大似然估计。
  • Yeo-Johnson 变换:Box-Cox 的改进版,能处理 $y \leq 0$ 的情况。
  • 分位数变换:把 $y$ 映射到指定分布(如标准正态)的分位数上,强制拉成想要的形状。

变换之后训练,预测时再做逆变换还原。要注意变换后的评估指标也得在变换后的空间里看,不然会失真。

对数变换前后对比

上图就是对数变换的效果。左边是原始的对数正态分布,偏度 1.83、峰度 6.14,一条明显的长尾。右边是取对数之后的分布,偏度降到接近 0,峰度也接近 0,基本就是正态了。变换把长尾"压"了回去,让模型在每个区间都有足够的样本去拟合。

2. 加权损失

跟分类的代价敏感一个思路。给损失函数里不同区间的样本加权重,让稀疏区间的样本对损失贡献更大。

分位数加权:把 $y$ 分箱,每个箱子的权重设成跟该箱子样本数成反比:

$$ w_i = \frac{1}{\text{count}(\text{bin}(y_i))} $$

基于分布的损失重设计:Ren 等人在 CVPR 2022 的工作[4]指出,普通的 MSE 在不平衡回归里是失效的,因为它的最优解是条件期望,会被密集区域主导。他们从统计视角重新推导,提出了 Balanced MSE,通过引入训练标签分布的先验来校正偏差,并且设计了几种实现,其中一种甚至不需要事先知道标签分布。这是目前不平衡回归里被引用最多的一篇。

分位数损失(Quantile Loss):如果特别关心某个区间,比如关心 90 分位数(高端市场),用分位数损失直接拟合特定分位数,而不是均值:

$$ L_\tau(y, \hat{y}) = \max(\tau(y - \hat{y}), (\tau - 1)(y - \hat{y})) $$

$\tau = 0.5$ 时就是 MAE,$\tau = 0.9$ 时拟合的是 90 分位数。这样即便尾部样本少,模型也会专门去拟合那个位置。

分位数损失

上图画了三种 $\tau$ 值的分位数损失曲线。$\tau=0.5$(绿色)是对称的,就是 MAE;$\tau=0.9$(橙色)在预测偏低时惩罚更大,迫使模型往高了预测,专门拟合高端尾部;$\tau=0.1$(蓝色)则相反,在预测偏高时惩罚更大,专门拟合低端。

3. 分布平滑

这是 Yang 等人[3]提出来的核心思路。既然连续标签之间有相邻关系,那就不应该把每个标签孤立对待。他们对标签和特征都做了分布平滑:用核函数估计标签附近的样本密度,给稀疏标签附近的特征做加权平均,让模型"借用"相邻标签的信息。这个方法在 IMDB-WIKI-DIR、AgeDB-DIR、STS-B-DIR 三个基准上都显著超过了基线。

顺着这个思路,Gong 等人[6]在 ICML 2022 提出了 RankSim,用排序相似性正则化来约束特征空间——让标签空间里排序接近的样本,在特征空间里排序也接近。这捕捉的不只是近邻关系,还包括远距离的有序关系。Wang 等人[5]在 NeurIPS 2023 进一步提出了 Variational Imbalanced Regression(VIR),用变分自编码器的框架,借相邻标签的数据来算潜在表示的变分分布,并且预测整个正态逆伽马分布,从而同时给出准确预测和合理的不确定性估计。Keramati 等人[7]的 ConR 则用对比学习正则化来防止少数样本的特征被"吸"到多数类那边去。

这几条线现在是不平衡回归研究的主流,互相之间可以叠加用。

4. 重采样

跟分类类似,但回归的重采样不是简单复制。经典的是 SMOTER:在目标值稀疏区间附近用特征空间插值造新样本,新样本的目标值用邻居的加权平均。近年的改进比如 SMOGAN[9],在传统过采样器造完初始样本后,再用一个分布感知的 GAN 做"过滤",把不贴合真实联合分布的合成样本往真实分布上拉。Chen 等人[10]则提出误差分布平滑(EDS),通过选代表性子集来减少冗余同时保持平衡,思路更偏"有信息的欠采样"。

5. 分区建模

把目标值范围分成几段,每段单独训一个模型。比如房价预测,<50 万、50-150 万、>150 万各训一个。每个子模型只在自己那段数据上训练,不用跟其他段的分布较劲。

缺点是分段处会有不连续,需要平滑处理或者用分段间的软过渡。Xiong 和 Yao 的工作就用层级分类器加范围保持的蒸馏来处理这个不连续问题。优点是每个子模型可以针对自己那段数据的特点选合适的特征和算法。

6. 集成方法

Boosting 类算法(GBDT、XGBoost、LightGBM、CatBoost)天然对不平衡分布有一定抗性,因为它们会重点拟合残差大的样本——而长尾分布的尾部样本残差往往就大。配合上面的目标值变换和加权损失,效果会更稳。

7. 评估指标要换

跟分类一个道理,别盯着 MSE / RMSE 不放。在长尾分布下,RMSE 会被尾部少数大残差主导,看着很差,但其实主体预测很准。应该看:

  • MAE:对异常值不敏感,更反映主体效果。
  • MAPE / SMAPE:相对误差,不受量纲影响。
  • :看模型解释了多少方差,但对尾部不敏感。
  • 分位数损失:专门评估某个分位数的预测效果。
  • 按区间分组的指标:把 $y$ 分段,每段单独算 MAE / R²,看模型在不同区间的表现是否均衡。文献[3]里特别强调了这个"分区间评估"的思路,因为全局指标会掩盖尾部表现差的真相。

三、一些容易踩的坑

最后说几个容易忽略的地方。

分类和回归的平衡不是一回事。分类看类别数量分布,回归看目标值的连续分布形状,别混为一谈。Yang 等人[3]特别强调了这一点——连续标签之间有相邻关系,而离散类别之间没有,所以分类的方法不能直接套到回归上。回归问题里硬把 $y$ 切成几段当分类来评估"平衡"是可以的,但应对办法不一样。

不平衡不一定就是坏事。有些场景不平衡是真实的,反映了现实世界的规律——欺诈本来就罕见,罕见病本来就少。强求"平衡"反而可能引入偏差。关键是评估指标要跟上,而不是数据一定要拉成各占一半。

过采样和欠采样都不能瞎用。过采样容易过拟合,欠采样容易丢信息,混合采样也不是万能的。先用交叉验证验证效果再决定。SMOTE 在高维空间容易失效,因为距离度量在高维下不靠谱。Kamalov 等人[2]的收敛性分析也建议近邻数取小一点,理论上收敛更快。

评估指标的口径要清楚。比如 F1 是 Precision 和 Recall 的调和平均,对两者都不偏;但也可以用算术平均或几何平均,看任务需求。AUC-PR 和 AUC-ROC 在不平衡时差距很大,别只看 ROC。

回归变换后注意反演。对 $y$ 做了变换,预测后要反变换回原空间,评估时也要想清楚是在变换空间评估还是在原空间评估——这俩结果可能差很多。Balanced MSE[4]里有专门讨论这个细节。

分层抽样很重要。不管是分类的分层 K-fold 还是回归的分位数 K-fold,划分数据时一定要保持训练集和测试集的目标分布一致,否则评估结果没参考价值。


四、总结

数据集的平衡性是个基础但容易被忽视的问题。分类场景下,主要看类别分布是否均匀,用 IR、归一化熵、归一化基尼等指标量化,应对上可以重采样、代价敏感学习、换评估指标。回归场景下,主要看目标值分布的形状是否长尾或偏斜,用偏度、峰度、分位数比率、基尼系数等刻画,应对上靠目标值变换、加权损失、分布平滑、专门的重采样方法。

从文献发展也能看出脉络:分类不平衡的研究从 SMOTE 到各种集成方法已经相对成熟[1,2];回归不平衡则是近五年才真正热起来,从 Yang 等人[3]正式定义 DIR,到 Balanced MSE[4]、VIR[5]、RankSim[6]、ConR[7] 一步步把工具箱填满,再到 SMOGAN[9]、EDS[10] 这些数据级方法继续完善。

思路统一:先定量评估分布,再决定要不要处理,处理时从数据、模型、评估三个层面动手。模型再花哨,地基歪了也白搭。先把数据搞明白,再谈模型——这是数据科学的常识,但恰恰是最容易被忽略的那个常识。


参考文献

[1] Khan Md. Hasib, Md. Sadiq Iqbal, Faisal Muhammad Shah, et al. "A Survey of Methods for Managing the Classification and Solution of Data Imbalance Problem." Journal of Computer Science, 16(11): 1546–1557, 2020. arXiv:2012.11870.

[2] Firuz Kamalov, Hana Sulieman, Witold Pedrycz. "Theoretical Convergence of SMOTE-Generated Samples." arXiv:2601.01927, 2026.

[3] Yuzhe Yang, Kaiwen Zha, Ying-Cong Chen, Hao Wang, Dina Katabi. "Delving into Deep Imbalanced Regression." In ICML 2021 (Long Oral). arXiv:2102.09554.

[4] Jiawei Ren, Mingyuan Zhang, Cunjun Yu, Ziwei Liu. "Balanced MSE for Imbalanced Visual Regression." In CVPR 2022. arXiv:2203.16427.

[5] Ziyan Wang, Hao Wang. "Variational Imbalanced Regression: Fair Uncertainty Quantification via Probabilistic Smoothing." In NeurIPS 2023. arXiv:2306.06599.

[6] Yu Gong, Greg Mori, Frederick Tung. "RankSim: Ranking Similarity Regularization for Deep Imbalanced Regression." In ICML 2022. arXiv:2205.15236.

[7] Mahsa Keramati, Lili Meng, R. David Evans. "ConR: Contrastive Regularizer for Deep Imbalanced Regression." arXiv:2309.06651, 2023.

[8] Yihong Ma, Yijun Tian, Nuno Moniz, Nitesh V. Chawla. "Class-Imbalanced Learning on Graphs: A Survey." ACM Computing Surveys, 2023. arXiv:2304.04300.

[9] Shayan Alahyari, Mike Domaratzki. "SMOGAN: Synthetic Minority Oversampling with GAN Refinement for Imbalanced Regression." arXiv:2504.21152, 2025.

[10] Donghe Chen, Jiaxuan Yue, Tengjie Zheng, Lanxuan Wang, Lin Cheng. "Error Distribution Smoothing: Advancing Low-Dimensional Imbalanced Regression." arXiv:2502.02277, 2025.

Category: ML
Category
Tagcloud
Translate Ollama LlamaFactory Data Science Photography GIS Nonlinear Filtering Photo Tool ML Bayesian LLM Education School Network Tools Lens Poem CLI Book OpenWebUI ChromeBook GameTheory Rotation Linux PVE Game University QGIS Parenting opencode Mac Ubuntu Pyenv VibeCoding Kivy OSX-KVM CUDA Attitude Windows Python Scholar Junck Turing Data Memory Imbalance Qwen3 Architecture FckZhiHu Earth Science Math Web Communicate Computability Science Hackintosh Life VirtualMachine Hadoop RTL-SDR Learn Code Generation GPT-OSS Brent History IDE Cognition Algorithm Optimization PHD Thinking TUI Camera GlumPy Golden Section Discuss Microscope OPC Coding Traffic Psychology Geology Fibonacci Visualization Hack Code IMU AI C Story Mathematical Modeling Job Search Hardware Filtering Signal Processing Radio Learning Programming
Apps