facebook / prophet

Tool for producing high quality forecasts for time series data that has multiple seasonality with linear or non-linear growth.
https://facebook.github.io/prophet
MIT License
18.26k stars 4.51k forks source link

Suppressing Stan optimizer printing in Python #223

Open StuartGordonReid opened 7 years ago

StuartGordonReid commented 7 years ago

Hi,

I recently started using Prophet in Python, and whilst it is working (quite well), it is printing out messages which appear to be generated by PySTAN and suppressing output from stdout, stderr, and stdin doesn't seem to stop it. Could you add an option to suppress output from PySTAN. I've attached screenshots of the output and the function I am trying to use to suppress the output which is not working:

output from prophet

suppress output

Kind regards, Stuart Reid

bletham commented 7 years ago

I worked on this a bit for #170 and didn't make it very far. Help here is greatly appreciated :-) We may need an upstream change.

randlet commented 7 years ago

Here's a decorator that DOES suppress output:

# from https://stackoverflow.com/questions/11130156/suppress-stdout-stderr-print-from-python-functions
class suppress_stdout_stderr(object):
    '''
    A context manager for doing a "deep suppression" of stdout and stderr in
    Python, i.e. will suppress all print, even if the print originates in a
    compiled C/Fortran sub-function.
       This will not suppress raised exceptions, since exceptions are printed
    to stderr just before a script exits, and after the context manager has
    exited (at least, I think that is why it lets exceptions through).

    '''
    def __init__(self):
        # Open a pair of null files
        self.null_fds = [os.open(os.devnull, os.O_RDWR) for x in range(2)]
        # Save the actual stdout (1) and stderr (2) file descriptors.
        self.save_fds = (os.dup(1), os.dup(2))

    def __enter__(self):
        # Assign the null pointers to stdout and stderr.
        os.dup2(self.null_fds[0], 1)
        os.dup2(self.null_fds[1], 2)

    def __exit__(self, *_):
        # Re-assign the real stdout/stderr back to (1) and (2)
        os.dup2(self.save_fds[0], 1)
        os.dup2(self.save_fds[1], 2)
        # Close the null files
        os.close(self.null_fds[0])
        os.close(self.null_fds[1])

# used like
 with suppress_stdout_stderr():
     p = Propet(*kwargs).fit(training_data)
jimanvlad commented 7 years ago

@randlet Awesome. Has this been pulled?

randlet commented 7 years ago

Not yet, but I would be happy to work up a PR this week if @bletham is ok with using a context manager to (optionally) suppress output.

randlet commented 7 years ago

Actually, I don't know how the copyright/FB contributors agreement would work for this. I didn't write the code and AFAIK Stack Overflow snippets (prior to 2016) are licensed with CC Share Alike license. @bletham do you know if it's possible for you to accept PR's for code that has a CC-SA license?

bletham commented 7 years ago

@randlet thanks for posting this, this is really great and should be immediately useful to people.

I'm not sure about the license thing. I think I'm inclined to just leave this issue open so that people can find and use the decorator, and then wait a bit longer on the Stan team for an upstream fix. They're definitely aware of the issue (https://github.com/stan-dev/rstan/issues/49) and say as of April that it will be suppressed soon, then it'll just be a matter of adding the verbose arg to prophet.

randlet commented 7 years ago

That approach makes good sense to me. Thanks!

randlet commented 7 years ago

Just wanted to post an update to this. We are running Prophet in a long running process doing lots of forecasting and found we were running out of file descriptors pretty quickly causing issues on the server. I finally traced it back to the decorator posted above which leaks 2 file descriptors every time it is used.

I have modified it to the following with success:

class suppress_stdout_stderr(object):
    '''
    A context manager for doing a "deep suppression" of stdout and stderr in
    Python, i.e. will suppress all print, even if the print originates in a
    compiled C/Fortran sub-function.
       This will not suppress raised exceptions, since exceptions are printed
    to stderr just before a script exits, and after the context manager has
    exited (at least, I think that is why it lets exceptions through).

    '''
    def __init__(self):
        # Open a pair of null files
        self.null_fds = [os.open(os.devnull, os.O_RDWR) for x in range(2)]
        # Save the actual stdout (1) and stderr (2) file descriptors.
        self.save_fds = [os.dup(1), os.dup(2)]

    def __enter__(self):
        # Assign the null pointers to stdout and stderr.
        os.dup2(self.null_fds[0], 1)
        os.dup2(self.null_fds[1], 2)

    def __exit__(self, *_):
        # Re-assign the real stdout/stderr back to (1) and (2)
        os.dup2(self.save_fds[0], 1)
        os.dup2(self.save_fds[1], 2)
        # Close the null files
        for fd in self.null_fds + self.save_fds:
            os.close(fd)
eeilon79 commented 6 years ago

Hi. I've got here from issue #219, yet this solution definitely does not work with Prophet.fit logger.INFO messages like "Disabling yearly seasonality..." and "Disabling daily seasonality...". I'm quite at a loss of how to get rid of them, actually. Nothing works.

eeilon79 commented 6 years ago

Nevermind. Turns out that if you specify "daily_seasonality=False" etc. the messages disappear. Although why on earth an unspecified parameter should generate a console output, Facebook knows.

bletham commented 6 years ago

Those messages can be disabled by setting the logging level for fbprophet to something above INFO:

import logging
logging.getLogger('fbprophet').setLevel(logging.WARNING)

The reason for them is to avoid a scenario where a new user fits a time series with default values, seasonality is disabled e.g. due to too short a time series, but the user doesn't know this and is left wondering why the seasonality doesn't show up in the plot like in the documentation. With the info message they will know that it was on purpose and not a bug.

eeilon79 commented 6 years ago

Hello Ben, Thank you for your answer. Sorry about the earlier disgruntled post and about the delay in my response. I've tried logging before, probably with the wrong syntax. However, now it works. Thanks.

smontanaro commented 6 years ago

Any progress on this? I just started to experiment with Prophet in an existing application which spits out HTML to stdout. All this business about log joing probabilities and Hessians is wreaking havoc with that. I finally had to wrap this sort of business around my prophet calls:

stdout = os.dup(1)
stderr = os.dup(2)
null = os.open(os.devnull, os.O_RDWR)
os.dup2(null, 1)
os.dup2(null, 2)
try:
    ... prophet calls here ...
finally:
    os.dup2(stdout, 1)
    os.dup2(stderr, 2)

I will eventually turn that into a decorator. Still would be nice to have an official way to do that suppression, one which didn't require so many system calls... (I'm guessing Stan has no clean way to tell it to shut up?)

I'm using 0.2.1 (package from Conda forge). Maybe a later version has this problem solved?

bletham commented 6 years ago

The upstream issue in Stan is still open, there is nothing in the later version of prophet to fix this. I was hoping for an upstream fix to avoid having to mess around with redirecting stderr, but we may have to just do it for the next version. In the meantime a decorator like that is the way to go.

drcrook1 commented 6 years ago

Is there a link to the docs on how to do this? I'm running thousands of simulations and this is killing me...

eeilon79 commented 6 years ago

Hi drcrook1,

I'm not sure what did the job for me, but it was either logging or suppress_stdout_stderr above. I had a class which had: a. suppress_stdout_stderr as a sub class b. a fit function that called for suppress_stdout_stderr: The relevant parts of the code are:

import logging
from fbprophet import Prophet

logging.getLogger('fbprophet').setLevel(logging.WARNING)

model = Prophet()
with self.suppress_stdout_stderr():
    model.fit(fit_data)

Hopes it helps.

eeilon79 commented 5 years ago

Errr.. There was a question by shaidams64 which I don't see now, but I would answer anyway: suppress_stdout_stderr wasn't a sub class of a fit class. I had a parent class which managed the entire simulation which had suppress_stdout_stderr as a sub_class and also had a fit function that used that class in the way I've quoted above. The model.fit class which was used on the fit function was imported from another library. The fit function and suppress_stdout_stderr class are both children of the same parent class.

eromoe commented 5 years ago

I found that : 1 . suppress_stdout_stderr works if you run it in main process .

  1. Didn't work when I used joblib or multiprocessing , it would still print the log, which seem slow down training .
wuxiyu commented 5 years ago

@eromoe hi, it fixes using this. This will help you silence logger

mattc-eostar commented 5 years ago
import logging
from fbprophet import Prophet

logging.getLogger('fbprophet').setLevel(logging.WARNING)

model = Prophet()
with self.suppress_stdout_stderr():
    model.fit(fit_data)

@eromoe, I have this successfully blocking the output across multiple processes.

ShreyasJothish commented 4 years ago

Using the wrapper code, I was able to suppress INFO logs but is there a way to stop WARNING logs too?

I tried setting the log level to ERROR but still WARNING logs appear.

logging.getLogger('fbprophet').setLevel(logging.ERROR)

sammo commented 4 years ago

@ShreyasJothish this should do it.

# Turn off pystan warnings
warnings.simplefilter("ignore", DeprecationWarning)
warnings.simplefilter("ignore", FutureWarning)

# Turn off fbprophet stdout logger
logging.getLogger('fbprophet').setLevel(logging.ERROR)
RonanFelipe commented 4 years ago

I have tried the solution from @bletham and the one from @sammo but nothing, still having those Iteration messages.

mattc-eostar commented 4 years ago

@RonanFelipe Use the suppression class given above from @randlet https://github.com/facebook/prophet/issues/223#issuecomment-326455744

Then use:

import logging
from fbprophet import Prophet

logging.getLogger('fbprophet').setLevel(logging.WARNING)

model = Prophet()
with self.suppress_stdout_stderr():
    model.fit(fit_data)
RonanFelipe commented 4 years ago

Thanks @mattc-eostar, it worked.

CrashLaker commented 3 years ago

Running the code proposed by @randlet solved it for me thank you so much.

However running this on AWS lambda sometimes gives me this error multiple times: OSError: [Errno 24] Too many open files: '/dev/null'

Can anyone help? python3.7 pystan==2.18 fbprophet==0.5

randlet commented 3 years ago

@CrashLaker did you see my revised code which addresses running out of file descriptors here: https://github.com/facebook/prophet/issues/223#issuecomment-326455744

decloedt commented 3 years ago

Is there a workaround to suppress stan output in R?

sahilee26 commented 2 years ago

@ShreyasJothish this should do it.

# Turn off pystan warnings
warnings.simplefilter("ignore", DeprecationWarning)
warnings.simplefilter("ignore", FutureWarning)

# Turn off fbprophet stdout logger
logging.getLogger('fbprophet').setLevel(logging.ERROR)

thanks it worked

chinmaydas96 commented 2 years ago

It didn't work for me while I am using the cross_validation while model fitting using dask, Does anyone know a workaround for that thing?

I was using this like this.

with self.suppress_stdout_stderr(): df_cv = cross_validation(m, horizon='7 days', period='7 days', initial='500 days', parallel='dask')

mraxilus commented 2 years ago

Any progress on getting a module level option?

Ne-oL commented 2 years ago
import logging
from fbprophet import Prophet

logging.getLogger('fbprophet').setLevel(logging.WARNING)

model = Prophet()
with self.suppress_stdout_stderr():
    model.fit(fit_data)

@eromoe, I have this successfully blocking the output across multiple processes.

just wanted to point out that Facebook has since changed the naming of the module, so the above code isn't working properly anymore, i have taken liberty to modify it to my own use (I'm calling prophet inside a function on multiple cores using multiprocessing module), below is my code:

import warnings
import logging
import os
from prophet import Prophet

def func():
    warnings.simplefilter("ignore", DeprecationWarning)
    warnings.simplefilter("ignore", FutureWarning)
    logging.getLogger('prophet').setLevel(logging.ERROR) #Notice that i had modified the name from 'fbprophet' to just 'prophet'
    .......
    model = Prophet()
    with suppress_stdout_stderr():
                model.fit(fit_data)

i hope it helps anyone who wonders here for the same issue.

maffei2443 commented 1 year ago
import logging
from fbprophet import Prophet

logging.getLogger('fbprophet').setLevel(logging.WARNING)

model = Prophet()
with self.suppress_stdout_stderr():
    model.fit(fit_data)

@eromoe, I have this successfully blocking the output across multiple processes.

just wanted to point out that Facebook has since changed the naming of the module, so the above code isn't working properly anymore, i have taken liberty to modify it to my own use (I'm calling prophet inside a function on multiple cores using multiprocessing module), below is my code:

import warnings
import logging
import os
from prophet import Prophet

def func():
    warnings.simplefilter("ignore", DeprecationWarning)
    warnings.simplefilter("ignore", FutureWarning)
    logging.getLogger('prophet').setLevel(logging.ERROR) #Notice that i had modified the name from 'fbprophet' to just 'prophet'
    .......
    model = Prophet()
    with suppress_stdout_stderr():
                model.fit(fit_data)

i hope it helps anyone who wonders here for the same issue. Didn't work for me, unfortunately. I'm using prophet on IBM Cloud.

seanslma commented 1 year ago

FYI, the previous class suppress_stdout_stderr will not work appropriately in multi-thread tasks

mn047 commented 1 year ago

Hi, I have class suppress_stdout_stderr and worked for me, but I would like to stop it. anyone can help with this

`class suppress_stdout_stderr(object): def init(self):

Open a pair of null files

    self.null_fds = [os.open(os.devnull, os.O_RDWR) for x in range(2)]
    # Save the actual stdout (1) and stderr (2) file descriptors.
    self.save_fds = (os.dup(1), os.dup(2))

def __enter__(self):
    # Assign the null pointers to stdout and stderr.
    os.dup2(self.null_fds[0], 1)
    os.dup2(self.null_fds[1], 2)

def __exit__(self, *_):
    # Re-assign the real stdout/stderr back to (1) and (2)
    os.dup2(self.save_fds[0], 1)
    os.dup2(self.save_fds[1], 2)
    # Close the null files
    os.close(self.null_fds[0])
    os.close(self.null_fds[1])`
samposm commented 1 year ago
class suppress_stdout_stderr(object):
    '''
    A context manager for doing a "deep suppression" of stdout and stderr in
    Python, i.e. will suppress all print, even if the print originates in a
    compiled C/Fortran sub-function.
       This will not suppress raised exceptions, since exceptions are printed
    to stderr just before a script exits, and after the context manager has
    exited (at least, I think that is why it lets exceptions through).

    '''
    def __init__(self):
        # Open a pair of null files
        self.null_fds = [os.open(os.devnull, os.O_RDWR) for x in range(2)]
        # Save the actual stdout (1) and stderr (2) file descriptors.
        self.save_fds = [os.dup(1), os.dup(2)]

    def __enter__(self):
        # Assign the null pointers to stdout and stderr.
        os.dup2(self.null_fds[0], 1)
        os.dup2(self.null_fds[1], 2)

    def __exit__(self, *_):
        # Re-assign the real stdout/stderr back to (1) and (2)
        os.dup2(self.save_fds[0], 1)
        os.dup2(self.save_fds[1], 2)
        # Close the null files
        for fd in self.null_fds + self.save_fds:
            os.close(fd)

I don't think you need to open /dev/null twice. And for this kind of simple context manager use, you don't necessarily need an __init__ separate from __enter__. Here is a modified version:

from types import TracebackType
from typing import Union

class SilentStdoutStderr(object):
    """
    Context manager to temporarily silence stdout and stderr. Use with `with`.
    """
    stdout, stderr = sys.__stdout__.fileno(), sys.__stderr__.fileno()

    def __enter__(self) -> None:
        self.devnull = os.open(os.devnull, os.O_RDWR)
        self.orig_stdout, self.orig_stderr = os.dup(self.stdout), os.dup(self.stderr)
        # point stdout, stderr to /dev/null
        os.dup2(self.devnull, self.stdout)
        os.dup2(self.devnull, self.stderr)

    def __exit__(
        self,
        exc_type: Union[type[BaseException], None],
        exc_val: Union[BaseException, None],
        exc_tb: Union[TracebackType, None],
    ) -> None:
        print(flush=True)
        # restore stdout, stderr back
        os.dup2(self.orig_stdout, self.stdout)
        os.dup2(self.orig_stderr, self.stderr)
        # close all file descriptors
        for file in [self.devnull, self.orig_stdout, self.orig_stderr]:
            os.close(file)

But this is not important, both work anyway.

However, I managed to silence logging with just:

import logging

logging.getLogger("prophet").setLevel(logging.WARNING)
logging.getLogger("cmdstanpy").disabled=True
mn047 commented 1 year ago

I want to revert it back to see log, how can I do that?

piccolomo commented 1 year ago

Thank you @samposm what you quoted worked for me as well:

import logging

logging.getLogger("prophet").setLevel(logging.WARNING)
logging.getLogger("cmdstanpy").disabled=True
SinghJivjot commented 9 months ago
class suppress_stdout_stderr(object):
    '''
    A context manager for doing a "deep suppression" of stdout and stderr in
    Python, i.e. will suppress all print, even if the print originates in a
    compiled C/Fortran sub-function.
       This will not suppress raised exceptions, since exceptions are printed
    to stderr just before a script exits, and after the context manager has
    exited (at least, I think that is why it lets exceptions through).

    '''
    def __init__(self):
        # Open a pair of null files
        self.null_fds = [os.open(os.devnull, os.O_RDWR) for x in range(2)]
        # Save the actual stdout (1) and stderr (2) file descriptors.
        self.save_fds = [os.dup(1), os.dup(2)]

    def __enter__(self):
        # Assign the null pointers to stdout and stderr.
        os.dup2(self.null_fds[0], 1)
        os.dup2(self.null_fds[1], 2)

    def __exit__(self, *_):
        # Re-assign the real stdout/stderr back to (1) and (2)
        os.dup2(self.save_fds[0], 1)
        os.dup2(self.save_fds[1], 2)
        # Close the null files
        for fd in self.null_fds + self.save_fds:
            os.close(fd)

I don't think you need to open /dev/null twice. And for this kind of simple context manager use, you don't necessarily need an __init__ separate from __enter__. Here is a modified version:

from types import TracebackType
from typing import Union

class SilentStdoutStderr(object):
    """
    Context manager to temporarily silence stdout and stderr. Use with `with`.
    """
    stdout, stderr = sys.__stdout__.fileno(), sys.__stderr__.fileno()

    def __enter__(self) -> None:
        self.devnull = os.open(os.devnull, os.O_RDWR)
        self.orig_stdout, self.orig_stderr = os.dup(self.stdout), os.dup(self.stderr)
        # point stdout, stderr to /dev/null
        os.dup2(self.devnull, self.stdout)
        os.dup2(self.devnull, self.stderr)

    def __exit__(
        self,
        exc_type: Union[type[BaseException], None],
        exc_val: Union[BaseException, None],
        exc_tb: Union[TracebackType, None],
    ) -> None:
        print(flush=True)
        # restore stdout, stderr back
        os.dup2(self.orig_stdout, self.stdout)
        os.dup2(self.orig_stderr, self.stderr)
        # close all file descriptors
        for file in [self.devnull, self.orig_stdout, self.orig_stderr]:
            os.close(file)

But this is not important, both work anyway.

However, I managed to silence logging with just:

import logging

logging.getLogger("prophet").setLevel(logging.WARNING)
logging.getLogger("cmdstanpy").disabled=True

Thanks sir, saved a lot of time and headache looking at the cmdstanpy logs