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

Simplify BaseLogger attach APIs #1006

Merged
merged 17 commits into from
May 5, 2020
Merged

Simplify BaseLogger attach APIs #1006

merged 17 commits into from
May 5, 2020

Conversation

erip
Copy link
Contributor

@erip erip commented May 3, 2020

Fixes #980

Description: Adds new interface, docstring, and implementations for simplified base_logger API.

Check list:

  • New tests are added (if a new feature is added)
  • New doc strings: description and/or example code are in RST format
  • Documentation is updated (if required)

@erip erip changed the title Feature/simplify logger Simplify BaseLogger attach APIs May 3, 2020
@erip
Copy link
Contributor Author

erip commented May 3, 2020

I think the failure is unrelated. I still need to write some tests, though.

@sdesrozis
Copy link
Contributor

sdesrozis commented May 4, 2020

@erip Thanks :)

The file ignite/contrib/engines/common.py should be refactored to avoid import all loggers module (see setup_any_logging function).

What do you think to add methods for BaseWeightsScalarHandler and BaseWeightsHistHandler too ?

@erip
Copy link
Contributor Author

erip commented May 4, 2020

Thanks for the comment @sdesrozis. I was thinking about the setup_any_logger code while doing this and I this it's possible to clean it up. I'm wondering if we should deprecate it or just completely remove it. It's technically part of the public API, but doesn't really serve any use outside of a private method... WDYT?

@sdesrozis
Copy link
Contributor

sdesrozis commented May 4, 2020

Thanks for the comment @sdesrozis. I was thinking about the setup_any_logger code while doing this and I this it's possible to clean it up. I'm wondering if we should deprecate it or just completely remove it. It's technically part of the public API, but doesn't really serve any use outside of a private method... WDYT?

I suppose setup_any_logger is private. If it’s useless, we could remove it. We should just provide the setup of concrete loggers on public API. I don’t think it’s worth depreciating.

@vfdev-5 you agree ?

@vfdev-5
Copy link
Collaborator

vfdev-5 commented May 4, 2020

Technically setup_any_logger is in v0.3.0 as a public API method (but, yes I agree that it is more private than public and not documented). So, technically we should not just remove it without any suggestions on how to use new API. At least we can raise an exception with appropriate message.

@vfdev-5
Copy link
Collaborator

vfdev-5 commented May 4, 2020

ERROR OF SDESROZIS! USED MY PHONE AND EDIT THE POST :( Sorry :( The code is correct - sdesrozis

class BaseOutputHandler:

    def __call__(self, x):
        print("BaseOutputHandler", x)


class Engine:
    def __init__(self):
        self.handlers = []

    def __call__(self, x):
        for h in self.handlers:
            h(x)


class BaseLogger:
    output_handler_cls = BaseOutputHandler

    def attach_output(self, e):
        e.handlers.append(self.output_handler_cls())


class TbOutputHandler:
    def __call__(self, x):
        print("TbOutputHandler", x ** 2)


class TbLogger(BaseLogger):
    output_handler_cls = TbOutputHandler


engine = Engine()

BaseLogger().attach_output(engine)
TbLogger().attach_output(engine)

engine(2)

> python test.py
> BaseOutputHandler 2
> TbOutputHandler 4

@erip
Copy link
Contributor Author

erip commented May 4, 2020

I was just thinking that approach myself. 😆 I think it's reasonable as long as we mark the Base...Handlers as ABCMeta to force them to be abstract.

@vfdev-5
Copy link
Collaborator

vfdev-5 commented May 4, 2020

@erip please go ahead with that approach :)

@sdesrozis
Copy link
Contributor

Here I would prefer use interface to provide contract to implement for developers of logger. It's more or less the same but more constrained.

What do you think about that ?

from abc import ABCMeta, abstractmethod


class BaseOutputHandler:

    def __call__(self, x):
        print("BaseOutputHandler", x)


class Engine:
    def __init__(self):
        self.handlers = []

    def __call__(self, x):
        for h in self.handlers:
            h(x)


class BaseLogger(metaclass=ABCMeta):

    # should not provide an implementation
    @abstractmethod
    def output_handler(self):
        pass

    def attach_output(self, e):
        e.handlers.append(self.output_handler())


class TbOutputHandler:
    def __call__(self, x):
        print("TbOutputHandler", x ** 2)


class TbLogger(BaseLogger):

    def output_handler(self):
        return TbOutputHandler()


engine = Engine()

# It don't work, BaseLogger is for inheritance
# BaseLogger().attach_output(engine)
TbLogger().attach_output(engine)

engine(2)

> python test.py
> TbOutputHandler 4

@erip
Copy link
Contributor Author

erip commented May 4, 2020

@sdesrozis agreed. Have a look at this - hopefully this fits your model.

@sdesrozis
Copy link
Contributor

I should have defined output_handler and output_opt_handler as abstract function

In BaseLogger

@abstract
def output_handler(self) -> BaseOutputHandler
    pass

And in concrete logger

class MyOutputHandler(BaseOutputHandler):
    ....

class MyLogger(BaseLogger):
    def output_handler(self) -> BaseOutputHandler
        return MyOutputHandler

I think my typehints is wrong : the good type is “class of BaseOutputHandler “

@vfdev-5
Copy link
Collaborator

vfdev-5 commented May 4, 2020

@sdesrozis or even more proper solution would be to dispatch those creators

class BaseLogger:
    @abstractmethod
    def create_output_handler(self, *args, **kwargs) -> BaseOutputHandler
        pass

    def attach_output_handler(self, engine: Engine, event_name: str, *args: Any, **kwargs: Mapping):
        engine.add_event_handler(event_name, self.create_output_handler(*args, **kwargs))

class MyOutputHandler(BaseOutputHandler):
    ....

class MyLogger(BaseLogger):
    def create_output_handler(self, ) -> MyOutputHandler
        return MyOutputHandler(*args, **kwargs)

But a solution with class attributes is shorter :)

EDIT: I think this is what was suggested by @sisp here : #901 (comment)

@sdesrozis
Copy link
Contributor

That looks like the perfect pattern here!

@sdesrozis
Copy link
Contributor

Abstract method is a guideline for developer. I prefer that but @erip chooses at the end 😊 I talk and I do nothing 😅 (except edit messages 😱)

@vfdev-5
Copy link
Collaborator

vfdev-5 commented May 5, 2020

@erip I added the code with implementation of _create_output_handler and _create_opt_param_handler, added docstrings... Attaching the way it is done, it should fix failed tests.
Remains helper methods setup_*_logging for other loggers.

@vfdev-5 vfdev-5 requested a review from sdesrozis May 5, 2020 13:29
@erip
Copy link
Contributor Author

erip commented May 5, 2020

I think I've got the setup... helpers wrapped up. Over to @sdesrozis for review. 🥳

Copy link
Contributor

@sdesrozis sdesrozis left a comment

Choose a reason for hiding this comment

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

Nice work 👍🏻

@sdesrozis sdesrozis merged commit 6624337 into pytorch:master May 5, 2020
Returns:
NeptuneLogger
"""
logger = NeptuneLogger()
Copy link
Collaborator

Choose a reason for hiding this comment

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

I was thinking about those methods and loggers that need **kwargs to initialize.
Either we need to pass those things or directly work on the instance of such logger...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was thinking about this, too. I think the tricky thing is that the setup_X_logger by convention passes in default kwargs. Adding *logger_constructor_args and **logger_constructor_kwargs isn't easy because of that. I think there are some clever things we could possibly do... will try to draft a gist quickly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See here

Copy link
Contributor

Choose a reason for hiding this comment

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

Like here ?

logger = VisdomLogger(**kwargs)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that only works if it's a **kwargs-only constructor.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think that only works if it's a **kwargs-only constructor.

Can’t test, I just have my phone 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, I was wrong. As long as we can guarantee the user specifies all constructor args they can pass it in as a dict at the end like in VisdomLogger even for positional args.

Copy link
Contributor

@sdesrozis sdesrozis May 5, 2020

Choose a reason for hiding this comment

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

So let’s define defaults and that will be ok 👌🏻

Sorry I was too prompt to merge.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Have #1015 out :-)

@vfdev-5
Copy link
Collaborator

vfdev-5 commented May 5, 2020

Seems like I was late before it waas merged :(

@vfdev-5
Copy link
Collaborator

vfdev-5 commented May 5, 2020

@erip or @sdesrozis could you add that in another PR please

@erip erip deleted the feature/simplify-logger branch May 5, 2020 21:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Simplified base logger's API
3 participants