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

[python-package] Passing non-serializable loss function since 4.0.0. #6056

Open
Deimos357 opened this issue Aug 20, 2023 · 2 comments
Open
Labels

Comments

@Deimos357
Copy link
Contributor

Deimos357 commented Aug 20, 2023

Hello. I have custom non-serializable loss function which I was passing through fobj param of lgb.train().

Since #5052 it's not possible any more.

Also it's not possible to pass non-serializable loss function as params['fobj'] value, because of multiple copy.deepcopy(params) usages in train() function.

So what is the way to pass such loss function in 4.0.0?

@jameslamb
Copy link
Collaborator

Can you please share the function definition and a minimal, reproducible example showing how you're using it and the error you're getting?

@jameslamb jameslamb changed the title Passing non-serializable loss function since 4.0.0. [python-package] Passing non-serializable loss function since 4.0.0. Aug 20, 2023
@Deimos357
Copy link
Contributor Author

@jameslamb
Minimal example for 4.0.0:

import threading

import lightgbm as lgb
import numpy as np


class LossWrapper:
    def __init__(self):
        self.barrier = threading.Barrier(2)

    def calc(self, pred, data):
        y = data.get_label()
        return pred - y, np.ones(len(y))


def main():
    dataset = lgb.Dataset(np.random.rand(100, 2), np.random.rand(100))
    loss_wrapper = LossWrapper()
    booster = lgb.train(params={'objective': loss_wrapper.calc}, train_set=dataset)


if __name__ == '__main__':
    main()

Output:

Traceback (most recent call last):
  File "D:\stuff\lgbm-test\400\main.py", line 23, in <module>
    main()
  File "D:\stuff\lgbm-test\400\main.py", line 19, in main
    booster = lgb.train(params={'objective': loss_wrapper.calc}, train_set=dataset)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\stuff\lgbm-test\400\venv\Lib\site-packages\lightgbm\engine.py", line 159, in train
    params = copy.deepcopy(params)
             ^^^^^^^^^^^^^^^^^^^^^
  File "D:\python311\Lib\copy.py", line 146, in deepcopy
    y = copier(x, memo)
        ^^^^^^^^^^^^^^^
  File "D:\python311\Lib\copy.py", line 231, in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
                             ^^^^^^^^^^^^^^^^^^^^^
  File "D:\python311\Lib\copy.py", line 146, in deepcopy
    y = copier(x, memo)
        ^^^^^^^^^^^^^^^
  File "D:\python311\Lib\copy.py", line 238, in _deepcopy_method
    return type(x)(x.__func__, deepcopy(x.__self__, memo))
                               ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\python311\Lib\copy.py", line 172, in deepcopy
    y = _reconstruct(x, memo, *rv)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\python311\Lib\copy.py", line 271, in _reconstruct
    state = deepcopy(state, memo)
            ^^^^^^^^^^^^^^^^^^^^^
  File "D:\python311\Lib\copy.py", line 146, in deepcopy
    y = copier(x, memo)
        ^^^^^^^^^^^^^^^
  File "D:\python311\Lib\copy.py", line 231, in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
                             ^^^^^^^^^^^^^^^^^^^^^
  File "D:\python311\Lib\copy.py", line 172, in deepcopy
    y = _reconstruct(x, memo, *rv)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\python311\Lib\copy.py", line 271, in _reconstruct
    state = deepcopy(state, memo)
            ^^^^^^^^^^^^^^^^^^^^^
  File "D:\python311\Lib\copy.py", line 146, in deepcopy
    y = copier(x, memo)
        ^^^^^^^^^^^^^^^
  File "D:\python311\Lib\copy.py", line 231, in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
                             ^^^^^^^^^^^^^^^^^^^^^
  File "D:\python311\Lib\copy.py", line 172, in deepcopy
    y = _reconstruct(x, memo, *rv)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\python311\Lib\copy.py", line 271, in _reconstruct
    state = deepcopy(state, memo)
            ^^^^^^^^^^^^^^^^^^^^^
  File "D:\python311\Lib\copy.py", line 146, in deepcopy
    y = copier(x, memo)
        ^^^^^^^^^^^^^^^
  File "D:\python311\Lib\copy.py", line 231, in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
                             ^^^^^^^^^^^^^^^^^^^^^
  File "D:\python311\Lib\copy.py", line 161, in deepcopy
    rv = reductor(4)
         ^^^^^^^^^^^
TypeError: cannot pickle '_thread.lock' object

For 3.3.5 we can change
booster = lgb.train(params={'objective': loss_wrapper.calc}, train_set=dataset)
to
booster = lgb.train(params={}, train_set=dataset, fobj=loss_wrapper.calc)
and training will work fine.

Obviously this example makes no sense. In practice I'm synchronizing loss calculation of multiple boosters so they can share multi-variable loss through the wrapper.
I can rewrite whole python-package training loop to achieve the same results, but implementation with threading is much easier and cleaner.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants