fitness_postprocessor (macula_tweann v0.18.1)
View SourceFitness postprocessing for multi-objective optimization.
This module provides fitness transformation functions to apply domain-specific knowledge or constraints to raw fitness values before selection. Common use cases:
- Penalize complex solutions (parsimony pressure) - Normalize fitness across objectives - Apply Pareto dominance for multi-objective optimization - Age-based fitness adjustments
Postprocessing Strategies
Size Proportional: - Penalize agents with more neurons/connections - Encourages simpler solutions (Occam's Razor) - Helps prevent bloat in network topology - Formula: AdjustedFitness = RawFitness - (Size * Penalty)
Normalize: - Scale fitness to [0, 1] range within population - Prevents one objective from dominating in multi-objective case - Useful when objectives have different scales - Formula: Normalized = (F - Min) / (Max - Min)
Pareto Dominance: - Assign ranks based on Pareto dominance relationships - Non-dominated solutions get rank 1 (best) - Enables true multi-objective optimization - Returns Pareto fronts as fitness ranks
Multi-Objective Fitness
All functions work with fitness vectors [F1, F2, ...]. Single-objective case is just a vector with one element [F1].
Summary
Functions
Normalize fitness values to [0, 1] range.
Assign Pareto ranks based on dominance relationships.
Apply size-proportional fitness penalty (parsimony pressure).
Functions
Normalize fitness values to [0, 1] range.
Scales each fitness objective independently to the range [0, 1] based on the min and max values in the population. This prevents objectives with large magnitudes from dominating selection.
If all agents have the same fitness for an objective (no variance), that objective is set to 0.5 for all agents.
Example: AgentFitnesses = [ {agent1, [100.0, 10.0]}, {agent2, [50.0, 20.0]}, {agent3, [0.0, 15.0]} ] Result = [ {agent1, [1.0, 0.0]}, % Max on F1, min on F2 {agent2, [0.5, 1.0]}, % Mid on F1, max on F2 {agent3, [0.0, 0.5]} % Min on F1, mid on F2 ]
Assign Pareto ranks based on dominance relationships.
Implements fast non-dominated sorting (NSGA-II algorithm) to assign Pareto ranks to agents. Lower rank is better (rank 1 = non-dominated).
Agent A dominates Agent B if: - A is no worse than B in all objectives, AND - A is strictly better than B in at least one objective
The result is a single-objective fitness [Rank] where Rank indicates which Pareto front the agent belongs to.
Example: AgentFitnesses = [ {agent1, [100.0, 10.0]}, % Good on F1, poor on F2 {agent2, [50.0, 50.0]}, % Balanced {agent3, [10.0, 100.0]}, % Poor on F1, good on F2 {agent4, [30.0, 30.0]} % Dominated by agent2 ] Result = [ {agent1, [1.0]}, % Front 1 (non-dominated) {agent2, [1.0]}, % Front 1 (non-dominated) {agent3, [1.0]}, % Front 1 (non-dominated) {agent4, [2.0]} % Front 2 (dominated by agent2) ]
Apply size-proportional fitness penalty (parsimony pressure).
Penalizes agents with larger networks to encourage compact solutions. This helps prevent bloat where networks grow unnecessarily complex without improving performance.
The penalty is calculated as: Size * PenaltyFactor where Size is the number of neurons in the agent's network.
Example: AgentFitnesses = [ {agent1, [100.0]}, {agent2, [100.0]}, {agent3, [80.0]} ] PenaltyFactor = 0.1
Assuming agent1 has 10 neurons, agent2 has 5 neurons, agent3 has 15 neurons: Result = [ {agent1, [99.0]}, % 100.0 - (10 * 0.1) {agent2, [99.5]}, % 100.0 - (5 * 0.1) {agent3, [78.5]} % 80.0 - (15 * 0.1) ]