NeuroDino
A small neural network that plays Chrome's T-Rex game. Watch it play live,
with its network drawn next to it:
NeuroDino Space. The
code that trained it: github.com/ahm3texe/NeuroDino.
The paper describes all three models and how they were tested:
paper/neurodino-en.pdf
(Turkish original: paper/neurodino.pdf).
This is v3. The earlier models are in v2/ and v1/.
Model
A multilayer perceptron written with NumPy, no ML framework:
- 12 inputs β 56 hidden neurons (tanh) β 3 outputs (softmax): jump, duck, run
- 899 parameters: weights 12Γ56 + 56Γ3, biases 56 + 3
- hidden neurons 48β55 feed only duck; their weights to jump and run are 0
- every frame the obstacles move, the game computes the inputs and the dino takes the likeliest action
Inputs
Missing obstacles give 0. Distances are in game pixels, the speed in pixels per frame at 60 fps, and "the next obstacle" is the nearest one ahead of the dino.
| # | Name | Value |
|---|---|---|
| 0 | Obj1 Dist | 1 β min(1, frames to reach the next obstacle / 50): 1 is about to hit |
| 1 | Obj1 Act | 1 if the next obstacle is the highest bird (y < 75), which flies over a running dino |
| 2 | Obj1 W | min(1, its width / 75) |
| 3β5 | Obj2 Dist, Act, W | The same for the obstacle after it |
| 6 | Speed | Game speed / 13, the top speed |
| 7 | Gap | 1 β min(1, frames between the two obstacles / 15) |
| 8 | DinoY | min(1, height above the ground / 30) during a jump |
| 9 | DinoVel | Vertical velocity / 10 within [β1, 1] during a jump, negative while rising |
| 10 | Air | 1 while jumping |
| 11 | Duck | 1 while ducking |
The reference implementation is
neurodino/sensors.py.
Usage
import numpy as np
from huggingface_hub import hf_hub_download
from safetensors.numpy import load_file
w = load_file(hf_hub_download("ahm3texe/neurodino", "model.safetensors"))
def act(inputs):
"""The 12 inputs above in, 'jump', 'duck' or 'run' out."""
hidden = np.tanh(w["weights_ih"] @ inputs + w["bias_h"])
logits = w["weights_ho"] @ hidden + w["bias_o"]
return ["jump", "duck", "run"][int(np.argmax(logits))] # = argmax of the softmax
Compute the inputs after the obstacles have moved for the frame, as the
website and watch_model.py do. Duck in the air is a fast drop. To watch it
play, run watch_model.py from the GitHub repository.
Training
- v1: a genetic algorithm with 50 networks per generation (the best two carry over unchanged, the rest are bred by tournament selection, uniform crossover and Gaussian mutation), 83 generations from random weights. Each generation played a single game. Its runner let the networks decide before the obstacles moved each frame, so v1 acts on what it saw a frame earlier.
- v2: the same algorithm for 40 more generations starting from v1, in the website's loop, with every network playing the same 10 games each generation, and picked on validation games. 12-48-3; it never ducks.
- v3: v2 crashed about once in a hundred hours at top speed, nearly always still in the air when the next obstacle needed a jump; a fast drop would have saved it. v3 adds 8 hidden neurons that drive only duck. Frames in the air were labelled by playing on from them twice, with a duck and without, and the new neurons and duck's output row learned from those labels by gradient descent, with duck's bias then lowered by 1. Everything else is v2's, so every jump is exactly as v2 makes it.
The logs are training_log.csv (v1), training_log_v2.csv and
validation_v2.csv; v3's pipeline is in the repository's
training/.
Results
On the website's engine, four-hour games:
| Games | Ended before top speed | Lasted 4 hours | Crashes at top speed | |
|---|---|---|---|---|
| v1 | 200 (test) | 113 | 85 | 2 |
| v2 | 200 (test) | 0 | 190 | 10 |
| v3 | 200 (test) | 0 | 198 | 2 |
| v2 | 1,000 new | 0 | 960 | 40 (1 every ~100 h) |
| v3 | 1,000 new | 0 | 997 | 3 (1 every ~1,300 h) |
Neither v2 nor v3 had played the test games (seeds 1β200) or the new ones
before. Every game is in the repository's
evaluation/.
Limitations
- It was taught to duck only to drop fast in the air, not under a bird: Obj Act marks only the highest bird, which needs no action, so a bird it would have to duck under looks the same as a low one it can jump.
- Its timing is tuned to about a frame; played in another game loop, the results change completely (see v1 in the repository's evaluation).
- It sees at most two obstacles and tells distances apart only up to 50 frames ahead; Gap never rises above 0 with Chrome's normal obstacle gaps.
Files
| File | Contents |
|---|---|
model.safetensors |
weights_ih (56Γ12), bias_h (56), weights_ho (3Γ56), bias_o (3), float64 |
best-brain.json |
The same weights in the format the website loads |
config.json |
Sizes, activations, action and input names, input constants |
training_log.csv |
v1's training: best, average and record score of every generation |
training_log_v2.csv, validation_v2.csv |
v2's training and validation |
v2/, v1/ |
The earlier models' weights, in the same two formats |
License
MIT. The game it learned on is Chromium's T-Rex runner (BSD-3-Clause), ported to Python; not affiliated with Google.
- Downloads last month
- 44