bunobee 0.0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bunobee/__init__.py +4 -0
- bunobee/hyper_tune.py +180 -0
- bunobee/models/__init__.py +1 -0
- bunobee/models/dlt.py +541 -0
- bunobee/models/m5/__init__.py +0 -0
- bunobee/models/m5/m5_ssp_mcmc.py +135 -0
- bunobee/models/m5/m5_ssp_optim.py +423 -0
- bunobee/models/ssp/__init__.py +19 -0
- bunobee/models/ssp/kalman_1d.py +537 -0
- bunobee/models/ssp/kalman_1d_ekf.py +455 -0
- bunobee/models/ssp/kalman_1d_st.py +194 -0
- bunobee/models/ssp/kalman_1d_st_ekf.py +209 -0
- bunobee/models/ssp/plotting.py +249 -0
- bunobee/models/ssp/posterior.py +157 -0
- bunobee/models/ssp/prior.py +239 -0
- bunobee/models/ssp/transforms.py +608 -0
- bunobee/models/ssp/vfactory.py +73 -0
- bunobee/regression.py +232 -0
- bunobee/saturation.py +131 -0
- bunobee/simulation/__init__.py +0 -0
- bunobee/simulation/ssp.py +97 -0
- bunobee/utils.py +33 -0
- bunobee-0.0.4.dist-info/METADATA +45 -0
- bunobee-0.0.4.dist-info/RECORD +27 -0
- bunobee-0.0.4.dist-info/WHEEL +5 -0
- bunobee-0.0.4.dist-info/licenses/LICENSE +21 -0
- bunobee-0.0.4.dist-info/top_level.txt +1 -0
bunobee/__init__.py
ADDED
bunobee/hyper_tune.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import jax
|
|
2
|
+
import jax.numpy as jnp
|
|
3
|
+
import numpy as np
|
|
4
|
+
from jax import vmap
|
|
5
|
+
import logging
|
|
6
|
+
import xarray as xr
|
|
7
|
+
|
|
8
|
+
from typing import Dict, Tuple
|
|
9
|
+
|
|
10
|
+
from .utils import flatten_front_dim
|
|
11
|
+
from .models.dlt import run_dlt_model
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger("wunku")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def slice_trend_single(trend_comp: jnp.ndarray, h: int) -> jnp.ndarray:
|
|
17
|
+
"""Slices a single trend into overlapping windows of size h.
|
|
18
|
+
|
|
19
|
+
Args
|
|
20
|
+
----
|
|
21
|
+
trend_comp: array representing the trend component with shape (n_steps, )
|
|
22
|
+
h: Size of the window to slice the trend into.
|
|
23
|
+
|
|
24
|
+
Returns
|
|
25
|
+
-------
|
|
26
|
+
A 2D array of shape (n_windows, h) where each row is a window of size h where
|
|
27
|
+
num_windows = n_steps - h + 1.
|
|
28
|
+
"""
|
|
29
|
+
n_steps = trend_comp.shape[0]
|
|
30
|
+
num_windows = n_steps - h + 1
|
|
31
|
+
|
|
32
|
+
def get_window(t_idx):
|
|
33
|
+
return jax.lax.dynamic_slice(trend_comp, start_indices=(t_idx,), slice_sizes=(h,))
|
|
34
|
+
|
|
35
|
+
t_indices = jnp.arange(num_windows)
|
|
36
|
+
# (n_windows, h)
|
|
37
|
+
return vmap(get_window)(t_indices)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def slice_trend(trend_comp, h):
|
|
41
|
+
"""Slices multiple trends into overlapping windows of size h.
|
|
42
|
+
Args
|
|
43
|
+
----
|
|
44
|
+
trend_comp: array representing multiple trend components with shape (N_samples, n_steps)
|
|
45
|
+
h: Size of the window to slice the trends into.
|
|
46
|
+
|
|
47
|
+
Returns
|
|
48
|
+
-------
|
|
49
|
+
A 3D array of shape (N_samples, n_windows, h) where each row is a window of size h
|
|
50
|
+
and num_windows = n_steps - h + 1 for each trend.
|
|
51
|
+
"""
|
|
52
|
+
# trends: shape (N_samples, T)
|
|
53
|
+
return vmap(lambda trend_comp: slice_trend_single(trend_comp, h))(trend_comp)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def generate_forecast_span_samples(idata: xr.Dataset, h: int) -> jnp.ndarray:
|
|
57
|
+
# how to do a sliding window prediction?
|
|
58
|
+
dlt_comp = flatten_front_dim(idata["dlt_comp"].to_numpy(), n=2)
|
|
59
|
+
reg_comp = flatten_front_dim(idata["reg_comp"].to_numpy(), n=2)
|
|
60
|
+
|
|
61
|
+
logger.debug("dlt_comp shape:", dlt_comp.shape)
|
|
62
|
+
logger.debug("reg_comp shape:", reg_comp.shape)
|
|
63
|
+
|
|
64
|
+
dlt_comp_slice = dlt_comp[:, : -(h - 1), None]
|
|
65
|
+
reg_comp_slice = slice_trend(reg_comp, h=h)
|
|
66
|
+
|
|
67
|
+
# (n_samples, n_windows, h)
|
|
68
|
+
yhat_span = dlt_comp_slice + reg_comp_slice
|
|
69
|
+
return yhat_span
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def compute_log_likelihood(yhat_span, sigma, y):
|
|
73
|
+
"""Computes the log likelihood of the forecasted span given the observations.
|
|
74
|
+
|
|
75
|
+
Args
|
|
76
|
+
----
|
|
77
|
+
yhat_span: Forecasted span of shape (n_samples, n_windows, h).
|
|
78
|
+
sigma: Standard deviation of the noise, shape (n_samples,)
|
|
79
|
+
y_slice: Observations for the forecasted span, shape (n_windows, h).
|
|
80
|
+
|
|
81
|
+
Returns
|
|
82
|
+
-------
|
|
83
|
+
A 3D array of log probabilities of shape (n_samples, n_windows, h).
|
|
84
|
+
"""
|
|
85
|
+
h = yhat_span.shape[-1]
|
|
86
|
+
# reshape for broadcasting
|
|
87
|
+
# (n_samples, 1, 1)
|
|
88
|
+
sigma_broadcast = sigma[:, None, None]
|
|
89
|
+
# (n_windos, h)
|
|
90
|
+
y_slice = slice_trend_single(y, h=h)
|
|
91
|
+
# (1, n_windows, h)
|
|
92
|
+
y_slice_broadcast = y_slice[None, :, :]
|
|
93
|
+
|
|
94
|
+
# compute squared error
|
|
95
|
+
sq_error = (y_slice_broadcast - yhat_span) ** 2
|
|
96
|
+
|
|
97
|
+
# compute log likelihood per (s, t, h)
|
|
98
|
+
log_prob = -0.5 * jnp.log(2 * jnp.pi) - jnp.log(sigma_broadcast) - 0.5 * sq_error / (sigma_broadcast**2)
|
|
99
|
+
|
|
100
|
+
return log_prob
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def compute_wbic(loglk: jnp.ndarray) -> float:
|
|
104
|
+
"""Compute Weighted Bayesian Information Criterion (WBIC) from log likelihood.
|
|
105
|
+
Args
|
|
106
|
+
----
|
|
107
|
+
loglk: array-like, log likelihood per samples per obs; should be with values of shape (n_samples, n_steps, h)
|
|
108
|
+
"""
|
|
109
|
+
# in original paper, they use sum but it leads to unstable scale due to different size of datasets
|
|
110
|
+
# we use mean instead
|
|
111
|
+
loglk_per_sample = jnp.nanmean(loglk, axis=(-1, -2))
|
|
112
|
+
# (n_steps * h, )
|
|
113
|
+
nobs = loglk.shape[-1] * loglk.shape[-2]
|
|
114
|
+
beta = 1.0 / jnp.log(nobs)
|
|
115
|
+
wbic = -(1.0 / beta) * jnp.nanmean(loglk_per_sample)
|
|
116
|
+
wbic = float(wbic)
|
|
117
|
+
return wbic
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def run_dlt_model_and_compute_wbic(params: Tuple, data: Dict[str, np.ndarray], h: int = 12) -> float:
|
|
121
|
+
lev_sm, slp_sm, theta = params
|
|
122
|
+
y = data["y"]
|
|
123
|
+
x_seas = data["x_seas"]
|
|
124
|
+
x_glb_trend = data["x_glb_trend"]
|
|
125
|
+
logger.info(f"Trying lev_sm={lev_sm:.4f}, slp_sm={slp_sm:.4f}, theta={theta:.4f}")
|
|
126
|
+
|
|
127
|
+
idata = run_dlt_model(
|
|
128
|
+
lev_sm=lev_sm,
|
|
129
|
+
slp_sm=slp_sm,
|
|
130
|
+
theta=theta,
|
|
131
|
+
x_seas=x_seas,
|
|
132
|
+
x_glb_trend=x_glb_trend,
|
|
133
|
+
y=y,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
yhat_span = generate_forecast_span_samples(idata=idata, h=h)
|
|
137
|
+
loglk = compute_log_likelihood(
|
|
138
|
+
yhat_span=yhat_span,
|
|
139
|
+
sigma=flatten_front_dim(idata["sigma"].to_numpy(), n=2),
|
|
140
|
+
y=y,
|
|
141
|
+
)
|
|
142
|
+
wbic = compute_wbic(loglk)
|
|
143
|
+
|
|
144
|
+
print(f"WBIC: {wbic:.4f}")
|
|
145
|
+
return wbic
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def hyper_tuning_dlt_with_wbic(
|
|
149
|
+
data: Dict[str, np.ndarray],
|
|
150
|
+
# forecast horizon
|
|
151
|
+
h: int = 12,
|
|
152
|
+
n_calls: int = 15,
|
|
153
|
+
random_state: int = 42,
|
|
154
|
+
):
|
|
155
|
+
print("Starting hyperparameter tuning for DLT model using WBIC...")
|
|
156
|
+
# print args
|
|
157
|
+
print(f"h: {h}, n_calls: {n_calls}, random_state: {random_state}")
|
|
158
|
+
# try import skopt
|
|
159
|
+
try:
|
|
160
|
+
from skopt import gp_minimize
|
|
161
|
+
from skopt.space import Real
|
|
162
|
+
except ImportError:
|
|
163
|
+
raise ImportError("Please install scikit-optimize to run hyperparameter tuning.")
|
|
164
|
+
|
|
165
|
+
# Define the hyperparam space
|
|
166
|
+
search_space = [
|
|
167
|
+
Real(0.0001, 0.1, prior="log-uniform", name="lev_sm"),
|
|
168
|
+
Real(0.001, 0.1, prior="log-uniform", name="slp_sm"),
|
|
169
|
+
Real(0, 1.0, prior="uniform", name="theta"),
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
# Run Bayesian Optimization
|
|
173
|
+
result = gp_minimize(
|
|
174
|
+
func=lambda params: run_dlt_model_and_compute_wbic(params=params, data=data, h=h),
|
|
175
|
+
dimensions=search_space,
|
|
176
|
+
n_calls=n_calls,
|
|
177
|
+
random_state=random_state,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
return result
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|