基于StratifiedKFold的GridSearchCV中,如何在各折训练集的两类样本中分别识别并剔除异常值?
基于StratifiedKFold的GridSearchCV中,如何在各折训练集的两类样本中分别识别并剔除异常值?
嘿,这个问题抓得特别准——你已经意识到全数据集处理异常值会导致数据泄露,这可是建模里的关键坑!接下来咱们一步步搞定在分层交叉验证的每折训练集里,针对患病和健康两类样本分别处理异常值的问题:
核心思路
要解决这个问题,核心是只在每折的训练集上做异常值处理,绝对不能碰测试集的信息,而且要按类别(患病/健康)分别检测剔除。我们可以通过自定义一个符合sklearn接口的预处理类,把它嵌入到Pipeline里,再和StratifiedKFold+GridSearchCV结合起来。
步骤1:自定义按类别剔除异常值的预处理类
这个类需要实现sklearn的BaseEstimator和TransformerMixin接口,这样才能和Pipeline兼容。它会在拟合阶段(只针对训练集)计算每类样本的异常值边界,转换阶段用这些边界筛选训练集的样本,测试集则保持原样。
import numpy as np from sklearn.base import BaseEstimator, TransformerMixin class ClassSpecificOutlierRemover(BaseEstimator, TransformerMixin): def __init__(self, threshold=1.5): self.threshold = threshold # IQR的倍数,可根据需求调整 self.lower_bounds = {} # 存储每个类别的特征下界 self.upper_bounds = {} # 存储每个类别的特征上界 def fit(self, X, y): # 只在训练集上拟合:按类别计算每个特征的IQR边界 for class_label in np.unique(y): X_class = X[y == class_label] q1 = np.percentile(X_class, 25, axis=0) q3 = np.percentile(X_class, 75, axis=0) iqr = q3 - q1 self.lower_bounds[class_label] = q1 - self.threshold * iqr self.upper_bounds[class_label] = q3 + self.threshold * iqr return self def transform(self, X, y=None): # 转换阶段:训练集传y,按边界筛选;测试集不传y,直接返回原数据 if y is None: return X X_clean = [] y_clean = [] for class_label in np.unique(y): mask_class = y == class_label X_class = X[mask_class] y_class = y[mask_class] # 用拟合好的边界筛选当前类别的正常样本 mask_valid = np.all((X_class >= self.lower_bounds[class_label]) & (X_class <= self.upper_bounds[class_label]), axis=1) X_clean.append(X_class[mask_valid]) y_clean.append(y_class[mask_valid]) return np.vstack(X_clean), np.hstack(y_clean)
步骤2:构建Pipeline并整合到GridSearchCV里
把自定义的异常值处理类和分类器(AdaBoost/RandomForest)放到Pipeline里,再搭配StratifiedKFold做交叉验证,这样每折的训练集都会先经过异常值处理,再训练分类器,完全避免数据泄露。
from sklearn.pipeline import Pipeline from sklearn.model_selection import StratifiedKFold, GridSearchCV from sklearn.ensemble import AdaBoostClassifier, RandomForestClassifier # 以AdaBoost为例,RandomForest只需替换classifier即可 pipe = Pipeline([ ('outlier_remover', ClassSpecificOutlierRemover()), ('classifier', AdaBoostClassifier()) ]) # 设置网格搜索的参数:可以把异常值阈值也加入搜索,找到最优标准 param_grid = { 'outlier_remover__threshold': [1.0, 1.5, 2.0], 'classifier__n_estimators': [50, 100, 200], 'classifier__learning_rate': [0.1, 0.5, 1.0] } # 初始化分层交叉验证,确保每折的类别分布和原数据集一致 skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) # 初始化网格搜索,用你关注的平均精度作为评分指标 grid_search = GridSearchCV(pipe, param_grid, cv=skf, scoring='average_precision', n_jobs=-1) # 拟合数据,这里要传X和y,因为自定义类需要标签来分组 grid_search.fit(X, y) # 查看最佳结果 print(f"最佳平均精度: {grid_search.best_score_:.4f}") print(f"最佳参数组合: {grid_search.best_params_}")
几个关键注意点
- 异常值检测方法:我这里用的是鲁棒性强的IQR方法,如果你的数据近似正态分布,也可以换成Z-score(计算每个样本的Z值,剔除绝对值大于3的),或者用Isolation Forest这类模型,只需修改
fit和transform里的逻辑就行。 - 测试集不处理:测试集绝对不能做异常值剔除,否则会引入数据泄露——我们只能用训练集的信息来判断什么是异常值。
- 超参数优化:把异常值的阈值也加入网格搜索,能帮你找到最适合当前数据集的异常值筛选标准,进一步提升模型性能。
备注:内容来源于stack exchange,提问作者hanpat99
相关产品推荐
相关产品推荐

