AliSaadatV commited on
Commit
d08bd7c
·
verified ·
1 Parent(s): f3de91a

Upload core.py

Browse files
Files changed (1) hide show
  1. core.py +263 -0
core.py ADDED
@@ -0,0 +1,263 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Core Bio-ACDC optimization loop.
3
+ Manages the coevolution of biological LM populations and sequence tasks.
4
+ """
5
+
6
+ import os
7
+ import json
8
+ import logging
9
+ import numpy as np
10
+ import torch
11
+ from typing import List, Dict, Optional, Tuple, Any
12
+ from dataclasses import dataclass, field
13
+ from pathlib import Path
14
+
15
+ from .archive import BioArchive, BioSolution
16
+ from .tasks import BioTaskPool
17
+ from .evaluator import BioEvaluator
18
+ from .mergers import BioModelMerger
19
+ from .mutators import BioMutator
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ @dataclass
25
+ class BioACDCConfig:
26
+ """Configuration for Bio-ACDC optimization."""
27
+
28
+ # Population settings
29
+ archive_size: int = 50
30
+ num_generations: int = 100
31
+ offspring_per_gen: int = 10
32
+
33
+ # Model settings
34
+ seed_model_paths: List[str] = field(default_factory=list)
35
+ base_model_path: Optional[str] = None
36
+
37
+ # Task settings
38
+ num_tasks_per_eval: int = 20
39
+ task_difficulty_threshold: float = 0.5
40
+
41
+ # Evolution settings
42
+ use_mutation: bool = True
43
+ mutation_rate: float = 0.3
44
+
45
+ # Evaluation settings
46
+ eval_batch_size: int = 8
47
+ max_sequence_length: int = 1024
48
+
49
+ # Output
50
+ output_dir: str = "./bio_acdc_output"
51
+ save_frequency: int = 5
52
+
53
+ # Device
54
+ device: str = "cuda" if torch.cuda.is_available() else "cpu"
55
+
56
+
57
+ class BioACDC:
58
+ """
59
+ Main Bio-ACDC optimization controller.
60
+
61
+ Coevolves a population of biological language models with synthetic
62
+ sequence tasks to discover specialized experts.
63
+ """
64
+
65
+ def __init__(
66
+ self,
67
+ config: BioACDCConfig,
68
+ task_pool: BioTaskPool,
69
+ evaluator: BioEvaluator,
70
+ merger: BioModelMerger,
71
+ mutator: Optional[BioMutator] = None,
72
+ ):
73
+ self.config = config
74
+ self.task_pool = task_pool
75
+ self.evaluator = evaluator
76
+ self.merger = merger
77
+ self.mutator = mutator
78
+
79
+ # Initialize archive
80
+ self.archive = BioArchive(
81
+ archive_size=config.archive_size,
82
+ k_neighbors=5,
83
+ dominated_score=999.0,
84
+ )
85
+
86
+ # Setup output directories
87
+ self.output_dir = Path(config.output_dir)
88
+ self.model_dir = self.output_dir / "models"
89
+ self.archive_dir = self.output_dir / "archives"
90
+ self.task_dir = self.output_dir / "tasks"
91
+ self.eval_dir = self.output_dir / "evaluations"
92
+
93
+ for d in [self.model_dir, self.archive_dir, self.task_dir, self.eval_dir]:
94
+ d.mkdir(parents=True, exist_ok=True)
95
+
96
+ # RNG
97
+ self.rng = np.random.RandomState(42)
98
+
99
+ logger.info(f"Bio-ACDC initialized with config: {config}")
100
+
101
+ def initialize_seed_population(self) -> None:
102
+ """Load and evaluate seed models into the archive."""
103
+ logger.info(f"Initializing seed population from {len(self.config.seed_model_paths)} models")
104
+
105
+ for model_path in self.config.seed_model_paths:
106
+ logger.info(f"Evaluating seed model: {model_path}")
107
+
108
+ # Get current task pool
109
+ tasks = self.task_pool.get_tasks(n=self.config.num_tasks_per_eval)
110
+
111
+ # Evaluate model on tasks
112
+ metrics = self.evaluator.evaluate_model(model_path, tasks)
113
+
114
+ # Create solution
115
+ skill_vector = {t.task_id: metrics.get(t.task_id, 0.0) for t in tasks}
116
+ fitness = np.mean(list(skill_vector.values())) if skill_vector else 0.0
117
+
118
+ solution = BioSolution(
119
+ model_path=model_path,
120
+ fitness=fitness,
121
+ skill_vector=skill_vector,
122
+ generation=0,
123
+ )
124
+
125
+ self.archive.add_solution(solution)
126
+ logger.info(f"Seed model {model_path}: fitness={fitness:.4f}")
127
+
128
+ # Save initial archive
129
+ self._save_archive(0)
130
+
131
+ def evolve(self) -> List[BioSolution]:
132
+ """Run the main optimization loop."""
133
+ logger.info("Starting Bio-ACDC evolution")
134
+
135
+ # Initialize
136
+ self.initialize_seed_population()
137
+
138
+ for generation in range(1, self.config.num_generations + 1):
139
+ logger.info(f"=== Generation {generation}/{self.config.num_generations} ===")
140
+
141
+ # Generate new models
142
+ new_solutions = self._generate_offspring(generation)
143
+
144
+ # Evaluate on current task pool
145
+ for sol in new_solutions:
146
+ tasks = self.task_pool.get_tasks(n=self.config.num_tasks_per_eval)
147
+ metrics = self.evaluator.evaluate_model(sol.model_path, tasks)
148
+
149
+ skill_vector = {t.task_id: metrics.get(t.task_id, 0.0) for t in tasks}
150
+ sol.fitness = np.mean(list(skill_vector.values())) if skill_vector else 0.0
151
+ sol.skill_vector = skill_vector
152
+
153
+ # Update archive with dominated novelty search
154
+ self.archive.update(new_solutions)
155
+
156
+ # Generate new tasks based on archive weaknesses
157
+ self._update_task_pool(generation)
158
+
159
+ # Save checkpoint
160
+ if generation % self.config.save_frequency == 0:
161
+ self._save_archive(generation)
162
+ self._save_tasks(generation)
163
+
164
+ # Log status
165
+ best = self.archive.get_best()
166
+ if best:
167
+ logger.info(f"Generation {generation}: Best fitness={best.fitness:.4f}, Archive size={len(self.archive.solutions)}")
168
+
169
+ # Final save
170
+ self._save_archive(self.config.num_generations)
171
+
172
+ return self.archive.solutions
173
+
174
+ def _generate_offspring(self, generation: int) -> List[BioSolution]:
175
+ """Create new models by merging parents from archive."""
176
+ new_solutions = []
177
+
178
+ for i in range(self.config.offspring_per_gen):
179
+ # Select parents
180
+ if len(self.archive.solutions) >= 2:
181
+ parents = self.rng.choice(
182
+ self.archive.solutions,
183
+ size=2,
184
+ replace=False,
185
+ )
186
+ else:
187
+ # Fallback to seed models
188
+ parents = self.rng.choice(
189
+ self.config.seed_model_paths,
190
+ size=2,
191
+ replace=False,
192
+ )
193
+ parents = [
194
+ BioSolution(model_path=p, fitness=0.0, skill_vector={}, generation=0)
195
+ for p in parents
196
+ ]
197
+
198
+ parent_paths = [p.model_path for p in parents]
199
+
200
+ # Merge
201
+ save_path = str(self.model_dir / f"gen_{generation}_ind_{i}")
202
+
203
+ try:
204
+ merged_path = self.merger.merge(
205
+ parent_paths=parent_paths,
206
+ save_path=save_path,
207
+ )
208
+
209
+ # Mutate if enabled
210
+ if self.config.use_mutation and self.mutator is not None:
211
+ if self.rng.random() < self.config.mutation_rate:
212
+ merged_path = self.mutator.mutate(
213
+ model_path=merged_path,
214
+ save_path=save_path + "_mutated",
215
+ )
216
+
217
+ sol = BioSolution(
218
+ model_path=merged_path,
219
+ fitness=0.0, # Will be evaluated
220
+ skill_vector={},
221
+ generation=generation,
222
+ parent_paths=parent_paths,
223
+ )
224
+ new_solutions.append(sol)
225
+
226
+ except Exception as e:
227
+ logger.error(f"Failed to create offspring {i}: {e}")
228
+ continue
229
+
230
+ return new_solutions
231
+
232
+ def _update_task_pool(self, generation: int) -> None:
233
+ """Generate new tasks targeting archive weaknesses."""
234
+ # Analyze what tasks the archive struggles with
235
+ difficulty_weights = self.archive.compute_difficulty_weights()
236
+
237
+ # Generate new tasks
238
+ new_tasks = self.task_pool.generate_new_tasks(
239
+ archive=self.archive,
240
+ num_tasks=5,
241
+ difficulty_weights=difficulty_weights,
242
+ )
243
+
244
+ # Save generated tasks
245
+ for task in new_tasks:
246
+ task_path = self.task_dir / f"gen_{generation}_{task.task_id}.json"
247
+ with open(task_path, "w") as f:
248
+ json.dump(task.to_dict(), f, indent=2)
249
+
250
+ logger.info(f"Generated {len(new_tasks)} new tasks")
251
+
252
+ def _save_archive(self, generation: int) -> None:
253
+ """Save archive to disk."""
254
+ archive_path = self.archive_dir / f"archive_gen_{generation}.json"
255
+ self.archive.save(archive_path)
256
+ logger.info(f"Archive saved to {archive_path}")
257
+
258
+ def _save_tasks(self, generation: int) -> None:
259
+ """Save current task pool."""
260
+ tasks_path = self.task_dir / f"tasks_gen_{generation}.json"
261
+ tasks_data = [t.to_dict() for t in self.task_pool.tasks]
262
+ with open(tasks_path, "w") as f:
263
+ json.dump(tasks_data, f, indent=2)