mirror of
https://github.com/saymrwulf/prophet.git
synced 2026-06-30 03:37:53 +00:00
Simplify setup.py, make CmdStan the default backend (#2088)
This commit is contained in:
parent
f16c782661
commit
9968f8be41
12 changed files with 88 additions and 386 deletions
20
.github/workflows/build-and-test.yml
vendored
20
.github/workflows/build-and-test.yml
vendored
|
|
@ -6,9 +6,6 @@ on:
|
|||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
env:
|
||||
CMDSTAN_VERSION: "2.26.1"
|
||||
|
||||
jobs:
|
||||
build-and-test-python:
|
||||
|
||||
|
|
@ -38,26 +35,11 @@ jobs:
|
|||
key: ${{ runner.os }}-pip-${{ hashFiles('**/python/requirements.txt') }}-v1
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
- name: "Restore cmdstan cache"
|
||||
id: cache-cmdstan
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.DEFAULT_HOME }}/.cmdstan
|
||||
key: ${{ runner.os }}-cmdstan-${{ env.CMDSTAN_VERSION }}-v1
|
||||
- name: "Download cmdstan"
|
||||
if: steps.cache-cmdstan.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
wget https://github.com/stan-dev/cmdstan/releases/download/v${{ env.CMDSTAN_VERSION }}/cmdstan-${{ env.CMDSTAN_VERSION }}.tar.gz -O /tmp/cmdstan.tar.gz &> /dev/null
|
||||
mkdir $HOME/.cmdstan
|
||||
tar -xf /tmp/cmdstan.tar.gz -C $HOME/.cmdstan &> /dev/null
|
||||
- name: Install and test
|
||||
run: |
|
||||
pip install -U -r python/requirements.txt dask[dataframe] distributed
|
||||
cd python
|
||||
STAN_BACKEND=PYSTAN python setup.py develop test
|
||||
python setup.py clean
|
||||
rm -rf prophet/stan_model
|
||||
STAN_BACKEND=CMDSTANPY python setup.py develop test
|
||||
python setup.py develop test
|
||||
|
||||
build-and-test-r:
|
||||
|
||||
|
|
|
|||
39
.github/workflows/wheel.yml
vendored
39
.github/workflows/wheel.yml
vendored
|
|
@ -5,9 +5,9 @@ on:
|
|||
types: [ created ]
|
||||
workflow_dispatch: {}
|
||||
|
||||
|
||||
env:
|
||||
STAN_BACKEND: "PYSTAN,CMDSTANPY"
|
||||
CMDSTAN_VERSION: "2.26.1"
|
||||
STAN_BACKEND: "CMDSTANPY"
|
||||
|
||||
jobs:
|
||||
make-wheel-windows:
|
||||
|
|
@ -55,13 +55,6 @@ jobs:
|
|||
python -m pip install --upgrade pip
|
||||
python -m pip install cibuildwheel build delvewheel
|
||||
|
||||
- name: "Restore cmdstan cache"
|
||||
id: cache-cmdstan
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.DEFAULT_HOME }}/.cmdstan
|
||||
key: ${{ runner.os }}-cmdstan-${{ env.CMDSTAN_VERSION }}-v1
|
||||
|
||||
- name: "Restore RTools40"
|
||||
id: cache-rtools
|
||||
uses: actions/cache@v2
|
||||
|
|
@ -69,21 +62,12 @@ jobs:
|
|||
path: C:/rtools40
|
||||
key: ${{ runner.os }}-${{ env.OS_VERSION }}-rtools-v1
|
||||
|
||||
- name: "Download cmdstan"
|
||||
if: steps.cache-cmdstan.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
$ProgressPreference = "SilentlyContinue"
|
||||
Invoke-WebRequest https://github.com/stan-dev/cmdstan/releases/download/v${{ env.CMDSTAN_VERSION }}/cmdstan-${{ env.CMDSTAN_VERSION }}.tar.gz -OutFile "D:/a/_temp/cmdstan.tar.gz"
|
||||
New-Item -Path "$HOME" -Name ".cmdstan" -ItemType "directory"
|
||||
tar -xf "D:/a/_temp/cmdstan.tar.gz" -C "$HOME/.cmdstan"
|
||||
|
||||
- name: "Build wheel"
|
||||
run: |
|
||||
cd python && python -m cibuildwheel --output-dir wheelhouse
|
||||
env:
|
||||
CIBW_ENVIRONMENT: >
|
||||
STAN_BACKEND="${{ env.STAN_BACKEND }}"
|
||||
CMDSTAN_VERSION=${{ env.CMDSTAN_VERSION }}
|
||||
PIP_CACHE_DIR="${{ env.PIP_DEFAULT_CACHE }}"
|
||||
CIBW_BUILD: cp36-* cp37-* cp38-*
|
||||
CIBW_ARCHS: native
|
||||
|
|
@ -112,17 +96,16 @@ jobs:
|
|||
- name: "Get OS version (Linux)"
|
||||
if: startsWith(runner.os, 'Linux')
|
||||
run: |
|
||||
echo "OS_VERSION=`lsb_release -sr`" >> $GITHUB_ENV
|
||||
echo "PIP_DEFAULT_CACHE=$HOME/.cache/pip" >> $GITHUB_ENV
|
||||
echo "DEFAULT_HOME=$HOME" >> $GITHUB_ENV
|
||||
|
||||
- name: "Get OS version (macOS)"
|
||||
if: startsWith(runner.os, 'macOS')
|
||||
run: |
|
||||
echo "OS_VERSION=`sw_vers -productVersion`" >> $GITHUB_ENV
|
||||
echo "PIP_DEFAULT_CACHE=$HOME/Library/Caches/pip" >> $GITHUB_ENV
|
||||
echo "DEFAULT_HOME=$HOME" >> $GITHUB_ENV
|
||||
|
||||
|
||||
- name: "Checkout repo"
|
||||
uses: actions/checkout@v2
|
||||
|
||||
|
|
@ -146,31 +129,15 @@ jobs:
|
|||
python -m pip install --upgrade pip
|
||||
python -m pip install cibuildwheel build
|
||||
|
||||
- name: "Restore cmdstan cache"
|
||||
id: cache-cmdstan
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.DEFAULT_HOME }}/.cmdstan
|
||||
key: ${{ runner.os }}-cmdstan-${{ env.CMDSTAN_VERSION }}-v1
|
||||
|
||||
- name: "Download cmdstan"
|
||||
if: steps.cache-cmdstan.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
wget https://github.com/stan-dev/cmdstan/releases/download/v${{ env.CMDSTAN_VERSION }}/cmdstan-${{ env.CMDSTAN_VERSION }}.tar.gz -O /tmp/cmdstan.tar.gz &> /dev/null
|
||||
mkdir $HOME/.cmdstan
|
||||
tar -xf /tmp/cmdstan.tar.gz -C $HOME/.cmdstan &> /dev/null
|
||||
|
||||
- name: "Build wheel"
|
||||
run: |
|
||||
cd python && python -m cibuildwheel --output-dir wheelhouse
|
||||
env:
|
||||
CIBW_ENVIRONMENT: >
|
||||
STAN_BACKEND="${{ env.STAN_BACKEND }}"
|
||||
CMDSTAN_VERSION=${{ env.CMDSTAN_VERSION }}
|
||||
# Linux builds run in a Docker container, need to point the cache to the host machine.
|
||||
CIBW_ENVIRONMENT_LINUX: >
|
||||
STAN_BACKEND="${{ env.STAN_BACKEND }}"
|
||||
CMDSTAN_VERSION=${{ env.CMDSTAN_VERSION }}
|
||||
HOME="/host/${{ env.DEFAULT_HOME }}"
|
||||
PIP_CACHE_DIR="/host/${{ env.PIP_DEFAULT_CACHE }}"
|
||||
CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014
|
||||
|
|
|
|||
32
README.md
32
README.md
|
|
@ -56,7 +56,7 @@ On Windows, R requires a compiler so you'll need to [follow the instructions](ht
|
|||
|
||||
If you have custom Stan compiler settings, install from source rather than the CRAN binary.
|
||||
|
||||
## Installation in Python
|
||||
## Installation in Python - PyPI release
|
||||
|
||||
Prophet is on PyPI, so you can use `pip` to install it. From v0.6 onwards, Python 2 is no longer supported. As of v1.0, the package name on PyPI is "prophet"; prior to v1.0 it was "fbprophet".
|
||||
|
||||
|
|
@ -64,12 +64,9 @@ Prophet is on PyPI, so you can use `pip` to install it. From v0.6 onwards, Pytho
|
|||
# Install pystan with pip before using pip to install prophet
|
||||
# pystan>=3.0 is currently not supported
|
||||
pip install pystan==2.19.1.1
|
||||
|
||||
pip install prophet
|
||||
```
|
||||
|
||||
The default dependency that Prophet has is `pystan`. PyStan has its own [installation instructions](http://pystan.readthedocs.io/en/latest/installation_beginner.html). Install pystan with pip before using pip to install prophet.
|
||||
|
||||
#### Experimental backend - cmdstanpy
|
||||
|
||||
You can also choose a (more experimental) alternative stan backend called `cmdstanpy`. It requires the [CmdStan](https://mc-stan.org/users/interfaces/cmdstan) command line interface and you will have to specify the environment variable `STAN_BACKEND` pointing to it, for example:
|
||||
|
|
@ -90,20 +87,41 @@ $ CMDSTAN=/tmp/cmdstan-2.22.1 STAN_BACKEND=PYSTAN,CMDSTANPY pip install prophet
|
|||
|
||||
After installation, you can [get started!](https://facebook.github.io/prophet/docs/quick_start.html#python-api)
|
||||
|
||||
If you upgrade the version of PyStan installed on your system, you may need to reinstall prophet ([see here](https://github.com/facebook/prophet/issues/324)).
|
||||
If you upgraded the version of PyStan installed on your system, you may need to reinstall prophet ([see here](https://github.com/facebook/prophet/issues/324)).
|
||||
|
||||
### Anaconda
|
||||
|
||||
Use `conda install gcc` to set up gcc. The easiest way to install Prophet is through conda-forge: `conda install -c conda-forge prophet`.
|
||||
The easiest way to install Prophet is through conda-forge: `conda install -c conda-forge prophet`.
|
||||
|
||||
### Windows
|
||||
|
||||
On Windows, PyStan requires a compiler so you'll need to [follow the instructions](https://pystan2.readthedocs.io/en/latest/windows.html). The easiest way to install Prophet in Windows is in Anaconda.
|
||||
The easiest way to install Prophet in Windows is in Anaconda.
|
||||
|
||||
### Linux
|
||||
|
||||
Make sure compilers (gcc, g++, build-essential) and Python development tools (python-dev, python3-dev) are installed. In Red Hat systems, install the packages gcc64 and gcc64-c++. If you are using a VM, be aware that you will need at least 4GB of memory to install prophet, and at least 2GB of memory to use prophet.
|
||||
|
||||
## Installation in Python - Development version
|
||||
|
||||
Since Pystan2 is no longer being maintained, the python package will move to depend solely on `cmdstanpy` (benefits described [here](https://github.com/facebook/prophet/issues/2041)). This has been updated in the development version of the package (1.1), but this version hasn't yet been released to PyPI. If you would like to use `cmdstanpy` only for your workflow, you can clone this repo and build from source manually:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/facebook/prophet.git
|
||||
cd prophet/python
|
||||
python -m install -r requirements.txt
|
||||
python setup.py develop
|
||||
```
|
||||
|
||||
By default, Prophet will use a fixed version of `cmdstan` (downloading and installing it if necessary) to compile the model executables. If this is undesired and you would like to use your own existing `cmdstan` installation, you can set the environment variable `PROPHET_REPACKAGE_CMDSTAN` to `False`:
|
||||
|
||||
```bash
|
||||
export PROPHET_REPACKAGE_CMDSTAN=False; python setup.py develop
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
Using `cmdstanpy` with Windows requires a Unix-compatible C compiler such as mingw-gcc. If cmdstanpy is installed first, one can be installed via the `cmdstanpy.install_cxx_toolchain` command.
|
||||
|
||||
## Changelog
|
||||
|
||||
### Version 1.0 (2021.03.28)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
include stan/unix/*.stan
|
||||
include stan/win/*.stan
|
||||
include stan/*.stan
|
||||
include LICENSE
|
||||
include requirements.txt
|
||||
|
||||
|
|
|
|||
|
|
@ -18,18 +18,17 @@ Full documentation and examples available at the homepage: https://facebook.gith
|
|||
- Rob Hyndman's [forecast package](http://robjhyndman.com/software/forecast/)
|
||||
- [Statsmodels](http://statsmodels.sourceforge.net/)
|
||||
|
||||
## Installation
|
||||
## Installation - PyPI release
|
||||
|
||||
```shell
|
||||
pip install prophet
|
||||
```
|
||||
Note: Installation requires PyStan, which has its [own installation instructions](http://pystan.readthedocs.io/en/latest/installation_beginner.html).
|
||||
On Windows, PyStan requires a compiler so you'll need to [follow the instructions](http://pystan.readthedocs.io/en/latest/windows.html).
|
||||
The key step is installing a recent [C++ compiler](https://visualstudio.microsoft.com/visual-cpp-build-tools/)
|
||||
See [Installation in Python - PyPI release](https://github.com/facebook/prophet#installation-in-python---pypi-release)
|
||||
|
||||
## Installation using Docker and docker-compose (via Makefile)
|
||||
## Installation - Development version
|
||||
|
||||
Simply type `make build` and if everything is fine you should be able to `make shell` or alternative jump directly to `make py-shell`.
|
||||
See [Installation in Python - Development version](https://github.com/facebook/prophet#installation-in-python---development-version)
|
||||
|
||||
### Installation using Docker and docker-compose (via Makefile)
|
||||
|
||||
Simply type `make build` and if everything is fine you should be able to `make shell` or alternative jump directly to `make py-shell`.
|
||||
|
||||
To run the tests, inside the container `cd python/prophet` and then `python -m unittest`
|
||||
|
||||
|
|
@ -41,4 +40,4 @@ To run the tests, inside the container `cd python/prophet` and then `python -m u
|
|||
>>> m.fit(df) # df is a pandas.DataFrame with 'y' and 'ds' columns
|
||||
>>> future = m.make_future_dataframe(periods=365)
|
||||
>>> m.predict(future)
|
||||
```
|
||||
```
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from abc import abstractmethod, ABC
|
||||
from tempfile import mkdtemp
|
||||
from typing import Tuple
|
||||
from collections import OrderedDict
|
||||
from enum import Enum
|
||||
|
|
@ -61,30 +62,21 @@ class IStanBackend(ABC):
|
|||
class CmdStanPyBackend(IStanBackend):
|
||||
CMDSTAN_VERSION = "2.26.1"
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
import cmdstanpy
|
||||
cmdstanpy.set_cmdstan_path(
|
||||
pkg_resources.resource_filename("prophet", f"stan_model/cmdstan-{self.CMDSTAN_VERSION}")
|
||||
# this must be set before super.__init__() for load_model to work on Windows
|
||||
local_cmdstan = pkg_resources.resource_filename(
|
||||
"prophet", f"stan_model/cmdstan-{self.CMDSTAN_VERSION}"
|
||||
)
|
||||
if Path(local_cmdstan).exists():
|
||||
cmdstanpy.set_cmdstan_path(local_cmdstan)
|
||||
super().__init__()
|
||||
|
||||
@staticmethod
|
||||
def get_type():
|
||||
return StanBackendEnum.CMDSTANPY.name
|
||||
|
||||
def _add_tbb_to_path(self):
|
||||
"""Add the TBB library to $PATH on Windows only. Required for loading model binaries."""
|
||||
if PLATFORM == "win":
|
||||
tbb_path = pkg_resources.resource_filename(
|
||||
"prophet",
|
||||
f"stan_model/cmdstan-{self.CMDSTAN_VERSION}/stan/lib/stan_math/lib/tbb"
|
||||
)
|
||||
os.environ["PATH"] = ";".join(
|
||||
list(OrderedDict.fromkeys([tbb_path] + os.environ.get("PATH", "").split(";")))
|
||||
)
|
||||
|
||||
def load_model(self):
|
||||
import cmdstanpy
|
||||
self._add_tbb_to_path()
|
||||
model_file = pkg_resources.resource_filename(
|
||||
'prophet',
|
||||
'stan_model/prophet_model.bin',
|
||||
|
|
@ -102,6 +94,7 @@ class CmdStanPyBackend(IStanBackend):
|
|||
inits=stan_init,
|
||||
algorithm='Newton' if stan_data['T'] < 100 else 'LBFGS',
|
||||
iter=int(1e4),
|
||||
output_dir = mkdtemp(),
|
||||
)
|
||||
args.update(kwargs)
|
||||
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ class TestDiagnostics(TestCase):
|
|||
period='10 days',
|
||||
cutoffs=[pd.Timestamp('2012-07-31'), pd.Timestamp('2012-08-31')])
|
||||
self.assertEqual(len(df_cv1['cutoff'].unique()), 2)
|
||||
|
||||
|
||||
def test_cross_validation_uncertainty_disabled(self):
|
||||
df = self.__df.copy()
|
||||
for uncertainty in [0, False]:
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
requires = [
|
||||
"setuptools>=42",
|
||||
"wheel",
|
||||
"pystan~=2.19.1.1",
|
||||
"cmdstanpy==0.9.77",
|
||||
"cmdstanpy>=1.0.0",
|
||||
]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
Cython>=0.22
|
||||
cmdstanpy==0.9.77
|
||||
cmdstanpy>=1.0.0
|
||||
pystan~=2.19.1.1
|
||||
numpy>=1.15.4
|
||||
pandas>=1.0.4
|
||||
|
|
|
|||
156
python/setup.py
156
python/setup.py
|
|
@ -4,11 +4,8 @@
|
|||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
import os
|
||||
import pickle
|
||||
import platform
|
||||
import subprocess
|
||||
import sys
|
||||
from collections import OrderedDict
|
||||
import platform
|
||||
from pathlib import Path
|
||||
from shutil import copy, copytree, rmtree
|
||||
from typing import List
|
||||
|
|
@ -20,18 +17,10 @@ from setuptools.command.build_py import build_py
|
|||
from setuptools.command.develop import develop
|
||||
from setuptools.command.test import test as test_command
|
||||
|
||||
PLATFORM = "unix"
|
||||
if platform.platform().startswith("Win"):
|
||||
PLATFORM = "win"
|
||||
|
||||
MODEL_DIR = os.path.join("stan", PLATFORM)
|
||||
MODEL_DIR = "stan"
|
||||
MODEL_TARGET_DIR = os.path.join("prophet", "stan_model")
|
||||
|
||||
# TODO: Remove when upgrading to cmdstanpy 1.0, use cmdstanpy internals instead
|
||||
# cmdstan utils
|
||||
MAKE = os.getenv("MAKE", "make" if PLATFORM != "win" else "mingw32-make")
|
||||
EXTENSION = ".exe" if PLATFORM == "win" else ""
|
||||
|
||||
CMDSTAN_VERSION = "2.26.1"
|
||||
BINARIES_DIR = "bin"
|
||||
BINARIES = ["diagnose", "print", "stanc", "stansummary"]
|
||||
|
|
@ -39,72 +28,6 @@ TBB_PARENT = "stan/lib/stan_math/lib"
|
|||
TBB_DIRS = ["tbb", "tbb_2019_U8"]
|
||||
|
||||
|
||||
# TODO: Remove when upgrading to cmdstanpy 1.0, use cmdstanpy internals instead
|
||||
def clean_all_cmdstan(verbose: bool = False) -> None:
|
||||
"""Run `make clean-all` in the current directory (must be a cmdstan library).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
verbose: when ``True``, print build msgs to stdout.
|
||||
"""
|
||||
cmd = [MAKE, "clean-all"]
|
||||
proc = subprocess.Popen(
|
||||
cmd,
|
||||
cwd=None,
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
env=os.environ,
|
||||
)
|
||||
while proc.poll() is None:
|
||||
if proc.stdout:
|
||||
output = proc.stdout.readline().decode("utf-8").strip()
|
||||
if verbose and output:
|
||||
print(output, flush=True)
|
||||
_, stderr = proc.communicate()
|
||||
if proc.returncode:
|
||||
msgs = ['Command "make clean-all" failed']
|
||||
if stderr:
|
||||
msgs.append(stderr.decode("utf-8").strip())
|
||||
raise RuntimeError("\n".join(msgs))
|
||||
|
||||
|
||||
# TODO: Remove when upgrading to cmdstanpy 1.0, use cmdstanpy internals instead
|
||||
def build_cmdstan(verbose: bool = False) -> None:
|
||||
"""Run `make build` in the current directory (must be a cmdstan library).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
verbose: when ``True``, print build msgs to stdout.
|
||||
"""
|
||||
cmd = [MAKE, "build"]
|
||||
proc = subprocess.Popen(
|
||||
cmd,
|
||||
cwd=None,
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
env=os.environ,
|
||||
)
|
||||
while proc.poll() is None:
|
||||
if proc.stdout:
|
||||
output = proc.stdout.readline().decode("utf-8").strip()
|
||||
if verbose and output:
|
||||
print(output, flush=True)
|
||||
_, stderr = proc.communicate()
|
||||
if proc.returncode:
|
||||
msgs = ['Command "make build" failed']
|
||||
if stderr:
|
||||
msgs.append(stderr.decode("utf-8").strip())
|
||||
raise RuntimeError("\n".join(msgs))
|
||||
# Add tbb to the $PATH on Windows
|
||||
if PLATFORM == "win":
|
||||
libtbb = os.path.join(os.getcwd(), "stan", "lib", "stan_math", "lib", "tbb")
|
||||
os.environ["PATH"] = ";".join(
|
||||
list(OrderedDict.fromkeys([libtbb] + os.environ.get("PATH", "").split(";")))
|
||||
)
|
||||
|
||||
|
||||
def prune_cmdstan(cmdstan_dir: str) -> None:
|
||||
"""
|
||||
Keep only the cmdstan executables and tbb files (minimum required to run a cmdstanpy commands on a pre-compiled model).
|
||||
|
|
@ -116,6 +39,7 @@ def prune_cmdstan(cmdstan_dir: str) -> None:
|
|||
rmtree(temp_dir)
|
||||
temp_dir.mkdir()
|
||||
|
||||
print("Copying ", original_dir, " to ", temp_dir, " for pruning")
|
||||
copytree(original_dir / BINARIES_DIR, temp_dir / BINARIES_DIR)
|
||||
for f in (temp_dir / BINARIES_DIR).iterdir():
|
||||
if f.is_dir():
|
||||
|
|
@ -128,51 +52,42 @@ def prune_cmdstan(cmdstan_dir: str) -> None:
|
|||
rmtree(original_dir)
|
||||
temp_dir.rename(original_dir)
|
||||
|
||||
|
||||
def get_cmdstan_cache() -> str:
|
||||
"""Default directory for an existing cmdstan library. Prevents unnecessary re-downloads of cmdstan."""
|
||||
return Path.home().resolve() / ".cmdstan" / f"cmdstan-{CMDSTAN_VERSION}"
|
||||
def repackage_cmdstan():
|
||||
return os.environ.get("PROPHET_REPACKAGE_CMDSTAN", "").lower() not in ["false", "0"]
|
||||
|
||||
|
||||
def download_cmdstan(cache_dir: Path) -> None:
|
||||
"""Ensure the cmdstan library exists in the cache directory."""
|
||||
import cmdstanpy
|
||||
from cmdstanpy.install_cmdstan import retrieve_version
|
||||
|
||||
if cache_dir.is_dir():
|
||||
print(f"Found existing cmdstan library at {cache_dir}")
|
||||
else:
|
||||
print(f"Downloading cmdstan to {cache_dir}")
|
||||
cache_dir.parent.mkdir(parents=True, exist_ok=True)
|
||||
with cmdstanpy.utils.pushd(cache_dir.parent):
|
||||
retrieve_version(version=CMDSTAN_VERSION, progress=False)
|
||||
|
||||
|
||||
def install_cmdstan_toolchain():
|
||||
def maybe_install_cmdstan_toolchain():
|
||||
"""Install C++ compilers required to build stan models on Windows machines."""
|
||||
import cmdstanpy
|
||||
from cmdstanpy.install_cxx_toolchain import main as _install_cxx_toolchain
|
||||
_install_cxx_toolchain()
|
||||
|
||||
try:
|
||||
cmdstanpy.utils.cxx_toolchain_path()
|
||||
except Exception:
|
||||
_install_cxx_toolchain({"version": None, "dir": None, "verbose": True})
|
||||
cmdstanpy.utils.cxx_toolchain_path()
|
||||
|
||||
|
||||
def install_cmdstan_deps(cmdstan_dir: Path):
|
||||
import cmdstanpy
|
||||
from multiprocessing import cpu_count
|
||||
|
||||
cmdstan_cache = get_cmdstan_cache()
|
||||
download_cmdstan(cmdstan_cache)
|
||||
if cmdstan_dir.is_dir():
|
||||
rmtree(cmdstan_dir)
|
||||
copytree(cmdstan_cache, cmdstan_dir)
|
||||
if repackage_cmdstan():
|
||||
if platform.platform().startswith("Win"):
|
||||
maybe_install_cmdstan_toolchain()
|
||||
print("Installing cmdstan to", cmdstan_dir)
|
||||
if os.path.isdir(cmdstan_dir):
|
||||
rmtree(cmdstan_dir)
|
||||
|
||||
if PLATFORM == "win":
|
||||
try:
|
||||
cmdstanpy.utils.cxx_toolchain_path()
|
||||
except Exception:
|
||||
install_cmdstan_toolchain()
|
||||
|
||||
with cmdstanpy.utils.pushd(cmdstan_dir):
|
||||
clean_all_cmdstan()
|
||||
build_cmdstan()
|
||||
cmdstanpy.set_cmdstan_path(str(cmdstan_dir))
|
||||
if not cmdstanpy.install_cmdstan(
|
||||
version=CMDSTAN_VERSION,
|
||||
dir=cmdstan_dir.parent,
|
||||
overwrite=True,
|
||||
verbose=True,
|
||||
cores=cpu_count(),
|
||||
progress=True,
|
||||
):
|
||||
raise RuntimeError("CmdStan failed to install in repackaged directory")
|
||||
|
||||
|
||||
def build_cmdstan_model(target_dir):
|
||||
|
|
@ -186,17 +101,21 @@ def build_cmdstan_model(target_dir):
|
|||
target_dir: Directory to copy the compiled model executable and core cmdstan files to.
|
||||
"""
|
||||
import cmdstanpy
|
||||
|
||||
cmdstan_dir = (Path(target_dir) / f"cmdstan-{CMDSTAN_VERSION}").resolve()
|
||||
install_cmdstan_deps(cmdstan_dir)
|
||||
model_name = "prophet.stan"
|
||||
target_name = "prophet_model.bin"
|
||||
sm = cmdstanpy.CmdStanModel(stan_file=os.path.join(MODEL_DIR, model_name))
|
||||
copy(sm.exe_file, os.path.join(target_dir, target_name))
|
||||
|
||||
# Clean up
|
||||
for f in Path(MODEL_DIR).iterdir():
|
||||
if f.is_file() and f.name != model_name:
|
||||
os.remove(f)
|
||||
prune_cmdstan(cmdstan_dir)
|
||||
|
||||
if repackage_cmdstan():
|
||||
prune_cmdstan(cmdstan_dir)
|
||||
|
||||
|
||||
def build_pystan_model(target_dir):
|
||||
|
|
@ -204,6 +123,7 @@ def build_pystan_model(target_dir):
|
|||
Compile the stan model using pystan and pickle it. The pickle is copied to {target_dir}/prophet_model.pkl.
|
||||
"""
|
||||
import pystan
|
||||
import pickle
|
||||
|
||||
model_name = "prophet.stan"
|
||||
target_name = "prophet_model.pkl"
|
||||
|
|
@ -215,7 +135,7 @@ def build_pystan_model(target_dir):
|
|||
|
||||
|
||||
def get_backends_from_env() -> List[str]:
|
||||
return os.environ.get("STAN_BACKEND", "PYSTAN").split(",")
|
||||
return os.environ.get("STAN_BACKEND", "CMDSTANPY").split(",")
|
||||
|
||||
|
||||
def build_models(target_dir):
|
||||
|
|
@ -322,7 +242,7 @@ with open("requirements.txt", "r") as f:
|
|||
|
||||
setup(
|
||||
name="prophet",
|
||||
version="1.0.1",
|
||||
version="1.1",
|
||||
description="Automatic Forecasting Procedure",
|
||||
url="https://facebook.github.io/prophet/",
|
||||
author="Sean J. Taylor <sjtz@pm.me>, Ben Letham <bletham@fb.com>",
|
||||
|
|
@ -330,7 +250,7 @@ setup(
|
|||
license="MIT",
|
||||
packages=find_packages(),
|
||||
install_requires=install_requires,
|
||||
python_requires=">=3",
|
||||
python_requires=">=3.6",
|
||||
zip_safe=False,
|
||||
include_package_data=True,
|
||||
ext_modules=[Extension("prophet.stan_model", [])],
|
||||
|
|
|
|||
|
|
@ -1,175 +0,0 @@
|
|||
// Copyright (c) Facebook, Inc. and its affiliates.
|
||||
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
functions {
|
||||
real[ , ] get_changepoint_matrix(real[] t, real[] t_change, int T, int S) {
|
||||
// Assumes t and t_change are sorted.
|
||||
real A[T, S];
|
||||
real a_row[S];
|
||||
int cp_idx;
|
||||
|
||||
// Start with an empty matrix.
|
||||
A = rep_array(0, T, S);
|
||||
a_row = rep_array(0, S);
|
||||
cp_idx = 1;
|
||||
|
||||
// Fill in each row of A.
|
||||
for (i in 1:T) {
|
||||
while ((cp_idx <= S) && (t[i] >= t_change[cp_idx])) {
|
||||
a_row[cp_idx] = 1;
|
||||
cp_idx = cp_idx + 1;
|
||||
}
|
||||
A[i] = a_row;
|
||||
}
|
||||
return A;
|
||||
}
|
||||
|
||||
// Logistic trend functions
|
||||
|
||||
real[] logistic_gamma(real k, real m, real[] delta, real[] t_change, int S) {
|
||||
real gamma[S]; // adjusted offsets, for piecewise continuity
|
||||
real k_s[S + 1]; // actual rate in each segment
|
||||
real m_pr;
|
||||
|
||||
// Compute the rate in each segment
|
||||
k_s[1] = k;
|
||||
for (i in 1:S) {
|
||||
k_s[i + 1] = k_s[i] + delta[i];
|
||||
}
|
||||
|
||||
// Piecewise offsets
|
||||
m_pr = m; // The offset in the previous segment
|
||||
for (i in 1:S) {
|
||||
gamma[i] = (t_change[i] - m_pr) * (1 - k_s[i] / k_s[i + 1]);
|
||||
m_pr = m_pr + gamma[i]; // update for the next segment
|
||||
}
|
||||
return gamma;
|
||||
}
|
||||
|
||||
real[] logistic_trend(
|
||||
real k,
|
||||
real m,
|
||||
real[] delta,
|
||||
real[] t,
|
||||
real[] cap,
|
||||
real[ , ] A,
|
||||
real[] t_change,
|
||||
int S,
|
||||
int T
|
||||
) {
|
||||
real gamma[S];
|
||||
real Y[T];
|
||||
|
||||
gamma = logistic_gamma(k, m, delta, t_change, S);
|
||||
for (i in 1:T) {
|
||||
Y[i] = cap[i] / (1 + exp(-(k + dot_product(A[i], delta))
|
||||
* (t[i] - (m + dot_product(A[i], gamma)))));
|
||||
}
|
||||
return Y;
|
||||
}
|
||||
|
||||
// Linear trend function
|
||||
|
||||
real[] linear_trend(
|
||||
real k,
|
||||
real m,
|
||||
real[] delta,
|
||||
real[] t,
|
||||
real[ , ] A,
|
||||
real[] t_change,
|
||||
int S,
|
||||
int T
|
||||
) {
|
||||
real gamma[S];
|
||||
real Y[T];
|
||||
|
||||
for (i in 1:S) {
|
||||
gamma[i] = -t_change[i] * delta[i];
|
||||
}
|
||||
for (i in 1:T) {
|
||||
Y[i] = (k + dot_product(A[i], delta)) * t[i] + (
|
||||
m + dot_product(A[i], gamma));
|
||||
}
|
||||
return Y;
|
||||
}
|
||||
|
||||
// Flat trend function
|
||||
|
||||
real[] flat_trend(
|
||||
real m,
|
||||
int T
|
||||
) {
|
||||
return rep_array(m, T);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
data {
|
||||
int T; // Number of time periods
|
||||
int<lower=1> K; // Number of regressors
|
||||
real t[T]; // Time
|
||||
real cap[T]; // Capacities for logistic trend
|
||||
real y[T]; // Time series
|
||||
int S; // Number of changepoints
|
||||
real t_change[S]; // Times of trend changepoints
|
||||
real X[T,K]; // Regressors
|
||||
vector[K] sigmas; // Scale on seasonality prior
|
||||
real<lower=0> tau; // Scale on changepoints prior
|
||||
int trend_indicator; // 0 for linear, 1 for logistic, 2 for flat
|
||||
real s_a[K]; // Indicator of additive features
|
||||
real s_m[K]; // Indicator of multiplicative features
|
||||
}
|
||||
|
||||
transformed data {
|
||||
real A[T, S];
|
||||
A = get_changepoint_matrix(t, t_change, T, S);
|
||||
}
|
||||
|
||||
parameters {
|
||||
real k; // Base trend growth rate
|
||||
real m; // Trend offset
|
||||
real delta[S]; // Trend rate adjustments
|
||||
real<lower=0> sigma_obs; // Observation noise
|
||||
real beta[K]; // Regressor coefficients
|
||||
}
|
||||
|
||||
transformed parameters {
|
||||
real trend[T];
|
||||
real Y[T];
|
||||
real beta_m[K];
|
||||
real beta_a[K];
|
||||
|
||||
if (trend_indicator == 0) {
|
||||
trend = linear_trend(k, m, delta, t, A, t_change, S, T);
|
||||
} else if (trend_indicator == 1) {
|
||||
trend = logistic_trend(k, m, delta, t, cap, A, t_change, S, T);
|
||||
} else if (trend_indicator == 2){
|
||||
trend = flat_trend(m, T);
|
||||
}
|
||||
|
||||
for (i in 1:K) {
|
||||
beta_m[i] = beta[i] * s_m[i];
|
||||
beta_a[i] = beta[i] * s_a[i];
|
||||
}
|
||||
|
||||
for (i in 1:T) {
|
||||
Y[i] = (
|
||||
trend[i] * (1 + dot_product(X[i], beta_m)) + dot_product(X[i], beta_a)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
model {
|
||||
//priors
|
||||
k ~ normal(0, 5);
|
||||
m ~ normal(0, 5);
|
||||
delta ~ double_exponential(0, tau);
|
||||
sigma_obs ~ normal(0, 0.5);
|
||||
beta ~ normal(0, sigmas);
|
||||
|
||||
// Likelihood
|
||||
y ~ normal(Y, sigma_obs);
|
||||
}
|
||||
Loading…
Reference in a new issue