Skip to content
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

WIP: Load back parameters from saved model file (fixes #2613) #4802

Closed
wants to merge 9 commits into from

Conversation

zyxue
Copy link
Contributor

@zyxue zyxue commented Nov 14, 2021

This is still just an idea to collect feedbacks.

The approach in this PR:

  1. made loaded_parameter_ an attribute of Boosting instead of GBDT so it can be accessed in c_api.cpp.
  2. implemented LGBM_BoosterGetConfig to return loaded_parameter_ as a string.
  3. loaded_parameter_ can be parsed in Python code into proper types for the params dictionary.

test test_booster_load_params_when_passed_model_str passes

This PR is related to #2613

A couple of questions:

  • why isn't loaded_parameter_ a parameter of Boosting instead of GBDT already? The other boosting types don't have parameters?
  • Do I understand correctly that c_api.cpp can only deal with gbdt as I see it's hard-coded in
    boosting_.reset(Boosting::CreateBoosting("gbdt", filename));

Copy link
Collaborator

@jameslamb jameslamb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @zyxue , sorry for the very long delay without a review! We really appreciate you working on this. I'm ready to review this and help move it forward.

  1. Please remove all commented-out code.
  2. Please merge in the latest changes from master
  3. Please try to get this to a state where at least one test in the Python package is passing for one parameter...then we can iterate from there based on review comments.

At first glance, I have one suggestion about a change to the approach...instead of passing the raw content of the parameters block from a text format back through LGBM_BoosterGetConfig(), I think it would be better to have code on the C/C++ side parse that information and pass back a JSON string.

That way, every interface to LightGBM (for example, the Python and R packages in this repo) doesn't need to have some version of this code (with one line per parameter):

for line in io.StringIO(_params_str):
    if line.startswith('[boosting: '):
        self.params['boosting'] = line.strip().replace(f"[boosting: ", "").replace("]", "")

and could instead just pass the result of LGBM_BoosterGetConfig() to a JSON parser like json in Python or {jsonlite} in R.

@jameslamb jameslamb changed the title Load back parameters from saved model file. Load back parameters from saved model file (fixes #2613) Jan 1, 2022
@jameslamb
Copy link
Collaborator

@zyxue thanks for coming back to this pull request!

As you work on it, please do not rebase + force push. Use merge commits instead (e.g. git merge master). Overwriting the commit history makes it more difficult for reviewers to understand the changes you've made in response to review comments.

This project squashes all pull request commits into a single commit on merge, so you don't need to worry about having too many commits here.

@zyxue
Copy link
Contributor Author

zyxue commented Jan 16, 2022

@jameslamb , gotcha, thank you for the tip.

@zyxue
Copy link
Contributor Author

zyxue commented Jan 17, 2022

@jameslamb
Copy link
Collaborator

Is https://github.com/microsoft/LightGBM/blob/master/include/LightGBM/utils/json11.h expected to be the same as https://raw.githubusercontent.com/dropbox/json11/master/json11.hpp ?

That is where the json11 code in LightGBM is originally from, but we consider it "vendored in", meaning that since it was first added to this project, LightGBM-specific modifications have sometimes been made to it.

@zyxue
Copy link
Contributor Author

zyxue commented Jan 17, 2022

@jameslamb , I've reimplemented LGBM_BoosterGetConfig to return parameters as a json string, please let me know what you think (I'm still relatively new to c++).

Copy link
Collaborator

@jameslamb jameslamb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks very much! I left some suggestions for a reorganization of the code on the C/C++ side. But let's see what other maintainers say.

char* out_str
) {
API_BEGIN();
Booster* ref_booster = reinterpret_cast<Booster*>(handle);
Copy link
Collaborator

@jameslamb jameslamb Jan 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to propose a different organization of this code, but please don't make any changes to the organization until another maintainer like @StrikerRUS, @shiyu1994, or @tongwu-msft comments.

Instead of all this logic being in src/c_api.cpp, I think most of the implementation for LGBM_BoosterGetConfig() should live somewhere else. Similar to how LGBM_BoosterSaveModelToString() is defined here in src/c_api.cpp.

int LGBM_BoosterSaveModelToString(BoosterHandle handle,

And then it is just a thin wrapper on GBDT::SaveModelToString().

bool GBDT::SaveModelToFile(int start_iteration, int num_iteration, int feature_importance_type, const char* filename) const {

I also think it's desirable for any code that needs to list every parameter by name should be in the automatically-generated methods in src/io/config_auto.cpp.

So I think this should be approached as following:

  1. Add a method like GBDT::GetLoadedConfig() which reformats loaded_parameter_ into the appropriate format a parameters string and then initializes a Config object by calling Config::GetMembersFromString()
  2. Add a method in src/io/config_auto.cpp like Config::DumpConfigToJson(), which returns a JSON representation of a Config object.
  3. Alter LGBM_BoosterGetConfig() to get the output of Config::DumpConfigToJson().

Sorry if this is overly complicated. Let's see what other maintainers say. And we are here to help! Thanks again for your help with this feature.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reformats loaded_parameter_ into the appropriate format a parameters string

@jameslamb , do you actually mean reformatting loaded_parameter_ into a std::unordered_map<std::string, std::string> object?

I'm a bit confused by the name GetMembersFromString, it actually means GetMembersFrom an unordered_map, right?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean doing whatever is necessary to turn loaded_parameter_ into a Config object, yes. You're right that the method Config::GetMembersFromString() takes an unordered map with string keys and string values.

@zyxue
Copy link
Contributor Author

zyxue commented Jan 22, 2022

please don't make any changes to the organization until another maintainer like @StrikerRUS, @shiyu1994, or @tongwu-msft comments.
Hey @jameslamb , should I update now or wait till more comments come in?

@jameslamb
Copy link
Collaborator

As I said, please wait until another maintainer offers their opinion. While you wait on that, you can try working through the other suggestions like #4802 (comment).

@jameslamb jameslamb changed the title Load back parameters from saved model file (fixes #2613) WIP: Load back parameters from saved model file (fixes #2613) Feb 11, 2022
} else if (line.rfind("[tree_learner: ", 0) == 0) {
obj["tree_learner"] = Json{extract_param("tree_learner", line)};
} else if (line.rfind("[verbose: ", 0) == 0) {
obj["verbose"] = Json{std::stoi(extract_param("verbose", line))};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can maintain a Key-value map, and load parameters in a for-loop, rather than use many if-elsees.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per #5244, I would find extracting the categorical_feature param helpful as well.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would find extracting the categorical_feature param helpful as well

this is now being addressed in #5424 . @johnpaulett if you're interested in influencing the shape that this support takes in LightGBM, you're welcome to leave review comments on that PR.

@guolinke
Copy link
Collaborator

guolinke commented Mar 1, 2022

@shiyu1994 can you take a look for this PR?

@jameslamb
Copy link
Collaborator

Due to a lack of activity on this pull request, we've decided to move forward with a separate PR for this feature in #5424, so I'm going to close this.

@zyxue thanks very much for your interest in LightGBM and for attempting this! If you have more time to work with us in the future, we'd welcome additional contributions. I'd be happy to suggest some smaller contributions which might not involve as much discussion and require as much of your time and energy.

Copy link

This pull request 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.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 15, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants