final implementation of shc
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,95 +1,113 @@
|
|||||||
{
|
{
|
||||||
"cortex": {
|
"cortex": {
|
||||||
"id": 0.5107239887106383,
|
"id": 0.17818153832773553,
|
||||||
"sensor_ids": [
|
"sensor_ids": [
|
||||||
5.685637947817566e-10
|
5.685438203051634e-10
|
||||||
],
|
],
|
||||||
"actuator_ids": [
|
"actuator_ids": [
|
||||||
5.685637947817563e-10
|
5.685438203051628e-10
|
||||||
],
|
],
|
||||||
"neuron_ids": [
|
"neuron_ids": [
|
||||||
0.3776999353275148,
|
0.2035391483142075,
|
||||||
0.7030052650887313,
|
0.06722681140391684,
|
||||||
0.9936497204289092
|
0.646276420829124
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"sensor": {
|
"sensor": {
|
||||||
"id": 5.685637947817566e-10,
|
"id": 5.685438203051634e-10,
|
||||||
"name": "xor_GetInput",
|
"name": "xor_GetInput",
|
||||||
"vector_length": 2,
|
"vector_length": 2,
|
||||||
"cx_id": 0.5107239887106383,
|
"cx_id": 0.17818153832773553,
|
||||||
"fanout_ids": [
|
"fanout_ids": [
|
||||||
0.3776999353275148,
|
0.2035391483142075,
|
||||||
0.7030052650887313
|
0.06722681140391684
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"actuator": {
|
"actuator": {
|
||||||
"id": 5.685637947817563e-10,
|
"id": 5.685438203051628e-10,
|
||||||
"name": "xor_SendOutput",
|
"name": "xor_SendOutput",
|
||||||
"vector_length": 1,
|
"vector_length": 1,
|
||||||
"cx_id": 0.5107239887106383,
|
"cx_id": 0.17818153832773553,
|
||||||
"fanin_ids": [
|
"fanin_ids": [
|
||||||
0.9936497204289092
|
0.646276420829124
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"neurons": [
|
"neurons": [
|
||||||
{
|
{
|
||||||
"id": 0.3776999353275148,
|
"id": 0.2035391483142075,
|
||||||
"layer_index": 0,
|
"layer_index": 0,
|
||||||
"cx_id": 0.5107239887106383,
|
"cx_id": 0.17818153832773553,
|
||||||
"activation_function": "tanh",
|
"activation_function": "tanh",
|
||||||
"input_weights": [
|
"input_weights": [
|
||||||
{
|
{
|
||||||
"input_id": 5.685637947817566e-10,
|
"input_id": 5.685438203051634e-10,
|
||||||
"weights": [
|
"weights": [
|
||||||
-1.7328567115118854,
|
6.283185307179586,
|
||||||
0.31546591460152307
|
6.283185307179586
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input_id": "bias",
|
||||||
|
"weights": [
|
||||||
|
6.280908700445529
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"output_ids": [
|
"output_ids": [
|
||||||
0.24149710385676537
|
0.43659818311553367
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 0.7030052650887313,
|
"id": 0.06722681140391684,
|
||||||
"layer_index": 0,
|
"layer_index": 0,
|
||||||
"cx_id": 0.5107239887106383,
|
"cx_id": 0.17818153832773553,
|
||||||
"activation_function": "tanh",
|
"activation_function": "tanh",
|
||||||
"input_weights": [
|
"input_weights": [
|
||||||
{
|
{
|
||||||
"input_id": 5.685637947817566e-10,
|
"input_id": 5.685438203051634e-10,
|
||||||
"weights": [
|
"weights": [
|
||||||
1.507492385500833,
|
6.283185307179586,
|
||||||
-1.5181033637128052
|
6.283185307179586
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input_id": "bias",
|
||||||
|
"weights": [
|
||||||
|
-6.283185307179586
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"output_ids": [
|
"output_ids": [
|
||||||
0.24149710385676537
|
0.43659818311553367
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 0.9936497204289092,
|
"id": 0.646276420829124,
|
||||||
"layer_index": 1,
|
"layer_index": 1,
|
||||||
"cx_id": 0.5107239887106383,
|
"cx_id": 0.17818153832773553,
|
||||||
"activation_function": "tanh",
|
"activation_function": "tanh",
|
||||||
"input_weights": [
|
"input_weights": [
|
||||||
{
|
{
|
||||||
"input_id": 0.3776999353275148,
|
"input_id": 0.2035391483142075,
|
||||||
"weights": [
|
"weights": [
|
||||||
0.9998252528454215
|
6.283185307179586
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input_id": 0.7030052650887313,
|
"input_id": 0.06722681140391684,
|
||||||
"weights": [
|
"weights": [
|
||||||
-1.7243886895741118
|
-6.283185307179586
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input_id": "bias",
|
||||||
|
"weights": [
|
||||||
|
-6.283185307179586
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"output_ids": [
|
"output_ids": [
|
||||||
5.685637947817563e-10
|
5.685438203051628e-10
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
# actors/cortex.py
|
|
||||||
import time
|
import time
|
||||||
from actor import Actor
|
from actor import Actor
|
||||||
|
|
||||||
@@ -12,12 +11,12 @@ class Cortex(Actor):
|
|||||||
self.actuators = actuator_pids
|
self.actuators = actuator_pids
|
||||||
self.exoself_pid = exoself_pid
|
self.exoself_pid = exoself_pid
|
||||||
|
|
||||||
self.awaiting_sync = set() # AIDs, von denen wir im aktuellen Schritt noch Sync erwarten
|
self.awaiting_sync = set()
|
||||||
self.fitness_acc = 0.0 # akkumulierte Fitness über die Episode
|
self.fitness_acc = 0.0
|
||||||
self.ef_acc = 0 # akkumulierte EndFlags im aktuellen Schritt/Episode
|
self.ef_acc = 0
|
||||||
self.cycle_acc = 0 # Anzahl abgeschlossener Sense-Think-Act Zyklen
|
self.cycle_acc = 0
|
||||||
self.active = False # aktiv/inaktiv (wartet auf reactivate)
|
self.active = False
|
||||||
self._t0 = None # Startzeit der Episode
|
self._t0 = None
|
||||||
|
|
||||||
async def _kick_sensors(self):
|
async def _kick_sensors(self):
|
||||||
for s in self.sensors:
|
for s in self.sensors:
|
||||||
@@ -35,7 +34,6 @@ class Cortex(Actor):
|
|||||||
self.active = True
|
self.active = True
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
# Initialisierung: falls Actuatoren schon gesetzt sind, Episode starten
|
|
||||||
if self.actuators:
|
if self.actuators:
|
||||||
self._reset_for_new_episode()
|
self._reset_for_new_episode()
|
||||||
await self._kick_sensors()
|
await self._kick_sensors()
|
||||||
@@ -44,10 +42,8 @@ class Cortex(Actor):
|
|||||||
msg = await self.inbox.get()
|
msg = await self.inbox.get()
|
||||||
tag = msg[0]
|
tag = msg[0]
|
||||||
|
|
||||||
# Optional: Exoself kann AIDs registrieren, bevor wir starten
|
|
||||||
if tag == "register_actuators":
|
if tag == "register_actuators":
|
||||||
_, aids = msg
|
_, aids = msg
|
||||||
# Map aids auf echte Actuatoren falls nötig; hier übernehmen wir sie direkt
|
|
||||||
self.awaiting_sync = set(aids)
|
self.awaiting_sync = set(aids)
|
||||||
if not self.active:
|
if not self.active:
|
||||||
self._reset_for_new_episode()
|
self._reset_for_new_episode()
|
||||||
@@ -56,7 +52,6 @@ class Cortex(Actor):
|
|||||||
|
|
||||||
if tag == "sync" and self.active:
|
if tag == "sync" and self.active:
|
||||||
print("CORTEX: got sync message: ", msg)
|
print("CORTEX: got sync message: ", msg)
|
||||||
# Aktuator meldet Schrittabschluss
|
|
||||||
_t, aid, fitness, halt_flag = msg
|
_t, aid, fitness, halt_flag = msg
|
||||||
|
|
||||||
print("----------------")
|
print("----------------")
|
||||||
@@ -66,36 +61,28 @@ class Cortex(Actor):
|
|||||||
print("halt_flag:", halt_flag)
|
print("halt_flag:", halt_flag)
|
||||||
print("----------------")
|
print("----------------")
|
||||||
|
|
||||||
# akkumulieren
|
|
||||||
self.fitness_acc += float(fitness)
|
self.fitness_acc += float(fitness)
|
||||||
self.ef_acc += int(halt_flag)
|
self.ef_acc += int(halt_flag)
|
||||||
|
|
||||||
# diesen Aktuator als "eingetroffen" markieren
|
|
||||||
if aid in self.awaiting_sync:
|
if aid in self.awaiting_sync:
|
||||||
self.awaiting_sync.remove(aid)
|
self.awaiting_sync.remove(aid)
|
||||||
|
|
||||||
print("CORTEX: awaiting sync: ", self.awaiting_sync)
|
print("CORTEX: awaiting sync: ", self.awaiting_sync)
|
||||||
|
|
||||||
# Wenn alle Aktuatoren gemeldet haben, ist ein Zyklus fertig
|
|
||||||
if not self.awaiting_sync:
|
if not self.awaiting_sync:
|
||||||
print("CORTEX: cycle completed.")
|
print("CORTEX: cycle completed.")
|
||||||
self.cycle_acc += 1
|
self.cycle_acc += 1
|
||||||
|
|
||||||
# Episodenende, wenn irgendein Aktuator EndFlag setzt
|
|
||||||
if self.ef_acc > 0:
|
if self.ef_acc > 0:
|
||||||
elapsed = time.perf_counter() - self._t0
|
elapsed = time.perf_counter() - self._t0
|
||||||
# An Exoself berichten
|
|
||||||
await self.exoself_pid.send((
|
await self.exoself_pid.send((
|
||||||
"evaluation_completed",
|
"evaluation_completed",
|
||||||
self.fitness_acc,
|
self.fitness_acc,
|
||||||
self.cycle_acc,
|
self.cycle_acc,
|
||||||
elapsed
|
elapsed
|
||||||
))
|
))
|
||||||
# inaktiv werden – warten auf reactivate
|
|
||||||
self.active = False
|
self.active = False
|
||||||
# Flags für nächste Episode werden erst bei reactivate gesetzt
|
|
||||||
else:
|
else:
|
||||||
# Nächsten Zyklus starten
|
|
||||||
self.ef_acc = 0
|
self.ef_acc = 0
|
||||||
self._reset_for_new_cycle()
|
self._reset_for_new_cycle()
|
||||||
await self._kick_sensors()
|
await self._kick_sensors()
|
||||||
@@ -103,17 +90,14 @@ class Cortex(Actor):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if tag == "reactivate":
|
if tag == "reactivate":
|
||||||
# neue Episode starten
|
|
||||||
self._reset_for_new_episode()
|
self._reset_for_new_episode()
|
||||||
await self._kick_sensors()
|
await self._kick_sensors()
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if tag == "terminate":
|
if tag == "terminate":
|
||||||
# geordnet alle Kinder beenden
|
|
||||||
for a in (self.sensors + self.actuators + self.neurons):
|
for a in (self.sensors + self.actuators + self.neurons):
|
||||||
await a.send(("terminate",))
|
await a.send(("terminate",))
|
||||||
return
|
return
|
||||||
|
|
||||||
elif tag == "backup_from_neuron":
|
elif tag == "backup_from_neuron":
|
||||||
# vom Neuron an Cortex -> an Exoself weiterreichen
|
|
||||||
await self.exoself_pid.send(msg)
|
await self.exoself_pid.send(msg)
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
# exoself.py
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import math
|
import math
|
||||||
@@ -15,10 +14,6 @@ from scape import XorScape
|
|||||||
|
|
||||||
|
|
||||||
class Exoself(Actor):
|
class Exoself(Actor):
|
||||||
"""
|
|
||||||
Exoself übersetzt den Genotyp (JSON) in einen laufenden Phenotyp (Actors) und
|
|
||||||
steuert das simple Neuroevolution-Training (Backup/Restore/Perturb + Reactivate).
|
|
||||||
"""
|
|
||||||
def __init__(self, genotype: Dict[str, Any], file_name: Optional[str] = None):
|
def __init__(self, genotype: Dict[str, Any], file_name: Optional[str] = None):
|
||||||
super().__init__("Exoself")
|
super().__init__("Exoself")
|
||||||
self.g = genotype
|
self.g = genotype
|
||||||
@@ -40,29 +35,22 @@ class Exoself(Actor):
|
|||||||
self.MAX_ATTEMPTS = 50
|
self.MAX_ATTEMPTS = 50
|
||||||
self.actuator_scape = None
|
self.actuator_scape = None
|
||||||
|
|
||||||
# zuletzt perturbierte Neuronen (für Restore)
|
|
||||||
self._perturbed: List[Neuron] = []
|
self._perturbed: List[Neuron] = []
|
||||||
|
|
||||||
# ---------- Convenience ----------
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_file(path: str) -> "Exoself":
|
def from_file(path: str) -> "Exoself":
|
||||||
with open(path, "r") as f:
|
with open(path, "r") as f:
|
||||||
g = json.load(f)
|
g = json.load(f)
|
||||||
return Exoself(g, file_name=path)
|
return Exoself(g, file_name=path)
|
||||||
|
|
||||||
# ---------- Public API ----------
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
# 1) Netzwerk bauen
|
|
||||||
self._build_pid_map_and_spawn()
|
self._build_pid_map_and_spawn()
|
||||||
|
|
||||||
# 2) Cortex verlinken + starten
|
|
||||||
self._link_cortex()
|
self._link_cortex()
|
||||||
|
|
||||||
# 3) Actors starten (Sensoren/Neuronen/Aktuatoren)
|
|
||||||
for a in self.sensor_actors + self.neuron_actors + self.actuator_actors + [self.actuator_scape]:
|
for a in self.sensor_actors + self.neuron_actors + self.actuator_actors + [self.actuator_scape]:
|
||||||
self.tasks.append(asyncio.create_task(a.run()))
|
self.tasks.append(asyncio.create_task(a.run()))
|
||||||
|
|
||||||
# 4) Hauptloop: auf Cortex-Events hören
|
|
||||||
while True:
|
while True:
|
||||||
msg = await self.inbox.get()
|
msg = await self.inbox.get()
|
||||||
tag = msg[0]
|
tag = msg[0]
|
||||||
@@ -75,23 +63,12 @@ class Exoself(Actor):
|
|||||||
await self._terminate_all()
|
await self._terminate_all()
|
||||||
return
|
return
|
||||||
|
|
||||||
# in exoself.py, innerhalb der Klasse Exoself
|
|
||||||
|
|
||||||
async def run_evaluation(self):
|
async def run_evaluation(self):
|
||||||
"""
|
|
||||||
Eine einzelne Episode/Evaluation:
|
|
||||||
- baut & verlinkt das Netz
|
|
||||||
- startet alle Actors
|
|
||||||
- wartet auf 'evaluation_completed' vom Cortex
|
|
||||||
- beendet alles und liefert (fitness, evals, cycles, elapsed)
|
|
||||||
"""
|
|
||||||
# 1) Netzwerk bauen & Cortex verlinken
|
|
||||||
print("build network and link...")
|
print("build network and link...")
|
||||||
self._build_pid_map_and_spawn()
|
self._build_pid_map_and_spawn()
|
||||||
print("link cortex...")
|
print("link cortex...")
|
||||||
self._link_cortex()
|
self._link_cortex()
|
||||||
|
|
||||||
# 2) Sensor/Neuron/Aktuator-Tasks starten (Cortex startete _link_cortex bereits)
|
|
||||||
for a in self.sensor_actors + self.neuron_actors + self.actuator_actors:
|
for a in self.sensor_actors + self.neuron_actors + self.actuator_actors:
|
||||||
self.tasks.append(asyncio.create_task(a.run()))
|
self.tasks.append(asyncio.create_task(a.run()))
|
||||||
|
|
||||||
@@ -100,20 +77,16 @@ class Exoself(Actor):
|
|||||||
|
|
||||||
print("network actors are running...")
|
print("network actors are running...")
|
||||||
|
|
||||||
# 3) Auf Abschluss warten
|
|
||||||
while True:
|
while True:
|
||||||
msg = await self.inbox.get()
|
msg = await self.inbox.get()
|
||||||
print("message in exsoself: ", msg)
|
print("message in exsoself: ", msg)
|
||||||
tag = msg[0]
|
tag = msg[0]
|
||||||
if tag == "evaluation_completed":
|
if tag == "evaluation_completed":
|
||||||
_, fitness, cycles, elapsed = msg
|
_, fitness, cycles, elapsed = msg
|
||||||
# 4) Sauber terminieren
|
|
||||||
await self._terminate_all()
|
await self._terminate_all()
|
||||||
# Evals = 1 (eine Episode)
|
|
||||||
return float(fitness), 1, int(cycles), float(elapsed)
|
return float(fitness), 1, int(cycles), float(elapsed)
|
||||||
elif tag == "terminate":
|
elif tag == "terminate":
|
||||||
await self._terminate_all()
|
await self._terminate_all()
|
||||||
# Falls vorzeitig terminiert wurde
|
|
||||||
return float("-inf"), 0, 0, 0.0
|
return float("-inf"), 0, 0, 0.0
|
||||||
|
|
||||||
# ---------- Build ----------
|
# ---------- Build ----------
|
||||||
@@ -127,23 +100,20 @@ class Exoself(Actor):
|
|||||||
self.cx_actor = Cortex(
|
self.cx_actor = Cortex(
|
||||||
cid=cx["id"],
|
cid=cx["id"],
|
||||||
exoself_pid=self,
|
exoself_pid=self,
|
||||||
sensor_pids=[], # werden gleich gesetzt
|
sensor_pids=[],
|
||||||
neuron_pids=[],
|
neuron_pids=[],
|
||||||
actuator_pids=[]
|
actuator_pids=[]
|
||||||
)
|
)
|
||||||
|
|
||||||
self.actuator_scape = XorScape()
|
self.actuator_scape = XorScape()
|
||||||
|
|
||||||
# Neuronen nach Layer gruppieren (damit outputs korrekt gesetzt werden)
|
|
||||||
layers: Dict[int, List[Dict[str, Any]]] = defaultdict(list)
|
layers: Dict[int, List[Dict[str, Any]]] = defaultdict(list)
|
||||||
for n in self.g["neurons"]:
|
for n in self.g["neurons"]:
|
||||||
layers[n["layer_index"]].append(n)
|
layers[n["layer_index"]].append(n)
|
||||||
ordered_layers = [layers[i] for i in sorted(layers)]
|
ordered_layers = [layers[i] for i in sorted(layers)]
|
||||||
|
|
||||||
# Platzhalter: wir benötigen später Referenzen nach ID
|
|
||||||
id2neuron_actor: Dict[Any, Neuron] = {}
|
id2neuron_actor: Dict[Any, Neuron] = {}
|
||||||
|
|
||||||
# Zuerst alle Neuronen erzeugen (ohne Outputs), damit wir Referenzen haben
|
|
||||||
for layer in ordered_layers:
|
for layer in ordered_layers:
|
||||||
for n in layer:
|
for n in layer:
|
||||||
input_idps = [(iw["input_id"], iw["weights"]) for iw in n["input_weights"]]
|
input_idps = [(iw["input_id"], iw["weights"]) for iw in n["input_weights"]]
|
||||||
@@ -157,16 +127,11 @@ class Exoself(Actor):
|
|||||||
id2neuron_actor[n["id"]] = neuron
|
id2neuron_actor[n["id"]] = neuron
|
||||||
self.neuron_actors.append(neuron)
|
self.neuron_actors.append(neuron)
|
||||||
|
|
||||||
# Jetzt Outputs pro Layer setzen:
|
|
||||||
# - für Nicht-Output-Layer: Outputs = Neuronen der nächsten Schicht
|
|
||||||
# - für Output-Layer: Outputs = Aktuator(en) (setzen wir nachdem Aktuatoren erzeugt sind)
|
|
||||||
for li in range(len(ordered_layers) - 1):
|
for li in range(len(ordered_layers) - 1):
|
||||||
next_pids = [id2neuron_actor[nx["id"]] for nx in ordered_layers[li + 1]]
|
next_pids = [id2neuron_actor[nx["id"]] for nx in ordered_layers[li + 1]]
|
||||||
for n in ordered_layers[li]:
|
for n in ordered_layers[li]:
|
||||||
id2neuron_actor[n["id"]].outputs = next_pids
|
id2neuron_actor[n["id"]].outputs = next_pids
|
||||||
|
|
||||||
# Aktuatoren anlegen (brauchen cx_pid und fanin_ids)
|
|
||||||
# Genotyp kann "actuator" (ein Objekt) oder "actuators" (Liste) haben – wir unterstützen beides.
|
|
||||||
actuators = self._get_actuators_block()
|
actuators = self._get_actuators_block()
|
||||||
if not actuators:
|
if not actuators:
|
||||||
raise ValueError("Genotype must include 'actuator' or 'actuators'.")
|
raise ValueError("Genotype must include 'actuator' or 'actuators'.")
|
||||||
@@ -184,14 +149,12 @@ class Exoself(Actor):
|
|||||||
)
|
)
|
||||||
self.actuator_actors.append(actuator)
|
self.actuator_actors.append(actuator)
|
||||||
|
|
||||||
# Output-Layer Neuronen → Outputs = Aktuatoren
|
|
||||||
if ordered_layers:
|
if ordered_layers:
|
||||||
last_layer = ordered_layers[-1]
|
last_layer = ordered_layers[-1]
|
||||||
out_targets = self.actuator_actors # Liste
|
out_targets = self.actuator_actors
|
||||||
for n in last_layer:
|
for n in last_layer:
|
||||||
id2neuron_actor[n["id"]].outputs = out_targets
|
id2neuron_actor[n["id"]].outputs = out_targets
|
||||||
|
|
||||||
# Sensor(en) anlegen (brauchen cx_pid und fanout auf erste Schicht)
|
|
||||||
sensors = self._get_sensors_block()
|
sensors = self._get_sensors_block()
|
||||||
if not sensors:
|
if not sensors:
|
||||||
raise ValueError("Genotype must include 'sensor' or 'sensors'.")
|
raise ValueError("Genotype must include 'sensor' or 'sensors'.")
|
||||||
@@ -224,24 +187,46 @@ class Exoself(Actor):
|
|||||||
return [self.g["actuator"]]
|
return [self.g["actuator"]]
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# ---------- Link ----------
|
|
||||||
def _link_cortex(self):
|
def _link_cortex(self):
|
||||||
"""
|
|
||||||
Übergibt dem Cortex die fertigen Listen und setzt awaiting_sync.
|
|
||||||
Startet dann den Cortex-Task.
|
|
||||||
"""
|
|
||||||
self.cx_actor.sensors = [a for a in self.sensor_actors if a]
|
self.cx_actor.sensors = [a for a in self.sensor_actors if a]
|
||||||
self.cx_actor.neurons = [a for a in self.neuron_actors if a]
|
self.cx_actor.neurons = [a for a in self.neuron_actors if a]
|
||||||
self.cx_actor.actuators = [a for a in self.actuator_actors if a]
|
self.cx_actor.actuators = [a for a in self.actuator_actors if a]
|
||||||
|
|
||||||
# Wichtig: vor Start die erwarteten AIDs setzen,
|
|
||||||
# damit der erste Sensor-Trigger nicht in eine leere awaiting_sync läuft.
|
|
||||||
self.cx_actor.awaiting_sync = set(a.aid for a in self.cx_actor.actuators)
|
self.cx_actor.awaiting_sync = set(a.aid for a in self.cx_actor.actuators)
|
||||||
|
|
||||||
# Cortex starten
|
|
||||||
self.tasks.append(asyncio.create_task(self.cx_actor.run()))
|
self.tasks.append(asyncio.create_task(self.cx_actor.run()))
|
||||||
|
|
||||||
# ---------- Training-Loop Reaction ----------
|
async def train_until_stop(self):
|
||||||
|
self._build_pid_map_and_spawn()
|
||||||
|
self._link_cortex()
|
||||||
|
|
||||||
|
# 2) Start tasks
|
||||||
|
for a in self.sensor_actors + self.neuron_actors + self.actuator_actors:
|
||||||
|
self.tasks.append(asyncio.create_task(a.run()))
|
||||||
|
if self.actuator_scape:
|
||||||
|
self.tasks.append(asyncio.create_task(self.actuator_scape.run()))
|
||||||
|
|
||||||
|
while True:
|
||||||
|
msg = await self.inbox.get()
|
||||||
|
tag = msg[0]
|
||||||
|
|
||||||
|
if tag == "evaluation_completed":
|
||||||
|
_, fitness, cycles, elapsed = msg
|
||||||
|
maybe_stats = await self._on_evaluation_completed(fitness, cycles, elapsed)
|
||||||
|
# _on_evaluation_completed() ruft bei Stop bereits _backup_genotype() und _terminate_all()
|
||||||
|
if isinstance(maybe_stats, dict):
|
||||||
|
# Trainingsende – Daten aus self.* zurückgeben (wie im Buch: Fitness/Evals/Cycles/Time)
|
||||||
|
return (
|
||||||
|
float(self.highest_fitness),
|
||||||
|
int(self.eval_acc),
|
||||||
|
int(self.cycle_acc),
|
||||||
|
float(self.time_acc),
|
||||||
|
)
|
||||||
|
|
||||||
|
elif tag == "terminate":
|
||||||
|
await self._terminate_all()
|
||||||
|
return float("-inf"), 0, 0, 0.0
|
||||||
|
|
||||||
async def _on_evaluation_completed(self, fitness: float, cycles: int, elapsed: float):
|
async def _on_evaluation_completed(self, fitness: float, cycles: int, elapsed: float):
|
||||||
self.eval_acc += 1
|
self.eval_acc += 1
|
||||||
self.cycle_acc += int(cycles)
|
self.cycle_acc += int(cycles)
|
||||||
@@ -249,21 +234,20 @@ class Exoself(Actor):
|
|||||||
|
|
||||||
print(f"[Exoself] evaluation_completed: fitness={fitness:.6f} cycles={cycles} time={elapsed:.3f}s")
|
print(f"[Exoself] evaluation_completed: fitness={fitness:.6f} cycles={cycles} time={elapsed:.3f}s")
|
||||||
|
|
||||||
if fitness > self.highest_fitness:
|
REL = 1e-6
|
||||||
|
if fitness > self.highest_fitness * (1.0 + REL):
|
||||||
self.highest_fitness = fitness
|
self.highest_fitness = fitness
|
||||||
self.attempt = 0
|
self.attempt = 0
|
||||||
# Backup aller Neuronen
|
|
||||||
for n in self.neuron_actors:
|
for n in self.neuron_actors:
|
||||||
await n.send(("weight_backup",))
|
await n.send(("weight_backup",))
|
||||||
else:
|
else:
|
||||||
self.attempt += 1
|
self.attempt += 1
|
||||||
# Restore nur der zuletzt perturbierten Neuronen
|
|
||||||
for n in self._perturbed:
|
for n in self._perturbed:
|
||||||
await n.send(("weight_restore",))
|
await n.send(("weight_restore",))
|
||||||
|
|
||||||
# Stop-Kriterium?
|
|
||||||
if self.attempt >= self.MAX_ATTEMPTS:
|
if self.attempt >= self.MAX_ATTEMPTS:
|
||||||
print(f"[Exoself] STOP. Best fitness={self.highest_fitness:.6f} evals={self.eval_acc} cycles={self.cycle_acc}")
|
print(
|
||||||
|
f"[Exoself] STOP. Best fitness={self.highest_fitness:.6f} evals={self.eval_acc} cycles={self.cycle_acc}")
|
||||||
await self._backup_genotype()
|
await self._backup_genotype()
|
||||||
await self._terminate_all()
|
await self._terminate_all()
|
||||||
return {
|
return {
|
||||||
@@ -273,7 +257,6 @@ class Exoself(Actor):
|
|||||||
"time_acc": self.time_acc,
|
"time_acc": self.time_acc,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Perturbiere Teilmenge der Neuronen
|
|
||||||
tot = len(self.neuron_actors)
|
tot = len(self.neuron_actors)
|
||||||
mp = 1.0 / math.sqrt(max(1, tot))
|
mp = 1.0 / math.sqrt(max(1, tot))
|
||||||
self._perturbed = [n for n in self.neuron_actors if random.random() < mp]
|
self._perturbed = [n for n in self.neuron_actors if random.random() < mp]
|
||||||
@@ -281,25 +264,13 @@ class Exoself(Actor):
|
|||||||
for n in self._perturbed:
|
for n in self._perturbed:
|
||||||
await n.send(("weight_perturb",))
|
await n.send(("weight_perturb",))
|
||||||
|
|
||||||
# Nächste Episode starten
|
|
||||||
await self.cx_actor.send(("reactivate",))
|
await self.cx_actor.send(("reactivate",))
|
||||||
|
|
||||||
# ---------- Backup Genotype ----------
|
|
||||||
async def _backup_genotype(self):
|
async def _backup_genotype(self):
|
||||||
"""
|
|
||||||
Holt von allen Neuronen die aktuellen Weights und schreibt sie in self.g.
|
|
||||||
Speichert optional in self.file_name.
|
|
||||||
"""
|
|
||||||
# 1) Request
|
|
||||||
remaining = len(self.neuron_actors)
|
remaining = len(self.neuron_actors)
|
||||||
for n in self.neuron_actors:
|
for n in self.neuron_actors:
|
||||||
await n.send(("get_backup",))
|
await n.send(("get_backup",))
|
||||||
|
|
||||||
# 2) Collect vom Cortex-Postfach (Neuronen senden an cx_pid → cx leitet an Exoself weiter
|
|
||||||
# oder du hast sie direkt an Exoself schicken lassen; falls direkt an Cortex, dann
|
|
||||||
# lausche hier stattdessen auf self.cx_actor.inbox. In deinem Neuron-Code geht es an cx_pid,
|
|
||||||
# und in deiner bisherigen Implementierung hast du aus dem Cortex-Postfach gelesen.
|
|
||||||
# Hier vereinfachen wir: Neuronen senden direkt an EXOSELF (passe Neuron ggf. an).
|
|
||||||
backups: List[Tuple[Any, List[Tuple[Any, List[float]]]]] = []
|
backups: List[Tuple[Any, List[Tuple[Any, List[float]]]]] = []
|
||||||
|
|
||||||
while remaining > 0:
|
while remaining > 0:
|
||||||
@@ -309,8 +280,6 @@ class Exoself(Actor):
|
|||||||
backups.append((nid, idps))
|
backups.append((nid, idps))
|
||||||
remaining -= 1
|
remaining -= 1
|
||||||
|
|
||||||
# 3) Update JSON
|
|
||||||
# exoself.py -> in _backup_genotype()
|
|
||||||
id2n = {n["id"]: n for n in self.g["neurons"]}
|
id2n = {n["id"]: n for n in self.g["neurons"]}
|
||||||
for nid, idps in backups:
|
for nid, idps in backups:
|
||||||
if nid not in id2n:
|
if nid not in id2n:
|
||||||
@@ -324,17 +293,14 @@ class Exoself(Actor):
|
|||||||
input_id, weights = item
|
input_id, weights = item
|
||||||
new_iw.append({"input_id": input_id, "weights": list(weights)})
|
new_iw.append({"input_id": input_id, "weights": list(weights)})
|
||||||
id2n[nid]["input_weights"] = new_iw
|
id2n[nid]["input_weights"] = new_iw
|
||||||
# Bias mit abspeichern (Variante B):
|
|
||||||
if bias_val is not None:
|
if bias_val is not None:
|
||||||
id2n[nid].setdefault("input_weights", []).append({"input_id": "bias", "weights": [bias_val]})
|
id2n[nid].setdefault("input_weights", []).append({"input_id": "bias", "weights": [bias_val]})
|
||||||
|
|
||||||
# 4) Save
|
|
||||||
if self.file_name:
|
if self.file_name:
|
||||||
with open(self.file_name, "w") as f:
|
with open(self.file_name, "w") as f:
|
||||||
json.dump(self.g, f, indent=2)
|
json.dump(self.g, f, indent=2)
|
||||||
print(f"[Exoself] Genotype updated → {self.file_name}")
|
print(f"[Exoself] Genotype updated → {self.file_name}")
|
||||||
|
|
||||||
# ---------- Termination ----------
|
|
||||||
async def _terminate_all(self):
|
async def _terminate_all(self):
|
||||||
for a in self.sensor_actors + self.neuron_actors + self.actuator_actors:
|
for a in self.sensor_actors + self.neuron_actors + self.actuator_actors:
|
||||||
await a.send(("terminate",))
|
await a.send(("terminate",))
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
{
|
|
||||||
"cortex": {
|
|
||||||
"id": 0.0873421806271496,
|
|
||||||
"sensor_ids": [
|
|
||||||
5.6856379409509e-10
|
|
||||||
],
|
|
||||||
"actuator_ids": [
|
|
||||||
5.685637940950896e-10
|
|
||||||
],
|
|
||||||
"neuron_ids": [
|
|
||||||
0.6384794610574114,
|
|
||||||
0.6023131059017351,
|
|
||||||
0.39277072802910173
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"sensor": {
|
|
||||||
"id": 5.6856379409509e-10,
|
|
||||||
"name": "xor_GetInput",
|
|
||||||
"vector_length": 2,
|
|
||||||
"cx_id": 0.0873421806271496,
|
|
||||||
"fanout_ids": [
|
|
||||||
0.6384794610574114,
|
|
||||||
0.6023131059017351
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"actuator": {
|
|
||||||
"id": 5.685637940950896e-10,
|
|
||||||
"name": "xor_SendOutput",
|
|
||||||
"vector_length": 1,
|
|
||||||
"cx_id": 0.0873421806271496,
|
|
||||||
"fanin_ids": [
|
|
||||||
0.39277072802910173
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"neurons": [
|
|
||||||
{
|
|
||||||
"id": 0.6384794610574114,
|
|
||||||
"layer_index": 0,
|
|
||||||
"cx_id": 0.0873421806271496,
|
|
||||||
"activation_function": "tanh",
|
|
||||||
"input_weights": [
|
|
||||||
{
|
|
||||||
"input_id": 5.6856379409509e-10,
|
|
||||||
"weights": [
|
|
||||||
-1.241701053140722,
|
|
||||||
-0.9643293453192259
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"output_ids": [
|
|
||||||
0.20086494757397733
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 0.6023131059017351,
|
|
||||||
"layer_index": 0,
|
|
||||||
"cx_id": 0.0873421806271496,
|
|
||||||
"activation_function": "tanh",
|
|
||||||
"input_weights": [
|
|
||||||
{
|
|
||||||
"input_id": 5.6856379409509e-10,
|
|
||||||
"weights": [
|
|
||||||
-0.38795737506820904,
|
|
||||||
-0.5125123920689245
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"output_ids": [
|
|
||||||
0.20086494757397733
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 0.39277072802910173,
|
|
||||||
"layer_index": 1,
|
|
||||||
"cx_id": 0.0873421806271496,
|
|
||||||
"activation_function": "tanh",
|
|
||||||
"input_weights": [
|
|
||||||
{
|
|
||||||
"input_id": 0.6384794610574114,
|
|
||||||
"weights": [
|
|
||||||
-0.7426574958298033
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"input_id": 0.6023131059017351,
|
|
||||||
"weights": [
|
|
||||||
-0.9823211499227558
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"output_ids": [
|
|
||||||
5.685637940950896e-10
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -6,9 +6,6 @@ import time
|
|||||||
from typing import Dict, List, Tuple, Any, Optional
|
from typing import Dict, List, Tuple, Any, Optional
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------
|
|
||||||
# Utilities / ID & Weights
|
|
||||||
# -----------------------------
|
|
||||||
def generate_id() -> float:
|
def generate_id() -> float:
|
||||||
return random.random()
|
return random.random()
|
||||||
|
|
||||||
@@ -17,35 +14,6 @@ def create_neural_weights(vector_length: int) -> List[float]:
|
|||||||
return [random.uniform(-2.0, 2.0) for _ in range(vector_length)]
|
return [random.uniform(-2.0, 2.0) for _ in range(vector_length)]
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------
|
|
||||||
# Morphology "Interface"
|
|
||||||
# -----------------------------
|
|
||||||
"""
|
|
||||||
Erwartete Morphologie-API (duck-typed):
|
|
||||||
|
|
||||||
- get_InitSensor(morphology) -> dict mit Feldern:
|
|
||||||
{
|
|
||||||
"id": <beliebig>,
|
|
||||||
"name": <sensor-funktionsname, z.B. "rng" oder "xor_GetInput">,
|
|
||||||
"vector_length": <int>,
|
|
||||||
# optional: "scape": <frei nutzbar>,
|
|
||||||
}
|
|
||||||
|
|
||||||
- get_InitActuator(morphology) -> dict mit Feldern:
|
|
||||||
{
|
|
||||||
"id": <beliebig>,
|
|
||||||
"name": <aktor-funktionsname, z.B. "pts" oder "xor_SendOutput">,
|
|
||||||
"vector_length": <int>,
|
|
||||||
# optional: "scape": <frei nutzbar>,
|
|
||||||
}
|
|
||||||
|
|
||||||
Du kannst z.B. ein kleines Modul/Objekt übergeben, das diese Funktionen bereitstellt.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------
|
|
||||||
# Genotype-Erzeugung
|
|
||||||
# -----------------------------
|
|
||||||
def construct(
|
def construct(
|
||||||
morphology_module,
|
morphology_module,
|
||||||
hidden_layer_densities: List[int],
|
hidden_layer_densities: List[int],
|
||||||
@@ -53,29 +21,12 @@ def construct(
|
|||||||
*,
|
*,
|
||||||
add_bias: bool = False,
|
add_bias: bool = False,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
|
||||||
Baut einen Genotyp (JSON-kompatibles Dict) analog zur Erlang-Version:
|
|
||||||
|
|
||||||
- wählt Initial-Sensor & -Aktuator aus Morphologie
|
|
||||||
- bestimmt Output-Layer-Dichte = actuator.vector_length
|
|
||||||
- erzeugt alle Neuronen-Schichten & Gewichte
|
|
||||||
- setzt fanout_ids/fanin_ids
|
|
||||||
- erstellt Cortex mit Listen der IDs
|
|
||||||
- speichert optional in file_name
|
|
||||||
|
|
||||||
add_bias: Wenn True, hängt pro Neuron einen Bias als ('bias', b) an die Input-Liste an.
|
|
||||||
Dein bisheriges JSON nutzt KEIN Bias-Element. Standard=False für Kompatibilität.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Seed wie im Buch
|
|
||||||
rnd_seed = time.time_ns() & 0xFFFFFFFF
|
rnd_seed = time.time_ns() & 0xFFFFFFFF
|
||||||
random.seed(rnd_seed)
|
random.seed(rnd_seed)
|
||||||
|
|
||||||
# 1) Sensor & Aktuator aus Morphologie
|
|
||||||
S = morphology_module.get_InitSensor(morphology_module)
|
S = morphology_module.get_InitSensor(morphology_module)
|
||||||
A = morphology_module.get_InitActuator(morphology_module)
|
A = morphology_module.get_InitActuator(morphology_module)
|
||||||
|
|
||||||
# Pflichtfelder normalisieren
|
|
||||||
sensor = {
|
sensor = {
|
||||||
"id": S.get("id", generate_id()),
|
"id": S.get("id", generate_id()),
|
||||||
"name": S["name"],
|
"name": S["name"],
|
||||||
@@ -96,14 +47,11 @@ def construct(
|
|||||||
# "scape": A.get("scape")
|
# "scape": A.get("scape")
|
||||||
}
|
}
|
||||||
|
|
||||||
# 2) Output-Layer-Dichte = actuator.vector_length (wie im Buch)
|
|
||||||
output_vl = actuator["vector_length"]
|
output_vl = actuator["vector_length"]
|
||||||
layer_densities = list(hidden_layer_densities) + [output_vl]
|
layer_densities = list(hidden_layer_densities) + [output_vl]
|
||||||
|
|
||||||
# 3) Cortex-ID festlegen
|
|
||||||
cortex_id = generate_id()
|
cortex_id = generate_id()
|
||||||
|
|
||||||
# 4) Neuronen-Schichten erzeugen
|
|
||||||
layers = _create_neuro_layers(
|
layers = _create_neuro_layers(
|
||||||
cx_id=cortex_id,
|
cx_id=cortex_id,
|
||||||
sensor=sensor,
|
sensor=sensor,
|
||||||
@@ -112,7 +60,6 @@ def construct(
|
|||||||
add_bias=add_bias,
|
add_bias=add_bias,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 5) Sensor/Aktuator mit Cortex/Fanout/Fanin verbinden
|
|
||||||
input_layer = layers[0]
|
input_layer = layers[0]
|
||||||
output_layer = layers[-1]
|
output_layer = layers[-1]
|
||||||
|
|
||||||
@@ -122,7 +69,6 @@ def construct(
|
|||||||
actuator["cx_id"] = cortex_id
|
actuator["cx_id"] = cortex_id
|
||||||
actuator["fanin_ids"] = [n["id"] for n in output_layer]
|
actuator["fanin_ids"] = [n["id"] for n in output_layer]
|
||||||
|
|
||||||
# 6) Cortex-Objekt
|
|
||||||
neuron_ids = [n["id"] for layer in layers for n in layer]
|
neuron_ids = [n["id"] for layer in layers for n in layer]
|
||||||
cortex = {
|
cortex = {
|
||||||
"id": cortex_id,
|
"id": cortex_id,
|
||||||
@@ -154,32 +100,19 @@ def _create_neuro_layers(
|
|||||||
*,
|
*,
|
||||||
add_bias: bool,
|
add_bias: bool,
|
||||||
) -> List[List[Dict[str, Any]]]:
|
) -> List[List[Dict[str, Any]]]:
|
||||||
"""
|
|
||||||
Baut alle Neuronen-Schichten.
|
|
||||||
- Erste Schicht erhält als Input den Sensor (Vektor).
|
|
||||||
- Mittlere Schichten erhalten skalare Inputs von voriger Schicht.
|
|
||||||
- Letzte Schicht verbindet zum Aktuator (output_ids = [actuator.id]).
|
|
||||||
"""
|
|
||||||
layers: List[List[Dict[str, Any]]] = []
|
layers: List[List[Dict[str, Any]]] = []
|
||||||
|
|
||||||
# Platzhalter-Input: (input_id, vector_length)
|
|
||||||
input_idps: List[Tuple[float, int]] = [(sensor["id"], sensor["vector_length"])]
|
input_idps: List[Tuple[float, int]] = [(sensor["id"], sensor["vector_length"])]
|
||||||
|
|
||||||
for layer_index, layer_density in enumerate(layer_densities):
|
for layer_index, layer_density in enumerate(layer_densities):
|
||||||
# Neuron-IDs generieren
|
|
||||||
neuron_ids = [generate_id() for _ in range(layer_density)]
|
neuron_ids = [generate_id() for _ in range(layer_density)]
|
||||||
|
|
||||||
# output_ids: Standardmäßig IDs der nächsten Schicht (werden nach dem Layer bekannt)
|
|
||||||
if layer_index < len(layer_densities) - 1:
|
if layer_index < len(layer_densities) - 1:
|
||||||
# IDs der nächste Schicht vorab erzeugen (für output_ids)
|
|
||||||
next_ids = [generate_id() for _ in range(layer_densities[layer_index + 1])]
|
next_ids = [generate_id() for _ in range(layer_densities[layer_index + 1])]
|
||||||
# Aber: wir erzeugen hier *nur* die IDs für output_ids, nicht die Neuronen der nächsten Schicht
|
|
||||||
output_ids = next_ids
|
output_ids = next_ids
|
||||||
else:
|
else:
|
||||||
# letzte Schicht → zum Aktuator
|
|
||||||
output_ids = [actuator["id"]]
|
output_ids = [actuator["id"]]
|
||||||
|
|
||||||
# Layer-Neuronen erzeugen
|
|
||||||
this_layer: List[Dict[str, Any]] = []
|
this_layer: List[Dict[str, Any]] = []
|
||||||
for _nid in neuron_ids:
|
for _nid in neuron_ids:
|
||||||
proper_input = _create_neural_input(input_idps, add_bias=add_bias)
|
proper_input = _create_neural_input(input_idps, add_bias=add_bias)
|
||||||
@@ -188,27 +121,18 @@ def _create_neuro_layers(
|
|||||||
"layer_index": layer_index,
|
"layer_index": layer_index,
|
||||||
"cx_id": cx_id,
|
"cx_id": cx_id,
|
||||||
"activation_function": "tanh",
|
"activation_function": "tanh",
|
||||||
# JSON-Konvention: [{"input_id": id, "weights": [...]}, ...]
|
"input_weights": [{"input_id": i, "weights": w} for (i, w) in proper_input],
|
||||||
"input_weights": [{"input_id": i, "weights": w} for (i, w) in proper_input if not _is_bias_tuple((i, w))],
|
|
||||||
"output_ids": output_ids[:], # Kopie
|
"output_ids": output_ids[:], # Kopie
|
||||||
}
|
}
|
||||||
this_layer.append(neuron)
|
this_layer.append(neuron)
|
||||||
|
|
||||||
layers.append(this_layer)
|
layers.append(this_layer)
|
||||||
|
|
||||||
# Inputs für nächste Schicht: jetzt sind es skalare Outputs der aktuellen Neuronen
|
|
||||||
input_idps = [(n["id"], 1) for n in this_layer]
|
input_idps = [(n["id"], 1) for n in this_layer]
|
||||||
|
|
||||||
# WICHTIG: oben haben wir für mittlere Layer „künstliche“ next_ids erzeugt, damit output_ids befüllt waren.
|
|
||||||
# In deiner Laufzeit-Topologie ignorieren wir diese Platzhalter und verdrahten später im Exoself die
|
|
||||||
# tatsächlichen Actor-Referenzen schichtweise. Für den Genotyp sind die output_ids der Zwischenlayer
|
|
||||||
# nicht kritisch (du überschreibst sie ohnehin beim Phenotype-Mapping). Die letzte Schicht zeigt korrekt auf den Aktuator.
|
|
||||||
|
|
||||||
return layers
|
return layers
|
||||||
|
|
||||||
|
|
||||||
def _is_bias_tuple(t: Tuple[Any, Any]) -> bool:
|
def _is_bias_tuple(t: Tuple[Any, Any]) -> bool:
|
||||||
"""Hilfsfunktion falls du später Bias im JSON aufnehmen willst."""
|
|
||||||
key, _ = t
|
key, _ = t
|
||||||
return isinstance(key, str) and key == "bias"
|
return isinstance(key, str) and key == "bias"
|
||||||
|
|
||||||
@@ -218,10 +142,6 @@ def _create_neural_input(
|
|||||||
*,
|
*,
|
||||||
add_bias: bool,
|
add_bias: bool,
|
||||||
) -> List[Tuple[Any, List[float]]]:
|
) -> List[Tuple[Any, List[float]]]:
|
||||||
"""
|
|
||||||
Wandelt Platzhalter (input_id, vector_length) in (input_id, weights[]) um.
|
|
||||||
Optional einen Bias als ('bias', [b]) anhängen (Erlang-Variante).
|
|
||||||
"""
|
|
||||||
proper: List[Tuple[Any, List[float]]] = []
|
proper: List[Tuple[Any, List[float]]] = []
|
||||||
for input_id, vl in input_idps:
|
for input_id, vl in input_idps:
|
||||||
proper.append((input_id, create_neural_weights(vl)))
|
proper.append((input_id, create_neural_weights(vl)))
|
||||||
@@ -232,9 +152,6 @@ def _create_neural_input(
|
|||||||
return proper
|
return proper
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------
|
|
||||||
# File I/O & Print
|
|
||||||
# -----------------------------
|
|
||||||
def save_genotype(file_name: str, genotype: Dict[str, Any]) -> None:
|
def save_genotype(file_name: str, genotype: Dict[str, Any]) -> None:
|
||||||
with open(file_name, "w") as f:
|
with open(file_name, "w") as f:
|
||||||
json.dump(genotype, f, indent=2)
|
json.dump(genotype, f, indent=2)
|
||||||
@@ -253,7 +170,6 @@ def print_genotype(file_name: str) -> None:
|
|||||||
nids = cx.get("neuron_ids", [])
|
nids = cx.get("neuron_ids", [])
|
||||||
aids = cx.get("actuator_ids", [])
|
aids = cx.get("actuator_ids", [])
|
||||||
|
|
||||||
# Indexe für schnellen Zugriff
|
|
||||||
nid2n = {n["id"]: n for n in g.get("neurons", [])}
|
nid2n = {n["id"]: n for n in g.get("neurons", [])}
|
||||||
sid2s = {g["sensor"]["id"]: g["sensor"]} if "sensor" in g else {s["id"]: s for s in g.get("sensors", [])}
|
sid2s = {g["sensor"]["id"]: g["sensor"]} if "sensor" in g else {s["id"]: s for s in g.get("sensors", [])}
|
||||||
aid2a = {g["actuator"]["id"]: g["actuator"]} if "actuator" in g else {a["id"]: a for a in g.get("actuators", [])}
|
aid2a = {g["actuator"]["id"]: g["actuator"]} if "actuator" in g else {a["id"]: a for a in g.get("actuators", [])}
|
||||||
|
|||||||
@@ -6,16 +6,10 @@ MorphologyType = Union[str, Callable[[str], List[Dict[str, Any]]]]
|
|||||||
|
|
||||||
|
|
||||||
def generate_id() -> float:
|
def generate_id() -> float:
|
||||||
"""
|
|
||||||
Zeitbasierte float-ID (ähnlich wie im Buch).
|
|
||||||
"""
|
|
||||||
now = time.time()
|
now = time.time()
|
||||||
return 1.0 / now
|
return 1.0 / now
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
# Morphologie-API (duck-typed, wie im Erlang-Original)
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
def get_InitSensor(morphology: MorphologyType) -> Dict[str, Any]:
|
def get_InitSensor(morphology: MorphologyType) -> Dict[str, Any]:
|
||||||
sensors = get_Sensors(morphology)
|
sensors = get_Sensors(morphology)
|
||||||
if not sensors:
|
if not sensors:
|
||||||
@@ -41,13 +35,6 @@ def get_Actuators(morphology: MorphologyType) -> List[Dict[str, Any]]:
|
|||||||
|
|
||||||
|
|
||||||
def _resolve_morphology(morphology: MorphologyType) -> Callable[[str], List[Dict[str, Any]]]:
|
def _resolve_morphology(morphology: MorphologyType) -> Callable[[str], List[Dict[str, Any]]]:
|
||||||
"""
|
|
||||||
Akzeptiert:
|
|
||||||
- eine Callable Morphologie-Funktion (z.B. xor_mimic)
|
|
||||||
- einen String (z.B. "xor_mimic") -> per Registry auflösen
|
|
||||||
- ein Modul-Objekt (z.B. import morphology) -> Standard 'xor_mimic' aus dem Modul
|
|
||||||
"""
|
|
||||||
# 1) Bereits eine Callable-Funktion? (z.B. xor_mimic)
|
|
||||||
if callable(morphology):
|
if callable(morphology):
|
||||||
return morphology
|
return morphology
|
||||||
|
|
||||||
@@ -55,14 +42,11 @@ def _resolve_morphology(morphology: MorphologyType) -> Callable[[str], List[Dict
|
|||||||
if isinstance(morphology, str):
|
if isinstance(morphology, str):
|
||||||
reg = {
|
reg = {
|
||||||
"xor_mimic": xor_mimic,
|
"xor_mimic": xor_mimic,
|
||||||
# weitere Morphologien hier registrieren...
|
|
||||||
}
|
}
|
||||||
if morphology in reg:
|
if morphology in reg:
|
||||||
return reg[morphology]
|
return reg[morphology]
|
||||||
raise ValueError(f"Unknown morphology name: {morphology}")
|
raise ValueError(f"Unknown morphology name: {morphology}")
|
||||||
|
|
||||||
# 3) Modul-Objekt: versuche eine Standard-Morphologie-Funktion daraus zu nehmen
|
|
||||||
# Hier nehmen wir 'xor_mimic' als Default (du kannst das generalisieren).
|
|
||||||
try:
|
try:
|
||||||
# Ist es ein Modul mit einer Funktion 'xor_mimic'?
|
# Ist es ein Modul mit einer Funktion 'xor_mimic'?
|
||||||
if hasattr(morphology, "xor_mimic") and callable(getattr(morphology, "xor_mimic")):
|
if hasattr(morphology, "xor_mimic") and callable(getattr(morphology, "xor_mimic")):
|
||||||
@@ -73,31 +57,23 @@ def _resolve_morphology(morphology: MorphologyType) -> Callable[[str], List[Dict
|
|||||||
raise TypeError("morphology must be a callable, a module with 'xor_mimic', or a registered string key")
|
raise TypeError("morphology must be a callable, a module with 'xor_mimic', or a registered string key")
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
# Beispiel-Morphologie: XOR (wie im Buch)
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
def xor_mimic(kind: str) -> List[Dict[str, Any]]:
|
def xor_mimic(kind: str) -> List[Dict[str, Any]]:
|
||||||
"""
|
|
||||||
Liefert je nach 'kind' ('sensors' | 'actuators') die Definitionen.
|
|
||||||
Felder sind so benannt, dass sie direkt mit unserem Genotype/Phenotype-Code
|
|
||||||
kompatibel sind (vector_length statt 'vl', etc.).
|
|
||||||
"""
|
|
||||||
if kind == "sensors":
|
if kind == "sensors":
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"id": generate_id(),
|
"id": generate_id(),
|
||||||
"name": "xor_GetInput", # Sensorfunktion (muss in deinem Sensor-Actor implementiert sein)
|
"name": "xor_GetInput",
|
||||||
"vector_length": 2,
|
"vector_length": 2,
|
||||||
"scape": {"private": "xor_sim"} # optional, falls du Scapes nutzt
|
"scape": {"private": "xor_sim"}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
elif kind == "actuators":
|
elif kind == "actuators":
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"id": generate_id(),
|
"id": generate_id(),
|
||||||
"name": "xor_SendOutput", # Aktuatorfunktion (muss in deinem Actuator-Actor implementiert sein)
|
"name": "xor_SendOutput",
|
||||||
"vector_length": 1,
|
"vector_length": 1,
|
||||||
"scape": {"private": "xor_sim"} # optional
|
"scape": {"private": "xor_sim"}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -17,12 +17,21 @@ class Neuron(Actor):
|
|||||||
# input_idps: [(input_id, [w1, w2, ...])]
|
# input_idps: [(input_id, [w1, w2, ...])]
|
||||||
self.inputs = {}
|
self.inputs = {}
|
||||||
self.order = []
|
self.order = []
|
||||||
|
|
||||||
|
"""
|
||||||
for (inp_id, weights) in input_idps:
|
for (inp_id, weights) in input_idps:
|
||||||
self.order.append(inp_id)
|
self.order.append(inp_id)
|
||||||
self.inputs[inp_id] = {"weights": list(weights), "got": False, "val": None}
|
self.inputs[inp_id] = {"weights": list(weights), "got": False, "val": None}
|
||||||
# WICHTIG: Bias lernbar machen
|
"""
|
||||||
self.bias = random.uniform(-2.0, 2.0)
|
|
||||||
# Für Backup/Restore
|
self.bias = 0.0
|
||||||
|
for (inp_id, weights) in input_idps:
|
||||||
|
if inp_id == "bias":
|
||||||
|
self.bias = float(weights[0])
|
||||||
|
else:
|
||||||
|
self.order.append(inp_id)
|
||||||
|
self.inputs[inp_id] = {"weights": list(weights), "got": False, "val": None}
|
||||||
|
|
||||||
self._backup_inputs = None
|
self._backup_inputs = None
|
||||||
self._backup_bias = None
|
self._backup_bias = None
|
||||||
|
|
||||||
@@ -61,13 +70,11 @@ class Neuron(Actor):
|
|||||||
print(f"Neuron {self.nid}: input_sum={acc + self.bias:.3f}, output={out:.3f}")
|
print(f"Neuron {self.nid}: input_sum={acc + self.bias:.3f}, output={out:.3f}")
|
||||||
|
|
||||||
elif tag == "get_backup":
|
elif tag == "get_backup":
|
||||||
# Packe Gewichte + Bias zurück
|
|
||||||
idps = [(i, self.inputs[i]["weights"]) for i in self.order]
|
idps = [(i, self.inputs[i]["weights"]) for i in self.order]
|
||||||
idps.append(("bias", self.bias))
|
idps.append(("bias", self.bias))
|
||||||
await self.cx_pid.send(("backup_from_neuron", self.nid, idps))
|
await self.cx_pid.send(("backup_from_neuron", self.nid, idps))
|
||||||
|
|
||||||
elif tag == "weight_backup":
|
elif tag == "weight_backup":
|
||||||
# Tiefkopie der Gewichte + Bias
|
|
||||||
print(f"Neuron {self.nid}: backing up weights")
|
print(f"Neuron {self.nid}: backing up weights")
|
||||||
self._backup_inputs = {k: {"weights": v["weights"][:]} for k, v in self.inputs.items()}
|
self._backup_inputs = {k: {"weights": v["weights"][:]} for k, v in self.inputs.items()}
|
||||||
self._backup_bias = self.bias
|
self._backup_bias = self.bias
|
||||||
@@ -79,21 +86,17 @@ class Neuron(Actor):
|
|||||||
self.bias = self._backup_bias
|
self.bias = self._backup_bias
|
||||||
|
|
||||||
elif tag == "weight_perturb":
|
elif tag == "weight_perturb":
|
||||||
# In neuron.py weight_perturb, add:
|
|
||||||
print(f"Neuron {self.nid}: perturbing {len([w for i in self.order for w in self.inputs[i]['weights']])} weights")
|
print(f"Neuron {self.nid}: perturbing {len([w for i in self.order for w in self.inputs[i]['weights']])} weights")
|
||||||
# MP wie im Buch: 1/sqrt(TotalWeightsIncludingBias)
|
|
||||||
tot_w = sum(len(self.inputs[i]["weights"]) for i in self.order) + 1
|
tot_w = sum(len(self.inputs[i]["weights"]) for i in self.order) + 1
|
||||||
mp = 1 / math.sqrt(tot_w)
|
mp = 1 / math.sqrt(tot_w)
|
||||||
delta_mag = 2.0 * math.pi
|
delta_mag = 2.0 * math.pi
|
||||||
sat_lim = 2.0 * math.pi
|
sat_lim = 2.0 * math.pi
|
||||||
|
|
||||||
# Gewichte perturbieren
|
|
||||||
for i in self.order:
|
for i in self.order:
|
||||||
ws = self.inputs[i]["weights"]
|
ws = self.inputs[i]["weights"]
|
||||||
for j in range(len(ws)):
|
for j in range(len(ws)):
|
||||||
if random.random() < mp:
|
if random.random() < mp:
|
||||||
ws[j] = _sat(ws[j] + (random.random() - 0.5) * delta_mag, -sat_lim, sat_lim)
|
ws[j] = _sat(ws[j] + (random.random() - 0.5) * delta_mag, -sat_lim, sat_lim)
|
||||||
# Bias perturbieren
|
|
||||||
if random.random() < mp:
|
if random.random() < mp:
|
||||||
self.bias = _sat(self.bias + (random.random() - 0.5) * delta_mag, -sat_lim, sat_lim)
|
self.bias = _sat(self.bias + (random.random() - 0.5) * delta_mag, -sat_lim, sat_lim)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
# actors/scape.py
|
|
||||||
from actor import Actor
|
from actor import Actor
|
||||||
import math
|
import math
|
||||||
|
|
||||||
@@ -14,7 +13,7 @@ class XorScape(Actor):
|
|||||||
]
|
]
|
||||||
self.index = 0
|
self.index = 0
|
||||||
self.error_acc = 0.0
|
self.error_acc = 0.0
|
||||||
self.last_actuator = None # <-- wer uns zuletzt "action" geschickt hat
|
self.last_actuator = None
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
while True:
|
while True:
|
||||||
@@ -23,9 +22,8 @@ class XorScape(Actor):
|
|||||||
|
|
||||||
if tag == "sense":
|
if tag == "sense":
|
||||||
print("SCAPE: got sensed by sensor...")
|
print("SCAPE: got sensed by sensor...")
|
||||||
# Sensor fragt nach Input
|
|
||||||
_, sid, from_pid = msg
|
_, sid, from_pid = msg
|
||||||
self.last_actuator = from_pid # merken, wer beteiligt ist
|
self.last_actuator = from_pid
|
||||||
inputs, correct = self.data[self.index]
|
inputs, correct = self.data[self.index]
|
||||||
print("SENSOR input /correct: ", inputs, correct)
|
print("SENSOR input /correct: ", inputs, correct)
|
||||||
await from_pid.send(("percept", inputs))
|
await from_pid.send(("percept", inputs))
|
||||||
@@ -33,25 +31,23 @@ class XorScape(Actor):
|
|||||||
elif tag == "action":
|
elif tag == "action":
|
||||||
_, output, from_pid = msg
|
_, output, from_pid = msg
|
||||||
_, correct = self.data[self.index]
|
_, correct = self.data[self.index]
|
||||||
|
|
||||||
# Calculate RMSE like the Erlang version
|
|
||||||
step_error = sum((o - c) ** 2 for o, c in zip(output, correct))
|
step_error = sum((o - c) ** 2 for o, c in zip(output, correct))
|
||||||
step_rmse = math.sqrt(step_error) # This is the key change!
|
step_rmse = math.sqrt(step_error)
|
||||||
|
|
||||||
print(f"XOR PATTERN: Input={self.data[self.index][0]} → Network={output} → Expected={correct}")
|
print(f"XOR PATTERN: Input={self.data[self.index][0]} → Network={output} → Expected={correct}")
|
||||||
|
|
||||||
self.index += 1
|
self.index += 1
|
||||||
|
|
||||||
if self.index >= len(self.data):
|
if self.index >= len(self.data):
|
||||||
# Episode finished - calculate final fitness
|
|
||||||
total_rmse = math.sqrt(self.error_acc + step_rmse) # Not step_error!
|
total_rmse = math.sqrt(self.error_acc + step_rmse)
|
||||||
fitness = 1.0 / (total_rmse + 1e-5)
|
fitness = 1.0 / (total_rmse + 1e-5)
|
||||||
await from_pid.send(("result", fitness, 1))
|
await from_pid.send(("result", fitness, 1))
|
||||||
self.index = 0
|
self.index = 0
|
||||||
self.error_acc = 0.0
|
self.error_acc = 0.0
|
||||||
else:
|
else:
|
||||||
# Continue episode
|
# Continue episode
|
||||||
self.error_acc += step_rmse # Add RMSE, not raw error
|
self.error_acc += step_rmse
|
||||||
await from_pid.send(("result", 0.0, 0))
|
await from_pid.send(("result", 0.0, 0))
|
||||||
|
|
||||||
elif tag == "terminate":
|
elif tag == "terminate":
|
||||||
|
|||||||
@@ -1,22 +1,14 @@
|
|||||||
# trainer.py
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import os
|
||||||
import time
|
import time
|
||||||
from typing import Any, Dict, List, Tuple, Optional
|
from typing import Any, Dict, List, Tuple, Optional
|
||||||
|
|
||||||
import morphology # dein morphology.py
|
import morphology
|
||||||
from genotype import construct, save_genotype, print_genotype
|
from genotype import construct, save_genotype, print_genotype
|
||||||
from exoself import Exoself # deine Actor-basierte Exoself-Implementierung
|
from exoself import Exoself
|
||||||
|
|
||||||
|
|
||||||
class Trainer:
|
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__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
morphology_spec=morphology,
|
morphology_spec=morphology,
|
||||||
@@ -36,12 +28,8 @@ class Trainer:
|
|||||||
self.fitness_target = fitness_target
|
self.fitness_target = fitness_target
|
||||||
self.experimental_file = experimental_file
|
self.experimental_file = experimental_file
|
||||||
self.best_file = best_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
|
self.exoself_steps_per_eval = exoself_steps_per_eval
|
||||||
|
|
||||||
# Laufende Akkus (wie im Erlang-Code)
|
|
||||||
self.best_fitness = float("-inf")
|
self.best_fitness = float("-inf")
|
||||||
self.best_genotype: Optional[Dict[str, Any]] = None
|
self.best_genotype: Optional[Dict[str, Any]] = None
|
||||||
|
|
||||||
@@ -49,57 +37,38 @@ class Trainer:
|
|||||||
self.cycle_acc = 0
|
self.cycle_acc = 0
|
||||||
self.time_acc = 0.0
|
self.time_acc = 0.0
|
||||||
|
|
||||||
async def _run_one_attempt(self) -> Tuple[float, int, int, float, Dict[str, Any]]:
|
async def _run_one_attempt(self) -> Tuple[float, int, int, float]:
|
||||||
"""
|
|
||||||
Ein Trainingsversuch:
|
|
||||||
- Genotyp konstruieren
|
|
||||||
- Exoself laufen lassen
|
|
||||||
- Fitness/Evals/Cycles/Time zurückgeben + den verwendeten Genotyp
|
|
||||||
"""
|
|
||||||
print("constructing genotype...")
|
print("constructing genotype...")
|
||||||
geno = construct(self.morphology_spec, self.hds, file_name=self.experimental_file, add_bias=True)
|
geno = construct(
|
||||||
|
self.morphology_spec,
|
||||||
# Exoself starten und bis zum evaluation_completed warten
|
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)
|
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
|
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):
|
async def go(self):
|
||||||
"""
|
|
||||||
Entspricht dem Erlang loop/…:
|
|
||||||
Wiederholt Versuche, bis Stoppbedingung erfüllt.
|
|
||||||
"""
|
|
||||||
attempt = 1
|
attempt = 1
|
||||||
while True:
|
while True:
|
||||||
print(".........")
|
print(".........")
|
||||||
print("current attempt: ", attempt)
|
print("current attempt: ", attempt)
|
||||||
print(".........")
|
print(".........")
|
||||||
# Stoppbedingung vor Versuch?
|
|
||||||
if attempt > self.max_attempts or self.eval_acc >= self.eval_limit or self.best_fitness >= self.fitness_target:
|
if attempt > self.max_attempts or self.eval_acc >= self.eval_limit or self.best_fitness >= self.fitness_target:
|
||||||
# Ausgabe wie im Buch
|
# Abschlussausgabe wie im Buch
|
||||||
if self.best_genotype and self.best_file:
|
if self.best_file and os.path.exists(self.best_file):
|
||||||
# bestes Genotypfile ausgeben/„drucken“
|
|
||||||
save_genotype(self.best_file, self.best_genotype)
|
|
||||||
print_genotype(self.best_file)
|
print_genotype(self.best_file)
|
||||||
print(
|
print(
|
||||||
f" Morphology: {getattr(self.morphology_spec, '__name__', str(self.morphology_spec))} | "
|
f" Morphology: {getattr(self.morphology_spec, '__name__', str(self.morphology_spec))} | "
|
||||||
f"Best Fitness: {self.best_fitness} | EvalAcc: {self.eval_acc}"
|
f"Best Fitness: {self.best_fitness} | EvalAcc: {self.eval_acc}"
|
||||||
)
|
)
|
||||||
# Optional: an „Benchmarker“ melden – bei dir vermutlich nicht nötig
|
|
||||||
return {
|
return {
|
||||||
"best_fitness": self.best_fitness,
|
"best_fitness": self.best_fitness,
|
||||||
"eval_acc": self.eval_acc,
|
"eval_acc": self.eval_acc,
|
||||||
@@ -109,43 +78,35 @@ class Trainer:
|
|||||||
}
|
}
|
||||||
|
|
||||||
print("RUN ONE ATTEMPT!")
|
print("RUN ONE ATTEMPT!")
|
||||||
# --- Ein Versuch ---
|
fitness, evals, cycles, elapsed = await self._run_one_attempt()
|
||||||
fitness, evals, cycles, elapsed, geno = await self._run_one_attempt()
|
|
||||||
|
|
||||||
print("update akkus...")
|
print("update akkus...")
|
||||||
|
|
||||||
# Akkus updaten
|
|
||||||
self.eval_acc += evals
|
self.eval_acc += evals
|
||||||
self.cycle_acc += cycles
|
self.cycle_acc += cycles
|
||||||
self.time_acc += elapsed
|
self.time_acc += elapsed
|
||||||
|
|
||||||
# Besser als bisher?
|
# Besser als bisher?
|
||||||
if fitness > self.best_fitness:
|
if fitness > self.best_fitness:
|
||||||
# „experimental.json“ → „best.json“ (semantisch wie file:rename(...))
|
|
||||||
self.best_fitness = fitness
|
self.best_fitness = fitness
|
||||||
self.best_genotype = geno
|
if self.best_file and self.experimental_file and os.path.exists(self.experimental_file):
|
||||||
if self.best_file:
|
os.replace(self.experimental_file, self.best_file)
|
||||||
save_genotype(self.best_file, geno)
|
|
||||||
# Reset Attempt-Zähler (wie Erlang: Attempt=0 nach Verbesserung)
|
|
||||||
attempt = 1
|
attempt = 1
|
||||||
else:
|
else:
|
||||||
attempt += 1
|
attempt += 1
|
||||||
|
|
||||||
|
|
||||||
# --------------------------
|
|
||||||
# Beispiel: ausführen
|
|
||||||
# --------------------------
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Beispielkonfiguration (XOR-Morphologie, kleine Topologie)
|
|
||||||
trainer = Trainer(
|
trainer = Trainer(
|
||||||
morphology_spec=morphology, # oder morphology.xor_mimic
|
morphology_spec=morphology,
|
||||||
hidden_layer_densities=[2], # wie im Buch-Beispiel
|
hidden_layer_densities=[2],
|
||||||
max_attempts=1000,
|
max_attempts=200,
|
||||||
eval_limit=float("inf"),
|
eval_limit=float("inf"),
|
||||||
fitness_target=float("inf"),
|
fitness_target=99.9,
|
||||||
experimental_file="experimental.json",
|
experimental_file="experimental.json",
|
||||||
best_file="best.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())
|
asyncio.run(trainer.go())
|
||||||
|
|||||||
Reference in New Issue
Block a user