CoolFace
Modelpublic

1990two/evolutionary_turing

sourceHugging Faceapache-2.0updated 1y agoView on Hugging Face
0likes
evolutionary_turing_docs.py956 linesDownload Raw Back to root
1############################################################################################################################################2#|| - - - |8.19.2025| - - -                         ||   Evolutionary Turing Machine   ||                         - - - | 1990two | - - -||#3############################################################################################################################################4"""5Mathematical Foundation & Conceptual Documentation6-------------------------------------------------7 8CORE PRINCIPLE:9Combines Neural Turing Machines (external memory architectures) with evolutionary10algorithms to create adaptive memory systems that evolve both their architecture11and parameters through natural selection, enabling discovery of optimal memory12access patterns and computational structures.13 14MATHEMATICAL FOUNDATION:15=======================16 171. NEURAL TURING MACHINE DYNAMICS:18   Content-based addressing: w_t^c = softmax(β_t ⊙ K[M_t, k_t])19   Where:20   - w_t: attention weights over memory locations21   - β_t: key strength (focus parameter)22   - K[M,k]: cosine similarity between memory M and key k23   - M_t: memory matrix at time t24   - k_t: generated key vector25 262. MEMORY OPERATIONS:27   Read: r_t = Σ_i w_t^r[i] × M_t[i]28   Erase: M̃_t[i] = M_{t-1}[i] ⊙ (1 - w_t^w[i] ⊙ e_t)29   Add: M_t[i] = M̃_t[i] + w_t^w[i] ⊙ a_t30   31   Where:32   - r_t: read vector33   - e_t: erase vector ∈ [0,1]^M34   - a_t: add vector ∈ ℝ^M35   - ⊙: element-wise product36 373. EVOLUTIONARY FITNESS:38   F(individual) = α·task_performance + β·memory_efficiency + γ·stability39   40   Where:41   - task_performance: accuracy on computational tasks42   - memory_efficiency: 1/(parameter_count/baseline)43   - stability: consistency across multiple runs44 454. GENETIC OPERATIONS:46   Architecture Crossover: A_child = random_blend(A_parent1, A_parent2)47   Parameter Mutation: θ'_i = θ_i + ε·N(0,σ²) with probability p_mut48   Selection: P(selection) ∝ exp(F(individual)/T)49   50   Where T is selection temperature.51 525. POPULATION DYNAMICS:53   Elite Preservation: Keep top k% individuals54   Tournament Selection: Choose parents via tournament55   Replacement Strategy: (μ + λ) evolution strategy56 57CONCEPTUAL REASONING:58====================59 60WHY EVOLUTIONARY + TURING MACHINES?61- Fixed NTM architectures may be suboptimal for specific tasks62- Manual architecture design is time-intensive and domain-specific63- Evolution can discover novel memory access patterns64- Natural selection optimizes both structure and parameters simultaneously65 66KEY INNOVATIONS:671. **Evolvable Architecture**: Memory size, heads, controller complexity all mutable682. **Task-Adaptive Evolution**: Fitness functions guide toward task-specific solutions693. **Multi-Objective Optimization**: Balance performance, efficiency, and stability704. **Hierarchical Mutation**: Different rates for architecture vs parameters715. **Memory Access Pattern Evolution**: Learn optimal attention strategies72 73APPLICATIONS:74- Algorithmic learning (sorting, copying, associative recall)75- Adaptive control systems with memory requirements76- Meta-learning for memory-augmented architectures77- Neural architecture search for sequence modeling78- Continual learning with evolving memory structures79 80COMPLEXITY ANALYSIS:81- Individual Evaluation: O(T·(D² + M·H)) where T=sequence length, D=hidden size, M=memory slots, H=heads82- Population Evolution: O(P·evaluations) where P=population size83- Architecture Mutation: O(1) for parameter changes, O(M) for structural changes84- Memory: O(P·(D² + M²)) for population storage85 86BIOLOGICAL INSPIRATION:87- Neural plasticity and synaptic evolution88- Natural selection of neural circuits89- Memory consolidation and forgetting mechanisms90- Adaptive brain architecture development91"""92 93from __future__ import annotations94 95from dataclasses import dataclass96from typing import Dict, List, Optional, Tuple, Union97import math98import torch99import torch.nn as nn100import torch.nn.functional as F101from copy import deepcopy102 103 104@dataclass105class NTMConfig:106    """Configuration for Neural Turing Machine architecture.107    108    Defines the structure and hyperparameters for a single NTM individual109    in the evolutionary population. All parameters are evolvable.110    """111    input_dim: int112    output_dim: int113    controller_dim: int = 128114    controller_layers: int = 1115    memory_slots: int = 128116    memory_dim: int = 32117    heads_read: int = 1118    heads_write: int = 1119    init_std: float = 0.1120 121############################################################################################################################################122####################################################    - - - Neural Turing Machine - - -    ###############################################123 124class NeuralTuringMachine(nn.Module):125    """Neural Turing Machine with external memory and attention mechanisms.126    127    Implements the complete NTM architecture including:128    - LSTM controller for sequence processing129    - External memory matrix with read/write operations130    - Content-based addressing via cosine similarity131    - Differentiable memory operations (erase, add)132    133    Mathematical Details:134    - Controller processes input + read vectors: h_t = LSTM(x_t ⊕ r_{t-1}, h_{t-1})135    - Interface parameters: keys, strengths, erase/add vectors136    - Attention: w_t = softmax(β_t ⊙ cosine_sim(M_t, k_t))137    - Memory updates preserve differentiability for gradient-based learning138    """139    def __init__(self, cfg: NTMConfig):140        super().__init__()141        self.cfg = cfg142        R, W, Dm = cfg.heads_read, cfg.heads_write, cfg.memory_dim143 144        # Controller: processes input + read vectors145        ctrl_in = cfg.input_dim + R * Dm146        self.controller = nn.LSTMCell(ctrl_in, cfg.controller_dim)147 148        # Interface: generates read/write parameters149        iface_read = R * (Dm + 1)  # key + strength per read head150        iface_write = W * (Dm + 1 + Dm + Dm)  # key + strength + erase + add per write head151        self.interface = nn.Linear(cfg.controller_dim, iface_read + iface_write)152        153        # Output head: combines controller state + read vectors154        self.output = nn.Linear(cfg.controller_dim + R * Dm, cfg.output_dim)155 156        self.reset_parameters()157 158    def reset_parameters(self):159        """Initialize parameters with appropriate distributions.160        161        Uses Xavier initialization for linear layers and orthogonal162        initialization for LSTM recurrent weights to ensure stable training.163        Forget gate bias is initialized to 1.0 for better gradient flow.164        """165        for m in self.modules():166            if isinstance(m, nn.Linear):167                nn.init.xavier_uniform_(m.weight)168                nn.init.zeros_(m.bias)169            if isinstance(m, nn.LSTMCell):170                nn.init.xavier_uniform_(m.weight_ih)171                nn.init.orthogonal_(m.weight_hh)172                nn.init.zeros_(m.bias_ih)173                nn.init.zeros_(m.bias_hh)174                # Forget gate bias = 1.0 for better gradient flow175                hs = m.bias_ih.shape[0] // 4176                m.bias_ih.data[hs:2*hs].fill_(1.0)177                m.bias_hh.data[hs:2*hs].fill_(1.0)178 179    def initial_state(self, batch_size: int, device=None):180        """Initialize NTM state including memory, attention weights, and controller state.181        182        Args:183            batch_size: Number of parallel sequences184            device: Target device for tensors185            186        Returns:187            Dictionary containing:188            - M: Memory matrix [batch_size, memory_slots, memory_dim]189            - w_r: Read attention weights [batch_size, heads_read, memory_slots]190            - w_w: Write attention weights [batch_size, heads_write, memory_slots]191            - r: Read vectors [batch_size, heads_read, memory_dim]192            - h, c: LSTM controller states193        """194        cfg = self.cfg195        device = device or next(self.parameters()).device196 197        # Initialize memory with small random values198        M = torch.zeros(batch_size, cfg.memory_slots, cfg.memory_dim, device=device)199        if cfg.init_std > 0:200            M.normal_(0.0, cfg.init_std)201 202        # Initialize attention weights uniformly (all locations equally attended)203        w_r = torch.ones(batch_size, cfg.heads_read, cfg.memory_slots, device=device) / cfg.memory_slots204        w_w = torch.ones(batch_size, cfg.heads_write, cfg.memory_slots, device=device) / cfg.memory_slots205        206        # Initialize read vectors and controller states207        r = torch.zeros(batch_size, cfg.heads_read, cfg.memory_dim, device=device)208        h = torch.zeros(batch_size, cfg.controller_dim, device=device)209        c = torch.zeros(batch_size, cfg.controller_dim, device=device)210 211        return {'M': M, 'w_r': w_r, 'w_w': w_w, 'r': r, 'h': h, 'c': c}212 213    def step(self, x: torch.Tensor, state: Dict[str, torch.Tensor]):214        """Execute one forward step of NTM computation.215        216        Complete NTM forward pass:217        1. Controller processes input + previous reads218        2. Interface generates memory operation parameters219        3. Content-based addressing computes attention weights220        4. Memory operations (read, erase, add)221        5. Output generation222        223        Args:224            x: Input tensor [batch_size, input_dim]225            state: Current NTM state dictionary226            227        Returns:228            y: Output tensor [batch_size, output_dim]229            new_state: Updated state dictionary230        """231        cfg = self.cfg232        B = x.shape[0]233 234        # Step 1: Controller forward pass235        ctrl_in = torch.cat([x, state['r'].view(B, -1)], dim=-1)236        h, c = self.controller(ctrl_in, (state['h'], state['c']))237        238        # Step 2: Generate interface parameters239        iface = self.interface(h)240        R, W, Dm = cfg.heads_read, cfg.heads_write, cfg.memory_dim241 242        # Parse interface outputs243        offset = 0244        # Read parameters: keys and strengths245        k_r = iface[:, offset:offset + R * Dm].view(B, R, Dm)246        offset += R * Dm247        beta_r = F.softplus(iface[:, offset:offset + R])248        offset += R249 250        # Write parameters: keys, strengths, erase vectors, add vectors251        k_w = iface[:, offset:offset + W * Dm].view(B, W, Dm)252        offset += W * Dm253        beta_w = F.softplus(iface[:, offset:offset + W])254        offset += W255        erase = torch.sigmoid(iface[:, offset:offset + W * Dm]).view(B, W, Dm)256        offset += W * Dm257        add = torch.tanh(iface[:, offset:offset + W * Dm]).view(B, W, Dm)258 259        def address(M, k, beta, prev_weight=None):260            """Content-based addressing mechanism.261            262            Computes attention weights using cosine similarity between263            memory contents and generated keys, focused by strength parameter.264            265            Mathematical Details:266            - Cosine similarity: sim(M[i], k) = (M[i] · k) / (||M[i]|| ||k||)267            - Focused attention: w = softmax(β ⊙ sim)268            - Optional momentum: adds small fraction of previous weights269            270            Args:271                M: Memory matrix [batch_size, slots, memory_dim]272                k: Key vectors [batch_size, heads, memory_dim]273                beta: Strength parameters [batch_size, heads]274                prev_weight: Previous attention weights for momentum275                276            Returns:277                Attention weights [batch_size, heads, slots]278            """279            # Normalize for cosine similarity280            M_norm = torch.norm(M, dim=-1, keepdim=True).clamp_min(1e-8)281            k_norm = torch.norm(k, dim=-1, keepdim=True).clamp_min(1e-8)282            283            # Cosine similarity: M[i] · k / (||M[i]|| ||k||)284            cos_sim = torch.sum(M.unsqueeze(1) * k.unsqueeze(2), dim=-1) / (285                M_norm.squeeze(-1).unsqueeze(1) * k_norm.squeeze(-1).unsqueeze(-1)286            )287            288            # Apply strength and optional momentum289            content_logits = beta.unsqueeze(-1) * cos_sim290            if prev_weight is not None:291                content_logits = content_logits + 0.02 * prev_weight  # Small momentum term292            293            return F.softmax(content_logits, dim=-1)294 295        # Step 3: Compute attention weights296        w_r = address(state['M'], k_r, beta_r, prev_weight=state.get('w_r'))297        w_w = address(state['M'], k_w, beta_w, prev_weight=state.get('w_w'))298        299        # Step 4: Memory operations300        # Read: weighted sum over memory locations301        r = torch.sum(w_r.unsqueeze(-1) * state['M'].unsqueeze(1), dim=2)302 303        # Write: erase then add304        M = state['M']305        if W > 0:306            # Erase: M[i] := M[i] ⊙ (1 - w[i] ⊙ e)307            erase_term = torch.prod(1 - w_w.unsqueeze(-1) * erase.unsqueeze(2), dim=1)308            M = M * erase_term309            310            # Add: M[i] := M[i] + w[i] ⊙ a311            add_term = torch.sum(w_w.unsqueeze(-1) * add.unsqueeze(2), dim=1)312            M = M + add_term313 314        # Step 5: Generate output315        y = self.output(torch.cat([h, r.view(B, -1)], dim=-1))316 317        new_state = {'M': M, 'w_r': w_r, 'w_w': w_w, 'r': r, 'h': h, 'c': c}318        return y, new_state319 320    def forward(self, x: torch.Tensor, state=None):321        """Forward pass for single step or sequence.322        323        Handles both single-step operation (for interactive use) and324        sequence processing (for training/evaluation).325        326        Args:327            x: Input tensor [batch_size, input_dim] or [batch_size, seq_len, input_dim]328            state: Optional initial state (created if None)329            330        Returns:331            For single step: (output, new_state)332            For sequence: (output_sequence, final_state)333        """334        if x.dim() == 2:  # Single step335            if state is None:336                state = self.initial_state(x.shape[0], x.device)337            return self.step(x, state)338 339        # Sequence processing340        B, T, _ = x.shape341        if state is None:342            state = self.initial_state(B, x.device)343 344        outputs = []345        for t in range(T):346            y, state = self.step(x[:, t], state)347            outputs.append(y)348 349        return torch.stack(outputs, dim=1), state350 351@dataclass352class EvolutionaryTuringConfig:353    """Configuration for evolutionary optimization of NTM population.354    355    Defines hyperparameters for the evolutionary algorithm including356    population size, mutation rates, selection pressure, and fitness357    evaluation parameters.358    """359    population_size: int = 100360    mutation_rate: float = 0.1361    architecture_mutation_rate: float = 0.05362    elite_ratio: float = 0.2363    max_generations: int = 200364    input_dim: int = 8365    output_dim: int = 8366    device: str = 'cpu'367    seed: Optional[int] = None368 369############################################################################################################################################370#################################################    - - - Fitness Evaluation - - -    #####################################################371 372class FitnessEvaluator:373    """Comprehensive fitness evaluation for NTM individuals.374    375    Evaluates NTM performance on multiple algorithmic tasks to assess376    general computational capability. Includes efficiency penalties377    to encourage compact, effective architectures.378    379    Tasks:380    1. Copy Task: Tests basic memory read/write capabilities381    2. Associative Recall: Tests content-based memory access382    3. Efficiency: Penalizes excessive parameters383    384    Mathematical Details:385    - Copy task measures sequence reproduction accuracy386    - Associative recall tests key-value pair memory387    - Composite fitness balances multiple objectives388    """389    def __init__(self, device: str = 'cpu'):390        self.device = device391 392    def copy_task(self, ntm: NeuralTuringMachine, seq_len: int = 8, batch_size: int = 16) -> float:393        """Evaluate NTM on sequence copying task.394        395        The copy task is fundamental for testing memory capabilities:396        1. Present input sequence397        2. Present delimiter (end-of-sequence marker)398        3. Evaluate output sequence reproduction accuracy399        400        Mathematical Details:401        - Input: x₁, x₂, ..., xₜ, delimiter402        - Target: reproduce x₁, x₂, ..., xₜ after delimiter403        - Loss: MSE between predicted and target sequences404        - Accuracy: 1 / (1 + loss) for bounded score ∈ [0,1]405        406        Args:407            ntm: NTM individual to evaluate408            seq_len: Length of sequences to copy409            batch_size: Number of parallel sequences410            411        Returns:412            Copy task accuracy score ∈ [0,1]413        """414        with torch.no_grad():415            # Generate random binary sequences416            x = torch.randint(0, 2, (batch_size, seq_len, ntm.cfg.input_dim),417                              device=self.device, dtype=torch.float32)418 419            # Add delimiter (end-of-sequence marker)420            delimiter = torch.zeros(batch_size, 1, ntm.cfg.input_dim, device=self.device)421            delimiter[:, :, -1] = 1  # Use last dimension as delimiter signal422 423            # Complete input: sequence + delimiter424            input_seq = torch.cat([x, delimiter], dim=1)425            426            try:427                output, _ = ntm(input_seq)428                429                # Compare output to target (original sequence)430                T = seq_len431                D = ntm.cfg.output_dim432                pred = output[:, -T:, :D]  # Last T outputs433                434                # Handle dimension mismatch by using overlap435                d = min(ntm.cfg.input_dim, D)436                loss = F.mse_loss(pred[..., :d], x[..., :d])437                accuracy = 1.0 / (1.0 + loss.item())438                return accuracy439            except:440                # Return zero for failed evaluations (architecture issues)441                return 0.0442 443    def associative_recall(self, ntm: NeuralTuringMachine, num_pairs: int = 4) -> float:444        """Evaluate NTM on associative memory recall task.445        446        Tests content-based memory access by storing key-value pairs447        and then querying with keys to retrieve associated values.448        449        Task Structure:450        1. Store phase: present key-value pairs451        2. Query phase: present keys (with zero values)452        3. Evaluate: check if correct values are recalled453        454        Mathematical Details:455        - Keys: k₁, k₂, ..., kₙ (half of input dimension)456        - Values: v₁, v₂, ..., vₙ (other half of input dimension)457        - Query: present [k₁, 0], expect output [0, v₁]458        - Score based on MSE between recalled and target values459        460        Args:461            ntm: NTM individual to evaluate462            num_pairs: Number of key-value pairs to store/recall463            464        Returns:465            Associative recall accuracy score ∈ [0,1]466        """467        with torch.no_grad():468            batch_size = 8469            dim = ntm.cfg.input_dim470            471            # Generate key-value pairs472            keys = torch.randn(batch_size, num_pairs, dim // 2, device=self.device)473            values = torch.randn(batch_size, num_pairs, dim // 2, device=self.device)474            pairs = torch.cat([keys, values], dim=-1)475 476            # Query format: keys with zero values477            test_keys = torch.cat([keys, torch.zeros_like(values)], dim=-1)478            expected_values = torch.cat([torch.zeros_like(keys), values], dim=-1)479 480            # Complete sequence: store pairs then query481            input_seq = torch.cat([pairs, test_keys], dim=1)482            target_seq = torch.cat([torch.zeros_like(pairs), expected_values], dim=1)483 484            try:485                output, _ = ntm(input_seq)486                487                # Evaluate query phase (second half of sequence)488                D = ntm.cfg.output_dim489                d = min(dim, D)490                loss = F.mse_loss(output[:, num_pairs:, :d], target_seq[:, num_pairs:, :d])491                accuracy = 1.0 / (1.0 + loss.item())492                return accuracy493            except:494                return 0.0495 496    def evaluate_fitness(self, ntm: NeuralTuringMachine) -> Dict[str, float]:497        """Comprehensive fitness evaluation across multiple criteria.498        499        Evaluates individual on multiple tasks and efficiency metrics500        to encourage both performance and architectural parsimony.501        502        Fitness Components:503        1. Copy Task (50%): Basic memory functionality504        2. Associative Recall (30%): Content-based memory access505        3. Efficiency (20%): Parameter count penalty506        507        Mathematical Details:508        - Each component scored ∈ [0,1]509        - Efficiency = 1 / (1 + params/baseline)510        - Composite = weighted combination511        512        Args:513            ntm: NTM individual to evaluate514            515        Returns:516            Dictionary containing individual and composite fitness scores517        """518        copy_score = self.copy_task(ntm)519        recall_score = self.associative_recall(ntm)520 521        # Efficiency penalty based on parameter count522        param_count = sum(p.numel() for p in ntm.parameters())523        efficiency = 1.0 / (1.0 + param_count / 100000)  # Normalize to reasonable range524 525        # Weighted composite fitness526        composite_score = 0.5 * copy_score + 0.3 * recall_score + 0.2 * efficiency527 528        return {529            'copy': copy_score,530            'recall': recall_score,531            'efficiency': efficiency,532            'composite': composite_score533        }534 535###############################################################################################################################################536#################################################    - - - Evolutionary Turing Machine - - -    ###############################################537 538class EvolutionaryTuringMachine:539    """Evolutionary optimization system for Neural Turing Machine architectures.540    541    Implements a complete evolutionary algorithm for discovering optimal542    NTM architectures and parameters through natural selection. Uses543    both architectural mutations (structure) and parameter mutations.544    545    Evolutionary Operations:546    1. Selection: Tournament/rank-based parent selection547    2. Crossover: Architecture and parameter blending548    3. Mutation: Structure modification and parameter perturbation549    4. Replacement: Elite preservation with new offspring550    551    The system evolves both the neural architecture (memory size, heads,552    controller complexity) and the connection weights simultaneously.553    """554    def __init__(self, cfg: EvolutionaryTuringConfig):555        self.cfg = cfg556        self.evaluator = FitnessEvaluator(cfg.device)557        self.generation = 0558        self.best_fitness = 0.0559        self.population = []560 561        if cfg.seed is not None:562            torch.manual_seed(cfg.seed)563 564    def create_random_config(self) -> NTMConfig:565        """Generate random NTM architecture configuration.566        567        Creates diverse initial population by randomizing all568        architectural hyperparameters within reasonable bounds.569        570        Architectural Parameters:571        - Controller dimension: [64, 256]572        - Memory slots: [32, 256]573        - Memory dimension: [16, 64]574        - Read/write heads: [1, 4] and [1, 3]575        576        Returns:577            Random NTM configuration578        """579        return NTMConfig(580            input_dim=self.cfg.input_dim,581            output_dim=self.cfg.output_dim,582            controller_dim=torch.randint(64, 256, (1,)).item(),583            controller_layers=torch.randint(1, 3, (1,)).item(),584            memory_slots=torch.randint(32, 256, (1,)).item(),585            memory_dim=torch.randint(16, 64, (1,)).item(),586            heads_read=torch.randint(1, 4, (1,)).item(),587            heads_write=torch.randint(1, 3, (1,)).item(),588            init_std=0.1589        )590 591    def mutate_architecture(self, cfg: NTMConfig) -> NTMConfig:592        """Apply architectural mutations to NTM configuration.593        594        Modifies structural parameters with probability architecture_mutation_rate.595        Each architectural parameter can be independently mutated with596        small random perturbations.597        598        Mutation Operations:599        - Controller dimension: ±32 units600        - Memory slots: ±16 units  601        - Memory dimension: ±8 units602        - Read/write heads: ±1 head (within bounds)603        604        Args:605            cfg: Original NTM configuration606            607        Returns:608            Mutated NTM configuration609        """610        new_cfg = deepcopy(cfg)611 612        if torch.rand(1) < self.cfg.architecture_mutation_rate:613            new_cfg.controller_dim = max(32, new_cfg.controller_dim + torch.randint(-32, 33, (1,)).item())614 615        if torch.rand(1) < self.cfg.architecture_mutation_rate:616            new_cfg.memory_slots = max(16, new_cfg.memory_slots + torch.randint(-16, 17, (1,)).item())617 618        if torch.rand(1) < self.cfg.architecture_mutation_rate:619            new_cfg.memory_dim = max(8, new_cfg.memory_dim + torch.randint(-8, 9, (1,)).item())620 621        if torch.rand(1) < self.cfg.architecture_mutation_rate:622            new_cfg.heads_read = max(1, min(4, new_cfg.heads_read + torch.randint(-1, 2, (1,)).item()))623 624        if torch.rand(1) < self.cfg.architecture_mutation_rate:625            new_cfg.heads_write = max(1, min(3, new_cfg.heads_write + torch.randint(-1, 2, (1,)).item()))626 627        return new_cfg628 629    def mutate_parameters(self, ntm: NeuralTuringMachine) -> NeuralTuringMachine:630        """Apply parameter mutations to NTM weights.631        632        Performs Gaussian perturbations to network parameters with633        probability mutation_rate per parameter. Creates a new NTM634        instance to avoid modifying the original.635        636        Mathematical Details:637        - Each parameter p mutated with probability mutation_rate638        - Mutation: p' = p + ε where ε ~ N(0, 0.01²)639        - Preserves network architecture, only modifies weights640        641        Args:642            ntm: Original NTM individual643            644        Returns:645            New NTM with mutated parameters646        """647        new_ntm = NeuralTuringMachine(ntm.cfg).to(self.cfg.device)648        new_ntm.load_state_dict(deepcopy(ntm.state_dict()))649        650        with torch.no_grad():651            for p in new_ntm.parameters():652                # Apply mutation mask (probability mutation_rate per element)653                mask = (torch.rand_like(p) < self.cfg.mutation_rate)654                p.add_(torch.randn_like(p) * 0.01 * mask)655                656        return new_ntm657 658    def crossover(self, parent1: NeuralTuringMachine, parent2: NeuralTuringMachine) -> NeuralTuringMachine:659        """Create offspring through architectural crossover.660        661        Combines architectural features from two parents by randomly662        selecting each architectural parameter from either parent.663        The resulting offspring has a new random weight initialization.664        665        Crossover Strategy:666        - Each architectural parameter chosen from parent1 or parent2 (50% each)667        - New weights initialized randomly (architectural crossover only)668        - Alternative: could implement parameter-level crossover669        670        Args:671            parent1: First parent NTM672            parent2: Second parent NTM673            674        Returns:675            Offspring NTM with hybrid architecture676        """677        cfg1, cfg2 = parent1.cfg, parent2.cfg678 679        # Create hybrid configuration680        new_cfg = NTMConfig(681            input_dim=self.cfg.input_dim,682            output_dim=self.cfg.output_dim,683            controller_dim=cfg1.controller_dim if torch.rand(1) < 0.5 else cfg2.controller_dim,684            memory_slots=cfg1.memory_slots if torch.rand(1) < 0.5 else cfg2.memory_slots,685            memory_dim=cfg1.memory_dim if torch.rand(1) < 0.5 else cfg2.memory_dim,686            heads_read=cfg1.heads_read if torch.rand(1) < 0.5 else cfg2.heads_read,687            heads_write=cfg1.heads_write if torch.rand(1) < 0.5 else cfg2.heads_write,688            init_std=0.1689        )690 691        # Create new individual with hybrid architecture692        child = NeuralTuringMachine(new_cfg).to(self.cfg.device)693        return child694 695    def initialize_population(self):696        """Create initial population with diverse random architectures.697        698        Generates population_size individuals with random architectural699        configurations to ensure diversity in the initial gene pool.700        Each individual is initialized with different structural parameters.701        """702        self.population = []703        for _ in range(self.cfg.population_size):704            cfg = self.create_random_config()705            ntm = NeuralTuringMachine(cfg).to(self.cfg.device)706            self.population.append(ntm)707 708    def evolve_generation(self) -> Dict[str, float]:709        """Execute one generation of evolutionary optimization.710        711        Complete generational evolution cycle:712        1. Evaluate all individuals in population713        2. Select elite individuals for survival714        3. Generate offspring through crossover and mutation715        4. Replace non-elite individuals with offspring716        5. Update statistics and generation counter717        718        Uses (μ + λ) evolution strategy with elite preservation719        to ensure best solutions are never lost.720        721        Returns:722            Dictionary containing generation statistics723        """724        # Step 1: Evaluate population fitness725        fitness_scores = []726        for ntm in self.population:727            fitness = self.evaluator.evaluate_fitness(ntm)728            fitness_scores.append(fitness['composite'])729 730        # Step 2: Selection - sort by fitness (descending)731        sorted_indices = sorted(range(len(fitness_scores)), key=lambda i: fitness_scores[i], reverse=True)732 733        # Step 3: Elite preservation734        elite_count = int(self.cfg.elite_ratio * self.cfg.population_size)735        elites = [self.population[i] for i in sorted_indices[:elite_count]]736 737        # Step 4: Generate offspring to fill remaining population738        new_population = elites.copy()739 740        while len(new_population) < self.cfg.population_size:741            if torch.rand(1) < 0.3 and len(elites) >= 2:742                # Crossover: select two random elite parents743                parent1, parent2 = torch.randperm(len(elites))[:2]744                child = self.crossover(elites[parent1], elites[parent2])745            else:746                # Mutation: select random elite parent747                parent_idx = torch.randint(0, elite_count, (1,)).item()748                parent = elites[parent_idx]749 750                if torch.rand(1) < 0.5:751                    # Parameter mutation752                    child = self.mutate_parameters(parent)753                else:754                    # Architectural mutation755                    new_cfg = self.mutate_architecture(parent.cfg)756                    child = NeuralTuringMachine(new_cfg).to(self.cfg.device)757 758            new_population.append(child)759 760        # Step 5: Update population and statistics761        self.population = new_population[:self.cfg.population_size]762        self.generation += 1763 764        best_fitness = max(fitness_scores)765        avg_fitness = sum(fitness_scores) / len(fitness_scores)766        self.best_fitness = max(self.best_fitness, best_fitness)767 768        return {769            'generation': self.generation,770            'best_fitness': best_fitness,771            'avg_fitness': avg_fitness,772            'best_ever': self.best_fitness773        }774 775    def run_evolution(self) -> List[Dict[str, float]]:776        """Execute complete evolutionary optimization run.777        778        Runs evolution for max_generations, tracking progress and779        printing periodic updates. Returns complete optimization780        history for analysis and visualization.781        782        Returns:783            List of generation statistics dictionaries784        """785        self.initialize_population()786 787        history = []788        for gen in range(self.cfg.max_generations):789            stats = self.evolve_generation()790            history.append(stats)791 792            # Periodic progress reporting793            if gen % 10 == 0:794                print(f"Gen {gen}: Best={stats['best_fitness']:.4f}, Avg={stats['avg_fitness']:.4f}")795 796        return history797 798    def get_best_model(self) -> NeuralTuringMachine:799        """Retrieve the best individual from current population.800        801        Evaluates all current individuals and returns the one802        with highest composite fitness score.803        804        Returns:805            Best NTM individual from population806        """807        fitness_scores = []808        for ntm in self.population:809            fitness = self.evaluator.evaluate_fitness(ntm)810            fitness_scores.append(fitness['composite'])811 812        best_idx = max(range(len(fitness_scores)), key=lambda i: fitness_scores[i])813        return self.population[best_idx]814 815###########################################################################################################################################816##################################################- - -   DEMO AND TESTING   - - -#########################################################817 818def test_evolutionary_turing():819    """Comprehensive test of evolutionary NTM optimization."""820    print(" Testing Evolutionary Turing Machine - Adaptive Memory Architecture Evolution")821    print("=" * 90)822    823    # Create evolutionary system824    config = EvolutionaryTuringConfig(825        population_size=20,  # Small for demo826        max_generations=30,827        input_dim=8,828        output_dim=8,829        mutation_rate=0.15,830        architecture_mutation_rate=0.1,831        elite_ratio=0.3,832        device='cpu'833    )834    835    system = EvolutionaryTuringMachine(config)836    837    print(f"Created Evolutionary Turing System:")838    print(f"  - Population size: {config.population_size}")839    print(f"  - Max generations: {config.max_generations}")840    print(f"  - Architecture mutation rate: {config.architecture_mutation_rate}")841    print(f"  - Parameter mutation rate: {config.mutation_rate}")842    print(f"  - Elite preservation: {config.elite_ratio*100:.0f}%")843    844    # Test individual components first845    print("\n Testing individual NTM...")846    test_config = system.create_random_config()847    test_ntm = NeuralTuringMachine(test_config).to(config.device)848    849    print(f"Random NTM architecture:")850    print(f"  - Controller: {test_config.controller_dim}D")851    print(f"  - Memory: {test_config.memory_slots} × {test_config.memory_dim}")852    print(f"  - Heads: {test_config.heads_read}R/{test_config.heads_write}W")853    854    # Test fitness evaluation855    fitness = system.evaluator.evaluate_fitness(test_ntm)856    print(f"\nFitness evaluation:")857    for task, score in fitness.items():858        print(f"  - {task.capitalize()}: {score:.3f}")859    860    # Test evolutionary operations861    print("\n Testing evolutionary operations...")862    863    # Test mutation864    mutated_ntm = system.mutate_parameters(test_ntm)865    print("✓ Parameter mutation successful")866    867    # Test architectural mutation868    mutated_config = system.mutate_architecture(test_config)869    print("✓ Architecture mutation successful")870    871    # Test crossover872    parent2_config = system.create_random_config()873    parent2 = NeuralTuringMachine(parent2_config).to(config.device)874    offspring = system.crossover(test_ntm, parent2)875    print("✓ Crossover operation successful")876    877    # Run short evolutionary optimization878    print(f"\n Running evolutionary optimization...")879    print("(This may take a few minutes)")880    881    history = system.run_evolution()882    883    print(f"\nEvolution completed!")884    print(f"  - Final generation: {system.generation}")885    print(f"  - Best fitness achieved: {system.best_fitness:.4f}")886    887    # Analyze evolution progress888    initial_fitness = history[0]['best_fitness']889    final_fitness = history[-1]['best_fitness']890    improvement = final_fitness - initial_fitness891    892    print(f"\nEvolution analysis:")893    print(f"  - Initial best fitness: {initial_fitness:.4f}")894    print(f"  - Final best fitness: {final_fitness:.4f}")895    print(f"  - Total improvement: {improvement:.4f}")896    print(f"  - Average generation improvement: {improvement/len(history):.4f}")897    898    # Get and analyze best individual899    best_ntm = system.get_best_model()900    best_fitness = system.evaluator.evaluate_fitness(best_ntm)901    902    print(f"\nBest evolved architecture:")903    print(f"  - Controller: {best_ntm.cfg.controller_dim}D")904    print(f"  - Memory: {best_ntm.cfg.memory_slots} × {best_ntm.cfg.memory_dim}")905    print(f"  - Heads: {best_ntm.cfg.heads_read}R/{best_ntm.cfg.heads_write}W")906    print(f"  - Parameters: {sum(p.numel() for p in best_ntm.parameters()):,}")907    908    print(f"\nBest individual performance:")909    for task, score in best_fitness.items():910        print(f"  - {task.capitalize()}: {score:.4f}")911    912    print("\n Evolutionary Turing Machine test completed!")913    print("✓ Population initialization and diversity")914    print("✓ Fitness evaluation on algorithmic tasks")915    print("✓ Architectural and parameter mutations")916    print("✓ Crossover and offspring generation")917    print("✓ Elite preservation and selection")918    print("✓ Multi-generational evolution and improvement")919    920    return True921 922def architecture_evolution_demo():923    """Demonstrate architectural evolution patterns."""924    print("\n" + "="*70)925    print(" ARCHITECTURE EVOLUTION DEMONSTRATION")926    print("="*70)927    928    config = EvolutionaryTuringConfig(population_size=10, max_generations=10)929    system = EvolutionaryTuringMachine(config)930    931    # Generate diverse initial architectures932    architectures = []933    for _ in range(5):934        cfg = system.create_random_config()935        architectures.append(cfg)936    937    print("Initial architecture diversity:")938    for i, cfg in enumerate(architectures):939        params = (cfg.controller_dim * cfg.controller_dim + 940                 cfg.memory_slots * cfg.memory_dim)941        print(f"  Arch {i+1}: {cfg.controller_dim}D controller, {cfg.memory_slots}×{cfg.memory_dim} memory, {params:,} params")942    943    # Show mutation effects944    print("\nMutation examples:")945    base_cfg = architectures[0]946    for i in range(3):947        mutated = system.mutate_architecture(base_cfg)948        print(f"  Mutation {i+1}: {mutated.controller_dim}D controller, {mutated.memory_slots}×{mutated.memory_dim} memory")949    950    print("\n Evolution discovers optimal architectures through natural selection!")951    print("   Larger controllers and memories often emerge for complex tasks")952 953if __name__ == "__main__":954    test_evolutionary_turing()955    architecture_evolution_demo()956