По существу мне нужно 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 вещах:
- Средняя квадратичная разница в процентном представлении каждой категории по сравнению с общим набором данных
- Квадратная разница между пропорциональной длиной набора данных по сравнению с тем, каким она должна быть в соответствии с предоставленным соотношением (60: 20: 20)
Взвешивание этих двух входов в функцию потерь выполняется статическим гиперпараметром hparam_mse_wgt
.Для моего конкретного набора данных значение 0,1 работало хорошо, но я бы посоветовал вам поиграть с ним, если вы используете эту функцию.Установка этого значения в 0 приведет к установлению приоритетов только для поддержания коэффициента разделения и игнорирует стратификацию.Установка в 1 будет наоборот.
Используя эту функцию потерь, я затем перебираю каждый предмет (группу) и добавляю его к соответствующему набору данных (обучение, проверка или тестирование) в зависимости от того, какая из них имеет наибольшую потерю.функция.
Это не особенно сложно, но это делает работу для меня.Он не обязательно будет работать для каждого набора данных, но чем он больше, тем больше шансов.Надеюсь, кто-то найдет это полезным.