Showing posts with label ML. Show all posts
Showing posts with label ML. Show all posts

Making a start on Hilbert Spaces and matrix calculations using CPU

Mike's Notes

Working notes on finding a way for Pipi to use genetic algorithms with Hilbert Spaces and matrix calculations. A desktop exercise with Google at the moment. Computational experiments on CPU to come. Some generated Python and CFML sample code below to start playing with. 

It looks promising; it might help address limitations of other methods for using fitness landscapes. The output curves do look better.

This might void the need to use a Cerebras wafer for these fitness landscape calculations.

The next step is to watch free maths lectures on YouTube, then do a lot of drawings and process diagrams. Then play with code and see what happens. 😎

The maths formulas were in LaTeX format and were converted to non-LaTeX format using the free online LatexConverter.

Note

Nothing has been verified or tested. 😎

Resources

References

  • Reference

Repository

  • Home > Ajabbi Research > Library >
  • Home > Handbook > 

Last Updated

03/09/2026

Making a start on Hilbert Spaces and matrix calculations using CPU

By: Mike Peters & Google Search AI Mode
On a Sandy Beach: 30/08/2026

Mike Peters: Mike is the inventor and architect of Pipi and the founder of Ajabbi.

Google Search AI Mode: Free and surprisingly good.

...

Hilbert Spaces

Hilbert spaces use matrix calculations extensively, especially when the space is finite-dimensional or when dealing with linear operators. Here is how they connect:

  1. Finite-Dimensional Spaces
    • Direct Equivalence: An (n)-dimensional Hilbert space is isomorphic to (ℂⁿ) (complex numbers) or (ℝⁿ) (real numbers).
    • Vectors: Elements in the space are represented as column vectors.
    • Operators: Linear operators acting on the space are represented as (n × n) matrices.
    • Inner Product: The inner product is calculated using matrix multiplication (specifically, the conjugate transpose of a vector multiplied by another vector).
  2. Infinite-Dimensional Spaces
    • Infinite Matrices: In infinite-dimensional Hilbert spaces (like L²), the space of square-integrable functions, operators can be represented as infinite matrices.
    • Basis Dependence: By choosing an orthonormal basis, any linear operator can be written as a matrix with infinitely many rows and columns.
  3. Quantum Mechanics (Matrix Mechanics)
    • State Vectors: Quantum states are vectors in a Hilbert space.
    • Observables: Physical properties (like position, momentum, or spin) are Hermitian operators.
    • Heisenberg's Formulation: Werner Heisenberg's formulation of quantum mechanics is literally called "matrix mechanics" because it uses matrix calculations to solve Hilbert space problems.

In machine learning, Hilbert spaces—specifically Reproducible Kernel Hilbert Spaces (RKHS)—rely heavily on matrix calculations to make complex, non-linear data linearly separable.

Instead of explicitly mapping data points to an infinite-dimensional space, ML algorithms use the Kernel Trick to compute everything via an N × N matrix, where N is the number of data samples.

The Gram (Kernel) Matrix

The core matrix calculation in RKHS is the Gram Matrix (or Kernel Matrix), denoted as K.

  • Formula: (Kᵢⱼ = k(xᵢ, xⱼ) = ⟨ Φ(xᵢ), Φ(xⱼ) ⟩_{H})
  • Meaning: Each entry i,j is the inner product of data points (xᵢ) and (xⱼ) in the Hilbert space (H).
  • Properties: It is always a symmetric and positive semi-definite (PSD) matrix.

Core Matrix Operations in RKHS

Machine learning algorithms manipulate this kernel matrix using standard linear algebra:

  • Matrix Inversion: Used in Kernel Ridge Regression to solve for weights: (Kv = λ v).
  • Eigenvalue Decomposition: Used in Kernel PCA to find non-linear principal components by solving (Kv = λ v).
  • Quadratic Programming: Used in Support Vector Machines (SVMs) to maximise margins using matrix-vector products involving (K).

Computational Bottlenecks

Because (K) scales with the number of data points, scaling Hilbert space methods requires specialised matrix approximations:

  • Nyström Approximation: Factorises a low-rank sub-matrix to approximate the massive N × N matrix.
  • Random Fourier Features: Avoids the exact matrix calculation entirely by mapping data into a randomised, lower-dimensional space.

Graph image

Genetic Algorithms

Combining genetic algorithms (GAs) with Reproducing Kernel Hilbert Spaces (RKHS) is entirely possible, highly effective, and actively used in machine learning research.

In this hybrid framework, the fitness landscape is modelled as a continuous function belonging to an RKHS. This approach addresses a major limitation of traditional GAs: evaluating an individual's true fitness can be computationally expensive (e.g., running a physics simulation or a wet-lab experiment). The RKHS serves as an intelligent surrogate model.

Graph image

Core Mathematical Engines At Work

To make this framework highly effective, three core components work in tandem:

  • The Representer Theorem: This is the mathematical backbone of your RKHS surrogate. It guarantees that your continuous fitness approximation (̂{f}(x)) can be written as a finite linear combination of kernel evaluations centred at your sampled points:

(ᶠ(x)=∑ ᵢ₌₁ᴺα ᵢK(x,xᵢ))

  • This scales the search space completely independently of its true dimensionality, reducing prediction cost to a simple (O(N)) vector dot product.
  • Informed Exploration vs. Exploitation: Instead of letting the GA evaluate individuals blindly on the surrogate, you can use the RKHS variance (uncertainty) to construct an Acquisition Function (like Expected Improvement or Upper Confidence Bound). The GA then maximises this acquisition function rather than the raw estimated fitness, forcing the algorithm to intelligently search unmapped areas of the landscape.
  • Gram Matrix Regularisation: As new true data points are evaluated, they enter the Gram Matrix. To prevent numerical instability or overfitting as (N) grows, a small regularisation ridge ((λ I)) is maintained, smoothly adjusting the landscape's rigidity without rebuilding the framework from scratch.

How the Combination Works

[GA Population] ---> [Evaluate on RKHS Surrogate] ---> [Select & Crossover] ^ | |__________________ [Update RKHS with True Data] _________|


  1. The RKHS as the Fitness Landscape: You treat the unknown fitness landscape (f(x)) as a smooth function within an RKHS. By evaluating a small set of initial points, you use Kernel Ridge Regression or Kriging (Gaussian Process Regression) to build a continuous, analytical approximation of the landscape.
  2. Genetic Search on the Surrogate: The GA searches this approximated RKHS landscape. Because evaluating the RKHS kernel matrix is mathematically cheap (O(N)) for a new prediction, the GA can evolve through thousands of generations in seconds.
  3. Adaptive Sampling (Bayesian Optimisation): The best candidates found by the GA are evaluated using the true, expensive fitness function. These new data points are added back into the Gram matrix, updating the RKHS landscape to make it more accurate where it matters most.

Key Advantages of This Hybrid Approach

  • Global Search in Infinite Dimensions: Traditional GAs struggle with high-dimensional continuous spaces. By mapping the search space into an RKHS via a kernel (like the Gaussian RBF kernel), the GA implicitly searches a highly expressive, infinite-dimensional feature space.
  • Guaranteed Smoothness: Functions in an RKHS are mathematically smooth and bounded [6]. This eliminates extreme, discontinuous noise in the fitness landscape, allowing the GA to find gradient-like paths toward global optima without getting trapped in microscopic local minima.
  • Exact Kernel Alignment: You can use a GA to optimise the hyperparameters of the RKHS kernel itself (e.g., finding the optimal length-scale (σ ) of an RBF kernel) to perfectly align the geometry of the Hilbert space with your actual problem landscape.

Real-World Use Cases

  • Protein Design & Biochemistry: Designing new molecules where the "fitness" is binding affinity. The RKHS models the chemical landscape based on known proteins, and the GA mutates amino acid sequences to find optimal binders.
  • Hyperparameter Optimisation: Optimising complex neural network architectures where the evaluation landscape is highly non-linear and expensive to compute.
  • Robotics Control: Evolving locomotion parameters for robotics where physical testing is slow or dangerous.

Implementation Challenges

  • The Matrix Bottleneck Returns: As your GA discovers more points and updates the RKHS landscape, your Gram matrix (K) grows. Inverting this matrix to update the landscape scales at O(N³). You will need to use Nyström approximations or sparse online kernel methods if your total evaluation points exceed 10,000.
  • Exploration vs. Exploitation: GAs are aggressive exploiters. If your RKHS surrogate model is inaccurate in an unexplored region, the GA might get stuck optimising a "fake" peak. You must include an uncertainty metric (like the variance in a Gaussian Process) to force the GA to explore unknown areas.

Kernel Ridge Regression or Kriging (Gaussian Process Regression)

Kernel Ridge Regression (KRR) and Kriging (Gaussian Process Regression, or GPR) are both kernel-based methods for learning non-linear functions from data, but they differ fundamentally in their underlying statistical framework, objective functions, and outputs.

Key Differences

  • Core Approach: KRR minimises a regularised mean-squared error loss function in a Reproducing Kernel Hilbert Space (RKHS). GPR uses a probabilistic (Bayesian) approach, defining a Gaussian process prior over functions and updating it with a likelihood function based on observed data.
  • Uncertainty Estimation: KRR outputs point predictions only. GPR (and Kriging) naturally quantifies uncertainty, providing full posterior distributions, variance estimates, and confidence intervals.
  • Hyperparameter Optimisation: KRR typically optimises kernel parameters using grid search with cross-validation on a loss function. GPR optimises hyperparameters via gradient ascent on the marginal likelihood.
  • Terminology & Origin: Kriging originated in geostatistics (mining) to find the Best Linear Unbiased Predictor (BLUP), whereas GPR stems from machine learning and stochastic processes. Mathematically, standard Kriging is equivalent to GPR under matching covariance and prior assumptions

When to Use Which

  • Use Kernel Ridge Regression when: You need a fast, deterministic, regularised non-linear regressor and do not require predictive uncertainty bounds.
  • Use Kriging / Gaussian Process Regression when: You need confidence intervals for predictions, want to optimise hyperparameters via marginal likelihood, or are modelling spatial/geostatistical data.

Python Code (not tested)

Here is a clean, modular Python template using scikit-learn to stitch a Genetic Algorithm loop to a Gaussian Process (RKHS surrogate) framework.

import numpy as np
from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.gaussian_process.kernels import Matern

# --- 1. CONFIGURATION & SIMULATION BACKEND ---
BOUNDS = np.array([[-5.0, 5.0], [-5.0, 5.0]])  # 2D search space boundaries
N_DIM = BOUNDS.shape[0]
POP_SIZE = 20
GENERATIONS = 10
SURROGATE_MAX_ITER = 5   # Active learning/Bayesian optimization loops

def true_expensive_fitness(x):
    """
    Represents your expensive simulation or physical experiment.
    Expects a 1D array of shape (N_DIM,). Returns a scalar.
    """
    # Example: Multi-modal Ackley function (minimization converted to maximization)
    return -1 * (20 * np.exp(-0.2 * np.sqrt(0.5 * np.sum(x**2))) + 
                 np.exp(0.5 * np.sum(np.cos(2 * np.pi * x))) - 20 - np.e)

# --- 2. SURROGATE MODEL (RKHS / GAUSSIAN PROCESS) ---
# Using Matérn kernel with explicit noise handling (alpha) as regularization
kernel = Matern(nu=2.5)
surrogate = GaussianProcessRegressor(kernel=kernel, alpha=1e-6, normalize_y=True, random_state=42)

# --- 3. GENETIC ALGORITHM CORE ENGINE (OPERATING ON SURROGATE) ---
def init_population(pop_size, bounds):
    return np.random.uniform(bounds[:, 0], bounds[:, 1], size=(pop_size, len(bounds)))

def crossover(parent1, parent2):
    # Blend crossover (BLX-alpha)
    alpha = 0.5
    gamma = (1 + 2 * alpha) * np.random.random(size=N_DIM) - alpha
    return parent1 + gamma * (parent2 - parent1)

def mutate(individual, bounds, rate=0.2, scale=0.5):
    for i in range(len(individual)):
        if np.random.random() < rate:
            individual[i] += np.random.normal(0, scale)
            individual[i] = np.clip(individual[i], bounds[i, 0], bounds[i, 1])
    return individual

def run_ga_on_surrogate(model, bounds, pop_size, generations):
    """
    Standard GA that queries the cheap RKHS surrogate model instead of the true fitness.
    """
    population = init_population(pop_size, bounds)
    
    for _ in range(generations):
        # O(N) evaluation using the trained kernel model
        # predict() returns (mean, std). We maximize the predicted mean fitness.
        fitnesses, _ = model.predict(population, return_std=True)
        
        # Rank-based selection
        idx = np.argsort(fitnesses)[::-1]
        population = population[idx]
        
        # Breed next generation
        next_gen = list(population[:2])  # Elitist preservation
        while len(next_gen) < pop_size:
            p1, p2 = population[np.random.randint(0, 5)], population[np.random.randint(0, 5)]
            child = crossover(p1, p2)
            child = mutate(child, bounds)
            next_gen.append(child)
            
        population = np.array(next_gen)
        
    # Return best candidate found by GA in this surrogate landscape
    best_idx = np.argmax(model.predict(population, return_std=False))
    return population[best_idx]

# --- 4. THE ACTIVE LEARNING HYBRID LOOP ---
# Step A: Initialize with a small Latin Hypercube or random experimental design
X_train = init_population(pop_size=10, bounds=BOUNDS)
y_train = np.array([true_expensive_fitness(x) for x in X_train])

print("Starting Hybrid GA-RKHS Optimization Engine...\n")

for loop in range(SURROGATE_MAX_ITER):
    print(f"--- Iteration {loop + 1}/{SURROGATE_MAX_ITER} ---")
    print(f"Gram Matrix Size: {X_train.shape[0]} points")
    
    # Step B: Fit/Update RKHS surrogate model
    surrogate.fit(X_train, y_train)
    
    # Step C: Let the GA aggressively explore the surrogate landscape
    # This costs thousands of operations but takes milliseconds
    proposed_candidate = run_ga_on_surrogate(surrogate, BOUNDS, POP_SIZE, GENERATIONS)
    
    # Step D: Adaptive Sampling (Evaluate the candidate on the true simulation)
    true_score = true_expensive_fitness(proposed_candidate)
    print(f"GA Proposed Vector: {np.round(proposed_candidate, 4)}")
    print(f"True Fitness Score: {true_score:.4f}")
    
    # Step E: Augment data (Updates the Gram matrix for the next iteration)
    X_train = np.vstack([X_train, proposed_candidate])
    y_train = np.append(y_train, true_score)
    print(f"Current Best Score Found: {np.max(y_train):.4f}\n")

print("Optimization Complete.")

Enhancements 

  • Regularisation (Ridge): The alpha=1e-6 in GaussianProcessRegressor serves as the λI identity-matrix offset. This prevents numerical inversion crashes if the GA proposes points structurally close to existing data.
  • Normalisation Engine: normalize_y=True transforms unstable fitness functions to a standard normal distribution mapping, stabilising kernel hyperparameter tuning during .fit().
  • Scalability Hand-off: run_ga_on_surrogate can run for massive populations or generations; it never interacts with true_expensive_fitness, protecting your heavy computation budget.

CFML Code (not tested)

<cfscript>
/**
 * CFML Hybrid GA-RKHS Optimization Engine
 */

// --- 1. CONFIGURATION & CONFIG MATRIX ---
variables.bounds = [ {min: -5.0, max: 5.0}, {min: -5.0, max: 5.0} ]; // 2D Search Space
variables.popSize = 20;
variables.generations = 10;
variables.surrogateMaxIter = 5;

/**
 * Represents your expensive simulation or backend physical experiment.
 * Minimisation problem converted to Maximisation.
 */
public numeric function trueExpensiveFitness(required array x) {
    // Standard multi-modal benchmark proxy
    var sumSq = 0;
    var sumCos = 0;
    var n = arrayLen(arguments.x);
    
    for (var i = 1; i <= n; i++) {
        sumSq += arguments.x[i] ^ 2;
        sumCos += cos(2 * pi() * arguments.x[i]);
    }
    
    var term1 = -20.0 * exp(-0.2 * sqrt(0.5 * sumSq));
    var term2 = -exp(0.5 * sumCos);
    return -1 * (term1 + term2 - 20 - exp(1));
}

// --- 2. SURROGATE ENGINE (Continuous Analytical Proxy) ---
/**
 * Approximates f(x) using existing Gram dataset via standard RBF/IDW Kernel.
 */
public numeric function predictSurrogateFitness(required array x, required array xTrain, required array yTrain) {
    var totalWeight = 0;
    var weightedSum = 0;
    var p = 2; // Power parameter for spatial continuity
    var regularizationRidge = 1e-6; // Prevents division by zero on exact matches
    
    for (var i = 1; i <= arrayLen(arguments.xTrain); i++) {
        var distSq = 0;
        for (var d = 1; d <= arrayLen(arguments.x); d++) {
            distSq += (arguments.x[d] - arguments.xTrain[i][d]) ^ 2;
        }
        var dist = sqrt(distSq) + regularizationRidge;
        var weight = 1.0 / (dist ^ p);
        
        totalWeight += weight;
        weightedSum += weight * arguments.yTrain[i];
    }
    
    return weightedSum / totalWeight;
}

// --- 3. GENETIC ALGORITHM CORE ENGINE (OPERATING ON CHEAP SURROGATE) ---
public array function initPopulation(required numeric popSize, required array bounds) {
    var pop = [];
    for (var i = 1; i <= arguments.popSize; i++) {
        var ind = [];
        for (var d = 1; d <= arrayLen(arguments.bounds); d++) {
            arrayAppend(ind, rand() * (arguments.bounds[d].max - arguments.bounds[d].min) + arguments.bounds[d].min);
        }
        arrayAppend(pop, ind);
    }
    return pop;
}

public array function crossover(required array p1, required array p2) {
    var child = [];
    var alpha = 0.5; // Blend Crossover parameter
    for (var d = 1; d <= arrayLen(arguments.p1); d++) {
        var gamma = (1 + 2 * alpha) * rand() - alpha;
        arrayAppend(child, arguments.p1[d] + gamma * (arguments.p2[d] - arguments.p1[d]));
    }
    return child;
}

public array function mutate(required array individual, required array bounds, numeric rate=0.2, numeric scale=0.5) {
    var mutated = duplicate(arguments.individual);
    for (var d = 1; d <= arrayLen(mutated); d++) {
        if (rand() < arguments.rate) {
            // Box-Muller transform for normal distribution mutation
            var u1 = rand(); var u2 = rand();
            if(u1 == 0) u1 = 0.0001;
            var normalRandom = sqrt(-2.0 * log(u1)) * cos(2.0 * pi() * u2);
            
            mutated[d] += normalRandom * arguments.scale;
            // Clip boundaries
            if (mutated[d] < arguments.bounds[d].min) mutated[d] = arguments.bounds[d].min;
            if (mutated[d] > arguments.bounds[d].max) mutated[d] = arguments.bounds[d].max;
        }
    }
    return mutated;
}

public array function runGaOnSurrogate(required array xTrain, required array yTrain, required array bounds, required numeric popSize, required numeric generations) {
    var population = initPopulation(arguments.popSize, arguments.bounds);
    
    for (var gen = 1; gen <= arguments.generations; gen++) {
        // Evaluate complete generation instantly on the analytical surrogate proxy
        var scoredPop = [];
        for (var i = 1; i <= arrayLen(population); i++) {
            arrayAppend(scoredPop, {
                vector: population[i],
                score: predictSurrogateFitness(population[i], arguments.xTrain, arguments.yTrain)
            });
        }
        
        // Rank-based Sort Descending
        arraySort(scoredPop, function(a, b) {
            return b.score > a.score ? 1 : (b.score < a.score ? -1 : 0);
        });
        
        // Rebuild next generation
        var nextGen = [ scoredPop[1].vector, scoredPop[2].vector ]; // Elitist strategy
        while (arrayLen(nextGen) < arguments.popSize) {
            var parent1 = scoredPop[randRange(1, 5)].vector;
            var parent2 = scoredPop[randRange(1, 5)].vector;
            var child = crossover(parent1, parent2);
            child = mutate(child, arguments.bounds);
            arrayAppend(nextGen, child);
        }
        
        // Update population pointer
        for (var k = 1; k <= arrayLen(nextGen); k++) {
            population[k] = nextGen[k];
        }
    }
    
    // Return best vector from final generation iteration
    return population[1];
}

// --- 4. EXECUTING THE HYBRID SEED LOOP ---
writeOutput("Starting CFML Hybrid GA-RKHS Optimization Engine...<br><br>");

// Step A: Initialize Gram Dataset
variables.X_train = initPopulation(10, variables.bounds);
variables.y_train = [];
for (var i = 1; i <= arrayLen(variables.X_train); i++) {
    arrayAppend(variables.y_train, trueExpensiveFitness(variables.X_train[i]));
}

// Step B: Active Learning Global Intercept Loop
for (var loop = 1; loop <= variables.surrogateMaxIter; loop++) {
    writeOutput("--- Iteration #loop#/#variables.surrogateMaxIter# ---<br>");
    writeOutput("Gram Matrix Dataset Size: " & arrayLen(variables.X_train) & " points<br>");
    
    // Step C: Let the GA scan the math surrogate space aggressively
    var proposedCandidate = runGaOnSurrogate(variables.X_train, variables.y_train, variables.bounds, variables.popSize, variables.generations);
    
    // Step D: Run costly physical simulation check on candidate
    var trueScore = trueExpensiveFitness(proposedCandidate);
    
    writeOutput("GA Proposed Vector: [" & arrayToList(proposedCandidate, ", ") & "]<br>");
    writeOutput("True Real-World Fitness Score: " & numberFormat(trueScore, "0.0000") & "<br>");
    
    // Step E: Augment data matrices
    arrayAppend(variables.X_train, proposedCandidate);
    arrayAppend(variables.y_train, trueScore);
    
    writeOutput("Current System Best Score: " & numberFormat(arrayMax(variables.y_train), "0.0000") & "<br><br>");
}

writeOutput("Optimization Execution Cycle Terminated.");
</cfscript>

Key Architectural Shifts in the CFML Variant

  • Script-Only Architecture: Written purely within <cfscript> blocks to remove legacy tag overhead and maximise processing throughput for vector arithmetic loops.
  • Native Box-Muller Generator: Because ColdFusion’s native rand() produces a flat uniform distribution, a standard Box-Muller transformation was written directly into the mutate() method to correctly inject normal (Gaussian) distribution mutations.
  • Surrogate Mapping Array Matrix: Replaces Python arrays with native array structs. The predictSurrogateFitness method models continuous field behaviours natively via distances weighted dynamically across the current known points.

Lec 50 Kernel Ridge Regression (KRR)

NPTEL - Indian Institute of Science, Bengaluru

YouTube: 09/2025 35:13

How to Accelerate Protein Structure Prediction at Proteome-Scale

Mike's Notes

Impressive and socially useful. The original article has many links.

Resources

References

  • Reference

Repository

  • Home > Ajabbi Research > Library > Subscriptions > NVIDEA Developer
  • Home > Handbook > 

Last Updated

19/04/2026

How to Accelerate Protein Structure Prediction at Proteome-Scale

By: Christian Dallago, Kyle Tretina, Kyle Gion and Neel Patel
NVIDEA Developer: 09/04/2026

Chris Dallago is a computer scientist turned bioinformatician, passionately models biological mechanisms using machine learning. He's advanced bio-sequence representation learning, contributing to its establishment, notably in transformer models. Chris is dedicated to solving scarce data problems, such as designing proteins for therapeutic and industrial applications.

Kyle Tretina is a product marketing leader at NVIDIA, focused on advancing AI for digital biology and drug discovery. He drives the strategy and storytelling behind BioNeMo and our work with BioPharma, shaping how next-generation foundation models and GPU-accelerated microservices transform molecular and protein design. With a PhD in molecular microbiology and immunology, Kyle bridges science and strategy, translating breakthroughs in AI, chemistry, and biology into platforms that accelerate discovery for researchers, startups, and pharmaceutical companies worldwide.

Kyle Gion is a product manager for Research at NVIDIA, where he translates R&D in digital biology and molecular science into impactful products. He focuses on guiding research that applies computational biology, computational chemistry, and AI to life sciences, drawing on experience that spans both building scientific software and developing cystic fibrosis therapies. Kyle earned his bachelor's and master's degrees in Chemical Engineering from Brown University.

Neel Patel is a drug discovery scientist at NVIDIA, focusing on cheminformatics and computational structural biology. Before joining NVIDIA, Neel was a computational chemist in big pharma, where he worked on structure-based drug design. He holds a Ph.D. from the University of Southern California. He lives in San Diego with his family and enjoys hiking and traveling.

Proteins rarely function in isolation as individual monomers. Most biological processes are governed by proteins interacting with other proteins, forming protein complexes whose structures are described in the hierarchy of protein structure as the quaternary representation. 

This represents one level of complexity up from tertiary representations, the 3D structure of monomers, which are commonly known since the emergence of AlphaFold2 and the creation of the Protein Data Bank.

Structural information for the vast majority of complexes remains unavailable. While the AlphaFold Protein Structure Database (AFDB), jointly developed by Google DeepMind and EMBL’s European Bioinformatics Institute (EMBL-EBI), transformed access to monomeric protein structures, interaction-aware structural biology at the proteome scale has remained a bottleneck with unique challenges:

  • Massive combinatorial interaction space
  • High computational cost for multiple sequence alignment (MSA) generation and protein folding
  • Inference scaling across millions of complexes
  • Confidence calibration and benchmarking
  • Dataset consistency and biological interpretability

In recent work, we extended the AFDB with large-scale predictions of homomeric protein complexes generated by a high-throughput pipeline based on AlphaFold-Multimer—made possible by NVIDIA accelerated computing. Additionally, we predicted heteromeric complexes to compare the accuracy of different complex prediction modalities.

In particular, for the predictions of these datasets, we leveraged kernel-level accelerations from MMseqs2-GPU for MSA generation, and NVIDIA TensorRT and NVIDIA cuEquivariance for deep-learning-based protein folding. We then mapped the workload to HPC-scale inference by maximizing the utilization of all available GPUs, including scale-out to multiple clusters.

This blog describes the major principles we adopted to increase protein folding throughput, from adopting libraries and SDKs to optimizations to reduce the computational complexity of the workload. These principles can help you set up a similar pipeline yourself by borrowing from the techniques we used to create this new dataset.

So, if you are a:

  • Computational biologist scaling structure prediction pipelines
  • AI researcher training generative protein models
  • HPC engineer optimizing GPU workloads
  • Bioinformatician team building structural resources

You will learn how to:

  • Design a proteome-scale complex prediction strategy
  • Separate MSA generation from structure inference for efficiency
  • Scale AlphaFold-Multimer workflows across GPU clusters

Prerequisites

  • Technical knowledge
  • Python and shell scripting
  • SLURM as HPC workload scheduler
  • Basic structural biology 
  • Familiarity with AlphaFold/ColabFold/OpenFold or similar pipelines

Infrastructure

We describe scaling on a multi-GPU and multi-node NVIDIA DGX H100 Superpod cluster

This cluster includes high-speed storage to store MSAs and intermediate outputs

Software

  • Access to MMseqs2-GPU
  • Familiarity with TensorRT

If not using a model with integrated cuEquivariance, knowledge about triangular attention and multiplication operations 

Procedure/Steps

1. Define the dataset you’d like to compute

Begin by defining the scope of prediction. Because predicting protein complexes can become a combinatorial problem, it’s useful to understand what may be most interesting. In some cases, if your proteomes are small enough, an all-against-all (dimeric) complex prediction might be tractable; however, this could change if you want to predict large datasets of proteomes.

Here’s how we decided to go about it:

  • Homomeric complexes: We selected all proteomes represented in the AFDB and sorted them by perceived importance (e.g., proteomes of human concern or commonly accessed). This allowed us to rank proteomes for computation in a particular order, making execution more manageable.
  • Heteromeric complexes: This is where things can get complicated, fast. For our heteromeric runs, we decided to focus on complexes originating from several reference proteomes and proteomes included in the WHO list of important proteomes. As there’s an intractable number of combinations of complexes that can be derived from these proteomes, for our runs, we focused on dimers (complexes of two proteins), within the same proteome (no inter-proteome complexes) that had “physical” interaction evidence in STRING. As we sought coverage, we decided to consider all interactions reported in STRING for these proteomes, rather than further filtering. Evidence in the literature suggests that filtering for STRING scores >700 can further reduce the number of inputs while increasing the likelihood of well-predicted complexes.

2. Decoupling MSA generation from structure prediction

MSA generation and structure inference are both compute-intensive but scale differently, as we recently presented in a white paper. We thus approached these computations as separate steps and implemented separate SLURM pipelines. In general, for optimal use of a node, we set up MSA generation and structure prediction this way.

MSA generation

We generated MSAs using colabfold_search with the MMseqs2-GPU backend. While MMSeqs2-GPU scales across GPUs on a node natively, we chose to spawn one MMseqs2-GPU server process per GPU on a node for easier process management. In colabfold_search, the GPUs are only used for the ungappedfilter stages and not the subsequent alignment stages (which are multithreaded CPU processes).

Therefore, we can stack colabfold_search calls and start the next one once the GPU is no longer used by the previous one, by monitoring the colabfold_search output, to reduce GPU idle time.

Although this approach oversubscribes CPU resources, in practice, we found that on a DGX H100 node, up to 25% of the overall increase in throughput can be achieved with three staggered colabfold_search processes, at the expense of slower processing of individual input chunks. 

On determining reasonable input chunk sizes, there are two factors to consider. Smaller chunk sizes result in more chunks, which means more per-process overheads, such as database loading, which can take a couple of minutes each, even on fast storage. (Pre-staging the databases on the fastest storage available, such as the on-node SSD, helps with throughput as well.) On the other hand, larger chunks take more time to finish. On a SLURM cluster with a job time limit, this results in more unfinished chunks.

The sweet spot will depend on the cluster configuration, but for our DGX H100 node with a 4-hour wall time limit, the chunk size of 300 sequences seemed to work well with the staggering colabfold_search approach.

Structure prediction

In order to increase structure prediction throughput, we leveraged both optimizations in data handling for JAX-based folding through ColabFold, as well as accelerated tooling developed at NVIDIA, including TensorRT, and cuEquivariance for OpenFold-based folding.

Deep learning inference parameters

First, we selected inference parameters that struck a good balance between accuracy and speed. Protein inference setup for all deep learning inference pipelines (ColabFold and OpenFold), thus utilized:

  • Weights: 1x weights from AlphaFold Multimer (model_1_multimer_v3)
  • Four recycles (with early stopping)
  • No relaxation
  • MSAs: frozen MSAs generated through ColabFold-search (using MMseqs2-GPU), as described above

Accuracy validation

  Homodimer PDB set (125 proteins)
Model High Medium Accept Incorr Usable DockQ
DockQ >0.8 >0.6 >0.3 >0      
ColabFold 52 37 12 21 89 (72.95%) 0.637
OpenFold with TensorRT and cuEquivariance 53 39 10 20 92 (75.41%) 0.647

Table 1. A comparison of interface accuracy between ColabFold and OpenFold (accelerated by TensorRT and cuEquivariance) across a benchmark set of 125 homodimer proteins.

As we used different inference pipelines, we performed accuracy validation using a curated benchmark set of 125 X-ray resolved PDB homodimers released after AlphaFold2 was introduced, thus minimizing the potential for information leakage.

Predicted complexes for each deep learning implementation were compared against experimental reference structures using DockQ, which evaluates interface accuracy via the fraction of native contacts (Fnat), fraction of non-native contacts (Fnonnat), interface RMSD (iRMS), and ligand RMSD after receptor alignment (LRMS), and assigns standard CAPRI classifications of high, medium, acceptable, or incorrect.

Across the PDB homodimer benchmark, OpenFold accelerated through TensorRT and cuEquivariance reproduces ColabFold interface accuracy, achieving a similar fraction of “high” scoring predictions and comparable mean DockQ scores. This indicates that the accelerated implementations preserve interface-level structural accuracy relative to the ColabFold baseline.

MSA preparation and sequence packing

For ColabFold-based homodimer inferences, higher throughput can be achieved by packing homodimers of equal length into a batch for processing, sorted by their MSA depth in descending order. This reduces the number of JAX recompilations, thereby increasing end-to-end throughput. This trick, however, does not work when processing heterodimers, because the lengths of the individual chains differ.

For OpenFold, whether for homodimers or heterodimers, this packing strategy is not needed, as the method doesn’t require re-compilation. However, given a dependency between sequence length and execution time, reserving longer sequences for individual jobs may be beneficial if operating with specific SLURM runtimes. To further optimize the process, input featurizations (CPU-bound) were performed for the next input query alongside the inference step for the current query (GPU-bound).

Additionally, OpenFold’s throughput was enhanced through the integration of the NVIDIA cuEquivariance library and NVIDIA TensorRT SDK. These modular libraries and SDKs can be leveraged to accelerate operations common in protein structure AI and general inference AI workloads, respectively. We previously described how TensorRT can be leveraged to accelerate OpenFold inference.

3. Optimize GPU utilization with SLURM

As alluded to in the previous section, depending on the available hardware, you can increase throughput by “packing” GPUs and nodes. SLURM is a great orchestrator, and we divided the inference workflows in SLURM scripts to:

  • Pack multiple predictions per node
  • Match GPU memory to sequence length
  • Reduce idle time between jobs
  • Separate short vs long sequence queues

Our workload was mapped to a H100 DGX Superpod HPC system. We could thus deploy inference across NVIDIA H100 GPUs on multi-node clusters, leveraging exclusive execution on a single node, and packing each GPU with as many processes as saturated the GPU utilization for both MSA processing and deep learning inference.

Helpful tips:

  • Group jobs by total residue length
  • Monitor GPU memory fragmentation
  • Use asynchronous I/O to avoid disk bottlenecks

4. Making quality predictions accessible to the world

In partnership with EMBL-EBI, the Steineggerlab at Seoul National University, and Google DeepMind, we explored complex structure prediction analysis. We highlight that predicting these biological systems remains challenging. Unlike protein monomer prediction, where predicted Local Distance Difference Test (pLDDT) can inform overall prediction quality, yielding a balanced amount of plausible predictions, in the complex scenario, assessing interface plausibility is much harder. This has to do with the fact that assessing complexes involves global and per-chain confidence metrics, as well as local confidence metrics at the interface.

Simply put, is the interface between two monomers plausible, and is it predicted in the right pocket? These questions are much harder to answer than more “local” questions about monomer likelihood, given the very limited data available. Therefore, we make available a set of high-confidence structures through the AlphaFold Database, thereby enabling, for the first time, exploration of protein complexes. We intend to refine our approach further and expand the universe of available protein complexes in the AlphaFold Database.

Getting started

Proteome-scale quaternary structure prediction requires more than just running AlphaFold-Multimer at scale. Success depends on:

  • Evidence-driven interaction selection
  • Decoupled and optimized compute workflows
  • GPU-aware job orchestration
  • Confidence calibration and validation
  • Dataset health monitoring

By combining STRING-guided selection, MMseqs2-GPU acceleration, and NVIDIA H100-powered multimer inference, this work extends AFDB into a unified, interaction-aware structural resource.

This infrastructure enables:

  • Variant interpretation at interfaces
  • Systems-level structural biology
  • Drug target validation
  • Generative protein design benchmarking

Resources

Read more about the project here: https://research.nvidia.com/labs/dbr/assets/data/manuscripts/afdb.pdf 

Accelerated libraries and SDKs are available here:

  • MMseqs2-GPU
  • NVIDIA cuEquivariance
  • NVIDIA TensorRT

If you wish to deploy MSA search and protein folding easily, you can get accelerated inference pipelines through NVIDIA’s Inference Microservices (NIMs):

  • MSA Search NIM 
  • OpenFold2 NIM

The predictions from this effort are available through https://alphafold.com

Toward Ultra-Long-Horizon Agentic Science: Cognitive Accumulation for Machine Learning Engineering

Mike's Notes

Very useful.

Resources

References

  • Toward Ultra-Long-Horizon Agentic Science: Cognitive Accumulation for Machine Learning Engineering. arXiv:2601.10402

Repository

  • Home > Ajabbi Research > Library > Subscriptions > Turing Post
  • Home > Handbook > 

Last Updated

01/04/2026

Toward Ultra-Long-Horizon Agentic Science: Cognitive Accumulation for Machine Learning Engineering

By: Xinyu Zhu, Yuzhu Cai, Zexi Liu, Bingyang Zheng, Cheng Wang, Rui Ye, Jiaao Chen, Hanrui Wang, Wei-Chen Wang, Yuzhi Zhang, Linfeng Zhang, Weinan E, Di Jin, Siheng Chen, Yanfeng Wang
arXiv: 15/01/2026

Abstract

The advancement of artificial intelligence toward agentic science is currently bottlenecked by the challenge of ultra-long-horizon autonomy, the ability to sustain strategic coherence and iterative correction over experimental cycles spanning days or weeks. While Large Language Models (LLMs) have demonstrated prowess in short-horizon reasoning, they are easily overwhelmed by execution details in the high-dimensional, delayed-feedback environments of real-world research, failing to consolidate sparse feedback into coherent long-term guidance. Here, we present ML-Master 2.0, an autonomous agent that masters ultra-long-horizon machine learning engineering (MLE) which is a representative microcosm of scientific discovery. By reframing context management as a process of cognitive accumulation, our approach introduces Hierarchical Cognitive Caching (HCC), a multi-tiered architecture inspired by computer systems that enables the structural differentiation of experience over time. By dynamically distilling transient execution traces into stable knowledge and cross-task wisdom, HCC allows agents to decouple immediate execution from long-term experimental strategy, effectively overcoming the scaling limits of static context windows. In evaluations on OpenAI's MLE-Bench under 24-hour budgets, ML-Master 2.0 achieves a state-of-the-art medal rate of 56.44%. Our findings demonstrate that ultra-long-horizon autonomy provides a scalable blueprint for AI capable of autonomous exploration beyond human-precedent complexities.

Introduction

Refer to the original at arXiv >

How RNNs Work (And Why Everyone Stopped Using Them)

Mike's Notes

A great explanation of how RNNs work.

Resources

References

  • Attention is All You Need by Ashish Vaswani, Noam Shazeer, Niki Parmar, Jakob Uszkoreit, Llion Jones, Aidan N. Gomez, Lukasz Kaiser, Illia Polosukhin. 2017. ArXiv 1706.03762.

Repository

  • Home > Ajabbi Research > Library > Subscriptions > Into AI
  • Home > Handbook > 

Last Updated

28/03/2026

How RNNs Work (And Why Everyone Stopped Using Them)

By: Dr. Ashish Bamania and Jose Parreño Garcia
Into AI: 07/02/2026

Dr. Ashish Bamania: I help you to level up in AI and Quantum Computing
Jose Parreño Garcia: I write about Data Science, Machine Learning and leading data teams. I have built teams from scratch and lead 50+ data scientists @Skyscanner. Now, I share my experience with you.

A gentle walkthrough of how Recurrent Neural Networks (RNNs) work, and the math that breaks them.

This week’s newsletter is written by Jose Parreño Garcia. He is a senior Data Science manager at Skyscanner.

He regularly shares insights on building effective teams, developing leadership skills, and advancing careers in Data Science and Machine Learning through his newsletter, Senior Data Science Lead.

You can also find him and stay up to date with his content on LinkedIn.

This week, I went all the way back to 2017. That’s when the now-legendary ‘Attention is All You Need’ paper came out — the one that introduced the world to Transformers, and set the foundation for everything from ChatGPT to image generation to code-writing copilots.

And sure, I could jump straight into explaining how Transformers work. But given the impact these models have had — and the fact that you probably see the word “attention” 30 times a week now — I thought it would be worth taking a step back (actually 2 steps back).

Before we can truly understand Transformers, we need to understand where they came from. And that means revisiting the architectures that paved the way: Recurrent Neural Networks (RNNs) and Long Short-Term Memory (LSTM) networks.

In this blog post, I am diving into RNNs.

We will walk through what they are, how they work, and most importantly, why they struggle. By the end, RNNs will feel like a clever little for-loop with memory instead of scary maths magic.

Ready? Let’s jump in!

What we will cover in this article

  1. How is an RNN different from a classical Deep Neural Network? (or other classical sequence models)
  2. Introducing a made-up use case with 3 data points for Stock price prediction
  3. The scary official diagram of an RNN cell. (Don’t worry, we will break it down super easily.)
  4. A walkthrough of RNN calculations (And you will see how the maths is not that scary after all.)
  5. Three problems associated with RNNs

How is an RNN different from a classical Deep Neural Network?

I assume in this post that you have worked with (or are familiar with) the basics of classical Deep Neural Networks (from here on, DNNs).

If my assumption is correct, then the diagram below should feel really familiar. It is a diagram of a DNN with:

  • X: A set of input nodes, representing the variables you want to use for prediction.
  • H1, H2: 2 hidden layers with 4 nodes. This is where the parameters that learn how to dial up or down specific signals from X. Basically, the knobs the model adjusts to learn.
  • Y: The prediction node. In this case, it’s only 1 for simplicity.

Diagram of a classical Deep neural network (DNN)

Now, there are 2 main things to highlight in this diagram:

  1. The DNN processes all the data at once.
    You see the X input? From a DNN’s perspective, it’s a torrent of data, all pushed and processed at once. There is nowhere in this diagram that the network can say, “Hey, can you just send me X1 and X2 first, and then I can process X3 and X4?”.
  2. The DNN is feed-forward (or sequential).
    In other words, the data flows from left to right (from X → Y). There is nowhere in this diagram that a node can stop the data flow and ask: “Hey, what data did I have in the previous step?” It is oblivious to that.

DNNs are really powerful, but they are also “memory-less”

DNNs are “memory-less” because of the two points mentioned above. And being “memory-less” means that DNNs really struggle to predict when sequence or order matters.

Take a simple stock price prediction scenario. The only way that DNNs can consider what happened yesterday or the day before is if you tell them what matters. This is usually done by manually creating features like:

  • Yesterday’s price
  • A moving average over the past 7 days
  • A sine-transformed day-of-week feature to capture seasonality

But wasn’t Deep learning supposed to eliminate Feature engineering?

Yes (and no).

Neural networks do learn internal representations (i.e., “features”) from raw data. But when it comes to sequences, classical deep neural nets still need all the help they can get (so, kind of back again to square one, where we have to feature engineer stuff…)

This is where Recurrent Neural Networks (RNNs) come in.

Recurrent neural networks (RNNs) are a class of artificial neural networks designed for processing sequential data, such as text, speech, and time series, where the order of elements is important.

Their two main characteristics are:

  1. RNNs process one input at a time.
    Instead of taking all of the input X in one big gulp, RNNs look at one data point at a time — like reading a sentence word by word, or stepping through a stock price day by day. This allows them to focus on how each input evolves over time.
  2. RNNs are recursive.
    Yes, the information flows left to right, but at each step, it can also look at what happened before (kind of a right-to-left motion). It’s like a left-to-right with memory.

Don’t worry if this feels dense right now, we will break it down step by step.

By the end of this post, you will not only understand what “recurrent connections” mean, but you will also see why RNNs became a foundational architecture for handling sequences.

Introducing a made-up use case for Stock price prediction

Ok, before we get into the RNN section, let me introduce you to the simplest stock price prediction exercise in the course of human history.

The diagram below shows a toy time series where:

  • For simplicity, all stock prices are set to 0.
  • We have 3 data points.
  • The idea is to use yesterday’s and today’s stock prices to predict tomorrow’s stock price.

I want to introduce this in such a simple way because:

  1. I want to actually show you the maths of the RNN using super simple numbers.
  2. Labelling each step as yesterday, today, and tomorrow will help anchor the RNN diagrams that follow.

Let’s keep this mental model in our back pocket — it’s going to make the scary-looking RNN diagrams feel a lot less scary.

Ok, now we are ready to get scared by a diagram of an RNN cell!

The scary official diagram of an RNN cell

So far, we have discussed how RNNs differ from classical DNNs because they remember their past. But what does that actually look like inside the model?

Well... time to face the infamous RNN diagram.

It might look like a tangle of wires and equations at first, but don’t worry, we will walk through it slowly, tie it back to the stock price example, and by the end, you will see it’s just a simple process of multiplication, addition, and a squiggly activation function or two.

The diagram below is a vanilla RNN cell.

I can sense you sweating… a flow diagram? With parameters? With maths operations? Ok, let’s break it up so that you don’t have to process it all at once.

Here are the elements to focus on:

  1. Note the X, h and Y elements (similar to the classical DNN diagram).
  2. X(t) represents today's data point, h(t-1) is what came from yesterday, h(t) is what is passed to predict tomorrow, and Y(t+1) is the predicted data point.
  3. Y(t+1) represents the prediction that we want.
  4. tanh and softmax are just activation functions. The same kind you have seen in regular neural networks, so nothing special for RNNs. They take raw values and squash them into a friendlier range, like between -1 and 1 (for tanh) or 0 and 1 (for softmax).
  5. Finally, there are a couple of blobs with mult and sum. These are just visual aids so that you can see the operations in action when we pull the numbers in.
  6. There are maths operations outside of the cell. These are basically there just so that we can transform a squashed value coming out of tanh, into a real value that makes sense. For example, transform 0.9 coming out of the tanh function to maybe $5.

✍️ Quick note

Technically, the RNN makes a prediction y(t) after seeing input x(t) and memory h(t-1).

But since in our toy example we are trying to predict the next value, it’s tempting to call it y(t+1) — just know that it’s really y(t) in the math, but the target we are aiming for is the value at t+1.

I purposely named it y(t+1) for pedagogical reasons for this post.

Let’s map this diagram with the theoretical RNN math function

In the image below, I have added the math functions that lead to the two outputs from the RNN cell: y(t+1) and h(t).

So, if we wanted to either predict an output (Y) or carry to the next stage (h), then the neural network should learn:

  • Wx: This is the weight applied to today’s data, X(t). It controls how much the model should care about today’s stock price. Extreme case, if Wx = 0, then this means we don’t care about X(t) because Wx * X(t) would yield 0.
  • Wh: This is the weight applied to the previous hidden state, h(t–1). It tells the model how much to rely on memory. If Wh = 0, the past is forgotten.
    ⚠️ Don’t misinterpret h(t-1) by thinking it is yesterday’s stock price. It is what comes out of the cell (with its multiplications, sums, and activation functions) applied to yesterday’s stock price.

  • b: This is the bias term. Think of it as a small correction applied regardless of the input. It’s important in training, but not very interesting for understanding how RNNs work conceptually (as it affects DNNs the same way). If you want to deep dive, check this link.

I believe that only when we plug in numbers to these diagrams will we start really understanding what is happening inside the RNN. Let’s do this next.

A Walkthrough With Real Numbers

Alright, time to take what we have learnt and run it step by step. Instead of just showing the internals of a single RNN cell, we’ll now “unroll” it (you will see what that is in a second).

This will finally answer the big question: how does an RNN actually use the past to predict the future?

How do we represent tomorrow’s prediction diagrammatically?

Pretty simple. We just copy and paste the same RNN cell forward in time, one per data point in our sequence.

In our toy stock price example, we only have two data points (t–1 and t), so we unroll the cell twice in order to make a prediction at t+1.

And this is why they are called recurrent, because the same logic is applied over and over, like a for loop.

Cool, now that you are comfortable with what happens inside a single RNN cell, let’s walk through this unrolled diagram in detail to ensure we are all on the same page.

  1. We begin by plugging in yesterday’s stock price. Because there is no data prior to yesterday, we can ignore the previous hidden state input h(t-2).
  2. Using both math functions, we then calculate y(t) and h(t-1). From these two, only h(t-1) is useful for us. This is the value that describes the memory, and that will be passed to the next cell. y(t) is irrelevant, so we ignore it.
  3. Finally, we plug in today’s data and run through the relevant maths operations to calculate y(t+1). You can see from the diagram that the RNN is using today’s data X(t) and yesterday’s data h(t-1) from memory to calculate what could happen tomorrow.

💡 An important highlight: Shared weights and biases

“Wait a sec... you are using the same Wx, Wh, and b in both cells. Shouldn’t they be different?”

Great question! This is the part that makes RNNs elegant, but also tricky (you will see at the end how these shared weights break an RNN’s learning process).

Unlike feedforward layers that might learn new weights for every input, an RNN cell reuses the same weights at every timestep. That’s the “recurrent” part. An RNN not only repeats the cell structure, but it also repeats the exact same function with the same learned parameters.

So yes — Wx, Wh, and b are constant across time. What changes is the input x(t) and the memory from the previous step h(t–1), which is how the model updates its thinking as it moves forward.

Plugging numbers into the diagram

Before doing some basic maths, let’s talk about the numbers being used:

  1. Note how I substituted Wh, Wx, b, W_output and b_output with numbers. I made these numbers up, but they are the ones the neural network would tweak during its learning process.
  2. The input data points are the ones we know from the time series. X(t-1) and X(t) are both 0.

Now we are ready to take pen and paper and perform all the calculations in this diagram.

Plugging in all the numbers, y(t+1) comes out to be 0. Nice, this is what we expected from our mock stock price time series!

What would the diagram look like if we had 50 data points?

Well... you would copy the RNN cell forward 49 times, just like a for loop with 50 iterations (luckily for me, I am not drawing that diagram...)

But structurally, nothing changes. You still:

  • Reuse the same weights
  • Pass memory from each time step to the next
  • And only apply the final output prediction where it matters, which is usually the last cell in the sequence

What we learn from the above examples is that:

  • RNNs reuse the same weights and biases at each step.
  • Hidden states h(t) are like memory, passed forward through time.
  • Outputs y(t) are generated by applying a linear transformation and softmax on the hidden state.
  • Even a dumb toy dataset of all zeros reveals the internal mechanics beautifully.

That wasn’t that hard, right? Well, I have bad news for you…

Three Major Problems With RNNs

These vanilla RNNs are never used for real-world use cases because they come with three big problems:

  • They are slow to train.
  • They suffer from the problem of vanishing gradients
  • They suffer from the problem of the exploding gradients

Let’s cover these in detail in their own sections.

Problem 1: Training a vanilla RNN is very slow

Deep Neural Networks (DNNs) process all their inputs at once in a single forward pass. Everything flows from left to right, layer by layer. That means training can occur in parallel across many data points and GPU cores.

This is not the case with RNNs.

Because RNNs depend on previous hidden states, they are inherently sequential. You can’t calculate h(t) until you have calculated h(t–1).

It’s like reading a book: you can’t understand chapter 5 until you have read chapter 4.

RNNs make you walk through time, one step at a time, and this sequential dependency kills parallelism.

That is the first problem: RNNs are powerful, but they pay for it in training speed.

Problem 2: The problem of the Vanishing gradients

To explain this problem, I would have to take some mathematical shortcuts. To really know what is happening under the hood, you need to be familiar with the chain rule used in backpropagation. But showing the full impact of vanishing gradients using the backpropagation formula would be overkill.

Let’s zoom in on just one parameter in the RNN, i.e. Wh, the weight that is multiplied by the previous hidden state.

In our earlier 3-step example, Wh was used once (just one multiplication). But, if you are training an RNN for 50 timesteps (say, 50 days of stock data), that means Wh shows up 49 times in the full chain of calculations.

When the model tries to update Wh via backpropagation, mathematically, the gradient is multiplying Wh over and over again, kind of like:

Uh-oh. What happens when Wh < 1?

Try plugging in Wh = 0.5:

That’s basically zero. This means that the update to Wh during training is so tiny, it’s like the model is frozen. It can’t escape its starting point. It just sits there, unable to learn anything useful.

Problem 3: The problem of the Exploding gradients

This is the opposite problem. If vanishing gradients are the slow death of learning, exploding gradients are the chaotic opposite.

What happens when Wh > 1? Say Wh = 1.5.

That’s nearly a billion. This means that during backpropagation, the gradient becomes massive. And with a gradient that large, your weight update becomes a wild jump.

The result is that your model overshoots the loss minimum, bounces around the optimisation landscape like a drunk pinball (love that game), and probably never converges.

This is the exploding gradients problem. Same root cause as vanishing gradients — compounding multiplications through time — but now the problem is too much signal, instead of too little.

What’s next: How LSTMs fixed all of this (mostly)

With everything we have covered, you might think, “Hey, that vanilla RNN was easy enough to understand, but with the stated problems, it also looks pretty useless, right?”

I would mostly agree. Training a vanilla RNN is not impossible, but it requires skill, feature engineering, fine-tuning, and time. The vanishing and exploding gradient problem is the one that mostly holds back an RNN.

This is why LSTMs were introduced.

LSTMs are improved versions of RNNs. With built-in mechanisms (called Gates) to decide what to keep, what to forget, and what to pass forward, they were designed specifically to beat vanishing and exploding gradients at their own game.

LSTMs are for the next post, where we will explore how they work and why they became the go-to tool for sequence modelling… at least until Transformers came along.

Now, I want to hear from you!

In this post, we broke down how RNNs work, from the vanilla cell structure to why they struggle to train in the real world.

We kept it simple, even used an all-zero dataset, and uncovered the quiet math that makes RNNs nearly impossible to scale.

But now I’m curious about your experience.

  • Have you ever built or trained an RNN model?
  • Did you run into vanishing gradients or the joys of exploding updates?
  • Maybe you were introduced to LSTMs (or GRUs) straight away and skipped vanilla RNNs altogether?
  • Or maybe you are just now connecting the dots between hidden states, time steps, and why Transformers were such a leap.

Drop your thoughts, experiences, or lingering questions in the comments. I would love to hear how you’ve approached sequence modelling in your ML journey.

Thanks again to Jose Parreño Garcia for writing this week’s newsletter.

Don’t forget to subscribe to his newsletter and connect with him on LinkedIn.