// Cloned by K.Ellis on 30 Nov 2019 from World "Perceptron" by "Coding Train" project
// Please leave this clone trail here.
var errorCountZero = 0; //KE
var errorCountTwo = 0; //KE
var errorCountMinusTwo = 0; //KE
// Port from
// https://github.com/nature-of-code/noc-examples-p5.js/tree/master/chp10_nn/NOC_10_01_Perceptron
// A list of points we will use to "train" the perceptron
let training = new Array(400);
const LearningConstant = 0.1; // easier to watch if it is low
// Coordinate space
let xmin = -1;
let ymin = -1;
let xmax = 1;
let ymax = 1;
// set fixed width run header
AB.headerWidth ( 400 );
// function to draw a line
// y = ax + b
// original: y = 0.3 * x + 0.4
const a = AB.randomFloatAtoB ( 0.1, 0.9 );
const b = AB.randomFloatAtoB ( 0.1, 0.9 );
const trainingLimit = training.length*5; //KE
function f(x)
{
return ( a * x + b );
}
// classification is are you above or below line
// perceptron should move towards the line
function getClassification ( x, y )
{
if (y < f(x)) return ( -1 );
else return ( 1 );
}
// Daniel Shiffman
// The Nature of Code
// http://natureofcode.com
// Simple Perceptron Example
// See: http://en.wikipedia.org/wiki/Perceptron
// Perceptron Class
// Perceptron is created with n weights and learning constant
class Perceptron
{
constructor(n, c)
{
// Array of weights for inputs
this.weights = new Array(n);
// Start with random weights
for (let i = 0; i < this.weights.length; i++) {
this.weights[i] = random(-1, 1);
}
this.c = c; // learning rate/constant
}
// Function to train the Perceptron
// Weights are adjusted based on "desired" answer
train(inputs, desired)
{
// Guess the result
let guess = this.feedforward(inputs);
// Compute the factor for changing the weight based on the error
// Error = desired output - guessed output
// Note this can only be 0, -2, or 2
// Multiply by learning constant
let error = desired - guess;
if(error === 0) errorCountZero++;
if(error === -2) errorCountMinusTwo++;
if(error === 2) errorCountTwo++;
/*
if(step == training.length/3) this.c = 0.03;
if(step == training.length/2) this.c = 0.01;
if(step == training.length) this.c = 0.005;
if(step == training.length * 2) this.c = 0.001;
*/
if(step == 100) this.c = 0.05;
if(step == 200) this.c = 0.03;
if(step == 300) this.c = 0.01;
if(step == 400) this.c = 0.007;
if(step == 600) this.c = 0.005;
if(step == 800) this.c = 0.001;
console.log("Step No. = " + step + " , error = " + error + ", c = " + this.c + ", -2 count = " + errorCountMinusTwo + " , +2 count = " + errorCountTwo + " , 0 count = " + errorCountZero );
// Adjust weights based on weightChange * input
for (let i = 0; i < this.weights.length; i++)
{
this.weights[i] += this.c * error * inputs[i];
}
}
// Guess -1 or 1 based on input values
feedforward(inputs)
{
// Sum all values
let sum = 0;
for (let i = 0; i < this.weights.length; i++)
{
sum += inputs[i] * this.weights[i];
}
// Result is sign of the sum, -1 or 1
//console.log("sum " + this.activate(sum)); //KE
return this.activate(sum);
}
activate(sum)
{
if (sum > 0) return 1;
else return -1;
}
// Return weights
getWeights()
{
return this.weights;
}
}
// The Nature of Code
// Daniel Shiffman
// http://natureofcode.com
// Simple Perceptron Example
// See: http://en.wikipedia.org/wiki/Perceptron
// Code based on text "Artificial Intelligence", George Luger
// A Perceptron object
let ptron;
// We will train the perceptron with one "Point" object at a time
let count = 0;
function setup()
{
createCanvas(800, 800);
// The perceptron has 3 inputs
// x, y, and bias
ptron = new Perceptron ( 3, LearningConstant );
// Create a random set of training points and calculate the "known" answer
for (let i = 0; i < training.length; i++)
{
let x = random(xmin, xmax);
let y = random(ymin, ymax);
let answer = getClassification ( x, y );
training[i] =
{
input: [x, y, 1],
output: answer
};
}
}
var step = 1;
AB.world.endRun = function()
{
if ( AB.abortRun )
AB.msg ( "<font color=green> <B> Training steps limit reached : </B> </font>" + trainingLimit +
"<br> <font color=green> <B> Error 0 = </B> </font>" + errorCountZero +
"<br> <font color=green> <B> Error +2 = </B> </font>" + errorCountTwo +
"<br> <font color=green> <B> Error -2 = </B> </font>" + errorCountMinusTwo +
"<br> <font color=green> <B> Error 0 as % of t-steps= </B> </font>" + Math.trunc( (errorCountZero / step) * 100) );
};
function draw()
{
if(step == trainingLimit) {
AB.abortRun = true;
}else{
AB.msg ( "Line: y = " + a.toFixed(2) + " x + " + b.toFixed(2) +
"<br> Step: " + step );
step++;
background('black');
// Draw the line
strokeWeight(3);
stroke('darkblue');
let x1 = map(xmin, xmin, xmax, 0, width);
let y1 = map(f(xmin), ymin, ymax, height, 0);
let x2 = map(xmax, xmin, xmax, 0, width);
let y2 = map(f(xmax), ymin, ymax, height, 0);
line(x1, y1, x2, y2);
// Draw the line based on the current weights
// Formula is weights[0]*x + weights[1]*y + weights[2] = 0
stroke('white');
let weights = ptron.getWeights();
//console.log("weights " + weights); // KE
x1 = xmin;
y1 = (-weights[2] - weights[0] * x1) / weights[1];
x2 = xmax;
y2 = (-weights[2] - weights[0] * x2) / weights[1];
x1 = map(x1, xmin, xmax, 0, width);
y1 = map(y1, ymin, ymax, height, 0);
x2 = map(x2, xmin, xmax, 0, width);
y2 = map(y2, ymin, ymax, height, 0);
line(x1, y1, x2, y2);
// Train the Perceptron with one "training" point at a time
AB.msg ( "<br> Training on single point: " + (count + 1), 2 );
ptron.train(training[count].input, training[count].output);
count = (count + 1) % training.length;
// Draw all the points
AB.msg ( "<br> Drawing points 0 to " + (count - 0), 3 ); //KE this was 1
for (let i = 0; i < count; i++)
{
strokeWeight(1);
let guess = ptron.feedforward(training[i].input);
let x = map(training[i].input[0], xmin, xmax, 0, width);
let y = map(training[i].input[1], ymin, ymax, height, 0);
// original version: based on what the Perceptron would "guess" - shows how its guess changes over time
// if (guess > 0)
// this version: correct answer
if ( getClassification ( training[i].input[0], training[i].input[1] ) == 1 )
{
stroke('lightgreen');
fill('lightgreen');
}
else
{
stroke('lightpink');
fill('lightpink');
}
ellipse(x, y, 12, 12);
}
}
}