Использование выборок при валидации моделей
Рассмотрим, как правильно делить данные на выборки, чтобы потом не приходилось делать это опять, несмотря на использование различных способов предобработки для разных алгоритмов машинного обучения. Сначала скачаем и сделаем быструю обработку датасета о пассажирах "Титаника":
import pandas as pd from sklearn.metrics import roc_auc_score import numpy as np np.random.seed(0) df = pd.read_csv("https://s3.amazonaws.com/h2o-public-test-data/smalldata/gbm_test/titanic.csv") df['sex'] = df['sex'].map({'male':1, 'female':0}) df[['age', 'body', 'fare']] = df[['age', 'body', 'fare']].fillna(df[['age', 'body', 'fare']].agg('median').to_dict()) df = df.fillna('unknown') df = df.sample(frac=1).reset_index(drop=True) df.head()
Теперь определим категориальные и числовые колонки:
id_cols = ['name', 'ticket'] cat_level = 30 target_col = 'survived' cols_ser = df.drop([target_col]+id_cols, axis=1).nunique() cat_cols = cols_ser[cols_ser<=cat_level].index num_cols = cols_ser[cols_ser>cat_level].index str_cols = df.drop([target_col]+id_cols, axis=1).select_dtypes(exclude=np.number).columns cat_cols = cat_cols.union(str_cols).tolist() num_cols = num_cols.difference(str_cols).tolist() display(cat_cols) display(num_cols)
Так как данные перемешаны разделим их с помощью np.split и будем работать с индексами выборок (о способах разделения писал ранее) :
tr_idx, val_idx, ts_idx = np.split(np.arange(df.shape[0]), [round(0.8*df.shape[0]), round(0.9*df.shape[0])])
Ниже демонстрируются способы работы с выборками в алгоритмах, предобработка датасета в которых различается. CatBoost:
from catboost import CatBoostClassifier model_cat = CatBoostClassifier(cat_features = cat_cols, silent=True) model_cat.fit(df.drop(columns=id_cols+[target_col]).iloc[tr_idx], df[target_col].iloc[tr_idx]) auc_cat = roc_auc_score(df[target_col].iloc[val_idx], model_cat.predict_proba(df.drop(columns=id_cols+[target_col]).iloc[val_idx])[:,1]) auc_cat
LightGBM (для категориальных колонок достаточно задать тип category):
from lightgbm import LGBMClassifier model_lgb = LGBMClassifier() model_lgb.fit(df.drop(columns=id_cols+[target_col]).\ astype({it:'category' for it in cat_cols}).iloc[tr_idx], df[target_col].iloc[tr_idx]) auc_lgb = roc_auc_score(df[target_col].iloc[val_idx], model_lgb.predict_proba(df.drop(columns=id_cols+[target_col])\ .astype({it:'category' for it in cat_cols}).iloc[val_idx])[:,1]) auc_lgb
Для логистической регрессии требуется большая предобработка, поэтому придется создать свои датасеты, но без нового разбиения. Сначала кодирование:
from sklearn.preprocessing import OneHotEncoder oh = OneHotEncoder(sparse=False, handle_unknown='ignore') ar_tr = oh.fit_transform(df[cat_cols].iloc[tr_idx]) ar_val = oh.transform(df[cat_cols].iloc[val_idx]) ar_ts = oh.transform(df[cat_cols].iloc[ts_idx]) X_tr = pd.DataFrame(ar_tr, columns=oh.get_feature_names_out(cat_cols), index=df.iloc[tr_idx].index) X_val = pd.DataFrame(ar_val, columns=oh.get_feature_names_out(cat_cols), index=df.iloc[val_idx].index) X_ts = pd.DataFrame(ar_ts, columns=oh.get_feature_names_out(cat_cols), index=df.iloc[ts_idx].index) X_y_tr = pd.concat([df[id_cols+[target_col]+num_cols].iloc[tr_idx], X_tr], axis=1) X_y_val = pd.concat([df[id_cols+[target_col]+num_cols].iloc[val_idx], X_val], axis=1) X_y_ts = pd.concat([df[id_cols+[target_col]+num_cols].iloc[ts_idx], X_ts], axis=1)
from sklearn.preprocessing import StandardScaler sc = StandardScaler() X_y_tr[num_cols] = sc.fit_transform(X_y_tr[num_cols]) X_y_val[num_cols] = sc.transform(X_y_val[num_cols]) X_y_ts[num_cols] = sc.transform(X_y_ts[num_cols])
from sklearn.linear_model import LogisticRegression model_log = LogisticRegression() model_log.fit(X_y_tr.drop(columns=id_cols+[target_col]), X_y_tr[target_col]) roc_auc_score(X_y_val[target_col],model_log.predict_proba( X_y_val.drop(columns=id_cols+[target_col]))[:,1])
Сравните этот способ с применением Pipeline-ов. Он позволяет работать с датасетом без особой предобработки почти так же, как CatBoost или LightGBM:
from sklearn.compose import make_column_transformer from sklearn.pipeline import Pipeline ct = make_column_transformer((OneHotEncoder(sparse=False, handle_unknown='ignore'), cat_cols), (StandardScaler(), num_cols), remainder='passthrough') model_log_pipe = Pipeline(steps=[('prep', ct), ('clf', LogisticRegression())]) model_log_pipe.fit(df.drop(columns=id_cols+[target_col]).iloc[tr_idx], df[target_col].iloc[tr_idx]) roc_auc_score(df[target_col].iloc[val_idx], model_log_pipe.predict_proba( df.drop(columns=id_cols+[target_col]).iloc[val_idx])[:,1])
После сравнения валидационных метрик отбираем лучшую модель и тестируем. При этом перед проверкой на тест данных обучаемся на датасете из тренировочной и валидационной выборок:
tr_val_idx = np.concatenate([tr_idx, val_idx]) model_cat.fit(df.drop(columns=id_cols+[target_col]).iloc[tr_val_idx], df[target_col].iloc[tr_val_idx]) roc_auc_score(df[target_col].iloc[ts_idx], model_cat.predict_proba( df.drop(columns=id_cols+[target_col]).iloc[ts_idx])[:,1])
Отмечу, что выше раскрыты не все шаги по валидации моделей, полный стек включает еще отбор гиперпараметров и признаков.
Не пропустите ничего интересного и подписывайтесь на страницы канала в других социальных сетях: