测试折叠上的 CV 和欠采样
Posted
技术标签:
【中文标题】测试折叠上的 CV 和欠采样【英文标题】:CV and under sampling on a test fold 【发布时间】:2021-08-04 19:43:13 【问题描述】:我在构建包含不平衡数据的 ML 分类器时有点迷失 (80:20)。数据集有 30 列;目标是标签。 我想预测主要课程。 我正在尝试重现以下步骤:
在训练/测试上拆分数据 在火车组上执行 CV 仅在测试折叠上应用欠采样 借助 CV 选择模型后,对训练集进行欠采样并训练分类器 估计未触及测试集的性能(召回)我所做的如下所示:
y = df['Label']
X = df.drop('Label',axis=1)
X.shape, y.shape
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 12)
X_train.shape, X_test.shape
tree = DecisionTreeClassifier(max_depth = 5)
tree.fit(X_train, y_train)
y_test_tree = tree.predict(X_test)
y_train_tree = tree.predict(X_train)
acc_train_tree = accuracy_score(y_train,y_train_tree)
acc_test_tree = accuracy_score(y_test,y_test_tree)
我对如何在训练集上执行 CV、在测试折叠上应用欠采样以及对训练集进行欠采样和训练分类器有一些疑问。 你熟悉这些步骤吗?如果你是,我会很感激你的帮助。
如果我这样做:
y = df['Label']
X = df.drop('Label',axis=1)
X.shape, y.shape
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 12)
X_train.shape, X_test.shape
tree = DecisionTreeClassifier(max_depth = 5)
tree.fit(X_train, y_train)
y_test_tree = tree.predict(X_test)
y_train_tree = tree.predict(X_train)
acc_train_tree = accuracy_score(y_train,y_train_tree)
acc_test_tree = accuracy_score(y_test,y_test_tree)
# CV
scores = cross_val_score(tree,X_train, y_train,cv = 3, scoring = "accuracy")
ypred = cross_val_predict(tree,X_train,y_train,cv = 3)
print(classification_report(y_train,ypred))
accuracy_score(y_train,ypred)
confusion_matrix(y_train,ypred)
我得到这个输出
precision recall f1-score support
-1 0.73 0.99 0.84 291
1 0.00 0.00 0.00 105
accuracy 0.73 396
macro avg 0.37 0.50 0.42 396
weighted avg 0.54 0.73 0.62 396
我想我在上面的代码中遗漏了一些东西或做错了什么。
数据样本:
Have_0 Have_1 Have_2 Have_letters Label
1 0 1 1 1
0 0 0 1 -1
1 1 1 1 -1
0 1 0 0 1
1 1 0 0 1
1 0 0 1 -1
1 0 0 0 1
【问题讨论】:
【参考方案1】:通常,创建交叉验证集的最佳方法是模拟您的测试数据。在您的情况下,如果我们要将您的数据分为 3 个集合(训练、交叉验证、测试),最好的方法是创建具有相同比例的真标签/假标签的集合。这就是我在以下函数中所做的。
import numpy as np
import math
X=DF[["Have_0","Have_1","Have_2","Have_letters"]]
y=DF["Label"]
def create_cv(X,y):
if type(X)!=np.ndarray:
X=X.values
y=y.values
test_size=1/5
proportion_of_true=y[y==1].shape[0]/y.shape[0]
num_test_samples=math.ceil(y.shape[0]*test_size)
num_test_true_labels=math.floor(num_test_samples*proportion_of_true)
num_test_false_labels=math.floor(num_test_samples-num_test_true_labels)
y_test=np.concatenate([y[y==0][:num_test_false_labels],y[y==1][:num_test_true_labels]])
y_train=np.concatenate([y[y==0][num_test_false_labels:],y[y==1][num_test_true_labels:]])
X_test=np.concatenate([X[y==0][:num_test_false_labels] ,X[y==1][:num_test_true_labels]],axis=0)
X_train=np.concatenate([X[y==0][num_test_false_labels:],X[y==1][num_test_true_labels:]],axis=0)
return X_train,X_test,y_train,y_test
X_train,X_test,y_train,y_test=create_cv(X,y)
X_train,X_crossv,y_train,y_crossv=create_cv(X_train,y_train)
通过这样做,我们得到了具有以下形状的集合(它们都具有相同比例的真标签/假标签):
【讨论】:
谢谢你的回答,哈坎。请问你这是否也考虑到不平衡类?因为我猜想这个 70:30 的不平衡会导致不好的结果,可能我也应该考虑抽样不足(尽管如果我不知道在哪一步)。 StratifiedShuffleSplit 能做到吗?您是否有任何相关信息(例如,纸张)可以证明这种选择的合理性?我发现很多论文更喜欢随机抽样(under/over/SMOTE)+ CV。感谢您的帮助 是的,这考虑了不平衡,因为它保留了不平衡的比率。但是,根据数据欠采样也可能会导致更好的结果。我的建议是 bagging & ensembling,你不需要选择一个数据拆分,你基本上可以使用这两种技术并获得集成。关于stratifeidss,我觉得StratifiedShuffleSplit和我做的有点相似。我认为这种情况下更可取。您可以做的另一件事是尽可能创建人工真实的标签。 有道理,谢谢哈坎。当我尝试运行代码时出现此错误:Name: Bool_Label, Length: 466, dtype: bool)' is an invalid key
由于 ---> 15 X_test=np.concatenate([X[:,y==0][:,:num_test_false_labels],X[:,y==1][:,:num_test_true_labels]],axis=1)
。因为我有 -1 而不是 1(我想预测的类;它是我数据集中的大多数)和 1 而不是 0,所以我将它们替换如下:df['Bool_Label'] = np.where(df['Label']==-1, 1, 0)
。你知道那个错误是什么意思和/或那行代码应该是哪个值?
嗯,如果你有标签 1 和 -1,你可以用 -1 替换 0 并再试一次吗?(基本上什么错误说数据框没有这个键值所以它是无效的)
我认为它应该是这样的:***.com/questions/55291667/… 但我不知道如何在这个例子中使用它。我在 X[] 中添加了 .iloc,但出现了一个新错误【参考方案2】:
我假设您的测试数据不具有代表性,因为它对于此目的来说太小了(拆分几次后剩下的不多,因为Cross-Validation 正在进一步拆分数据集)。
对于欠采样和过采样,有一个很棒的库,名为 imbalanced-learn。它还带有良好的文档,例如under sampling。
鉴于您的样本数据:
from io import StringIO
import pandas as pd
from sklearn.model_selection import train_test_split
from imblearn.under_sampling import RandomUnderSampler
df = pd.read_csv(StringIO(
"""
Have_0 Have_1 Have_2 Have_letters Label
1 0 1 1 1
0 0 0 1 -1
1 1 1 1 -1
0 1 0 0 1
1 1 0 0 1
1 0 0 1 -1
1 0 0 0 1
"""
), sep='\s+')
y = df['Label']
X = df.drop('Label',axis=1)
然后您可以对训练数据集进行欠采样:
under_sampler = RandomUnderSampler(random_state=0)
X_train_resampled, y_train_resampled = under_sampler.fit_resample(X_train, y_train)
您可以将其传递给交叉验证。缺点是它将根据平衡数据集进行评估(作为 CV 的一部分)。不过,这对于模型选择来说可能没问题。相反,您可以将采样应用于每个 CV 折叠的训练拆分(就像您必须为过采样所做的那样)。
如果您的数据集很小,那么您可以改用over sampling。在这种情况下,您需要注意过度采样的数据不会在之后拆分。那是因为它会造成数据泄漏(导致错误的分数;请参阅imbalance-learn's common pitfalls)。使用train_test_split
很容易避免这种情况,因为您可以在调用train_test_split
之后调用采样器。但是使用交叉验证会导致更多的分裂(隐藏在cross_val_score
中)。这里需要在每次 CV 拆分后进行过采样。例如,您可以使用 sklearn 的 KFold 或 StratifiedKFold 类来做到这一点。
类似这样的:
def get_train_sampled_cv_splits(train_test_indices_splits, sampler, y):
for train_indices, test_indices in train_test_indices_splits:
y_train_split = y.iloc[train_indices]
train_indices_resampled, _ = sampler.fit_resample(train_indices.reshape(-1, 1), y_train_split)
yield train_indices_resampled.reshape(-1), test_indices
over_sampler = RandomOverSampler(random_state=0)
kf = KFold(n_splits=2, shuffle=True, random_state=42)
resampled_train_test_indices_splits = get_train_sampled_cv_splits(
kf.split(X_train, y_train),
over_sampler,
y
)
cross_val_score(tree, X_train, y_train, cv=resampled_train_test_indices_splits, scoring="f1")
您还应该注意不平衡数据集的指标(准确度通常不是很好)。有人在 Kaggle 上分享了一张图表 Evaluation Metrics for Imbalanced Classification,可能有用。
【讨论】:
以上是关于测试折叠上的 CV 和欠采样的主要内容,如果未能解决你的问题,请参考以下文章
线性混合效应模型Linear Mixed-Effects Models的部分折叠Gibbs采样
仅在训练折叠上使用带有 SMOTE 过采样的 sklearn 的 RandomizedSearchCV