|
15 | 15 |
|
16 | 16 | from openevolve.config import Config |
17 | 17 | from openevolve.database import Program, ProgramDatabase |
| 18 | +from openevolve.utils.metrics_utils import safe_numeric_average |
18 | 19 |
|
19 | 20 | logger = logging.getLogger(__name__) |
20 | 21 |
|
@@ -145,8 +146,6 @@ def _run_iteration_worker( |
145 | 146 | ] |
146 | 147 |
|
147 | 148 | # Sort by metrics for top programs |
148 | | - from openevolve.utils.metrics_utils import safe_numeric_average |
149 | | - |
150 | 149 | island_programs.sort( |
151 | 150 | key=lambda p: p.metrics.get("combined_score", safe_numeric_average(p.metrics)), |
152 | 151 | reverse=True, |
@@ -425,6 +424,17 @@ async def run_evolution( |
425 | 424 | # Island management |
426 | 425 | programs_per_island = max(1, max_iterations // (self.config.database.num_islands * 10)) |
427 | 426 | current_island_counter = 0 |
| 427 | + |
| 428 | + # Early stopping tracking |
| 429 | + early_stopping_enabled = self.config.early_stopping_patience is not None |
| 430 | + if early_stopping_enabled: |
| 431 | + best_score = float('-inf') |
| 432 | + iterations_without_improvement = 0 |
| 433 | + logger.info(f"Early stopping enabled: patience={self.config.early_stopping_patience}, " |
| 434 | + f"threshold={self.config.convergence_threshold}, " |
| 435 | + f"metric={self.config.early_stopping_metric}") |
| 436 | + else: |
| 437 | + logger.info("Early stopping disabled") |
428 | 438 |
|
429 | 439 | # Process results as they complete |
430 | 440 | while ( |
@@ -519,8 +529,6 @@ async def run_evolution( |
519 | 529 | "combined_score" not in child_program.metrics |
520 | 530 | and not self._warned_about_combined_score |
521 | 531 | ): |
522 | | - from openevolve.utils.metrics_utils import safe_numeric_average |
523 | | - |
524 | 532 | avg_score = safe_numeric_average(child_program.metrics) |
525 | 533 | logger.warning( |
526 | 534 | f"⚠️ No 'combined_score' metric found in evaluation results. " |
@@ -563,6 +571,40 @@ async def run_evolution( |
563 | 571 | ) |
564 | 572 | break |
565 | 573 |
|
| 574 | + # Check early stopping |
| 575 | + if early_stopping_enabled and child_program.metrics: |
| 576 | + # Get the metric to track for early stopping |
| 577 | + current_score = None |
| 578 | + if self.config.early_stopping_metric in child_program.metrics: |
| 579 | + current_score = child_program.metrics[self.config.early_stopping_metric] |
| 580 | + elif self.config.early_stopping_metric == "combined_score": |
| 581 | + # Default metric not found, use safe average (standard pattern) |
| 582 | + current_score = safe_numeric_average(child_program.metrics) |
| 583 | + else: |
| 584 | + # User specified a custom metric that doesn't exist |
| 585 | + logger.warning(f"Early stopping metric '{self.config.early_stopping_metric}' not found, using safe numeric average") |
| 586 | + current_score = safe_numeric_average(child_program.metrics) |
| 587 | + |
| 588 | + if current_score is not None and isinstance(current_score, (int, float)): |
| 589 | + # Check for improvement |
| 590 | + improvement = current_score - best_score |
| 591 | + if improvement >= self.config.convergence_threshold: |
| 592 | + best_score = current_score |
| 593 | + iterations_without_improvement = 0 |
| 594 | + logger.debug(f"New best score: {best_score:.4f} (improvement: {improvement:+.4f})") |
| 595 | + else: |
| 596 | + iterations_without_improvement += 1 |
| 597 | + logger.debug(f"No improvement: {iterations_without_improvement}/{self.config.early_stopping_patience}") |
| 598 | + |
| 599 | + # Check if we should stop |
| 600 | + if iterations_without_improvement >= self.config.early_stopping_patience: |
| 601 | + logger.info( |
| 602 | + f"Early stopping triggered at iteration {completed_iteration}: " |
| 603 | + f"No improvement for {iterations_without_improvement} iterations " |
| 604 | + f"(best score: {best_score:.4f})" |
| 605 | + ) |
| 606 | + break |
| 607 | + |
566 | 608 | except Exception as e: |
567 | 609 | logger.error(f"Error processing result from iteration {completed_iteration}: {e}") |
568 | 610 |
|
|
0 commit comments