21from sklearn.metrics
import accuracy_score, f1_score, mean_squared_error, r2_score
34 if y.dtype == object
or str(y.dtype)
in (
"string",
"bool",
"boolean"):
35 return "classification"
36 if y.nunique() <= 20
and y.dtype.kind
in "iu":
37 return "classification"
41def _build_pipeline(method: str, task: str, X: pd.DataFrame, train_cfg: dict[str, Any]) -> Pipeline:
42 num_cols = [c
for c
in X.columns
if pd.api.types.is_numeric_dtype(X[c])]
43 cat_cols = [c
for c
in X.columns
if c
not in num_cols]
44 transformers: list[tuple[str, Any, list[str]]] = []
46 transformers.append((
"num", SimpleImputer(strategy=
"median"), num_cols))
53 (
"impute", SimpleImputer(strategy=
"most_frequent")),
54 (
"oh", OneHotEncoder(handle_unknown=
"ignore", sparse_output=
False)),
62 pre = ColumnTransformer(transformers, remainder=
"drop")
63 if task ==
"classification":
65 LogisticRegression(max_iter=int(train_cfg.get(
"max_iter")
or 400))
67 else HistGradientBoostingClassifier(
68 max_depth=int(train_cfg.get(
"max_depth")
or 6),
69 min_samples_leaf=int(train_cfg.get(
"min_samples_leaf")
or 2),
74 Ridge(alpha=float(train_cfg.get(
"alpha")
or 1.0))
76 else HistGradientBoostingRegressor(
77 max_depth=int(train_cfg.get(
"max_depth")
or 6),
78 min_samples_leaf=int(train_cfg.get(
"min_samples_leaf")
or 2),
81 return Pipeline([(
"pre", pre), (
"est", est)])
84def _scores(task: str, y_true: Any, y_pred: Any) -> dict[str, float]:
85 if task ==
"classification":
87 "accuracy": float(accuracy_score(y_true, y_pred)),
88 "f1_macro": float(f1_score(y_true, y_pred, average=
"macro", zero_division=0)),
90 rmse = float(np.sqrt(mean_squared_error(y_true, y_pred)))
91 return {
"rmse": rmse,
"r2": float(r2_score(y_true, y_pred))}
94def _gate_score(metric: str, scores: dict[str, float], task: str) -> float:
97 return scores[
"accuracy"]
if task ==
"classification" else scores[
"rmse"]
104 if target
not in df.columns:
105 raise RecipeError(f
"target column {target!r} not in {list(df.columns)}")
107 X = df.drop(columns=[target])
110 task =
_task(recipe, y)
111 method = str(recipe.train[
"method"]).lower()
112 seed = int(recipe.train.get(
"seed")
or 0)
113 test_size = float(recipe.train.get(
"test_size")
or 0.2)
115 strat = y
if task ==
"classification" and y.nunique() > 1
else None
117 X_tr, X_te, y_tr, y_te = train_test_split(
118 X, y, test_size=test_size, random_state=seed, stratify=strat
121 X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=test_size, random_state=seed)
125 pred = pipe.predict(X_te)
126 scores =
_scores(task, y_te, pred)
127 recorder.log(1, loss=float(scores.get(
"rmse")
or 1.0 - scores.get(
"accuracy", 0.0)))
129 ckpt_path = out_dir /
"checkpoints" /
"model.pkl"
130 ckpt_path.parent.mkdir(parents=
True, exist_ok=
True)
131 with ckpt_path.open(
"wb")
as fh:
132 pickle.dump({
"pipeline": pipe,
"task": task,
"target": target,
"columns": list(X.columns)}, fh)
133 recorder.signal(1, f
"checkpoint saved ({ckpt_path.name})")
135 inspect: dict[str, Any] = {
"task": task,
"features": list(X.columns)}
137 perm = permutation_importance(pipe, X_te, y_te, n_repeats=5, random_state=seed)
139 zip(X.columns, perm.importances_mean.tolist()),
140 key=
lambda x: abs(x[1]),
143 inspect[
"permutation_importance"] = [
144 {
"feature": str(name),
"importance": float(val)}
for name, val
in ranking
146 except Exception
as exc:
147 inspect[
"permutation_importance_error"] = str(exc)
149 metric = str(recipe.eval.get(
"metric")
or (
"accuracy" if task ==
"classification" else "rmse"))
151 min_score = recipe.eval.get(
"min_score")
152 if min_score
is None:
153 min_score = recipe.eval.get(
"max_score")
if metric ==
"rmse" else None
154 lower_is_better = metric
in (
"rmse",
"mae",
"loss")
155 if min_score
is None:
160 thresh = float(min_score)
161 passed = score <= thresh
if lower_is_better
else score >= thresh
166 score=round(float(score), 6),
167 min_score=
None if min_score
is None else float(min_score),
169 details={
"scores": scores,
"n_train": int(len(X_tr)),
"n_eval": int(len(X_te))},
172 "checkpoint":
Checkpoint(path=str(ckpt_path), kind=
"sklearn", step=1, extra={
"task": task}),