final implementation of shc

This commit is contained in:
2025-09-26 14:18:00 +02:00
parent 0ace1cec3c
commit 90e67652ab
12 changed files with 186 additions and 462 deletions

View File

@@ -1,22 +1,14 @@
# trainer.py
import asyncio
import os
import time
from typing import Any, Dict, List, Tuple, Optional
import morphology # dein morphology.py
import morphology
from genotype import construct, save_genotype, print_genotype
from exoself import Exoself # deine Actor-basierte Exoself-Implementierung
from exoself import Exoself
class Trainer:
"""
Stochastischer Hillclimber wie im Erlang-Original.
- morphology_spec: entweder das morphology-Modul, ein String-Key, oder eine Callable-Morphologie
- hidden_layer_densities: z.B. [4, 3]
- max_attempts / eval_limit / fitness_target: Stoppbedingungen
- experimental_file / best_file: Pfade, falls du wie im Buch speichern willst
"""
def __init__(
self,
morphology_spec=morphology,
@@ -36,12 +28,8 @@ class Trainer:
self.fitness_target = fitness_target
self.experimental_file = experimental_file
self.best_file = best_file
# Wenn deine Exoself/Cortex eine feste Anzahl Zyklen pro „Evaluation“ braucht,
# kannst du hier default 0 lassen (Cortex entscheidet über Halt-Flag),
# oder einen Wert setzen.
self.exoself_steps_per_eval = exoself_steps_per_eval
# Laufende Akkus (wie im Erlang-Code)
self.best_fitness = float("-inf")
self.best_genotype: Optional[Dict[str, Any]] = None
@@ -49,57 +37,38 @@ class Trainer:
self.cycle_acc = 0
self.time_acc = 0.0
async def _run_one_attempt(self) -> Tuple[float, int, int, float, Dict[str, Any]]:
"""
Ein Trainingsversuch:
- Genotyp konstruieren
- Exoself laufen lassen
- Fitness/Evals/Cycles/Time zurückgeben + den verwendeten Genotyp
"""
async def _run_one_attempt(self) -> Tuple[float, int, int, float]:
print("constructing genotype...")
geno = construct(self.morphology_spec, self.hds, file_name=self.experimental_file, add_bias=True)
# Exoself starten und bis zum evaluation_completed warten
geno = construct(
self.morphology_spec,
self.hds,
file_name=self.experimental_file, # <-- schreibt Startnetz nach experimental.json
add_bias=True
)
fitness, evals, cycles, elapsed = await self._evaluate_with_exoself(geno)
return fitness, evals, cycles, elapsed, geno
async def _evaluate_with_exoself(self, genotype: Dict[str, Any]) -> Tuple[float, int, int, float]:
"""
Startet Exoself (deine Actor-basierte Variante) und wartet,
bis der Cortex die Evaluation abgeschlossen hat.
Erwartete Rückgabe: fitness, evals, cycles, elapsed
"""
print("creating exoself...")
ex = Exoself(genotype)
# Exoself.run() sollte idealerweise einen Tuple (fitness, evals, cycles, time)
# liefern. Falls deine Version aktuell „backup“-Listen liefert, ersetze das hier
# mit der passenden Logik oder benutze das „evaluation_completed“-Signal aus dem Cortex.
fitness, evals, cycles, elapsed = await ex.run_evaluation()
return fitness, evals, cycles, elapsed
async def _evaluate_with_exoself(self, genotype: Dict[str, Any]) -> Tuple[float, int, int, float]:
print("creating exoself...")
ex = Exoself(genotype, file_name=self.experimental_file)
best_fitness, evals, cycles, elapsed = await ex.train_until_stop()
return best_fitness, evals, cycles, elapsed
async def go(self):
"""
Entspricht dem Erlang loop/…:
Wiederholt Versuche, bis Stoppbedingung erfüllt.
"""
attempt = 1
while True:
print(".........")
print("current attempt: ", attempt)
print(".........")
# Stoppbedingung vor Versuch?
if attempt > self.max_attempts or self.eval_acc >= self.eval_limit or self.best_fitness >= self.fitness_target:
# Ausgabe wie im Buch
if self.best_genotype and self.best_file:
# bestes Genotypfile ausgeben/„drucken“
save_genotype(self.best_file, self.best_genotype)
# Abschlussausgabe wie im Buch
if self.best_file and os.path.exists(self.best_file):
print_genotype(self.best_file)
print(
f" Morphology: {getattr(self.morphology_spec, '__name__', str(self.morphology_spec))} | "
f"Best Fitness: {self.best_fitness} | EvalAcc: {self.eval_acc}"
)
# Optional: an „Benchmarker“ melden bei dir vermutlich nicht nötig
return {
"best_fitness": self.best_fitness,
"eval_acc": self.eval_acc,
@@ -109,43 +78,35 @@ class Trainer:
}
print("RUN ONE ATTEMPT!")
# --- Ein Versuch ---
fitness, evals, cycles, elapsed, geno = await self._run_one_attempt()
fitness, evals, cycles, elapsed = await self._run_one_attempt()
print("update akkus...")
# Akkus updaten
self.eval_acc += evals
self.cycle_acc += cycles
self.time_acc += elapsed
# Besser als bisher?
if fitness > self.best_fitness:
# „experimental.json“ → „best.json“ (semantisch wie file:rename(...))
self.best_fitness = fitness
self.best_genotype = geno
if self.best_file:
save_genotype(self.best_file, geno)
# Reset Attempt-Zähler (wie Erlang: Attempt=0 nach Verbesserung)
if self.best_file and self.experimental_file and os.path.exists(self.experimental_file):
os.replace(self.experimental_file, self.best_file)
attempt = 1
else:
attempt += 1
# --------------------------
# Beispiel: ausführen
# --------------------------
if __name__ == "__main__":
# Beispielkonfiguration (XOR-Morphologie, kleine Topologie)
trainer = Trainer(
morphology_spec=morphology, # oder morphology.xor_mimic
hidden_layer_densities=[2], # wie im Buch-Beispiel
max_attempts=1000,
morphology_spec=morphology,
hidden_layer_densities=[2],
max_attempts=200,
eval_limit=float("inf"),
fitness_target=float("inf"),
fitness_target=99.9,
experimental_file="experimental.json",
best_file="best.json",
exoself_steps_per_eval=0, # 0 => Cortex/Scape steuern Halt-Flag
exoself_steps_per_eval=0,
)
asyncio.run(trainer.go())