简介这份PDF文献面向机器学习研究者、数据挖掘工程师及算法方向的学生聚焦类别不平衡场景下传统KNN分类器偏向多数类、少数类识别率低的核心痛点。文章提出REKRNN算法将k-秩近邻规则嵌入Bagging集成框架并融合欠采样、过采样等重采样技术与随机子空间法通过增加基学习器多样性来平衡训练集、抑制过拟合在多个不平衡数据集上兼顾总体精度与少数类召回。资源包共1个PDF文件约521KB内容为完整的学术论文含摘要、引言、相关研究、算法设计与仿真实验等章节可直接用于算法复现、论文写作引用或课程汇报参考。目前已有111人学习适合希望深入理解不平衡分类与集成学习结合思路的读者研读。1. 不平衡分类的破局点为什么秩次比距离更抗偏做过信用欺诈检测或罕见病辅助诊断的同行都清楚当正负样本比例拉到 99:1哪怕模型把所有人判成“健康”或“守信”总体准确率照样能刷到 99%可这种数字在业务侧一文不值。传统 KNN 在这种场景下会严重偏向多数类因为它的决策完全依赖欧氏距离——多数类样本在空间里密密麻麻少数类样本被淹没距离最近的 k 个邻居大概率全是多数类。这篇 2020 年发表在《应用数学进展》上的工作核心思路是把“距离”换成“秩次”再套进 Bagging 框架做集成最终在 Abalone9-18 数据集上把少数类查全率从 37.5% 拉到 92.86%AUC 从 68.04% 提到 92.55%。它适合谁适合手头正被不平衡二分类折磨、想找一个比 SMOTE随机森林更轻量、可解释性更强的替代方案的算法工程师和数据分析师。整份资源是一篇完整的学术论文 PDF含算法伪代码、得分函数推导和五组对比实验照着复现能跑通一套完整的 REKRNN 流程。2. 拆解 REKRNN从得分函数到 Bagging 集成的四个技术件2.1 秩次 k 近邻k-RNN的得分函数怎么算原始 k-RNN 只能处理单变量因为“排序”这个概念在单特征下很直观把两类样本和测试样本混在一起按数值降序排取测试样本左右各 k 个作为邻居投票。但现实数据几乎都是多特征的直接对多维向量排序没有自然定义。Bagui 和 Pal 的扩展方案是引入一个得分函数把 p 维样本映射到一维实数用这个实数当秩次。得分函数的形式是R(Z; μ₁, μ₂, Σ₁, Σ₂) (Z - (μ₁μ₂)/2)ᵀ Σ⁻¹ (μ₁ - μ₂)其中 μ₁、μ₂ 是两类样本的均值向量Σ₁、Σ₂ 是协方差矩阵。实际中总体参数未知用样本均值 X̄、Ȳ 和样本协方差 S₁、S₂ 代替得到估计得分函数。这个函数本质上是 Fisher 线性判别方向的一种变形它同时考虑了类间均值差异和类内协方差结构所以比单纯欧氏距离更能反映样本在两类之间的相对位置。计算完所有训练样本的得分后按升序排列测试样本插入其中左右各取 k 个作为秩次最近邻投票决定类别。这里有个容易翻车的点协方差矩阵在高维小样本下可能是奇异的直接求逆会报错。常见做法是加一个小的岭回归项比如 1e-6 倍单位阵或者用伪逆。论文里没有展开这一步但复现时绕不开。2.2 混合重采样少数类 Bootstrap 多数类降采样Bagging 的标准做法是有放回抽 N 个样本但在不平衡数据上这会出大问题——少数类本来就只有几十个样本Bootstrap 抽完可能只剩十几个甚至更少基学习器根本学不到少数类的分布。REKRNN 的改进很直接对少数类做 Bootstrap 重复采样有放回对多数类做降采样无放回让每个训练子集里两类样本数量大致相等。具体操作上假设少数类有 P 个样本多数类有 N 个样本每个子集从少数类中有放回抽 P 个从多数类中无放回抽 P 个如果 N 足够大这样每个子集的类别比例接近 1:1。重复 r 次得到 r 个平衡子集。这个策略的好处是少数类样本被反复利用不会因为随机性丢失关键信息多数类虽然被降采样但通过多个子集的集成整体上多数类的信息并没有被丢弃太多。参数 r 的选择需要权衡r 太小集成效果不明显r 太大训练时间线性增长。论文里没有给出 r 的具体取值但根据 Abalone9-18 的实验规模731 样本、8 特征r 取 20 到 50 是比较合理的区间。我一般会从 30 起步看 AUC 是否还在涨涨不动就停。2.3 随机子空间降维与增加基学习器差异性的双重收益随机子空间法的操作是在每个重采样后的训练子集上不从全部 d 个特征里训练而是随机选 n 个特征n d只用这 n 个特征训练一个 k-RNN 基学习器。这样做有两个好处一是降低单次训练的计算量尤其在特征维度高的时候效果明显二是不同基学习器看到的特征子集不同预测结果的相关性降低投票集成的泛化性能更好。特征数 n 的选取有个经验规则分类任务里常用 √d 或 log₂d。以 Abalone9-18 的 8 个特征为例√8 ≈ 2.8取 n3 或 n4 比较合适。n 太小会导致单个基学习器欠拟合n 太大会让基学习器之间差异不足。实际操作中可以把 n 作为一个超参数用交叉验证在 {2, 3, 4, 5} 里搜一下。2.4 Bagging 投票集成最终决策怎么定四个技术件里最后一个就是 Bagging 的标准投票。r 个基学习器各自对测试样本给出预测类别统计两类票数多数票即为最终结果。由于每个基学习器是在平衡子集上训练的且特征子集不同投票结果不会像单个 k-RNN 那样一边倒向多数类。整个流程用伪代码概括就是# REKRNN 核心流程伪代码 # 输入训练集 S少数类样本数 P多数类样本数 N基学习器数 r特征子集大小 n # 输出测试样本的预测类别 for i in range(r): # 步骤1混合重采样 minority_bootstrap resample(minority_samples, n_samplesP, replaceTrue) majority_downsample resample(majority_samples, n_samplesP, replaceFalse) balanced_subset concat(minority_bootstrap, majority_downsample) # 步骤2随机特征子集 feature_idx random_choice(range(d), sizen, replaceFalse) subset_features balanced_subset[:, feature_idx] # 步骤3训练 k-RNN 基学习器 # 计算两类均值向量和协方差矩阵 mu1 mean(subset_features[class1]) mu2 mean(subset_features[class0]) S1 cov(subset_features[class1]) S2 cov(subset_features[class0]) # 计算所有训练样本的得分并排序 scores compute_score(subset_features, mu1, mu2, S1, S2) sorted_indices argsort(scores) # 步骤4对测试样本预测 test_score compute_score(test_sample[feature_idx], mu1, mu2, S1, S2) insert_pos searchsorted(scores[sorted_indices], test_score) left_neighbors sorted_indices[max(0, insert_pos-k):insert_pos] right_neighbors sorted_indices[insert_pos:insert_posk] neighbors concat(left_neighbors, right_neighbors) prediction[i] majority_vote(labels[neighbors]) # 最终投票 final_prediction majority_vote(prediction)这段伪代码里几个关键参数需要说明k是秩次最近邻的单侧数量论文里通过十折交叉验证确定r是基学习器数量影响集成效果和训练时间n是随机特征子集大小控制基学习器差异性和单模型复杂度。compute_score函数就是前面说的得分函数注意协方差矩阵求逆前要加正则项。3. 复现实验Abalone9-18 数据集上的完整跑通步骤3.1 数据准备与不平衡率确认Abalone9-18 来自 KEEL 数据集仓库是鲍鱼年龄预测的二分类变体原始 9 分类里取第 9 类和第 18 类合并成二分类问题。数据集含 731 个样本、8 个特征多数类 684 个少数类 42 个不平衡率 16.68。下载后先确认格式KEEL 的 .dat 文件通常是逗号分隔最后一列是类别标签。import pandas as pd import numpy as np from sklearn.model_selection import train_test_split # 读取 KEEL 格式数据跳过前几行注释 df pd.read_csv(abalone9-18.dat, skiprows10, headerNone) X df.iloc[:, :-1].values y df.iloc[:, -1].values # 确认不平衡率 majority_count np.sum(y 0) minority_count np.sum(y 1) print(f多数类: {majority_count}, 少数类: {minority_count}, 不平衡率: {majority_count/minority_count:.2f}) # 按 70/30 划分训练集和测试集stratify 保证划分后比例一致 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifyy )这里skiprows10是因为 KEEL 的 .dat 文件头部有若干行元信息具体行数因文件而异打开看一眼就知道。stratifyy很关键不做分层的话少数类在测试集里可能只剩十几个评估结果波动会很大。3.2 得分函数与 k-RNN 基学习器的实现得分函数的计算需要两类样本的均值向量和协方差矩阵。协方差矩阵求逆前加正则项是必须的否则 8 维特征下 42 个少数类样本的协方差矩阵大概率奇异。def compute_score(X, mu1, mu2, S1_inv, S2_inv): 计算样本的秩次得分X 形状为 (n_samples, n_features) n X.shape[0] scores np.zeros(n) for i in range(n): z X[i] # 得分函数综合两类均值和协方差信息 term1 (z - mu1) S1_inv (z - mu1) term2 (z - mu2) S2_inv (z - mu2) scores[i] term1 - term2 return scores def train_krnn(X_train, y_train, k5, reg1e-6): 训练一个 k-RNN 基学习器返回模型参数 X1 X_train[y_train 1] X0 X_train[y_train 0] mu1 np.mean(X1, axis0) mu2 np.mean(X0, axis0) # 协方差矩阵加正则项防止奇异 S1 np.cov(X1.T) reg * np.eye(X_train.shape[1]) S2 np.cov(X0.T) reg * np.eye(X_train.shape[1]) S1_inv np.linalg.inv(S1) S2_inv np.linalg.inv(S2) # 计算所有训练样本得分并排序 scores compute_score(X_train, mu1, mu2, S1_inv, S2_inv) sorted_idx np.argsort(scores) return { mu1: mu1, mu2: mu2, S1_inv: S1_inv, S2_inv: S2_inv, sorted_idx: sorted_idx, sorted_scores: scores[sorted_idx], y_sorted: y_train[sorted_idx], k: k } def predict_krnn(model, X_test): 用训练好的 k-RNN 模型预测测试样本 predictions [] for z in X_test: score compute_score(z.reshape(1, -1), model[mu1], model[mu2], model[S1_inv], model[S2_inv])[0] # 找到插入位置 pos np.searchsorted(model[sorted_scores], score) # 左右各取 k 个邻居 left max(0, pos - model[k]) right min(len(model[sorted_scores]), pos model[k]) neighbor_labels model[y_sorted][left:right] # 投票 pred 1 if np.sum(neighbor_labels 1) len(neighbor_labels) / 2 else 0 predictions.append(pred) return np.array(predictions)compute_score里用的是估计得分函数的简化形式实际论文里的公式包含更多项但核心思想就是比较样本到两类中心的“加权距离”。reg1e-6是正则化系数如果协方差矩阵条件数很大可以适当调大到 1e-4。k的取值通过交叉验证确定论文里没有给出具体值我一般从 3 到 15 搜一遍。3.3 混合重采样与随机子空间的集成循环把前面的基学习器套进 Bagging 循环每个子集做混合重采样和随机特征选择。def reknn_fit(X_train, y_train, r30, n_features3, k5): 训练 REKRNN 集成模型 models [] n_minority np.sum(y_train 1) minority_idx np.where(y_train 1)[0] majority_idx np.where(y_train 0)[0] for i in range(r): # 少数类 Bootstrap 采样 boot_minority np.random.choice(minority_idx, sizen_minority, replaceTrue) # 多数类降采样 down_majority np.random.choice(majority_idx, sizen_minority, replaceFalse) subset_idx np.concatenate([boot_minority, down_majority]) X_subset X_train[subset_idx] y_subset y_train[subset_idx] # 随机特征子集 feat_idx np.random.choice(X_train.shape[1], sizen_features, replaceFalse) X_subset_feat X_subset[:, feat_idx] # 训练 k-RNN 基学习器 model train_krnn(X_subset_feat, y_subset, kk) model[feat_idx] feat_idx models.append(model) return models def reknn_predict(models, X_test): 集成预测 all_preds [] for model in models: X_test_feat X_test[:, model[feat_idx]] preds predict_krnn(model, X_test_feat) all_preds.append(preds) # 多数投票 all_preds np.array(all_preds) final_preds (np.sum(all_preds, axis0) len(models) / 2).astype(int) return final_predsr30是基学习器数量n_features3是随机特征子集大小这两个参数都可以调。reknn_predict里每个基学习器只在自己选的特征子集上预测最后投票。注意投票阈值是len(models)/2因为二分类且类别编码为 0/1票数过半即为 1。3.4 五指标评估与结果对照论文用了 Accuracy、Sensitivity、Specificity、F-measure、AUC 五个指标。复现时用 sklearn 的混淆矩阵和 roc_auc_score 算就行。from sklearn.metrics import confusion_matrix, roc_auc_score, f1_score def evaluate(y_true, y_pred, y_probNone): tn, fp, fn, tp confusion_matrix(y_true, y_pred).ravel() accuracy (tp tn) / (tp tn fp fn) sensitivity tp / (tp fn) # 少数类查全率 specificity tn / (tn fp) # 多数类查准率 f_measure 2 * sensitivity * specificity / (sensitivity specificity) auc roc_auc_score(y_true, y_prob) if y_prob is not None else None return { Accuracy: accuracy, Sensitivity: sensitivity, Specificity: specificity, F-measure: f_measure, AUC: auc } # 训练并评估 models reknn_fit(X_train, y_train, r30, n_features3, k5) y_pred reknn_predict(models, X_test) results evaluate(y_test, y_pred) print(results)论文报告的结果是 Accuracy 92.27%、Sensitivity 92.86%、Specificity 92.23%、F-measure 92.54%、AUC 92.55%。复现时由于随机种子和参数选择不同结果会有几个百分点的波动但 Sensitivity 和 AUC 应该明显高于单 k-RNN 和 KNN。如果 Sensitivity 低于 80%优先检查重采样比例和 k 值。4. 避坑与排查复现 REKRNN 时最容易翻车的五个点4.1 协方差矩阵奇异导致得分函数报错现象运行np.linalg.inv(S1)时抛出LinAlgError: Singular matrix程序中断。原因少数类样本数42 个接近甚至少于特征数8 个协方差矩阵秩不足直接求逆失败。这是小样本高维场景的经典问题。解决在协方差矩阵上加正则项S1 reg * np.eye(n_features)reg从 1e-6 起步如果还报错就逐步加到 1e-4 或 1e-3。另一个方案是用np.linalg.pinv求伪逆但伪逆的数值稳定性不如正则化我一般优先用正则化。4.2 少数类在某个 Bootstrap 子集中完全消失现象某个基学习器的训练子集里少数类样本数为 0训练时np.cov对空数组报错或者模型直接退化成多数类预测器。原因虽然 REKRNN 对少数类做了 Bootstrap 有放回采样但如果少数类原始样本极少比如少于 10 个某次采样仍然可能全部重复到同一个样本导致协方差矩阵完全奇异。更极端的情况是采样代码写错用了无放回。解决确认少数类采样用的是replaceTrue。如果少数类样本数少于 20考虑先做一次 SMOTE 过采样扩充到 30 以上再进 REKRNN 循环。另外可以在循环里加一个检查如果子集里某一类样本数少于 3跳过这个基学习器重新采样。4.3 随机特征子集选到了无关特征导致基学习器性能崩塌现象集成模型的 AUC 比单个 k-RNN 还低或者不同随机种子下结果波动极大AUC 方差超过 0.1。原因n_features设得太小比如 n1某个基学习器恰好选到了一个区分度很低的特征预测结果接近随机。虽然投票能部分纠正但如果 r 不够大坏基学习器会拖累整体。解决把n_features从 √d 起步不要低于 2。同时增大 r 到 50 以上让投票的纠错能力更强。如果特征维度确实很高d 50可以考虑先用方差过滤或互信息筛掉一批明显无关的特征再做随机子空间。4.4 测试集划分没有分层导致评估指标失真现象Sensitivity 在不同随机种子下从 60% 跳到 95%或者测试集里少数类样本只有个位数算出来的查全率没有统计意义。原因train_test_split默认不做分层少数类样本本来就少随机划分后测试集里可能只剩 5 到 10 个少数类样本一两个预测错误就会让 Sensitivity 大幅波动。解决划分时加stratifyy保证训练集和测试集的类别比例与原始数据一致。如果少数类总数少于 50建议用 5 折分层交叉验证代替单次划分报告平均指标和标准差。4.5 把 REKRNN 当成万能方案套到多分类或高维稀疏数据上现象在文本分类或图像分类的不平衡数据上跑 REKRNN训练时间极长且效果不如随机森林或 XGBoost。原因REKRNN 的基学习器 k-RNN 依赖协方差矩阵求逆时间复杂度是 O(d³) 每轮d 是特征数。在 d 100 的高维稀疏数据上协方差矩阵求逆会成为瓶颈而且稀疏数据的协方差矩阵估计本身就不稳定。另外 REKRNN 的设计是针对二分类的多分类需要扩展投票策略。解决高维稀疏数据优先考虑基于树的集成方法随机森林、LightGBM它们对稀疏特征和不平衡数据都有成熟的处理方案。REKRNN 的适用场景是低维d 50、中等样本量几百到几千、二分类的不平衡问题。如果非要用在高维数据上先做 PCA 或特征选择降到 20 维以下再试。5. 进阶技巧把 REKRNN 的得分函数改造成可解释的少数类风险评分跑通基础版之后我习惯做一件事把得分函数的值直接当成少数类的风险评分来用。因为得分函数本质上是样本在两类之间的相对位置度量得分越高说明样本越靠近少数类这个连续值比 0/1 预测结果包含更多信息。具体做法是在predict_krnn里不只返回投票类别还返回测试样本的得分值。然后对所有基学习器的得分取平均得到一个集成的风险评分。这个评分可以按百分位切成三档高风险前 10%、中风险10% 到 30%、低风险后 70%。在信用欺诈场景里高风险名单直接送人工审核中风险名单做二次验证低风险名单自动放行。这样就把一个分类算法变成了一个可运营的风险分层工具。验证这个改造是否有效可以画两条曲线一条是风险评分从高到低排序后的累计少数类捕获率Cumulative Gain另一条是随机排序的基线。如果前 10% 的高风险样本里捕获了 60% 以上的少数类说明评分排序能力合格。论文里的 AUC 是 92.55%对应的 Cumulative Gain 在前 20% 处应该能到 80% 左右。还有一个实用技巧是给得分函数加一个温度参数。原始得分函数的尺度受协方差矩阵影响不同数据集上数值范围差异很大。加一个温度 T 做缩放score_scaled score / TT 可以用训练集得分的标准差来估计。这样风险评分的阈值在不同数据集之间更有可比性部署时不用每次重新调分档边界。从那以后我每次拿到不平衡二分类任务都会先跑一遍 REKRNN 的得分函数把连续评分和业务阈值对齐再决定要不要上更复杂的模型。这套流程在几个风控项目里帮我省掉了不少调参时间也避免了直接上深度学习带来的解释成本。希望帮到你。本文还有配套的精品资源点击获取