/** COMBINES perceptron, adaline (LMS and alpha-LMS), and pocket programs. (Sep. 18, 1995) DATA FILE FORMAT for training data: first line: learning-rule-char(l/a/p/k) number-of-dimensions, normalize-flag, max_iters, weight-dump-frequency eta is-classification-problem mse-desired remaining lines: x1 x2 ... xn desired-output E.g., a 3 1 100 10 0.01 1 0.01 (otherwise 0 all by itself on the first line) 2 1 1 0 1 2 2 1 The program can do two things: Learn and Test. 1. Learn mode -- the network is trained in this mode. an input pattern whose class membership is known, is presented to the network and compared with the response of the perceptron. Data is automatically normalized Weights are adjusted repeatedly, until either the node classifies all training patterns correctly, or the MSE level requested by the user is reached, or the maximum number of presentations is exceeded. After learning, all the information relevant to the structure of the net, including weights and thresholds, are printed on the standard output (stdout). 2. Test mode -- present patterns and the network classifies them. No learning goes on in this mode. ASSUMPTIONS: 1. Only one "computing" node. 2. MSE is monitored, but tolerable no. of misclassifications is not an option that can terminate training. COMPILING: cc -o outputfilename -g programfilename -lm The "-g" option has been left in so that the dbx debugger may be used in the event of an error; "-O" will optimize the code. **/ #include #include #include #include #define NMXUNIT 50 /* max no. of units in a layer*/ #define NMXINP 1000 /* max no. of input samples */ #define NMXIATTR 20 /* max no. of input features */ #define BUFSIZE 80 /* useful for char arrays and such */ #define ERROR 0 /* for abnormal exits */ #define OK 1 /* for normal exits */ #define CLASS_1 1 #define CLASS_2 0 #define TEST 1 /* used to decide what data to print */ #define LEARN 0 #define RUN_MAX 1000 /* max run length */ /* Data base: declarations of variables -- global declarations; bad software practice, but convenient. Also, whenever use is made of these global variables in a routine, extern declarations have been explicitly made. This should expiate the sin somewhat. */ int current_run_length=0; int best_run_length = 0; int best_mismatch = 999999; float best_found [NMXIATTR] = 0.0; int random_preferred = 0; /* program to be modified allowing user to set this <------- */ float multiplier = 1.0; /* for input normalization */ int is_classif = 0; float min_err = 0.0001; /* specified by user */ int results_unsatisfactory = 1; float bias = -1.0; /* the offset */ int dump_freq = 1; int norm_flag = 0; char learn_file[BUFSIZE], test_file[BUFSIZE]; float input [NMXINP] [NMXIATTR + 1] = 0.0, weight[NMXUNIT], out[NMXINP] = 0.0 ; float original_input [NMXINP] [NMXIATTR + 1] = 0.0; /* there is some memory wastage here, but it's not worth fixing */ float input_length [NMXINP] = 1.0; char learning_char; int learning_rule = 0; /* 0 for LMS, 1 for alpha-LMS, 2 for perceptron, 3 for pocket */ float eta = 1.0; float max_inp=1.0; float min_inp=0.0; int nunit = 0, ndata = 0, itermax, errmax; /* no. of units, max. number of data samples max. number of presentations, max error allowable */ main(argc,argv) /* Enter the dragon */ int argc; char *argv[]; { /* main routine begins */ extern int optind; extern char *optarg; int lflag = 0, tflag = 0, fflag = 0; int random(), user_interface(); void read_data(), learn(), test(), initwt(), print_initial(); FILE *fp; /* initialize */ strcpy(learn_file, ""); strcpy(test_file, ""); if(user_interface(argc, argv, learn_file, test_file, &lflag, &tflag)) { if (lflag) /* if task is to learn */ { read_data(learn_file, LEARN); /*initialize the weights of perceptron network */ initwt(); print_initial("DATA FILE", LEARN); learn(); } /* Or, only/also test the perceptron */ if (tflag) { read_data(test_file, TEST); print_initial("DATA FILE", TEST); test(); } exit(OK); /* polite exit */ } } /* main routine ends */ /* user_interface() -- Fancy stuff. Tries to parse the command line and processes it. If required data isn't specified, the user is prodded. */ int user_interface(argc, argv, learn_file, test_file, lptr, tptr) int argc, *lptr, *tptr; char *argv[]; char *learn_file, *test_file; { char ch, select[2]; int fflag = 0; /* Command Line interface -- parse and process */ *lptr = 0; /* flags to check if options have been specified */ *tptr = 0; while((ch = getopt(argc, argv, "l:t:h")) != EOF) { switch(ch) { case 'l' : *lptr = 1; strcpy(learn_file, optarg); break; case 't' : *tptr = 1; strcpy(test_file, optarg); break; case 'h' : system("more per.help"); return(ERROR); default : printf("Usage: %s [-l filename] [-t filename] [-h] \n",argv[0]); return(ERROR); } } /* if user has not yet specified whether session involves learning or testing/simulating */ if(!(*lptr) && !(*tptr) ){ /* prodding begins */ printf("\n** Select L(learning) or T(output generation) **\n"); do{ scanf("%c", select); switch(select[0]) { case 't': case 'T': *tptr = 1; printf("Enter the name of test file ->"); scanf("%s", test_file); break; case 'l': case 'L': *lptr = 1; printf("Enter the name of data file ->"); scanf("%s", learn_file); break; default: printf("\nselect learning or output generation "); break; } }while ((select[0]!='o') && (select[0]!='O') && (select[0]!='l') && (select[0]!='L')); } /* prodding ends */ return(OK); /* polite exit */ } /* user_interface routine ends */ /* ********************************************************************** read_data() -- Reads data from input file (if in learn mode) or from test file (if in test mode). In test mode, read_data picks up the network configuration and weights from appropriate file. ********************************************************************** */ void read_data(filename, mode) char *filename; int mode; { /* read_data begins */ int i, m, data_not_over = 1; FILE *fp_in, *fp_net; extern int nunit, ndata, itermax; extern float input [NMXINP] [NMXIATTR + 1], bias; if ((fp_in = fopen(filename,"r")) == NULL) { perror("Cannot open input file"); exit(ERROR); } if(mode == TEST) { if ((fp_net = fopen("net_data", "r")) == NULL) { perror("Cannot open network data file"); exit(ERROR); } /* read network and test/simulation parameters */ fscanf(fp_net,"%d %f",&nunit, &bias); for(i = 0; i <= nunit; i++) fscanf(fp_net, "%f", weight + i); fclose(fp_net); } else{ /* learn mode */ /* read network and program parameters */ fscanf(fp_in, "%c", &learning_char); if (learning_char != '0') fscanf(fp_in,"%d %d %d %d %f %d %f", &nunit, &norm_flag, &itermax, &dump_freq, &eta, &is_classif, &min_err); else /* assume interactive input */ { printf("\nEnter choice of learning rule (l for LMS, a for alpha-LMS, p for perceptron, k for pocket)"); scanf("%s", &learning_char ); printf("\nEnter no. of input dimensions, excluding output (e.g., 2)"); scanf("%d", &nunit); printf("\nShould we normalize input data? (Enter 0 or 1)"); scanf("%d", &norm_flag); printf("\nEnter max. no. of presentations to be executed (e.g., 100)"); scanf("%d", &itermax); if (itermax < 1) {itermax = 100; printf("\nAssuming 100 presentations.");} printf("\nEnter frequency (no. of presentations) with which results should be printed (e.g., 10)"); scanf("%d", &dump_freq ); if (dump_freq < 1) {dump_freq = 1;printf("\nPrinting every presentation.");} printf("\nEnter learning rate (e.g., 0.01)"); scanf("%f", &eta ); printf("\nIs this a classification problem? (0/1)"); scanf("%d", &is_classif ); printf("\nEnter Mean Square Error for termination criterion (e.g., 0.01)"); scanf("%f", &min_err ); } switch (learning_char) { case 'l': learning_rule = 0; break; case 'L': learning_rule = 0; break; case 'a': learning_rule = 1; break; case 'A': learning_rule = 1; break; case 'p': learning_rule = 2; break; case 'P': learning_rule = 2; break; case 'k': learning_rule = 3; break; case 'K': learning_rule = 3; break; default : printf("\nUnrecognized learning rule option; assuming LMS"); } } /* read data table */ for(i = 0; data_not_over ; i++) { /* Tricky prog. here. Watch out */ for (m = 0; m <= nunit - mode ; m++) { data_not_over = (fscanf(fp_in, "%f", &original_input[i][m]) == EOF)? 0:1 ; if ((mode != TEST) && (m != nunit)) if (original_input[i][m] > max_inp) max_inp = original_input[i][m]; else if (original_input[i][m] < min_inp) min_inp = original_input[i][m]; } ndata = i; /* boundary adjustment */ } fflush(fp_in); fclose(fp_in); if (is_classif) for (i=0; i< ndata; i++) input[i][nunit] = (original_input[i][nunit] > 0.5)? 1 : -1; /* THAT changes the target outputs */ /* normalize all numbers to lie between max_inp and min_inp*/ multiplier = (norm_flag) ? 1/(max_inp - min_inp) : 1; for (i=0; i < ndata; i++){ input_length[i] = 1; for (m=0; m < nunit; m++) {input[i][m] = multiplier * (original_input[i][m] - min_inp) ; input_length[i] += (input[i][m] * input[i][m]); } input_length[i] = sqrt(input_length[i]); /* printf("\nSample %d, Original input %f, New input %f, length %f", i, original_input[i][0], input[i][0], input_length[i]); */ } } /* read_data ends */ /* learn() -- uses algorithm on pages 164-167 of Minsky & Papert's book: Perceptrons. It needs no arguments because everything has been declared in the global database. */ void learn(filename) FILE *filename; { extern int itermax; extern float input [NMXINP] [NMXIATTR + 1], weight[NMXUNIT]; int posish, k, i, j, l, mismatch, class_value, presentation_number = 1; int complete_count = 9999999; int finished = 0; float val; FILE *fp_net; void print_results(); print_results("DATA FILE", LEARN); i= ndata -1; /* to ensure beginning with i=0 */ do { /* main learning loop */ presentation_number++; mismatch = 0; if (((learning_rule < 3) /* not POCKET */ || (current_run_length > 5*ndata) ) && !(random_preferred) ) i = (i+1) % ndata; else i = random() % ndata; out[i] = weight[nunit]; /* taking care of bias */ for (j = 0; j < nunit; j++) out[i] += weight[j] * input[i][j]; if (learning_rule > 1) /* PERCEPTRON or POCKET */ { if (((input[i][nunit] > 0) && (out[i] < 0)) || ((input[i][nunit] < 0) && (out[i] > 0)) ) {/* incorrect classification */ if (learning_rule == 3) /* POCKET */ {if (current_run_length > best_run_length) { printf("\n Current run length = %d, Best run length = %d", current_run_length, best_run_length); best_run_length = current_run_length; for (posish=0; posish<= nunit; posish++) best_found[posish] = weight[posish]; } current_run_length = 0; } /*that ends pocket special case */ for(l = 0; l < nunit; l++) /* PERCEPTRON or POCKET */ weight[l] += (out[i]<0) ? input[i][l]* eta : 0 - input[i][l]* eta ; weight[nunit] += ((input[i][nunit] > 0) ? eta : 0-eta ); } /*that ends incorrect classification case*/ else /*correctly classified */ if (learning_rule == 3) current_run_length++; } /* that ends perceptron or pocket case */ else /* LMS or ALPHA-LMS */ if (!(is_classif) || (((input[i][nunit] > 0) && (out[i] < 1)) || ((input[i][nunit] < 0) && (out[i] > -1)) ) ) { for(l = 0; l < nunit; l++) weight[l] += (learning_rule) ? /**alpha-LMS **/ input[i][l]*(input[i][nunit] - out[i])* eta /(input_length[i]): /**otherwise ordinary-LMS**/ eta*input[i][l]*(input[i][nunit] - out[i]); weight[nunit] += (learning_rule) ? /**alpha-LMS **/ (input[i][nunit] - out[i])* eta /(input_length[i]): /**otherwise ordinary-LMS**/ eta*(input[i][nunit] - out[i]); } /* printf("Sample %f, output %f, weight %f, bias %f\n", input[i][0], out[i], weight[0], weight[1]); */ if ((presentation_number % dump_freq) == 0) {printf("\nPresentation No. %d\n", presentation_number); print_results("DATA FILE", LEARN); printf("\nresults_unsatisfactory = %d", results_unsatisfactory); } } while( (presentation_number <= itermax) && (results_unsatisfactory)); fp_net = fopen("net_data", "w"); fprintf(fp_net,"\n %d dimensions, %6.2f bias \n",nunit, bias); if (learning_rule == 3) { if ((current_run_length > best_run_length) || !(results_unsatisfactory) ) { for (posish=0; posish <= nunit; posish++) best_found[posish] = weight[posish]; best_run_length = current_run_length; } else{ for (posish=0; posish <= nunit; posish++) weight[posish] = best_found[posish]; } fprintf(fp_net, "\nBest run length %d\n", best_run_length); } fprintf(fp_net, "\nFinal weights:"); for(i = 0; i <= nunit; i++) fprintf(fp_net, "%6.2f ", weight[i]); fprintf(fp_net, "\nNumber of presentations = %d\n", presentation_number); printf("\nNumber of presentations = %d\n", presentation_number); /* print results on standard output */ print_results("DATA FILE", LEARN); /* DATA_FILE is header for table */ } /* test() -- quite similar to learn, only that no weight adjustment is done. Merely computes the dot product of the input pattern and the weight vector and takes appropriate action depending on whether it is positive or negative. */ void test() { extern int itermax; extern float input [NMXINP] [NMXIATTR + 1], weight[NMXUNIT]; void print_results(); int k, i, j, l; for(i = 0; i < ndata; i++) { /*compute dot product of weight[] and input vector */ out[i] = 0.0; for (j = 0; j < nunit; j++) { out[i] += weight[j] * input[i][j]; } out[i] += weight[nunit]; /* taking care of bias */ } print_results("RESULT OF TEST/SIMULATION", TEST); } /* print_results() -- print out network data and behavior on stdout. May be redirected to a file while executing the program. */ void print_results(table_title, mode) int mode; char table_title[BUFSIZE]; { extern float input [NMXINP] [NMXIATTR + 1], weight[NMXUNIT]; extern int ndata, nunit; float MSE = 0.0; float sum_err = 0.0; int i,j,m; float net_output; int correct1 = 0; int correct2 = 0; int misclass1 = 0; int misclass2 = 0; printf("\nWEIGHT DISTRIBUTION:"); for (m = 0; m < nunit; m++) { printf("wt[%d] = %6.2f\t", m, weight[m]); } printf("wt[%d] (bias) = %6.2f", nunit, weight[nunit]); printf("\n"); /* next 4-5 lines print the table header */ /* printf("%s\n", table_title); for(i = 1; i <= nunit; i++) printf(" x%d \t", i); */ if (mode == TEST) printf("CLASS \n"); else printf("DESIRED CLASS vs. NETWORK'S RESULT \n"); /* now print network behavior */ for(i = 0; i < ndata; i++){ for (m = 0; m < nunit ; m++) printf("%6.2f\t", input[i][m]); if(mode == TEST) { printf(" Node output = %f, )", out[i]); if (is_classif) if(out[i] > 0) printf("class %d\n", CLASS_1); else printf("class %d\n", CLASS_2); } else /* LEARN mode */ { net_output = weight[nunit]; for (j=0;j 0) && (net_output < 1)) || ((input[i][nunit] < 0) && (net_output > -1)) ) ) sum_err = sum_err + (net_output-input[i][nunit]) * (net_output-input[i][nunit]); if ((input[i][nunit] < 0) && (net_output>0)) { printf("*<------\n"); (misclass2)++; } else if ((input[i][nunit] > 0) && (net_output <0)) { printf("#<------\n"); (misclass1)++; } else { printf("\n"); if (input[i][nunit] > 0) (correct1)++ ; else (correct2)++; } } } printf("Class 0: %d correctly classified, %d mistakes \n", correct2,misclass2); printf("Class 1: %d mistakes, %d correctly classified \n", misclass1, correct1); MSE = sum_err/ndata; printf("Mean Square Error: %f \n", MSE); results_unsatisfactory = (MSE > min_err) && (misclass1 + misclass2); } /* ------------------------------------------------------------ */ void print_initial(table_title, mode) int mode; char table_title[BUFSIZE]; { extern float input [NMXINP] [NMXIATTR + 1], weight[NMXUNIT]; extern int ndata, nunit; int i, m; printf("\nNETWORK PARAMETERS\n"); printf("No. of input dimensions = %d\n", nunit); printf("No. of input patterns = %d\n", ndata); printf("Max. number of iter = %d\n", itermax); printf("bias = %6.2f\n", bias); } /* initialize weights with random numbers between -0.5 and +0.5 */ void initwt() { extern float weight[NMXUNIT], bias; int random(), i; for (i=0; i < nunit; i++) *(weight + i) = random()/pow(2.0,15.0) - 0.5; *(weight + nunit) = bias; /* taking care of bias */ } /* random number generator (computer independent) */ long randseed = 568731L; int random() { randseed = 15625L * randseed + 22221L; return((randseed >> 16) & 0x7FFF); }