little-walkers-nn.js

total 0
used 0
limit 0
/* title: Poly Bugs! files: head point pointlist stage mouse dragging stroke ../point_src/random.js ../point_src/relative.js ../point_src/screenwrap.js ../point_src/iter/lerp.js ../point_src/smooth-number.js --- Little walking points. */ class NeuralNetwork { constructor(inputSize, hiddenSize, outputSize, learningRate=0.03) { this.inputSize = inputSize; this.hiddenSize = hiddenSize; this.outputSize = outputSize; this.weightsInputToHidden = Array.from({ length: hiddenSize }, () => Array.from({ length: inputSize }, () => Math.random() * 2 - 1) ); this.biasHidden = Array(hiddenSize).fill(0); this.weightsHiddenToOutput = Array.from({ length: outputSize }, () => Array.from({ length: hiddenSize }, () => Math.random() * 2 - 1) ); this.biasOutput = Array(outputSize).fill(0); this.learningRate = learningRate; // Adjusted learning rate this.hiddenLayer = new Array(this.hiddenSize); } feedForward(inputs) { for (let i = 0; i < this.hiddenSize; i++) { this.hiddenLayer[i] = 0; for (let j = 0; j < this.inputSize; j++) { this.hiddenLayer[i] += this.weightsInputToHidden[i][j] * inputs[j]; } this.hiddenLayer[i] += this.biasHidden[i]; this.hiddenLayer[i] = sigmoid(this.hiddenLayer[i]); } const output = new Array(this.outputSize); for (let i = 0; i < this.outputSize; i++) { output[i] = 0; for (let j = 0; j < this.hiddenSize; j++) { output[i] += this.weightsHiddenToOutput[i][j] * this.hiddenLayer[j]; } output[i] += this.biasOutput[i]; output[i] = sigmoid(output[i]); } return output; } trainOne(inputs, target) { for (let i = 0; i < this.hiddenSize; i++) { this.hiddenLayer[i] = 0; for (let j = 0; j < this.inputSize; j++) { this.hiddenLayer[i] += this.weightsInputToHidden[i][j] * inputs[j]; } this.hiddenLayer[i] += this.biasHidden[i]; this.hiddenLayer[i] = sigmoid(this.hiddenLayer[i]); } const output = new Array(this.outputSize); for (let i = 0; i < this.outputSize; i++) { output[i] = 0; for (let j = 0; j < this.hiddenSize; j++) { output[i] += this.weightsHiddenToOutput[i][j] * this.hiddenLayer[j]; } output[i] += this.biasOutput[i]; output[i] = sigmoid(output[i]); } const errorsOutput = new Array(this.outputSize); const errorsHidden = new Array(this.hiddenSize); for (let i = 0; i < this.outputSize; i++) { errorsOutput[i] = target[i] - output[i]; for (let j = 0; j < this.hiddenSize; j++) { this.weightsHiddenToOutput[i][j] += this.learningRate * errorsOutput[i] * output[i] * (1 - output[i]) * this.hiddenLayer[j]; } this.biasOutput[i] += this.learningRate * errorsOutput[i]; } for (let i = 0; i < this.hiddenSize; i++) { errorsHidden[i] = 0; for (let j = 0; j < this.outputSize; j++) { errorsHidden[i] += this.weightsHiddenToOutput[j][i] * errorsOutput[j]; } this.biasHidden[i] += this.learningRate * errorsHidden[i]; for (let j = 0; j < this.inputSize; j++) { this.weightsInputToHidden[i][j] += this.learningRate * errorsHidden[i] * this.hiddenLayer[i] * (1 - this.hiddenLayer[i]) * inputs[j]; } } } train(count=30000) { let trainingData = this.trainingData for (let i = 0; i < parseInt(count); i++) { const data = trainingData[Math.floor(Math.random() * trainingData.length)]; this.trainOne([data.x, data.y], this.encodeOne(data.label)); } console.log("Training complete"); } } class Classifier extends NeuralNetwork { /* Simple classifier. in -> values out -> classification. */ constructor(inputSize, hiddenSize, outputSize, learningRate=0.03) { super(inputSize, hiddenSize, outputSize, learningRate=0.03) this.trainingData = this.createTrainingData() } createTrainingData() { const trainingData = []; const numDataPoints = 1000; // Adjust the number of data points as needed for (let i = 0; i < numDataPoints; i++) { const x = Math.random() * 2 - 1; // Random x value between -1 and 1 const y = Math.random() * 2 - 1; // Random y value between -1 and 1 let label; if (x <= 0 && y < 0) { label = "blue"; } else if (x <= 0 && y > 0) { label = "green"; } else if (x > 0 && y <= 0) { label = "red"; } else { label = "purple"; } trainingData.push({ x, y, label }); } return trainingData } classifications(){ const encoding = { blue: [1, 0, 0, 0], red: [0, 1, 0, 0], green: [0, 0, 1, 0], purple: [0, 0, 0, 1] }; return encoding } predict() { let points = []; for (let i = 0; i < 20; i++) { const x = Math.random() * 2 - 1; // Random x-coordinate between -1 and 1 const y = Math.random() * 2 - 1; // Random y-coordinate between -1 and 1 const output = this.feedForward([x, y]); const predictedLabel = this.decodeOne(output); points.push({ x, y, predictedLabel }); } console.log(points); console.log(this.hiddenLayer); return [points, this.hiddenLayer] } ask(...v) { return this.decodeOne(this.feedForward(v)); } encodeOne(label) { const encoding = this.classifications() return encoding[label]; } decodeOne(output) { const labels = Object.keys(this.classifications()) const maxIndex = output.indexOf(Math.max(...output)); return labels[maxIndex]; } } class WalkerNetwork extends NeuralNetwork { constructor(inputSize=2, hiddenSize=6, outputSize=2, learningRate=0.03) { super(inputSize, hiddenSize, outputSize, learningRate) this.exploration = .15 this.maxForward = 20 this.maxRotation = 30 this.rewardDiscount = .95 this.memorySize = 30 this.memory = [] this.score = 0 } think(inputs, explore=true) { if(inputs.length !== this.inputSize) { throw new Error(`Expected ${this.inputSize} sensor values, received ${inputs.length}`) } const sensorValues = inputs.map(value => Math.max(-1, Math.min(1, value))) const expected = this.feedForward(sensorValues) const action = expected.map(value => { if(!explore) return value const noise = (Math.random() * 2 - 1) * this.exploration return Math.max(0, Math.min(1, value + noise)) }) if(explore) { this.memory.push({ inputs: sensorValues, expected: expected.slice(), action: action.slice() }) if(this.memory.length > this.memorySize) this.memory.shift() } return [ action[0] * this.maxForward, (action[1] * 2 - 1) * this.maxRotation ] } reward(value=1) { if(!Number.isFinite(value)) return this.score += value const reward = Math.max(-1, Math.min(1, value)) const decisions = this.memory.splice(0) decisions.forEach((decision, index) => { const age = decisions.length - 1 - index const strength = reward * Math.pow(this.rewardDiscount, age) const target = decision.expected.map((expected, outputIndex) => { const explored = decision.action[outputIndex] return Math.max(0, Math.min(1, expected + (explored - expected) * strength )) }) this.trainOne(decision.inputs, target) }) } } // var hiddenNodes = parseInt(6); // neuralNetwork = new NeuralNetwork(2, hiddenNodes, 4); // Example; neuralNetwork = new Classifier(2, 6, 4); function sigmoid(x) { return 1 / (1 + Math.exp(-x)); } const walkTime = 10 class AutoLerpPoint extends Point { tick = 0 xSet(value) { // console.log('set x') if(this.newX == undefined) { this.newX = new Value(value, value, undefined, true) } this.startDateX = +(new Date) this.endDateX = this.startDateX + walkTime // 1 second this.tickX = 0 let old = this._opts['x'] this.newX.a = old this.newX.b = value this.newX.step = 0 return old } rotationSet(value) { // console.log('set x') if(this.newR == undefined) { this.newR = new Value(value, value, undefined, true) } this.startDateRotation = +(new Date) this.endDateRotation = this.startDateRotation + walkTime // 1 second this.tickRotation = 0 let old = this._opts['rotation'] this.newR.a = old this.newR.b = value this.newR.step = 0 return old } onScreenWrapChange(x, y) { if(y !== undefined){ this.newY.a = this.newY.b = y this.newY.step = 1 } if(x !== undefined) { this.newX.a = this.newX.b = x this.newX.step = 1 } } ySet(value) { // console.log('set y') if(this.newY == undefined) { this.newY = new Value(value, value, undefined, true) } this.startDateY = +(new Date) this.endDateY = this.startDateY + walkTime // 1 second this.tickY = 0 let old = this._opts['y'] this.newY.a = old this.newY.b = value this.newY.step = 0 return old } currentSlide(k) { if(k == 'x') { let width = this.endDateX - this.startDateX let slide = 1 - ( this.endDateX - (this.startDateX + this.tickX) ) / width return slide; } if(k == 'y') { let width = this.endDateY - this.startDateY let slide = 1 - ( this.endDateY - (this.startDateY + this.tickY) ) / width return slide; } if(k == 'rotation') { let width = this.endDateRotation - this.startDateRotation let slide = 1 - ( this.endDateRotation - (this.startDateRotation + this.tickRotation) ) / width return slide; } } step() { // let slide = this.currentSlide() // console.log(slide) this.tick++ this.tickX++ this.tickY++ this.tickRotation++ this._opts['x'] = this.newX.get(this.currentSlide('x')) this._opts['y'] = this.newY.get(this.currentSlide('y')) if(this.newR){ this._opts['rotation'] = this.newR.get(this.currentSlide('rotation')) } } project(distance, rotation, relative=true) { if(rotation !== undefined && relative == true) { rotation = (this.UP + rotation) % 360 } let np = new Point(projectFrom(this, distance, rotation)) np.rotation = this.rotation return np } } class MainStage extends Stage { // canvas = document.getElementById('playspace'); canvas = 'playspace' mounted(){ let count = 10 let multiplier = [400, 400, 5, 270] let offset = this.center.copy().subtract(multiplier[0] * .5) this.points = PointList.generate.random(count, multiplier, offset) this.points = this.points.cast(AutoLerpPoint) this.points.each.color = ()=>random.color([290, 310], [50,100], [22,60]) this.points.each.radius = ()=>random.int(1, 15) this.points.forEach(p => p.brain = new WalkerNetwork()) this.actionInterval = walkTime this.sensorRange = 800 this.progressRewardScale = .05 this.food = new Point({radius: 10, color: '#72b84a'}) this.spawnFood() this.targetPoint = 3 this.dragging.add(this.points[0]) this.tick = 0 } spawnFood() { this.food.x = this.center.x + random.int(-400, 400) this.food.y = this.center.y + random.int(-400, 400) this.points.forEach(p => p.previousFoodDistance = undefined) } readSensors(p) { const x = this.food.x - p.x const y = this.food.y - p.y const bearingRadians = Math.atan2(y, x) - p.radians const bearing = Math.atan2( Math.sin(bearingRadians), Math.cos(bearingRadians) ) / Math.PI const distance = Math.hypot(x, y) const normalizedDistance = Math.max(-1, Math.min(1, (distance / this.sensorRange) * 2 - 1 )) return [bearing, normalizedDistance] } rewardProgress(p) { const distance = p.distanceTo(this.food) const previousDistance = p.previousFoodDistance p.previousFoodDistance = distance if(previousDistance === undefined) return 0 const progress = previousDistance - distance const plausibleMovement = Math.abs(progress) <= p.brain.maxForward * 1.5 if(!plausibleMovement) return 0 const normalizedProgress = Math.max(-1, Math.min(1, progress / p.brain.maxForward )) const reward = normalizedProgress * this.progressRewardScale p.brain.reward(reward) return reward } neuralMove(p) { if(this.tick % this.actionInterval !== 0) return this.rewardProgress(p) const [forward, rotation] = p.brain.think(this.readSensors(p)) p.rotation += rotation p.relative.forward(forward, 0, p.brain.maxForward) } awardFood(p) { const touching = p.distanceTo(this.food) <= p.radius + this.food.radius if(!touching) return false p.brain.reward(1) this.spawnFood() return true } draw(ctx){ this.tick++; this.clear(ctx) this.screenWrap.performMany(this.points) this.points.forEach(p=>{ this.neuralMove(p) p.step() this.awardFood(p) }) this.food.pen.fill(ctx, this.food.color) // this.points.pen.fill(ctx) // this.points.pen.lines(ctx, { width: 2, color: '#111'}) this.points.pen.indicator(ctx, { width: 2}) } } stage = MainStage.go(/*{ loop: true }*/)
Run
Meta Data
title Poly Bugs!
imports ()
files ('head', 'point', 'pointlist', 'stage', 'mouse', 'dragging', 'stroke', '../point_src/random.js', '../point_src/relative.js', '../point_src/screenwrap.js', '../point_src/iter/lerp.js', '../point_src/smooth-number.js')
unused_keys ()
unknown_keys ()
filepath_exists True
path little-walkers-nn.js
filepath little-walkers-nn.js
clean_files ('../point_src/core/head.js', '../point_src/pointpen.js', '../point_src/compass.js', '../point_src/center.js', '../point_src/point-content.js', '../point_src/pointdraw.js', '../point_src/relative-xy.js', '../point_src/pointcast.js', '../point_src/point.js', '../point_src/pointlistdraw.js', '../point_src/pointlistgradient.js', '../point_src/pointlistshape.js', '../point_src/pointlistgenerator.js', '../point_src/unpack.js', '../point_src/pointlist.js', '../point_src/pointlistpen.js', '../point_src/stage-hooks.js', '../point_src/stage-resize.js', '../point_src/functions/resolve.js', '../point_src/stage.js', '../point_src/events.js', '../point_src/automouse.js', '../point_src/functions/clamp.js', '../point_src/distances.js', '../point_src/protractor.js', '../point_src/text/beta.js', '../point_src/dragging.js', '../point_src/setunset.js', '../point_src/stroke.js', '../point_src/random.js', '../point_src/relative.js', '../point_src/screenwrap.js', '../point_src/iter/lerp.js', '../point_src/smooth-number.js')
markdown {'html': '<hr />\n<p>Little walking points.</p>', 'content': 'title: Poly Bugs!\nfiles:\n head\n point\n pointlist\n stage\n mouse\n dragging\n stroke\n ../point_src/random.js\n ../point_src/relative.js\n ../point_src/screenwrap.js\n ../point_src/iter/lerp.js\n ../point_src/smooth-number.js\n\n---\n\nLittle walking points.'}