Разделение сложного набора данных - StratifiedGroupShuffleSplit - PullRequest
2 голосов
/ 03 июля 2019

У меня есть набор данных ~ 2 м наблюдений, которые мне нужно разделить на обучающие, проверочные и тестовые наборы в соотношении 60:20:20.Упрощенная выдержка из моего набора данных выглядит следующим образом:

+---------+------------+-----------+-----------+
| note_id | subject_id | category  |   note    |
+---------+------------+-----------+-----------+
|       1 |          1 | ECG       | blah ...  |
|       2 |          1 | Discharge | blah ...  |
|       3 |          1 | Nursing   | blah ...  |
|       4 |          2 | Nursing   | blah ...  |
|       5 |          2 | Nursing   | blah ...  |
|       6 |          3 | ECG       | blah ...  |
+---------+------------+-----------+-----------+

Существует несколько категорий, которые неравномерно сбалансированы, поэтому я должен убедиться, что наборы для обучения, проверки и тестирования имеют одинаковое соотношение категорийкак в исходном наборе данных.С этой частью все в порядке, я могу просто использовать StratifiedShuffleSplit из библиотеки sklearn.

Однако мне также необходимо убедиться, что наблюдения по каждому предмету не разделены на наборы данных обучения, проверки и тестирования.Все наблюдения по данному предмету должны быть в одном ведре, чтобы моя обученная модель никогда не видела предмет раньше, когда дело доходит до проверки / тестирования.Например, каждое наблюдение subject_id 1 должно быть в тренировочном наборе.

Я не могу придумать, как обеспечить разделение по категориям на категорию , предотвратить загрязнение (если не найти лучшего слова) subject_id по наборам данных, обеспечитьразделение 60:20:20 и убедитесь, что набор данных каким-то образом перемешан.Любая помощь будет оценена!

Спасибо!


РЕДАКТИРОВАТЬ:

Теперь я узнал, что группирование по категориям и объединение групп по разбиениям набора данных также может быть выполнено с помощью sklearnчерез функцию GroupShuffleSplit.Итак, по сути, мне нужен комбинированный стратифицированный и сгруппированный тасование, то есть StratifiedGroupShuffleSplit, которого не существует.Выпуск Github: https://github.com/scikit-learn/scikit-learn/issues/12076

Ответы [ 2 ]

0 голосов
/ 04 июля 2019

По существу мне нужно StratifiedGroupShuffleSplit, которого не существует ( Github Issue ).Это связано с тем, что поведение такой функции неясно, и выполнить это, чтобы получить набор данных, который является как сгруппированным, так и стратифицированным, не всегда возможно ( также обсуждается здесь ) - особенно с сильно несбалансированным набором данных, таким как мой.В моем случае, я хочу, чтобы группировка выполнялась строго, чтобы гарантировать, что не будет никакого перекрытия групп, в то время как стратификация и разделение набора данных на 60:20:20 будут выполняться приблизительно, то есть так, как это возможно.

Как упоминает Ганем, у меня нет другого выбора, кроме как создать функцию для самостоятельного разделения набора данных, что я и сделал ниже:

def StratifiedGroupShuffleSplit(df_main):

    df_main = df_main.reindex(np.random.permutation(df_main.index)) # shuffle dataset

    # create empty train, val and test datasets
    df_train = pd.DataFrame()
    df_val = pd.DataFrame()
    df_test = pd.DataFrame()

    hparam_mse_wgt = 0.1 # must be between 0 and 1
    assert(0 <= hparam_mse_wgt <= 1)
    train_proportion = 0.6 # must be between 0 and 1
    assert(0 <= train_proportion <= 1)
    val_test_proportion = (1-train_proportion)/2

    subject_grouped_df_main = df_main.groupby(['subject_id'], sort=False, as_index=False)
    category_grouped_df_main = df_main.groupby('category').count()[['subject_id']]/len(df_main)*100

    def calc_mse_loss(df):
        grouped_df = df.groupby('category').count()[['subject_id']]/len(df)*100
        df_temp = category_grouped_df_main.join(grouped_df, on = 'category', how = 'left', lsuffix = '_main')
        df_temp.fillna(0, inplace=True)
        df_temp['diff'] = (df_temp['subject_id_main'] - df_temp['subject_id'])**2
        mse_loss = np.mean(df_temp['diff'])
        return mse_loss

    i = 0
    for _, group in subject_grouped_df_main:

        if (i < 3):
            if (i == 0):
                df_train = df_train.append(pd.DataFrame(group), ignore_index=True)
                i += 1
                continue
            elif (i == 1):
                df_val = df_val.append(pd.DataFrame(group), ignore_index=True)
                i += 1
                continue
            else:
                df_test = df_test.append(pd.DataFrame(group), ignore_index=True)
                i += 1
                continue

        mse_loss_diff_train = calc_mse_loss(df_train) - calc_mse_loss(df_train.append(pd.DataFrame(group), ignore_index=True))
        mse_loss_diff_val = calc_mse_loss(df_val) - calc_mse_loss(df_val.append(pd.DataFrame(group), ignore_index=True))
        mse_loss_diff_test = calc_mse_loss(df_test) - calc_mse_loss(df_test.append(pd.DataFrame(group), ignore_index=True))

        total_records = len(df_train) + len(df_val) + len(df_test)

        len_diff_train = (train_proportion - (len(df_train)/total_records))
        len_diff_val = (val_test_proportion - (len(df_val)/total_records))
        len_diff_test = (val_test_proportion - (len(df_test)/total_records)) 

        len_loss_diff_train = len_diff_train * abs(len_diff_train)
        len_loss_diff_val = len_diff_val * abs(len_diff_val)
        len_loss_diff_test = len_diff_test * abs(len_diff_test)

        loss_train = (hparam_mse_wgt * mse_loss_diff_train) + ((1-hparam_mse_wgt) * len_loss_diff_train)
        loss_val = (hparam_mse_wgt * mse_loss_diff_val) + ((1-hparam_mse_wgt) * len_loss_diff_val)
        loss_test = (hparam_mse_wgt * mse_loss_diff_test) + ((1-hparam_mse_wgt) * len_loss_diff_test)

        if (max(loss_train,loss_val,loss_test) == loss_train):
            df_train = df_train.append(pd.DataFrame(group), ignore_index=True)
        elif (max(loss_train,loss_val,loss_test) == loss_val):
            df_val = df_val.append(pd.DataFrame(group), ignore_index=True)
        else:
            df_test = df_test.append(pd.DataFrame(group), ignore_index=True)

        print ("Group " + str(i) + ". loss_train: " + str(loss_train) + " | " + "loss_val: " + str(loss_val) + " | " + "loss_test: " + str(loss_test) + " | ")
        i += 1

    return df_train, df_val, df_test

df_train, df_val, df_test = StratifiedGroupShuffleSplit(df_main)

Я создал произвольную функцию потерь, основанную на 2 вещах:

  1. Средняя квадратичная разница в процентном представлении каждой категории по сравнению с общим набором данных
  2. Квадратная разница между пропорциональной длиной набора данных по сравнению с тем, каким она должна быть в соответствии с предоставленным соотношением (60: 20: 20)

Взвешивание этих двух входов в функцию потерь выполняется статическим гиперпараметром hparam_mse_wgt.Для моего конкретного набора данных значение 0,1 работало хорошо, но я бы посоветовал вам поиграть с ним, если вы используете эту функцию.Установка этого значения в 0 приведет к установлению приоритетов только для поддержания коэффициента разделения и игнорирует стратификацию.Установка в 1 будет наоборот.

Используя эту функцию потерь, я затем перебираю каждый предмет (группу) и добавляю его к соответствующему набору данных (обучение, проверка или тестирование) в зависимости от того, какая из них имеет наибольшую потерю.функция.

Это не особенно сложно, но это делает работу для меня.Он не обязательно будет работать для каждого набора данных, но чем он больше, тем больше шансов.Надеюсь, кто-то найдет это полезным.

0 голосов
/ 03 июля 2019

Я думаю, что в этом случае вам нужно создать свою собственную функцию для разделения данных. Это реализация мной:

def split(df, based_on='subject_id', cv=5):
    splits = []
    based_on_uniq = df[based_on]#set(df[based_on].tolist())
    based_on_uniq = np.array_split(based_on_uniq, cv)
    for fold in based_on_uniq:
        splits.append(df[df[based_on] == fold.tolist()[0]])
    return splits


if __name__ == '__main__':
    df = pd.DataFrame([{'note_id': 1, 'subject_id': 1, 'category': 'test1', 'note': 'test1'},
                       {'note_id': 2, 'subject_id': 1, 'category': 'test2', 'note': 'test2'},
                       {'note_id': 3, 'subject_id': 2, 'category': 'test3', 'note': 'test3'},
                       {'note_id': 4, 'subject_id': 2, 'category': 'test4', 'note': 'test4'},
                       {'note_id': 5, 'subject_id': 3, 'category': 'test5', 'note': 'test5'},
                       {'note_id': 6, 'subject_id': 3, 'category': 'test6', 'note': 'test6'},
                       {'note_id': 7, 'subject_id': 4, 'category': 'test7', 'note': 'test7'},
                       {'note_id': 8, 'subject_id': 4, 'category': 'test8', 'note': 'test8'},
                       {'note_id': 9, 'subject_id': 5, 'category': 'test9', 'note': 'test9'},
                       {'note_id': 10, 'subject_id': 5, 'category': 'test10', 'note': 'test10'},
                       ])
    print(split(df))
...