Multi-Frequency Selection

Multi-frequency selection followed by mutation is what makes Multiple-Frequencies Population-Based Training (MF-PBT, Doulazmi et al.) possible: the selection operator reshapes the population and nominates the agents to perturb, and the shared mutation step then perturbs them.

Multi-frequency selection replaces tournament selection. The population is split into n_subpopulations subpopulations that each evolve at their own frequency: a slow subpopulation only exploits/explores every few evaluation cycles. Preserving several evolution frequencies keeps the greedy short-horizon behaviour of single-frequency PBT from collapsing the whole population onto one hyperparameter schedule.

Within each subpopulation, agents are ranked by fitness into four brackets: winners, survivors, open-for-migration and losers. Each evolution step keeps the winners and survivors unchanged and replaces every loser with a clone of a winner; migration then fills the open-for-migration slots with stronger agents from other subpopulations. When a migrant moves from a faster subpopulation to a slower subpopulation, only the network weights are transferred. However, if an agent moves from a slower subpopulation to a faster one, a full clone is migrated. We use asymmetric migration so the long-horizon subpopulations only inherit good weights and never a potentially over-optimized hyperparameter schedule, while collapsed faster subpopulations get a full clone to restore diversity and escape convergence traps.

Selection returns the indices of those winner-clones, which are finally perturbed by the shared Mutations.mutation() step, called with its indices argument so only the clones are mutated (see Mutation).

The class MultiFrequencySelection implements the multi-frequency selection operator needed in MF-PBT; its select() returns the global elite, the evolved population, and the winner-clone indices to mutate. MF-PBT supports an accelerator and both the classic-RL and the LLM finetuning algorithms.

from agilerl.hpo.multi_frequency import MultiFrequencySelection

multi_frequency_selection = MultiFrequencySelection(
    population_size=16,                  # 16 agents, split into...
    n_subpopulations=2,                  # ...two subpopulations of 8 agents each
    evolution_frequency_ratios=[1, 5],   # One fast, one 5x slower subpopulation
    n_winners=2,                         # Bracket sizes must sum to
    n_survivors=0,                       # population_size // n_subpopulations (= 8)
    n_open_for_migration=2,
    n_losers=4,
    seed=42,                             # Derived from the run's global seed
)

When to use it. MF-PBT is most effective with 16 or more agents (as recommended by the paper), where there is room for several subpopulations at different frequencies; its slower subpopulations make it more robust to premature convergence than tournament selection. The trade-offs are that it needs a larger population to be worthwhile and exposes more configuration parameters. For small populations, prefer Tournament Selection.

Configuring from a manifest

In a training manifest, multi-frequency and tournament selection share the single selection_strategy block (also accepted under its former name, tournament_selection), discriminated by a strategy field that defaults to tournament. Set strategy: multi_frequency and provide the subpopulation layout in the same block. As with tournament selection, training.pop_size is the mandatory population size; MF-PBT reads it and derives the per-subpopulation size as pop_size // n_subpopulations:

selection_strategy:
  strategy: multi_frequency
  n_subpopulations: 2                 # >= 2
  evolution_frequency_ratios: [1, 5]  # strictly increasing ints >= 1, one per subpop
  n_winners: 2                        # >= 1
  n_survivors: 0                      # >= 0
  n_open_for_migration: 2             # >= 1
  n_losers: 4                         # >= 1

training:
  pop_size: 16                        # >= 6, a multiple of n_subpopulations

n_subpopulations defaults to 2. The bracket sizes must sum to the subpopulation size pop_size // n_subpopulations; when omitted they default to round(0.25 * subpop) winners and open-for-migration agents, 0 survivors and the remainder as losers, and evolution_frequency_ratios defaults to [1, 5, 10, …]. The recommended configuration is the one shown above: 16 agents in 2 subpopulations of 8, split into 2 winners / 0 survivors / 2 open-for-migration / 4 losers with frequency ratios [1, 5].

Parameters

class agilerl.hpo.multi_frequency.MultiFrequencySelection(population_size: int, n_subpopulations: int = 2, evolution_frequency_ratios: list[int] | None = None, n_winners: int | None = None, n_survivors: int | None = None, n_open_for_migration: int | None = None, n_losers: int | None = None, seed: int | None = None)

The multi-frequency selection operator needed in MF-PBT.

Parameters:
  • population_size (int) – Total number of agents in the population (>= 6, a multiple of n_subpopulations, and large enough that population_size // n_subpopulations >= 3).

  • n_subpopulations (int) – Number of subpopulations (>= 2; migration has nothing to draw from with a single subpopulation).

  • evolution_frequency_ratios (list[int] | None) – Per-subpopulation evolution-frequency ratios delta_i (strictly increasing integers, delta_i >= 1; one per subpopulation). None or [] -> [1, 5, 10, …].

  • n_winners (int | None) – Agents in the winners bracket (>= 1; None -> round(0.25 * subpopulation_size)).

  • n_survivors (int | None) – Agents in the survivors bracket (>= 0; None -> 0).

  • n_open_for_migration (int | None) – Agents in the open-for-migration bracket (>= 1; None -> round(0.25 * subpopulation_size)).

  • n_losers (int | None) – Agents in the losers bracket (>= 1; None -> the remainder subpopulation_size - n_winners - n_survivors - n_open_for_migration).

  • seed (int | None) – Seed for the reproducible winner-clone selection in _clone_winners_over_losers(), derived from the run’s global seed. None leaves the RNG unseeded.

Raises:

ValueError – If population_size < 6, population_size is not a multiple of n_subpopulations, population_size // n_subpopulations < 3, n_subpopulations < 2, n_winners < 1, n_survivors < 0, n_open_for_migration < 1, n_losers < 1, the four brackets do not sum to population_size // n_subpopulations, or the frequency ratios are not n_subpopulations strictly-increasing integers >= 1.

select(population: list[EvolvableAlgorithmProtocol]) tuple[EvolvableAlgorithmProtocol, list[EvolvableAlgorithmProtocol], list[int]]

Select the agents to be migrated and mutated during an MF-PBT evolution cycle.

Parameters:

population (PopulationType) – The whole population.

Returns:

(elite, population, indices_to_mutate). The pre-evolution global elite, the evolved population with migrants and clones, and the indices of the winner clones to be perturbed.

Return type:

tuple[EvolvableAlgorithmProtocol, PopulationType, list[int]]