LoginSignup
0
0

More than 5 years have passed since last update.

Toy-Neural-Network-JSでsteering

Posted at

概要

Toy-Neural-Network-JSでsteeringやってみた。

写真

image.png

サンプルコード

let sigmoid = new ActivationFunction(x => 1 / (1 + Math.exp(-x)), y => y * (1 - y));
let tanh = new ActivationFunction(x => Math.tanh(x), y => 1 - (y * y));
function mutate(x) {
    if (random(1) < 0.1) 
    {
        let offset = randomGaussian() * 0.5;
        let newx = x + offset;
        return newx;
    }
    else
    {
        return x;
    }
}
class Sensor 
{
    constructor(angle) {
        this.dir = p5.Vector.fromAngle(angle);
        this.val = 0;
    }
}
class Vehicle 
{
    constructor(brain) {
        this.acceleration = createVector();
        this.velocity = createVector();
        this.position = createVector(random(width), random(height));
        this.r = 4;
        this.maxforce = 0.1;
        this.maxspeed = 4;
        this.minspeed = 0.25;
        this.maxhealth = 3;
        this.score = 0;
        this.sensors = [];
        for (let angle = 0; angle < TWO_PI; angle += sensorAngle) 
        {
            this.sensors.push(new Sensor(angle));
        }
        if (brain) 
        {
            this.brain = brain.copy();
            this.brain.mutate(mutate);
        }
        else
        {
            let inputs = this.sensors.length + 6;
            this.brain = new NeuralNetwork(inputs, 32, 2);
        }
        this.health = 1;
    }
    update() {
        this.velocity.add(this.acceleration);
        this.velocity.limit(this.maxspeed);
        if (this.velocity.mag() < this.minspeed)
        {
            this.velocity.setMag(this.minspeed);
        }
        this.position.add(this.velocity);
        this.acceleration.mult(0);
        this.health = constrain(this.health, 0, this.maxhealth);
        this.health -= 0.005;
        this.score += 1;
    }
    dead() {
        return (this.health < 0 || this.position.x > width + this.r || this.position.x < -this.r || this.position.y > height + this.r || this.position.y < -this.r);
    }
    clone(prob) {
        let r = random(1);
        if (r < prob) 
        {
            return new Vehicle(this.brain);
        }
    }
    think(food) {
        for (let j = 0; j < this.sensors.length; j++) 
        {
            this.sensors[j].val = sensorLength;
        }
        for (let i = 0; i < food.length; i++) 
        {
            let otherPosition = food[i];
            let dist = p5.Vector.dist(this.position, otherPosition);
            if (dist > sensorLength)
            {
                continue;
            }
            let toFood = p5.Vector.sub(otherPosition, this.position);
            for (let j = 0; j < this.sensors.length; j++)
            {
                let delta = this.sensors[j].dir.angleBetween(toFood);
                if (delta < sensorAngle / 2)
                {
                    this.sensors[j].val = min(this.sensors[j].val, dist);
                }
            }
        }
        let inputs = [];
        inputs[0] = constrain(map(this.position.x, foodBuffer, 0, 0, 1), 0, 1);
        inputs[1] = constrain(map(this.position.y, foodBuffer, 0, 0, 1), 0, 1);
        inputs[2] = constrain(map(this.position.x, width - foodBuffer, width, 0, 1), 0, 1);
        inputs[3] = constrain(map(this.position.y, height - foodBuffer, height, 0, 1), 0, 1);
        inputs[4] = this.velocity.x / this.maxspeed;
        inputs[5] = this.velocity.y / this.maxspeed;
        for (let j = 0; j < this.sensors.length; j++)
        {
            inputs[j + 6] = map(this.sensors[j].val, 0, sensorLength, 1, 0);
        }
        let outputs = this.brain.predict(inputs);
        let desired = createVector(2 * outputs[0] - 1, 2 * outputs[1] - 1);
        desired.mult(this.maxspeed);
        let steer = p5.Vector.sub(desired, this.velocity);
        steer.limit(this.maxforce);
        this.applyForce(steer);
    }
    eat(list) {
        for (let i = list.length - 1; i >= 0; i--) 
        {
            let d = p5.Vector.dist(list[i], this.position);
            if (d < foodRadius) 
            {
                list.splice(i, 1);
                this.health++;
            }
        }
    }
    applyForce(force) {
        this.acceleration.add(force);
    }
    display() {
        let green = color(0, 255, 255, 255);
        let red = color(255, 0, 100, 100);
        let col = lerpColor(red, green, this.health)
        push();
        translate(this.position.x, this.position.y);
        if (debug.checked()) 
        {
            for (let i = 0; i < this.sensors.length; i++) 
            {
                let val = this.sensors[i].val;
                if (val > 0)
                {
                    stroke(col);
                    strokeWeight(map(val, 0, sensorLength, 4, 0));
                    let position = this.sensors[i].dir;
                    line(0, 0, position.x * val, position.y * val);
                }
            }
            noStroke();
            fill(255, 200);
            text(int(this.score), 10, 0);
        }
        let theta = this.velocity.heading() + PI / 2;
        rotate(theta);
        fill(col);
        strokeWeight(1);
        stroke(col);
        beginShape();
        vertex(0, -this.r * 2);
        vertex(-this.r, this.r * 2);
        vertex(this.r, this.r * 2);
        endShape(CLOSE);
        pop();
    }
    highlight() {
        fill(255, 255, 255, 50);
        stroke(255);
        ellipse(this.position.x, this.position.y, 32, 32);
    }
}
let population = [];
let food = [];
let debug;
let speedSlider;
let speedSpan;
let foodRadius = 8;
let foodAmount = 25;
let foodBuffer = 50;
let totalSensors = 8;
let sensorLength = 150;
let sensorAngle = (Math.PI * 2) / totalSensors;
let best = null;
function setup() {
    let canvas = createCanvas(640, 360);
    canvas.parent('canvascontainer');
    debug = select('#debug');
    speedSlider = select('#speedSlider');
    speedSpan = select('#speed');
    for (let i = 0; i < 20; i++)
    {
        population[i] = new Vehicle();
    }
}
function draw() {
    background(0);
    let cycles = speedSlider.value();
    speedSpan.html(cycles);
    for (let n = 0; n < cycles; n++)
    {
        while (food.length < foodAmount) 
        {
            food.push(createVector(random(foodBuffer, width - foodBuffer), random(foodBuffer, height - foodBuffer)));
        }
        for (let v of population)
        {
            v.eat(food);
        }
        let record = -1;
        for (let i = population.length - 1; i >= 0; i--) 
        {
            let v = population[i];
            v.think(food);
            v.update(food);
            if (v.dead())
            {
                population.splice(i, 1);
            } 
            else
            {
                if (v.score > record) 
                {
                    record = v.score;
                    best = v;
                }
            }
        }
        if (population.length < 20) 
        {
            for (let v of population) 
            {
                let newVehicle = v.clone(0.1 * v.score / record);
                if (newVehicle != null) 
                {
                    population.push(newVehicle);
                }
            }
        }
    }
    for (let i = 0; i < food.length; i++)
    {
        fill(100, 255, 100, 200);
        stroke(100, 255, 100);
        ellipse(food[i].x, food[i].y, foodRadius * 2);
    }
    if (debug.checked()) 
    {
        best.highlight();
    }
    for (let v of population) 
    {
        v.display();
    }
}

成果物

以上。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0