-
Notifications
You must be signed in to change notification settings - Fork 3.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[python-package] custom objective function returns strange leaf node values #5114
Comments
Hi @ShaharKSegal, thank you for your interest in LightGBM. The difference you're seeing is due to the initial score. When you use the built-in objective, it starts the boosting from the mean of the label, i.e. the initial score for each sample is the average of label. When you use a custom objective boosting starts from zero by default, unless you explicitly set the initial scores in the dataset. I've modified your example to incorporate this: import numpy as np
import pandas as pd
import lightgbm as lgb
import sklearn
import sklearn.datasets
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
def l2_loss(y, data):
t = data.get_label()
grad = y - t
hess = np.ones_like(y)
return grad, hess
X, y = sklearn.datasets.load_diabetes(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
lgb_train = lgb.Dataset(X_train, label=y_train)
avg_label = y_train.mean()
ds_with_init_score = lgb.Dataset(X_train, y_train, init_score=np.full_like(y_train, avg_label))
# Using built-in objective
lgbm_params = { 'learning_rate': 0.1, 'objective': 'l2', 'n_estimators': 1, 'random_seed': 0}
model = lgb.train(lgbm_params, lgb_train)
# Using custom objective
model2 = lgb.train(lgbm_params, ds_with_init_score, fobj=l2_loss)
# Perform Inference
y_pred = model.predict(X_test)
y_pred2 = model2.predict(X_test) + avg_label # have to add back the init_score
print(mean_squared_error(y_test, y_pred))
print(mean_squared_error(y_test, y_pred2))
tree_df = model.trees_to_dataframe()
tree_df2 = model2.trees_to_dataframe()
# assert all columns besides the value column
pd.testing.assert_frame_equal(tree_df.drop('value', axis=1), tree_df2.drop('value', axis=1))
print('=' * 20)
# assert value column, doesn't raise an error anymore
pd.testing.assert_series_equal(tree_df.value, tree_df2.value + avg_label) |
Hi @jmoralez , thank you for your quick reply! It seems to do the trick, but feel rather odd that you have to add back the average to the prediction for the custom objective but not for the built in one. |
I'd say a good reference is the boost_from_average parameter. |
This issue has been automatically locked since there has not been any recent activity since it was closed. To start a new related discussion, open a new issue at https://github.com/microsoft/LightGBM/issues including a reference to this. |
Description
I got different leaf node values when using a custom objective function which should be identical to the built-in function (e.g. square loss).
Additional Information
I've inspected the single estimator (tree) case , and it seems that the two models perform the same exact splits, the only difference is the leaf node values.
I would like to note that I suspect that the learning_rate has something to do with it. The learning rate doesn't affect the leaf node values in the built-in objective model for a single tree, but it greatly affects them in the custom loss model (increasing with the learning rate). At learning_rate = 1.0 the two models returns almost identical trees.
Reproducable Example
A toy example (with learning_rate=0.1):
Simulation output:
Environment Info
tested on LGBM 3.3.2 and 3.2.1 (install via pip) with Windows 10 and python 3.7
The text was updated successfully, but these errors were encountered: