Skip to contentSkip to Content
DocsUser GuideOptimizerAGILE Overview

AGILE Overview

AGILE (Adaptive Gradient-Informed Levy Exploration) is the built-in hyperparameter optimizer in SPIRES. It automates the search for optimal reservoir configurations by combining global exploration via Levy flights with local refinement via gradient-informed search, evaluated under a multi-fidelity budget scheme that accelerates the optimization process.

The Hyperparameter Search Problem

A SPIRES reservoir has many configurable parameters: number of neurons, spectral radius, connectivity, neuron type, fractional order, regularization strength, and more. The performance of the reservoir on a given task depends on all of these jointly, and the performance landscape is typically non-convex, multi-modal, and expensive to evaluate (each configuration requires building a reservoir, driving it with data, training, and evaluating).

Manual tuning is tedious and unlikely to find good configurations. Grid search is exponentially expensive in the number of parameters. Random search is better but wasteful. AGILE provides a principled, automated alternative.

Algorithm Overview

AGILE operates in three phases at each iteration:

1. Levy Flight Exploration

The algorithm samples new candidate configurations by applying Levy-distributed perturbations to the current best configuration. Levy flights are random walks where the step size is drawn from a heavy-tailed distribution:

P()1β,0<β2P(\ell) \sim \ell^{-1-\beta}, \qquad 0 < \beta \leq 2

The heavy tail means that most steps are small (local exploration), but occasional large steps (long jumps) enable the algorithm to escape local optima and explore distant regions of the configuration space. This is more efficient than uniform random sampling because it naturally balances exploitation (small steps near the current best) and exploration (large jumps to new regions).

After the Levy flight proposes candidate configurations, AGILE performs a local search around the most promising candidates. This uses finite-difference gradient estimates to move toward local optima in the performance landscape. The gradient search refines the coarse solutions found by the Levy flight into high-quality configurations.

3. Multi-Fidelity Evaluation

Not all candidate configurations are evaluated at full fidelity. AGILE uses a sequence of budgets with increasing cost and accuracy:

  • Low-fidelity budgets: Use a small fraction of the data, fewer random seeds, and shorter time limits. These are cheap to evaluate and quickly eliminate poor configurations.
  • High-fidelity budgets: Use more data, more seeds, and longer time limits. These are expensive but provide accurate performance estimates.

Configurations are promoted from one budget level to the next only if they perform well at the current level. This successive halving strategy ensures that computational effort is focused on the most promising configurations.

SPIRES API

The optimizer is invoked via spires_optimize():

int spires_optimize( const spires_reservoir_config *base_config, const struct spires_opt_budget *budgets, int num_budgets, const struct spires_opt_score *score, struct spires_opt_result *out, const double *input_series, const double *target_series, size_t series_length );

Parameters:

ParameterDescription
base_configStarting configuration; AGILE explores around this
budgetsArray of budget structs defining the multi-fidelity schedule
num_budgetsNumber of budget levels
scoreScoring configuration (metric, variance penalty, cost penalty)
outOutput struct to receive the best configuration found
input_seriesTraining input data
target_seriesTraining target data
series_lengthLength of the time series

Returns: 0 on success, non-zero on failure.

Complete Example

#include <spires.h> #include <stdio.h> int main(void) { /* Base configuration to optimize around */ spires_reservoir_config base = { .num_neurons = 300, .num_inputs = 1, .num_outputs = 1, .spectral_radius = 0.9, .ei_ratio = 0.8, .input_strength = 0.1, .connectivity = 0.1, .dt = 1.0, .connectivity_type = SPIRES_CONN_RANDOM, .neuron_type = SPIRES_NEURON_FLIF_GL, .neuron_params = NULL, }; /* Multi-fidelity budget schedule: coarse to fine */ struct spires_opt_budget budgets[] = { { .data_fraction = 0.2, .num_seeds = 2, .time_limit_sec = 30.0 }, { .data_fraction = 0.5, .num_seeds = 3, .time_limit_sec = 120.0 }, { .data_fraction = 1.0, .num_seeds = 5, .time_limit_sec = 600.0 }, }; int num_budgets = 3; /* Scoring: AUROC with variance and cost penalties */ struct spires_opt_score score = { .lambda_var = 0.5, .lambda_cost = 0.1, .metric = SPIRES_METRIC_AUROC, }; /* Prepare training data */ /* (assume input_data and target_data are populated) */ size_t series_len = 5000; /* Run optimization */ struct spires_opt_result result; int ret = spires_optimize( &base, budgets, num_budgets, &score, &result, input_data, target_data, series_len ); if (ret == 0) { printf("Optimization succeeded!\n"); printf("Best score: %.4f\n", result.best_score); printf("Metric mean: %.4f\n", result.metric_mean); printf("Metric std: %.4f\n", result.metric_std); printf("Best log10(ridge): %.2f\n", result.best_log10_ridge); printf("Best num_neurons: %d\n", result.best_config.num_neurons); printf("Best spec. radius: %.3f\n", result.best_config.spectral_radius); /* Use the optimized configuration */ spires_reservoir *r = NULL; spires_status s = spires_reservoir_create(&result.best_config, &r); if (s == SPIRES_OK) { double lambda = pow(10.0, result.best_log10_ridge); spires_train_ridge(r, input_data, target_data, series_len, lambda); /* ... inference ... */ spires_reservoir_destroy(r); } } else { fprintf(stderr, "Optimization failed\n"); } return 0; }

What AGILE Searches Over

The optimizer explores the following dimensions of the configuration space:

ParameterSearch Strategy
spectral_radiusContinuous; perturbed by Levy flight
ei_ratioContinuous; bounded to (0,1)(0, 1)
input_strengthContinuous; log-scale perturbation
connectivityContinuous; bounded to (0,1)(0, 1)
Ridge parameter λ\lambdaContinuous; searched in log-space
neuron_typeDiscrete; sampled from enabled types
connectivity_typeDiscrete; sampled from enabled types
Fractional order α\alphaContinuous; bounded to (0,1](0, 1]

The num_neurons, num_inputs, and num_outputs are held fixed at the values specified in the base configuration — they define the problem structure and are not searched.

Termination

AGILE terminates when:

  • The time limit of the current budget level is reached, or
  • A convergence criterion is met (the best score has not improved by more than a threshold over a specified number of iterations), or
  • All budget levels have been exhausted.

The result struct contains the best configuration found across all budget levels.

Practical Recommendations

  1. Start with a reasonable base configuration: AGILE explores around the base. A completely random starting point wastes early budget levels.

  2. Use at least 3 budget levels: The multi-fidelity scheme is most effective with 3—5 levels. Fewer levels reduce the acceleration benefit; more levels add overhead.

  3. Set generous time limits: The final budget level should have enough time for thorough evaluation. Under-budgeting the last level may cause AGILE to stop before finding the true optimum.

  4. Match the metric to your task: Use AUROC for balanced classification, AUPRC for imbalanced data. See Scoring Metrics.

  5. Tune variance penalty: If you need robust configurations that perform consistently across seeds, increase lambda_var. If you want the absolute best single-seed performance, set it to zero.


← Online Delta Rule | Budget Configuration →

Last updated on