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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add no improvement handler (similar to early stopping handler) #2314

Open
arisliang opened this issue Nov 9, 2021 · 8 comments 路 May be fixed by #2574
Open

Add no improvement handler (similar to early stopping handler) #2314

arisliang opened this issue Nov 9, 2021 · 8 comments 路 May be fixed by #2574
Assignees
Projects

Comments

@arisliang
Copy link

馃殌 Feature

To add a 'no-improvement' handler. It's similar to early stopping, except that early stopping calls the training to terminate, 'no-improvement' handler would've calls a function that user pass in. User could pass in different functions that they want to execute if they find no improvements is made. They may want to try various things before terminate the training.

@sdesrozis
Copy link
Contributor

@arisliang Thank you for this FR !

Could we imagine that the actual EarlyStopping is just a specific case of the more general NoImprovmentHandler you suggested? I mean NoImprovmentHandler + Stop Policy Function = EarlyStopping? And maybe it's worth considering other trending policies than no-improvment like too-fast-improvment or whatever. What do you think ?

@arisliang
Copy link
Author

arisliang commented Nov 10, 2021

@sdesrozis Thanks for the response.

Yes exactly. I'd imagine EarlyStopping would be a specific case of the more general NoImprovmentHandler by passing in the Stop Policy Function. So NoImprovement determines a scenario where no improvement is made, and decouples the action to be taken, be it stop training, logging, or other things.

I haven't thought about other trending policies. It's certainly possible as another enhancement to make things even more general.

@arisliang
Copy link
Author

Not sure which is more suitable, no-improvement may even be thought of as an scenario-based event, and actions like stop training can then register as an event handler when no-improvement happens.

@vfdev-5 vfdev-5 added this to To do in 0.5.1 via automation Nov 10, 2021
@sdesrozis
Copy link
Contributor

@arisliang I will try to write a piece of code to illustrate my thoughts.

@sdesrozis
Copy link
Contributor

sdesrozis commented Nov 16, 2021

Ok so the actual EarlyStopping is the following.

class EarlyStopping:
    
    def __init__(self, patience, score_function, trainer):
        self.score_function = score_function
        self.patience = patience
        self.trainer = trainer
        self.counter = 0
        self.best_score = float('-inf')
        
    def __call__(self, engine):
        score = self.score_function(engine)

        if score <= self.best_score:
            self.best_score = score
            self.counter += 1
            print("EarlyStopping: %i / %i" % (self.counter, self.patience))
            if self.counter >= self.patience:
                print("EarlyStopping: Stop training")
                self.trainer.terminate()
        else:
            self.best_score = score
            self.counter = 0

We could think about a more flexible implementation based on a score_function (as today) associated to a stop_function (i.e. what to do whether no improvment) and a pass_function (i.e. what to do whether improvment).

class TriggerHandlerState:

    def __init__(self):
        self.best_score = float('-inf')
        self.counter = 0

class TriggerHandler:

    def __init__(self, score_function, pass_function, stop_function):
        self.score_function = score_function
        self.pass_function= pass_function
        self.stop_function = stop_function
        self.state = TriggerHandlerState()

    def __call__(self, engine):

        score = self.score_function(engine, self.state)

        if self._update_and_check_state(score):
            self.stop_function(self.state)
        else:
            self.pass_function(self.state)

    def _update_and_check_state(self, score):
        if score <= self.state.best_score:
            self.state.best_score = score
            self.state.counter += 1
            return True
        else:
            return False

What do you think ?

@arisliang
Copy link
Author

Looks great for both current need and flexible. What is stuck_function? Is it just typo of pass_function?

@sdesrozis
Copy link
Contributor

Looks great for both current need and flexible. What is stuck_function? Is it just typo of pass_function?

Yeah, a typo. Fixed. Let's see whether this design works although the naming, concepts and scope need to be carefully considered.

@Ishan-Kumar2
Copy link
Contributor

Hi @sdesrozis and @arisliang, I am interested in taking this up.
From what I understand, we should create a Base class called something like NoImprovementHandler and then use it to create the EarlyStopping because it is a special case of that.

I was wondering, should we also keep the decision function of the Base class fixed as
if self.counter >= self.patience
or are there some use-cases where allowing the user to change this would also help?

@Ishan-Kumar2 Ishan-Kumar2 linked a pull request May 19, 2022 that will close this issue
3 tasks
@vfdev-5 vfdev-5 moved this from To do to In progress in 0.5.1 Jan 5, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
0.5.1
  
In progress
Development

Successfully merging a pull request may close this issue.

4 participants