如何进行组K折验证并保持数据平衡?

2024-10-01 09:32:20 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在根据组值在训练和测试集中分割一些数据。如何才能获得平衡的数据?在

为了解决二元分类任务,我有100个样本,每个样本都有一个唯一的ID一个主题和一个标签(1或0)。在

为了避免在一个人物识别任务中退化,我需要同一个主题不能同时出现在训练和测试集中。在

受试者的数量少于样本数量(57),一些受试者只出现在一个样本中,而在许多具有相同或不同标签的样本中。在

我可以简单地用sklearn的gropKfold来做,但我希望我的数据是平衡的(或者至少接近平衡)

我尝试使用以下代码:

n_shuffles = 2
group_k_fold = GroupKFold(n_splits=5)

        for i in range(n_shuffles):
            X_shuffled, y_shuffled, groups_shuffled = shuffle(idx, labels, subjects, random_state=i)
            splits = group_k_fold.split(X_shuffled, y_shuffled, groups_shuffled)

            for train_idx, val_idx in splits:     
                X = perezDataFrame.loc[perezDataFrame['ID'].isin(X_shuffled[train_idx]),AU_names].values
                X = preprocessing.normalize(X, norm='l2')
                y = perezDataFrame.loc[perezDataFrame['ID'].isin(X_shuffled[train_idx]),'label'].values

                XTest = perezDataFrame.loc[perezDataFrame['ID'].isin(X_shuffled[val_idx]),AU_names].values
                XTest = preprocessing.normalize(XTest, norm='l2')
                yTest = perezDataFrame.loc[perezDataFrame['ID'].isin(X_shuffled[val_idx]),'label'].values

其中idx、subjects和labels分别是ID、subjects和labels的列表。在

但数据非常不平衡。在

我也试过了:

^{pr2}$

但这不是Kfold,所以我不能保证同一个样品只保留一个折。在


Tags: 数据idlabelstrainvalloc样本values
1条回答
网友
1楼 · 发布于 2024-10-01 09:32:20

所以我不认为有一个默认的scikit learn crossvalidator可以实现您想要的,但是应该可以创建一个。在

我的方法是循环检查所有的科目,然后贪婪地将他们分配到测试集中的一部分,这取决于分配对折叠的大小以及目标类比率的改善程度。在

我生成了一些与您的问题类似的示例数据:

import pandas as pd
import numpy as np


n_subjects = 50
n_observations = 100
n_positives = 15

positive_subjects = np.random.randint(0, n_subjects, n_positives)
data = pd.DataFrame({
    'subject': np.random.randint(0, n_subjects, n_observations)
}).assign(
    target=lambda d: d['subject'].isin(positive_subjects)
)


subject target
0   14  False
1   12  True
2   10  False
3   36  False
4   21  False

然后我们可以使用下面的代码片段进行赋值

^{pr2}$

并验证它是否如我们预期的那样工作:


for fold, subjects in test_subjects_per_fold.items():
    print('-'*80)
    print(f'for fold {fold}')
    test_data = data.loc[lambda d: d['subject'].isin(subjects)]
    train_data = data.loc[lambda d: ~d['subject'].isin(subjects)]

    print('train - pos rate:', train_data['target'].mean(), 'size:', len(train_data))
    print('test - pos rate:', test_data['target'].mean(), 'size:', len(test_data))

                                        
for fold 0
train - pos rate: 0.3 size: 80
test - pos rate: 0.3 size: 20
                                        
for fold 1
train - pos rate: 0.3037974683544304 size: 79
test - pos rate: 0.2857142857142857 size: 21
                                        
for fold 2
train - pos rate: 0.2962962962962963 size: 81
test - pos rate: 0.3157894736842105 size: 19
                                        
for fold 3
train - pos rate: 0.3 size: 80
test - pos rate: 0.3 size: 20
                                        
for fold 4
train - pos rate: 0.3 size: 80
test - pos rate: 0.3 size: 20

变量命名可以在这里和那里得到改进,但总的来说,我认为这种方法可以解决您的问题。在

在scikit-learn兼容的crossvalidator中实现这一点看起来像这样,尽管它需要更多的重新设计。在

class StratifiedGroupKFold(_BaseKFold):

    ...


    def _iter_test_indices(self, X, y, groups):
        test_subjects_per_fold = {fold: [] for fold in range(n_folds)}

        for subject in data['subject'].unique():

            target_rate_improvement = np.array([self.target_rate_improvements(X, y, test_subjects_per_fold[fold], subject) for fold in range(self.n_folds)])  
            size_improvements = np.array(self.size_improvement(X, y, test_subjects_per_fold, self.n_folds)) * 0.001
            best_fold = np.argmax(target_rate_improvement +size_improvements)
            test_subjects_per_fold[best_fold] += [subject]

        for subjects in test_subjects_per_fold.values():
            yield data['subject'].isin(subjects)], ~data['subject'].isin(subjects)]

相关问题 更多 >