How to implement custom loss function in scikit-learn?
Quick answer
scikit-learn does not take arbitrary training loss functions the way Keras does — most estimators have a fixed built-in objective. What you CAN customise is the evaluation metric used for model selection: wrap your metric with make_scorer(your_metric, greater_is_better=...) and pass it to GridSearchCV or cross_val_score. That steers hyperparameter selection, but it does not change what the model minimises during fit. For a genuinely custom training objective, use a library that accepts one — XGBoost or LightGBM take a custom objective returning the gradient and hessian, and SGDRegressor/SGDClassifier expose a fixed menu of built-in losses via the loss= argument.
Short answer: You usually can't pass an arbitrary training loss to a scikit-learn estimator — most have a fixed built-in objective. What you customise is the evaluation metric for model selection: wrap it with make_scorer(fn, greater_is_better=...) and pass it to GridSearchCV/cross_val_score. That steers which model is chosen, not what fit minimises. For a true custom training objective, use XGBoost/LightGBM (which take a gradient+hessian objective).
There's a common misconception here, so it's worth being precise: a loss is what a model minimises during training; a metric/scorer is how you evaluate a fitted model. scikit-learn lets you customise the second freely, but the first is mostly fixed.
What you can't do
Most scikit-learn estimators hard-code their objective — LinearRegression minimises squared error, LogisticRegression minimises log loss — and there's no loss=my_function parameter to override it. The partial exception is SGDClassifier/SGDRegressor, which expose a menu of built-in losses (hinge, log_loss, squared_error, huber, …) via loss=, but you still can't plug in an arbitrary Python function.
What you can do: a custom scorer with make_scorer
If your goal is to select a model by a metric that matters to you (cost-weighted error, a business KPI), wrap it with make_scorer and use it as the scoring argument. Here's a real, runnable example — a cost matrix where false negatives cost 5× a false positive:
import numpy as np
from sklearn.metrics import make_scorer, confusion_matrix
from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import RandomForestClassifier
def cost_metric(y_true, y_pred):
tn, fp, fn, tp = confusion_matrix(y_true, y_pred).ravel()
return fp * 1 + fn * 5 # total cost — lower is better
# greater_is_better=False flips the sign so GridSearchCV minimises it
cost_scorer = make_scorer(cost_metric, greater_is_better=False)
grid = GridSearchCV(
RandomForestClassifier(random_state=0),
param_grid={"max_depth": [3, 5, None]},
scoring=cost_scorer,
)
grid.fit(X_train, y_train)
print(grid.best_params_) # the model with the lowest costgreater_is_better=False tells scikit-learn lower is better, so it negates the score internally and still "maximises". Crucially, this changes which hyperparameters win, not the objective each forest minimises while fitting.
For probability-based metrics, add response_method="predict_proba" (scikit-learn ≥ 1.4) so the scorer receives probabilities rather than hard labels.
What you can do: a true custom training objective (XGBoost/LightGBM)
When you genuinely need the model to train against your loss, use a gradient-boosting library that accepts one. You supply a function returning the gradient and hessian of your loss w.r.t. the prediction:
import numpy as np
from xgboost import XGBRegressor
def squared_log_error(y_pred, dtrain):
y_true = dtrain.get_label()
grad = (np.log1p(y_pred) - np.log1p(y_true)) / (y_pred + 1)
hess = ((-np.log1p(y_pred) + np.log1p(y_true) + 1) / (y_pred + 1) ** 2)
return grad, hess
model = XGBRegressor(objective=squared_log_error, n_estimators=200)
model.fit(X_train, y_train)Choosing the right tool
| Goal | Use |
|---|---|
| Pick the best model by a custom metric | make_scorer + GridSearchCV/cross_val_score |
| A different built-in training loss | SGDClassifier/SGDRegressor loss= |
| A genuinely custom training objective | XGBoost / LightGBM custom objective (grad + hess) |
| Full control over the loss | PyTorch / Keras, not scikit-learn |
Related guides
Sources
Key takeaways
- •scikit-learn estimators mostly have fixed objectives - you cannot pass an arbitrary training loss the way Keras allows.
- •To optimise a custom metric for model selection or cross-validation, wrap it with make_scorer(fn, greater_is_better=...).
- •Set greater_is_better=False when lower is better (a loss) so the score is oriented correctly.
- •For a true custom training objective, use a library that accepts one - XGBoost or LightGBM take a custom objective with gradient and hessian.
Frequently asked questions
Can I pass a custom loss function to a scikit-learn model?
Not as a training objective for most estimators, which have fixed objectives. Use make_scorer to score a custom metric during GridSearchCV or cross_val_score.
What does make_scorer do?
It turns a plain metric function into a scorer that GridSearchCV and cross_val_score can use, with greater_is_better controlling whether higher or lower is better.
How do I optimise a genuinely custom training objective?
Use a gradient-boosting library such as XGBoost or LightGBM, which accept a custom objective function returning the gradient and hessian.
Software Engineering Leader & Technical Author · Updated September 9, 2026