checkpoint: port evolutionary and population dynamics
This commit is contained in:
@@ -22,9 +22,9 @@
|
|||||||
namespace fces {
|
namespace fces {
|
||||||
|
|
||||||
// Controller input dimension (layer stats features)
|
// Controller input dimension (layer stats features)
|
||||||
constexpr int GENOME_INPUT_DIM = 9;
|
constexpr int GENOME_INPUT_DIM = 14;
|
||||||
// Controller hidden dimension
|
// Controller hidden dimension
|
||||||
constexpr int GENOME_HIDDEN_DIM = 16;
|
constexpr int GENOME_HIDDEN_DIM = 8;
|
||||||
// Controller output dimension: [multiplier, sign_gate, wd_mult]
|
// Controller output dimension: [multiplier, sign_gate, wd_mult]
|
||||||
constexpr int GENOME_OUTPUT_DIM = 3;
|
constexpr int GENOME_OUTPUT_DIM = 3;
|
||||||
// Total genome size: input->hidden weights + hidden biases + hidden->output weights + output biases
|
// Total genome size: input->hidden weights + hidden biases + hidden->output weights + output biases
|
||||||
|
|||||||
@@ -59,19 +59,55 @@ private:
|
|||||||
float grokking_coefficient_;
|
float grokking_coefficient_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FuzzySet represents a fuzzy set with a trapezoidal membership function.
|
||||||
|
*/
|
||||||
|
class FuzzySet {
|
||||||
|
public:
|
||||||
|
FuzzySet(std::string name, float a, float b, float c, float d) noexcept
|
||||||
|
: name_(std::move(name)), a_(a), b_(b), c_(c), d_(d) {}
|
||||||
|
|
||||||
|
float membership(float x) const noexcept {
|
||||||
|
if (!std::isfinite(x)) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
if (x <= a_ || x >= d_) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
if (x >= b_ && x <= c_) {
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
if (x > a_ && x < b_) {
|
||||||
|
float range = b_ - a_;
|
||||||
|
return (x - a_) / (range > 0.0f ? range : 1e-9f);
|
||||||
|
}
|
||||||
|
if (x > c_ && x < d_) {
|
||||||
|
float range = d_ - c_;
|
||||||
|
return (d_ - x) / (range > 0.0f ? range : 1e-9f);
|
||||||
|
}
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& name() const noexcept { return name_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string name_;
|
||||||
|
float a_;
|
||||||
|
float b_;
|
||||||
|
float c_;
|
||||||
|
float d_;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fitness metrics for multi-objective evaluation.
|
* Fitness metrics for multi-objective evaluation.
|
||||||
*/
|
*/
|
||||||
struct FitnessMetrics {
|
struct FitnessMetrics {
|
||||||
float loss_improvement = 0.0f;
|
float training_advantage = 0.0f;
|
||||||
float sparsity_score = 0.0f;
|
float validation_advantage = 0.0f;
|
||||||
float stability_score = 0.0f;
|
float grad_cv = 0.0f;
|
||||||
float novelty_score = 0.0f;
|
float sparsity_delta = 0.0f;
|
||||||
|
float consistency_gap = 0.0f;
|
||||||
/// Weighted combination
|
float stable_rank = 0.0f;
|
||||||
float total(float alpha = 0.7f, float beta = 0.3f) const {
|
|
||||||
return alpha * loss_improvement + beta * sparsity_score;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -79,12 +115,24 @@ struct FitnessMetrics {
|
|||||||
*/
|
*/
|
||||||
class FuzzyFitnessEvaluator {
|
class FuzzyFitnessEvaluator {
|
||||||
public:
|
public:
|
||||||
FitnessMetrics evaluate(
|
FuzzyFitnessEvaluator() noexcept;
|
||||||
float loss_before,
|
|
||||||
float loss_after,
|
float evaluate(const FitnessMetrics& metrics) const noexcept;
|
||||||
float sparsity = 0.0f,
|
|
||||||
float val_loss = -1.0f
|
private:
|
||||||
) const;
|
FuzzySet stability_set_;
|
||||||
|
FuzzySet train_set_;
|
||||||
|
FuzzySet val_set_;
|
||||||
|
FuzzySet sparsity_set_;
|
||||||
|
FuzzySet consistency_set_;
|
||||||
|
FuzzySet rank_set_;
|
||||||
|
|
||||||
|
float w_stability_ = 0.2f;
|
||||||
|
float w_train_ = 0.2f;
|
||||||
|
float w_val_ = 0.3f;
|
||||||
|
float w_sparsity_ = 0.1f;
|
||||||
|
float w_consistency_ = 0.2f;
|
||||||
|
float w_rank_ = 0.1f;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace fces
|
} // namespace fces
|
||||||
|
|||||||
@@ -74,10 +74,18 @@ private:
|
|||||||
float rollback_ema_ = 0.0f;
|
float rollback_ema_ = 0.0f;
|
||||||
int stagnation_counter_ = 0;
|
int stagnation_counter_ = 0;
|
||||||
float last_loss_velocity_ = 0.0f;
|
float last_loss_velocity_ = 0.0f;
|
||||||
|
float last_sparsity_ = 0.0f;
|
||||||
|
|
||||||
// RAM backup
|
// RAM backup
|
||||||
std::vector<torch::Tensor> ram_backup_;
|
std::vector<torch::Tensor> ram_backup_;
|
||||||
|
|
||||||
|
// Layer stats and group mappings
|
||||||
|
std::vector<std::vector<float>> layer_stats_;
|
||||||
|
std::vector<int> param_group_mapping_;
|
||||||
|
std::unique_ptr<SpectralSensor> spectral_sensor_;
|
||||||
|
SpectralController spectral_controller_;
|
||||||
|
float last_spectral_rank_ = 0.0f;
|
||||||
|
|
||||||
// Internal methods
|
// Internal methods
|
||||||
void gather_stats();
|
void gather_stats();
|
||||||
void apply_parameter_updates(const torch::Tensor& actions);
|
void apply_parameter_updates(const torch::Tensor& actions);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ namespace fces {
|
|||||||
*/
|
*/
|
||||||
class SpectralSensor {
|
class SpectralSensor {
|
||||||
public:
|
public:
|
||||||
|
SpectralSensor() = default;
|
||||||
explicit SpectralSensor(torch::nn::Module& model);
|
explicit SpectralSensor(torch::nn::Module& model);
|
||||||
|
|
||||||
/// Track a layer's weight tensor
|
/// Track a layer's weight tensor
|
||||||
|
|||||||
@@ -30,14 +30,21 @@ Genome Genome::clone() const {
|
|||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
|
|
||||||
FuzzyController::FuzzyController()
|
FuzzyController::FuzzyController()
|
||||||
: id(next_id_++), origin("random") {
|
: id(next_id_++), origin("genesis") {
|
||||||
genome.randomize(rng_);
|
genome.randomize(rng_);
|
||||||
// Bias output toward acceleration (V2.1 insight)
|
// Bias output toward acceleration (V2.1 insight)
|
||||||
// Set output biases (last GENOME_OUTPUT_DIM elements) to +2.0
|
// Set output biases (last GENOME_OUTPUT_DIM elements) to +2.0, -1.0, 0.0 with noise
|
||||||
constexpr int bias_start = GENOME_SIZE - GENOME_OUTPUT_DIM;
|
constexpr int bias_start = GENOME_SIZE - GENOME_OUTPUT_DIM;
|
||||||
for (int i = bias_start; i < GENOME_SIZE; ++i) {
|
std::normal_distribution<float> bias_noise(0.0f, 0.5f);
|
||||||
genome.weights[i] = 2.0f;
|
genome.weights[bias_start] = 2.0f + bias_noise(rng_);
|
||||||
}
|
genome.weights[bias_start + 1] = -1.0f + bias_noise(rng_);
|
||||||
|
genome.weights[bias_start + 2] = 0.0f + bias_noise(rng_);
|
||||||
|
|
||||||
|
// Initialize plasticity with variance to seed evolution
|
||||||
|
std::normal_distribution<float> plast_noise(0.0f, 0.05f);
|
||||||
|
genome.plasticity = std::max(0.01f, 0.1f + plast_noise(rng_));
|
||||||
|
genome.sigma_gene = 0.1f;
|
||||||
|
genome.gene_success.fill(1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
FuzzyController::FuzzyController(Genome genome)
|
FuzzyController::FuzzyController(Genome genome)
|
||||||
@@ -61,46 +68,111 @@ torch::Tensor FuzzyController::decide_update(
|
|||||||
const float* w = genome.weights.data();
|
const float* w = genome.weights.data();
|
||||||
|
|
||||||
// Layer 1: input -> hidden
|
// Layer 1: input -> hidden
|
||||||
const float* W1 = w; // [GENOME_INPUT_DIM x GENOME_HIDDEN_DIM]
|
const float* W1 = w; // [(GENOME_INPUT_DIM + 1) x GENOME_HIDDEN_DIM]
|
||||||
const float* b1 = w + (GENOME_INPUT_DIM * GENOME_HIDDEN_DIM); // [GENOME_HIDDEN_DIM]
|
|
||||||
// Layer 2: hidden -> output
|
// Layer 2: hidden -> output
|
||||||
const float* W2 = b1 + GENOME_HIDDEN_DIM; // [GENOME_HIDDEN_DIM x GENOME_OUTPUT_DIM]
|
const float* W2 = w + ((GENOME_INPUT_DIM + 1) * GENOME_HIDDEN_DIM); // [(GENOME_HIDDEN_DIM + 1) x GENOME_OUTPUT_DIM]
|
||||||
const float* b2 = W2 + (GENOME_HIDDEN_DIM * GENOME_OUTPUT_DIM); // [GENOME_OUTPUT_DIM]
|
|
||||||
|
|
||||||
for (int g = 0; g < num_groups; ++g) {
|
for (int g = 0; g < num_groups; ++g) {
|
||||||
|
// One-Hot Layer Type: Clamp to 5 to avoid overflow for new categories
|
||||||
|
float layer_type_val = (layer_stats[g].size() >= 3) ? layer_stats[g][2] : 5.0f;
|
||||||
|
int type_idx = std::min(5, static_cast<int>(layer_type_val));
|
||||||
|
std::array<float, 5> type_onehot{0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
|
||||||
|
if (type_idx >= 0 && type_idx < 5) {
|
||||||
|
type_onehot[type_idx] = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
// Build input vector
|
// Build input vector
|
||||||
std::array<float, GENOME_INPUT_DIM> input{};
|
std::array<float, GENOME_INPUT_DIM> input{};
|
||||||
if (!layer_stats[g].empty()) {
|
float gn = (layer_stats[g].size() >= 1) ? layer_stats[g][0] : 0.0f;
|
||||||
// Copy available stats, pad with context
|
float sp = (layer_stats[g].size() >= 2) ? layer_stats[g][1] : 0.0f;
|
||||||
const int n = std::min(static_cast<int>(layer_stats[g].size()), GENOME_INPUT_DIM - 4);
|
|
||||||
for (int i = 0; i < n; ++i) {
|
|
||||||
input[i] = layer_stats[g][i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Append global context
|
|
||||||
input[GENOME_INPUT_DIM - 4] = loss_trend;
|
|
||||||
input[GENOME_INPUT_DIM - 3] = step_pct;
|
|
||||||
input[GENOME_INPUT_DIM - 2] = grad_stability;
|
|
||||||
input[GENOME_INPUT_DIM - 1] = stagnation_intensity;
|
|
||||||
|
|
||||||
// Forward pass: hidden = tanh(W1 * input + b1)
|
// Sanitization matching nan_to_num
|
||||||
|
if (!std::isfinite(gn) || std::isnan(gn)) gn = 0.0f;
|
||||||
|
if (gn > 10.0f) gn = 10.0f;
|
||||||
|
if (gn < 0.0f) gn = 0.0f;
|
||||||
|
|
||||||
|
if (!std::isfinite(sp) || std::isnan(sp)) sp = 0.0f;
|
||||||
|
if (sp > 1.0f) sp = 1.0f;
|
||||||
|
if (sp < 0.0f) sp = 0.0f;
|
||||||
|
|
||||||
|
input[0] = gn;
|
||||||
|
input[1] = sp;
|
||||||
|
input[2] = loss_trend;
|
||||||
|
input[3] = step_pct;
|
||||||
|
input[4] = (num_groups > 1) ? static_cast<float>(g) / (num_groups - 1.0f) : 0.0f;
|
||||||
|
input[5] = rollback_rate;
|
||||||
|
input[6] = grad_stability;
|
||||||
|
input[7] = spectral_alpha;
|
||||||
|
input[8] = type_onehot[0];
|
||||||
|
input[9] = type_onehot[1];
|
||||||
|
input[10] = type_onehot[2];
|
||||||
|
input[11] = type_onehot[3];
|
||||||
|
input[12] = type_onehot[4];
|
||||||
|
input[13] = projected_drift;
|
||||||
|
|
||||||
|
// Forward pass: hidden = tanh(W1 * [input, 1])
|
||||||
std::array<float, GENOME_HIDDEN_DIM> hidden{};
|
std::array<float, GENOME_HIDDEN_DIM> hidden{};
|
||||||
for (int h = 0; h < GENOME_HIDDEN_DIM; ++h) {
|
for (int h = 0; h < GENOME_HIDDEN_DIM; ++h) {
|
||||||
float sum = b1[h];
|
float sum = W1[GENOME_INPUT_DIM * GENOME_HIDDEN_DIM + h]; // Bias weight
|
||||||
for (int i = 0; i < GENOME_INPUT_DIM; ++i) {
|
for (int i = 0; i < GENOME_INPUT_DIM; ++i) {
|
||||||
sum += W1[h * GENOME_INPUT_DIM + i] * input[i];
|
sum += input[i] * W1[i * GENOME_HIDDEN_DIM + h];
|
||||||
}
|
}
|
||||||
hidden[h] = std::tanh(sum);
|
hidden[h] = std::tanh(sum);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output = W2 * hidden + b2
|
// Output = W2 * [hidden, 1]
|
||||||
|
std::array<float, GENOME_OUTPUT_DIM> out_layer{};
|
||||||
for (int o = 0; o < GENOME_OUTPUT_DIM; ++o) {
|
for (int o = 0; o < GENOME_OUTPUT_DIM; ++o) {
|
||||||
float sum = b2[o];
|
float sum = W2[GENOME_HIDDEN_DIM * GENOME_OUTPUT_DIM + o]; // Bias weight
|
||||||
for (int h = 0; h < GENOME_HIDDEN_DIM; ++h) {
|
for (int h = 0; h < GENOME_HIDDEN_DIM; ++h) {
|
||||||
sum += W2[o * GENOME_HIDDEN_DIM + h] * hidden[h];
|
sum += hidden[h] * W2[h * GENOME_OUTPUT_DIM + o];
|
||||||
}
|
}
|
||||||
actions[g][o] = sum;
|
out_layer[o] = sum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse Output
|
||||||
|
float log_mult_raw = out_layer[0];
|
||||||
|
float log_wd_raw = out_layer[2];
|
||||||
|
float sign_logit = out_layer[1];
|
||||||
|
|
||||||
|
if (!std::isfinite(sign_logit) || std::isnan(sign_logit)) {
|
||||||
|
sign_logit = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float noise_std = 0.0f;
|
||||||
|
if (genome.plasticity > 0.01f) {
|
||||||
|
noise_std += genome.plasticity;
|
||||||
|
}
|
||||||
|
if (stagnation_intensity > 0.0f) {
|
||||||
|
noise_std += stagnation_intensity * 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kzm_damping > 0.0f) {
|
||||||
|
noise_std *= (1.0f - kzm_damping);
|
||||||
|
log_mult_raw *= (1.0f - kzm_damping);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (noise_std > 0.0f) {
|
||||||
|
std::normal_distribution<float> noise_dist(0.0f, noise_std);
|
||||||
|
log_mult_raw += noise_dist(rng_);
|
||||||
|
log_wd_raw += noise_dist(rng_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!std::isfinite(log_mult_raw) || std::isnan(log_mult_raw)) {
|
||||||
|
log_mult_raw = 0.0f;
|
||||||
|
}
|
||||||
|
log_mult_raw = std::max(-6.0f, std::min(6.0f, log_mult_raw));
|
||||||
|
float mult = std::pow(2.0f, log_mult_raw);
|
||||||
|
|
||||||
|
if (!std::isfinite(log_wd_raw) || std::isnan(log_wd_raw)) {
|
||||||
|
log_wd_raw = 0.0f;
|
||||||
|
}
|
||||||
|
log_wd_raw = std::max(-6.0f, std::min(6.0f, log_wd_raw));
|
||||||
|
float wd_mult = std::pow(2.0f, log_wd_raw);
|
||||||
|
|
||||||
|
actions[g][0] = mult;
|
||||||
|
actions[g][1] = sign_logit;
|
||||||
|
actions[g][2] = wd_mult;
|
||||||
}
|
}
|
||||||
|
|
||||||
return actions;
|
return actions;
|
||||||
@@ -108,67 +180,174 @@ torch::Tensor FuzzyController::decide_update(
|
|||||||
|
|
||||||
FuzzyController FuzzyController::mutate(float current_loss, float sigma_scale) const {
|
FuzzyController FuzzyController::mutate(float current_loss, float sigma_scale) const {
|
||||||
Genome child_genome = genome.clone();
|
Genome child_genome = genome.clone();
|
||||||
std::normal_distribution<float> noise(0.0f, genome.sigma_gene * sigma_scale);
|
std::normal_distribution<float> std_normal(0.0f, 1.0f);
|
||||||
|
|
||||||
|
float tau = 0.2f;
|
||||||
|
float new_sigma = genome.sigma_gene * std::exp(tau * std_normal(rng_));
|
||||||
|
new_sigma = std::max(0.001f, std::min(0.8f, new_sigma));
|
||||||
|
|
||||||
|
float new_plast = genome.plasticity * std::exp(tau * std_normal(rng_));
|
||||||
|
new_plast = std::max(0.0f, std::min(0.5f, new_plast));
|
||||||
|
|
||||||
|
float loss_val = std::max(0.0f, current_loss);
|
||||||
|
float annealing_factor = std::sqrt(loss_val + 0.1f);
|
||||||
|
float effective_sigma = new_sigma * sigma_scale * annealing_factor;
|
||||||
|
|
||||||
|
std::normal_distribution<float> noise(0.0f, effective_sigma);
|
||||||
for (size_t i = 0; i < child_genome.weights.size(); ++i) {
|
for (size_t i = 0; i < child_genome.weights.size(); ++i) {
|
||||||
child_genome.weights[i] += noise(rng_);
|
child_genome.weights[i] += noise(rng_);
|
||||||
|
child_genome.gene_success[i] = genome.gene_success[i] * 0.95f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
child_genome.sigma_gene = new_sigma;
|
||||||
|
child_genome.plasticity = new_plast;
|
||||||
|
|
||||||
FuzzyController child(child_genome);
|
FuzzyController child(child_genome);
|
||||||
child.origin = "mutation";
|
child.origin = "mutation";
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
FuzzyController FuzzyController::crossover(const FuzzyController& partner, bool use_alignment) const {
|
FuzzyController FuzzyController::crossover(const FuzzyController& partner, bool /*use_alignment*/) const {
|
||||||
Genome child_genome;
|
Genome child_genome;
|
||||||
std::uniform_real_distribution<float> coin(0.0f, 1.0f);
|
std::uniform_real_distribution<float> u_dist(0.0f, 1.0f);
|
||||||
|
|
||||||
for (size_t i = 0; i < child_genome.weights.size(); ++i) {
|
for (size_t i = 0; i < child_genome.weights.size(); ++i) {
|
||||||
if (use_alignment && genome.gene_success[i] > partner.genome.gene_success[i]) {
|
float success_self = genome.gene_success[i];
|
||||||
child_genome.weights[i] = genome.weights[i];
|
float success_partner = partner.genome.gene_success[i];
|
||||||
} else if (use_alignment && partner.genome.gene_success[i] > genome.gene_success[i]) {
|
float prob_a = success_self / (success_self + success_partner + 1e-9f);
|
||||||
child_genome.weights[i] = partner.genome.weights[i];
|
|
||||||
|
bool choose_self = false;
|
||||||
|
if (u_dist(rng_) < 0.1f) {
|
||||||
|
// 10% Random injection
|
||||||
|
choose_self = (u_dist(rng_) < 0.5f);
|
||||||
} else {
|
} else {
|
||||||
// Uniform crossover
|
// 90% Meritocratic
|
||||||
child_genome.weights[i] = (coin(rng_) < 0.5f)
|
choose_self = (u_dist(rng_) < prob_a);
|
||||||
? genome.weights[i]
|
}
|
||||||
: partner.genome.weights[i];
|
|
||||||
|
if (choose_self) {
|
||||||
|
child_genome.weights[i] = genome.weights[i];
|
||||||
|
child_genome.gene_success[i] = success_self;
|
||||||
|
} else {
|
||||||
|
child_genome.weights[i] = partner.genome.weights[i];
|
||||||
|
child_genome.gene_success[i] = success_partner;
|
||||||
}
|
}
|
||||||
child_genome.gene_success[i] = 0.0f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
child_genome.sigma_gene = (genome.sigma_gene + partner.genome.sigma_gene) * 0.5f;
|
||||||
|
child_genome.plasticity = (genome.plasticity + partner.genome.plasticity) * 0.5f;
|
||||||
|
|
||||||
FuzzyController child(child_genome);
|
FuzzyController child(child_genome);
|
||||||
child.origin = "crossover";
|
child.origin = "crossover";
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
FuzzyController FuzzyController::create_orthogonal_child(float intensity) const {
|
FuzzyController FuzzyController::create_orthogonal_child(float intensity) const {
|
||||||
Genome child_genome = genome.clone();
|
Genome child_genome;
|
||||||
// Negate a random subset of weights
|
std::normal_distribution<float> norm_dist(0.0f, 1.0f);
|
||||||
std::uniform_real_distribution<float> coin(0.0f, 1.0f);
|
|
||||||
for (size_t i = 0; i < child_genome.weights.size(); ++i) {
|
float norm_elite = 0.0f;
|
||||||
if (coin(rng_) < 0.3f * intensity) {
|
for (float w : genome.weights) {
|
||||||
child_genome.weights[i] = -child_genome.weights[i];
|
norm_elite += w * w;
|
||||||
|
}
|
||||||
|
norm_elite = std::sqrt(norm_elite) + 1e-9f;
|
||||||
|
|
||||||
|
std::array<float, GENOME_SIZE> random_vec{};
|
||||||
|
float dot_product = 0.0f;
|
||||||
|
for (size_t i = 0; i < GENOME_SIZE; ++i) {
|
||||||
|
random_vec[i] = norm_dist(rng_);
|
||||||
|
dot_product += random_vec[i] * genome.weights[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<float, GENOME_SIZE> orthogonal_vec{};
|
||||||
|
float norm_ortho = 0.0f;
|
||||||
|
for (size_t i = 0; i < GENOME_SIZE; ++i) {
|
||||||
|
float projection = (dot_product / (norm_elite * norm_elite)) * genome.weights[i];
|
||||||
|
orthogonal_vec[i] = random_vec[i] - projection;
|
||||||
|
norm_ortho += orthogonal_vec[i] * orthogonal_vec[i];
|
||||||
|
}
|
||||||
|
norm_ortho = std::sqrt(norm_ortho) + 1e-9f;
|
||||||
|
|
||||||
|
std::array<float, GENOME_SIZE> scaled_vec{};
|
||||||
|
float final_norm = 0.0f;
|
||||||
|
for (size_t i = 0; i < GENOME_SIZE; ++i) {
|
||||||
|
scaled_vec[i] = orthogonal_vec[i] * (norm_elite / norm_ortho) * intensity;
|
||||||
|
final_norm += scaled_vec[i] * scaled_vec[i];
|
||||||
|
}
|
||||||
|
final_norm = std::sqrt(final_norm);
|
||||||
|
|
||||||
|
float max_allowed = std::max(norm_elite, 10.0f);
|
||||||
|
if (final_norm > max_allowed) {
|
||||||
|
float scale = max_allowed / (final_norm + 1e-9f);
|
||||||
|
for (size_t i = 0; i < GENOME_SIZE; ++i) {
|
||||||
|
scaled_vec[i] *= scale;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
child_genome.weights = scaled_vec;
|
||||||
|
child_genome.gene_success.fill(1.0f);
|
||||||
|
child_genome.sigma_gene = 0.2f;
|
||||||
|
child_genome.plasticity = 0.2f;
|
||||||
|
|
||||||
FuzzyController child(child_genome);
|
FuzzyController child(child_genome);
|
||||||
child.origin = "phoenix_rebirth";
|
child.origin = "phoenix_rebirth";
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<FuzzyController, FuzzyController> FuzzyController::banach_tarski_fission(float intensity) const {
|
std::pair<FuzzyController, FuzzyController> FuzzyController::banach_tarski_fission(float intensity) const {
|
||||||
Genome plus_genome = genome.clone();
|
Genome plus_genome;
|
||||||
Genome minus_genome = genome.clone();
|
Genome minus_genome;
|
||||||
std::normal_distribution<float> noise(0.0f, 0.1f * intensity);
|
|
||||||
|
|
||||||
for (size_t i = 0; i < genome.weights.size(); ++i) {
|
float norm_parent = 0.0f;
|
||||||
float delta = noise(rng_);
|
float max_success = 0.0f;
|
||||||
plus_genome.weights[i] += delta;
|
for (size_t i = 0; i < GENOME_SIZE; ++i) {
|
||||||
minus_genome.weights[i] -= delta;
|
norm_parent += genome.weights[i] * genome.weights[i];
|
||||||
|
if (genome.gene_success[i] > max_success) {
|
||||||
|
max_success = genome.gene_success[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
norm_parent = std::sqrt(norm_parent) + 1e-9f;
|
||||||
|
max_success += 1e-9f;
|
||||||
|
|
||||||
|
std::normal_distribution<float> norm_dist(0.0f, 1.0f);
|
||||||
|
std::array<float, GENOME_SIZE> noise{};
|
||||||
|
float dot_product = 0.0f;
|
||||||
|
for (size_t i = 0; i < GENOME_SIZE; ++i) {
|
||||||
|
float saliency = genome.gene_success[i] / max_success;
|
||||||
|
noise[i] = norm_dist(rng_) * saliency;
|
||||||
|
dot_product += noise[i] * genome.weights[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
return {FuzzyController(plus_genome), FuzzyController(minus_genome)};
|
std::array<float, GENOME_SIZE> fission_vec{};
|
||||||
|
float norm_fission = 0.0f;
|
||||||
|
for (size_t i = 0; i < GENOME_SIZE; ++i) {
|
||||||
|
fission_vec[i] = noise[i] - (dot_product / (norm_parent * norm_parent)) * genome.weights[i];
|
||||||
|
norm_fission += fission_vec[i] * fission_vec[i];
|
||||||
|
}
|
||||||
|
norm_fission = std::sqrt(norm_fission) + 1e-9f;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < GENOME_SIZE; ++i) {
|
||||||
|
float scaled_fission = fission_vec[i] * (norm_parent / norm_fission) * 0.1f * intensity;
|
||||||
|
plus_genome.weights[i] = genome.weights[i] + scaled_fission;
|
||||||
|
minus_genome.weights[i] = genome.weights[i] - scaled_fission;
|
||||||
|
|
||||||
|
plus_genome.gene_success[i] = 1.0f;
|
||||||
|
minus_genome.gene_success[i] = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
plus_genome.sigma_gene = genome.sigma_gene * 0.9f;
|
||||||
|
minus_genome.sigma_gene = genome.sigma_gene * 0.9f;
|
||||||
|
|
||||||
|
plus_genome.plasticity = genome.plasticity;
|
||||||
|
minus_genome.plasticity = genome.plasticity;
|
||||||
|
|
||||||
|
FuzzyController child_plus(plus_genome);
|
||||||
|
child_plus.origin = "fission_plus";
|
||||||
|
|
||||||
|
FuzzyController child_minus(minus_genome);
|
||||||
|
child_minus.origin = "fission_minus";
|
||||||
|
|
||||||
|
return {child_plus, child_minus};
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace fces
|
} // namespace fces
|
||||||
|
|||||||
@@ -18,7 +18,35 @@ FuzzyController& EvolutionManager::get_active_controller() {
|
|||||||
void EvolutionManager::update_population_dynamics(
|
void EvolutionManager::update_population_dynamics(
|
||||||
float loss_velocity, float ema_loss, int step_counter, int total_steps
|
float loss_velocity, float ema_loss, int step_counter, int total_steps
|
||||||
) {
|
) {
|
||||||
// TODO: Port full dynamics (auto-sizing, lockdown, evolution triggers)
|
float progress = static_cast<float>(step_counter) / std::max(1, total_steps);
|
||||||
|
|
||||||
|
if (step_counter % 20 == 0) {
|
||||||
|
population_.evolve(
|
||||||
|
std::abs(loss_velocity),
|
||||||
|
loss_velocity,
|
||||||
|
progress
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!auto_population_ || step_counter % 50 != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int current_pop = population_.size();
|
||||||
|
float adaptive_threshold = 0.05f * (1.0f + ema_loss);
|
||||||
|
adaptive_threshold = std::min(0.5f, adaptive_threshold);
|
||||||
|
|
||||||
|
if (std::abs(loss_velocity) < adaptive_threshold) {
|
||||||
|
int target_pop = 200;
|
||||||
|
if (target_pop > current_pop) {
|
||||||
|
population_.resize(target_pop, progress);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int target_pop = 40;
|
||||||
|
if (target_pop < current_pop) {
|
||||||
|
population_.resize(target_pop, progress);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace fces
|
} // namespace fces
|
||||||
|
|||||||
@@ -59,13 +59,38 @@ float FitnessEngine::compute_kzm_damping(float spectral_alpha) const {
|
|||||||
// FuzzyFitnessEvaluator
|
// FuzzyFitnessEvaluator
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
|
|
||||||
FitnessMetrics FuzzyFitnessEvaluator::evaluate(
|
FuzzyFitnessEvaluator::FuzzyFitnessEvaluator() noexcept
|
||||||
float loss_before, float loss_after, float sparsity, float val_loss
|
: stability_set_("Stable", -1.0f, 0.0f, 0.1f, 0.5f),
|
||||||
) const {
|
train_set_("Effective", 0.0f, 0.05f, 1.0f, 10.0f),
|
||||||
FitnessMetrics metrics;
|
val_set_("Generalizing", 0.0f, 0.05f, 1.0f, 10.0f),
|
||||||
metrics.loss_improvement = loss_before - loss_after;
|
sparsity_set_("Sparse", 0.0f, 0.001f, 1.0f, 1.0f),
|
||||||
metrics.sparsity_score = sparsity * sparsity; // Quadratic reward
|
consistency_set_("Consistent", -1.0f, 0.0f, 0.02f, 0.1f),
|
||||||
return metrics;
|
rank_set_("LowRank", -1.0f, 0.0f, 5.0f, 20.0f) {}
|
||||||
|
|
||||||
|
float FuzzyFitnessEvaluator::evaluate(const FitnessMetrics& metrics) const noexcept {
|
||||||
|
float m_stability = stability_set_.membership(metrics.grad_cv);
|
||||||
|
float m_train = train_set_.membership(metrics.training_advantage);
|
||||||
|
float m_val = val_set_.membership(metrics.validation_advantage);
|
||||||
|
float m_sparsity = sparsity_set_.membership(metrics.sparsity_delta);
|
||||||
|
float m_consistency = consistency_set_.membership(metrics.consistency_gap);
|
||||||
|
float m_rank = rank_set_.membership(metrics.stable_rank);
|
||||||
|
|
||||||
|
float weighted_score =
|
||||||
|
m_stability * w_stability_ +
|
||||||
|
m_train * w_train_ +
|
||||||
|
m_val * w_val_ +
|
||||||
|
m_sparsity * w_sparsity_ +
|
||||||
|
m_consistency * w_consistency_ +
|
||||||
|
m_rank * w_rank_;
|
||||||
|
|
||||||
|
float total_weight = w_stability_ + w_train_ + w_val_ + w_sparsity_ + w_consistency_ + w_rank_;
|
||||||
|
if (total_weight > 0.0f) {
|
||||||
|
weighted_score /= total_weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// V153: Generalization-Aware Gate (Non-Linear)
|
||||||
|
float gate_efficiency = 0.5f + 0.5f * m_consistency;
|
||||||
|
return weighted_score * gate_efficiency;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace fces
|
} // namespace fces
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ Population::Population(
|
|||||||
}
|
}
|
||||||
|
|
||||||
FuzzyController& Population::get_active_controller() {
|
FuzzyController& Population::get_active_controller() {
|
||||||
// TODO: Implement sticky selection with interval
|
|
||||||
if (active_controller_ == nullptr || steps_active_ >= selection_interval_) {
|
if (active_controller_ == nullptr || steps_active_ >= selection_interval_) {
|
||||||
active_controller_ = &select_weighted();
|
active_controller_ = &select_weighted();
|
||||||
steps_active_ = 0;
|
steps_active_ = 0;
|
||||||
@@ -39,10 +38,73 @@ FuzzyController& Population::get_active_controller() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
FuzzyController& Population::select_weighted() {
|
FuzzyController& Population::select_weighted() {
|
||||||
// TODO: Implement tournament selection with age weighting
|
|
||||||
static thread_local std::mt19937 rng{std::random_device{}()};
|
static thread_local std::mt19937 rng{std::random_device{}()};
|
||||||
|
if (gladiators_.empty()) {
|
||||||
|
throw std::runtime_error("Empty gladiators population");
|
||||||
|
}
|
||||||
|
|
||||||
|
float sum_fit = 0.0f;
|
||||||
|
for (const auto& g : gladiators_) {
|
||||||
|
sum_fit += std::max(0.0f, g.fitness);
|
||||||
|
}
|
||||||
|
if (sum_fit == 0.0f) {
|
||||||
|
std::uniform_int_distribution<int> dist(0, static_cast<int>(gladiators_.size()) - 1);
|
||||||
|
return gladiators_[dist(rng)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select 3 random candidates for tournament
|
||||||
std::uniform_int_distribution<int> dist(0, static_cast<int>(gladiators_.size()) - 1);
|
std::uniform_int_distribution<int> dist(0, static_cast<int>(gladiators_.size()) - 1);
|
||||||
return gladiators_[dist(rng)];
|
int idx1 = dist(rng);
|
||||||
|
int idx2 = dist(rng);
|
||||||
|
int idx3 = dist(rng);
|
||||||
|
|
||||||
|
auto get_score = [this](const FuzzyController& c) {
|
||||||
|
float base_score = c.fitness + (0.01f * static_cast<float>(c.age));
|
||||||
|
// Add novelty score if archive has enough entries
|
||||||
|
if (behavioral_archive_.size() >= 5) {
|
||||||
|
float novelty = 0.0f;
|
||||||
|
// Get behavioral vector: first 20 weights
|
||||||
|
std::vector<float> behavior(c.genome.weights.begin(), c.genome.weights.begin() + std::min(20, static_cast<int>(c.genome.weights.size())));
|
||||||
|
std::vector<float> distances;
|
||||||
|
distances.reserve(behavioral_archive_.size());
|
||||||
|
for (const auto& archived : behavioral_archive_) {
|
||||||
|
float dist_sum = 0.0f;
|
||||||
|
for (size_t i = 0; i < behavior.size() && i < archived.size(); ++i) {
|
||||||
|
float diff = behavior[i] - archived[i];
|
||||||
|
dist_sum += diff * diff;
|
||||||
|
}
|
||||||
|
distances.push_back(std::sqrt(dist_sum));
|
||||||
|
}
|
||||||
|
std::sort(distances.begin(), distances.end());
|
||||||
|
int k = std::min(5, static_cast<int>(distances.size()));
|
||||||
|
float avg_dist = 0.0f;
|
||||||
|
for (int i = 0; i < k; ++i) {
|
||||||
|
avg_dist += distances[i];
|
||||||
|
}
|
||||||
|
if (k > 0) avg_dist /= static_cast<float>(k);
|
||||||
|
base_score += NOVELTY_WEIGHT * avg_dist;
|
||||||
|
}
|
||||||
|
return base_score;
|
||||||
|
};
|
||||||
|
|
||||||
|
FuzzyController* best = &gladiators_[idx1];
|
||||||
|
float best_score = get_score(*best);
|
||||||
|
|
||||||
|
FuzzyController* cand2 = &gladiators_[idx2];
|
||||||
|
float score2 = get_score(*cand2);
|
||||||
|
if (score2 > best_score) {
|
||||||
|
best = cand2;
|
||||||
|
best_score = score2;
|
||||||
|
}
|
||||||
|
|
||||||
|
FuzzyController* cand3 = &gladiators_[idx3];
|
||||||
|
float score3 = get_score(*cand3);
|
||||||
|
if (score3 > best_score) {
|
||||||
|
best = cand3;
|
||||||
|
best_score = score3;
|
||||||
|
}
|
||||||
|
|
||||||
|
return *best;
|
||||||
}
|
}
|
||||||
|
|
||||||
FuzzyController& Population::get_best_active() {
|
FuzzyController& Population::get_best_active() {
|
||||||
@@ -53,14 +115,42 @@ FuzzyController& Population::get_best_active() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
FuzzyController& Population::get_worst_active() {
|
FuzzyController& Population::get_worst_active() {
|
||||||
return *std::min_element(gladiators_.begin(), gladiators_.end(),
|
auto elites = get_elites();
|
||||||
[](const FuzzyController& a, const FuzzyController& b) {
|
std::vector<FuzzyController*> non_elites;
|
||||||
return a.fitness < b.fitness;
|
for (auto& g : gladiators_) {
|
||||||
|
bool is_elite = false;
|
||||||
|
for (auto* e : elites) {
|
||||||
|
if (e->id == g.id) {
|
||||||
|
is_elite = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!is_elite) {
|
||||||
|
non_elites.push_back(&g);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (non_elites.empty()) {
|
||||||
|
return *std::min_element(gladiators_.begin(), gladiators_.end(),
|
||||||
|
[](const FuzzyController& a, const FuzzyController& b) {
|
||||||
|
return a.fitness < b.fitness;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return **std::min_element(non_elites.begin(), non_elites.end(),
|
||||||
|
[](const FuzzyController* a, const FuzzyController* b) {
|
||||||
|
return a->fitness < b->fitness;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Population::kill(FuzzyController& controller) {
|
void Population::kill(FuzzyController& controller) {
|
||||||
// TODO: Elite protection
|
auto elites = get_elites();
|
||||||
|
for (auto* e : elites) {
|
||||||
|
if (e->id == controller.id) {
|
||||||
|
return; // Elite protection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto it = std::find_if(gladiators_.begin(), gladiators_.end(),
|
auto it = std::find_if(gladiators_.begin(), gladiators_.end(),
|
||||||
[&](const FuzzyController& c) { return c.id == controller.id; });
|
[&](const FuzzyController& c) { return c.id == controller.id; });
|
||||||
if (it != gladiators_.end()) {
|
if (it != gladiators_.end()) {
|
||||||
@@ -72,14 +162,31 @@ void Population::kill(FuzzyController& controller) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Population::update_controller_fitness(FuzzyController& controller, float reward, bool increment_eval) {
|
void Population::update_controller_fitness(FuzzyController& controller, float reward, bool increment_eval) {
|
||||||
controller.fitness += reward;
|
if (increment_eval) {
|
||||||
|
controller.age++;
|
||||||
|
controller.evaluation_count++;
|
||||||
|
}
|
||||||
controller.lifetime_fitness += reward;
|
controller.lifetime_fitness += reward;
|
||||||
if (increment_eval) controller.evaluation_count++;
|
|
||||||
controller.age++;
|
// Track in rolling history
|
||||||
|
constexpr size_t RECENT_WINDOW = 20;
|
||||||
|
controller.fitness_history.push_back(reward);
|
||||||
|
if (controller.fitness_history.size() > RECENT_WINDOW) {
|
||||||
|
controller.fitness_history.erase(controller.fitness_history.begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elite_strategy_ == EliteStrategy::EMA) {
|
||||||
|
constexpr float EMA_ALPHA = 0.1f;
|
||||||
|
controller.ema_fitness = (1.0f - EMA_ALPHA) * controller.ema_fitness + EMA_ALPHA * reward;
|
||||||
|
controller.fitness = reward;
|
||||||
|
} else if (elite_strategy_ == EliteStrategy::Rolling) {
|
||||||
|
controller.fitness = reward;
|
||||||
|
} else {
|
||||||
|
controller.fitness = reward;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Population::mark_violated(FuzzyController& controller) {
|
void Population::mark_violated(FuzzyController& controller) {
|
||||||
// Deduplicate
|
|
||||||
auto it = std::find_if(violated_controllers_.begin(), violated_controllers_.end(),
|
auto it = std::find_if(violated_controllers_.begin(), violated_controllers_.end(),
|
||||||
[&](const FuzzyController& c) { return c.id == controller.id; });
|
[&](const FuzzyController& c) { return c.id == controller.id; });
|
||||||
if (it == violated_controllers_.end()) {
|
if (it == violated_controllers_.end()) {
|
||||||
@@ -88,16 +195,292 @@ void Population::mark_violated(FuzzyController& controller) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
float Population::get_effective_fitness(const FuzzyController& controller, float training_progress) const {
|
float Population::get_effective_fitness(const FuzzyController& controller, float training_progress) const {
|
||||||
// TODO: Implement recency-weighted effective fitness (V42.5)
|
float recent_avg = 0.0f;
|
||||||
return controller.fitness;
|
if (!controller.fitness_history.empty()) {
|
||||||
|
float sum = 0.0f;
|
||||||
|
for (float f : controller.fitness_history) sum += f;
|
||||||
|
recent_avg = sum / controller.fitness_history.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
float lifetime_avg = 0.0f;
|
||||||
|
if (controller.evaluation_count > 0) {
|
||||||
|
lifetime_avg = controller.lifetime_fitness / static_cast<float>(controller.evaluation_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
float alpha = 0.2f + 0.6f * training_progress;
|
||||||
|
return alpha * recent_avg + (1.0f - alpha) * lifetime_avg;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Population::evolve(float current_loss, float velocity, float training_progress) {
|
void Population::evolve(float current_loss, float velocity, float training_progress) {
|
||||||
// TODO: Port full evolution logic from Python
|
static thread_local std::mt19937 rng{std::random_device{}()};
|
||||||
|
std::uniform_real_distribution<float> coin(0.0f, 1.0f);
|
||||||
|
|
||||||
|
if (gladiators_.empty()) return;
|
||||||
|
|
||||||
|
FuzzyController& worst = get_worst_active();
|
||||||
|
FuzzyController& best_active = get_best_active();
|
||||||
|
auto elites = get_elites();
|
||||||
|
|
||||||
|
// Update behavioral archive for novelty search
|
||||||
|
if (best_active.fitness > -999.0f) {
|
||||||
|
std::vector<float> behavior(best_active.genome.weights.begin(), best_active.genome.weights.begin() + std::min(20, static_cast<int>(best_active.genome.weights.size())));
|
||||||
|
behavioral_archive_.push_back(behavior);
|
||||||
|
if (behavioral_archive_.size() > BEHAVIORAL_ARCHIVE_SIZE) {
|
||||||
|
behavioral_archive_.erase(behavioral_archive_.begin());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase-dependent scheduling
|
||||||
|
float phase_sigma_mult = 1.0f;
|
||||||
|
float phase_phoenix_intensity = 1.0f;
|
||||||
|
if (training_progress < 0.1f) {
|
||||||
|
phase_sigma_mult = 2.0f;
|
||||||
|
phase_phoenix_intensity = 1.5f;
|
||||||
|
} else if (training_progress > 0.7f) {
|
||||||
|
phase_sigma_mult = 0.5f;
|
||||||
|
phase_phoenix_intensity = 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loss-linked mutation rate
|
||||||
|
float mutation_rate = 0.5f;
|
||||||
|
if (link_mutation_) {
|
||||||
|
mutation_rate = std::max(0.05f, std::min(1.0f, current_loss / 5.0f));
|
||||||
|
}
|
||||||
|
mutation_rate *= phase_sigma_mult;
|
||||||
|
mutation_rate = std::min(1.0f, mutation_rate);
|
||||||
|
|
||||||
|
// Pairing probabilities
|
||||||
|
float elite_prob = 0.3f;
|
||||||
|
if (link_elite_) {
|
||||||
|
elite_prob = std::max(0.2f, std::min(0.8f, 1.0f - current_loss / 5.0f));
|
||||||
|
}
|
||||||
|
float violator_prob = 0.1f;
|
||||||
|
if (link_violator_) {
|
||||||
|
violator_prob = std::max(0.0f, std::min(0.5f, (current_loss - 1.0f) / 4.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select parent
|
||||||
|
FuzzyController* parent = &best_active;
|
||||||
|
std::vector<FuzzyController*> partner_pool;
|
||||||
|
|
||||||
|
float roll = coin(rng);
|
||||||
|
if (roll < elite_prob && !elites.empty()) {
|
||||||
|
std::uniform_int_distribution<int> elite_dist(0, static_cast<int>(elites.size()) - 1);
|
||||||
|
parent = elites[elite_dist(rng)];
|
||||||
|
partner_pool = elites;
|
||||||
|
} else if (roll < elite_prob + violator_prob && !violated_controllers_.empty()) {
|
||||||
|
parent = &best_active;
|
||||||
|
// Filter living violators
|
||||||
|
for (auto& v : violated_controllers_) {
|
||||||
|
for (auto& g : gladiators_) {
|
||||||
|
if (g.id == v.id) {
|
||||||
|
partner_pool.push_back(&g);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (partner_pool.empty()) {
|
||||||
|
// Fallback
|
||||||
|
for (size_t i = 0; i < std::min(static_cast<size_t>(10), gladiators_.size()); ++i) {
|
||||||
|
partner_pool.push_back(&gladiators_[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parent = &best_active;
|
||||||
|
for (size_t i = 0; i < std::min(static_cast<size_t>(10), gladiators_.size()); ++i) {
|
||||||
|
partner_pool.push_back(&gladiators_[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crossover or mutation
|
||||||
|
FuzzyController child;
|
||||||
|
if (coin(rng) < 0.7f && partner_pool.size() > 1) {
|
||||||
|
std::uniform_int_distribution<int> pool_dist(0, static_cast<int>(partner_pool.size()) - 1);
|
||||||
|
FuzzyController* partner = partner_pool[pool_dist(rng)];
|
||||||
|
if (partner->id == parent->id) {
|
||||||
|
// Pick another if possible
|
||||||
|
for (auto* p : partner_pool) {
|
||||||
|
if (p->id != parent->id) {
|
||||||
|
partner = p;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
child = parent->crossover(*partner, false);
|
||||||
|
} else {
|
||||||
|
float sigma_mod = global_sigma_modifier_ * mutation_rate;
|
||||||
|
if (velocity < -0.05f) {
|
||||||
|
sigma_mod *= 1.0f / (1.0f + std::abs(velocity) * 10.0f);
|
||||||
|
}
|
||||||
|
// Epigenetic lock
|
||||||
|
if (parent->fitness > 0.5f) {
|
||||||
|
sigma_mod *= 0.1f;
|
||||||
|
}
|
||||||
|
child = parent->mutate(current_loss, sigma_mod);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recover temperature
|
||||||
|
global_sigma_modifier_ = std::min(1.0f, global_sigma_modifier_ * 1.01f);
|
||||||
|
|
||||||
|
// Fuzzy Pacer
|
||||||
|
if (use_fuzzy_pacer_) {
|
||||||
|
fitness_history_.push_back(best_active.fitness);
|
||||||
|
if (fitness_history_.size() > 20) {
|
||||||
|
fitness_history_.erase(fitness_history_.begin());
|
||||||
|
}
|
||||||
|
if (fitness_history_.size() >= 10) {
|
||||||
|
float trend = 0.0f;
|
||||||
|
for (size_t i = 1; i < fitness_history_.size(); ++i) {
|
||||||
|
trend += (fitness_history_[i] - fitness_history_[i - 1]);
|
||||||
|
}
|
||||||
|
trend /= (fitness_history_.size() - 1);
|
||||||
|
float diversity = get_diversity_index();
|
||||||
|
if (trend < 0.001f && diversity < 0.2f) {
|
||||||
|
global_sigma_modifier_ = std::min(5.0f, global_sigma_modifier_ * 1.2f);
|
||||||
|
} else if (trend > 0.01f) {
|
||||||
|
global_sigma_modifier_ = std::max(0.1f, global_sigma_modifier_ * 0.95f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Banach-Tarski Fission
|
||||||
|
if (use_banach_fission_ && coin(rng) < 0.2f && !elites.empty()) {
|
||||||
|
auto* prime_elite = elites[0];
|
||||||
|
auto fission_pair = prime_elite->banach_tarski_fission(phase_phoenix_intensity);
|
||||||
|
|
||||||
|
// Find second worst
|
||||||
|
FuzzyController* second_worst = nullptr;
|
||||||
|
for (auto& g : gladiators_) {
|
||||||
|
if (g.id != worst.id) {
|
||||||
|
if (second_worst == nullptr || g.fitness < second_worst->fitness) {
|
||||||
|
second_worst = &g;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace worst and second_worst with plus and minus child
|
||||||
|
if (second_worst) {
|
||||||
|
uint64_t sw_id = second_worst->id;
|
||||||
|
auto it = std::find_if(gladiators_.begin(), gladiators_.end(), [&](const FuzzyController& c) { return c.id == sw_id; });
|
||||||
|
if (it != gladiators_.end()) {
|
||||||
|
gladiators_.erase(it);
|
||||||
|
}
|
||||||
|
gladiators_.push_back(fission_pair.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t w_id = worst.id;
|
||||||
|
auto it = std::find_if(gladiators_.begin(), gladiators_.end(), [&](const FuzzyController& c) { return c.id == w_id; });
|
||||||
|
if (it != gladiators_.end()) {
|
||||||
|
gladiators_.erase(it);
|
||||||
|
}
|
||||||
|
gladiators_.push_back(fission_pair.second);
|
||||||
|
} else {
|
||||||
|
// Phoenix Rebirth or Standard replacement
|
||||||
|
uint64_t w_id = worst.id;
|
||||||
|
auto it = std::find_if(gladiators_.begin(), gladiators_.end(), [&](const FuzzyController& c) { return c.id == w_id; });
|
||||||
|
if (it != gladiators_.end()) {
|
||||||
|
gladiators_.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coin(rng) < 0.1f && !elites.empty()) {
|
||||||
|
auto* prime_elite = elites[0];
|
||||||
|
gladiators_.push_back(prime_elite->create_orthogonal_child(phase_phoenix_intensity));
|
||||||
|
} else {
|
||||||
|
gladiators_.push_back(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Periodic Reset
|
||||||
|
if (elite_strategy_ == EliteStrategy::Reset) {
|
||||||
|
reset_step_counter_++;
|
||||||
|
if (reset_step_counter_ >= 500) {
|
||||||
|
reset_step_counter_ = 0;
|
||||||
|
for (auto& g : gladiators_) {
|
||||||
|
g.fitness = 0.0f;
|
||||||
|
g.ema_fitness = 0.0f;
|
||||||
|
g.fitness_history.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Archive best
|
||||||
|
if (best_active.fitness > -999.0f) {
|
||||||
|
add_to_repository(best_active);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Population::resize(int target_size, float training_progress) {
|
void Population::resize(int target_size, float training_progress) {
|
||||||
// TODO: Port resize logic
|
int current_size = static_cast<int>(gladiators_.size());
|
||||||
|
if (current_size == target_size) return;
|
||||||
|
|
||||||
|
static thread_local std::mt19937 rng{std::random_device{}()};
|
||||||
|
|
||||||
|
if (current_size < target_size) {
|
||||||
|
int needed = target_size - current_size;
|
||||||
|
bool has_eval = false;
|
||||||
|
for (const auto& g : gladiators_) {
|
||||||
|
if (g.evaluation_count > 0) {
|
||||||
|
has_eval = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (has_eval) {
|
||||||
|
std::vector<std::pair<float, FuzzyController*>> candidates;
|
||||||
|
for (auto& g : gladiators_) {
|
||||||
|
candidates.push_back({get_effective_fitness(g, training_progress), &g});
|
||||||
|
}
|
||||||
|
std::sort(candidates.begin(), candidates.end(),
|
||||||
|
[](const std::pair<float, FuzzyController*>& a, const std::pair<float, FuzzyController*>& b) {
|
||||||
|
return a.first > b.first;
|
||||||
|
});
|
||||||
|
|
||||||
|
int limit = std::min(10, static_cast<int>(candidates.size()));
|
||||||
|
std::uniform_int_distribution<int> cand_dist(0, limit - 1);
|
||||||
|
for (int i = 0; i < needed; ++i) {
|
||||||
|
FuzzyController* parent = candidates[cand_dist(rng)].second;
|
||||||
|
float mutation_str = 0.1f;
|
||||||
|
auto child = parent->mutate(mutation_str, 1.0f);
|
||||||
|
|
||||||
|
float stability = 1.0f - std::min(1.0f, mutation_str);
|
||||||
|
std::uniform_real_distribution<float> noise_dist(-0.1f, 0.1f);
|
||||||
|
float noise = noise_dist(rng) * std::abs(parent->fitness);
|
||||||
|
child.fitness = parent->fitness * stability + noise;
|
||||||
|
|
||||||
|
gladiators_.push_back(child);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < needed; ++i) {
|
||||||
|
gladiators_.emplace_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::vector<FuzzyController*> evaluated;
|
||||||
|
std::vector<FuzzyController*> unevaluated;
|
||||||
|
for (auto& g : gladiators_) {
|
||||||
|
if (g.evaluation_count > 0) {
|
||||||
|
evaluated.push_back(&g);
|
||||||
|
} else {
|
||||||
|
unevaluated.push_back(&g);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(evaluated.begin(), evaluated.end(),
|
||||||
|
[this, training_progress](const FuzzyController* a, const FuzzyController* b) {
|
||||||
|
return get_effective_fitness(*a, training_progress) > get_effective_fitness(*b, training_progress);
|
||||||
|
});
|
||||||
|
|
||||||
|
std::vector<FuzzyController> new_pop;
|
||||||
|
new_pop.reserve(target_size);
|
||||||
|
for (int i = 0; i < std::min(target_size, static_cast<int>(evaluated.size())); ++i) {
|
||||||
|
new_pop.push_back(*evaluated[i]);
|
||||||
|
}
|
||||||
|
int remaining = target_size - static_cast<int>(new_pop.size());
|
||||||
|
for (int i = 0; i < std::min(remaining, static_cast<int>(unevaluated.size())); ++i) {
|
||||||
|
new_pop.push_back(*unevaluated[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
gladiators_ = std::move(new_pop);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Population::calm_down() {
|
void Population::calm_down() {
|
||||||
@@ -106,17 +489,78 @@ void Population::calm_down() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
float Population::get_diversity_index() const {
|
float Population::get_diversity_index() const {
|
||||||
// TODO: Compute behavioral spread
|
if (gladiators_.size() < 2) return 0.0f;
|
||||||
return 0.5f;
|
float sum_dist = 0.0f;
|
||||||
|
int count = 0;
|
||||||
|
for (size_t i = 0; i < gladiators_.size(); ++i) {
|
||||||
|
for (size_t j = i + 1; j < gladiators_.size(); ++j) {
|
||||||
|
float dist_sq = 0.0f;
|
||||||
|
for (size_t w = 0; w < GENOME_SIZE; ++w) {
|
||||||
|
float diff = gladiators_[i].genome.weights[w] - gladiators_[j].genome.weights[w];
|
||||||
|
dist_sq += diff * diff;
|
||||||
|
}
|
||||||
|
sum_dist += std::sqrt(dist_sq);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sum_dist / static_cast<float>(count);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<FuzzyController*> Population::get_elites() {
|
std::vector<FuzzyController*> Population::get_elites() {
|
||||||
// TODO: Implement strategy-aware elite selection
|
if (gladiators_.size() <= static_cast<size_t>(ELITE_COUNT)) {
|
||||||
return {};
|
std::vector<FuzzyController*> ptrs;
|
||||||
|
ptrs.reserve(gladiators_.size());
|
||||||
|
for (auto& g : gladiators_) {
|
||||||
|
ptrs.push_back(&g);
|
||||||
|
}
|
||||||
|
return ptrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<float, FuzzyController*>> candidates;
|
||||||
|
candidates.reserve(gladiators_.size());
|
||||||
|
for (auto& g : gladiators_) {
|
||||||
|
float effective_fitness = 0.0f;
|
||||||
|
if (elite_strategy_ == EliteStrategy::AgePenalty) {
|
||||||
|
effective_fitness = g.fitness / std::log(static_cast<float>(g.age) + 2.0f);
|
||||||
|
} else if (elite_strategy_ == EliteStrategy::EMA) {
|
||||||
|
effective_fitness = g.ema_fitness;
|
||||||
|
} else if (elite_strategy_ == EliteStrategy::Rolling) {
|
||||||
|
if (!g.fitness_history.empty()) {
|
||||||
|
float sum = 0.0f;
|
||||||
|
for (float f : g.fitness_history) sum += f;
|
||||||
|
effective_fitness = sum / g.fitness_history.size();
|
||||||
|
} else {
|
||||||
|
effective_fitness = g.fitness;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
effective_fitness = g.fitness;
|
||||||
|
}
|
||||||
|
candidates.push_back({effective_fitness, &g});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(candidates.begin(), candidates.end(),
|
||||||
|
[](const std::pair<float, FuzzyController*>& a, const std::pair<float, FuzzyController*>& b) {
|
||||||
|
return a.first > b.first;
|
||||||
|
});
|
||||||
|
|
||||||
|
std::vector<FuzzyController*> elites;
|
||||||
|
elites.reserve(ELITE_COUNT);
|
||||||
|
for (int i = 0; i < ELITE_COUNT; ++i) {
|
||||||
|
elites.push_back(candidates[i].second);
|
||||||
|
}
|
||||||
|
return elites;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Population::add_to_repository(const FuzzyController& controller) {
|
void Population::add_to_repository(const FuzzyController& controller) {
|
||||||
// TODO: Maintain sorted repository
|
auto it = std::lower_bound(repository_.begin(), repository_.end(), controller,
|
||||||
|
[](const FuzzyController& a, const FuzzyController& b) {
|
||||||
|
return a.fitness > b.fitness;
|
||||||
|
});
|
||||||
|
repository_.insert(it, controller);
|
||||||
|
|
||||||
|
if (repository_.size() > 1000) {
|
||||||
|
repository_.resize(1000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace fces
|
} // namespace fces
|
||||||
|
|||||||
Reference in New Issue
Block a user