masses/garescorer.c (1,043 lines of code) (raw):

/* @COPYRIGHT */ /* * This program uses PGAPack to do its GA stuff. * ftp://ftp.mcs.anl.gov/pub/pgapack/pgapack.tar.Z * I used this one instead of galib because it uses MPI * to spread load around. It also seems like the API is a little * cleaner. */ #include "pgapack.h" #include <unistd.h> #include <sys/time.h> #include <math.h> #include "tmp/scores.h" #include "tmp/tests.h" extern int num_spam, num_ham; /* in tmp/tests.h */ /* Use score ranges derived from hit-frequencies S/O ratio, * and numbers of mails hit. */ #define USE_SCORE_RANGES #define USE_VARIABLE_MUTATIONS /* Lamarckian evolution? (Jean-Baptiste Lamarck) */ /* inheritance of acquired characters / soft inheritance / Lamarckism */ #define LAMARCK double evaluate(PGAContext *, int, int); int GetIntegerParameter(char *query); void dump(FILE *); void WriteString(PGAContext *ctx, FILE *fp, int p, int pop); void showSummary(PGAContext *ctx); #if defined(USE_VARIABLE_MUTATIONS) || (! defined(USE_SCORE_RANGES)) int myMutation(PGAContext *, int, int, double); # ifdef LAMARCK int adapt(PGAContext *, int, int, int, int,int); # endif #endif #ifdef USE_VARIABLE_MUTATIONS void CreateString (PGAContext *, int, int, int); void Crossover (PGAContext *, int, int, int, int, int, int); void CopyString (PGAContext *, int, int, int, int); int DuplicateString (PGAContext *, int, int, int, int); MPI_Datatype BuildDT (PGAContext *, int, int); #endif void dump(FILE *); void WriteString(PGAContext *ctx, FILE *fp, int p, int pop); void showSummary(PGAContext *ctx); double evaluate_inner(); double threshold = 5.0; double nybias = 10.0; double fptarget = -1.0; /* -1 means unused */ int save_every_n_generations = 50; int no_change_val = 300; int pop_size = 50; int replace_num = 33; int maxiter = 30000; struct timeval t0 = { 0, 0 }; int t0_iter = 0; #ifdef USE_VARIABLE_MUTATIONS double mutation_rate = 0.03; double base_mutation_rate = 0.03; #ifdef LAMARCK int adapt_yn = 0; int adapt_ny = 0; #endif double mutation_rate_modifier = 0.85; int num_better_same = 0; int num_worse = 0; #else const double mutation_rate = 0.03; #endif const double mutation_noise = 0.5; #ifdef USE_VARIABLE_MUTATIONS const double min_mutation_noise = 0.1; #endif const double regression_coefficient = 0.75; #ifndef USE_SCORE_RANGES const double SCORE_CAP = 4.0; const double NEG_SCORE_CAP = -9.0; #endif #ifdef USE_VARIABLE_MUTATIONS const double crossover_rate = 0.5; #else const double crossover_rate = 0.65; #endif int justCount = 0; void usage() { #ifdef USE_MPI int rank; MPI_Comm_rank(MPI_COMM_WORLD, &rank); if(rank == 0) { #endif printf("usage: evolve [-s size] [args]\n" "\n" " -s size = population size (50 recommended)\n" " -e num_epochs = number of epochs (generations) to run (30000 default)\n" " -r replace = number of individuals to replace each generation (20 recommended)\n" " -b nybias = bias towards false negatives (10.0 default)\n" " -f fptarget = target FP percentage (alt fitness function, off by default)\n" " -t threshold = threshold for spam/nonspam decision (5 default)\n" "\n" " -C = just count hits and exit, no evolution\n\n"); #ifdef USE_MPI } #endif exit (30); } void init_data() { #ifdef USE_MPI int rank; MPI_Comm_rank(MPI_COMM_WORLD, &rank); if (rank == 0) { #endif loadtests(); loadscores(); nybias = nybias*((double)num_spam)/((double)num_ham); #ifdef USE_VARIABLE_MUTATIONS mutation_rate_modifier = (double)pow(mutation_rate_modifier, (double)1/num_mutable); #endif #ifdef USE_MPI } MPI_Bcast(num_tests_hit, num_nondup, MPI_CHAR, 0, MPI_COMM_WORLD); MPI_Bcast(&nybias, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD); MPI_Bcast(is_spam, num_nondup, MPI_CHAR, 0, MPI_COMM_WORLD); MPI_Bcast(tests_hit, num_nondup*max_hits_per_msg, MPI_SHORT, 0, MPI_COMM_WORLD); #ifdef USE_VARIABLE_MUTATIONS MPI_Bcast(&mutation_rate_modifier, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD); #endif MPI_Bcast(is_mutable, num_scores, MPI_CHAR, 0, MPI_COMM_WORLD); MPI_Bcast(range_lo, num_scores, MPI_DOUBLE, 0, MPI_COMM_WORLD); MPI_Bcast(range_hi, num_scores, MPI_DOUBLE, 0, MPI_COMM_WORLD); MPI_Bcast(bestscores, num_scores, MPI_DOUBLE, 0, MPI_COMM_WORLD); MPI_Bcast(scores, num_scores, MPI_DOUBLE, 0, MPI_COMM_WORLD); #endif } /* this is about 35% faster than calling PGAGetRealAllele() directly inside * score_msg(), in my tests. */ void load_scores_into_lookup(PGAContext *ctx, int p, int pop) { int i; for (i = 0; i < num_mutable; i++) { lookup[i] = PGAGetRealAllele(ctx, p, pop, i); #ifdef LAMARCK yn_hit[i] = ny_hit[i] = 0; #endif } } int main(int argc, char **argv) { PGAContext *ctx; int i,p; int arg; #ifdef USE_MPI MPI_Init(&argc, &argv); #endif while ((arg = getopt (argc, argv, "b:r:s:e:t:f:C")) != -1) { switch (arg) { case 'b': nybias = atof(optarg); break; case 'f': fptarget = atof(optarg); break; case 't': threshold = (double) atof(optarg); break; case 's': pop_size = atoi(optarg); break; case 'e': maxiter = atoi(optarg); break; case 'r': replace_num = atoi(optarg); break; case 'C': justCount = 1; break; case '?': usage(); break; } } init_data(); ctx = PGACreate(&argc, argv, PGA_DATATYPE_REAL, num_scores, PGA_MINIMIZE); PGASetUserFunction(ctx, PGA_USERFUNCTION_PRINTSTRING, (void *)WriteString); PGASetUserFunction(ctx, PGA_USERFUNCTION_ENDOFGEN, (void *)showSummary); /* use a tiny population - just want to get into the evaluate function */ if (justCount) { pop_size = 2; replace_num = 1; } PGASetPopSize(ctx, pop_size); PGASetRealInitRange (ctx, range_lo, range_hi); PGASetMutationBoundedFlag(ctx, PGA_FALSE); PGASetNumReplaceValue(ctx, replace_num); /* Defaults to this - Allen */ /* PGASetMutationOrCrossoverFlag(ctx, PGA_TRUE); */ if (justCount) { /* don't allow any mutation or crossover */ PGASetMutationType(ctx, PGA_MUTATION_CONSTANT); PGASetRealInitRange (ctx, bestscores, bestscores); PGASetCrossoverProb(ctx, 0.0); for(i=0; i<num_scores; i++) { for(p=0; p<pop_size; p++) { /* just counting? score[i] = defaultscore[i] in that case */ PGASetRealAllele(ctx, p, PGA_NEWPOP, i, bestscores[i]); } } } else { #if (! defined(USE_SCORE_RANGES)) || defined(USE_VARIABLE_MUTATIONS) PGASetUserFunction(ctx, PGA_USERFUNCTION_MUTATION, (void *)myMutation); #else PGASetMutationType(ctx, PGA_MUTATION_RANGE); #endif /* PGASetCrossoverType(ctx, PGA_CROSSOVER_ONEPT); */ PGASetCrossoverProb(ctx, crossover_rate); #ifdef USE_VARIABLE_MUTATIONS mutation_rate = 0.15/sqrt(num_mutable); base_mutation_rate = mutation_rate; PGASetMutationProb(ctx, mutation_rate); PGASetUserFunction(ctx, PGA_USERFUNCTION_CROSSOVER, (void *)Crossover); PGASetUserFunction(ctx, PGA_USERFUNCTION_CREATESTRING, (void *)CreateString); PGASetUserFunction(ctx, PGA_USERFUNCTION_COPYSTRING, (void *)CopyString); PGASetUserFunction(ctx, PGA_USERFUNCTION_DUPLICATE, (void *)DuplicateString); PGASetUserFunction(ctx, PGA_USERFUNCTION_BUILDDATATYPE, (void *)BuildDT); #endif } PGASetPrintFrequencyValue(ctx,300); PGASetPrintOptions(ctx, PGA_REPORT_AVERAGE); PGASetStoppingRuleType(ctx, PGA_STOP_NOCHANGE); PGASetMaxNoChangeValue(ctx, no_change_val); PGASetMaxGAIterValue(ctx, maxiter); PGASetUp(ctx); #ifndef USE_VARIABLE_MUTATIONS if (! justCount) { /* Now initialize the scores */ for(i=0; i<num_scores; i++) { for(p=0; p<pop_size; p++) { #ifndef USE_SCORE_RANGES if (is_mutable[i]) { if(bestscores[i] > SCORE_CAP) bestscores[i] = SCORE_CAP; else if(bestscores[i] < NEG_SCORE_CAP) bestscores[i] = NEG_SCORE_CAP; } #endif PGASetRealAllele(ctx, p, PGA_NEWPOP, i, bestscores[i]); } } } #endif /* ! USE_VARIABLE_MUTATIONS */ (void)gettimeofday(&t0, (struct timezone *)NULL); PGARun(ctx, evaluate); PGADestroy(ctx); #ifdef USE_MPI MPI_Finalize(); #endif return(0); } int ga_yy,ga_yn,ga_ny,ga_nn; #ifdef USE_VARIABLE_MUTATIONS int num_mutated = 0; int var_mutated = 0; int iters_same_passed = 0; #ifdef LAMARCK int weight_balance; int adapt_times = 0; int adapt_crossover = 0; int adapt_repeat = 0; int adapt_overshot = 0; int adapt_fp_add = 0; int adapt_fn_add = 0; #endif #endif double ynscore,nyscore,yyscore,nnscore; double score_msg(PGAContext *ctx, int p, int pop, int i) { double msg_score = 0.0; int j; /* For every test the message hit on */ for(j=num_tests_hit[i]-1; j>=0; j--) { /* Up the message score by the allele for this test in the genome * msg_score += PGAGetRealAllele(ctx, p, pop, tests_hit[i][j]); */ msg_score += lookup[tests_hit[i][j]]; } msg_score += scores[i]; /* base from non-mutable */ /* Ok, now we know the score for this message. * Let's see how this genome did... */ if(is_spam[i]) { if(msg_score >= threshold) { /* Good positive */ ga_yy += tests_count[i]; yyscore += msg_score*tests_count[i]; /* Each true positive means yyscore += at least 5 */ } else { /* False negative */ ga_yn += tests_count[i]; ynscore += msg_score*tests_count[i]; /* Each false negative means that ynscore += less than 5 */ #ifdef LAMARCK for(j=num_tests_hit[i]-1; j>=0; j--) yn_hit[tests_hit[i][j]] = 1; #endif } } else { if(msg_score >= threshold) { /* False positive */ ga_ny += tests_count[i]; nyscore += msg_score*tests_count[i]; /* Each false positive means nyscore += more than 5 */ #ifdef LAMARCK for(j=num_tests_hit[i]-1; j>=0; j--) ny_hit[tests_hit[i][j]] = 1; #endif } else { /* Good negative */ ga_nn += tests_count[i]; nnscore += msg_score*tests_count[i]; /* Each good negative means nnscore += less than 5 */ } } return msg_score*tests_count[i]; } double evaluate(PGAContext *ctx, int p, int pop) { double tot_score = 0.0; int i; yyscore = ynscore = nyscore = nnscore = 0.0; ga_yy=ga_yn=ga_ny=ga_nn=0; load_scores_into_lookup(ctx, p, pop); /* For every message */ for (i=num_nondup-1; i>=0; i--) { tot_score += score_msg(ctx,p,pop,i); } if (justCount) { dump(stdout); exit (0); } return evaluate_inner(); } /* So can figure out how would evaluate without above - Allen */ double evaluate_inner() { double dist_from_target_fp_rate_multiplier; double ynweight,nyweight; /* just count how far they were from the threshold, in each case */ ynweight = (ga_yn * threshold) - ynscore; nyweight = nyscore - (ga_ny * threshold); #ifdef LAMARCK if (ynweight > (nyweight*nybias)) weight_balance = -1; else if (ynweight < (nyweight*nybias)) weight_balance = 1; else weight_balance = 0; #endif if (fptarget >= 0.0) { /* abs((FP rate as percentage) - (target FP rate)) */ dist_from_target_fp_rate_multiplier = fabs(((ga_ny / (float) num_ham) * 100.0) - fptarget); /* now ensure it's >= 1.0 and a large multiplier */ dist_from_target_fp_rate_multiplier = (dist_from_target_fp_rate_multiplier * 10) + 1.0; /* criteria, in order of priority: FP/FN rate, in number of messages; * distance from target FP rate; then the distance of FP and FN scores * from the threshold (as the least important criterion) */ return ((100 * (ga_yn + ga_ny)) * dist_from_target_fp_rate_multiplier) + (ynweight + nyweight*nybias); } else { return ynweight + /* all FNs' points from threshold */ nyweight*nybias; /* all FPs' points from threshold */ } } #ifdef LAMARCK int adapt(PGAContext *ctx, int p, int pop, int done_eval, int threshold, int repeat) { double *myscores; int i; int changed = 0; double tmp,old_evaluation,new_evaluation; if (justCount) { return 0; } adapt_times++; if (done_eval && PGAGetEvaluationUpToDateFlag(ctx, p, pop)) old_evaluation = PGAGetEvaluation(ctx, p, pop); else { old_evaluation = evaluate(ctx, p, pop); PGASetEvaluation(ctx, p, pop, old_evaluation); PGASetEvaluationUpToDateFlag(ctx, p, pop, PGA_TRUE); } if ((double)ga_yn > ((double)ga_ny*nybias)) weight_balance--; else if ((double)ga_yn < ((double)ga_ny*nybias)) weight_balance++; if ((weight_balance < (threshold-1)) && (weight_balance > -threshold)) return 0; myscores = PGAGetIndividual(ctx, p, pop)->chrom; if (repeat) { for (i = 0; i < num_mutable; i++) { if ((yn_hit[i] && (weight_balance < 0)) || (ny_hit[i] && (weight_balance > 0))) { if (((weight_balance < 0) && #ifdef USE_SCORE_RANGES (myscores[i] < range_hi[i]) && #endif (myscores[i] < -(double)0.01)) || ((weight_balance > 0) && #ifdef USE_SCORE_RANGES (myscores[i] > range_lo[i]) && #endif (myscores[i] > (double)0.01))) { tmp_scores[i][0] = (double)0.001*rint(myscores[i]); /* reducing */ #ifdef USE_SCORE_RANGES if (((myscores[i] < -(double)0.01) && ((myscores[i] - tmp_scores[i][0]) > range_hi[i])) || ((myscores[i] > (double)0.01) && ((myscores[i] - tmp_scores[i][0]) < range_lo[i]))) { tmp_scores[i][0] = 0; } #endif if (tmp_scores[i][0]) { changed = 1; lookup[i] = 0; } } else tmp_scores[i][0] = 0; } else tmp_scores[i][0] = 0; } if (! changed) /* if can't reduce, don't do anything - safe */ return 0; /* For every message */ for (i=num_nondup-1; i>=0; i--) { tmp_total[i] = scores[i]; scores[i] = score_msg(ctx,p,pop,i)/tests_count[i]; /* score sans ones modifying */ } for (i = 0; i < num_mutable; i++) { if (tmp_scores[i][0]) { lookup[i] = myscores[i]; tmp_scores[i][1] = 1; if (weight_balance < 0) { yn_hit[i] = 1; ny_hit[i] = 0; } else { ny_hit[i] = 1; yn_hit[i] = 0; } } else { lookup[i] = 0; tmp_scores[i][1] = 0; yn_hit[i] = ny_hit[i] = 0; } } new_evaluation = old_evaluation; /* avoid a warning */ while (1) { changed = 0; for (i = 0; i < num_mutable; i++) { if (((tmp_scores[i][0] < 0) && yn_hit[i] && /* going up */ #ifdef USE_SCORE_RANGES ((lookup[i] - tmp_scores[i][0]) < range_hi[i]) && #endif (weight_balance < 0) && (lookup[i] < -(double)0.01)) || ((tmp_scores[i][0] > 0) && ny_hit[i] && /* going down */ #ifdef USE_SCORE_RANGES ((lookup[i] - tmp_scores[i][0]) > range_lo[i]) && #endif (weight_balance > 0) && (lookup[i] > (double)0.01))) { lookup[i] -= tmp_scores[i][0]; changed = 1; } else tmp_scores[i][0] = 0; yn_hit[i] = ny_hit[i] = 0; } if (changed) { if (weight_balance > 0) adapt_ny++; else adapt_yn++; adapt_repeat++; } else break; yyscore = ynscore = nyscore = nnscore = 0.0; ga_yy=ga_yn=ga_ny=ga_nn=0; for (i=num_nondup-1; i>=0; i--) (void)score_msg(ctx,p,pop,i); new_evaluation = evaluate_inner(); if (new_evaluation > old_evaluation) { for (i = 0; i < num_mutable; i++) { if (tmp_scores[i][0]) lookup[i] += tmp_scores[i][0]; } new_evaluation = old_evaluation; adapt_overshot++; break; } else old_evaluation = new_evaluation; if ((double)ga_yn > ((double)ga_ny*nybias)) weight_balance--; else if ((double)ga_yn < ((double)ga_ny*nybias)) weight_balance++; if ((weight_balance < (threshold-1)) && (weight_balance > -threshold)) break; } for (i=num_nondup-1; i>=0; i--) scores[i] = tmp_total[i]; for (i=0; i < num_mutable; i++) { if (tmp_scores[i][1]) myscores[i] = lookup[i]; } PGASetEvaluation(ctx, p, pop, new_evaluation); PGASetEvaluationUpToDateFlag(ctx, p, pop, PGA_TRUE); return 1; } else { for (i = 0; i < num_mutable; i++) { if ((yn_hit[i] && (weight_balance < 0)) || (ny_hit[i] && (weight_balance > 0))) { tmp = (double)0.001*rint(myscores[i]); if (! tmp) { if (myscores[i] > (double)0.01) tmp = (double)0.001; else if (myscores[i] < -(double)0.01) tmp = -(double)0.001; } #ifdef USE_SCORE_RANGES if (tmp && (((myscores[i] > 0) && ((myscores[i] - tmp) < range_lo[i])) || ((myscores[i] < 0) && ((myscores[i] - tmp) > range_hi[i])))) tmp = 0; #endif if (tmp) { myscores[i] -= tmp; changed = 1; } } } if (changed) { if (weight_balance > 0) adapt_ny++; else adapt_yn++; return 1; } else return 0; } } #endif /* * This mutation function tosses a weighted coin for each allele. * If the allele is to be mutated, then the way it's mutated is to regress it * toward the mean of the population for that allele, then add a little * gaussian noise. * * [To the _mean_? Weird... - Allen] * * Aug 21 2002 jm: we now use ranges and allow PGA to take care of it, if * USE_SCORE_RANGES is defined. * * Modified for variable mutations - 9/26/02 - Allen * */ #if defined(USE_VARIABLE_MUTATIONS) || (! defined(USE_SCORE_RANGES)) int myMutation(PGAContext *ctx, int p, int pop, double mr) { int count=0; int i; # ifdef USE_VARIABLE_MUTATIONS double *myscores; double old_evaluation,new_evaluation,min_score,max_score; myscores = PGAGetIndividual(ctx, p, pop)->chrom; if (PGAGetEvaluationUpToDateFlag(ctx, p, pop)) old_evaluation = PGAGetEvaluation(ctx, p, pop); else { old_evaluation = evaluate(ctx, p, pop); PGASetEvaluation(ctx, p, pop, old_evaluation); PGASetEvaluationUpToDateFlag(ctx, p, pop, PGA_TRUE); } for (i=0; i<num_mutable; i++) { tmp_scores[i][0] = 0; if (PGARandomFlip(ctx, mr)) { #ifdef USE_SCORE_RANGES min_score = range_lo[i]; max_score = range_hi[i]; #else min_score = SCORE_CAP; max_score = NEG_SCORE_CAP; #endif if (myscores[i] > max_score) myscores[i] = max_score; else if (myscores[i] < min_score) myscores[i] = min_score; tmp_scores[i][1] = (max_score - min_score)/4; myscores[i+num_scores] *= pow(2,(PGARandomGaussian(ctx,0,mutation_noise*2))); if (myscores[i+num_scores] < min_mutation_noise) myscores[i+num_scores] = min_mutation_noise; else if (myscores[i+num_scores] > tmp_scores[i][1]) myscores[i+num_scores] = tmp_scores[i][1]; while (! tmp_scores[i][0]) { tmp_scores[i][0] = PGARandomGaussian(ctx,0, myscores[i+num_scores]); #ifdef USE_SCORE_RANGES if (((double)(myscores[i] + tmp_scores[i][0]) >= max_score) || ((double)(myscores[i] + tmp_scores[i][0]) <= min_score)) { if (myscores[i+num_scores] > mutation_noise) { myscores[i+num_scores] = (myscores[i+num_scores] + mutation_noise)/2; tmp_scores[i][0] = 0; } else if ((double)(myscores[i] + tmp_scores[i][0]) >= max_score) { tmp_scores[i][0] = max_score - myscores[i] - (double)0.001; break; } else { tmp_scores[i][0] = min_score - myscores[i] + (double)0.001; break; } } #endif } myscores[i] += tmp_scores[i][0]; count++; } } if (count > 0) { var_mutated++; new_evaluation = evaluate(ctx, p, pop); if (new_evaluation > old_evaluation) { /* Did previous try go too far away? */ if (iters_same_passed) { /* in 2nd phase */ count = 0; for (i=0; i<num_mutable; i++) { if (tmp_scores[i][0]) { if (myscores[i+num_scores] > mutation_noise) { tmp_scores[i][1] = PGARandomGaussian(ctx,0,mutation_noise); count++; } else tmp_scores[i][1] = PGARandomGaussian(ctx,0,myscores[i+num_scores]); tmp_scores[i][1] = copysign(tmp_scores[i][1],tmp_scores[i][0]); #ifdef USE_SCORE_RANGES if ((double)(myscores[i] + tmp_scores[i][1] - tmp_scores[i][0]) >= range_hi[i]) { tmp_scores[i][1] = range_hi[i] - myscores[i] + tmp_scores[i][0] - (double)0.001; } else if ((double)(myscores[i] + tmp_scores[i][1] - tmp_scores[i][0]) <= range_lo[i]) { tmp_scores[i][1] = range_lo[i] - myscores[i] + tmp_scores[i][0] + (double)0.001; } #endif myscores[i] += tmp_scores[i][1] - tmp_scores[i][0]; } } if (count > 0) { num_mutated++; new_evaluation = evaluate(ctx, p, pop); if (PGAGetNoDuplicatesFlag(ctx) == PGA_FALSE) { /* Hack to avoid redoing evaluation without need - Allen */ count = 0; PGASetEvaluation(ctx, p, pop, new_evaluation); PGASetEvaluationUpToDateFlag(ctx, p, pop, PGA_TRUE); } if (new_evaluation <= old_evaluation) { /* Previous try went too far away */ if (mr < base_mutation_rate) num_better_same++; for (i=0; i<num_mutable; i++) { if (tmp_scores[i][0] && (myscores[i+num_scores] > mutation_noise) && (fabs(tmp_scores[i][1]) < fabs(tmp_scores[i][0]))) myscores[i+num_scores] = (myscores[i+num_scores] + mutation_noise)/2; } } else { #ifdef LAMARCK if (mr < base_mutation_rate) { count = adapt(ctx,p,pop,1,1,0); if (count) { count = adapt(ctx,p,pop,0,2,1); if (count) new_evaluation = PGAGetEvaluation(ctx, p, pop); else new_evaluation = evaluate(ctx, p, pop); if (new_evaluation > old_evaluation) num_worse++; else num_better_same++; /* only had to adapt once */ if (PGAGetNoDuplicatesFlag(ctx) == PGA_FALSE) { /* Hack to avoid redoing evaluation without need - Allen */ count = 0; PGASetEvaluation(ctx, p, pop, new_evaluation); PGASetEvaluationUpToDateFlag(ctx, p, pop, PGA_TRUE); } } else num_worse++; } else #endif num_worse++; } } else { /* didn't decrease mutation SD */ #ifdef LAMARCK if (mr < base_mutation_rate) { count = adapt(ctx,p,pop,0,1,0); new_evaluation = evaluate(ctx, p, pop); } #endif if (new_evaluation > old_evaluation) { #ifdef LAMARCK if ((mr < base_mutation_rate) && count) { count = adapt(ctx,p,pop,1,2,1); if (count) { new_evaluation = PGAGetEvaluation(ctx, p, pop); if (new_evaluation > old_evaluation) num_worse++; else num_better_same++; } else num_worse++; } else #endif num_worse++; } else num_better_same++; if (PGAGetNoDuplicatesFlag(ctx) == PGA_FALSE) { /* Hack to avoid redoing evaluation without need - Allen */ count = 0; PGASetEvaluation(ctx, p, pop, new_evaluation); PGASetEvaluationUpToDateFlag(ctx, p, pop, PGA_TRUE); } } if ((! count) && (PGAGetNoDuplicatesFlag(ctx) == PGA_TRUE)) count++; } else num_worse++; } else { if (PGAGetNoDuplicatesFlag(ctx) == PGA_FALSE) { /* Hack to avoid redoing evaluation without need - Allen */ count = 0; PGASetEvaluation(ctx, p, pop, new_evaluation); PGASetEvaluationUpToDateFlag(ctx, p, pop, PGA_TRUE); } num_better_same++; } } #ifdef LAMARCK else if (mr < base_mutation_rate) { count = adapt(ctx,p,pop,1,2,0); if (! count) num_better_same++; /* adapt not working, use mutation */ } #endif # else /* USE_VARIABLE_MUTATIONS */ int j; for (i=0; i<num_mutable; i++) { if(PGARandomFlip(ctx, mr)) { double gene_sum=0.0; /* Find the mean */ for(j=0; j<pop_size; j++) { if(p!=j) gene_sum += PGAGetRealAllele(ctx, j, pop, i); } gene_sum /= (double)(pop_size-1); /* Regress towards it... */ gene_sum = (1.0-regression_coefficient)*gene_sum+regression_coefficient*PGAGetRealAllele(ctx, p, pop, i); /* Set this gene in this allele to be the average, plus some gaussian noise */ if(gene_sum > SCORE_CAP) gene_sum = SCORE_CAP; else if(gene_sum < NEG_SCORE_CAP) gene_sum = NEG_SCORE_CAP; PGASetRealAllele(ctx, p, pop, i, PGARandomGaussian(ctx, gene_sum, mutation_noise)); count++; } } # endif /* !USE_VARIABLE_MUTATIONS */ return count; } #endif /* USE_VARIABLE_MUTATIONS || !USE_SCORE_RANGES */ void dump(FILE *fp) { fprintf (fp,"\n# SUMMARY for threshold %3.1f:\n", threshold); fprintf (fp, "# Correctly non-spam: %6d %4.3f%% (%4.3f%% of non-spam corpus)\n", ga_nn, (ga_nn / (float) num_tests) * 100.0, (ga_nn / (float) num_ham) * 100.0); fprintf (fp, "# Correctly spam: %6d %4.3f%% (%4.3f%% of spam corpus)\n", ga_yy, (ga_yy / (float) num_tests) * 100.0, (ga_yy / (float) num_spam) * 100.0); fprintf (fp, "# False positives: %6d %4.3f%% (%4.3f%% of nonspam, %6.0f weighted)\n", ga_ny, (ga_ny / (float) num_tests) * 100.0, (ga_ny / (float) num_ham) * 100.0, nyscore*nybias); fprintf (fp, "# False negatives: %6d %4.3f%% (%4.3f%% of spam, %6.0f weighted)\n", ga_yn, (ga_yn / (float) num_tests) * 100.0, (ga_yn / (float) num_spam) * 100.0, ynscore); fprintf (fp,"# Average score for spam: %3.1f nonspam: %3.1f\n",(ynscore+yyscore)/((double)(ga_yn+ga_yy)),(nyscore+nnscore)/((double)(ga_nn+ga_ny))); fprintf (fp,"# Average for false-pos: %3.1f false-neg: %3.1f\n",(nyscore/(double)ga_ny),(ynscore/(double)ga_yn)); fprintf (fp,"# TOTAL: %6d %3.2f%%\n\n", num_tests, 100.0); } /***************************************************************************** * WriteString sends a visual representation of the chromosome out to fp * *****************************************************************************/ void WriteString(PGAContext *ctx, FILE *fp, int p, int pop) { int i; #ifdef USE_MPI int rank; MPI_Comm_rank(MPI_COMM_WORLD, &rank); if(0 == rank) { #endif evaluate(ctx,p,pop); dump(fp); for(i=0; i<num_scores; i++) { fprintf(fp,"score %-30s %2.3f\n", score_names[i],PGAGetRealAllele(ctx, p, pop, i)); } fprintf ( fp,"\n" ); #ifdef USE_MPI } #endif } #ifdef USE_VARIABLE_MUTATIONS double last_best = 0; #endif void showSummary(PGAContext *ctx) { #ifdef USE_MPI int rank; MPI_Comm_rank(MPI_COMM_WORLD, &rank); if(0 == rank) { #endif if(0 == PGAGetGAIterValue(ctx) % save_every_n_generations) { int genome = PGAGetBestIndex(ctx,PGA_OLDPOP); FILE *scores_file = NULL; (void)evaluate(ctx, genome, PGA_OLDPOP); PGAGetEvaluation(ctx, genome, PGA_OLDPOP); scores_file = fopen("garescorer.scores","w"); WriteString(ctx, scores_file, genome, PGA_OLDPOP); fclose(scores_file); #ifdef USE_VARIABLE_MUTATIONS if (! justCount) { printf("\nPop size, replacement: %d %d\n", pop_size, replace_num); /* printf("\nMutations (rate, good, bad, var, num): %3.7f %d %d %d %d\n", mutation_rate, num_better_same, num_worse, var_mutated, num_mutated); */ var_mutated = 0; num_mutated = 0; if (! iters_same_passed) { if (! last_best) last_best = ctx->rep.Best; else if ((last_best*0.999) < ctx->rep.Best) /* too slow! */ iters_same_passed = 1; else last_best = ctx->rep.Best; } #ifdef LAMARCK printf("\n"); printf("Adapt (t, fneg, fneg_add, fpos, fpos_add): %d %d %d %d %d\n", adapt_times,adapt_yn,adapt_fn_add,adapt_ny,adapt_fp_add); printf("Adapt (over, cross, repeat): %d %d %d\n", adapt_overshot,adapt_crossover,adapt_repeat); adapt_times = adapt_overshot = adapt_crossover = adapt_repeat = adapt_yn = adapt_ny = adapt_fn_add = adapt_fp_add = 0; #endif } #endif { struct timeval t1; if (gettimeofday(&t1, (struct timezone *)NULL) == 0) { double dt = (t1.tv_sec + t1.tv_usec * 1.0e-6) - (t0.tv_sec + t0.tv_usec * 1.0e-6); int iter = PGAGetGAIterValue(ctx); if (dt < 1e-6) dt = 1e-6; printf("Performance: %.3f iterations/s, iteration no. %d\n", (iter-t0_iter)/dt, iter); t0.tv_sec = t1.tv_sec; t0.tv_usec = t0.tv_usec; t0_iter = iter; } } dump(stdout); } else if(0 == PGAGetGAIterValue(ctx) % 5) { printf("%d",(PGAGetGAIterValue(ctx)/5)%10); } #ifdef USE_VARIABLE_MUTATIONS if (! justCount) { if ((num_better_same*4) >= num_worse) mutation_rate /= mutation_rate_modifier; else if ((num_better_same*4) < num_worse) { if ((mutation_rate > base_mutation_rate) || iters_same_passed) mutation_rate *= mutation_rate_modifier; else if (ctx->ga.ItersOfSame >= (no_change_val/2)) { iters_same_passed = 1; mutation_rate *= mutation_rate_modifier; printf("\nMutation rate %3.7f (ItersOfSame %d)\n", mutation_rate,ctx->ga.ItersOfSame); } else return; } if (mutation_rate > mutation_rate_modifier) { mutation_rate = mutation_rate_modifier; printf("\nMutation rate max: %3.7f\n",mutation_rate); } else if (mutation_rate < 0.05/sqrt(num_mutable)) { mutation_rate = 0.05/sqrt(num_mutable); printf("\nMutation rate min: %3.7f\n",mutation_rate); } PGASetMutationProb(ctx, mutation_rate); num_better_same = 0; num_worse = 0; } #endif #ifdef USE_MPI } #endif } #ifdef USE_VARIABLE_MUTATIONS /***************************************************************************** * CreateString allocates and initializes a chromosome. If InitFlag is * * set to true, then it will initialize the chromosome using the best known * * values; otherwise, it sets each double to 0.0 and each int to 0. * *****************************************************************************/ void CreateString(PGAContext *ctx, int p, int pop, int InitFlag) { int i; double *myscore; PGAIndividual *new; new = PGAGetIndividual(ctx, p, pop); if (!(new->chrom = malloc(sizeof(double)*num_scores*2))) { fprintf(stderr, "No room for new->chrom"); exit(1); } myscore = new->chrom; if (InitFlag) { for(i=0; i<num_scores; i++) myscore[i] = bestscores[i]; for(i=num_scores; i<num_scores*2; i++) myscore[i] = mutation_noise; } else { for(i=0; i<num_scores*2; i++) myscore[i] = 0.0; } } /***************************************************************************** * Crossover implements uniform crossover on the chromosome. * *****************************************************************************/ void Crossover(PGAContext *ctx, int p1, int p2, int pop1, int t1, int t2, int pop2) { int i; double *parent1, *parent2, *child1, *child2; double pu; #ifdef LAMARCK double parent1_eval, parent2_eval, child1_eval, child2_eval; #endif parent1 = PGAGetIndividual(ctx, p1, pop1)->chrom; parent2 = PGAGetIndividual(ctx, p2, pop1)->chrom; child1 = PGAGetIndividual(ctx, t1, pop2)->chrom; child2 = PGAGetIndividual(ctx, t2, pop2)->chrom; pu = PGAGetUniformCrossoverProb(ctx); for (i = 0; i < num_mutable; i++) { if (PGARandomFlip(ctx, pu)) { child1[i] = parent2[i]; child2[i] = parent1[i]; if (num_mutated > 0) { if (fabs(parent1[i+num_scores] - mutation_noise) > fabs(parent1[i+num_scores] - parent2[i+num_scores])) child2[i+num_scores] = (parent1[i+num_scores] + parent2[i+num_scores])/2; else child2[i+num_scores] = (parent1[i+num_scores] + mutation_noise)/2; if (fabs(parent2[i+num_scores] - mutation_noise) > fabs(parent2[i+num_scores] - parent1[i+num_scores])) child1[i+num_scores] = (parent2[i+num_scores] + parent1[i+num_scores])/2; else child1[i+num_scores] = (parent2[i+num_scores] + mutation_noise)/2; } else { /* Doing intermediate recombination due to usage * of exponential multiplication in mutation - Allen */ child1[i+num_scores] = child2[i+num_scores] = (parent1[i+num_scores] + parent2[i+num_scores])/2; } } else { child1[i] = parent1[i]; child2[i] = parent2[i]; if (pu < 0.5) { /* more grouped */ child1[i+num_scores] = parent1[i+num_scores]; child2[i+num_scores] = parent2[i+num_scores]; } else { if (num_mutated > 0) { if (fabs(parent1[i+num_scores] - mutation_noise) > fabs(parent1[i+num_scores] - parent2[i+num_scores])) child1[i+num_scores] = (parent1[i+num_scores] + parent2[i+num_scores])/2; else child1[i+num_scores] = (parent1[i+num_scores] + mutation_noise)/2; if (fabs(parent2[i+num_scores] - mutation_noise) > fabs(parent2[i+num_scores] - parent1[i+num_scores])) child2[i+num_scores] = (parent2[i+num_scores] + parent1[i+num_scores])/2; else child2[i+num_scores] = (parent2[i+num_scores] + mutation_noise)/2; } else { /* Doing intermediate recombination due to usage * of exponential multiplication in mutation - Allen */ child1[i+num_scores] = child2[i+num_scores] = (parent1[i+num_scores] + parent2[i+num_scores])/2; } } } } for (i = num_mutable; i < num_scores; i++) { child1[i] = parent1[i]; child2[i] = parent2[i]; child1[i+num_scores] = parent1[i+num_scores]; child2[i+num_scores] = parent2[i+num_scores]; } #ifdef LAMARCK if ((PGAGetMutationAndCrossoverFlag(ctx) == PGA_FALSE) && (mutation_rate < base_mutation_rate) && (PGAGetEvaluationUpToDateFlag(ctx, p1, pop1) == PGA_TRUE) && (PGAGetEvaluationUpToDateFlag(ctx, p2, pop1) == PGA_TRUE)) { parent1_eval = PGAGetEvaluation(ctx, p1, pop1); parent2_eval = PGAGetEvaluation(ctx, p2, pop1); if (PGARandomFlip(ctx, (double)0.5)) { child1_eval = evaluate(ctx, t1, pop2); if ((child1_eval > parent1_eval) && (child1_eval > parent2_eval)) { /* Urk! */ if (PGARandomFlip(ctx, (double)(mutation_rate/base_mutation_rate))) adapt_crossover += adapt(ctx, t1, pop2, 1, 2, 0); else { /* low mr */ if (adapt(ctx, t1, pop2, 1, 1, 0)) adapt_crossover += adapt(ctx, t1, pop2, 0, 2, 1) + 1; adapt_crossover += adapt(ctx, t2, pop2, 0, 2, 0); } } else { PGASetEvaluation(ctx, t1, pop2, child1_eval); PGASetEvaluationUpToDateFlag(ctx, t1, pop2, PGA_TRUE); } } else { child2_eval = evaluate(ctx, t2, pop2); if ((child2_eval > parent1_eval) && (child2_eval > parent2_eval)) { /* Urk! */ if (PGARandomFlip(ctx, (double)(mutation_rate/base_mutation_rate))) adapt_crossover += adapt(ctx, t2, pop2, 1, 2, 0); else { /* low mr */ if (adapt(ctx, t2, pop2, 1, 1, 0)) adapt_crossover += adapt(ctx, t2, pop2, 0, 2, 1) + 1; adapt_crossover += adapt(ctx, t1, pop2, 0, 2, 0); } } else { PGASetEvaluation(ctx, t2, pop2, child2_eval); PGASetEvaluationUpToDateFlag(ctx, t2, pop2, PGA_TRUE); } } } #endif } /***************************************************************************** * CopyString makes a copy of the chromosome at (p1, pop1) and puts it at * * (p2, pop2). * *****************************************************************************/ void CopyString(PGAContext *ctx, int p1, int pop1, int p2, int pop2) { void *d, *s; s = PGAGetIndividual(ctx, p1, pop1)->chrom; d = PGAGetIndividual(ctx, p2, pop2)->chrom; memcpy(d, s, sizeof(double)*num_scores*2); } /***************************************************************************** * DuplicateString compares two chromosomes and returns 1 if they are the * * same and 0 if they are different. * *****************************************************************************/ int DuplicateString(PGAContext *ctx, int p1, int pop1, int p2, int pop2) { void *a, *b; a = PGAGetIndividual(ctx, p1, pop1)->chrom; b = PGAGetIndividual(ctx, p2, pop2)->chrom; return (!memcmp(a, b, sizeof(double)*num_scores*2)); } /***************************************************************************** * BuildDatattype builds an MPI datatype for sending strings to other * * processors. Consult your favorite MPI manual for more information. * *****************************************************************************/ MPI_Datatype BuildDT(PGAContext *ctx, int p, int pop) { MPI_Datatype DT_PGAIndividual; #ifdef USE_MPI int counts[3]; MPI_Aint displs[3]; MPI_Datatype types[3]; PGAIndividual *P; P = PGAGetIndividual(ctx, p, pop); /* Build the MPI datatype. Every user defined function needs these. * The first two calls are stuff that is internal to PGAPack, but * the user still must include it. See pgapack.h for details one the * fields (under PGAIndividual) */ MPI_Address(&P->evalfunc, &displs[0]); counts[0] = 2; types[0] = MPI_DOUBLE; /* Next, we have an integer, evaluptodate. */ MPI_Address(&P->evaluptodate, &displs[1]); counts[1] = 1; types[1] = MPI_INT; /* Finally, we have the actual user-defined string. */ MPI_Address(P->chrom, &displs[2]); counts[2] = num_scores*2; types[2] = MPI_DOUBLE; MPI_Type_struct(3, counts, displs, types, &DT_PGAIndividual); #endif /* defined(USE_MPI) */ MPI_Type_commit(&DT_PGAIndividual); return(DT_PGAIndividual); } #endif /* defined(USE_VARIABLE_MUTATIONS) */