Package comocma :: Module sofomore_logger
[hide private]
[frames] | no frames]

Source Code for Module comocma.sofomore_logger

  1  #!/usr/bin/env python3 
  2  # -*- coding: utf-8 -*- 
  3   
  4  import numpy as np 
  5  import cma 
  6  from cma import interfaces 
  7  import os 
  8  import matplotlib.pyplot as plt 
  9  import ast 
 10  import warnings 
 11   
12 -class SofomoreDataLogger(interfaces.BaseDataLogger):
13 """data logger for class `CMAEvolutionStrategy`. 14 15 The logger is identified by its name prefix and (over-)writes or 16 reads according data files. Therefore, the logger must be 17 considered as *global* variable with unpredictable side effects, 18 if two loggers with the same name and on the same working folder 19 are used at the same time. 20 21 Examples 22 ======== 23 :: 24 25 import cma 26 es = cma.CMAEvolutionStrategy(...) 27 logger = cma.CMADataLogger().register(es) 28 while not es.stop(): 29 ... 30 logger.add() # add can also take an argument 31 32 logger.plot() # or a short cut can be used: 33 cma.plot() # plot data from logger with default name 34 35 logger2 = cma.CMADataLogger('just_another_filename_prefix').load() 36 logger2.plot() 37 logger2.disp() 38 39 import cma 40 from matplotlib.pylab import * 41 res = cma.fmin(cma.ff.sphere, rand(10), 1e-0) 42 logger = res[-1] # the CMADataLogger 43 logger.load() # by "default" data are on disk 44 semilogy(logger.f[:,0], logger.f[:,5]) # plot f versus iteration, see file header 45 cma.s.figshow() 46 47 Details 48 ======= 49 After loading data, the logger has the attributes `xmean`, `xrecent`, 50 `std`, `f`, `D` and `corrspec` corresponding to ``xmean``, 51 ``xrecentbest``, ``stddev``, ``fit``, ``axlen`` and ``axlencorr`` 52 filename trails. 53 54 :See: `disp` (), `plot` () 55 """ 56 default_prefix = 'outsofomore' + os.sep 57 # default_prefix = 'outcmaes' 58 # names = ('axlen','fit','stddev','xmean','xrecentbest') 59 # key_names_with_annotation = ('std', 'xmean', 'xrecent') 60
61 - def __init__(self, name_prefix=default_prefix, modulo=1, append=False):
62 """initialize logging of data from a `CMAEvolutionStrategy` 63 instance, default ``modulo=1`` means logging with each call 64 65 """ 66 # super(CMAData, self).__init__({'iter':[], 'stds':[], 'D':[], 67 # 'sig':[], 'fit':[], 'xm':[]}) 68 # class properties: 69 # if isinstance(name_prefix, CMAEvolutionStrategy): 70 # name_prefix = name_prefix.opts.eval('verb_filenameprefix') 71 if name_prefix is None: 72 name_prefix = SofomoreDataLogger.default_prefix 73 self.name_prefix = os.path.abspath(os.path.join(*os.path.split(name_prefix))) 74 if name_prefix is not None and name_prefix.endswith((os.sep, '/')): 75 self.name_prefix = self.name_prefix + os.sep 76 self.file_names = ('hypervolume', 'hypervolume_archive', 'len_archive', 'ratio_active_kernel', 77 'ratio_nondom_incumb', 'ratio_nondom_offsp_incumb', 78 'median_sigmas', 'median_axis_ratios', 'median_min_stds', 79 'median_max_stds', 'median_stds') 80 self.modulo = modulo 81 """how often to record data, allows calling `add` without args""" 82 self.append = append 83 """append to previous data""" 84 self.counter = 0 85 """number of calls to `add`""" 86 self.last_iteration = 0 87 self.registered = False 88 self.persistent_communication_dict = cma.utilities.utils.DictFromTagsInString()
89
90 - def register(self, es, append=None, modulo=None):
91 """register a `Sofomore` instance for logging, 92 ``append=True`` appends to previous data logged under the same name, 93 by default previous data are overwritten. 94 95 """ 96 # if not isinstance(es, CMAEvolutionStrategy): 97 # utils.print_warning("""only class CMAEvolutionStrategy should 98 # be registered for logging. The used "%s" class may not to work 99 # properly. This warning may also occur after using `reload`. Then, 100 # restarting Python should solve the issue.""" % 101 # str(type(es))) 102 self.es = es 103 if append is not None: 104 self.append = append 105 if modulo is not None: 106 self.modulo = modulo 107 self.registered = True 108 return self
109
110 - def initialize(self, modulo=None):
111 """reset logger, overwrite original files, `modulo`: log only every modulo call""" 112 if modulo is not None: 113 self.modulo = modulo 114 try: 115 es = self.es # must have been registered 116 except AttributeError: 117 pass # TODO: revise usage of es... that this can pass 118 raise AttributeError('call register() before initialize()') 119 120 self.counter = 0 # number of calls of add 121 self.last_iteration = 0 # some lines are only written if iteration>last_iteration 122 if self.modulo <= 0: 123 return self 124 125 # create path if necessary 126 if os.path.dirname(self.name_prefix): 127 try: 128 os.makedirs(os.path.dirname(self.name_prefix)) 129 except OSError: 130 pass # folder exists 131 132 # write headers for output 133 134 fn = self.name_prefix + 'median_sigmas.dat' 135 try: 136 with open(fn, 'w') as f: 137 f.write('% # columns="iteration, evaluation, median sigmas, ' + 138 ', ' + 139 '\n') 140 except (IOError, OSError): 141 print('could not open file ' + fn) 142 143 fn = self.name_prefix + 'median_axis_ratios.dat' 144 try: 145 with open(fn, 'w') as f: 146 f.write('% # columns="iteration, evaluation, median axis ratios, ' + 147 ', ' + 148 '\n') 149 except (IOError, OSError): 150 print('could not open file ' + fn) 151 152 fn = self.name_prefix + 'median_min_stds.dat' 153 try: 154 with open(fn, 'w') as f: 155 f.write('% # columns="iteration, evaluation, median min stds, ' + 156 ', ' + 157 '\n') 158 except (IOError, OSError): 159 print('could not open file ' + fn) 160 161 fn = self.name_prefix + 'median_max_stds.dat' 162 try: 163 with open(fn, 'w') as f: 164 f.write('% # columns="iteration, evaluation, median max stds, ' + 165 ', ' + 166 '\n') 167 except (IOError, OSError): 168 print('could not open file ' + fn) 169 170 fn = self.name_prefix + 'median_stds.dat' 171 try: 172 with open(fn, 'w') as f: 173 f.write('% # columns="iteration, evaluation, median stds, ' + 174 '\n') 175 except (IOError, OSError): 176 print('could not open file ' + fn) 177 178 fn = self.name_prefix + 'hypervolume.dat' 179 try: 180 with open(fn, 'w') as f: 181 f.write('% # columns="iteration, evaluation, hypervolume, ' + 182 ', ' + 183 '\n') 184 except (IOError, OSError): 185 print('could not open file ' + fn) 186 187 fn = self.name_prefix + 'hypervolume_archive.dat' 188 try: 189 with open(fn, 'w') as f: 190 f.write('% # columns="iteration, evaluation, hypervolume of archive' + 191 '\n') 192 except (IOError, OSError): 193 print('could not open/write file ' + fn) 194 195 196 fn = self.name_prefix + 'len_archive.dat' 197 try: 198 with open(fn, 'w') as f: 199 f.write('% # columns="iter, evals, length archive" ' + 200 ', ' + 201 '\n') 202 except (IOError, OSError): 203 print('could not open/write file ' + fn) 204 205 fn = self.name_prefix + 'ratio_inactive_kernels.dat' 206 try: 207 with open(fn, 'w') as f: 208 f.write('% # columns="iteration, evaluation, ratio active kernels, ' + 209 '\n') 210 except (IOError, OSError): 211 print('could not open/write file ' + fn) 212 213 fn = self.name_prefix + 'ratio_nondom_incumb.dat' 214 try: 215 with open(fn, 'w') as f: 216 f.write('% # columns="iteration, evaluation, ratio nondominated incumbents, ' + 217 '\n') 218 except (IOError, OSError): 219 print('could not open/write file ' + fn) 220 221 222 fn = self.name_prefix + 'ratio_nondom_offsp_incumb.dat' 223 try: 224 with open(fn, 'w') as f: 225 f.write('% # columns="iter, evals, first quartile nondom ' + 226 'offspring and incumbent, median nondom offspring and ' + 227 'incumbent, last quartile nondom offspring and incumbent' + 228 ', ' + 229 '\n') 230 except (IOError, OSError): 231 print('could not open/write file ' + fn) 232 233 return self
234 # end def __init__ 235
236 - def add(self, es=None, more_data=(), modulo=None):
237 """append some logging data from `Sofomore` class instance `es`, 238 if ``number_of_times_called % modulo`` equals to zero, never if ``modulo==0``. 239 240 ``more_data`` is a list of additional data to be recorded where each 241 data entry must have the same length. 242 243 When used for a different optimizer class, this function can be 244 (easily?) adapted by changing the assignments under INTERFACE 245 in the implemention. 246 247 """ 248 mod = modulo if modulo is not None else self.modulo 249 self.counter += 1 250 if mod == 0 or (self.counter > 3 and (self.counter - 1) % mod): 251 return 252 if es is None: 253 try: 254 es = self.es # must have been registered 255 except AttributeError : 256 raise AttributeError('call `add` with argument `es` or ``register(es)`` before ``add()``') 257 elif not self.registered: 258 self.register(es) 259 260 if self.counter == 1 and not self.append and self.modulo != 0: 261 self.initialize() # write file headers 262 self.counter = 1 263 264 # --- INTERFACE, can be changed if necessary --- 265 # if not isinstance(es, CMAEvolutionStrategy): # not necessary 266 # utils.print_warning('type CMAEvolutionStrategy expected, found ' 267 # + str(type(es)), 'add', 'CMADataLogger') 268 evals = es.countevals 269 iteration = es.countiter 270 hypervolume = float(es.pareto_front_cut.hypervolume) 271 hypervolume_archive = 0.0 272 len_archive = 0 273 if es.isarchive: 274 hypervolume_archive = float(es.archive.hypervolume) 275 len_archive = len(es.archive) 276 ratio_inactive = 1 - len(es._active_indices) / len(es) 277 ratio_nondom_incumbent = len(es.pareto_front_cut)/len(es) 278 279 for i in range(len(es.offspring)): 280 idx = es.offspring[i][0] 281 kernel = es.kernels[idx] 282 283 temp_archive = es.NDA(kernel._last_offspring_f_values, es.reference_point) 284 temp_archive.add(kernel.objective_values) 285 es._ratio_nondom_offspring_incumbent[idx] = len(temp_archive) / ( 286 1 + len(kernel._last_offspring_f_values) ) 287 288 289 first_quartile_ratio_offspring_incumbent = np.percentile(es._ratio_nondom_offspring_incumbent, 25); 290 median_ratio_offspring_incumbent = np.percentile(es._ratio_nondom_offspring_incumbent, 50); 291 last_quartile_ratio_offspring_incumbent = np.percentile(es._ratio_nondom_offspring_incumbent, 75); 292 293 294 median_axis_ratios = np.median([kernel.D.max() / kernel.D.min() \ 295 if not kernel.opts['CMA_diagonal'] or kernel.countiter > kernel.opts['CMA_diagonal'] 296 else max(kernel.sigma_vec*1) / min(kernel.sigma_vec*1) for kernel in es.kernels]) 297 median_sigmas = np.median([kernel.sigma for kernel in es.kernels]) 298 median_min_stds = np.median([kernel.sigma * min(kernel.sigma_vec * kernel.dC**0.5) \ 299 for kernel in es.kernels]) 300 median_max_stds = np.median([kernel.sigma * max(kernel.sigma_vec * kernel.dC**0.5) \ 301 for kernel in es.kernels]) 302 median_stds = self.es.median_stds 303 304 305 # --- end interface --- 306 307 try: 308 # median axis ratios 309 fn = self.name_prefix + 'median_axis_ratios.dat' 310 with open(fn, 'a') as f: 311 f.write(str(iteration) + ' ' 312 + str(evals) + ' ' 313 + str(median_axis_ratios) 314 + '\n') 315 316 # median sigmas 317 fn = self.name_prefix + 'median_sigmas.dat' 318 with open(fn, 'a') as f: 319 f.write(str(iteration) + ' ' 320 + str(evals) + ' ' 321 + str(median_sigmas) 322 + '\n') 323 324 # median min stds 325 fn = self.name_prefix + 'median_min_stds.dat' 326 with open(fn, 'a') as f: 327 f.write(str(iteration) + ' ' 328 + str(evals) + ' ' 329 + str(median_min_stds) 330 + '\n') 331 332 # median max stds 333 fn = self.name_prefix + 'median_max_stds.dat' 334 with open(fn, 'a') as f: 335 f.write(str(iteration) + ' ' 336 + str(evals) + ' ' 337 + str(median_max_stds) 338 + '\n') 339 340 # median stds 341 fn = self.name_prefix + 'median_stds.dat' 342 with open(fn, 'a') as f: 343 f.write(str(iteration) + ' ' 344 + str(evals) + ' ' 345 + str(median_stds) 346 + '\n') 347 # hypervolume 348 if iteration > self.last_iteration: 349 fn = self.name_prefix + 'hypervolume.dat' 350 with open(fn, 'a') as f: 351 f.write(str(iteration) + ' ' 352 + str(evals) + ' ' 353 + str(hypervolume) + ' ' 354 + '\n') 355 356 # hypervolume archive 357 if iteration > self.last_iteration: 358 fn = self.name_prefix + 'hypervolume_archive.dat' 359 with open(fn, 'a') as f: 360 f.write(str(iteration) + ' ' 361 + str(evals) + ' ' 362 + str(hypervolume_archive) 363 + '\n') 364 365 # length archive 366 if iteration > self.last_iteration: 367 fn = self.name_prefix + 'len_archive.dat' 368 with open(fn, 'a') as f: 369 f.write(str(iteration) + ' ' 370 + str(evals) + ' ' 371 + str(len_archive) 372 + '\n') 373 # ratio of inactive kernels 374 if iteration > self.last_iteration: 375 fn = self.name_prefix + 'ratio_inactive_kernels.dat' 376 with open(fn, 'a') as f: 377 f.write(str(iteration) + ' ' 378 + str(evals) + ' ' 379 + str(ratio_inactive) 380 + '\n') 381 # ratio of non dominated incumbents 382 if iteration > self.last_iteration: 383 fn = self.name_prefix + 'ratio_nondom_incumb.dat' 384 with open(fn, 'a') as f: 385 f.write(str(iteration) + ' ' 386 + str(evals) + ' ' 387 + str(ratio_nondom_incumbent) 388 + '\n') 389 # ratio of nondominated [incumbent + its offspring] 390 if iteration > self.last_iteration: 391 fn = self.name_prefix + 'ratio_nondom_offsp_incumb.dat' 392 with open(fn, 'a') as f: 393 f.write(str(iteration) + ' ' 394 + str(evals) + ' ' 395 + str(first_quartile_ratio_offspring_incumbent) + ' ' 396 + str(median_ratio_offspring_incumbent) + ' ' 397 + str(last_quartile_ratio_offspring_incumbent) 398 + '\n') 399 400 401 except (IOError, OSError): 402 pass 403 404 self.last_iteration = iteration
405 406
407 - def load(self, filenames):
408 """ 409 """ 410 iteration = [] 411 countevals = [] 412 if isinstance(filenames, str): 413 filenames = [filenames] 414 res = [] 415 for i in range(len(filenames)): 416 filename = filenames[i] 417 with open(filename) as f: 418 tab = [line.rstrip() for line in f.readlines()[1:]] #the first 419 # line of our file is a headline 420 maxsplit = 2 if filename[-15:] == 'median_stds.dat' else -1 421 newtab = [list(map(ast.literal_eval,line.split(maxsplit = maxsplit))) for line in tab] 422 length = len(newtab[0]) 423 for k in range(2, length): 424 res += [np.array([line[k] for line in newtab])] 425 426 if i == 0: # we define iteration, countevals just for the first filename 427 iteration = np.array([line[0] for line in newtab]) 428 countevals = np.array([line[1] for line in newtab]) 429 430 return iteration, countevals, res
431
432 - def plot(self, filename, x_iteration = 0):
433 """ 434 """ 435 iteration, countevals, res = self.load(filename) 436 for i in range(len(res)): 437 if not x_iteration: 438 plt.plot(countevals, res) 439 else: 440 plt.plot(iteration, res)
441
442 - def plot_front(self, aspect=None):
443 """ 444 """ 445 if aspect is not None: 446 myaxes = plt.gca() 447 try: 448 myaxes.set_aspect(aspect) # usually, aspect = 'equal' 449 except: 450 pass 451 moes = self.es 452 try: 453 plt.plot([u[0] for u in moes.archive], [u[1] for u in moes.archive], '.', 454 label = "archive") 455 except: 456 pass 457 plt.plot([u[0] for u in moes.pareto_front_cut], [u[1] for u in moes.pareto_front_cut], 'o', 458 label = "cma-es incumbents") 459 pass
460 # plt.legend()
461 - def plot_ratios(self, iabscissa=1):
462 463 """ 464 """ 465 466 # also put tolx/median(max_stds) 467 468 from matplotlib import pyplot 469 fn_incumbent = self.name_prefix + 'ratio_nondom_incumb.dat' 470 fn_inactive = self.name_prefix + 'ratio_inactive_kernels.dat' 471 fn_nondom = self.name_prefix + 'ratio_nondom_offsp_incumb.dat' 472 filenames = fn_incumbent, fn_inactive, fn_nondom 473 iteration, countevals, res = self.load(filenames) 474 absciss = countevals if iabscissa else iteration 475 self._enter_plotting() 476 # color = iter(pyplot.cm.plasma_r(np.linspace(0.35, 1, 3))) 477 self._xlabel(iabscissa) 478 mylabel = ['ratio nondom incumbents', 'ratio inactive kernels', 479 '1st quartile ratio nondom off+incumb', 480 'median ratio nondom off+incumb', 481 '3rd quartile ratio nondom off+incumb'] 482 for i in range(5): 483 pyplot.plot(absciss, res[i], label = mylabel[i]) 484 # pyplot.plot(absciss, res[i], 485 # '-', color=next(color), label = mylabel[i]) 486 # pyplot.semilogy(absciss, res[i], 487 # '-', color=next(color), label = mylabel[i]) 488 # pyplot.hold(True) 489 pyplot.grid(True) 490 ax = np.array(pyplot.axis()) 491 # ax[1] = max(minxend, ax[1]) 492 pyplot.axis(ax) 493 # pyplot.title('') 494 pyplot.legend() 495 # pyplot.xticks(xticklocs) 496 self._xlabel(iabscissa) 497 self._finalize_plotting() 498 return self
499
500 - def plot_divers(self, iabscissa=1):
501 """ 502 """ 503 # also put tolx/median(max_stds) 504 505 from matplotlib import pyplot 506 fn_axis_ratios = self.name_prefix + 'median_axis_ratios.dat' 507 fn_max_stds = self.name_prefix + 'median_max_stds.dat' 508 fn_min_stds = self.name_prefix + 'median_min_stds.dat' 509 fn_sigmas = self.name_prefix + 'median_sigmas.dat' 510 fn_hypervolume = self.name_prefix + 'hypervolume.dat' 511 fn_archive = self.name_prefix + 'hypervolume_archive.dat' 512 fn_len_archive = self.name_prefix + 'len_archive.dat' 513 514 # we call `self.load` twice: for the median-related files and for the 515 # hypervolume related ones: because the iteration and countevals might 516 # be different, depending on the value of `iteration > self.last_iteration` 517 # inside the `add` method. 518 filenames_median = (fn_axis_ratios, fn_max_stds, fn_min_stds, fn_sigmas) 519 (iteration_median, countevals_median, 520 res_median) = self.load(filenames_median) 521 absciss_median = countevals_median if iabscissa else iteration_median 522 523 filenames_hypervolume = (fn_hypervolume, fn_archive, fn_len_archive) 524 (iteration_hypervolume, countevals_hypervolume, 525 res_hypervolume) = self.load(filenames_hypervolume) 526 absciss_hypervolume = (countevals_hypervolume if iabscissa 527 else iteration_hypervolume) 528 529 self._enter_plotting() 530 # color = iter(pyplot.cm.plasma_r(np.linspace(0.35, 1, 3))) 531 self._xlabel(iabscissa) 532 mylabel = ['median axis ratios', 'median max stds', 533 'median min stds', 'median sigmas', 534 'convergence gap', 'archive gap', 'inverse length archive'] 535 for i in range(4): 536 pyplot.semilogy(absciss_median, res_median[i], label = mylabel[i]) 537 # pyplot.plot(absciss, res[i], 538 # '-', color=next(color), label = mylabel[i]) 539 # pyplot.semilogy(absciss, res[i], 540 # '-', color=next(color), label = mylabel[i]) 541 # pyplot.hold(True) 542 offset_convergence_gap = self.es.best_hypervolume_pareto_front 543 pyplot.semilogy(absciss_hypervolume, [offset_convergence_gap - u 544 for u in res_hypervolume[0]], 545 label = mylabel[4], nonposy = 'clip') 546 current_archive = res_hypervolume[1] 547 try: 548 offset_archive_gap = current_archive[-1] 549 pyplot.semilogy(absciss_hypervolume, 550 [offset_archive_gap - u for u in current_archive], 551 label = mylabel[5], nonposy = 'clip') 552 except IndexError: 553 warnings.warn("empty archive") 554 pyplot.semilogy(absciss_hypervolume, [1/u for u in res_hypervolume[2]], 555 label = mylabel[6]) 556 pyplot.grid(True) 557 ax = np.array(pyplot.axis()) 558 # ax[1] = max(minxend, ax[1]) 559 pyplot.axis(ax) 560 # pyplot.title('') 561 pyplot.legend() 562 # pyplot.xticks(xticklocs) 563 self._xlabel(iabscissa) 564 self._finalize_plotting() 565 return self
566 567
568 - def plot_stds(self, iabscissa=1):
569 570 """ 571 """ 572 573 from matplotlib import pyplot 574 filename = self.name_prefix + 'median_stds.dat' 575 iteration, countevals, res = self.load(filename) 576 absciss = countevals if iabscissa else iteration 577 self._enter_plotting() 578 # color = iter(pyplot.cm.plasma_r(np.linspace(0.35, 1, 3))) 579 self._xlabel(iabscissa) 580 581 pyplot.semilogy(absciss, res[0]) 582 # pyplot.plot(absciss, res[i], 583 # '-', color=next(color), label = mylabel[i]) 584 # pyplot.semilogy(absciss, res[i], 585 # '-', color=next(color), label = mylabel[i]) 586 # pyplot.hold(True) 587 pyplot.grid(True) 588 ax = np.array(pyplot.axis()) 589 # ax[1] = max(minxend, ax[1]) 590 pyplot.axis(ax) 591 # pyplot.title('') 592 # pyplot.legend() 593 # pyplot.xticks(xticklocs) 594 self._xlabel(iabscissa) 595 pyplot.title("median (sorted) standard deviations in all coordinates") 596 self._finalize_plotting() 597 return self
598
599 - def _enter_plotting(self, fontsize=7):
600 """assumes that a figure is open """ 601 from matplotlib import pyplot 602 # interactive_status = matplotlib.is_interactive() 603 self.original_fontsize = pyplot.rcParams['font.size'] 604 # if font size deviates from default, we assume this is on purpose and hence leave it alone 605 if pyplot.rcParams['font.size'] == pyplot.rcParamsDefault['font.size']: 606 pyplot.rcParams['font.size'] = fontsize 607 # was: pyplot.hold(False) 608 # pyplot.gcf().clear() # opens a figure window, if non exists 609 pyplot.ioff()
610 - def _finalize_plotting(self):
611 from matplotlib import pyplot 612 pyplot.subplots_adjust(left=0.05, top=0.96, bottom=0.07, right=0.95) 613 # pyplot.tight_layout(rect=(0, 0, 0.96, 1)) 614 pyplot.draw() # update "screen" 615 pyplot.ion() # prevents that the execution stops after plotting 616 pyplot.show() 617 pyplot.rcParams['font.size'] = self.original_fontsize
618 - def _xlabel(self, iabscissa=1):
619 from matplotlib import pyplot 620 pyplot.xlabel('iterations' if iabscissa == 0 621 else 'function evaluations')
622