how to control output from fbprophet?
Asked Answered
Y

2

17

I would like to be able to suppress some output coming from fbprophet while fitting a forecasting model. This output ("Initial log joint probability...", "Optimization terminated normally:", "Convergence detected:...", etc.) is apparently coming from Stan's cpp code and I cannot find any obvious way to control it [I am using python interface]. Digging a little bit into the code discovers verbose=False as default in high level stan() routines (in pystan/api.py), but apparently this parameter does not suppress this printout. Is it feasible without code modifications?

Ylangylang answered 7/8, 2017 at 15:50 Comment(0)
M
12

Unfortunately, it's more complex than it should be. I think some of the output is coming from C or Fortran compiled code or something. Here is how you can do it (found here):

import os
import sys

import pandas as pd
from fbprophet import Prophet


# from https://mcmap.net/q/325042/-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])

m = Prophet()
df = pd.read_csv('somefile.csv')

with suppress_stdout_stderr():
    m.fit(minimal_df)

The 'simpler' way (if it would've worked, which it doesn't) would've been something like:

import os
import sys

import pandas as pd
from fbprophet import Prophet

m = Prophet()
df = pd.read_csv('somefile.csv')

orig_out = sys.stdout
sys.stdout = open(os.devnull, 'w')
m.fit(df)
sys.stdout = orig_out
Micromho answered 21/6, 2019 at 1:1 Comment(2)
Is the simpler way outdated? Because it doesn't seem to work for me inside a flask app.Oliveolivegreen
I'm not sure the simpler way worked; don't remember at this point if I had tested it or not. I seem to remember it not working though. I think it was a sort of suggestion of how it could be done usually, but not in the case of prophet.Micromho
S
4

When using the prophet.fit, unfortunately there is no easy ways to suppress logs.
Let's dive into details.

Overview:

  • Most of the time the logs generated by cmdstanpy package, a prophet dependency, not prophet itself.
  • prophet package determines logging level by the logging.getLogger.setLevel function. Interestingly, it appears there are no parameters that allow external management or modification of this setting for cmdstanpy.
  • If you manually set the logging level before running prophet fit method, it has no effect, until the first prophet.fit() run, while consecutive re-runs surprisingly will have effect. This could be due to the order of operations in how the logging levels are set and when the Prophet module first imports and configures its logger.
  • Now it might be evident why setting logging.getLogger("cmdstanpy").disabled=True will suppresses the logs while neither logging.getLogger("cmdstanpy").setLevel(logging.WARNING) nor logging.getLogger("prophet").setLevel(logging.WARNING) can, during the initial run.

Solutions that works as expected from the first run:

Suppress all cmdstanpy logs.

import logging
from prophet import Prophet

logging.getLogger("cmdstanpy").disabled = True #  turn 'cmdstanpy' logs off
model = Prophet()
model.fit(df_train)
logging.getLogger("cmdstanpy").disabled = False #  revert original setting

Note: I don't recommend turning logs off in general, as you won't see errors during the fit if any.

Suppress level-specific cmdstanpy logs.

Achieved by temporarily involving a custom, immutable logging class.

import logging
from prophet import Prophet

# 1. Immutable Logger Class Creation.
class ImmutableLogger(logging.Logger):
    def __init__(self, name, level=logging.NOTSET):
        super().__init__(name, level)
        self._level_set = False

    def setLevel(self, level):
        if not self._level_set:
            super().setLevel(level)
            self._level_set = True

# 2. Standard Logger Replacement with Immutable Logger.
logging.setLoggerClass(ImmutableLogger)

# 3. Logging Level Determination.
logging.getLogger("cmdstanpy").setLevel(logging.WARNING)

# 4. Execution of Prophet Code with level-specific log suppression.
model = Prophet()
model.fit(df_train)

# 5. Standard Logger Reversion and Cleanup.
logging.setLoggerClass(logging.Logger)
del logging.Logger.manager.loggerDict["cmdstanpy"]

Steps explanation:

  1. A custom logger class ImmutableLogger gets created. This class inherits from Python's standard logging.Logger class. The setLevel method in this class gets overridden to avoid any alterations to the logger's level once it's been set.

  2. The standard logger class in Python is substituted with the custom ImmutableLogger class using logging.setLoggerClass.

  3. The logging level of cmdstanpy gets set to WARNING. Because of the ImmutableLogger class, this level cannot be modified from now on.

  4. A Prophet model gets generated and fitted with the training data. During this procedure, only cmdstanpy logs with a level of WARNING or higher get displayed.

  5. The logger class gets switched back to Python's standard logger class. The cmdstanpy logger instance gets removed from the logger dictionary, effectively "resetting" the logger. This enables its re-initialization with default behaviors in any subsequent code.

Note:

  • If it's sufficient for you to suppress level-specific logs only from the second fit run and not from the initial one, you may simply use just "step 3" alone: logging.getLogger("cmdstanpy").setLevel(logging.WARNING).

Suppress level-specific prophet itself logs.

import logging
from prophet import Prophet

logging.getLogger("prophet").setLevel(logging.WARNING)
model = Prophet()
model.fit(df_train)
Snip answered 12/5, 2023 at 7:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.