Weltmodell im Code · Teil 1 von 3
Die Welt und ihre Trainingsdaten
Bevor ein Weltmodell etwas vorhersagen kann, braucht es eine Umgebung und Beispiele. Dieser Teil zeigt, wie aus einer 7×7-Karte ein Datensatz aus Zuständen, Aktionen und Folgen entsteht.
Die Umgebung ist eine normale Funktion
Für das kleine Beispiel braucht es kein Framework. Die Welt ist eine Liste aus Zeichenketten. Jede Position ist ein Paar aus Zeile und Spalte.
KARTE = [
"#######",
"#..#..#",
"#..#..#",
"#.....#",
"#.##..#",
"#....Z#",
"#######",
]
AKTIONEN = ["hoch", "runter", "links", "rechts"]
VERSATZ = [(-1, 0), (1, 0), (0, -1), (0, 1)]
Eine Aktion ist im Programm eine Zahl von 0 bis 3. Über VERSATZ wird daraus eine Änderung der Position. Das macht die Übergangsfunktion kurz:
def schritt(pos, aktion):
dz, ds = VERSATZ[aktion]
neu = (pos[0] + dz, pos[1] + ds)
if KARTE[neu[0]][neu[1]] == "#":
neu = pos
fertig = neu == ZIEL
belohnung = 1.0 if fertig else 0.0
return neu, belohnung, fertig
Die Funktion liefert drei Werte: die neue Position, die Belohnung und das Ende der Episode. Läuft der Agent gegen eine Wand, bleibt die Position unverändert. Auch solche erfolglosen Aktionen gehören in die Trainingsdaten. Sonst könnte das Modell lernen, dass jede Aktion den Agenten verschiebt.
Der Agent bekommt keine Position
Die Position wäre eine bequeme Eingabe für das Modell. Dann bliebe vom Problem allerdings wenig übrig. Der Agent bekommt stattdessen ein Bild aus 49 Grauwerten.
def beobachtung(pos):
bild = []
for z, zeile in enumerate(KARTE):
for s, feld in enumerate(zeile):
if (z, s) == pos:
bild.append(0.3)
elif feld == "#":
bild.append(1.0)
elif feld == "Z":
bild.append(0.6)
else:
bild.append(0.0)
return bild
Das Ergebnis ist eine flache Liste. Das Modell weiß nicht, dass jeweils sieben Werte eine Zeile bilden. Es sieht nur 49 Zahlen. Die räumliche Struktur muss es aus den wiederkehrenden Mustern ableiten.
Ein Übergang ist eine Zeile Erfahrung
Zum Sammeln wählt der Agent zufällige Aktionen. Vor und nach jeder Aktion rendert das Programm eine Beobachtung und speichert alles in einem Dictionary.
uebergaenge.append({
"vorher": beobachtung(pos),
"aktion": aktion,
"nachher": beobachtung(neu),
"belohnung": belohnung,
"fertig": fertig,
})
Mit 3.000 solcher Einträge entsteht daten.json. In meinem Lauf enthielten nur 1,3 Prozent der Übergänge eine Belohnung. Für das Belohnungsmodell ist der Datensatz damit stark unausgewogen.
Die Zahl 1 in random.Random(1) ist der Seed. Sie legt die Folge zufälliger Aktionen fest. Mit demselben Seed erzeugt das Programm beim nächsten Lauf dieselbe Aktionsfolge:
zufall = random.Random(1)
aktion = zufall.randrange(len(AKTIONEN))
Das zeigt noch nicht, ob das Verfahren allgemein funktioniert. Beim Entwickeln ist es trotzdem nützlich: Ändert sich das Ergebnis trotz gleicher Aktionsfolge, lässt sich die Ursache im Code oder in den Daten suchen.
Was nach Teil 1 vorliegt
Der Datensatz enthält viele kleine Fragen samt Antwort: „Wenn dieses Bild zu sehen ist und der Agent nach links geht, welches Bild folgt?“ Im nächsten Teil wird jedes Bild von 49 auf acht Werte verkleinert. Erst danach lernt das Dynamikmodell die Übergänge.
Weiter zu Teil 2Wie aus 49 Pixeln acht Zahlen werden →