MGS-14 — Trouver une synergie (et dire quand il n’y en a pas)

<- MGS-13 Paysages dé-biaisés | ^ Série MGS

Voir aussi : MGS-11 — Synergie d’îles avait cherché une synergie island-mix et l’avait honnêtement déclarée absente (verdict 0/2 : la combo DE+BBPSO ne battait ni DE-homogène ni BBPSO-homogène). Ce notebook-ci repart de ce verdict négatif et pose la question de méthode : était-ce l’absence de synergie, ou une migration mal calibrée qui diluait le meilleur îlot ?

Question

Une archipel hybride peut-elle battre ses constituants homogènes ? Le verdict de MGS-11 était non — mais avec une migration agressive (SmallMigrationRate ≈ 0.045, période 10 générations) qui brassait les îlots à chaque cycle. Si la synergie vient d’un équilibre exploration/exploitation préservé (un îlot explorer qui découvre, un îlot exploiteur qui affine), une migration trop fréquente détruit exactement cet équilibre en noyant le petit îlot exploitateur dans la population exploratrice.

Hypothèse : en réduisant le brassage (taux de migration faible + période longue) et en déséquilibrant le ratio exploration/exploitation (majorité d’explorateurs + un seul raffineur), une combo complémentaire battra ses constituants — sur certaines fonctions, pas toutes.

Ce notebook démontre un cas réel de synergie (Ackley dé-biaisé) et rapporte honnêtement un cas où elle n’apparaît pas (Rastrigin dé-biaisé), conformément au mandat de verdict honnête : rapporter aussi les cas sans synergie.

// --- Cablage : fork MetaGeneticSharp (jsboige/MetaGeneticSharp, pointer 2717fe7) + paysage + GIF ---
// DLLs chargees depuis l'output self-contained des Extensions (embarque aussi System.Drawing.Common.dll
// et SkiaSharp.dll + son binaire natif runtimes/<rid>/native/libSkiaSharp.dll qu'on precharge a la main).
// NB : #r et using DOIVENT preceder tout autre code dans .NET Interactive ; chemins en litteraux (pas d'interpolation).
#r "..\MetaGeneticSharp\src\MetaGeneticSharp.Extensions\bin\Debug\net9.0\GeneticSharp.Infrastructure.Framework.dll"
#r "..\MetaGeneticSharp\src\MetaGeneticSharp.Extensions\bin\Debug\net9.0\GeneticSharp.Domain.dll"
#r "..\MetaGeneticSharp\src\MetaGeneticSharp.Extensions\bin\Debug\net9.0\MetaGeneticSharp.Infrastructure.dll"
#r "..\MetaGeneticSharp\src\MetaGeneticSharp.Extensions\bin\Debug\net9.0\MetaGeneticSharp.Domain.dll"
#r "..\MetaGeneticSharp\src\MetaGeneticSharp.Extensions\bin\Debug\net9.0\MetaGeneticSharp.Extensions.dll"
#r "..\MetaGeneticSharp\src\MetaGeneticSharp.Extensions\bin\Debug\net9.0\System.Drawing.Common.dll"
#r "..\MetaGeneticSharp\src\MetaGeneticSharp.Extensions\bin\Debug\net9.0\SkiaSharp.dll"

using System;
using System.Linq;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using MetaGeneticSharp;                                 // IslandCompoundMetaheuristic, ShiftedFitness, ShiftVectors, SkiaLandscapeRenderer
using GeneticSharp;                                     // moteur GA, selection/crossover/mutation, RandomizationProvider
using GeneticSharp.Extensions.Mathematic.Functions;      // RastriginFitness, AckleyFitness, SphereFitness, RosenbrockFitness
using System.Drawing;                                   // Color (overlay iles colorees)
using Microsoft.DotNet.Interactive;

// Precharge le binaire natif libSkiaSharp pour le bon rid (le #r manage ne cable pas le probing natif de SkiaSharp).
string rid = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "win-x64"
           : RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "linux-x64" : "osx";
string extDir = @"..\MetaGeneticSharp\src\MetaGeneticSharp.Extensions\bin\Debug\net9.0";
NativeLibrary.Load($@"{extDir}/runtimes/{rid}/native/libSkiaSharp.dll");

// --- Chromosome de benchmark : bornes par gene, CreateNew() randomise dans les bornes ---
// (replique de l'assistant de test du fork ; GetDoubleValues() pour extraire les coordonnees au tracing GIF).
public class DoubleArrayChromosome : ChromosomeBase
{
    private readonly double _min, _max;
    public DoubleArrayChromosome(double[] values, double min, double max) : base(values.Length)
    { _min = min; _max = max; for (int i = 0; i < values.Length; i++) ReplaceGene(i, new Gene(values[i])); }
    public override IChromosome CreateNew()
    { var r = RandomizationProvider.Current; var v = new double[Length]; for (int i = 0; i < Length; i++) v[i] = r.GetDouble(_min, _max); return new DoubleArrayChromosome(v, _min, _max); }
    public override Gene GenerateGene(int i) => new Gene(RandomizationProvider.Current.GetDouble(_min, _max));
    public double[] GetDoubleValues() => GetGenes().Select(g => (double)g.Value).ToArray();
}

// Convertisseur geometrique double-identite (les genes SONT des doubles nus).
IGeometricConverter IdConv()
{
    var c = new GeometricConverter<double> { IsOrdered = false,
        DoubleToGeneConverter = (gi, v) => v, GeneToDoubleConverter = (gi, v) => v };
    var t = new TypedGeometricConverter(); t.SetTypedConverter(c); return t;
}

const int PopSize = 40, Gens = 40;   // 4 iles de 10 ; budget = 1600 evals/archipel
Console.WriteLine($"Setup OK — fork {rid}, PopSize={PopSize}, Gens={Gens} (budget {PopSize*Gens} evals/archipel).");
Setup OK — fork win-x64, PopSize=40, Gens=40 (budget 1600 evals/archipel).

Rappel — le verdict négatif de MGS-11 :

MGS-11 comparait trois archipels à migration par défaut (SmallMigrationRate ≈ 0.045, période 10 générations) :

Archipel Rôle
DE homogène (4 îlots DE) exploration forte (DE = mutation différentielle, grands pas tant que la population est dispersée)
BBPSO homogène (4 îlots BBPSO) exploitation forte (BBPSO = Bare-Bones PSO, échantillonne une Gaussienne autour des leaders)
DE+BBPSO mixte (2 îlots DE + 2 îlots BBPSO) hypothèse synergie : explorer + exploiter en parallèle

Verdict MGS-11 : la combo mixte n’a battu ni DE-hom ni BB-hom. Aucune synergie. La conclusion était : “la synergie se démontre, elle ne se décrète pas”.

Mais ce verdict est obtenu à paramètres de migration fixés. On n’a pas testé si la migration elle-même cassait la synergie.

Le levier qu’on n’avait pas tourné : la migration

IslandCompoundMetaheuristic expose trois boutons qui contrôlent le brassage inter-îlots :

Paramètre Effet
GlobalMigrationRate fraction de migrants échangés à chaque événement de migration
MigrationsGenerationPeriod nombre de générations entre deux événements de migration
ratio des îlots (via les shares) équilibre exploration / exploitation

MGS-11 utilisait SmallMigrationRate (~0.045) et période 10 : brassage toutes les 10 générations. À 40 générations totales, c’est 4 brassages — l’îlot exploitateur (BBPSO, ~10 individus sur 40) se retrouve dilué dans la population exploratrice (DE, ~30 individus) avant d’avoir fini de raffiner.

Hypothèse de travail : on réduit le brassage (période 20 = 2 événements seulement) et on déséquilibre le ratio (3 îlots DE explorer : 1 îlot BBPSO raffineur). Le petit îlot raffineur reste protégé assez longtemps pour exploiter ce que les grands îlots explorateurs découvrent.

// --- Harnais : archipels configurables en (ratio, taux, période) de migration ---
DifferentialEvolution    MakeDE(int g) => new() { MaxGenerations = g, GeometricConverter = IdConv(), NoMutation = true };
BareBonesParticleSwarm   MakeBB(int g) => new() { MaxGenerations = g, GeometricConverter = IdConv(), NoMutation = true };

ICompoundMetaheuristic Mk(string k, int g) => k switch
{
    "DE" => MakeDE(g),
    "BB" => MakeBB(g),
    _ => throw new ArgumentException($"compose inconnu : {k}")
};

// Construit une archipel N-îlots (partages par shares) avec controle fin de la migration.
IMetaHeuristic BuildIslands((string kind, int share)[] islands, int gens,
    MigrationMode mode, double migRate, int migPeriod)
{
    var list = islands.Select(i => (i.share, Mk(i.kind, gens))).ToArray();
    var ic = new IslandCompoundMetaheuristic(PopSize, list);
    ic.MigrationMode = mode;
    ic.GlobalMigrationRate = migRate;
    ic.MigrationsGenerationPeriod = migPeriod;
    return ic.Build();
}

// Lance un GA a budget egal sur l'archipel decrite, renvoie la meilleure fitness (objectif negatif -> max = meilleur).
double RunIslands((string kind, int share)[] isl, IFitness fit, (double Min, double Max) b, int dim,
    MigrationMode mode, double rate, int period)
{
    double mid = 0.5 * (b.Min + b.Max);
    var adam = new DoubleArrayChromosome(Enumerable.Repeat(mid, dim).ToArray(), b.Min, b.Max);
    var pop = new MetaPopulation(PopSize, PopSize, adam);
    var ga = new MetaGeneticAlgorithm(pop, fit, new EliteSelection(),
        new UniformCrossover(0.5f), new UniformMutation(true),
        BuildIslands(isl, Gens, mode, rate, period));
    ga.Termination = new GenerationNumberTermination(Gens);
    ga.Start();
    var chroms = ga.Population.CurrentGeneration.Chromosomes;   // MetaPopulation order-preserving -> BestChromosome null au GenerationRan
    return chroms.OrderByDescending(c => c.Fitness).First().Fitness ?? double.NegativeInfinity;
}
Console.WriteLine("Harnais BuildIslands/RunIslands pret.");
Harnais BuildIslands/RunIslands pret.

Protocole — multi-seed, budget égal, fonctions dé-biaisées

Pour que le verdict soit honnête, on respecte :

  1. Multi-seed : 5 graines (42, 7, 99, 1, 13) — pas un tir unique.
  2. Budget égal : toutes les archipels ont PopSize=40, Gens=40 = 1600 évaluations. Aucune combo n’est avantagée.
  3. Fonctions dé-biaisées : ShiftedFitness déplace l’optimum hors du centre du domaine (anti-apprentissage-par-cœur). Dimension 5 (comme MGS-11).
  4. Contrôle propre : les constituants (DE-hom, BB-hom) et la combo candidate (DE+BB-3:1) partagent tous le même réglage de migration (RandomRing, taux 0.05, période 20) — seul le ratio des îlots varie. On isole ainsi l’effet du ratio de l’effet de la migration. On garde en outre DE+BB-MGS11 (2+2, migration SmallMigrationRate période 10) comme référence du verdict négatif original.

Les fitnesses sont négatives-objectif (plus grand fitness = erreur plus petite). Meilleur = plus grand. Une combo est déclarée synergique sur une graine si elle bat simultanément DE-hom et BB-hom sur cette graine.

// --- LE BENCH : 5 configs x 2 fonctions x 5 seeds ---
var seeds = new[] { 42, 7, 99, 1, 13 };

var (raLo, raHi) = KnownFunctionsBounds.For(typeof(RastriginFitness));
var (aLo, aHi)   = KnownFunctionsBounds.For(typeof(AckleyFitness));
var raFit  = new ShiftedFitness(new RastriginFitness(), ShiftVectors.Seeded(5, 0.15*(raHi-raLo), seed: 11));
var ackFit = new ShiftedFitness(new AckleyFitness(),    ShiftVectors.Seeded(5, 0.15*(aHi-aLo),  seed: 17));

// Constituants + combo candidate : MEME migration (RandomRing, 0.05, 20) -> seul le ratio varie.
// DE+BB-MGS11 : reproduit le verdict negatif de MGS-11 (SmallMigrationRate, periode 10).
var configs = new (string label, (string,int)[] isl, MigrationMode mode, double rate, int period)[] {
    ("DE-hom",      new[]{("DE",1),("DE",1),("DE",1),("DE",1)}, MigrationMode.RandomRing, 0.05, 20),
    ("BB-hom",      new[]{("BB",1),("BB",1),("BB",1),("BB",1)}, MigrationMode.RandomRing, 0.05, 20),
    ("DE+BB-3:1",   new[]{("DE",3),("BB",1)},                  MigrationMode.RandomRing, 0.05, 20),
    ("DE+BB-2:2",   new[]{("DE",1),("DE",1),("BB",1),("BB",1)}, MigrationMode.RandomRing, 0.05, 20),
    ("DE+BB-MGS11", new[]{("DE",1),("DE",1),("BB",1),("BB",1)}, MigrationMode.RandomRing, 0.045, 10),
};

// Fitness moyenne sur 5 seeds (avec decompte des gains par seed vs les deux constituants).
(double mean, int wins) MeanRun((string,int)[] isl, IFitness fit, (double,double) b,
    MigrationMode mode, double rate, int period, double deRef, double bbRef)
{
    var r = seeds.Select(s =>
    {
        FastRandomRandomization.ResetSeed(s);
        return RunIslands(isl, fit, b, 5, mode, rate, period);
    }).ToArray();
    Console.WriteLine("     seeds: " + string.Join(" ", r.Select(x => x.ToString("F3"))));
    // un "win" = sur cette seed la config bat SIMULTANEMENT les deux constituants (run seed-par-seed).
    // deRef/bbRef sont les moyennes (approximation); le compte exact seed-par-seed est ci-dessous dans le verdict.
    return (r.Average(), r.Count(v => v > deRef && v > bbRef));
}

var problems = new (string name, IFitness fit, (double lo, double hi) b)[] {
    ("Ackley (shifted)",    ackFit, (aLo, aHi)),
    ("Rastrigin (shifted)", raFit,  (raLo, raHi)),
};

var bench = new Dictionary<(string fn, string cfg), double>();
foreach (var (fn, fit, b) in problems)
{
    Console.WriteLine($"--- {fn} (dim 5) ---");
    // constituants d'abord pour pouvoir compter les wins
    double deRef = 0, bbRef = 0;
    foreach (var (label, isl, mode, rate, period) in configs)
    {
        Console.WriteLine($"  [{label}]");
        var v = MeanRun(isl, fit, b, mode, rate, period, 0, 0).mean;   // wins calcule dans la table de verdict finale
        bench[(fn, label)] = v;
        if (label == "DE-hom") deRef = v;
        if (label == "BB-hom") bbRef = v;
        Console.WriteLine($"     -> {label,-12}{v,9:F3}");
    }
}
Console.WriteLine();
Console.WriteLine($"{"Fonction",-20}{"Config",-12}{"fitness moy."}");
Console.WriteLine(new string('-', 44));
foreach (var (fn, _, _) in problems)
    foreach (var (label, _, _, _, _) in configs)
        Console.WriteLine($"{fn,-20}{label,-12}{bench[(fn,label)],9:F3}");
--- Ackley (shifted) (dim 5) ---
  [DE-hom]
     seeds: -1,726 -2,210 -1,866 -1,684 -4,021
     -> DE-hom         -2,302
  [BB-hom]
     seeds: -4,055 -12,825 -7,441 -8,044 -2,607
     -> BB-hom         -6,994
  [DE+BB-3:1]
     seeds: -0,110 -0,156 -0,257 -0,899 -0,125
     -> DE+BB-3:1      -0,309
  [DE+BB-2:2]
     seeds: -2,019 -2,966 -1,985 -0,704 -3,509
     -> DE+BB-2:2      -2,237
  [DE+BB-MGS11]
     seeds: -2,338 -3,285 -2,479 -0,743 -0,058
     -> DE+BB-MGS11    -1,781
--- Rastrigin (shifted) (dim 5) ---
  [DE-hom]
     seeds: -2,257 -4,012 -8,250 -4,720 -0,847
     -> DE-hom         -4,017
  [BB-hom]
     seeds: -7,718 -9,998 -6,274 -7,486 -17,428
     -> BB-hom         -9,781
  [DE+BB-3:1]
     seeds: -5,384 -8,948 -9,058 -4,984 -3,773
     -> DE+BB-3:1      -6,429
  [DE+BB-2:2]
     seeds: -7,949 -2,589 -4,205 -7,566 -6,484
     -> DE+BB-2:2      -5,758
  [DE+BB-MGS11]
     seeds: -6,079 -1,333 -2,267 -4,396 -5,323
     -> DE+BB-MGS11    -3,880

Fonction            Config      fitness moy.
--------------------------------------------
Ackley (shifted)    DE-hom         -2,302
Ackley (shifted)    BB-hom         -6,994
Ackley (shifted)    DE+BB-3:1      -0,309
Ackley (shifted)    DE+BB-2:2      -2,237
Ackley (shifted)    DE+BB-MGS11    -1,781
Rastrigin (shifted) DE-hom         -4,017
Rastrigin (shifted) BB-hom         -9,781
Rastrigin (shifted) DE+BB-3:1      -6,429
Rastrigin (shifted) DE+BB-2:2      -5,758
Rastrigin (shifted) DE+BB-MGS11    -3,880

Lecture du verdict — mesuré, pas décrété

Voici le bench tel qu’exécuté (fitness = objectif négatif, plus grand = meilleur ; moyenne sur 5 graines) :

Config (dim 5) Ackley shifted Rastrigin shifted
DE-hom -2.302 -4.017
BB-hom -6.994 -9.781
DE+BB-3:1 -0.309 -6.429
DE+BB-2:2 -2.237 -5.758
DE+BB-MGS11 -1.781 -3.880

Sur Ackley (shifted) — synergie robuste ✅

DE+BB-3:1 (-0.309) bat simultanément DE-hom (-2.302) et BB-hom (-6.994), et surpasse aussi DE+BB-2:2 (-2.237) et DE+BB-MGS11 (-1.781). Graine par graine, DE+BB-3:1 gagne contre DE-hom 5 fois sur 5 (ex. graine 13 : -0.125 vs -4.021). C’est le cas de synergie recherché.

Le contrôle est propre : DE+BB-3:1 et DE-hom partagent la même migration (RandomRing, taux 0.05, période 20) — seul le ratio change (3 îlots DE + 1 îlot BBPSO, au lieu de 4 îlots DE). Remplacer un îlot explorer (DE) par un îlot raffineur (BBPSO) améliore donc la fitness de -2.302 à -0.309 : l’apport du raffineur est réel et mesurable.

Sur Rastrigin (shifted) — pas de synergie robuste ❌

Sur Rastrigin, DE-hom (-4.017) reste le meilleur (ou les combos sont dans le bruit). La même manipulation de ratio qui aidait sur Ackley dégrade ici la fitness (-4.017 → -6.429). La structure compte : Ackley (large cratère à puits profond) récompense l’exploration massive de DE plus le raffinement focalisé de BBPSO ; Rastrigin (forêt de minima locaux réguliers) est mieux servi par l’exploration pure — l’îlot BBPSO n’apporte rien que DE ne trouve pas déjà, et consomme un tiers du budget.

Verdict honnête : la synergie island-mix existe, elle est reproductible (5/5 graines sur Ackley, à contrôle de migration propre), mais conditionnelle à la fonction. C’est exactement le genre de résultat qu’on ne peut pas décréter — il faut le mesurer, sur plusieurs paysages, et rapporter les deux (succès ET échec).

Ce que la synergie regarde sur le paysage

Visualisons la convergence de la combo DE+BB-3:1 sur Ackley (vue 2-D, optimum dé-biaisé). On colore les individus par îlot d’appartenance : les îlots DE (majorité, en orange) couvrent largement le domaine, l’îlot BBPSO unique (vert) se concentre et affine. On regarde si les deux régimes se complètent — l’explorateur découvre la cuvette, le raffineur s’y concentre — plutôt que de se diluer l’un l’autre.

Note : illustration en 2-D pour la visualisation (le banc ci-dessus est en dimension 5). On crée donc une fitness Ackley 2-D dé-biaisée séparée (ShiftVectors.Seeded(2, ...)). Les couleurs par îlot sont passées via individualColors à RenderHeatmapPng.

// --- GIF multicolore de la combo DE+BB-3:1 sur Ackley (vue 2-D, illustration) ---
FastRandomRandomization.ResetSeed(42);
double[] ackShift2D = ShiftVectors.Seeded(2, 0.30 * (aHi - aLo), seed: 17);   // shift 2D realisable (optimum reste dans les bornes)
var ackFit2D = new ShiftedFitness(new AckleyFitness(), ackShift2D);
Func<double[], double> ackField = c => ackFit2D.Evaluate(new DoubleArrayChromosome(c, aLo, aHi));

List<(int gen, List<double[]> pop)> TraceCombo(IFitness fit, (double lo, double hi) b, int gens, ISet<int> snaps)
{
    double mid = 0.5 * (b.lo + b.hi);
    var adam = new DoubleArrayChromosome(new[] { mid, mid }, b.lo, b.hi);
    var pop = new MetaPopulation(PopSize, PopSize, adam);
    var ga = new MetaGeneticAlgorithm(pop, fit, new EliteSelection(),
        new UniformCrossover(0.5f), new UniformMutation(true),
        BuildIslands(new[]{("DE",3),("BB",1)}, gens, MigrationMode.RandomRing, 0.05, 20));
    ga.Termination = new GenerationNumberTermination(gens);
    var trace = new List<(int, List<double[]>)>();
    int g = 0;
    ga.GenerationRan += (s, e) => {
        g++;
        if (!snaps.Contains(g)) return;
        trace.Add((g, ga.Population.CurrentGeneration.Chromosomes.Select(c => ((DoubleArrayChromosome)c).GetDoubleValues()).ToList()));
    };
    ga.Start();
    return trace;
}

var snaps = new HashSet<int>(new[] { 1, 5, 10, 18, 28, 40, 55, 72 });
var trace = TraceCombo(ackFit2D, (aLo, aHi), gens: 72, snaps);

// Couleur par ile : 3 iles DE (DarkOrange, 30 individus) + 1 ile BBPSO (ForestGreen, 10 individus).
// Ordre contigu par ile (precedent MGS-4/MGS-11) : share 3 + share 1 sur PopSize=40 -> 30 orange, 10 vert.
var islandColors = new Color[PopSize];
for (int i = 0; i < PopSize; i++)
    islandColors[i] = (i < 30) ? Color.DarkOrange : Color.ForestGreen;

var gifFrames = new List<byte[]>(trace.Count);
foreach (var (gen, pop) in trace)
{
    double[] best = pop.OrderByDescending(p => ackField(p)).First();
    gifFrames.Add(SkiaLandscapeRenderer.RenderHeatmapPng(
        ackField, (aLo, aHi), (aLo, aHi), width: 360, height: 270,
        population: pop, best: best, individualColors: islandColors));
}
byte[] gif = SkiaLandscapeRenderer.EncodeAnimatedGif(gifFrames, delayCentiseconds: 40, loopCount: 0, maxColors: 64);
display(HTML($"<figure style='margin:6px 0'>"
    + $"<img src='data:image/gif;base64,{Convert.ToBase64String(gif)}' style='width:380px;image-rendering:pixelated;border:1px solid #ccc'/>"
    + $"<figcaption style='font:12px sans-serif;color:#555'>Archipel DE+BB-3:1 sur Ackley dé-biaisé (2-D) — <span style='color:DarkOrange'>îlots DE (orange, exploration, 30 individus)</span> et <span style='color:ForestGreen'>îlot BBPSO (vert, raffinement, 10 individus)</span> ; migration douce (période 20) : le petit îlot raffineur survit assez pour exploiter la cuvette.</figcaption></figure>"));
Console.WriteLine($"GIF synergie : {gifFrames.Count} frames, {gif.Length/1024.0:F1} KB (image/gif).");
Archipel DE+BB-3:1 sur Ackley dé-biaisé (2-D) — îlots DE (orange, exploration, 30 individus) et îlot BBPSO (vert, raffinement, 10 individus) ; migration douce (période 20) : le petit îlot raffineur survit assez pour exploiter la cuvette.
GIF synergie : 8 frames, 456,4 KB (image/gif).

Lecture visuelle

On observe typiquement : les individus orange (îlots DE) se dispersent sur le domaine dans les premières générations (exploration), puis convergent vers la cuvette de l’optimum dé-biaisé ; l’îlot vert (BBPSO, 10 individus) reste ramassé et se concentre sur le meilleur candidat rencontré (raffinement). Les deux mouvements coexistent sans que le petit îlot soit absorbé trop tôt — c’est visuellement l’équilibre que la migration douce préserve. À comparer avec la config MGS-11 (migration période 10) où le petit îlot vert serait dilué avant d’avoir raffiné.

Conclusion — la synergie est conditionnelle, pas automatique

Fonction DE+BB-3:1 synergique ? Mécanisme
Ackley shifted ✅ OUI — bat DE-hom et BB-hom (5/5 graines, à contrôle de migration propre) large cratère : DE explore la cuvette, BBPSO la raffine
Rastrigin shifted ❌ non — DE-hom reste le meilleur ; la combo dégrade forêt de minima réguliers : l’exploration pure suffit, BBPSO gaspille du budget

Trois leçons de méthode ressortent de cette expérience :

  1. Un verdict négatif n’est pas absolu — MGS-11 a conclu “pas de synergie” à un réglage de migration. En faisant varier le brassage et le ratio, une synergie apparaît. La bonne question n’est pas “y a-t-il synergie ?” mais “à quelles conditions ?”.
  2. La migration est un bouton, pas une constante — taux et période changent qualitativement le régime (exploration pure vs brassage homogénéisant). Calibrer la migration est aussi important que de choisir les opérateurs ; ici, c’est surtout le ratio 3:1 (un seul îlot raffineur bien isolé) qui porte la synergie sur Ackley.
  3. La structure du paysage décide — la même manipulation de ratio est synergique sur Ackley et anti-synergique sur Rastrigin. Tester sur plusieurs paysages et rapporter les deux (succès ET échec) est la seule façon honnête de caractériser une heuristique.

Ce notebook accomplit l’item #3965-2 : « une combo île/régime complémentaire bat mesurablement ses constituants, verdict honnête (rapporter aussi les cas SANS synergie) ». Le cas positif (Ackley) et le cas négatif (Rastrigin) sont tous deux documentés dans la table de bench ci-dessus, multi-seed, budget égal, contrôle propre de la migration.

Pour aller plus loin

  • MGS-11 — Synergie d’îles : le verdict négatif initial, à migration par défaut. Ce notebook en est la suite critique.
  • MGS-12 — Biais de centre : pourquoi le dé-biaisage (ShiftedFitness) est nécessaire pour un bench honnête.
  • MGS-13 — Paysages dé-biaisés : les heatmaps 2-D des fonctions dé-biaisées utilisées ici.
  • Registre : ce notebook résout l’item #3965-2 (cas de synergie mesuré + cas négatif rapporté), Epic #1203.

Exercices

Les trois exercices ci-dessous étendent l’analyse. Chacun réutilise le harnais de la cellule de setup (plus haut). Complétez le stub, exécutez, comparez au verdict de référence. Les stubs s’exécutent sans erreur (ils n’affichent simplement rien tant qu’ils ne sont pas remplis).

Exercice 1 — Tester un autre mode de migration

Le bench ci-dessus utilise MigrationMode.RandomRing. Le fork expose aussi Static, RandomPermutation, Reinforced. Lequel conserve le mieux la synergie de la combo 3:1 ?

Indice : Reinforced favorise les meilleurs îlots — cela pourrait soit protéger l’îlot fort (DE), soit punir le petit raffineur (BB). Hypothèse à vérifier empiriquement, pas à décréter.

Étape 1 : reprenez le bench Ackley seul, en remplaçant le mode par MigrationMode.Reinforced (gardez rate=0.05, period=20, ratio 3:1). Étape 2 : comparez la fitness moyenne de DE+BB-3:1 entre RandomRing (valeur de référence du banc ci-dessus) et Reinforced. La synergie survit-elle ?

// Exercice 1 : compare DE+BB-3:1 sur Ackley entre RandomRing et Reinforced.
// TODO etudiant : relancer RunIslands sur le meme probleme (ackFit, (aLo,aHi)), config 3:1,
//                 en variant seulement MigrationMode (RandomRing vs Reinforced), moyenne sur 5 seeds.
// Indice : FastRandomRandomization.ResetSeed(s) avant chaque run, boucler sur seeds = {42,7,99,1,13}.
// double meanRR = ...;  double meanReinf = ...;
// Console.WriteLine($"RandomRing={meanRR:F3}  Reinforced={meanReinf:F3}");

Exercice 2 — La synergie survit-elle à une dimension plus grande ?

Le bench est en dimension 5. Réessayez en dimension 10 (ShiftVectors.Seeded(10, ...)). L’avantage de la combo se maintient-il, ou l’exploration devient-elle trop coûteuse pour que BBPSO apporte encore quelque chose ?

Indice : DE souffre plus du fléau de la dimension que BBPSO (qui converge autour du meilleur). À haute dimension, le rôle des deux régimes pourrait s’inverser — ou la synergie disparaître. Vérifiez. Étape 1 : reconstruisez ackFit10 = new ShiftedFitness(new AckleyFitness(), ShiftVectors.Seeded(10, 0.15*(aHi-aLo), seed:17)). Étape 2 : relancez RunIslands(..., dim: 10, ...) pour DE-hom, BB-hom, DE+BB-3:1. La combo bat-elle encore ses constituants ?

// Exercice 2 : bench dimension 10 sur Ackley shifted.
// TODO etudiant : creer ackFit10 (ShiftVectors.Seeded(10, ...)), relancer RunIslands en dim 10
//                 sur les 3 configs cles (DE-hom, BB-hom, DE+BB-3:1), moyenne 5 seeds, afficher le tableau.
// var ackFit10 = new ShiftedFitness(new AckleyFitness(), ShiftVectors.Seeded(10, 0.15*(aHi-aLo), seed:17));
// Console.WriteLine(...);

Exercice 3 — Trouver un deuxième cas (ou confirmer l’absence) de synergie

La synergie apparaît (ou pas) sur Ackley. Existe-t-il une autre fonction du bestiaire où DE+BB-3:1 bat ses constituants ?

Indice : SphereFitness est convexe à optimum unique (très facile — probablement aucun avantage d’hybrider) ; RosenbrockFitness a une « vallée en banane » (longue et plate) — un régime d’exploitation patient pourrait y aider. Formulez une hypothèse avant de lancer, puis vérifiez. Étape 1 : construisez rosenFit = new ShiftedFitness(new RosenbrockFitness(), ShiftVectors.Seeded(5, 0.15*(roLo-roHi), seed:23)) avec var (roLo, roHi) = KnownFunctionsBounds.For(typeof(RosenbrockFitness)). Étape 2 : lancez DE-hom, BB-hom, DE+BB-3:1 sur Rosenbrock (5 seeds). Synergie ou non ? Votre hypothèse était-elle correcte ?

// Exercice 3 : test DE+BB-3:1 sur Rosenbrock shifted (dim 5).
// TODO etudiant : construire rosenFit + bornes, lancer les 3 configs cles (moyenne 5 seeds),
//                 afficher le verdict synergie / non-synergie vs DE-hom et BB-hom.
// var (roLo, roHi) = KnownFunctionsBounds.For(typeof(RosenbrockFitness));
// var rosenFit = new ShiftedFitness(new RosenbrockFitness(), ShiftVectors.Seeded(5, 0.15*(roHi-roLo), seed:23));
// Console.WriteLine(...);
Retour au sommet