From 898b52edcb47bcb3e9d6106e74ca73e74ea01e70 Mon Sep 17 00:00:00 2001 From: sillylaird Date: Thu, 3 Sep 2026 00:33:59 +0000 Subject: import live www.sillylaird.ca webroot --- .../examples/augmentation_example.py | 117 +++++++++++ .../examples/config_example.yaml | 131 +++++++++++++ .../architecture-design/examples/custom_dataset.py | 50 +++++ .../architecture-design/examples/custom_model.py | 217 +++++++++++++++++++++ .../examples/pipeline_example.sh | 189 ++++++++++++++++++ 5 files changed, 704 insertions(+) create mode 100644 .agents/skills/architecture-design/examples/augmentation_example.py create mode 100644 .agents/skills/architecture-design/examples/config_example.yaml create mode 100644 .agents/skills/architecture-design/examples/custom_dataset.py create mode 100644 .agents/skills/architecture-design/examples/custom_model.py create mode 100755 .agents/skills/architecture-design/examples/pipeline_example.sh (limited to '.agents/skills/architecture-design/examples') diff --git a/.agents/skills/architecture-design/examples/augmentation_example.py b/.agents/skills/architecture-design/examples/augmentation_example.py new file mode 100644 index 0000000..949b033 --- /dev/null +++ b/.agents/skills/architecture-design/examples/augmentation_example.py @@ -0,0 +1,117 @@ +""" +Data Augmentation Example + +Demonstrates how to create a custom data augmentation function +following the architecture design pattern. +""" + +import torch +from typing import Dict +from src.data_module.augmentation import register_augmentation + + +@register_augmentation("time_shift") +def time_shift(signal: torch.Tensor, max_shift: int = 10) -> torch.Tensor: + """Randomly shift signal in time. + + Args: + signal: Input signal tensor of shape (channels, time_steps) + max_shift: Maximum number of steps to shift + + Returns: + Shifted signal tensor + """ + shift = torch.randint(-max_shift, max_shift + 1, (1,)).item() + return torch.roll(signal, shifts=shift, dims=-1) + + +@register_augmentation("amplitude_scale") +def amplitude_scale( + signal: torch.Tensor, + min_scale: float = 0.8, + max_scale: float = 1.2 +) -> torch.Tensor: + """Randomly scale signal amplitude. + + Args: + signal: Input signal tensor of shape (channels, time_steps) + min_scale: Minimum scaling factor + max_scale: Maximum scaling factor + + Returns: + Scaled signal tensor + """ + scale = torch.empty(1).uniform_(min_scale, max_scale).item() + return signal * scale + + +@register_augmentation("gaussian_noise") +def add_gaussian_noise( + signal: torch.Tensor, + mean: float = 0.0, + std: float = 0.1 +) -> torch.Tensor: + """Add Gaussian noise to signal. + + Args: + signal: Input signal tensor of shape (channels, time_steps) + mean: Mean of Gaussian noise + std: Standard deviation of Gaussian noise + + Returns: + Signal with added noise + """ + noise = torch.randn_like(signal) * std + mean + return signal + noise + + +# Example: Composed augmentation +@register_augmentation("composed") +def composed_augmentation(signal: torch.Tensor, cfg) -> torch.Tensor: + """Apply multiple augmentations in sequence. + + Args: + signal: Input signal tensor + cfg: Configuration object with augmentation parameters + + Returns: + Augmented signal tensor + """ + # Apply each augmentation based on config + if cfg.augmentation.time_shift: + signal = time_shift(signal, cfg.augmentation.max_shift) + + if cfg.augmentation.amplitude_scale: + signal = amplitude_scale( + signal, + cfg.augmentation.min_scale, + cfg.augmentation.max_scale + ) + + if cfg.augmentation.gaussian_noise: + signal = add_gaussian_noise( + signal, + cfg.augmentation.noise_mean, + cfg.augmentation.noise_std + ) + + return signal + + +# Usage in dataset class +class AugmentedDataset: + """Example dataset with augmentation support.""" + + def __init__(self, cfg): + self.cfg = cfg + self.augmentation_fn = AugmentationFactory(cfg.augmentation.name) + + def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]: + # Load signal + signal = self.load_signal(idx) + + # Apply augmentation (training mode only) + if self.training and self.augmentation_fn: + signal = self.augmentation_fn(signal, self.cfg) + + return {"signal": signal, "label": self.labels[idx]} diff --git a/.agents/skills/architecture-design/examples/config_example.yaml b/.agents/skills/architecture-design/examples/config_example.yaml new file mode 100644 index 0000000..8b61442 --- /dev/null +++ b/.agents/skills/architecture-design/examples/config_example.yaml @@ -0,0 +1,131 @@ +# Hydra Configuration Example +# This demonstrates the config structure for training pipeline + +# Run with: python train.py --config-name=config_example + +defaults: + - training: default + - dataset: brain_decoder + - model: transformer + - override hydra/launcher: submitit_local + +# Project settings +project_name: brain_decoder +experiment_name: transformer_baseline + +# Random seed +seed: 42 + +# Device settings +device: cuda +num_workers: 4 +pin_memory: true + +# Training configuration +training: + epochs: 100 + batch_size: 32 + learning_rate: 0.001 + weight_decay: 0.0001 + gradient_clip: 1.0 + early_stopping: + patience: 10 + min_delta: 0.001 + + # Optimizer settings + optimizer: adamw + optimizer_params: + betas: [0.9, 0.999] + eps: 1.0e-08 + + # Scheduler settings + scheduler: cosine + scheduler_params: + warmup_epochs: 10 + min_lr: 1.0e-06 + + # Checkpoint settings + checkpoint: + save_every: 5 + save_best: true + monitor: val_loss + mode: min + +# Dataset configuration +dataset: + name: brain_decoder + task: movement_classification + target_size: + movement_classification: 5 + reconstruction: [64, 64] + + # Data paths + data_dir: ${dir.data_dir}/processed + train_split: train + val_split: val + test_split: test + + # Data loading + num_channels: 64 + sampling_rate: 1000 + sequence_length: 1000 + + # Augmentation + augmentation: + name: composed + time_shift: true + amplitude_scale: true + gaussian_noise: true + max_shift: 10 + min_scale: 0.8 + max_scale: 1.2 + noise_mean: 0.0 + noise_std: 0.1 + +# Model configuration +model: + name: Transformer + hidden_dim: 256 + num_heads: 8 + num_layers: 6 + dropout: 0.1 + activation: gelu + + # Architecture specific + encoder: + input_dim: 64 + embedding_dim: 256 + positional_encoding: true + + decoder: + output_dim: ${dataset.target_size.${dataset.task}} + pooling: avg + +# Logging configuration +logging: + logger: wandb + log_every: 10 + log_grads: false + + # TensorBoard + tensorboard: true + histogram: true + + # W&B + wandb: + project: ${project_name} + entity: null + tags: ["baseline", "transformer"] + +# Output directories +dir: + data_dir: ./data + output_dir: ./outputs + log_dir: ${dir.output_dir}/logs + checkpoint_dir: ${dir.output_dir}/checkpoints + figure_dir: ${dir.output_dir}/figures + table_dir: ${dir.output_dir}/tables + +# Debug settings +debug: false +fast_dev_run: false diff --git a/.agents/skills/architecture-design/examples/custom_dataset.py b/.agents/skills/architecture-design/examples/custom_dataset.py new file mode 100644 index 0000000..c9fe002 --- /dev/null +++ b/.agents/skills/architecture-design/examples/custom_dataset.py @@ -0,0 +1,50 @@ +""" +Example: Creating a Custom Dataset + +This example shows how to add a new dataset following the project architecture. +""" + +from torch.utils.data import Dataset +from typing import Dict +import torch +from src.data_module.dataset import register_dataset + + +@register_dataset("time_series") +class TimeSeriesDataset(Dataset): + """ + Time series dataset for sequence modeling. + + Args: + sequences: List of time series sequences + seq_length: Fixed sequence length (pad or truncate if needed) + """ + + def __init__(self, sequences: list, seq_length: int = 100): + self.sequences = sequences + self.seq_length = seq_length + + def __len__(self) -> int: + return len(self.sequences) + + def __getitem__(self, i: int) -> Dict[str, torch.Tensor]: + sequence = self.sequences[i] + + # Pad or truncate to fixed length + if len(sequence) < self.seq_length: + padding = torch.zeros(self.seq_length - len(sequence)) + sequence = torch.cat([sequence, padding]) + else: + sequence = sequence[:self.seq_length] + + return { + "input": sequence, + "label": sequence, # For autoencoder, etc. + "length": torch.tensor(min(len(self.sequences[i]), self.seq_length)) + } + + +# Usage in training: +# from src.data_module.dataset import DatasetFactory +# dataset = DatasetFactory("time_series")(sequences=training_data, seq_length=128) +# dataloader = DataLoader(dataset, batch_size=32, shuffle=True) diff --git a/.agents/skills/architecture-design/examples/custom_model.py b/.agents/skills/architecture-design/examples/custom_model.py new file mode 100644 index 0000000..0e00752 --- /dev/null +++ b/.agents/skills/architecture-design/examples/custom_model.py @@ -0,0 +1,217 @@ +""" +Example: Creating a Custom Model + +This example shows how to add a new model following the project architecture. +IMPORTANT: Models use a config-driven pattern where __init__ only accepts cfg. + +Key Requirements: +- Use @register_model('ModelName') decorator +- __init__ accepts ONLY cfg parameter +- All hyperparameters come from cfg (cfg.model.*, cfg.dataset.*, etc.) +- forward() returns dict: {"loss": loss, "labels": labels, "logits": logits} +""" + +import torch +import torch.nn as nn +import torch.nn.functional as F +from typing import Dict, Optional + +# Import the register_model decorator +# Location may vary: src.model_module.brain_decoder or src.model_module.model +from src.model_module.brain_decoder import register_model + + +@register_model('SimpleMLP') +class SimpleMLP(nn.Module): + """ + Simple Multi-Layer Perceptron for classification tasks. + + Config structure ( Hydra YAML ): + model: + input_dim: 100 + hidden_dim: 256 + output_dim: 10 + num_layers: 3 + dropout: 0.1 + dataset: + task: classification # Used to get target_size + target_size: + classification: 10 + """ + + def __init__(self, cfg): + super().__init__() + + # Store config + self.cfg = cfg + + # Get task info from config + self.task = cfg.dataset.task + + # Build model - ALL parameters from cfg + self.input_dim = cfg.model.input_dim + self.hidden_dim = cfg.model.get('hidden_dim', 256) + self.output_dim = cfg.dataset.target_size[cfg.dataset.task] + self.num_layers = cfg.model.get('num_layers', 3) + self.dropout = cfg.model.get('dropout', 0.1) + + # Build layers + layers = [] + in_dim = self.input_dim + + for i in range(self.num_layers): + layers.extend([ + nn.Linear(in_dim, self.hidden_dim), + nn.ReLU(), + nn.Dropout(self.dropout) + ]) + in_dim = self.hidden_dim + + # Output layer + layers.append(nn.Linear(self.hidden_dim, self.output_dim)) + self.network = nn.Sequential(*layers) + + # Loss function + self.loss_fn = nn.CrossEntropyLoss() + + def forward( + self, + x: torch.Tensor, + labels: Optional[torch.Tensor] = None, + **kwargs + ) -> Dict[str, Optional[torch.Tensor]]: + """ + Forward pass. + + Args: + x: Input tensor of shape (batch_size, input_dim) + labels: Ground truth labels (optional, for training) + + Returns: + Dictionary with: + - loss: Computed loss (None if labels not provided) + - labels: Ground truth labels + - logits: Model predictions + """ + logits = self.network(x) + + loss = None + if labels is not None: + # Convert labels to long type if needed + if labels.dtype != torch.long: + labels = labels.long() + loss = self.loss_fn(logits, labels) + + return { + "loss": loss, + "labels": labels, + "logits": logits + } + + +# ============================================ +# Example with Training/Inference Modes +# ============================================ + +@register_model('SimpleMLPWithModes') +class SimpleMLPWithModes(nn.Module): + """ + MLP with separate training and inference logic. + Shows how to handle different modes using self.training. + """ + + def __init__(self, cfg): + super().__init__() + self.cfg = cfg + self.task = cfg.dataset.task + + self.input_dim = cfg.model.input_dim + self.hidden_dim = cfg.model.get('hidden_dim', 256) + self.output_dim = cfg.dataset.target_size[cfg.dataset.task] + + self.fc_in = nn.Linear(self.input_dim, self.hidden_dim) + self.ln = nn.LayerNorm(self.hidden_dim) + self.fc_out = nn.Linear(self.hidden_dim, self.output_dim) + self.loss_fn = nn.CrossEntropyLoss() + + # Test-time augmentation config + self.tta_times = cfg.model.get('tta_times', 1) + + def forward( + self, + x: torch.Tensor, + labels: Optional[torch.Tensor] = None, + **kwargs + ) -> Dict[str, Optional[torch.Tensor]]: + """ + Forward pass with training/inference modes. + """ + if self.training: + # Training mode + x = x.float() + x = self.fc_in(x) + x = self.ln(x) + x = F.relu(x) + logits = self.fc_out(x) + + loss = None + if labels is not None: + if labels.dtype != torch.long: + labels = labels.long() + loss = self.loss_fn(logits, labels) + + return { + "loss": loss, + "labels": labels, + "logits": logits + } + else: + # Inference mode with TTA + all_logits = [] + with torch.no_grad(): + x = x.float() + for _ in range(self.tta_times): + x_aug = x.clone() + # Apply TTA transformations here if needed + + x_aug = self.fc_in(x_aug) + x_aug = self.ln(x_aug) + x_aug = F.relu(x_aug) + logits = self.fc_out(x_aug) + all_logits.append(logits) + + # Average predictions + avg_logits = torch.mean(torch.stack(all_logits), dim=0) + + loss = None + if labels is not None: + if labels.dtype != torch.long: + labels = labels.long() + loss = self.loss_fn(avg_logits, labels) + + return { + "loss": loss, + "labels": labels, + "logits": avg_logits + } + + +# ============================================ +# Config Example (Hydra YAML) +# ============================================ +""" +# run/conf/model/simple_mlp.yaml + +model: + name: SimpleMLP + input_dim: 100 + hidden_dim: 256 + output_dim: 10 + num_layers: 3 + dropout: 0.1 + tta_times: 1 + +# Then in training pipeline: +# from src.model_module.brain_decoder import ModelFactory +# model = ModelFactory(cfg.model.name)(cfg) +""" diff --git a/.agents/skills/architecture-design/examples/pipeline_example.sh b/.agents/skills/architecture-design/examples/pipeline_example.sh new file mode 100755 index 0000000..abd7b46 --- /dev/null +++ b/.agents/skills/architecture-design/examples/pipeline_example.sh @@ -0,0 +1,189 @@ +#!/bin/bash +############################################################################### +# Training Pipeline Script +# +# This script demonstrates the standard training pipeline execution pattern. +# It handles environment setup, configuration, and execution with proper +# error handling and logging. +# +# Usage: +# ./run/pipeline/training/train.sh --config-name=config_example +############################################################################### + +set -e # Exit on error +set -o pipefail # Exit on pipe failure + +# Script configuration +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +EXPERIMENT_NAME="baseline_experiment" +TIMESTAMP=$(date +"%Y%m%d_%H%M%S") + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +############################################################################### +# Helper Functions +############################################################################### + +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +cleanup() { + log_info "Cleaning up..." + # Add cleanup logic here (e.g., kill background processes) +} + +trap cleanup EXIT + +############################################################################### +# Environment Setup +############################################################################### + +setup_environment() { + log_info "Setting up environment..." + + # Activate virtual environment if it exists + if [ -f "${PROJECT_ROOT}/.venv/bin/activate" ]; then + source "${PROJECT_ROOT}/.venv/bin/activate" + log_info "Activated virtual environment" + fi + + # Check required commands + command -v python >/dev/null 2>&1 || { log_error "Python not found"; exit 1; } + + # Set Python path + export PYTHONPATH="${PROJECT_ROOT}/src:${PYTHONPATH}" + log_info "PYTHONPATH set to: ${PYTHONPATH}" +} + +############################################################################### +# Configuration +############################################################################### + +parse_arguments() { + # Default values + CONFIG="default" + GPUS=0 + SEED=42 + + # Parse arguments + while [[ $# -gt 0 ]]; do + case $1 in + --config-name|-c) + CONFIG="$2" + shift 2 + ;; + --gpus|-g) + GPUS="$2" + shift 2 + ;; + --seed|-s) + SEED="$2" + shift 2 + ;; + *) + log_warn "Unknown argument: $1" + shift + ;; + esac + done + + log_info "Configuration: ${CONFIG}" + log_info "GPUs: ${GPUS}" + log_info "Seed: ${SEED}" +} + +############################################################################### +# Main Training Function +############################################################################### + +run_training() { + log_info "Starting training..." + + # Output directory for this run + OUTPUT_DIR="${PROJECT_ROOT}/outputs/${EXPERIMENT_NAME}/${TIMESTAMP}" + mkdir -p "${OUTPUT_DIR}" + + log_info "Output directory: ${OUTPUT_DIR}" + + # Training command with Hydra + python "${PROJECT_ROOT}/train.py" \ + --config-name="${CONFIG}" \ + seed=${SEED} \ + dir.output_dir="${OUTPUT_DIR}" \ + training.device=cuda \ + hydra.output_dir="${OUTPUT_DIR}/hydra" \ + hydra.run.dir="${OUTPUT_DIR}/hydra" || { + log_error "Training failed!" + exit 1 + } + + log_info "Training completed successfully!" +} + +############################################################################### +# Post-Processing +############################################################################### + +post_process() { + log_info "Post-processing results..." + + # Copy logs to output directory + if [ -f "${OUTPUT_DIR}/hydra/*.log" ]; then + cp "${OUTPUT_DIR}/hydra/"*.log "${OUTPUT_DIR}/" + fi + + # Generate summary + log_info "Run summary:" + log_info " Config: ${CONFIG}" + log_info " Seed: ${SEED}" + log_info " Output: ${OUTPUT_DIR}" + + # Print path to best checkpoint + BEST_CHECKPOINT=$(find "${OUTPUT_DIR}" -name "best*.pt" | head -n 1) + if [ -n "${BEST_CHECKPOINT}" ]; then + log_info " Best checkpoint: ${BEST_CHECKPOINT}" + fi +} + +############################################################################### +# Main Execution +############################################################################### + +main() { + log_info "==========================================" + log_info "Training Pipeline" + log_info "==========================================" + + # Setup + setup_environment + + # Parse arguments + parse_arguments "$@" + + # Run training + run_training + + # Post-process + post_process + + log_info "==========================================" + log_info "Pipeline completed successfully!" + log_info "==========================================" +} + +# Run main function +main "$@" -- cgit v1.2.3