The Data Science Lab
Regression Using LightGBM
Documentation for the parameters can be found here and here.
Because the number of parameters is not manageable, you must rely on the default values and then try to find the handful of parameters that will create a good model. Based on my experience, the three most important parameters to explore and modify are n_estimators, min_data_in_leaf and learning_rate.
A LightGBM regression model is made up of n_estimators (default value is 100) relatively small decision trees that are called weak learners, or sometimes base learners. The weak trees are constructed sequentially where each tree uses gradients of the error from the previous tree. If the value of n_estimators is too small, then there aren't enough weak learners to create a model that predicts well (underfit). If the value of n_estimators is too large, then the model will overfit the training data and predict poorly on new, previously unseen data items.
The num_leaves parameter controls the overall size of the weak learner trees. The default value of 31 translates to a balanced tree that has five levels with 1, 2, 4, 8, 16 leaf nodes respectively. An unbalanced tree might have more levels. Weak learners that are too small might underfit, too large might overfit.
The max_depth parameter controls the number of levels that each weak learner has. The default value is -1 which means that there is no explicit limit. In most cases, the num_leaves parameter will prevent the depth of the weak learners from becoming too large.
The min_data_in_leaf parameter controls the size of the leaf nodes in the weak learners. The default value of 20 means that each leaf node must have at least 20 associated data items. For a relatively small set of training data, the default greatly reduces the number of leaf nodes. For the demo with 200 training items, there would be a maximum of 200 / 20 = 10 leaf nodes, which would likely underfit the model and lead to poor prediction accuracy. The demo modifies the value of min_data_in_leaf from 20 to 2, which gave much better results.
To recap, the n_estimators parameter controls the overall number of weak tree learners. The key parameters to control the size and shape of the weak learners are num_leaves, max_depth and min_data_in_leaf. Based on my experience, I typically experiment with n_estimators (the default value of 100 is often too large for small datasets) and min_data_in_leaf (the default of 20 is often too large for small datasets). I usually leave the num_leaves and max_depth parameter values at their default values of 31 and -1 (unlimited) respectively unless the model just doesn't predict well.
The demo modifies the learning_rate parameter from the default value of 0.10 to 0.05. The learning rate controls how much each weak learner tree changes from the previous learner. The effect of changing the learning_rate can vary quite a bit depending on the size and shape of the weak learners, but as a rule of thumb, smaller values work better for smaller datasets.
The demo modifies the value of the random_state parameter from its default value of None (Python's version of null) to 0. The None value means that results are not reproducible due to the random initialization component of the training process. Any value other than None will give (mostly) reproducible results, subject to multi-threading issues.
The demo modifies the value of the verbosity parameter from its default value of 1 to -1. The default value of 1 prints warning messages, regular error messages and fatal error messages. The demo value of -1 prints only fatal error messages. I did this only to keep the output small so I could take a screenshot. In a non-demo scenario you should leave the verbosity value at 1 in most situations.
After setting up the parameter values in a Dictionary collection, they are passed to the LGBMRegressor using the Python ** syntax which means unpack the values to parameters. Parameter values can be passed directly, for example model = lgbm.LGBMRegressor(n_estimators = 50, learning_rate = 0.05 and so on), but because there are so many parameters, this approach is rarely used.
The model is trained using the fit() method. Almost too easy because all the work is done when setting up the parameters.
Evaluating the Model
It's possible to evaluate a trained LightGBM regression model in several ways. The most basic approach is to compute prediction accuracy (number correct predictions divided by total number of predictions) on the training and test data. The demo program defines an accuracy() function where the key calling statements are:
acc_train = accuracy(model, x_train, y_train, 0.07)
print("accuracy on train data = %0.4f " % acc_train)
acc_test = accuracy(model, x_test, y_test, 0.07)
print("accuracy on test data = %0.4f " % acc_test))
The output of the simple accuracy() function is:
accuracy on train data = 0.9350
accuracy on test data = 0.7250
Recall that the demo accuracy() function defines a correct income prediction as one that's within 7 percent of the true income.
In many scenarios, a simple accuracy metric of the trained model computed on the training and test data is good enough. But in some scenarios, it's better to compute the accuracy of the trained model for various intervals of the dependent/target variable. The demo program defines an accuracy_matrix() function to compute accuracy for different intervals of target income, and a show_acc_matrix() to display a computed accuracy matrix.
The calling code for evaluating the training data is:
inc_pts = [0.00, 25000.00, 50000.00, 75000.00, 100000.00]
am_train = \
accuracy_matrix(model, x_train, y_train, 0.07, inc_pts)
print("Accuracy on training data (within 0.07 of true):")
show_acc_matrix(am_train, inc_pts)
The inc_pts ("income points") list defines four income intervals: $0 to $25,000, $25,000 to $50,000, $50,000 to $75,000, and $75,000 to $100,000. The output is:
Accuracy on training data (within 0.07 of true):
from to correct wrong count accuracy
0.00 25000.00 4 0 4 1.0000
25000.00 50000.00 74 10 84 0.8810
50000.00 75000.00 99 3 102 0.9706
75000.00 100000.00 10 0 10 1.0000
The test data is evaluated similarly. The output shows that the model is quite accurate when predicting incomes that are greater than $50,000 but not as accurate when predicting smaller incomes.
Using and Saving the LightGBM Regression Model
Using a trained LightGBM regression model is simple, subject to two minor syntax details. Example calling code is:
# male, age 35, Oklahoma, moderate
x = np.array([[0, 35, 2, 1]], dtype=np.float64)
y_pred = model.predict(x)
print("Predicted income = %0.2f " % y_pred[0])
Notice that the input x values have double square brackets to make the input a 2D matrix, which the LightGBM model predict() method requires. Alternatively, you can declare a 1D vector then reshape it to 2D:
x = np.array([0, 35, 2, 1], dtype=np.float64) # 1D
x = x.reshape(1, -1) # 1 row, n cols 2D
pred = model.predict(x)
The return value from the predict() method is an array rather than a scalar value. So, when the input is a single data item, and you want just the single predicted class, you can access the class at index [0] like so:
Y_pred = model.predict(x)
print("Predicted income = %0.2f " % y_pred[0])
Alternatively:
y_pred = model.predict(x) # array
y_pred = pred[0] # scalar
print("Predicted income = %0.2f " % y_pred)
The demo program does not save the trained LightGBM regression model. If you want to save a model you can do so in binary format using the Python pickle library. (In ordinary English, the word "pickle" means to preserve). The calling code would be:
import pickle
print("Saving model ")
pth = ".\\Models\\regression_model.pkl"
with open(pth, "wb") as f:
pickle.dump(model, f)
The code assumes the existence of a subdirectory named Models. The "wb" argument means "write to file as binary." The "pkl" extension is common but any extension name can be used.
A LightGBM model saved using pickle can be loaded into memory from another program and used like so:
pth = ".\\Models\\regression_model.pkl"
with open(pth, "rb") as f:
model2 = pickle.load(f)
x = np.array([[0, 35, 2, 1]], dtype=np.float64)
pred = model2.predict(x)
There are other ways to save a trained LightGBM model, but the pickle approach is the easiest and the most common.
Wrapping Up
The LightGBM system was inspired by the XGBoost (extreme gradient boosting) system, which in turn was inspired by earlier tree boosting algorithms. The "boosting" term of the LightGBM name refers to the technique of combining several weak learners into one strong learning model. The "gradient" term refers to the technique of using the Calculus gradient of the error of a weak learner to construct the next weak learner in the model sequence. The "machine" term is an old way to indicate that a system is a machine learning one rather than a classical statistics one.
Arguably, the two most powerful techniques for regression on non-trivial datasets are neural networks and tree boosting. In several recent regression problem contests, LightGBM entries did very well. This may be due, in part, to the fact that LightGBM can be used out-of-the-box, which leaves a lot of time for hyperparameter fine-tuning. Creating a neural network regression model requires significantly more background knowledge and effort.
About the Author
Dr. James McCaffrey works for Microsoft Research in Redmond, Wash. He has worked on several Microsoft products including Azure and Bing. James can be reached at [email protected].