Skip to content

Commit e1de9e9

Browse files
committed
working neuroevolution steering example
1 parent 1bce5fc commit e1de9e9

File tree

4 files changed

+245
-0
lines changed

4 files changed

+245
-0
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>ml5.js NeuroEvolution Steering</title>
8+
<script src="p5.min.js"></script>
9+
<script src="../../dist/ml5.js"></script>
10+
</head>
11+
<body>
12+
<script src="vehicle.js"></script>
13+
<script src="sketch.js"></script>
14+
</body>
15+
</html>

examples/NeuroEvolution-steering/p5.min.js

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
let vehicles = [];
2+
let timeSlider;
3+
let lifetime = 0;
4+
5+
let target;
6+
7+
function setup() {
8+
createCanvas(640, 240);
9+
// Initialize a set number of vehicles
10+
for (let i = 0; i < 50; i++) {
11+
vehicles[i] = new Vehicle(random(width), random(height));
12+
}
13+
ml5.tf.setBackend("cpu");
14+
15+
target = createVector(random(width), random(height));
16+
timeSlider = createSlider(1, 20, 1);
17+
}
18+
19+
function draw() {
20+
background(255);
21+
22+
for (let i = 0; i < timeSlider.value(); i++) {
23+
for (let v of vehicles) {
24+
v.edges();
25+
v.think(target);
26+
v.eat(target);
27+
v.update();
28+
}
29+
lifetime++;
30+
}
31+
32+
let best = null;
33+
let record = -1;
34+
for (let v of vehicles) {
35+
v.show();
36+
if (v.fitness > record) {
37+
record = v.fitness;
38+
best = v;
39+
}
40+
}
41+
42+
best.show();
43+
noStroke();
44+
fill(255, 0, 0, 100);
45+
circle(best.position.x, best.position.y, 64);
46+
fill(0);
47+
text(best.fitness, best.position.x + 12, best.position.y);
48+
fill(0, 0, 255, 100);
49+
circle(target.x, target.y, 32);
50+
51+
stroke(0, 100);
52+
strokeWeight(1);
53+
//line(target.x, target.y, best.position.x, best.position.y);
54+
55+
if (lifetime > 500) {
56+
normalizeFitness();
57+
reproduction();
58+
target = createVector(random(width), random(height));
59+
lifetime = 0;
60+
}
61+
// if (allVehiclesDead()) {
62+
// }
63+
64+
fill(0);
65+
text(lifetime, 10, 30);
66+
}
67+
68+
// function allVehiclesDead() {
69+
// for (let v of vehicles) {
70+
// if (v.alive) {
71+
// return false;
72+
// }
73+
// }
74+
// return true;
75+
// }
76+
77+
function normalizeFitness() {
78+
let recordFitness = 0;
79+
for (let v of vehicles) {
80+
if (v.fitness > recordFitness) {
81+
recordFitness = v.fitness;
82+
}
83+
v.fitness = pow(2, v.fitness);
84+
}
85+
console.log(recordFitness);
86+
87+
let sum = 0;
88+
for (let v of vehicles) {
89+
sum += v.fitness;
90+
}
91+
for (let v of vehicles) {
92+
v.fitness = v.fitness / sum;
93+
}
94+
}
95+
96+
function reproduction() {
97+
let nextVehicles = [];
98+
for (let i = 0; i < vehicles.length; i++) {
99+
let parentA = weightedSelection();
100+
let parentB = weightedSelection();
101+
let child = parentA.crossover(parentB);
102+
child.mutate(0.1);
103+
nextVehicles[i] = new Vehicle(random(width), random(height), child);
104+
}
105+
vehicles = nextVehicles;
106+
}
107+
108+
function weightedSelection() {
109+
let index = 0;
110+
let start = random(1);
111+
while (start > 0) {
112+
start = start - vehicles[index].fitness;
113+
index++;
114+
}
115+
index--;
116+
return vehicles[index].brain;
117+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
class Vehicle {
2+
constructor(x, y, brain) {
3+
this.position = createVector(x, y);
4+
this.velocity = createVector(0, 0);
5+
this.acceleration = createVector(0, 0);
6+
this.r = 4;
7+
this.maxspeed = 4;
8+
this.fitness = 0;
9+
10+
if (brain) {
11+
this.brain = brain;
12+
} else {
13+
this.brain = ml5.neuralNetwork({
14+
inputs: 2,
15+
outputs: 2,
16+
task: "regression",
17+
neuroEvolution: true,
18+
});
19+
}
20+
}
21+
22+
eat(food) {
23+
let d = p5.Vector.dist(this.position, food);
24+
if (d < 10) {
25+
this.fitness++;
26+
// this.position = createVector(random(width), random(height));
27+
target = createVector(random(width), random(height));
28+
}
29+
}
30+
31+
think(food) {
32+
let v = p5.Vector.sub(food, this.position);
33+
v.normalize();
34+
let inputs = [v.x, v.y];
35+
36+
// Predicting the force to apply
37+
const outputs = this.brain.predictSync(inputs);
38+
// let x = 2 * outputs[0].value - 1;
39+
// let y = 2 * outputs[1].value - 1;
40+
// let force = createVector(x, y); //.setMag(magnitude);
41+
// force.setMag(1);
42+
let angle = outputs[0].value * TWO_PI;
43+
let magnitude = outputs[1].value;
44+
let force = p5.Vector.fromAngle(angle).setMag(magnitude);
45+
this.applyForce(force);
46+
}
47+
48+
// Method to update location
49+
update() {
50+
// Update velocity
51+
this.velocity.add(this.acceleration);
52+
// Limit speed
53+
this.velocity.limit(this.maxspeed);
54+
this.position.add(this.velocity);
55+
// Reset acceleration to 0 each cycle
56+
this.acceleration.mult(0);
57+
58+
// if (
59+
// this.position.x > width ||
60+
// this.position.y > height ||
61+
// this.position.x < 0 ||
62+
// this.position.y < 0
63+
// ) {
64+
// this.alive = false;
65+
// }
66+
}
67+
68+
applyForce(force) {
69+
// We could add mass here if we want A = F / M
70+
this.acceleration.add(force);
71+
}
72+
73+
// Wraparound
74+
edges() {
75+
// this.position.x = constrain(this.position.x, 0, width);
76+
// this.position.y = constrain(this.position.y, 0, height);
77+
//if (this.position.x < -this.r) this.position.x = width + this.r;
78+
//if (this.position.y < -this.r) this.position.y = height + this.r;
79+
//if (this.position.x > width + this.r) this.position.x = -this.r;
80+
//if (this.position.y > height + this.r) this.position.y = -this.r;
81+
}
82+
83+
show() {
84+
//{!1} Vehicle is a triangle pointing in the direction of velocity
85+
let angle = this.velocity.heading();
86+
fill(127);
87+
stroke(0);
88+
push();
89+
translate(this.position.x, this.position.y);
90+
rotate(angle);
91+
beginShape();
92+
vertex(this.r * 2, 0);
93+
vertex(-this.r * 2, -this.r);
94+
vertex(-this.r * 2, this.r);
95+
endShape(CLOSE);
96+
pop();
97+
98+
//fill(0);
99+
//noStroke();
100+
//text(this.fitness, this.position.x + 12, this.position.y);
101+
102+
// let d = p5.Vector.dist(this.position, this.target);
103+
// if (d < 50) {
104+
// stroke(0, 200);
105+
// strokeWeight(1);
106+
// line(this.position.x, this.position.y, this.target.x, this.target.y);
107+
// fill(0, 255, 0, 100);
108+
// circle(this.target.x, this.target.y, 8);
109+
//}
110+
}
111+
}

0 commit comments

Comments
 (0)