我有一个不平衡的数据集,如下所示:
df['y'].value_counts(normalize=True) * 100
No 92.769441
Yes 7.230559
Name: y, dtype: float64
该数据集由 13194 行和 37 个特征组成。
我已经尝试过无数次尝试通过过采样和欠采样来平衡数据、用于异常值检测的一类 SVM、使用不同的分数、超参数调整等来提高模型的性能。其中一些方法略微提高了性能,但不如就像我想的那样:
应用 RandomUnderSampling:
from imblearn.under_sampling import RandomUnderSampler
rus = RandomUnderSampler(random_state=42)
X_train_rus, y_train_rus = rus.fit_resample(X_train, y_train)
# Define and fit AdaBoost classifier using undersampled data
ada_rus = AdaBoostClassifier(n_estimators=100, random_state=42)
ada_rus.fit(X_train_rus,y_train_rus)
y_pred_rus = ada_rus.predict(X_test)
evaluate_model(y_test, y_pred_rus)
使用 SMOTE 等过采样技术:
# SMOTE
from imblearn.over_sampling import SMOTE
# upsample minority class using SMOTE
sm = SMOTE(random_state=42)
X_train_sm, y_train_sm = sm.fit_sample(X_train, y_train)
# Define and fit AdaBoost classifier using upsample data
ada_sm = AdaBoostClassifier(n_estimators=100, random_state=42)
ada_sm.fit(X_train_sm,y_train_sm)
y_pred_sm = ada_sm.predict(X_test)
# compare predicted outcome through AdaBoost upsampled data with real outcome
evaluate_model(y_test, y_pred_sm)
然后我决定尝试从大多数类的样本中删除缺少数据的行,正如我在一篇文章中看到的那样。我通过增加 pandas dropna 函数中的阈值(thresh)参数逐渐做到了这一点,每次删除更多行时,性能都会提高。最后,我从多数类中删除了所有缺少数据的行,如下所示:
df_majority_droppedRows = df.query("y == 'No'").dropna()
df_minority = df.query("y == 'Yes'")
dfWithDroppedRows = pd.concat([df_majority_droppedRows, df_minority])
print(dfWithDroppedRows.shape)
(1632, 37)
这将我的行数显着减少到 1632 并改变了目标变量中的分布,使得以前的少数类(“是”)现在是多数类:
Yes 58.455882
No 41.544118
Name: y, dtype: float64
测试模型,我发现它表现最好,具有高召回率和精度值。
所以我的问题是,
为什么这种方法优于其他过采样和欠采样技术?
以前的少数类现在是多数类是否可以接受,或者这会导致过度拟合吗?
为大多数类样本构建一个依赖输入且没有缺失数据的模型是否现实?
编辑
针对@Ben Reiniger 评论中的问题:
- 我通过使用 KNNImputer 处理数字数据和 SimpleImputer 处理分类数据来处理数据中的缺失值,如下所示:
def preprocess (X):
# define categorical and numeric transformers
numeric_transformer = Pipeline(steps=[
('knnImputer', KNNImputer(n_neighbors=2, weights="uniform")),
('scaler', StandardScaler())])
categorical_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='constant', fill_value='missing')),
('onehot', OneHotEncoder(handle_unknown='ignore'))])
preprocessor = ColumnTransformer(transformers=[
('cat', categorical_transformer, selector(dtype_include=['object'])),
('num', numeric_transformer, selector(dtype_include=['float64','int64']))
])
X = pd.DataFrame(preprocessor.fit_transform(X))
return X
- 删除行后,我定义了矩阵和目标的特征,预处理然后拆分数据,如下所示:
# make feature matrix and target matrix
X = dfWithDroppedRows.drop(columns=['y'])
y = dfWithDroppedRows['y']
# encode target variable
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
y = le.fit_transform(y)
# preprocess feature matrix
X=preprocess(X)
# Split data into training and testing data
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, test_size=0.2, random_state=42)
- 最后,为了计算缺失值在两个类中是否相同分布(最初),我运行了以下
np.count_nonzero(df.query("y == 'No'").isna()) / df.query("y == 'No'").size
0.2791467938526762
np.count_nonzero(df.query("y == 'Yes'").isna()) / df.query("y == 'Yes'").size
0.24488639582979205
所以多数类有大约 28% 的缺失数据,少数类有大约 25% 的缺失数据。