Использование выборок при валидации моделей
Рассмотрим, как правильно делить данные на выборки, чтобы потом не приходилось делать это опять, несмотря на использование различных способов предобработки для разных алгоритмов машинного обучения. Сначала скачаем и сделаем быструю обработку датасета о пассажирах "Титаника":
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_catLightGBM (для категориальных колонок достаточно задать тип 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])
Отмечу, что выше раскрыты не все шаги по валидации моделей, полный стек включает еще отбор гиперпараметров и признаков.
Не пропустите ничего интересного и подписывайтесь на страницы канала в других социальных сетях: