Commit a28ad255 authored by Javi's avatar Javi
Browse files

Adding Simple Perceptron Processing initial code

parent 697a4004
Pipeline #237 canceled with stages
File added
//The class DatasetPoint extends its functionality
class DatasetPoint extends Point {
float bias = 1.0; //The bias is always equal to 1 because of the equation of the line
int label; //The label
DatasetPoint(float x_axis_width, float y_axis_height) {
x = random(-1, 1);
y = random(-1, 1);
this.x_axis_width = x_axis_width;
this.y_axis_height = y_axis_height;
calculateMargins();
float exact_line_y = exact_function(x);
if (y > exact_line_y) {
label = 1;
} else {
label = -1;
}
}
}
\ No newline at end of file
class Perceptron {
float[] weights = new float[3];
float[] prev_weights = new float[3];
// float lr = 0.001;
float lr = 0.04;
float error;
Perceptron() {
// Start with random weights
for (int i = 0; i < weights.length; i++) {
// weights[i] = random(-1,1);
weights[i] = random(-1.0, 1.0);
}
}
//The activation function
int sign(float n) {
if (n > 0) {
return 1;
} else {
return -1;
}
}
//inputs[] -> x,y of the point and bias (always 1)
//weights[] -> initially random(-1,1)
int feedforward(float[] inputs, boolean printText) {
float sum = 0;
for ( int i = 0; i< weights.length; i++) {
sum+=inputs[i]*weights[i];
}
int output =sign(sum);
if (printText == true) {
stroke(0);
fill(0);
text("Training epoch "+epoch+" training point "+(currentDataTraining+1)+"/"+trainingDataset.length+" -> Feedforward", text_x_separation, current_text_y);
current_text_y+=text_y_separation;
text("x = "+inputs[0]+" y = "+inputs[1]+" bias = "+inputs[2], text_x_separation, current_text_y);
current_text_y+=text_y_separation;
text("w0 = "+weights[0]+" w1 = "+weights[1]+" w2 = "+ weights[2], text_x_separation, current_text_y);
current_text_y+=text_y_separation;
text("sum = (x * w0) -> ( "+inputs[0]*weights[0]+" ) + (y * w1) -> "+inputs[1]*weights[1]+" + (bias*w2) -> "+inputs[2]*weights[2]+" = "+sum, text_x_separation, current_text_y);
current_text_y+=text_y_separation;
text("sign(sum) = if (sum > 0) = 1 else = -1 -> "+output, text_x_separation, current_text_y);
current_text_y+=text_y_separation;
}
return output;
}
//Formula of the line
//weights[0]*x + weights[1]*y + weights[2]*bias (1) = 0
//Only used for the points of the line. It's not used during the training
float caulculateY(float x) {
//float m = weights[0] / weights[1];
//float b = weights[2];
//return m*x + b;
float w0 = weights[0];
float w1 = weights[1];
float w2 = weights[2];
float bias = 1;
//w0*x + w1*y + w2*bias = 0
//w1*y = -w0*x - w2*bias
//y = -w0/w1*x - w2/w1*bias
float calculated_y = - (w0/w1)*x -(w2/w1)*bias ;
return calculated_y;
}
//1. For every input, multiply that input by its weight
//2. Sum all of the weighted inputs
//3. Compute the output of the perceptron based on that sum passed through an
// activation function (the sign of the sum)
// New weight = weight + ( Error * Input * LearningRate)
boolean train(float[] inputs, int target) {
// Guess the result
int guess = feedforward(inputs, true);
stroke(0);
fill(0);
if (verbose) {
current_text_y+=text_y_separation;
text("Calculating error", text_x_separation, current_text_y);
current_text_y+=text_y_separation;
text("guessed output = "+guess+" target output = "+target, text_x_separation, current_text_y);
current_text_y+=text_y_separation;
}
// 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
error = target - guess;
prev_weights[0] = weights[0];
prev_weights[1] = weights[1];
prev_weights[2] = weights[2];
// Adjust weights based on weightChange * input
for (int i = 0; i < weights.length; i++) {
weights[i] += error * inputs[i] * lr;
}
text("error = target - guess = " + error, text_x_separation, current_text_y);
current_text_y+=text_y_separation;
if ( error != 0) {
if (verbose) {
printWeightsAdjustment(inputs);
}
return true;
} else {
current_text_y+=text_y_separation;
text("Weights don't change!! Calculated points Left & Right stays the same.", text_x_separation, current_text_y);
current_text_y+=text_y_separation;
return false;
}
}
void printWeightsAdjustment(float[] inputs) {
text("Increment w0 = (error*x*lr) -> ("+error+"*"+inputs[0]+"*"+lr+") = "+ (error*inputs[0]*lr), text_x_separation, current_text_y);
current_text_y+=text_y_separation;
text("Increment w1 = (error*y*lr) -> ("+error+"*"+inputs[1]+"*"+lr+") = "+ (error*inputs[1]*lr), text_x_separation, current_text_y);
current_text_y+=text_y_separation;
text("Increment w2 = (error*bias*lr) -> ("+error+"*"+inputs[2]+"*"+lr+") = "+ (error*inputs[2]*lr), text_x_separation, current_text_y);
current_text_y+=text_y_separation;
current_text_y+=text_y_separation;
text("Adjusting weights", text_x_separation, current_text_y);
current_text_y+=text_y_separation;
text("prev w0 = "+prev_weights[0]+" new w0 = "+weights[0], text_x_separation, current_text_y);
current_text_y+=text_y_separation;
text("prev w1 = "+prev_weights[1]+" new w1 = "+weights[1], text_x_separation, current_text_y);
current_text_y+=text_y_separation;
text("prev w2 = "+prev_weights[2]+" new w2 = "+weights[2], text_x_separation, current_text_y);
current_text_y+=text_y_separation;
}
}
\ No newline at end of file
//Simple class Point
class Point {
float x;
float y;
float x_axis_width;
float y_axis_height;
float margin_x;
float margin_y;
boolean calculatedLine = false;
void show() {
if (calculatedLine == true) {
fill(255, 0, 0);
stroke(255, 0, 0);
ellipse(pixelX(), pixelY(), 8, 8);
text("("+x+","+y+")", pixelX()+10, pixelY()+10);
} else {
fill(0, 0);
stroke(0);
ellipse(pixelX(), pixelY(), 8, 8);
text("("+x+","+y+")", pixelX()+10, pixelY()+10);
}
}
Point() {
}
//Create points for representing a line -> no need of label
Point(float x_axis_width, float y_axis_height, float px, float py, boolean calculatedLine) {
x = px;
y = py;
this.x_axis_width = x_axis_width;
this.y_axis_height = y_axis_height;
this.calculatedLine=calculatedLine;
calculateMargins();
}
void calculateMargins() {
margin_x = (width-x_axis_width)/2.0;
margin_y = (height-y_axis_height)/2.0;
}
//It maps the coordinates from -1,1 into screen pixel coordinates
float pixelX() {
// float result = map(x, -1, 1, margin_x, width-margin_x);
float result = map(x, -1, 1, margin_x, width-margin_x);
return result;
}
float pixelY() {
float result = map(y, -1, 1, margin_y+y_axis_height, margin_y);
return result;
}
}
\ No newline at end of file
//The Perceptron Algorithm:
//1. Provide the perceptron with inputs for which there is a known answer
//2. Ask the perceptron to guess an answer
//3. Compute the error. (Did it get the answer right or wrong?)
//4. Adjust all the weights according with the error
//5. Return to Step 1 (click mousePressed again) and repeat!
Perceptron perceptron;
final int number_training_points = 10;
//Datasets
TrainingDatasetPoint[] trainingDataset = new TrainingDatasetPoint[number_training_points];
ArrayList<TestingDatasetPoint> testingDataset = new ArrayList<TestingDatasetPoint>();
//Index current data item
int currentDataTraining = -1;
//Size of the axis
float x_axis_width = 400;
float y_axis_height = 400;
PFont mathsFont;
final float text_x_separation = 10.0;
final float text_y_separation = 20.0;
float current_text_y = text_y_separation;
//Exact line that classifies the 2 groups
Point exact_line_p_left;
Point exact_line_p_right;
//We draw the current line based on the adjusted weights
// Formula is weights[0]*x + weights[1]*y + weights[2]*bias (1) = 0
Point calculated_line_p_left;
Point calculated_line_p_right;
float prev_calculated_line_p_left_y = 0.0;
float prev_calculated_line_p_right_y = 0.0;
float margin_x;
float margin_y;
boolean drawNextFrame = true;
boolean verbose = true;
boolean wasThereChange = false;
final int TRAINING = 1;
final int TESTING = 2;
int newTestingDataPoint = -1;
int currentState = TRAINING;
int epoch = 1;
float current_error_testing_set = 0.0;
void setup() {
size(1920, 1080);
perceptron = new Perceptron();
//Generate training set
for (int i = 0; i < trainingDataset.length; i++) {
trainingDataset[i] = new TrainingDatasetPoint(x_axis_width, y_axis_height);
}
mathsFont = createFont("SourceCodePro-Regular.ttf", 12);
textFont(mathsFont);
margin_x = (width - x_axis_width) / 2.0;
margin_y = (height - y_axis_height) / 2.0;
frameRate(30);
//we uses x=-1 and x=1 as the two points for drawing the line
//we calculate the ys by calling the function f(x)
exact_line_p_left = new Point(x_axis_width, y_axis_height, -1, exact_function(-1), false);
exact_line_p_right = new Point(x_axis_width, y_axis_height, 1, exact_function(1), false);
}
void draw() {
if (drawNextFrame == true) {
current_text_y = text_y_separation;
background(255);
noStroke();
fill(255, 237, 247);
rect(margin_x, margin_y, x_axis_width, y_axis_height);
noFill();
stroke(0);
//we are going to draw the line that we use to classify the 2 groups
line(exact_line_p_left.pixelX(), exact_line_p_left.pixelY(), exact_line_p_right.pixelX(), exact_line_p_right.pixelY());
// exact_line_p_left.show(false, true, false);
// exact_line_p_right.show(false, true, false);
exact_line_p_left.show();
exact_line_p_right.show();
//we are going to draw the current line according with the weights (initially random)
//we calculate the ys by calling the function f(x)
if (currentState == TRAINING) {
currentDataTraining++;
if ( currentDataTraining == trainingDataset.length) {
currentDataTraining = 0;
epoch++;
}
} else if (currentState == TESTING) {
//Visualize testing data
for (int i = 0; i < testingDataset.size(); i++) {
if (newTestingDataPoint == i) {
// testingDataset.get(i).show(true, false, false);
testingDataset.get(i).show(true);
} else {
// testingDataset.get(i).show(false, false, false);
testingDataset.get(i).show(false);
}
}
}
//Visualize training data
for (int i = 0; i < trainingDataset.length; i++) {
if (currentDataTraining == i && currentState == TRAINING) {
trainingDataset[i].show(true);
} else {
trainingDataset[i].show(false);
}
}
if (currentState == TRAINING) {
//Training
float[] inputs = {trainingDataset[currentDataTraining].x, trainingDataset[currentDataTraining].y, trainingDataset[currentDataTraining].bias};
wasThereChange = perceptron.train( inputs, trainingDataset[currentDataTraining].label);
}
stroke(255, 0, 0);
//We try to find the optimal weights for a line
//We draw the current line based on the adjusted weights. The line should ended up dividing the two groups (A,B) we are classifying
// Formula is weights[0]*x + weights[1]*y + weights[2]*bias (1) = 0
calculated_line_p_left = new Point( x_axis_width, y_axis_height, -1, perceptron.caulculateY(-1), true);
calculated_line_p_right = new Point( x_axis_width, y_axis_height, 1, perceptron.caulculateY(1), true);
line(calculated_line_p_left.pixelX(), calculated_line_p_left.pixelY(), calculated_line_p_right.pixelX(), calculated_line_p_right.pixelY());
// calculated_line_p_left.show(false, true, true);
// calculated_line_p_right.show(false, true, true);
calculated_line_p_left.show();
calculated_line_p_right.show();
drawNextFrame = false;
stroke(0);
fill(0);
if (currentState == TRAINING) {
if ( wasThereChange == true) {
if (verbose) {
printTrainingProcess();
}
prev_calculated_line_p_left_y = calculated_line_p_left.y;
prev_calculated_line_p_right_y = calculated_line_p_right.y;
}
} else if (currentState == TESTING) {
if (verbose) {
printTestingProcess();
}
}
}
}
void printTestingProcess() {
current_text_y+=text_y_separation;
text("Training Process Finished!", text_x_separation, current_text_y);
current_text_y+=text_y_separation;
text("Size of Training Dataset = "+trainingDataset.length, text_x_separation, current_text_y);
current_text_y+=text_y_separation;
text("Number of epochs for the training process = " + epoch, text_x_separation, current_text_y);
current_text_y+=text_y_separation;
current_text_y+=text_y_separation;
text("Testing Process", text_x_separation, current_text_y);
current_text_y+=text_y_separation;
text("Size of Testing Dataset = "+testingDataset.size(), text_x_separation, current_text_y);
current_text_y+=text_y_separation;
text("Classifiying Success Rate = "+current_error_testing_set+"%", text_x_separation, current_text_y);
current_text_y+=text_y_separation;
}
void addTestingDatasetPoint() {
testingDataset.add(new TestingDatasetPoint(x_axis_width, y_axis_height));
++newTestingDataPoint;
}
void calculateError() {
float countingHits = 0;
//Visualize testing data
for (int i = 0; i < testingDataset.size(); i++) {
if (testingDataset.get(i).wasProperlyClassified() == true) {
countingHits++;
}
}
current_error_testing_set = (countingHits / float(testingDataset.size()))*100.0;
}
void mouseClicked() {
if (currentState != TESTING) {
currentState = TESTING;
} else {
addTestingDatasetPoint();
calculateError();
}
drawNextFrame = true;
}
void keyPressed() {
drawNextFrame = true;
}
//w0 = x, w1 = y, w2 = b
//we arbitrarily decide the initial line that we would use to separate the 2 groups that
//we would like to classify
float exact_function(float x) {
float m = 0.3;
float b = 0.2;
//y = mx + b
float y = m*x + b;
return y;
}
float current_function_based_on_weights(float x) {
/* float m = 0.3;
float b = 0.2;
//y = mx + b
float y = m*x + b;
return y;*/
return perceptron.caulculateY(x);
}
void printTrainingProcess() {
String tempText ="";
current_text_y+=text_y_separation;
//calculated_y = - (w0/w1)*x -(w2/w1)*bias ;
text("Calculating Left Point x = "+calculated_line_p_left.x, text_x_separation, current_text_y);
current_text_y+=text_y_separation;
// text("calculated_y = -(w0/w1)*x -(w2/w1)*bias -> ( - "+perceptron.weights[0]+"/"+perceptron.weights[1]+ ") * "+calculated_line_p_left.x + " - ( "+perceptron.weights[2]+"/"+perceptron.weights[1]+ ") * "+calculated_line_p_left.bias, text_x_separation, current_text_y); current_text_y+=text_y_separation;
text("calculated_y = -(w0/w1)*x -(w2/w1)*bias", text_x_separation, current_text_y);
current_text_y+=text_y_separation;
text("calculated_y = -("+perceptron.weights[0]+"/"+perceptron.weights[1]+ ") * "+calculated_line_p_left.x + " - ( "+perceptron.weights[2]+"/"+perceptron.weights[1]+ ") * "+1, text_x_separation, current_text_y);
current_text_y+=text_y_separation;
if ( calculated_line_p_left.y > prev_calculated_line_p_left_y) {
tempText=" Point Left moves UP! "+(calculated_line_p_left.y - prev_calculated_line_p_left_y);
} else if (calculated_line_p_left.y < prev_calculated_line_p_left_y) {
tempText=" Point Left moves DOWN! "+(calculated_line_p_left.y - prev_calculated_line_p_left_y);
} else {
tempText=" Point Left Freezes!";
}
text("prev_calculated_y = "+prev_calculated_line_p_left_y+" calculated_y = "+calculated_line_p_left.y+tempText, text_x_separation, current_text_y);
current_text_y+=text_y_separation;
current_text_y+=text_y_separation;
text("Calculating Right Point x = "+calculated_line_p_right.x, text_x_separation, current_text_y);
current_text_y+=text_y_separation;
// text("calculated_y = -(w0/w1)*x -(w2/w1)*bias -> ( - "+perceptron.weights[0]+"/"+perceptron.weights[1]+ ") * "+calculated_line_p_right.x + " - ( "+perceptron.weights[2]+"/"+perceptron.weights[1]+ ") * "+calculated_line_p_right.bias, text_x_separation, current_text_y); current_text_y+=text_y_separation;
text("calculated_y = -(w0/w1)*x -(w2/w1)*bias", text_x_separation, current_text_y);
current_text_y+=text_y_separation;
text("calculated_y = -("+perceptron.weights[0]+"/"+perceptron.weights[1]+ ") * "+calculated_line_p_right.x + " - ( "+perceptron.weights[2]+"/"+perceptron.weights[1]+ ") * "+1, text_x_separation, current_text_y);
current_text_y+=text_y_separation;
if ( calculated_line_p_right.y > prev_calculated_line_p_right_y) {
tempText=" Point Right moves UP! "+ (calculated_line_p_right.y-prev_calculated_line_p_right_y);
} else if (calculated_line_p_right.y < prev_calculated_line_p_right_y) {
tempText=" Point Right moves DOWN! "+ (calculated_line_p_right.y-prev_calculated_line_p_right_y);
} else {
tempText=" Point Right Freezes!";
}
text("prev_calculated_y = "+prev_calculated_line_p_right_y+" calculated_y = "+calculated_line_p_right.y+tempText, text_x_separation, current_text_y);
current_text_y+=text_y_separation;
}
\ No newline at end of file
//The class TestingDatasetPoint extends the functionality of
//by adding methods to detect whether the point was properly classified
class TestingDatasetPoint extends DatasetPoint {
int predicted_label;
boolean successful_prediction;
float cross_size = 4;
TestingDatasetPoint(float x_axis_width, float y_axis_height) {
super(x_axis_width, y_axis_height);
}
boolean wasProperlyClassified() {
float calculated_line_y = current_function_based_on_weights(x);
if (y > calculated_line_y) {
predicted_label = 1;
} else {
predicted_label = -1;
}
if ( predicted_label == label) {
successful_prediction = true;
} else {
successful_prediction = false;
}