Swithing Kalman filter parameter tuning using synthetic anomalies#
This tutorial example presents how to calibrate the hyperparameters of the SSM & LSTM network, as well as the hyperparameters of a switching Kalman filter (SKF) using synthetic anomalies and the external Ray Tune library. The example involves the detection of anomalies that take the form of change points. The model relies on the SKF for estimating the probability of the local trend regime versus a local acceleration regime, whereas a high probability for the later indicates the presence of a change point in a time series.
The calibration of the LSTM neural network relies on the raw traning set that is deemed to be stationnary and the SKF hyperparameters are estimated using synthetic anomalies that are added on the raw traning set.
In this example, we use a simple sine-like signal onto which we add a synthetic regime change marking the time series swithcing from a stationnary regime to a trend-statinnary one.
Import libraries#
Import the various libraries that will be employed in this example.
[14]:
import copy
from pathlib import Path
project_root = Path.cwd().resolve().parents[1]
import ray
ray.shutdown()
ray.init(
runtime_env={
"working_dir": str(project_root),
"excludes": [".git"]
}
)
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from pytagi import Normalizer
import pytagi.metric as metric
2025-06-03 11:40:35,281 INFO worker.py:1852 -- Started a local Ray instance.
2025-06-03 11:40:35,303 INFO packaging.py:575 -- Creating a file package for local module '/Users/vuongdai/GitHub/canari'.
2025-06-03 11:40:35,318 INFO packaging.py:367 -- Pushing file package 'gcs://_ray_pkg_91a08a37fa1e6106.zip' (2.69MiB) to Ray cluster...
2025-06-03 11:40:35,322 INFO packaging.py:380 -- Successfully pushed file package 'gcs://_ray_pkg_91a08a37fa1e6106.zip'.
Import from Canari#
From Canari, we need to import several classes that will be reused in this example. Notably, we need to import the components that will be used to build the model; In terms of baseline, we the SKF will build two competing models using respectively the LocalTrend
and LocalAcceleration
components. The recurrent pattern is modelled using a LstmNetwork
and the residual is modelled by a WhiteNoise
compoment.
[15]:
from canari import (
DataProcess,
Model,
SKF,
ModelOptimizer,
SKFOptimizer,
plot_data,
plot_prediction,
plot_states,
plot_skf_states,
)
from canari.component import LocalTrend, LocalAcceleration, LstmNetwork, WhiteNoise
Read data#
The raw .csv
data is saved in a dataframe using the Pandas external library.
[16]:
project_root = Path.cwd().resolve().parents[1]
data_file = str(project_root / "data/toy_time_series/sine.csv")
df = pd.read_csv(data_file, skiprows=1, delimiter=",", header=None)
# Add synthetic anomaly to data
trend = np.linspace(0, 0, num=len(df))
time_anomaly = 120
new_trend = np.linspace(0, 1, num=len(df) - time_anomaly)
trend[time_anomaly:] = trend[time_anomaly:] + new_trend
df = df.add(trend, axis=0)
#
data_file_time = str(project_root / "data/toy_time_series/sine_datetime.csv")
time_index = pd.read_csv(data_file_time, skiprows=1, delimiter=",", header=None)
time_index = pd.to_datetime(time_index[0])
df.index = time_index
df.index.name = "time"
df.columns = ["values"]
Data preprocess#
In terms of pre-processsing, we want to add the hour-of-the-day as time-covariate for the LSTM network. Moreover we define here our choice of using the first 40% of the raw time series for trainig and the following 10% for the validaiton set. The remaining last 50% are the implicitely defined as the test set.
[17]:
output_col = [0]
data_processor = DataProcess(
data=df,
time_covariates=["hour_of_day"],
train_split=0.4,
validation_split=0.1,
output_col=output_col,
)
train_data, validation_data, test_data, all_data = data_processor.get_splits()
data_processor.data.head()
[17]:
values | hour_of_day | |
---|---|---|
time | ||
2000-01-03 00:00:00 | 0.00 | 0.0 |
2000-01-03 01:00:00 | -0.26 | 1.0 |
2000-01-03 02:00:00 | -0.50 | 2.0 |
2000-01-03 03:00:00 | -0.71 | 3.0 |
2000-01-03 04:00:00 | -0.87 | 4.0 |
A. Optimize the model hyperparameters#
This section presents how Canari relies on the Ray Tune library in order to optimize the hyperparameters for the SSM, the LSTM, and the SKF.
A.1. Parameter space#
First, we need to define which hyperparameters will be optimized and for each of them what is the range of values to be searched. In this example, we search for the optimal look-back window length between 10 and 30 time steps, and the residual component’s standard deviation between 0.001 and 0.2.
[18]:
param_space = {
"look_back_len": [10, 30],
"sigma_v": [1e-3, 2e-1],
}
A.2. Model as a function of parameters#
We need to create a function that makes our model depends on the parameters to be search over.
[ ]:
def initialize_model(param, train_data, validation_data):
local_trend = LocalTrend()
pattern = LstmNetwork(
look_back_len=param["look_back_len"],
num_features=2,
num_layer=1,
num_hidden_unit=50,
device="cpu",
manual_seed=1,
)
residual = WhiteNoise(std_error=param["sigma_v"])
model = Model(local_trend, pattern, residual)
model.auto_initialize_baseline_states(train_data["y"][0:24])
# Training
num_epoch = 50
for epoch in range(num_epoch):
(mu_validation_preds, std_validation_preds, states) = model.lstm_train(
train_data=train_data,
validation_data=validation_data,
)
model.set_memory(states=states, time_step=0)
# Unstandardize the predictions
mu_validation_preds_unnorm = Normalizer.unstandardize(
mu_validation_preds,
data_processor.scale_const_mean[data_processor.output_col],
data_processor.scale_const_std[data_processor.output_col],
)
std_validation_preds_unnorm = Normalizer.unstandardize_std(
std_validation_preds,
data_processor.scale_const_std[data_processor.output_col],
)
validation_obs = data_processor.get_data("validation").flatten()
validation_log_lik = metric.log_likelihood(
prediction=mu_validation_preds_unnorm,
observation=validation_obs,
std=std_validation_preds_unnorm,
)
model.early_stopping(
evaluate_metric=-validation_log_lik,
current_epoch=epoch,
max_epoch=num_epoch,
)
model.metric_optim = model.early_stop_metric
if epoch == model.optimal_epoch:
mu_validation_preds_optim = mu_validation_preds.copy()
std_validation_preds_optim = std_validation_preds.copy()
states_optim = copy.copy(states)
if model.stop_training:
break
return model, states_optim, mu_validation_preds_optim, std_validation_preds_optim
A.3. Define optimizer#
We instantiate and run the optimizer for our example. The results is a set of optimal hyperparameters for our model.
[20]:
model_optimizer = ModelOptimizer(
model=initialize_model,
param_space=param_space,
train_data=train_data,
validation_data = validation_data,
num_optimization_trial=20,
)
model_optimizer.optimize()
# 1/20 - Metric: -1.076 - Parameter: {'look_back_len': 25, 'sigma_v': 0.05328836981469993}
# 2/20 - Metric: -1.100 - Parameter: {'look_back_len': 21, 'sigma_v': 0.06919717649172338}
# 3/20 - Metric: -0.633 - Parameter: {'look_back_len': 29, 'sigma_v': 0.1866817610068976}
# 4/20 - Metric: -0.912 - Parameter: {'look_back_len': 13, 'sigma_v': 0.17408527179766783}
# 5/20 - Metric: -0.679 - Parameter: {'look_back_len': 25, 'sigma_v': 0.19146201318103992}
# 6/20 - Metric: -0.784 - Parameter: {'look_back_len': 18, 'sigma_v': 0.18812016434569095}
# 7/20 - Metric: -0.705 - Parameter: {'look_back_len': 26, 'sigma_v': 0.17194872613948659}
# 8/20 - Metric: -0.830 - Parameter: {'look_back_len': 26, 'sigma_v': 0.12771785837990235}
# 9/20 - Metric: -0.844 - Parameter: {'look_back_len': 29, 'sigma_v': 0.1104751968541289}
# 10/20 - Metric: -0.633 - Parameter: {'look_back_len': 26, 'sigma_v': 0.19782127164397462}
# 11/20 - Metric: -1.030 - Parameter: {'look_back_len': 24, 'sigma_v': 0.031901177075863416}
# 12/20 - Metric: -1.327 - Parameter: {'look_back_len': 19, 'sigma_v': 0.007680155280202473}
# 13/20 - Metric: -1.251 - Parameter: {'look_back_len': 19, 'sigma_v': 0.05025105399317167}
# 14/20 - Metric: -1.329 - Parameter: {'look_back_len': 19, 'sigma_v': 0.0035182210646527295}
# 15/20 - Metric: -1.268 - Parameter: {'look_back_len': 16, 'sigma_v': 0.008935335923750579}
# 16/20 - Metric: -1.408 - Parameter: {'look_back_len': 15, 'sigma_v': 0.0064736335252183275}
# 17/20 - Metric: -1.697 - Parameter: {'look_back_len': 10, 'sigma_v': 0.0012226751572942474}
# 18/20 - Metric: -1.377 - Parameter: {'look_back_len': 10, 'sigma_v': 0.08238787037357582}
# 19/20 - Metric: -1.424 - Parameter: {'look_back_len': 10, 'sigma_v': 0.07367386097364725}
2025-06-03 11:40:53,479 INFO tune.py:1009 -- Wrote the latest version of all result files and experiment state to '/Users/vuongdai/ray_results/Model_optimizer' in 0.0092s.
# 20/20 - Metric: -1.621 - Parameter: {'look_back_len': 10, 'sigma_v': 0.03413488586794264}
-----
Optimal parameters at trial #17: {'look_back_len': 10, 'sigma_v': 0.0012226751572942474}
-----
A.4 Get optimal model hyperparameters#
We then get the best model parameters and set them for our model.
[21]:
param_optim = model_optimizer.get_best_param()
model_optim, states_optim, mu_validation_preds, std_validation_preds = initialize_model(param_optim, train_data, validation_data)
model_optim_dict = model_optim.get_dict()
A.5 Hidden states and predictions#
We represent the time-series decomposition visually where the raw data is overlaid with the baseline hidden state represented by the level. The rate of change of the baseline is caracterised by the trend and acceleration hidden states. The recurrent pattern is captured by the LSTM neural network. The posterior estimate for the residuals are displayed for the white noise component.
Note that in this first trainig phase, the results are only presented for the training and validation sets.
[22]:
fig, ax = plt.subplots(figsize=(10, 6))
plot_data(
data_processor=data_processor,
standardization=True,
plot_test_data=False,
plot_column=output_col,
test_label="y",
)
plot_prediction(
data_processor=data_processor,
mean_validation_pred=mu_validation_preds,
std_validation_pred=std_validation_preds,
validation_label=[r"$\mu$", f"$\pm\sigma$"],
)
plot_states(
data_processor=data_processor,
states=states_optim,
standardization=True,
states_to_plot=["level"],
sub_plot=ax,
)
plt.legend()
plt.title("Validation predictions")
plt.show()

B. Optimize the switching Kalman silter (SKF) hyperparameters#
In this second step, we again use the Ray Tune library in order to optimize the SKF hyperparameters in order to 1) limit the false alarms, and 2) maximize the detectability of synthetic anomalies.
B.1. hyparameter space#
First, we need to define which hyperparameters will be optimized, and for each of them what is the range of values to be searched. In this example, we search for 1) the standard deviation of the process error that is added when transiting from the normal to the abnormal model, 2) the prior probability of having a regime switch fron normal to abnormal, and 3) the slope of the synthetic anomalies that will lead to a 50% detection rate.
[23]:
slope_upper_bound = 5e-2
slope_lower_bound = 1e-3
skf_param = {
"std_transition_error": [1e-6, 1e-3],
"norm_to_abnorm_prob": [1e-6, 1e-3],
"slope": [slope_lower_bound, slope_upper_bound],
}
B.2. SKF model as a function of parameters#
We create a function that will make our model depend on the hyperparameters defined above.
[24]:
def initialize_skf(skf_param, model_param: dict):
norm_model = Model.load_dict(model_param)
local_acceleration = LocalAcceleration()
pattern = LstmNetwork()
residual = WhiteNoise()
abnorm_model = Model(local_acceleration, pattern, residual)
skf = SKF(
norm_model=norm_model,
abnorm_model=abnorm_model,
std_transition_error=skf_param["std_transition_error"],
norm_to_abnorm_prob=skf_param["norm_to_abnorm_prob"],
)
return skf
B.3. Optimizer#
We instantiate the optimizer and run it over 40 optimization runs, each involving the generation of 50 synthetic anomalies. This procedure will return the optimized hyperparameter values in order to maximize the detectability of the smallest anomalies having a detection rate of 50% while not causing any false detection.
[25]:
skf_optimizer = SKFOptimizer(
initialize_skf=initialize_skf,
param_space=skf_param,
model_param=model_optim_dict,
data=train_data,
num_synthetic_anomaly=50,
num_optimization_trial=40,
)
skf_optimizer.optimize()
# 1/40 - Metric: 1.104 - Detection rate: 1.00 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 7.548375686913468e-05, 'norm_to_abnorm_prob': 0.00014194611119770737, 'slope': 0.020780768880331115}
# 2/40 - Metric: 1.046 - Detection rate: 1.00 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 7.884620831495087e-05, 'norm_to_abnorm_prob': 4.4572244275214544e-05, 'slope': 0.009221305128512295}
# 3/40 - Metric: 0.564 - Detection rate: 0.54 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 5.543375653345208e-05, 'norm_to_abnorm_prob': 1.1636534202242209e-05, 'slope': 0.004745812548808954}
# 4/40 - Metric: 1.074 - Detection rate: 1.00 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 2.4367341761031763e-05, 'norm_to_abnorm_prob': 0.0003547175103785403, 'slope': 0.014737126570637495}
# 5/40 - Metric: 0.807 - Detection rate: 0.76 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 0.0004001552683176568, 'norm_to_abnorm_prob': 3.512866813302457e-05, 'slope': 0.009453871625020283}
# 6/40 - Metric: 0.806 - Detection rate: 0.72 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 1.3582386508506395e-05, 'norm_to_abnorm_prob': 1.984070662180288e-05, 'slope': 0.01714065163419181}
# 7/40 - Metric: 2.021 - Detection rate: 0.00 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 2.918661013705935e-05, 'norm_to_abnorm_prob': 3.1912028427566147e-06, 'slope': 0.0041644253559859476}
# 8/40 - Metric: 2.099 - Detection rate: 0.26 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 6.766258672263214e-06, 'norm_to_abnorm_prob': 5.284496651254192e-06, 'slope': 0.019785155363330025}
# 9/40 - Metric: 2.168 - Detection rate: 0.00 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 1.0613238229229605e-06, 'norm_to_abnorm_prob': 3.420023630708142e-06, 'slope': 0.0335665478049676}
# 10/40 - Metric: 2.011 - Detection rate: 0.00 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 6.85790392761211e-05, 'norm_to_abnorm_prob': 5.255344050612447e-06, 'slope': 0.0021384302252757214}
# 11/40 - Metric: 2.018 - Detection rate: 0.10 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 0.0009726800316985949, 'norm_to_abnorm_prob': 1.887192932461192e-05, 'slope': 0.0035479862014793128}
# 12/40 - Metric: 2.026 - Detection rate: 0.00 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 1.3854224889392819e-05, 'norm_to_abnorm_prob': 3.0865455756059558e-06, 'slope': 0.005146581891777369}
# 13/40 - Metric: 2.006 - Detection rate: 0.00 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 0.0009669465975151814, 'norm_to_abnorm_prob': 0.00094970732148804, 'slope': 0.0011156751180375}
# 14/40 - Metric: 2.026 - Detection rate: 0.00 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 5.718419614758865e-06, 'norm_to_abnorm_prob': 1.0080889614081176e-06, 'slope': 0.0051023451138064385}
# 15/40 - Metric: 2.005 - Detection rate: 0.00 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 4.164182291443161e-06, 'norm_to_abnorm_prob': 1.360120845536992e-05, 'slope': 0.001093073057549373}
# 16/40 - Metric: 2.250 - Detection rate: 0.00 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 3.8932716460304884e-06, 'norm_to_abnorm_prob': 1.0270506727217617e-06, 'slope': 0.04999742040136994}
# 17/40 - Metric: 2.010 - Detection rate: 0.00 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 0.00017505673983552155, 'norm_to_abnorm_prob': 7.065635109832837e-05, 'slope': 0.0020188665932831195}
# 18/40 - Metric: 1.225 - Detection rate: 1.00 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 0.00015846204612173466, 'norm_to_abnorm_prob': 7.267743102607682e-05, 'slope': 0.044945575250066094}
# 19/40 - Metric: 1.065 - Detection rate: 1.00 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 0.00016435930406128048, 'norm_to_abnorm_prob': 0.00013139956818144176, 'slope': 0.013080473238147145}
# 20/40 - Metric: 2.057 - Detection rate: 0.00 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 1.5132430978750072e-06, 'norm_to_abnorm_prob': 1.0444860226789671e-05, 'slope': 0.01132058475841495}
# 21/40 - Metric: 2.051 - Detection rate: 0.00 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 1.770362204426584e-06, 'norm_to_abnorm_prob': 1.1379169803735443e-05, 'slope': 0.010150232091134363}
# 22/40 - Metric: 2.036 - Detection rate: 0.00 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 1.4231156755483033e-05, 'norm_to_abnorm_prob': 1.0840321536039143e-05, 'slope': 0.007221524605123355}
# 23/40 - Metric: 2.031 - Detection rate: 0.00 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 1.2310711131224613e-05, 'norm_to_abnorm_prob': 2.0678272742588518e-05, 'slope': 0.006283986990080973}
# 24/40 - Metric: 1.112 - Detection rate: 1.00 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 0.00044706826034710673, 'norm_to_abnorm_prob': 2.5107002668741186e-05, 'slope': 0.02242649578493224}
# 25/40 - Metric: 1.120 - Detection rate: 1.00 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 0.00032596560130721393, 'norm_to_abnorm_prob': 3.157894706390093e-05, 'slope': 0.024080217793642215}
# 26/40 - Metric: 1.098 - Detection rate: 1.00 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 0.00047054598223676615, 'norm_to_abnorm_prob': 2.9919945210974092e-05, 'slope': 0.019632232706018434}
# 27/40 - Metric: 2.015 - Detection rate: 0.00 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 0.00039811452921051946, 'norm_to_abnorm_prob': 4.14904299396465e-05, 'slope': 0.002994981451818317}
# 28/40 - Metric: 2.015 - Detection rate: 0.24 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 5.079935984657596e-05, 'norm_to_abnorm_prob': 5.35277656533547e-05, 'slope': 0.0029240924268901405}
# 29/40 - Metric: 2.015 - Detection rate: 0.18 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 4.862066664443989e-05, 'norm_to_abnorm_prob': 7.366675591893923e-05, 'slope': 0.003054715563299041}
# 30/40 - Metric: 0.873 - Detection rate: 0.84 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 4.691907956995337e-05, 'norm_to_abnorm_prob': 8.34193409475133e-05, 'slope': 0.006672952321015415}
# 31/40 - Metric: 0.680 - Detection rate: 0.64 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 3.6753336362871745e-05, 'norm_to_abnorm_prob': 6.285513310224048e-06, 'slope': 0.007939482643674445}
# 32/40 - Metric: 1.039 - Detection rate: 1.00 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 9.748761777962316e-05, 'norm_to_abnorm_prob': 0.0002022909231543896, 'slope': 0.007834113867160545}
# 33/40 - Metric: 1.149 - Detection rate: 1.00 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 2.191507547642465e-05, 'norm_to_abnorm_prob': 6.854543731667889e-06, 'slope': 0.029701319029460858}
# 34/40 - Metric: 1.045 - Detection rate: 1.00 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 9.331087849232703e-05, 'norm_to_abnorm_prob': 6.1425563234950555e-06, 'slope': 0.008961281839588402}
# 35/40 - Metric: 1.002 - Detection rate: 0.92 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 2.0061919261242e-05, 'norm_to_abnorm_prob': 8.211261003554903e-06, 'slope': 0.016483097735057527}
# 36/40 - Metric: 2.044 - Detection rate: 0.00 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 8.720446407859025e-06, 'norm_to_abnorm_prob': 2.086305317470769e-06, 'slope': 0.00878089941670478}
# 37/40 - Metric: 2.079 - Detection rate: 0.36 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 9.335235826652898e-06, 'norm_to_abnorm_prob': 1.6893588628217497e-05, 'slope': 0.01582002036127946}
# 38/40 - Metric: 1.001 - Detection rate: 0.94 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 3.5538000654214914e-05, 'norm_to_abnorm_prob': 1.6873240194386495e-06, 'slope': 0.012292715351676838}
# 39/40 - Metric: 1.043 - Detection rate: 0.98 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 3.2365427441018736e-05, 'norm_to_abnorm_prob': 1.6044953628897597e-05, 'slope': 0.012581759631730768}
2025-06-03 11:41:30,059 INFO tune.py:1009 -- Wrote the latest version of all result files and experiment state to '/Users/vuongdai/ray_results/SKF_optimizer' in 0.0118s.
# 40/40 - Metric: 1.019 - Detection rate: 0.96 - False rate: 0.00 - False alarm in train: No - Parameter: {'std_transition_error': 3.4449162429654994e-05, 'norm_to_abnorm_prob': 4.5906336736635114e-05, 'slope': 0.011877575626329928}
-----
Optimal parameters at trial #3: {'std_transition_error': 5.543375653345208e-05, 'norm_to_abnorm_prob': 1.1636534202242209e-05, 'slope': 0.004745812548808954}
-----
B.4. Get optimal SKF#
Set the optimized hyperparameters into the model in order to process the data from the test set.
[26]:
# Get optimal model
skf_param = skf_optimizer.get_best_param()
skf_optim = initialize_skf(skf_param, model_optim_dict)
B.5. Anomaly detection#
Once the training is complete, we perform the changepoint detection by using the SKF filter
or smoother
. The filter
represents the results obtained during online data processing and the smoother
those obtained during offline processing.
[27]:
filter_marginal_abnorm_prob, states = skf_optim.filter(data=all_data)
smooth_marginal_abnorm_prob, states = skf_optim.smoother()
B.6. Hidden states and proability of anomalies#
We represent the time-series decomposition visually where the raw data is overlaid with the baseline hidden state represented by the level. The rate of change of the baseline is characterized by the trend and acceleration hidden states. The recurrent pattern is captured by the LSTM neural network. The posterior estimate for the residuals are displayed for the white noise component. Finaly, the probability of anomaly obtained from the SKF indicated the possible location of change point from a stationnary regime to a trend-stationnary one.
Note that in this second optimization phase the results now extend into the test set.
[28]:
fig, ax = plot_skf_states(
data_processor=data_processor,
states=states,
model_prob=filter_marginal_abnorm_prob,
)
ax[0].axvline(
x=data_processor.data.index[time_anomaly],
color="r",
linestyle="--",
)
fig.suptitle("SKF hidden states", fontsize=10, y=1)
ax[-1].set_xlabel("MM-DD")
plt.show()
