Skip to main content

Online Deterministic Annealing (ODA)

A general-purpose learning model designed to meet the needs of Cyber-Physical Systems applications in which data and computational/communication resources are limited, and robustness and interpretability are prioritized.

An online prototype-based learning algorithm based on annealing optimization that is formulated as an recursive gradient-free stochastic approximation algorithm.

An interpretable and progressively growing competitive-learning neural network model.

A hierarchical, multi-resolution learning model.

Applications: Real-time clustering, classification, regression, (state-aggregation) reinforcement learning, hybrid system identification, graph partitioning, leader detection.

pip install

pip install online-deterministic-annealing

Current Version:

version = "1.0.1"

Dependencies:

dependencies = [
"numpy>=2.2.6",
"numba>=0.61.2",
"matplotlib>=3.10.3",
"scipy>=1.16.2",
"shapely>=2.1.2",
]
requires-python = ">=3.8"

License:

license = "MIT"

Demo

https://github.com/MavridisChristos/online-deterministic-annealing/tests/demo.py

Usage

The ODA architecture is coded in the ODA class inside online_deterministic_annealing/oda.py:

from online_deterministic_annealing.oda import ODA

Regarding the data format, they need to be a list of (n) lists of (m=1) d-vectors (np.arrays):

train_data_x = [[np.array], [np.array], [np.array], ...]
train_data_y = [np.array, np.array, np.array, ...] | make sure it is np.atleast1d()

The simplest way to train ODA on a dataset is:

clf = ODA()

clf.fit(train_data_x, train_data_y, train_labels,
        test_data_x, test_data_y, test_labels)

hatX, hatY, hatLabel, hatMode = clf.predict(test_data_x)

Notice that a dataset is not required, and one can train ODA using observations one at a time as follows:

while stopping_criterion:
    
    # Stop in the next converged configuration
    tl = len(clf.timeline)
    while len(clf.timeline)==tl and not clf.trained:
        train_datum_x, train_datum_y, train_label = system.observe()
        clf.train(train_datum_x, train_datum_y, train_label)

hatX, hatY, hatLabel, hatMode = clf.predict(test_data_x)

Clustering

For clustering set:

train_data_x = [[np.array], [np.array], [np.array], ...]
train_data_y = [np.atleast1d(0) for td in train_data_x]
train_labels = [0 for td in train_data_x] 

Classification

For classification, the labels need to be a list of (n) labels, preferably integer numbers (for numba.jit)

train_data_x = [[np.array], [np.array], [np.array], ...]
train_data_y = [np.atleast1d(0) for td in train_data_x]
train_labels = [ int, int , int, ...]

Regression

For regression (piece-wise constant function approximation) replace:

observe_xy = 0.9 | set a value between [0,1] to give more weight to x or y error
train_data_x = [[np.array], [np.array], [np.array], ...]
train_data_y = [np.array, np.array, np.array, ...]
train_labels = [0 for td in train_data_x] 

Simultaneous Classification and Regression (Hybrid Learning)

train_data_x = [[np.array], [np.array], [np.array], ...]
train_data_y = [np.array, np.array, np.array, ...]
train_labels = [ int, int , int, ...] 

Prediction

hatX, hatY, hatLabel, hatMode = clf.predict(test_data_x)
error_clustering, error_regression, error_classification = clf.score(data_x, data_y, labels)

All Parameters

Data

- train_data_x
    # Single layer: [[np.array], [np.array], [np.array], ...]
    # Multiple Layers/Resolutions: [[np.array, np.array, ...], [np.array, np.array, ...], [np.array, np.array, ...], ...]
- train_data_y
    # [ np.array, np.array, np.array, ... ] (np.atleast1d())
- train_labels
    # [ 0, 0, 0, ... ] (zero values for clustering)
    # [ int, int , int, ... ] (int values for classification with numba.jit)
- observe_xy = [0]
    # value in [0,1]. 0 considers only x values, 1 considers only y values, 0.5 considers bothe x,y equally, etc..

Bregman divergence

- Bregman_phi = ['phi_Eucl']
    # Defines Bregman divergence d_phi. 
    # Values in {'phi_Eucl', 'phi_KL'} (Squared Euclidean distance, KL divergence)

Termination Criteria

- Kmax = [32]
    # Limit in node's children. After that stop growing
- timeline_limit = 1e6
    # Limit in the number of convergent representations. (Developer Mode) 
- error_type = [0] 
    # 0:Clustering, 1:Regression, 2:Classification
- error_threshold = [0.01] 
    # Desired training error. 
- error_threshold_count = [3] 
    # Stop when reached 'error_threshold_count' times

Temperature Schedule

- Tmax = [0.9] 
- Tmin = [1e-2]
    # lambda max min values in [0,1]. T = (1-lambda)/lambda
- gamma_steady = [0.95] 
    # T' = gamma * T
- gamma_schedule = [[0.8,0.8]] 
    # Initial updates can be set to reduce faster

Tree Structure

- node_id = [0] 
    # Tree/branch parent node
- parent = None 
    # Pointer used to create tree-structured linked list

Regularization: Perturbation and Merging

- lvq = [0] 
    # Values in {0,1,2,3}
    # 0:ODA update
    # 1:ODA until Kmax. Then switch to 2:soft clustering with no perturbation/merging 
    # 2:soft clustering with no perturbation/merging 
    # 3: LVQ update (hard-clustering) with no perturbation/merging
- px_cut = [1e-5] 
    # Parameter e_r: threshold to find idle codevectors
- perturb_param = [1e-1] 
    # Perturb (dublicate) existing codevectors 
    # Parameter delta = d_phi(mu, mu+'delta')/T: 
- effective_neighborhood = [1e-0] 
    # Threshold to find merged (effective) codevectors
    # Parameter e_n = d_phi(mu, mu+'effective_neighborhood')/T

Convergence

- em_convergence = [1e-1]
- convergence_counter_threshold = [5]
    # Convergece when d_phi(mu',mu) < e_c * (1+bb_init)/(1+bb) for 'convergence_counter_threshold' times
    # Parameter e_c =  d_phi(mu, mu+'em_convergence')/T
- convergence_loops = [0]
    # Custom number of loops until convergence is considered true (overwrites e_c) (Developer mode)
- stop_separation = [1e9-1]
    # After 'stop_separation' loops, gibbs probabilities consider all codevectors regardless of class 
- bb_init = [0.9]
    # Initial bb value for stochastic approximation stepsize: 1/(bb+1)
- bb_step = [0.9]
    # bb+=bb_step

Verbose

- verbose = 2 
    # Values in {0,1,2}    
    # 0: don't show score
    # 1: show score only on tree node splits 
    # 2: show score after every SA convergence 

Numba Jit

- jit = True
    # Using jit/python for Bregman divergences

Model Parameters

Tree Structure

- self.id
- self.parent
- self.children

Status

- self.K
- self.T

- self.perturbed = False
- self.converged = False
- self.trained = False

Variables

- self.x 
- self.y 
- self.labels 
- self.classes
- self.parameters 
- self.model 
- self.optimizer 

- self.px 
- self.sx 

History

- self.myK 
- self.myT 
- self.myX 
- self.myY 
- self.myLabels 
- self.myParameters
- self.myModels
- self.myOptimizers

- self.myTrainError 
- self.myTestError 
- self.myLoops 
- self.myTime 
- self.myTreeK 
- self.myTreeLoops 

Convergence Parameters (development mode)

- self.e_p
- self.e_n
- self.e_c
- self.px_cut 
- self.lvq 
- self.convergence_loops 
- self.error_type 
- self.error_threshold 
- self.error_threshold_count 
- self.convergence_counter_threshold 
- self.bb_init
- self.bb_step 
- self.separate 
- self.stop_separation 
- self.bb 
- self.sa_steps 

Tree Structure and Multiple Resolutions

For multiple resolutions every parameter becomes a list of m parameters. Example for m=2:

Tmax = [0.9, 0.09]
Tmin = [0.01, 0.0001]

The training data should look like this:

train_data = [[np.array, np.array, ...], [np.array, np.array, ...], [np.array, np.array, ...], ...]

Description of the Optimization Algorithm

The observed data are represented by a random variable $$X: \Omega \rightarrow S\subseteq \mathbb{R}^d$$ defined in a probability space $(\Omega, \mathcal{F}, \mathbb{P})$.

Given a similarity measure (which can be any Bregman divergence, e.g., squared Euclidean distance, Kullback-Leibler divergence, etc.) $$d:S\rightarrow \mathrm{ri}(S)$$ the goal is to find a set $\mu$ of $M$ codevectors in the input space such that the following average distortion measure is minimized:

$$ \min_\mu J(\mu) := E[\min_i d(X,\mu_i)] $$

For supervised learning, e.g., classification and regression, each codevector $\mu_i$ is associated with a label $c_i$ as well. This process is equivalent to finding the most suitable set of $M$ local constant models, and results in a

Piecewise-constant approximation (partition) of the input space $S$.

To construct a learning algorithm that progressively increases the number of codevectors $M$ as needed, we define a probability space over an infinite number of local models, and constraint their distribution using the maximum-entropy principle at different levels.

First we need to adopt a probabilistic approach, and a discrete random variable $$Q:S \rightarrow \mu$$ with countably infinite domain $\mu$.

Then we constraint its distribution by formulating the multi-objective optimization:

$$\min_\mu F(\mu) := (1-\lambda) D(\mu) - \lambda H(\mu)$$ where $$D(\mu) := E[d(X,Q)] =\int p(x) \sum_i p(\mu_i|x) d_\phi(x,\mu_i) ~\textrm{d}x$$ and $$H(\mu) := E[-\log P(X,Q)] =H(X) - \int p(x) \sum_i p(\mu_i|x) \log p(\mu_i|x) ~\textrm{d}x $$ is the Shannon entropy.

This is now a problem of finding the locations ${\mu_i}$ and the corresponding probabilities ${p(\mu_i|x)}:={p(Q=\mu_i|X=x)}$.

The Lagrange multiplier $\lambda\in[0,1]$ is called the temperature parameter

and controls the trade-off between $D$ and $H$. As $\lambda$ is varied, we essentially transition from one solution of the multi-objective optimization (a Pareto point when the objectives are convex) to another, and:

Reducing the values of $\lambda$ results in a bifurcation phenomenon that increases $M$ and describes an annealing process.

The above sequence of optimization problems is solved for decreasing values of $\lambda$ using a

Recursive gradient-free stochastic approximation algorithm.

The annealing nature of the algorithm contributes to avoiding poor local minima, offers robustness with respect to the initial conditions, and provides a means to progressively increase the complexity of the learning model through an intuitive bifurcation phenomenon.

Cite

If you use this work in an academic context, please cite the following:

@article{mavridis2023annealing,
    author = {Mavridis, Christos and Baras, John S.},
    journal = {IEEE Transactions on Automatic Control},
    title = {Annealing Optimization for Progressive Learning With Stochastic Approximation},
    year = {2023},
    volume = {68},
    number = {5},
    pages = {2862-2874},
    publisher = {IEEE},
}

@article{mavridis2023online,
    title = {Online deterministic annealing for classification and clustering},
    author = {Mavridis, Christos and Baras, John S},
    journal = {IEEE Transactions on Neural Networks and Learning Systems},
    year = {2023},
    volume = {34},
    number = {10},
    pages = {7125-7134},
    publisher = {IEEE},
}
  
@article{mavridis2022multi,
    title = {Multi-Resolution Online Deterministic Annealing: A Hierarchical and Progressive Learning Architecture},
    author = {Mavridis, Christos and Baras, John},
    journal = {arXiv preprint arXiv:2212.08189},
    year = {2024},
}

Author

Christos N. Mavridis, Ph.D.
Division of Decision and Control Systems
School of Electrical Engineering and Computer Science,
KTH Royal Institute of Technology
https://mavridischristos.github.io/
mavridis (at) kth.se c.n.mavridis (at) gmail.com

Metadata

Release files for online-deterministic-annealing 1.0.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for online-deterministic-annealing 1.0.2
File Size Uploaded
online_deterministic_annealing-1.0.2.tar.gz 838.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for online-deterministic-annealing 1.0.2
File Interpreter ABI Platform
online_deterministic_annealing-1.0.2-py3-none-any.whl Python 3 none any Details

Total release size: 858.8 kB

Release files / online_deterministic_annealing-1.0.2.tar.gz

Download URL online_deterministic_annealing-1.0.2.tar.gz
Size 838.6 kB
Tags Source
SHA-256 checksum
How to use checksums
2652e95adb4dbc252b0ce97b62ec61a146245323c8f8285031b10ed4bc600eb7
BLAKE2b-256 checksum
How to use checksums
947a235c9b3921dc33da9fef5d7f33a50ed09de1ac9647463b736b9c14c81c4d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.7

Release files / online_deterministic_annealing-1.0.2-py3-none-any.whl

Download URL online_deterministic_annealing-1.0.2-py3-none-any.whl
Size 20.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
835c645f09fb34cfac4a5249f0d33fdc92a57a4991a26193060769aecfb09942
BLAKE2b-256 checksum
How to use checksums
0741af93d75b342e1444084293abdc81f32d504f2ba7030183f7045733521146
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.7

Release history Release notifications | RSS feed

This release

1.0.2 This release

2 release files

1.0.1

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page