morphml 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of morphml might be problematic. Click here for more details.
- morphml/__init__.py +14 -0
- morphml/api/__init__.py +26 -0
- morphml/api/app.py +326 -0
- morphml/api/auth.py +193 -0
- morphml/api/client.py +338 -0
- morphml/api/models.py +132 -0
- morphml/api/rate_limit.py +192 -0
- morphml/benchmarking/__init__.py +36 -0
- morphml/benchmarking/comparison.py +430 -0
- morphml/benchmarks/__init__.py +56 -0
- morphml/benchmarks/comparator.py +409 -0
- morphml/benchmarks/datasets.py +280 -0
- morphml/benchmarks/metrics.py +199 -0
- morphml/benchmarks/openml_suite.py +201 -0
- morphml/benchmarks/problems.py +289 -0
- morphml/benchmarks/suite.py +318 -0
- morphml/cli/__init__.py +5 -0
- morphml/cli/commands/experiment.py +329 -0
- morphml/cli/main.py +457 -0
- morphml/cli/quickstart.py +312 -0
- morphml/config.py +278 -0
- morphml/constraints/__init__.py +19 -0
- morphml/constraints/handler.py +205 -0
- morphml/constraints/predicates.py +285 -0
- morphml/core/__init__.py +3 -0
- morphml/core/crossover.py +449 -0
- morphml/core/dsl/README.md +359 -0
- morphml/core/dsl/__init__.py +72 -0
- morphml/core/dsl/ast_nodes.py +364 -0
- morphml/core/dsl/compiler.py +318 -0
- morphml/core/dsl/layers.py +368 -0
- morphml/core/dsl/lexer.py +336 -0
- morphml/core/dsl/parser.py +455 -0
- morphml/core/dsl/search_space.py +386 -0
- morphml/core/dsl/syntax.py +199 -0
- morphml/core/dsl/type_system.py +361 -0
- morphml/core/dsl/validator.py +386 -0
- morphml/core/graph/__init__.py +40 -0
- morphml/core/graph/edge.py +124 -0
- morphml/core/graph/graph.py +507 -0
- morphml/core/graph/mutations.py +409 -0
- morphml/core/graph/node.py +196 -0
- morphml/core/graph/serialization.py +361 -0
- morphml/core/graph/visualization.py +431 -0
- morphml/core/objectives/__init__.py +20 -0
- morphml/core/search/__init__.py +33 -0
- morphml/core/search/individual.py +252 -0
- morphml/core/search/parameters.py +453 -0
- morphml/core/search/population.py +375 -0
- morphml/core/search/search_engine.py +340 -0
- morphml/distributed/__init__.py +76 -0
- morphml/distributed/fault_tolerance.py +497 -0
- morphml/distributed/health_monitor.py +348 -0
- morphml/distributed/master.py +709 -0
- morphml/distributed/proto/README.md +224 -0
- morphml/distributed/proto/__init__.py +74 -0
- morphml/distributed/proto/worker.proto +170 -0
- morphml/distributed/proto/worker_pb2.py +79 -0
- morphml/distributed/proto/worker_pb2_grpc.py +423 -0
- morphml/distributed/resource_manager.py +416 -0
- morphml/distributed/scheduler.py +567 -0
- morphml/distributed/storage/__init__.py +33 -0
- morphml/distributed/storage/artifacts.py +381 -0
- morphml/distributed/storage/cache.py +366 -0
- morphml/distributed/storage/checkpointing.py +329 -0
- morphml/distributed/storage/database.py +459 -0
- morphml/distributed/worker.py +549 -0
- morphml/evaluation/__init__.py +5 -0
- morphml/evaluation/heuristic.py +237 -0
- morphml/exceptions.py +55 -0
- morphml/execution/__init__.py +5 -0
- morphml/execution/local_executor.py +350 -0
- morphml/integrations/__init__.py +28 -0
- morphml/integrations/jax_adapter.py +206 -0
- morphml/integrations/pytorch_adapter.py +530 -0
- morphml/integrations/sklearn_adapter.py +206 -0
- morphml/integrations/tensorflow_adapter.py +230 -0
- morphml/logging_config.py +93 -0
- morphml/meta_learning/__init__.py +66 -0
- morphml/meta_learning/architecture_similarity.py +277 -0
- morphml/meta_learning/experiment_database.py +240 -0
- morphml/meta_learning/knowledge_base/__init__.py +19 -0
- morphml/meta_learning/knowledge_base/embedder.py +179 -0
- morphml/meta_learning/knowledge_base/knowledge_base.py +313 -0
- morphml/meta_learning/knowledge_base/meta_features.py +265 -0
- morphml/meta_learning/knowledge_base/vector_store.py +271 -0
- morphml/meta_learning/predictors/__init__.py +27 -0
- morphml/meta_learning/predictors/ensemble.py +221 -0
- morphml/meta_learning/predictors/gnn_predictor.py +552 -0
- morphml/meta_learning/predictors/learning_curve.py +231 -0
- morphml/meta_learning/predictors/proxy_metrics.py +261 -0
- morphml/meta_learning/strategy_evolution/__init__.py +27 -0
- morphml/meta_learning/strategy_evolution/adaptive_optimizer.py +226 -0
- morphml/meta_learning/strategy_evolution/bandit.py +276 -0
- morphml/meta_learning/strategy_evolution/portfolio.py +230 -0
- morphml/meta_learning/transfer.py +581 -0
- morphml/meta_learning/warm_start.py +286 -0
- morphml/optimizers/__init__.py +74 -0
- morphml/optimizers/adaptive_operators.py +399 -0
- morphml/optimizers/bayesian/__init__.py +52 -0
- morphml/optimizers/bayesian/acquisition.py +387 -0
- morphml/optimizers/bayesian/base.py +319 -0
- morphml/optimizers/bayesian/gaussian_process.py +635 -0
- morphml/optimizers/bayesian/smac.py +534 -0
- morphml/optimizers/bayesian/tpe.py +411 -0
- morphml/optimizers/differential_evolution.py +220 -0
- morphml/optimizers/evolutionary/__init__.py +61 -0
- morphml/optimizers/evolutionary/cma_es.py +416 -0
- morphml/optimizers/evolutionary/differential_evolution.py +556 -0
- morphml/optimizers/evolutionary/encoding.py +426 -0
- morphml/optimizers/evolutionary/particle_swarm.py +449 -0
- morphml/optimizers/genetic_algorithm.py +486 -0
- morphml/optimizers/gradient_based/__init__.py +22 -0
- morphml/optimizers/gradient_based/darts.py +550 -0
- morphml/optimizers/gradient_based/enas.py +585 -0
- morphml/optimizers/gradient_based/operations.py +474 -0
- morphml/optimizers/gradient_based/utils.py +601 -0
- morphml/optimizers/hill_climbing.py +169 -0
- morphml/optimizers/multi_objective/__init__.py +56 -0
- morphml/optimizers/multi_objective/indicators.py +504 -0
- morphml/optimizers/multi_objective/nsga2.py +647 -0
- morphml/optimizers/multi_objective/visualization.py +427 -0
- morphml/optimizers/nsga2.py +308 -0
- morphml/optimizers/random_search.py +172 -0
- morphml/optimizers/simulated_annealing.py +181 -0
- morphml/plugins/__init__.py +35 -0
- morphml/plugins/custom_evaluator_example.py +81 -0
- morphml/plugins/custom_optimizer_example.py +63 -0
- morphml/plugins/plugin_system.py +454 -0
- morphml/reports/__init__.py +30 -0
- morphml/reports/generator.py +362 -0
- morphml/tracking/__init__.py +7 -0
- morphml/tracking/experiment.py +309 -0
- morphml/tracking/logger.py +301 -0
- morphml/tracking/reporter.py +357 -0
- morphml/utils/__init__.py +6 -0
- morphml/utils/checkpoint.py +189 -0
- morphml/utils/comparison.py +390 -0
- morphml/utils/export.py +407 -0
- morphml/utils/progress.py +392 -0
- morphml/utils/validation.py +392 -0
- morphml/version.py +7 -0
- morphml/visualization/__init__.py +50 -0
- morphml/visualization/analytics.py +423 -0
- morphml/visualization/architecture_diagrams.py +353 -0
- morphml/visualization/architecture_plot.py +223 -0
- morphml/visualization/convergence_plot.py +174 -0
- morphml/visualization/crossover_viz.py +386 -0
- morphml/visualization/graph_viz.py +338 -0
- morphml/visualization/pareto_plot.py +149 -0
- morphml/visualization/plotly_dashboards.py +422 -0
- morphml/visualization/population.py +309 -0
- morphml/visualization/progress.py +260 -0
- morphml-1.0.0.dist-info/METADATA +434 -0
- morphml-1.0.0.dist-info/RECORD +158 -0
- morphml-1.0.0.dist-info/WHEEL +4 -0
- morphml-1.0.0.dist-info/entry_points.txt +3 -0
- morphml-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
"""Particle Swarm Optimization (PSO) for neural architecture search.
|
|
2
|
+
|
|
3
|
+
PSO is a swarm intelligence algorithm inspired by social behavior of bird flocking
|
|
4
|
+
and fish schooling. Particles move through search space guided by their personal
|
|
5
|
+
best positions and the global best position.
|
|
6
|
+
|
|
7
|
+
Key Features:
|
|
8
|
+
- Swarm intelligence
|
|
9
|
+
- Velocity-based movement
|
|
10
|
+
- Cognitive and social components
|
|
11
|
+
- No gradient information needed
|
|
12
|
+
- Works in continuous spaces
|
|
13
|
+
|
|
14
|
+
Reference:
|
|
15
|
+
Kennedy, J., and Eberhart, R. "Particle Swarm Optimization."
|
|
16
|
+
IEEE International Conference on Neural Networks, 1995.
|
|
17
|
+
|
|
18
|
+
Author: Eshan Roy <eshanized@proton.me>
|
|
19
|
+
Organization: TONMOY INFRASTRUCTURE & VISION
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
24
|
+
|
|
25
|
+
import numpy as np
|
|
26
|
+
|
|
27
|
+
from morphml.core.dsl import SearchSpace
|
|
28
|
+
from morphml.core.search import Individual
|
|
29
|
+
from morphml.logging_config import get_logger
|
|
30
|
+
from morphml.optimizers.evolutionary.encoding import ArchitectureEncoder
|
|
31
|
+
|
|
32
|
+
logger = get_logger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class Particle:
|
|
37
|
+
"""
|
|
38
|
+
Particle in PSO swarm.
|
|
39
|
+
|
|
40
|
+
Each particle has:
|
|
41
|
+
- Current position in search space
|
|
42
|
+
- Current velocity
|
|
43
|
+
- Personal best position found so far
|
|
44
|
+
- Personal best fitness
|
|
45
|
+
|
|
46
|
+
Attributes:
|
|
47
|
+
position: Current position vector
|
|
48
|
+
velocity: Current velocity vector
|
|
49
|
+
best_position: Personal best position
|
|
50
|
+
best_fitness: Personal best fitness value
|
|
51
|
+
fitness: Current fitness
|
|
52
|
+
|
|
53
|
+
Example:
|
|
54
|
+
>>> particle = Particle(
|
|
55
|
+
... position=np.random.rand(50),
|
|
56
|
+
... velocity=np.random.rand(50) * 0.1,
|
|
57
|
+
... best_position=np.random.rand(50),
|
|
58
|
+
... best_fitness=-np.inf
|
|
59
|
+
... )
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
position: np.ndarray
|
|
63
|
+
velocity: np.ndarray
|
|
64
|
+
best_position: np.ndarray
|
|
65
|
+
best_fitness: float = -np.inf
|
|
66
|
+
fitness: float = 0.0
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class ParticleSwarmOptimizer:
|
|
70
|
+
"""
|
|
71
|
+
Particle Swarm Optimization for architecture search.
|
|
72
|
+
|
|
73
|
+
PSO uses a swarm of particles that move through continuous search space.
|
|
74
|
+
Each particle:
|
|
75
|
+
1. Remembers its personal best position (cognitive component)
|
|
76
|
+
2. Is attracted to the global best position (social component)
|
|
77
|
+
3. Has inertia that maintains its previous direction
|
|
78
|
+
|
|
79
|
+
Update Equations:
|
|
80
|
+
v_i(t+1) = w*v_i(t) + c1*r1*(p_i - x_i(t)) + c2*r2*(g - x_i(t))
|
|
81
|
+
x_i(t+1) = x_i(t) + v_i(t+1)
|
|
82
|
+
|
|
83
|
+
where:
|
|
84
|
+
- v_i: velocity of particle i
|
|
85
|
+
- x_i: position of particle i
|
|
86
|
+
- p_i: personal best of particle i
|
|
87
|
+
- g: global best position
|
|
88
|
+
- w: inertia weight (controls exploration vs exploitation)
|
|
89
|
+
- c1: cognitive coefficient (attraction to personal best)
|
|
90
|
+
- c2: social coefficient (attraction to global best)
|
|
91
|
+
- r1, r2: random numbers in [0,1]
|
|
92
|
+
|
|
93
|
+
Configuration:
|
|
94
|
+
num_particles: Swarm size (default: 30)
|
|
95
|
+
max_iterations: Maximum iterations (default: 100)
|
|
96
|
+
w: Inertia weight (default: 0.7)
|
|
97
|
+
w_min: Minimum inertia (default: 0.4)
|
|
98
|
+
w_max: Maximum inertia (default: 0.9)
|
|
99
|
+
c1: Cognitive coefficient (default: 1.5)
|
|
100
|
+
c2: Social coefficient (default: 1.5)
|
|
101
|
+
max_velocity: Velocity clamping (default: 0.5)
|
|
102
|
+
|
|
103
|
+
Example:
|
|
104
|
+
>>> from morphml.optimizers.evolutionary import ParticleSwarmOptimizer
|
|
105
|
+
>>> optimizer = ParticleSwarmOptimizer(
|
|
106
|
+
... search_space=space,
|
|
107
|
+
... config={
|
|
108
|
+
... 'num_particles': 30,
|
|
109
|
+
... 'max_iterations': 100,
|
|
110
|
+
... 'w': 0.7,
|
|
111
|
+
... 'c1': 1.5,
|
|
112
|
+
... 'c2': 1.5
|
|
113
|
+
... }
|
|
114
|
+
... )
|
|
115
|
+
>>> best = optimizer.optimize(evaluator)
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
def __init__(self, search_space: SearchSpace, config: Optional[Dict[str, Any]] = None):
|
|
119
|
+
"""
|
|
120
|
+
Initialize PSO optimizer.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
search_space: SearchSpace for architecture sampling
|
|
124
|
+
config: Configuration dictionary
|
|
125
|
+
"""
|
|
126
|
+
self.search_space = search_space
|
|
127
|
+
self.config = config or {}
|
|
128
|
+
|
|
129
|
+
# PSO parameters
|
|
130
|
+
self.num_particles = self.config.get("num_particles", 30)
|
|
131
|
+
self.max_iterations = self.config.get("max_iterations", 100)
|
|
132
|
+
|
|
133
|
+
# Inertia weight (can be adaptive)
|
|
134
|
+
self.w = self.config.get("w", 0.7)
|
|
135
|
+
self.w_min = self.config.get("w_min", 0.4)
|
|
136
|
+
self.w_max = self.config.get("w_max", 0.9)
|
|
137
|
+
self.adaptive_inertia = self.config.get("adaptive_inertia", True)
|
|
138
|
+
|
|
139
|
+
# Cognitive and social coefficients
|
|
140
|
+
self.c1 = self.config.get("c1", 1.5) # Personal best attraction
|
|
141
|
+
self.c2 = self.config.get("c2", 1.5) # Global best attraction
|
|
142
|
+
|
|
143
|
+
# Velocity clamping
|
|
144
|
+
self.max_velocity = self.config.get("max_velocity", 0.5)
|
|
145
|
+
|
|
146
|
+
# Architecture encoding
|
|
147
|
+
self.max_nodes = self.config.get("max_nodes", 20)
|
|
148
|
+
self.encoder = ArchitectureEncoder(search_space, self.max_nodes)
|
|
149
|
+
self.dim = self.encoder.get_dimension()
|
|
150
|
+
|
|
151
|
+
# Swarm state
|
|
152
|
+
self.particles: List[Particle] = []
|
|
153
|
+
self.global_best_position: Optional[np.ndarray] = None
|
|
154
|
+
self.global_best_fitness: float = -np.inf
|
|
155
|
+
self.global_best_individual: Optional[Individual] = None
|
|
156
|
+
|
|
157
|
+
# History
|
|
158
|
+
self.iteration = 0
|
|
159
|
+
self.history: List[Dict[str, Any]] = []
|
|
160
|
+
|
|
161
|
+
logger.info(
|
|
162
|
+
f"Initialized PSO: {self.num_particles} particles, "
|
|
163
|
+
f"dimension={self.dim}, max_iterations={self.max_iterations}"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
def initialize_swarm(self) -> None:
|
|
167
|
+
"""Initialize swarm with random particles."""
|
|
168
|
+
self.particles = []
|
|
169
|
+
|
|
170
|
+
for _i in range(self.num_particles):
|
|
171
|
+
# Random position in [0, 1]^dim
|
|
172
|
+
position = np.random.rand(self.dim)
|
|
173
|
+
|
|
174
|
+
# Random velocity (small initial values)
|
|
175
|
+
velocity = (np.random.rand(self.dim) - 0.5) * 0.1
|
|
176
|
+
|
|
177
|
+
# Create particle
|
|
178
|
+
particle = Particle(
|
|
179
|
+
position=position,
|
|
180
|
+
velocity=velocity,
|
|
181
|
+
best_position=position.copy(),
|
|
182
|
+
best_fitness=-np.inf,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
self.particles.append(particle)
|
|
186
|
+
|
|
187
|
+
logger.debug(f"Initialized swarm of {len(self.particles)} particles")
|
|
188
|
+
|
|
189
|
+
def evaluate_particle(self, particle: Particle, evaluator: Callable) -> None:
|
|
190
|
+
"""
|
|
191
|
+
Evaluate fitness of a particle.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
particle: Particle to evaluate
|
|
195
|
+
evaluator: Fitness evaluation function
|
|
196
|
+
"""
|
|
197
|
+
# Decode position to architecture
|
|
198
|
+
graph = self.encoder.decode(particle.position)
|
|
199
|
+
|
|
200
|
+
# Evaluate
|
|
201
|
+
fitness = evaluator(graph)
|
|
202
|
+
particle.fitness = fitness
|
|
203
|
+
|
|
204
|
+
# Update personal best
|
|
205
|
+
if fitness > particle.best_fitness:
|
|
206
|
+
particle.best_position = particle.position.copy()
|
|
207
|
+
particle.best_fitness = fitness
|
|
208
|
+
|
|
209
|
+
# Update global best
|
|
210
|
+
if fitness > self.global_best_fitness:
|
|
211
|
+
self.global_best_fitness = fitness
|
|
212
|
+
self.global_best_position = particle.position.copy()
|
|
213
|
+
|
|
214
|
+
# Store as Individual
|
|
215
|
+
individual = Individual(graph)
|
|
216
|
+
individual.fitness = fitness
|
|
217
|
+
self.global_best_individual = individual
|
|
218
|
+
|
|
219
|
+
def update_velocity(self, particle: Particle, iteration: int) -> np.ndarray:
|
|
220
|
+
"""
|
|
221
|
+
Update particle velocity using PSO equations.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
particle: Particle to update
|
|
225
|
+
iteration: Current iteration number
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
New velocity vector
|
|
229
|
+
"""
|
|
230
|
+
# Adaptive inertia weight (linearly decreasing)
|
|
231
|
+
if self.adaptive_inertia:
|
|
232
|
+
w = self.w_max - (self.w_max - self.w_min) * iteration / self.max_iterations
|
|
233
|
+
else:
|
|
234
|
+
w = self.w
|
|
235
|
+
|
|
236
|
+
# Random coefficients for stochasticity
|
|
237
|
+
r1 = np.random.rand(self.dim)
|
|
238
|
+
r2 = np.random.rand(self.dim)
|
|
239
|
+
|
|
240
|
+
# Cognitive component (personal best attraction)
|
|
241
|
+
cognitive = self.c1 * r1 * (particle.best_position - particle.position)
|
|
242
|
+
|
|
243
|
+
# Social component (global best attraction)
|
|
244
|
+
social = self.c2 * r2 * (self.global_best_position - particle.position)
|
|
245
|
+
|
|
246
|
+
# New velocity
|
|
247
|
+
new_velocity = w * particle.velocity + cognitive + social
|
|
248
|
+
|
|
249
|
+
# Velocity clamping to prevent explosion
|
|
250
|
+
new_velocity = np.clip(new_velocity, -self.max_velocity, self.max_velocity)
|
|
251
|
+
|
|
252
|
+
return new_velocity
|
|
253
|
+
|
|
254
|
+
def update_position(self, particle: Particle) -> np.ndarray:
|
|
255
|
+
"""
|
|
256
|
+
Update particle position.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
particle: Particle to update
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
New position vector
|
|
263
|
+
"""
|
|
264
|
+
new_position = particle.position + particle.velocity
|
|
265
|
+
|
|
266
|
+
# Clamp to bounds [0, 1]
|
|
267
|
+
new_position = np.clip(new_position, 0.0, 1.0)
|
|
268
|
+
|
|
269
|
+
return new_position
|
|
270
|
+
|
|
271
|
+
def optimize(self, evaluator: Callable) -> Individual:
|
|
272
|
+
"""
|
|
273
|
+
Run PSO optimization.
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
evaluator: Function that evaluates ModelGraph -> fitness
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
Best Individual found
|
|
280
|
+
|
|
281
|
+
Example:
|
|
282
|
+
>>> def my_evaluator(graph):
|
|
283
|
+
... return train_and_evaluate(graph)
|
|
284
|
+
>>> best = optimizer.optimize(my_evaluator)
|
|
285
|
+
>>> print(f"Best fitness: {best.fitness:.4f}")
|
|
286
|
+
"""
|
|
287
|
+
logger.info(f"Starting PSO optimization for {self.max_iterations} iterations")
|
|
288
|
+
|
|
289
|
+
# Initialize swarm
|
|
290
|
+
self.initialize_swarm()
|
|
291
|
+
|
|
292
|
+
# Evaluate initial swarm
|
|
293
|
+
for particle in self.particles:
|
|
294
|
+
self.evaluate_particle(particle, evaluator)
|
|
295
|
+
|
|
296
|
+
# Main PSO loop
|
|
297
|
+
for iteration in range(self.max_iterations):
|
|
298
|
+
self.iteration = iteration
|
|
299
|
+
|
|
300
|
+
# Update each particle
|
|
301
|
+
for particle in self.particles:
|
|
302
|
+
# Update velocity
|
|
303
|
+
particle.velocity = self.update_velocity(particle, iteration)
|
|
304
|
+
|
|
305
|
+
# Update position
|
|
306
|
+
particle.position = self.update_position(particle)
|
|
307
|
+
|
|
308
|
+
# Evaluate new position
|
|
309
|
+
self.evaluate_particle(particle, evaluator)
|
|
310
|
+
|
|
311
|
+
# Record history
|
|
312
|
+
avg_fitness = np.mean([p.fitness for p in self.particles])
|
|
313
|
+
best_fitness = max(p.fitness for p in self.particles)
|
|
314
|
+
|
|
315
|
+
self.history.append(
|
|
316
|
+
{
|
|
317
|
+
"iteration": iteration,
|
|
318
|
+
"global_best_fitness": self.global_best_fitness,
|
|
319
|
+
"avg_fitness": avg_fitness,
|
|
320
|
+
"best_fitness": best_fitness,
|
|
321
|
+
}
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
# Logging
|
|
325
|
+
if iteration % 10 == 0 or iteration == self.max_iterations - 1:
|
|
326
|
+
logger.info(
|
|
327
|
+
f"Iteration {iteration}/{self.max_iterations}: "
|
|
328
|
+
f"global_best={self.global_best_fitness:.4f}, "
|
|
329
|
+
f"avg={avg_fitness:.4f}"
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
logger.info(f"PSO complete. Best fitness: {self.global_best_fitness:.4f}")
|
|
333
|
+
|
|
334
|
+
return self.global_best_individual
|
|
335
|
+
|
|
336
|
+
def get_history(self) -> List[Dict[str, Any]]:
|
|
337
|
+
"""Get optimization history."""
|
|
338
|
+
return self.history
|
|
339
|
+
|
|
340
|
+
def plot_convergence(self, save_path: Optional[str] = None) -> None:
|
|
341
|
+
"""
|
|
342
|
+
Plot PSO convergence.
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
save_path: Optional path to save plot
|
|
346
|
+
"""
|
|
347
|
+
try:
|
|
348
|
+
import matplotlib.pyplot as plt
|
|
349
|
+
except ImportError:
|
|
350
|
+
logger.warning("matplotlib not available, cannot plot")
|
|
351
|
+
return
|
|
352
|
+
|
|
353
|
+
if not self.history:
|
|
354
|
+
logger.warning("No history to plot")
|
|
355
|
+
return
|
|
356
|
+
|
|
357
|
+
iterations = [h["iteration"] for h in self.history]
|
|
358
|
+
global_best = [h["global_best_fitness"] for h in self.history]
|
|
359
|
+
avg_fitness = [h["avg_fitness"] for h in self.history]
|
|
360
|
+
|
|
361
|
+
plt.figure(figsize=(10, 6))
|
|
362
|
+
plt.plot(iterations, global_best, "b-", linewidth=2, label="Global Best")
|
|
363
|
+
plt.plot(iterations, avg_fitness, "r--", linewidth=2, label="Average Fitness")
|
|
364
|
+
|
|
365
|
+
plt.xlabel("Iteration", fontsize=12)
|
|
366
|
+
plt.ylabel("Fitness", fontsize=12)
|
|
367
|
+
plt.title("PSO Convergence", fontsize=14, fontweight="bold")
|
|
368
|
+
plt.legend()
|
|
369
|
+
plt.grid(True, alpha=0.3)
|
|
370
|
+
plt.tight_layout()
|
|
371
|
+
|
|
372
|
+
if save_path:
|
|
373
|
+
plt.savefig(save_path, dpi=300, bbox_inches="tight")
|
|
374
|
+
logger.info(f"Convergence plot saved to {save_path}")
|
|
375
|
+
else:
|
|
376
|
+
plt.show()
|
|
377
|
+
|
|
378
|
+
plt.close()
|
|
379
|
+
|
|
380
|
+
def __repr__(self) -> str:
|
|
381
|
+
"""String representation."""
|
|
382
|
+
return (
|
|
383
|
+
f"ParticleSwarmOptimizer("
|
|
384
|
+
f"particles={self.num_particles}, "
|
|
385
|
+
f"w={self.w:.2f}, "
|
|
386
|
+
f"c1={self.c1:.2f}, "
|
|
387
|
+
f"c2={self.c2:.2f})"
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
# Convenience function
|
|
392
|
+
def optimize_with_pso(
|
|
393
|
+
search_space: SearchSpace,
|
|
394
|
+
evaluator: Callable,
|
|
395
|
+
num_particles: int = 30,
|
|
396
|
+
max_iterations: int = 100,
|
|
397
|
+
w: float = 0.7,
|
|
398
|
+
c1: float = 1.5,
|
|
399
|
+
c2: float = 1.5,
|
|
400
|
+
verbose: bool = True,
|
|
401
|
+
) -> Individual:
|
|
402
|
+
"""
|
|
403
|
+
Quick PSO optimization with sensible defaults.
|
|
404
|
+
|
|
405
|
+
Args:
|
|
406
|
+
search_space: SearchSpace to optimize over
|
|
407
|
+
evaluator: Fitness evaluation function
|
|
408
|
+
num_particles: Swarm size
|
|
409
|
+
max_iterations: Maximum iterations
|
|
410
|
+
w: Inertia weight
|
|
411
|
+
c1: Cognitive coefficient
|
|
412
|
+
c2: Social coefficient
|
|
413
|
+
verbose: Print progress
|
|
414
|
+
|
|
415
|
+
Returns:
|
|
416
|
+
Best Individual found
|
|
417
|
+
|
|
418
|
+
Example:
|
|
419
|
+
>>> from morphml.optimizers.evolutionary import optimize_with_pso
|
|
420
|
+
>>> best = optimize_with_pso(
|
|
421
|
+
... search_space=space,
|
|
422
|
+
... evaluator=my_evaluator,
|
|
423
|
+
... num_particles=30,
|
|
424
|
+
... max_iterations=100
|
|
425
|
+
... )
|
|
426
|
+
"""
|
|
427
|
+
optimizer = ParticleSwarmOptimizer(
|
|
428
|
+
search_space=search_space,
|
|
429
|
+
config={
|
|
430
|
+
"num_particles": num_particles,
|
|
431
|
+
"max_iterations": max_iterations,
|
|
432
|
+
"w": w,
|
|
433
|
+
"c1": c1,
|
|
434
|
+
"c2": c2,
|
|
435
|
+
},
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
best = optimizer.optimize(evaluator)
|
|
439
|
+
|
|
440
|
+
if verbose:
|
|
441
|
+
print(f"\n{'='*60}")
|
|
442
|
+
print("PSO Optimization Complete")
|
|
443
|
+
print(f"{'='*60}")
|
|
444
|
+
print(f"Best Fitness: {best.fitness:.4f}")
|
|
445
|
+
print(f"Iterations: {max_iterations}")
|
|
446
|
+
print(f"Swarm Size: {num_particles}")
|
|
447
|
+
print(f"{'='*60}\n")
|
|
448
|
+
|
|
449
|
+
return best
|