One of my school assignments was to write a simple ANSI C Perceptron algorithm that would be able to separate points on a two dimensional plane into two sets (-1 and 1). Fortunately, while reading a Wikipedia article about Perceptron, I have found a great external link at the bottom of it: C# implementation of a Perceptron. This helped me a lot to understand how the Perceptron works and how to implement it programatically.
What I have done is rewrite the code snippet from John Wakefield in the C language. Instead of data type double I used two integer arrays (one denotes x and the other one y coordinates of points from a training set). Plus I have added a simple code do draw a nice graph of the training set and its linear separation. I used GD to draw the image. I would also like to thank Amro for helping me finish this algorithm.
Here’s the entire code:
#include <stdio.h> #include <stdlib.h> #include <gd.h> #include <math.h> #define NUMEL 208 #define LEARNING_RATE 0.1 #define MAX_ITERATION 100 float randomFloat() { float r = (float)rand() / (float)RAND_MAX; return r; } int calculateOutput(float weights[], float x, float y) { float sum = x * weights[0] + y * weights[1] + weights[2]; return (sum >= 0) ? 1 : -1; } int main(int argc, char *argv[]) { srand(time(NULL)); float x[NUMEL], y[NUMEL], weights[3], localError, globalError, a, b; int outputs[NUMEL], patternCount, i, p, iteration, output; FILE *fp; if ((fp = fopen("training-set.txt", "r")) == NULL) { printf("Cannot open file.\n"); exit(1); } i = 0; while (fscanf(fp, "%f %f %d", &x[i], &y[i], &outputs[i]) != EOF) { if (outputs[i] == 0) { outputs[i] = -1; } printf("%.4f %.4f %d\n", x[i], y[i], outputs[i]); i++; } patternCount = i; system("PAUSE"); weights[0] = randomFloat(); weights[1] = randomFloat(); weights[2] = randomFloat(); iteration = 0; do { iteration++; globalError = 0; for (p = 0; p < patternCount; p++) { output = calculateOutput(weights, x[p], y[p]); localError = outputs[p] - output; // Update weights. weights[0] += LEARNING_RATE * localError * x[p]; weights[1] += LEARNING_RATE * localError * y[p]; weights[2] += LEARNING_RATE * localError; globalError += (localError * localError); } /* Root Mean Squared Error */ printf("Iteration %d : RMSE = %.4f\n", iteration, sqrt(globalError / patternCount)); } while (globalError != 0 && iteration <= MAX_ITERATION); // Display network generalisation. printf("X Y Output\n"); float j, k; for (j = -1; j <= 1; j += .5) { for (j = -1; j <= 1; j += .5) { // Calculate output. int output = calculateOutput(weights, j, k); printf("%.4f %.4f %s\n", j, k, (output == 1) ? "Blue" : "Red"); } } // Display modified weights. printf("Modified weights: %.2f %.2f\n", weights[0], weights[1]); // Create image representation. gdImagePtr im; im = gdImageCreateTrueColor(600, 600); if (im != 0) { // Allocate colors. int white = gdImageColorAllocate(im, 255, 255, 255); int lightGrey = gdImageColorAllocate(im, 220, 220, 220); int black = gdImageColorAllocate(im, 0, 0, 0); int blue = gdImageColorAllocate(im, 0, 0, 255); int red = gdImageColorAllocate(im, 255, 0, 0); int green = gdImageColorAllocate(im, 0, 200, 50); // White flood fill. gdImageFill(im, 0, 0, lightGrey); // Points. float cx, cy; for (i = 0; i < patternCount; i++) { // Calculate output. int output = calculateOutput(weights, x[i], y[i]); cx = floor(300 + 30*x[i] + 0.5); cy = floor(300 - 30*y[i] + 0.5); int color = (output == 1) ? blue : red; gdImageFilledEllipse(im, (int)cx, (int)cy, 5, 5, color); } // Linear separation a = -weights[0] / weights[1]; b = -weights[2] / weights[1]; printf("Decision boundary (line) equation: y = %.4fx + %.4f\n", a, b); // x = -10 => y = -10a+b // x = 10 => y = 10*a + b gdImageLine(im, 0, (int)(300 + 300*a - 30*b), 600, (300 - 300*a - 30*b), green); // X coordinate. gdImageLine(im, 0, 300, 600, 300, black); // Y coordinate. gdImageLine(im, 300, 0, 300, 600, black); fp = fopen("training.png", "wb"); if (fp != 0) { gdImagePng(im, fp); fclose(fp); } } gdImageDestroy(im); system("PAUSE"); return 0; }
I used the Dev C++ to write the program. Here is how to use GD in Dev C++.
#include <stdio.h> #include <stdlib.h> #include <gd.h> #include <math.h> float randomFloat() { srand(time(NULL)); float r = (float)rand() / (float)RAND_MAX; return r; } int calculateOutput(float weights[], float x, float y) { float sum = x * weights[0] + y * weights[1]; return (sum >= 0) ? 1 : -1; } int main(int argc, char *argv[]) { // X coordinates of the training set. float x[] = { -3.2, 1.1, 2.7, -1 }; // Y coordinates of the training set. float y[] = { 1.5, 3.3, 5.12, 2.1 }; // The training set outputs. int outputs[] = { 1, -1, -1, 1 }; int patternCount = sizeof(x) / sizeof(int); float weights[2]; weights[0] = randomFloat(); weights[1] = randomFloat(); float learningRate = 0.01; int iteration = 0; int i, p; float globalError; do { globalError = 0; int p = 0; // iterator for (p = 0; p < patternCount; p++) { // Calculate output. int output = calculateOutput(weights, x[p], y[p]); // Calculate error. float localError = outputs[p] - output; if (localError != 0) { // Update weights. for (i = 0; i < 2; i++) { float add = learningRate * localError; if (i == 0) { add *= x[p]; } else if (i == 1) { add *= y[p]; } weights[i] += add; } } // Convert error to absolute value. globalError += fabs(localError); printf("Iteration %d Error %5.2f\n", iteration, globalError); iteration++; } } while (globalError != 0); // Display network generalisation. printf("X Y Output\n"); float j, k; for (j = -1; j <= 1; j += .5) { for (j = -1; j <= 1; j += .5) { // Calculate output. int output = calculateOutput(weights, j, k); printf("%5.2f %5.2f %s\n", j, k, (output == 1) ? "Blue" : "Red"); } } // Display modified weights. printf("Modified weights: %5.2f %5.2f\n", weights[0], weights[1]); // Create image representation. gdImagePtr im; FILE *fp; im = gdImageCreateTrueColor(600, 600); if (im != 0) { // So the points are further from each other // and the graph is more readable. int multiplier = 50; // Allocate colors. int white = gdImageColorAllocate(im, 255, 255, 255); int lightGrey = gdImageColorAllocate(im, 220, 220, 220); int black = gdImageColorAllocate(im, 0, 0, 0); int blue = gdImageColorAllocate(im, 0, 0, 255); int red = gdImageColorAllocate(im, 255, 0, 0); int green = gdImageColorAllocate(im, 0, 200, 50); // White flood fill. gdImageFill(im, 0, 0, lightGrey); // Points. float cx, cy; for (i = 0; i < patternCount; i++) { // Calculate output. int output = calculateOutput(weights, x[i], y[i]); cx = floor(300 + multiplier*x[i] + 0.5); cy = floor(300 - multiplier*y[i] + 0.5); int color = (output == 1) ? blue : red; gdImageFilledEllipse(im, (int)cx, (int)cy, 10, 10, color); } // Linear separation. float a = 0, b = 0; for (i = 0; i < patternCount; i++) { int fx = (a > 0 && b > 0) ? 1 : 0; a += learningRate * (y[i] - fx) * x[i]; b += learningRate * (y[i] - fx); } printf("y = %5.2fx + %5.2f\n", a, b); // x = -300 => y = -300*a + b // x = 300 => y = 300*a + b gdImageLine(im, -2, (int)(300 + multiplier*300*a + b), 598, (int)(300 - multiplier*300*a + b), green); gdImageLine(im, -1, (int)(300 + multiplier*300*a + b), 599, 300 - (int)(multiplier*300*a + b), green); gdImageLine(im, 0, (int)(300 + multiplier*300*a + b), 600, 300 - (int)(multiplier*300*a + b), green); gdImageLine(im, 1, (int)(300 + multiplier*300*a + b), 601, 300 - (int)(multiplier*300*a + b), green); gdImageLine(im, 2, (int)(300 + multiplier*300*a + b), 602, 300 - (int)(multiplier*300*a + b), green); // X coordinate. gdImageLine(im, 0, 300, 600, 300, black); // Y coordinate. gdImageLine(im, 300, 0, 300, 600, black); fp = fopen("training.png", "wb"); if (fp != 0) { gdImagePng(im, fp); fclose(fp); } } gdImageDestroy(im); system("PAUSE"); return 0; }