Save and Load the eliobj¶
Here we introduce a method for harmonising two timeseries. This part may be more unusual or unfamiliar to people used to working with arrays, so it serves as an introduction into some of the concepts used in this package.
Imports¶
In [1]:
Copied!
import os
import shutil
os.environ["TF_ENABLE_ONEDNN_OPTS"] = "0"
from typing import Any, Union
import numpy as np
import tensorflow as tf
import tensorflow_probability as tfp
import elicito as el
tfd = tfp.distributions
import os
import shutil
os.environ["TF_ENABLE_ONEDNN_OPTS"] = "0"
from typing import Any, Union
import numpy as np
import tensorflow as tf
import tensorflow_probability as tfp
import elicito as el
tfd = tfp.distributions
2025-11-12 14:14:06.113705: I external/local_xla/xla/tsl/cuda/cudart_stub.cc:31] Could not find cuda drivers on your machine, GPU will not be used. 2025-11-12 14:14:06.159826: I tensorflow/core/platform/cpu_feature_guard.cc:210] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations. To enable the following instructions: AVX2 FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.
2025-11-12 14:14:07.813458: I external/local_xla/xla/tsl/cuda/cudart_stub.cc:31] Could not find cuda drivers on your machine, GPU will not be used.
2025-11-12 14:14:09.511406: E external/local_xla/xla/stream_executor/cuda/cuda_platform.cc:51] failed call to cuInit: INTERNAL: CUDA error: Failed call to cuInit: UNKNOWN ERROR (303)
In [2]:
Copied!
# prepare notebook and remove results from previous runs
if os.path.isdir("results"):
shutil.rmtree("results")
# prepare notebook and remove results from previous runs
if os.path.isdir("results"):
shutil.rmtree("results")
In [3]:
Copied!
# numeric, standardized predictor
def std_predictor(N: int, quantiles: list[Union[float, int]]) -> Any:
"""
Compute design matrix
Parameters
----------
N
number of observations
quantiles
quantiles to use in percentage
Returns
-------
:
design matrix
"""
X = tf.cast(np.arange(N), tf.float32)
X_std = (X - tf.reduce_mean(X)) / tf.math.reduce_std(X)
X_sel = tfp.stats.percentile(X_std, quantiles)
return X_sel
# implemented, generative model
class ToyModel:
"""
Generative model
"""
def __call__(self, prior_samples: Any, design_matrix: Any) -> dict[str, Any]:
"""
Run the generative model
Parameters
----------
prior_samples
samples from the prior distribution
design_matrix
design matrix
Returns
-------
:
dictionary with stored target quantities
"""
B = prior_samples.shape[0]
S = prior_samples.shape[1]
# preprocess shape of design matrix
X = tf.broadcast_to(design_matrix[None, None, :], (B, S, len(design_matrix)))
# linear predictor (= mu)
epred = tf.add(
prior_samples[:, :, 0][:, :, None],
tf.multiply(prior_samples[:, :, 1][:, :, None], X),
)
# data-generating model
likelihood = tfd.Normal(
loc=epred, scale=tf.expand_dims(prior_samples[:, :, -1], -1)
)
# prior predictive distribution (=height)
ypred = likelihood.sample()
# selected observations
y_X0, y_X1, y_X2 = (ypred[:, :, 0], ypred[:, :, 1], ypred[:, :, 2])
return dict(y_X0=y_X0, y_X1=y_X1, y_X2=y_X2)
# numeric, standardized predictor
def std_predictor(N: int, quantiles: list[Union[float, int]]) -> Any:
"""
Compute design matrix
Parameters
----------
N
number of observations
quantiles
quantiles to use in percentage
Returns
-------
:
design matrix
"""
X = tf.cast(np.arange(N), tf.float32)
X_std = (X - tf.reduce_mean(X)) / tf.math.reduce_std(X)
X_sel = tfp.stats.percentile(X_std, quantiles)
return X_sel
# implemented, generative model
class ToyModel:
"""
Generative model
"""
def __call__(self, prior_samples: Any, design_matrix: Any) -> dict[str, Any]:
"""
Run the generative model
Parameters
----------
prior_samples
samples from the prior distribution
design_matrix
design matrix
Returns
-------
:
dictionary with stored target quantities
"""
B = prior_samples.shape[0]
S = prior_samples.shape[1]
# preprocess shape of design matrix
X = tf.broadcast_to(design_matrix[None, None, :], (B, S, len(design_matrix)))
# linear predictor (= mu)
epred = tf.add(
prior_samples[:, :, 0][:, :, None],
tf.multiply(prior_samples[:, :, 1][:, :, None], X),
)
# data-generating model
likelihood = tfd.Normal(
loc=epred, scale=tf.expand_dims(prior_samples[:, :, -1], -1)
)
# prior predictive distribution (=height)
ypred = likelihood.sample()
# selected observations
y_X0, y_X1, y_X2 = (ypred[:, :, 0], ypred[:, :, 1], ypred[:, :, 2])
return dict(y_X0=y_X0, y_X1=y_X1, y_X2=y_X2)
Step 1: Create the eliobj¶
In [4]:
Copied!
# define the generative model
model = el.model(
obj=ToyModel, design_matrix=std_predictor(N=200, quantiles=[25, 50, 75])
)
# specify the model parameters and their prior distribution families
parameters = [
el.parameter(
name="beta0",
family=tfd.Normal,
hyperparams=dict(loc=el.hyper("mu0"), scale=el.hyper("sigma0", lower=0)),
),
el.parameter(
name="beta1",
family=tfd.Normal,
hyperparams=dict(
loc=el.hyper("mu1"),
scale=el.hyper("sigma1", lower=0), # TODO specify error message
),
),
el.parameter(
name="sigma",
family=tfd.HalfNormal,
hyperparams=dict(scale=el.hyper("sigma2", lower=0)),
),
]
# specify the target quantities and corresponding elicitation technique
targets = [
el.target(
name=f"y_X{i}",
query=el.queries.quantiles((0.05, 0.25, 0.50, 0.75, 0.95)),
loss=el.losses.MMD2(kernel="energy"),
weight=1.0,
)
for i in range(3)
]
# use an oracle to simulate a ground truth for the expert data
expert = el.expert.simulator(
ground_truth={
"beta0": tfd.Normal(loc=5, scale=1),
"beta1": tfd.Normal(loc=2, scale=1),
"sigma": tfd.HalfNormal(scale=10.0),
},
num_samples=10_000,
)
# specify the optimizer for gradient descent
optimizer = el.optimizer(
optimizer=tf.keras.optimizers.Adam, learning_rate=0.1, clipnorm=1.0
)
# define the trainer model with used approach, seed, etc.
trainer = el.trainer(method="parametric_prior", seed=0, epochs=4, progress=0)
# specify the initialization distribution, used to draw the initial values
# for the hyperparameters
initializer = el.initializer(
method="sobol",
iterations=32,
distribution=el.initialization.uniform(radius=1, mean=0),
)
eliobj = el.Elicit(
model=model,
parameters=parameters,
targets=targets,
expert=expert,
optimizer=optimizer,
trainer=trainer,
initializer=initializer,
)
# define the generative model
model = el.model(
obj=ToyModel, design_matrix=std_predictor(N=200, quantiles=[25, 50, 75])
)
# specify the model parameters and their prior distribution families
parameters = [
el.parameter(
name="beta0",
family=tfd.Normal,
hyperparams=dict(loc=el.hyper("mu0"), scale=el.hyper("sigma0", lower=0)),
),
el.parameter(
name="beta1",
family=tfd.Normal,
hyperparams=dict(
loc=el.hyper("mu1"),
scale=el.hyper("sigma1", lower=0), # TODO specify error message
),
),
el.parameter(
name="sigma",
family=tfd.HalfNormal,
hyperparams=dict(scale=el.hyper("sigma2", lower=0)),
),
]
# specify the target quantities and corresponding elicitation technique
targets = [
el.target(
name=f"y_X{i}",
query=el.queries.quantiles((0.05, 0.25, 0.50, 0.75, 0.95)),
loss=el.losses.MMD2(kernel="energy"),
weight=1.0,
)
for i in range(3)
]
# use an oracle to simulate a ground truth for the expert data
expert = el.expert.simulator(
ground_truth={
"beta0": tfd.Normal(loc=5, scale=1),
"beta1": tfd.Normal(loc=2, scale=1),
"sigma": tfd.HalfNormal(scale=10.0),
},
num_samples=10_000,
)
# specify the optimizer for gradient descent
optimizer = el.optimizer(
optimizer=tf.keras.optimizers.Adam, learning_rate=0.1, clipnorm=1.0
)
# define the trainer model with used approach, seed, etc.
trainer = el.trainer(method="parametric_prior", seed=0, epochs=4, progress=0)
# specify the initialization distribution, used to draw the initial values
# for the hyperparameters
initializer = el.initializer(
method="sobol",
iterations=32,
distribution=el.initialization.uniform(radius=1, mean=0),
)
eliobj = el.Elicit(
model=model,
parameters=parameters,
targets=targets,
expert=expert,
optimizer=optimizer,
trainer=trainer,
initializer=initializer,
)
Step 2: Save the unfitted eliobj object¶
Two approaches are possible:
- automatic saving:
namehas to be specified.- The results are then saved according to the
- following rule:
res/{method}/{name}_{seed}.pkl
- user-specific path:
filehas to be specified.- The path can be freely specified by the user.
In [5]:
Copied!
# use automatic saving approach
eliobj.save(name="m1")
# use user-specific file location
eliobj.save(file="results/m1_1")
# use automatic saving approach
eliobj.save(name="m1")
# use user-specific file location
eliobj.save(file="results/m1_1")
saved in: ./results/parametric_prior/m1_0.pkl saved in: ./results/m1_1.pkl
Load and fit the unfitted eliobj¶
In [6]:
Copied!
# load the eliobj
eliobj_m1 = el.utils.load("results/m1_1.pkl")
# fit the eliobj
eliobj_m1.fit()
# load the eliobj
eliobj_m1 = el.utils.load("results/m1_1.pkl")
# fit the eliobj
eliobj_m1.fit()
Inspect the fitted eliobj¶
Inspect configuration
In [7]:
Copied!
eliobj_m1
eliobj_m1
Out[7]:
Model hyperparameters: 5 Model parameters: 3 Targets -> Elicited summaries (loss components): 3 - y_X0 (128, 200) -> quantiles_y_X0 (128, 5) - y_X1 (128, 200) -> quantiles_y_X1 (128, 5) - y_X2 (128, 200) -> quantiles_y_X2 (128, 5) Prior samples: 200 (128, 200, 3) Batch size: 128 Epochs: 4 Method: parametric_prior Seed: 0 Optimizer: Adam(lr=0.1) Initializer: (method: sobol, iterations: 32)
Inspect results
- results for each epoch are stored in
history - results saved only for the last epoch are stored in
results
In [8]:
Copied!
# information saved in the results object
list(eliobj_m1.results.groups)
# information saved in the results object
list(eliobj_m1.results.groups)
Out[8]:
['/', '/history_stats', '/prior', '/model', '/target_quantity', '/elicited_summary', '/oracle', '/initialization', '/history_stats/loss', '/history_stats/hyperparameter']
Save and reload the fitted eliobj¶
In [9]:
Copied!
# save the fitted object
eliobj_m1.save(name="m2")
# load the fitted object
eliobj_m1_reload = el.utils.load("./results/parametric_prior/m2_0.pkl")
eliobj_m1_reload.results
# save the fitted object
eliobj_m1.save(name="m2")
# load the fitted object
eliobj_m1_reload = el.utils.load("./results/parametric_prior/m2_0.pkl")
eliobj_m1_reload.results
saved in: ./results/parametric_prior/m2_0.pkl
Out[9]:
<xarray.DatasetView> Size: 0B
Dimensions: ()
Data variables:
*empty*In [10]:
Copied!
# eliobj_m2_reload.fit()
# prompt:
# elicit object is already fitted.
# Do you want to fit it again and overwrite the results?
# Press 'n' to stop process and 'y' to continue fitting.
# user input: n
# eliobj_m2_reload.fit()
# prompt:
# elicit object is already fitted.
# Do you want to fit it again and overwrite the results?
# Press 'n' to stop process and 'y' to continue fitting.
# user input: n
Can I force re-fitting?¶
Sometimes, especially when we only want to test something, it can be
inconvenient to repeatedly confirm whether
results should be overwritten. To address this issue, you can set
overwrite=True to enable re-fitting without any prompts.
In [11]:
Copied!
eliobj_m1_reload.fit(overwrite=True)
eliobj_m1_reload.fit(overwrite=True)
Can I overwrite an eliobj that already exits as file on disk?¶
In [12]:
Copied!
# eliobj_m2_reload.save(name="m2")
# prompt:
# In provided directory exists already a file with identical name.
# Do you want to overwrite it?
# Press 'y' for overwriting and 'n' for abording.
# user input: n
# eliobj_m2_reload.save(name="m2")
# prompt:
# In provided directory exists already a file with identical name.
# Do you want to overwrite it?
# Press 'y' for overwriting and 'n' for abording.
# user input: n
Can I force overwriting an existing eliobj file?¶
In [13]:
Copied!
eliobj_m1_reload.save(name="m1", overwrite=True)
eliobj_m1_reload.save(name="m1", overwrite=True)
saved in: ./results/parametric_prior/m1_0.pkl