/* A list of points that we will use to train the perceptron. */
let training = new Array(500);
/* The constant to use when training the perceptron. */
const LearningConstant = 0.01;
/* Whether or not the training occurred without error. */
var errorFree = true;
/* The minimum and maximum x and y coordinates within which our points should reside. */
let xmin = -1;
let ymin = -1;
let xmax = 1;
let ymax = 1;
/* The fixed width within which our header should stay. */
AB.headerWidth(400);
/* Draws a line using "y = ax + b". */
const a = AB.randomFloatAtoB(0.1, 0.7);
const b = AB.randomFloatAtoB(0.1, 0.8);
/* Calculates the value for a given point. */
function f(x) {
return (a * x + b);
}
/* Classifies whether or not we are above the threshold line. */
function getClassification(x, y) {
if (y < f(x)) return (-1);
else return (1);
}
/* A perceptron that is created with n weights and a learning constant. */
class Perceptron {
constructor(n, c) {
/* The collection of weights for each of our inputs. */
this.weights = new Array(n);
/* Initialise our weights with random values. */
for (let i = 0; i < this.weights.length; i++) {
this.weights[i] = random(-1, 1);
}
/* Our learning constant. */
this.c = c;
}
/* Train the perceptron, and adjust the weights based on our desired answer. */
train(inputs, desired) {
/* Guess the result. */
let guess = this.feedforward(inputs);
/* Determine the amount by which the weight should be changed, based on the error.
- Error = desired output - guessed output.
- Note: this can only be 0, -2, or 2.
- Multiply it by learning constant. */
let error = desired - guess;
if (error) {
errorFree = false;
console.log("Error occurred.");
}
/* Adjust the weights based on weightChange * input. */
for (let i = 0; i < this.weights.length; i++) {
var previousWeight = this.weights[i];
this.weights[i] += this.c * error * inputs[i];
if (previousWeight !== this.weights[i]) {
console.log("Weight adjusted for input: " + inputs[i]);
} else {
console.log("Weight preserved for input: " + inputs[i]);
}
}
}
/* Guess -1 or 1 based on the input values. */
feedforward(inputs) {
/* Sum up all of the values. */
let sum = 0;
for (let i = 0; i < this.weights.length; i++) {
sum += inputs[i] * this.weights[i];
}
// The result is either -1 or 1.
return this.activate(sum);
}
/* Activate the neuron in question, if necessary. */
activate(sum) {
if (sum > 0) return 1;
else return -1;
}
/* Return the weights. */
getWeights() {
return this.weights;
}
}
/* A Perceptron object. */
let ptron;
/* We will train the perceptron with one point at a time. */
let count = 0;
/* Carry out any initial setup. */
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
};
}
}
/* To track which step of the training process that we are currently at. */
var step = 1;
/* Draw the training process. */
function draw() {
frameRate(60);
/* Reset our error-free flag. */
if (count === 0) {
errorFree = true;
}
AB.msg("Line: y = " + a.toFixed(2) + " x + " + b.toFixed(2) + "<br> Step: " + step);
step++;
/* Set the background to be black. */
background('black');
/* Draw the line based on the current weights. */
/* The formula is weights[0] * x + weights[1] * y + weights[2] = 0. */
stroke('white');
let weights = ptron.getWeights();
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, 2);
ptron.train(training[count].input, training[count].output);
count = (count + 1) % training.length;
/* Draw all of the points. */
AB.msg("<br> Drawing points 0 to " + (count - 1), 3);
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);
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);
}
if (count === training.length - 1 && errorFree) {
console.log("Completed a bout of training, error-free.");
noLoop();
}
}