github.com/yaricom/goNEAT@v0.0.0-20210507221059-e2110b885482/experiments/experiment.go (about) 1 package experiments 2 3 import ( 4 "time" 5 "sort" 6 "github.com/yaricom/goNEAT/neat/genetics" 7 "io" 8 "encoding/gob" 9 "fmt" 10 "math" 11 ) 12 13 // An Experiment is a collection of trials for one experiment. It's useful for statistical analysis of a series of 14 // experiments 15 type Experiment struct { 16 Id int 17 Name string 18 Trials 19 // The maximal allowed fitness score as defined by fitness function of experiment. 20 // It is used to normalize fitness score value used in efficiency score calculation. If this value 21 // is not set, than fitness score will not be normalized during efficiency score estimation. 22 MaxFintessScore float64 23 } 24 25 // Calculates average duration of experiment's trial 26 // Note, that most trials finish after solution solved, so this metric can be used to represent how efficient the solvers 27 // was generated 28 func (e *Experiment) AvgTrialDuration() time.Duration { 29 total := time.Duration(0) 30 for _, t := range e.Trials { 31 total += t.Duration 32 } 33 return total / time.Duration(len(e.Trials)) 34 } 35 36 // Calculates average duration of evaluations among all generations of organism populations in this experiment 37 func (e *Experiment) AvgEpochDuration() time.Duration { 38 total := time.Duration(0) 39 for _, t := range e.Trials { 40 total += t.AvgEpochDuration() 41 } 42 return total / time.Duration(len(e.Trials)) 43 } 44 45 // Calculates average number of generations evaluated per trial during this experiment. This can be helpful when estimating 46 // algorithm efficiency, because when winner organism is found the trial is terminated, i.e. less evaluations - more fast 47 // convergence. 48 func (e *Experiment) AvgGenerationsPerTrial() float64 { 49 total := 0.0 50 for _, t := range e.Trials { 51 total += float64(len(t.Generations)) 52 } 53 return total / float64(len(e.Trials)) 54 } 55 56 // Returns time of last trial's execution 57 func (e *Experiment) LastExecuted() time.Time { 58 var u time.Time 59 for _, e := range e.Trials { 60 ut := e.LastExecuted() 61 if u.Before(ut) { 62 u = ut 63 } 64 } 65 return u 66 } 67 68 // Finds the most fit organism among all epochs in this trial. It's also possible to get the best organism only among the ones 69 // which was able to solve the experiment's problem. Returns the best fit organism in this experiment among with ID of trial 70 // where it was found and boolean value to indicate if search was successful. 71 func (e *Experiment) BestOrganism(onlySolvers bool) (*genetics.Organism, int, bool) { 72 var orgs = make(genetics.Organisms, 0, len(e.Trials)) 73 for i, t := range e.Trials { 74 org, found := t.BestOrganism(onlySolvers) 75 if found { 76 orgs = append(orgs, org) 77 org.Flag = i 78 } 79 80 } 81 if len(orgs) > 0 { 82 sort.Sort(sort.Reverse(orgs)) 83 return orgs[0], orgs[0].Flag, true 84 } else { 85 return nil, -1, false 86 } 87 88 } 89 90 func (e *Experiment) Solved() bool { 91 for _, t := range e.Trials { 92 if t.Solved() { 93 return true 94 } 95 } 96 return false 97 } 98 99 // The fitness values of the best organisms for each trial 100 func (e *Experiment) BestFitness() Floats { 101 var x Floats = make([]float64, len(e.Trials)) 102 for i, t := range e.Trials { 103 if org, ok := t.BestOrganism(false); ok { 104 x[i] = org.Fitness 105 } 106 } 107 return x 108 } 109 110 // The age values of the organisms for each trial 111 func (e *Experiment) BestAge() Floats { 112 var x Floats = make([]float64, len(e.Trials)) 113 for i, t := range e.Trials { 114 if org, ok := t.BestOrganism(false); ok { 115 x[i] = float64(org.Species.Age) 116 } 117 } 118 return x 119 } 120 121 // The complexity values of the best organisms for each trial 122 func (e *Experiment) BestComplexity() Floats { 123 var x Floats = make([]float64, len(e.Trials)) 124 for i, t := range e.Trials { 125 if org, ok := t.BestOrganism(false); ok { 126 x[i] = float64(org.Phenotype.Complexity()) 127 } 128 } 129 return x 130 } 131 132 // Diversity returns the average number of species in each trial 133 func (e *Experiment) Diversity() Floats { 134 var x Floats = make([]float64, len(e.Trials)) 135 for i, t := range e.Trials { 136 x[i] = t.Diversity().Mean() 137 } 138 return x 139 } 140 141 // Trials returns the number of epochs in each trial 142 func (e *Experiment) Epochs() Floats { 143 var x Floats = make([]float64, len(e.Trials)) 144 for i, t := range e.Trials { 145 x[i] = float64(len(t.Generations)) 146 } 147 return x 148 } 149 150 // The number of trials solved 151 func (e *Experiment) TrialsSolved() int { 152 count := 0 153 for _, t := range e.Trials { 154 if t.Solved() { 155 count++ 156 } 157 } 158 return count 159 } 160 161 // The success rate 162 func (e *Experiment) SuccessRate() float64 { 163 soved := float64(e.TrialsSolved()) 164 return soved / float64(len(e.Trials)) 165 } 166 167 // Returns average number of nodes, genes, organisms evaluations, and species diversity of winner genomes among all 168 // trials, i.e. for all trials where winning solution was found 169 func (e *Experiment) AvgWinner() (avg_nodes, avg_genes, avg_evals, avg_diversity float64) { 170 total_nodes, total_genes, total_evals, total_diversity := 0, 0, 0, 0 171 count := 0 172 for i := 0; i < len(e.Trials); i++ { 173 t := e.Trials[i] 174 if t.Solved() { 175 nodes, genes, evals, diversity := t.Winner() 176 total_nodes += nodes 177 total_genes += genes 178 total_evals += evals 179 total_diversity += diversity 180 181 count++ 182 } 183 } 184 avg_nodes = float64(total_nodes) / float64(count) 185 avg_genes = float64(total_genes) / float64(count) 186 avg_evals = float64(total_evals) / float64(count) 187 avg_diversity = float64(total_diversity) / float64(count) 188 return avg_nodes, avg_genes, avg_evals, avg_diversity 189 } 190 191 // Calculates the efficiency score of the solution 192 // We are interested in efficient solver search solution that take 193 // less time per epoch, less generations per trial, and produce less complicated winner genomes. 194 // At the same time it should have maximal fitness score and maximal success rate among trials. 195 func (ex *Experiment) EfficiencyScore() float64 { 196 mean_complexity, mean_fitness := 0.0, 0.0 197 if len(ex.Trials) > 1 { 198 count := 0.0 199 for i := 0; i < len(ex.Trials); i++ { 200 t := ex.Trials[i] 201 if t.Solved() { 202 if t.WinnerGeneration == nil { 203 // find winner 204 t.Winner() 205 } 206 207 mean_complexity += float64(t.WinnerGeneration.Best.Phenotype.Complexity()) 208 mean_fitness += t.WinnerGeneration.Best.Fitness 209 210 count++ 211 } 212 } 213 mean_complexity /= count 214 mean_fitness /= count 215 } 216 217 // normalize and scale fitness score if appropriate 218 fitness_score := mean_fitness 219 if ex.MaxFintessScore > 0 { 220 fitness_score /= ex.MaxFintessScore 221 fitness_score *= 100 222 } 223 224 score := ex.AvgEpochDuration().Seconds() * 1000.0 * ex.AvgGenerationsPerTrial() * mean_complexity 225 if score > 0 { 226 score = ex.SuccessRate() * fitness_score / math.Log(score) 227 } 228 229 return score 230 } 231 232 // Prints experiment statistics 233 func (ex *Experiment) PrintStatistics() { 234 fmt.Printf("\nSolved %d trials from %d, success rate: %f\n", ex.TrialsSolved(), len(ex.Trials), ex.SuccessRate()) 235 fmt.Printf("Average\n\tTrial duration:\t\t%s\n\tEpoch duration:\t\t%s\n\tGenerations/trial:\t%.1f\n", 236 ex.AvgTrialDuration(), ex.AvgEpochDuration(), ex.AvgGenerationsPerTrial()) 237 // Print absolute champion statistics 238 if org, trid, found := ex.BestOrganism(true); found { 239 nodes, genes, evals, divers := ex.Trials[trid].Winner() 240 fmt.Printf("\nChampion found in %d trial run\n\tWinner Nodes:\t\t%d\n\tWinner Genes:\t\t%d\n\tWinner Evals:\t\t%d\n\n\tDiversity:\t\t%d", 241 trid, nodes, genes, evals, divers) 242 fmt.Printf("\n\tComplexity:\t\t%d\n\tAge:\t\t\t%d\n\tFitness:\t\t%f\n", 243 org.Phenotype.Complexity(), org.Species.Age, org.Fitness) 244 } else { 245 fmt.Println("\nNo winner found in the experiment!!!") 246 } 247 248 // Print average winner statistics 249 mean_complexity, mean_diversity, mean_age, mean_fitness := 0.0, 0.0, 0.0, 0.0 250 if len(ex.Trials) > 1 { 251 avg_nodes, avg_genes, avg_evals, avg_divers, avg_generations := 0.0, 0.0, 0.0, 0.0, 0.0 252 count := 0.0 253 for i := 0; i < len(ex.Trials); i++ { 254 t := ex.Trials[i] 255 256 if t.Solved() { 257 nodes, genes, evals, diversity := t.Winner() 258 avg_nodes += float64(nodes) 259 avg_genes += float64(genes) 260 avg_evals += float64(evals) 261 avg_divers += float64(diversity) 262 avg_generations += float64(len(t.Generations)) 263 264 mean_complexity += float64(t.WinnerGeneration.Best.Phenotype.Complexity()) 265 mean_age += float64(t.WinnerGeneration.Best.Species.Age) 266 mean_fitness += t.WinnerGeneration.Best.Fitness 267 268 count++ 269 270 // update trials array 271 ex.Trials[i] = t 272 } 273 } 274 avg_nodes /= count 275 avg_genes /= count 276 avg_evals /= count 277 avg_divers /= count 278 avg_generations /= count 279 fmt.Printf("\nAverage among winners\n\tWinner Nodes:\t\t%.1f\n\tWinner Genes:\t\t%.1f\n\tWinner Evals:\t\t%.1f\n\tGenerations/trial:\t%.1f\n\n\tDiversity:\t\t%f\n", 280 avg_nodes, avg_genes, avg_evals, avg_generations, avg_divers) 281 282 mean_complexity /= count 283 mean_age /= count 284 mean_fitness /= count 285 fmt.Printf("\tComplexity:\t\t%f\n\tAge:\t\t\t%f\n\tFitness:\t\t%f\n", 286 mean_complexity, mean_age, mean_fitness) 287 } 288 289 // Print the average values for each population of organisms evaluated 290 count := float64(len(ex.Trials)) 291 for _, t := range ex.Trials { 292 fitness, age, complexity := t.Average() 293 294 mean_complexity += complexity.Mean() 295 mean_diversity += t.Diversity().Mean() 296 mean_age += age.Mean() 297 mean_fitness += fitness.Mean() 298 } 299 mean_complexity /= count 300 mean_diversity /= count 301 mean_age /= count 302 mean_fitness /= count 303 fmt.Printf("\nAverages for all organisms evaluated during experiment\n\tDiversity:\t\t%f\n\tComplexity:\t\t%f\n\tAge:\t\t\t%f\n\tFitness:\t\t%f\n", 304 mean_diversity, mean_complexity, mean_age, mean_fitness) 305 306 score := ex.EfficiencyScore() 307 fmt.Printf("\nEfficiency score:\t\t%f\n\n", score) 308 } 309 310 // Encodes experiment and writes to provided writer 311 func (ex *Experiment) Write(w io.Writer) error { 312 enc := gob.NewEncoder(w) 313 return ex.Encode(enc) 314 } 315 316 // Encodes experiment with GOB encoding 317 func (ex *Experiment) Encode(enc *gob.Encoder) error { 318 err := enc.Encode(ex.Id) 319 err = enc.Encode(ex.Name) 320 321 // encode trials 322 err = enc.Encode(len(ex.Trials)) 323 for _, t := range ex.Trials { 324 err = t.Encode(enc) 325 if err != nil { 326 return err 327 } 328 } 329 return err 330 } 331 332 // Reads experiment data from provided reader and decodes it 333 func (ex *Experiment) Read(r io.Reader) error { 334 dec := gob.NewDecoder(r) 335 return ex.Decode(dec) 336 } 337 338 // Decodes experiment data 339 func (ex *Experiment) Decode(dec *gob.Decoder) error { 340 err := dec.Decode(&ex.Id) 341 err = dec.Decode(&ex.Name) 342 343 // decode Trials 344 var t_num int 345 err = dec.Decode(&t_num) 346 if err != nil { 347 return err 348 } 349 350 ex.Trials = make([]Trial, t_num) 351 for i := 0; i < t_num; i++ { 352 trial := Trial{} 353 err = trial.Decode(dec) 354 ex.Trials[i] = trial 355 } 356 return err 357 } 358 359 // Experiments is a sortable list of experiments by execution time and Id 360 type Experiments []Experiment 361 362 func (es Experiments) Len() int { 363 return len(es) 364 } 365 func (es Experiments) Swap(i, j int) { 366 es[i], es[j] = es[j], es[i] 367 } 368 func (es Experiments) Less(i, j int) bool { 369 ui := es[i].LastExecuted() 370 uj := es[j].LastExecuted() 371 if ui.Equal(uj) { 372 return es[i].Id < es[j].Id 373 } 374 return ui.Before(uj) 375 }