World Model in Code · Part 1 of 3
The World and Its Training Data
DE Auf Deutsch lesenBefore a world model can predict anything, it needs an environment and examples. This part shows how a 7×7 map turns into a dataset of states, actions and outcomes.
The environment is just a plain function
For this small example, no framework is needed. The world is a list of strings. Each position is a pair of row and column.
KARTE = [
"#######",
"#..#..#",
"#..#..#",
"#.....#",
"#.##..#",
"#....Z#",
"#######",
]
AKTIONEN = ["hoch", "runter", "links", "rechts"]
VERSATZ = [(-1, 0), (1, 0), (0, -1), (0, 1)]
In the program, an action is a number from 0 to 3. VERSATZ turns it into a change in position. That keeps the transition function short:
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
The function returns three values: the new position, the reward, and whether the episode has ended. If the agent runs into a wall, the position stays unchanged. These unsuccessful actions also belong in the training data. Otherwise the model could learn that every action moves the agent.
The agent doesn't get a position
The position would be a convenient input for the model. But then not much would be left of the problem. Instead, the agent gets an image made of 49 grayscale values.
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
The result is a flat list. The model doesn't know that every seven values form a row. It only sees 49 numbers. It has to derive the spatial structure from recurring patterns.
A transition is one line of experience
To collect data, the agent picks random actions. Before and after each action, the program renders an observation and stores everything in a dictionary.
uebergaenge.append({
"vorher": beobachtung(pos),
"aktion": aktion,
"nachher": beobachtung(neu),
"belohnung": belohnung,
"fertig": fertig,
})
With 3,000 such entries, daten.json is created. In my run, only 1.3 percent of transitions carried a reward. For the reward model, the dataset is therefore heavily imbalanced.
The number 1 in random.Random(1) is the seed. It fixes the sequence of random actions. With the same seed, the program produces the same action sequence on the next run:
zufall = random.Random(1)
aktion = zufall.randrange(len(AKTIONEN))
This doesn't yet show whether the approach works in general. During development it's useful anyway: if the result changes despite the same action sequence, the cause can be tracked down in the code or in the data.
What part 1 leaves us with
The dataset contains many small questions paired with answers: "If this image is visible and the agent moves left, which image follows?" In the next part, every image gets shrunk from 49 down to eight values. Only after that does the dynamics model learn the transitions.
Continue to part 2How 49 pixels become eight numbers →