Machine learning

Learning from data, from fitting a line to deep networks: optimisation, generalisation, kernels and Gaussian processes, neural networks and modern architectures.

Learning from data

Models, loss functions and likelihood, gradient descent, generalisation, and kernels and Gaussian processes.

1Learning from data

What it means for a model to learn, why least squares is the default, where it comes from in probability, and what changes when you pick a different loss.

about 14 min read

The question

You measured a rate constant at six temperatures and want the activation energy. You computed the adsorption energy of a molecule on twenty surfaces and want to predict it for a twenty-first. In both cases you have pairs (xi,yi)(x_i, y_i) and want a rule ff that turns a new xx into a good guess of yy. That is supervised learning, and almost everything in this course is a version of it: the rule gets more flexible (a line, a neural network, an interatomic potential), but the recipe stays the same.

The recipe has three ingredients:

  1. a model f(x;θ)f(x;\boldsymbol\theta), a family of functions indexed by parameters θ\boldsymbol\theta;
  2. a loss ℓ(y,y^)\ell(y, \hat y) that says how bad a prediction y^\hat y is when the truth is yy;
  3. an optimiser that searches for the parameters with the smallest average loss on the data.

Minimising the average loss over a data set D={(xi,yi)}i=1N\mathcal D = \{(x_i,y_i)\}_{i=1}^N is called empirical risk minimisation:

θ^=arg⁡min⁡θ  1N∑i=1Nℓ(yi,f(xi;θ)). \hat{\boldsymbol\theta} = \arg\min_{\boldsymbol\theta}\; \frac{1}{N}\sum_{i=1}^{N} \ell\big(y_i, f(x_i;\boldsymbol\theta)\big).

The word empirical matters. What we really want is a small error on data we have not seen, the expected loss over the whole population. We can only measure it on the sample we have, which is why the lesson on generalisation exists.

The simplest model: a straight line

Take f(x;w,b)=wx+bf(x; w, b) = wx + b and the squared loss ℓ=(y−y^)2\ell = (y - \hat y)^2. The objective is the sum of squared residuals

L(w,b)=∑i=1N(yi−wxi−b)2. L(w,b) = \sum_{i=1}^{N} \big(y_i - w x_i - b\big)^2 .

LL is a smooth bowl in (w,b)(w,b), so the minimum is where both partial derivatives vanish:

∂L∂b=−2∑i(yi−wxi−b)=0,∂L∂w=−2∑ixi (yi−wxi−b)=0. \frac{\partial L}{\partial b} = -2\sum_i (y_i - w x_i - b) = 0, \qquad \frac{\partial L}{\partial w} = -2\sum_i x_i\,(y_i - w x_i - b) = 0 .

The first equation says the residuals sum to zero, so the line passes through the centroid (xˉ,yˉ)(\bar x, \bar y). Substituting b=yˉ−wxˉb = \bar y - w\bar x into the second gives the familiar result

w^=∑i(xi−xˉ)(yi−yˉ)∑i(xi−xˉ)2,b^=yˉ−w^ xˉ. \hat w = \frac{\sum_i (x_i-\bar x)(y_i-\bar y)}{\sum_i (x_i-\bar x)^2}, \qquad \hat b = \bar y - \hat w\,\bar x .

Fit a line by hand, then let least squares do it

Loading the interactive figure…

Red segments are residuals. In “Fit it yourself” mode the dashed line is the least-squares answer: try to get your mean squared error down to it. Then add an outlier and compare the two automatic fits.

Try three things in the lab. First, fit the line yourself with the sliders and notice how hard it is to beat least squares; no other line has a smaller mean squared error, by construction. Second, press Add an outlier and watch the least-squares line swing towards it. Third, switch to least absolute deviation with the outlier still there. The squared loss punishes a residual of 4 sixteen times more than a residual of 1, so a single bad point can dominate; the absolute loss punishes it only four times more.

The matrix form, which every later model reuses

Stack the inputs into a design matrix X\mathbf X whose rows are feature vectors, here (1,xi)(1, x_i), and the targets into y\mathbf y. Then y^=Xθ\hat{\mathbf y} = \mathbf X\boldsymbol\theta and

L(θ)=∥y−Xθ∥2. L(\boldsymbol\theta) = \lVert \mathbf y - \mathbf X\boldsymbol\theta \rVert^2 .

Setting the gradient ∇θL=−2X⊤(y−Xθ)\nabla_{\boldsymbol\theta} L = -2\mathbf X^{\top}(\mathbf y - \mathbf X\boldsymbol\theta) to zero gives the normal equations

X⊤X θ^=X⊤y. \mathbf X^{\top}\mathbf X\,\hat{\boldsymbol\theta} = \mathbf X^{\top}\mathbf y .

Geometrically, Xθ^\mathbf X\hat{\boldsymbol\theta} is the orthogonal projection of y\mathbf y onto the column space of X\mathbf X: the residual vector is perpendicular to every feature column. Numerically, never form (X⊤X)−1(\mathbf X^\top\mathbf X)^{-1} explicitly; squaring X\mathbf X squares its condition number. Libraries solve the problem with a QR or singular-value decomposition instead (numpy.linalg.lstsq uses the SVD).

Where least squares comes from: maximum likelihood

Why squares and not absolute values or fourth powers? Suppose each measurement is the true line plus independent Gaussian noise of width σ\sigma:

yi=wxi+b+εi,εi∼N(0,σ2). y_i = w x_i + b + \varepsilon_i, \qquad \varepsilon_i \sim \mathcal N(0, \sigma^2).

The probability density of the whole data set, viewed as a function of the parameters, is the likelihood

p(y∣X,w,b)=∏i=1N12πσ2exp⁡ ⁣[−(yi−wxi−b)22σ2]. p(\mathbf y \mid \mathbf X, w, b) = \prod_{i=1}^N \frac{1}{\sqrt{2\pi\sigma^2}} \exp\!\left[-\frac{(y_i - wx_i - b)^2}{2\sigma^2}\right].

Taking the negative logarithm,

−ln⁡p=12σ2∑i(yi−wxi−b)2+N2ln⁡(2πσ2), -\ln p = \frac{1}{2\sigma^2}\sum_i (y_i - wx_i - b)^2 + \frac{N}{2}\ln(2\pi\sigma^2),

and the only part that depends on ww and bb is the sum of squares. Least squares is maximum likelihood under Gaussian noise. Every other loss corresponds to a different noise model: the absolute loss is maximum likelihood for Laplace (double-exponential) noise, whose heavier tails make outliers less surprising, which is why that fit ignored the outlier.

The same view tells you what to do when the error bars differ from point to point. If point ii has standard deviation σi\sigma_i, the likelihood gives weighted least squares, ∑i(yi−y^i)2/σi2\sum_i (y_i-\hat y_i)^2/\sigma_i^2: precise points count more. Fitting ln⁡k\ln k instead of kk silently changes the weights, because a constant relative error in kk is a constant absolute error in ln⁡k\ln k. That is usually what you want for rate constants, and it is worth knowing you chose it.

How good is the fit?

Three numbers are worth reporting.

  • The root-mean-square error, RMSE=L/N\text{RMSE} = \sqrt{L/N}, in the units of yy. Compare it with the measurement uncertainty: a fit far better than your error bars is suspicious.
  • The coefficient of determination, R2=1−∑i(yi−y^i)2/∑i(yi−yˉ)2R^2 = 1 - \sum_i (y_i - \hat y_i)^2 / \sum_i (y_i - \bar y)^2, the fraction of the variance of yy the model explains. R2R^2 says nothing about whether the model is right: a straight line through curved data can still give R2=0.98R^2 = 0.98. Always look at the residuals; they should look like noise, with no trend.
  • Uncertainties on the parameters. Under the Gaussian model the covariance of the estimate is cov⁡(θ^)=σ^2(X⊤X)−1\operatorname{cov}(\hat{\boldsymbol\theta}) = \hat\sigma^2(\mathbf X^\top\mathbf X)^{-1} with σ^2=L/(N−p)\hat\sigma^2 = L/(N-p) for pp parameters. An activation energy without an error bar is not a result.

In practice

The calculation in NumPy, here for an Arrhenius plot (run it locally, or in the Python lesson):

import numpy as np

T = np.array([300., 310., 320., 330., 340., 350.])           # K
k = np.array([1.2e-3, 2.4e-3, 4.6e-3, 8.5e-3, 1.5e-2, 2.6e-2])  # 1/s
R = 8.314462618                                              # J/(mol K)

X = np.column_stack([np.ones_like(T), 1.0 / T])   # features (1, 1/T)
y = np.log(k)
theta, res, rank, sv = np.linalg.lstsq(X, y, rcond=None)
lnA, slope = theta

N, p = X.shape
s2 = res[0] / (N - p)                         # residual variance
cov = s2 * np.linalg.inv(X.T @ X)             # acceptable for a tiny 2×2 problem
Ea = -slope * R / 1000                        # kJ/mol
dEa = np.sqrt(cov[1, 1]) * R / 1000
print(f"Ea = {Ea:.1f} ± {dEa:.1f} kJ/mol,  A = {np.exp(lnA):.2e} 1/s")

This prints Ea = 53.7 ± 0.1 kJ/mol, A = 2.65e+06 1/s. For nonlinear models, where no normal equations exist, scipy.optimize.curve_fit does the same job iteratively; the next lesson explains how such iterative optimisers work.

Check yourself

  1. You fit ln⁡k\ln k against 1/T1/T with ordinary least squares. Which assumption about the measurement errors in kk are you implicitly making?
  2. Which of these models is a linear regression problem?
  3. One data point is a gross outlier. Which fit is least affected by it?
  4. A straight-line fit gives R2=0.97R^2 = 0.97. What does that tell you?

References and further reading

  1. C. M. Bishop, Pattern Recognition and Machine Learning, Springer (2006), §1.1–1.2 and chapter 3 (linear models for regression).
  2. T. Hastie, R. Tibshirani and J. Friedman, The Elements of Statistical Learning, 2nd ed., Springer (2009), chapter 3.
  3. K. P. Murphy, Probabilistic Machine Learning: An Introduction, MIT Press (2022), chapters 4 (statistics) and 11 (linear regression).
  4. W. H. Press, S. A. Teukolsky, W. T. Vetterling and B. P. Flannery, Numerical Recipes, 3rd ed., Cambridge University Press (2007), chapter 15 (modelling of data), including why the SVD is preferred to the normal equations.
  5. P. J. Huber, “Robust estimation of a location parameter,” Annals of Mathematical Statistics 35, 73–101 (1964), the origin of robust losses.

2Gradient descent

How models with thousands to billions of parameters are fitted: follow the slope downhill, choose the step size, and why momentum and Adam help on badly shaped surfaces.

about 14 min readBuilds on Learning from data

Why we need an iterative method

The straight line in the previous lesson had a closed-form answer because its loss is quadratic in the parameters. Most models do not: a nonlinear curve fit, a neural network or an interatomic potential has a loss L(θ)L(\boldsymbol\theta) with no formula for its minimum. What we can compute cheaply is the gradient ∇L(θ)\nabla L(\boldsymbol\theta), the vector of partial derivatives that points in the direction of steepest increase. Gradient descent takes repeated small steps the other way:

θt+1=θt−η ∇L(θt), \boldsymbol\theta_{t+1} = \boldsymbol\theta_t - \eta\,\nabla L(\boldsymbol\theta_t),

where η>0\eta > 0 is the learning rate (step size). The idea goes back to Cauchy in 1847, and with the improvements below it trains essentially every neural network in use today.

Why this direction? A first-order Taylor expansion around θ\boldsymbol\theta gives L(θ+δ)≈L(θ)+∇L⊤δL(\boldsymbol\theta + \boldsymbol\delta) \approx L(\boldsymbol\theta) + \nabla L^{\top}\boldsymbol\delta. Among all steps of a fixed length, the one that lowers LL most is the one anti-parallel to ∇L\nabla L (by the Cauchy–Schwarz inequality). The expansion is only trustworthy for small δ\boldsymbol\delta, which is why the step size matters so much.

Three optimisers on three loss surfaces

Loading the interactive figure…

The colour bands are contours of the loss (logarithmic). Click to choose a start point, then Run. Try the stretched bowl with η = 0.12, then 0.30, then 0.35; try the banana valley with plain gradient descent before switching to momentum or Adam.

How large a step? The quadratic model

Near a minimum θ∗\boldsymbol\theta^* every smooth loss looks like a quadratic bowl,

L(θ)≈L(θ∗)+12(θ−θ∗)⊤H (θ−θ∗), L(\boldsymbol\theta) \approx L(\boldsymbol\theta^*) + \tfrac12(\boldsymbol\theta-\boldsymbol\theta^*)^{\top}\mathbf H\,(\boldsymbol\theta-\boldsymbol\theta^*),

where H\mathbf H is the Hessian, the matrix of second derivatives. Write the error et=θt−θ∗\mathbf e_t = \boldsymbol\theta_t - \boldsymbol\theta^*. Since ∇L=Het\nabla L = \mathbf H\mathbf e_t, one gradient step gives

et+1=(I−ηH) et. \mathbf e_{t+1} = (\mathbf I - \eta\mathbf H)\,\mathbf e_t .

Decompose e\mathbf e along the eigenvectors of H\mathbf H, with eigenvalues λ1≥⋯≥λn>0\lambda_1 \ge \dots \ge \lambda_n > 0 (the curvatures along each principal direction). Each component is multiplied by (1−ηλi)(1-\eta\lambda_i) at every step. Two consequences follow, and you can see both in the lab.

  1. Stability. Every factor must satisfy ∣1−ηλi∣<1|1-\eta\lambda_i| < 1, so the iteration converges only if η<2λmax⁡. \eta < \frac{2}{\lambda_{\max}} . The stretched bowl in the lab has curvatures 0.6 and 6, so it diverges for η>1/3\eta > 1/3. Above that threshold each step overshoots the valley floor by more than it started with.
  2. Speed. The slowest component shrinks by ∣1−ηλmin⁡∣|1-\eta\lambda_{\min}| per step. With the best fixed step, η=2/(λmax⁡+λmin⁡)\eta = 2/(\lambda_{\max}+\lambda_{\min}), the error contracts by κ−1κ+1,κ=λmax⁡λmin⁡, \frac{\kappa - 1}{\kappa + 1}, \qquad \kappa = \frac{\lambda_{\max}}{\lambda_{\min}}, per iteration. The condition number κ\kappa measures how stretched the bowl is. For κ=10\kappa = 10 this is 0.82 per step; for κ=104\kappa = 10^4, common in real problems, it is 0.9998, and gradient descent crawls.

This is why the scale of input features matters. If one descriptor is in ångströms and another in electronvolts per atom, the loss surface is stretched along one axis, κ\kappa is large, and training is slow. Standardising features (subtracting the mean and dividing by the standard deviation) is the cheapest optimisation trick there is.

Momentum

In a long, narrow valley, gradient descent zig-zags across the steep walls while making little progress along the floor. Polyak’s heavy-ball method adds a velocity that accumulates past gradients:

vt+1=β vt−η ∇L(θt),θt+1=θt+vt+1. \mathbf v_{t+1} = \beta\,\mathbf v_t - \eta\,\nabla L(\boldsymbol\theta_t), \qquad \boldsymbol\theta_{t+1} = \boldsymbol\theta_t + \mathbf v_{t+1}.

Components of the gradient that keep the same sign (along the valley floor) add up; components that flip sign from step to step (across the valley) cancel. With β=0.9\beta = 0.9 the effective step along a consistent direction is up to 1/(1−β)=101/(1-\beta) = 10 times larger. For quadratics, well-tuned momentum improves the convergence factor from about 1−1/κ1 - 1/\kappa to about 1−1/κ1 - 1/\sqrt\kappa, a dramatic gain when κ\kappa is large. Nesterov’s accelerated gradient, which evaluates the gradient at the look-ahead point θt+βvt\boldsymbol\theta_t + \beta\mathbf v_t, achieves this rate provably for convex problems.

Stochastic gradients

For a data set of NN points the gradient is a sum, ∇L=1N∑i∇ℓi\nabla L = \frac1N\sum_i \nabla \ell_i. When NN is a million training structures, computing it exactly for every step is wasteful. Stochastic gradient descent (SGD) estimates it from a random mini-batch B\mathcal B of, say, 32 points:

θt+1=θt−η 1∣B∣∑i∈B∇ℓi(θt). \boldsymbol\theta_{t+1} = \boldsymbol\theta_t - \eta\,\frac{1}{|\mathcal B|}\sum_{i\in\mathcal B}\nabla \ell_i(\boldsymbol\theta_t).

The estimate is unbiased but noisy. Robbins and Monro showed in 1951 that such a noisy iteration still converges if the learning rate decays so that ∑tηt=∞\sum_t \eta_t = \infty and ∑tηt2<∞\sum_t \eta_t^2 < \infty (for example ηt∝1/t\eta_t \propto 1/t). In practice the learning rate follows a schedule: a short warm-up, then a cosine or step decay. A full pass through the data is called an epoch.

Adaptive methods: Adam

Different parameters often need different step sizes: weights feeding rare features see small, infrequent gradients. Adam (adaptive moment estimation) keeps running averages of the gradient and of its element-wise square,

mt=β1mt−1+(1−β1) gt,st=β2st−1+(1−β2) gt2, \mathbf m_t = \beta_1\mathbf m_{t-1} + (1-\beta_1)\,\mathbf g_t, \qquad \mathbf s_t = \beta_2\mathbf s_{t-1} + (1-\beta_2)\,\mathbf g_t^{2},

corrects them for their initialisation at zero, m^t=mt/(1−β1t)\hat{\mathbf m}_t = \mathbf m_t/(1-\beta_1^t) and s^t=st/(1−β2t)\hat{\mathbf s}_t = \mathbf s_t/(1-\beta_2^t), and steps by

θt+1=θt−η m^ts^t+ϵ. \boldsymbol\theta_{t+1} = \boldsymbol\theta_t - \eta\,\frac{\hat{\mathbf m}_t}{\sqrt{\hat{\mathbf s}_t}+\epsilon}.

Dividing by the root-mean-square gradient makes the step size roughly η\eta in every coordinate, whatever the gradient’s scale, which is a crude per-parameter correction for bad conditioning. The defaults β1=0.9\beta_1 = 0.9, β2=0.999\beta_2 = 0.999 work for most problems. In the lab, notice that Adam with η=0.1\eta = 0.1 moves at a nearly constant speed even where the gradient is tiny, and how it can circle near the minimum. Most MLIP and GNN training scripts use Adam or its variant AdamW, which decouples weight decay from the adaptive step.

Second-order methods

If you can afford the Hessian, Newton’s method jumps straight to the minimum of the local quadratic model, θt+1=θt−H−1∇L\boldsymbol\theta_{t+1} = \boldsymbol\theta_t - \mathbf H^{-1}\nabla L, which removes the dependence on κ\kappa entirely and converges quadratically near the minimum. For least-squares problems the Gauss–Newton approximation H≈2J⊤J\mathbf H \approx 2\mathbf J^\top\mathbf J (with J\mathbf J the Jacobian of the residuals) needs only first derivatives; adding a damping term that interpolates towards gradient descent gives Levenberg–Marquardt, the algorithm behind scipy.optimize.curve_fit and most curve-fitting software. L-BFGS builds a low-rank approximation of H−1\mathbf H^{-1} from recent gradients and is the standard for geometry optimisation of molecules and crystals. For networks with millions of parameters and noisy mini-batch gradients, first-order methods remain the practical choice.

Where the gradient comes from

Every method above needs ∇L\nabla L. For a model with a million parameters, finite differences would need a million extra loss evaluations per step. Reverse-mode automatic differentiation (backpropagation) computes the full gradient for about the cost of two or three evaluations of the loss, whatever the number of parameters. It is the subject of the lesson on neural networks in practice, and its forward-mode cousin reappears in differentiable molecular dynamics.

Check yourself

  1. The largest curvature (Hessian eigenvalue) of your loss near the minimum is 50. Which fixed learning rate keeps plain gradient descent stable?
  2. One input feature is a lattice constant around 4 Å and another is a pressure around 10⁵ Pa. Why does that slow down gradient descent?
  3. What does momentum do in a long, narrow valley?
  4. Why is stochastic (mini-batch) gradient descent used for large data sets?

References and further reading

  1. J. Nocedal and S. J. Wright, Numerical Optimization, 2nd ed., Springer (2006): chapters 2–3 (line search and steepest descent), 6–7 (quasi-Newton and L-BFGS), 10 (least squares and Levenberg–Marquardt).
  2. I. Goodfellow, Y. Bengio and A. Courville, Deep Learning, MIT Press (2016), chapter 8 (optimisation for training deep models).
  3. B. T. Polyak, “Some methods of speeding up the convergence of iteration methods,” USSR Computational Mathematics and Mathematical Physics 4(5), 1–17 (1964).
  4. Y. Nesterov, “A method for solving the convex programming problem with convergence rate O(1/k²),” Soviet Mathematics Doklady 27, 372–376 (1983).
  5. H. Robbins and S. Monro, “A stochastic approximation method,” Annals of Mathematical Statistics 22, 400–407 (1951).
  6. D. P. Kingma and J. Ba, “Adam: A method for stochastic optimization,” International Conference on Learning Representations (2015), arXiv:1412.6980.
  7. I. Loshchilov and F. Hutter, “Decoupled weight decay regularization,” International Conference on Learning Representations (2019), arXiv:1711.05101 (AdamW).
  8. D. C. Liu and J. Nocedal, “On the limited memory BFGS method for large scale optimization,” Mathematical Programming 45, 503–528 (1989).
  9. S. Ruder, “An overview of gradient descent optimization algorithms,” arXiv:1609.04747 (2016).

3Generalisation and regularisation

Why a model that fits your data perfectly can be useless, the bias–variance trade-off, how penalties tame flexible models, and how to measure performance honestly, including the traps specific to materials data.

about 14 min readBuilds on Learning from data

Fitting is not the goal

A model is useful only if it predicts data it has not seen. With enough parameters any data set can be fitted exactly: a polynomial of degree N−1N-1 passes through NN points, noise included. Such a model has memorised the training set rather than learned the relationship behind it. The difference between error on the training data and error on new data is the generalisation gap, and controlling it is the central practical problem of machine learning.

In the lab, the dashed curve is the true function and each point carries Gaussian noise. Filled points are used for fitting; open points are held back for validation. Raise the polynomial degree from 0 and watch the two error curves on the right.

From underfitting to overfitting

Loading the interactive figure…

Left: data, the true function (dashed) and the fitted polynomial. Right: root-mean-square error on training and validation points for every degree at the current penalty λ. Try degree 11 with λ = 0, then raise λ; then increase the number of training points.

You should see three regimes:

  • Underfitting (degree 0–2). The model is too rigid to follow the curve. Training and validation errors are both high and similar.
  • A good fit (around degree 4–8 here). The validation error reaches its minimum, close to the noise level σ\sigma.
  • Overfitting (high degree, λ=0\lambda = 0). The training error keeps falling towards zero, but the curve develops wild oscillations between the points and the validation error rises. Notice how large the weights ∥w∥\lVert\mathbf w\rVert become: the model cancels huge positive and negative terms to thread through the noise.

The bias–variance decomposition

Why does a U-shape appear? Imagine repeating the whole experiment many times: new noise, new training set, new fit f^\hat f. At a fixed input xx, with y=f(x)+εy = f(x) + \varepsilon and noise variance σ2\sigma^2, the expected squared error of the prediction splits exactly into three terms:

E[(y−f^(x))2]=σ2⏟noise+(f(x)−E[f^(x)])2⏟bias2+E[(f^(x)−E[f^(x)])2]⏟variance. \mathbb E\big[(y - \hat f(x))^2\big] = \underbrace{\sigma^2}_{\text{noise}} + \underbrace{\big(f(x) - \mathbb E[\hat f(x)]\big)^2}_{\text{bias}^2} + \underbrace{\mathbb E\big[(\hat f(x) - \mathbb E[\hat f(x)])^2\big]}_{\text{variance}} .
  • Bias is systematic error: what the model gets wrong on average, because its family of functions cannot represent the truth. Rigid models have high bias.
  • Variance is how much the fit changes from one training sample to the next. Flexible models fitted to few points have high variance; press New noisy sample at degree 11 and watch the curve jump about.
  • Noise σ2\sigma^2 is irreducible: no model can predict it. A validation RMSE well below σ\sigma means something is wrong, usually leakage between training and test data.

Adding parameters lowers bias and raises variance. Adding data lowers variance without raising bias, which is why the overfitting at degree 11 largely disappears when you move the training-points slider to 60. More data is the most reliable regulariser.

Regularisation: preferring simple explanations

Instead of choosing the degree, keep a flexible model and penalise complexity. Ridge regression (Tikhonov regularisation) adds the squared size of the weights to the loss:

Lλ(w)=∥y−Xw∥2+λ∥w∥2,w^=(X⊤X+λI)−1X⊤y. L_\lambda(\mathbf w) = \lVert\mathbf y - \mathbf X\mathbf w\rVert^2 + \lambda\lVert\mathbf w\rVert^2, \qquad \hat{\mathbf w} = (\mathbf X^{\top}\mathbf X + \lambda\mathbf I)^{-1}\mathbf X^{\top}\mathbf y .

The extra λI\lambda\mathbf I also makes the matrix well-conditioned, which is why ridge solutions are numerically stable even when features are nearly collinear. In the lab, keep degree 11 and slide λ\lambda upwards: the oscillations disappear, the weights shrink, and the validation error drops well below its unregularised value. Too large a λ\lambda flattens the model towards zero and underfits again; λ\lambda is a hyperparameter that trades bias against variance continuously.

The Bayesian reading. Ridge regression is the most probable weight vector (the maximum a posteriori estimate) if, before seeing data, you believed each weight was drawn from a Gaussian N(0,τ2)\mathcal N(0, \tau^2), with λ=σ2/τ2\lambda = \sigma^2/\tau^2. A penalty is a prior belief that the function is simple. The lasso uses λ∥w∥1\lambda\lVert\mathbf w\rVert_1 instead (a Laplace prior); it drives many weights exactly to zero and so selects features, which is useful when you suspect only a few descriptors matter. Neural networks use the same idea under the name weight decay, along with other implicit regularisers: early stopping, dropout and data augmentation.

Measuring generalisation honestly

Hold-out validation. Split the data into training, validation and test sets. Fit on the training set, choose hyperparameters (degree, λ\lambda, architecture, learning rate) on the validation set, and report performance on the test set once, at the very end. Using the test set to make choices turns it into a second validation set and makes the reported error optimistic.

kk-fold cross-validation. With small data sets a single split is noisy. Divide the data into kk folds (5 or 10 is typical), train kk times, each time holding out one fold, and average the kk validation errors. Leave-one-out is the extreme case k=Nk = N.

In practice

With scikit-learn, a ridge model with a cross-validated penalty on standardised polynomial features takes a few lines:

import numpy as np
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
from sklearn.linear_model import RidgeCV
from sklearn.model_selection import KFold, cross_val_score

rng = np.random.default_rng(0)
x = rng.uniform(0, 4, 40)[:, None]
y = 0.9*np.sin(2.6*x[:, 0])*np.exp(-0.25*x[:, 0]) + 0.15*x[:, 0] + 0.18*rng.normal(size=40)

model = make_pipeline(PolynomialFeatures(10), StandardScaler(),
                      RidgeCV(alphas=np.logspace(-8, 2, 41)))
cv = KFold(n_splits=5, shuffle=True, random_state=0)
scores = cross_val_score(model, x, y, cv=cv, scoring="neg_root_mean_squared_error")
print(f"5-fold RMSE: {-scores.mean():.3f} ± {scores.std():.3f}")

RidgeCV chooses λ\lambda by an efficient leave-one-out estimate inside each training fold, so the outer cross-validation still measures an honest error. For grouped data, replace KFold with GroupKFold and pass a group label, such as the chemical system or the trajectory each sample came from.

Check yourself

  1. Training RMSE is 0.01 eV/atom and validation RMSE is 0.30 eV/atom. What is the most likely diagnosis?
  2. Which change reduces variance without increasing bias?
  3. You train an interatomic potential on 10,000 frames from one MD trajectory, split randomly 80/20. The test error is excellent. Why should you be suspicious?
  4. Ridge regression with penalty λ is equivalent to which Bayesian estimate?

References and further reading

  1. T. Hastie, R. Tibshirani and J. Friedman, The Elements of Statistical Learning, 2nd ed., Springer (2009), chapter 3.4 (shrinkage methods) and chapter 7 (model assessment and selection).
  2. C. M. Bishop, Pattern Recognition and Machine Learning, Springer (2006), §1.1, §3.1.4 (regularised least squares) and §3.2 (the bias–variance decomposition).
  3. S. Geman, E. Bienenstock and R. Doursat, “Neural networks and the bias/variance dilemma,” Neural Computation 4, 1–58 (1992).
  4. A. E. Hoerl and R. W. Kennard, “Ridge regression: biased estimation for nonorthogonal problems,” Technometrics 12, 55–67 (1970).
  5. R. Tibshirani, “Regression shrinkage and selection via the lasso,” Journal of the Royal Statistical Society B 58, 267–288 (1996).
  6. M. Belkin, D. Hsu, S. Ma and S. Mandal, “Reconciling modern machine-learning practice and the classical bias–variance trade-off,” Proceedings of the National Academy of Sciences 116, 15849–15854 (2019).
  7. B. Meredig, E. Antono, C. Church, M. Hutchinson, J. Ling, S. Paradiso, B. Blaiszik, I. Foster, B. Gibbons, J. Hattrick-Simpers, A. Mehta and L. Ward, “Can machine learning identify the next high-temperature superconductor? Examining extrapolation performance for materials discovery,” Molecular Systems Design & Engineering 3, 819–825 (2018).
  8. A. Dunn, Q. Wang, A. Ganose, D. Dopp and A. Jain, “Benchmarking materials property prediction methods: the Matbench test set and Automatminer reference algorithm,” npj Computational Materials 6, 138 (2020).

4Kernels and Gaussian processes

A prediction without an error bar cannot guide an experiment. Gaussian processes give principled uncertainties: the prior over functions, the posterior in closed form, kernels, marginal likelihood, and how uncertainty is estimated for neural networks.

about 14 min readBuilds on Generalisation and regularisation

Why uncertainty is not optional

A model predicts that a new alloy has a formation energy of −0.21 eV/atom. Should you synthesise it? That depends entirely on whether the prediction is good to ±0.01 or ±0.3 eV/atom, and on whether the alloy resembles anything in the training data. Scientific uses of machine learning, such as choosing the next experiment, flagging an MD configuration the potential has never seen, or deciding when to stop a screening campaign, all need the model to say how sure it is.

It helps to separate two kinds of uncertainty.

  • Aleatoric uncertainty is noise in the data themselves: measurement error, or the variance of a property across nominally identical samples. More data does not remove it.
  • Epistemic uncertainty is the model’s ignorance: it grows away from the training data and shrinks as data are added. This is the kind that tells you where to measure next.

Gaussian processes (GPs) model both, in closed form, and are the reference against which other uncertainty methods are judged.

A distribution over functions

A Gaussian process is a probability distribution over functions f(x)f(x) such that the values at any finite set of inputs are jointly Gaussian. It is specified by a mean function (often zero) and a covariance function, or kernel, k(x,x′)k(x, x'), which says how strongly the function values at two inputs are correlated:

f∼GP(0,k),cov⁡(f(x),f(x′))=k(x,x′). f \sim \mathcal{GP}\big(0, k\big), \qquad \operatorname{cov}\big(f(x), f(x')\big) = k(x, x').

The most common choice is the squared-exponential (RBF) kernel

k(x,x′)=σf2exp⁡ ⁣(−(x−x′)22ℓ2). k(x,x') = \sigma_f^2 \exp\!\left(-\frac{(x-x')^2}{2\ell^2}\right).

It encodes a belief about smoothness: points closer than about one length-scale ℓ\ell have nearly equal function values, while points much further apart are nearly independent. σf\sigma_f sets the typical amplitude. Press Clear, then Draw 3 functions in the lab: before any data, these are samples from the prior. Change ℓ\ell and draw again to see what the kernel assumes.

A Gaussian process, one measurement at a time

Loading the interactive figure…

Click to measure the hidden function at any x. The band is the 95% credible interval; the orange line marks the point of highest uncertainty. Try “Observe where most uncertain” several times, and compare a length-scale of 0.2 with one of 3.

Conditioning on data

Suppose we have noisy observations yi=f(xi)+εiy_i = f(x_i) + \varepsilon_i, εi∼N(0,σn2)\varepsilon_i \sim \mathcal N(0,\sigma_n^2), collected in a vector y\mathbf y. By the definition of a GP, the observations and the unknown value f∗=f(x∗)f_* = f(x_*) at a new input are jointly Gaussian:

(yf∗)∼N ⁣(0,  (K+σn2Ik∗k∗⊤k∗∗)), \begin{pmatrix}\mathbf y\\ f_*\end{pmatrix} \sim \mathcal N\!\left(\mathbf 0,\; \begin{pmatrix} \mathbf K + \sigma_n^2\mathbf I & \mathbf k_*\\ \mathbf k_*^{\top} & k_{**}\end{pmatrix}\right),

where Kij=k(xi,xj)K_{ij} = k(x_i,x_j), (k∗)i=k(xi,x∗)(\mathbf k_*)_i = k(x_i, x_*) and k∗∗=k(x∗,x∗)k_{**} = k(x_*,x_*). Conditioning a multivariate Gaussian on some of its components is a standard identity, and it gives the posterior predictive distribution in closed form:

μ∗=k∗⊤(K+σn2I)−1y,σ∗2=k∗∗−k∗⊤(K+σn2I)−1k∗. \mu_* = \mathbf k_*^{\top}\big(\mathbf K + \sigma_n^2\mathbf I\big)^{-1}\mathbf y, \qquad \sigma_*^2 = k_{**} - \mathbf k_*^{\top}\big(\mathbf K + \sigma_n^2\mathbf I\big)^{-1}\mathbf k_* .

Read these two lines carefully, because they explain everything the lab shows.

  • The mean μ∗\mu_* is a weighted sum of kernel functions centred on the data points, μ∗=∑iαi k(xi,x∗)\mu_* = \sum_i \alpha_i\,k(x_i, x_*) with α=(K+σn2I)−1y\boldsymbol\alpha = (\mathbf K + \sigma_n^2\mathbf I)^{-1}\mathbf y. Far from all data the kernel vanishes and the mean returns to the prior mean, zero.
  • The variance starts at the prior variance k∗∗=σf2k_{**} = \sigma_f^2 and is reduced by an amount that depends only on where the data are, not on their values. Uncertainty shrinks near observations and grows back over a distance of about ℓ\ell.
  • Near an observation the variance does not go to zero but to about σn2\sigma_n^2: the model knows the measurement is noisy.

In code, (K+σn2I)(\mathbf K + \sigma_n^2\mathbf I) is factorised once by Cholesky decomposition, LL⊤\mathbf L\mathbf L^\top, and both formulas follow by triangular solves. The cost is O(N3)\mathcal O(N^3) for NN training points, comfortable up to a few thousand points and the main limitation of exact GPs. Sparse approximations that summarise the data with M≪NM \ll N inducing points reduce it to O(NM2)\mathcal O(NM^2).

Choosing the kernel’s hyperparameters

ℓ\ell, σf\sigma_f and σn\sigma_n are not fitted by the formulas above; they are chosen by maximising the log marginal likelihood, the probability of the observed data under the GP prior with all possible functions integrated out:

ln⁡p(y∣X,θ)=−12 y⊤Ky−1y⏟data fit  −  12ln⁡∣Ky∣⏟complexity penalty  −  N2ln⁡2π,Ky=K+σn2I. \ln p(\mathbf y \mid \mathbf X, \boldsymbol\theta) = \underbrace{-\tfrac12\,\mathbf y^{\top}\mathbf K_y^{-1}\mathbf y}_{\text{data fit}} \;\underbrace{-\;\tfrac12\ln\lvert\mathbf K_y\rvert}_{\text{complexity penalty}} \;-\; \tfrac N2\ln 2\pi, \qquad \mathbf K_y = \mathbf K + \sigma_n^2\mathbf I .

The first term rewards fitting the data; the second penalises kernels that could explain too many different data sets (a very short length-scale can fit anything). The balance is an automatic Occam’s razor, and it is why GPs rarely overfit badly. Press Fit hyperparameters in the lab after adding six or eight points; the readout shows the log marginal likelihood for whatever settings you choose by hand, and you can try to beat the optimiser.

In practice

scikit-learn’s implementation maximises the marginal likelihood for you:

import numpy as np
from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.gaussian_process.kernels import ConstantKernel, RBF, WhiteKernel

rng = np.random.default_rng(1)
X = rng.uniform(0, 10, 12)[:, None]
y = np.sin(1.1 * X[:, 0]) + 0.35 * np.cos(2.7 * X[:, 0]) + 0.08 * X[:, 0] + 0.05 * rng.normal(size=12)

kernel = ConstantKernel(1.0) * RBF(length_scale=1.0) + WhiteKernel(noise_level=0.01)
gp = GaussianProcessRegressor(kernel, normalize_y=True, n_restarts_optimizer=5, random_state=0)
gp.fit(X, y)                                   # maximises the log marginal likelihood
print(gp.kernel_)                              # fitted hyperparameters

Xq = np.array([[2.0], [5.0], [12.0]])
mu, sd = gp.predict(Xq, return_std=True)
for x, m, s in zip(Xq[:, 0], mu, sd):
    print(f"x = {x:5.1f}:  {m:6.3f} ± {2*s:.3f}")

The output shows the behaviour you want: ±0.13 at x=5x = 5, inside the data; ±0.58 at x=2x = 2, where samples are sparse; and ±1.3 at x=12x = 12, outside the training range, where the model honestly falls back to its prior.

1.08**2 * RBF(length_scale=0.846) + WhiteKernel(noise_level=0.00489)
x =   2.0:   0.705 ± 0.575
x =   5.0:  -0.152 ± 0.127
x =  12.0:   0.208 ± 1.295

Uncertainty for neural networks

Neural networks do not provide uncertainties by default, and a single network can be confidently wrong far from its training data. The practical options are:

  • Deep ensembles. Train MM networks (typically 5) from different random initialisations and use the spread of their predictions as the uncertainty. Simple, robust and the usual choice for machine-learned interatomic potentials, where the disagreement between ensemble members flags configurations that need a new DFT calculation.
  • Monte Carlo dropout. Keep dropout active at prediction time and average many stochastic forward passes; cheaper, but usually less reliable than ensembles.
  • Last-layer and Laplace approximations. Treat only the final layer (or a local Gaussian around the trained weights) probabilistically.
  • Conformal prediction. A distribution-free wrapper: use the errors on a held-out calibration set to turn any model’s predictions into intervals with a guaranteed coverage, for example 90% of future values fall inside. It assumes only that calibration and future data are exchangeable, so it cannot rescue extrapolation.

Whatever the method, check the calibration: on held-out data, about 95% of true values should fall inside the predicted 95% intervals. Over-confident uncertainties are worse than none, because they are trusted.

Check yourself

  1. In a Gaussian process, what does the posterior variance at a new point depend on?
  2. You set the length-scale ℓ much shorter than the spacing between data points. What happens between the points?
  3. Which term of the log marginal likelihood stops the GP from choosing an absurdly short length-scale that fits the noise?
  4. An ensemble of five interatomic potentials agrees closely on most MD frames but disagrees strongly on a few. What is the sensible use of this signal?

References and further reading

  1. C. E. Rasmussen and C. K. I. Williams, Gaussian Processes for Machine Learning, MIT Press (2006), chapters 2 (regression), 4 (covariance functions) and 5 (model selection); free online at gaussianprocess.org/gpml.
  2. V. L. Deringer, A. P. Bartók, N. Bernstein, D. M. Wilkins, M. Ceriotti and G. Csányi, “Gaussian process regression for materials and molecules,” Chemical Reviews 121, 10073–10141 (2021).
  3. A. P. Bartók, M. C. Payne, R. Kondor and G. Csányi, “Gaussian approximation potentials: the accuracy of quantum mechanics, without the electrons,” Physical Review Letters 104, 136403 (2010).
  4. B. Lakshminarayanan, A. Pritzel and C. Blundell, “Simple and scalable predictive uncertainty estimation using deep ensembles,” Advances in Neural Information Processing Systems 30 (2017).
  5. Y. Gal and Z. Ghahramani, “Dropout as a Bayesian approximation: representing model uncertainty in deep learning,” Proceedings of ICML, 1050–1059 (2016).
  6. A. N. Angelopoulos and S. Bates, “Conformal prediction: a gentle introduction,” Foundations and Trends in Machine Learning 16, 494–591 (2023).
  7. K. Tran, W. Neiswanger, J. Yoon, Q. Zhang, E. Xing and Z. W. Ulissi, “Methods for comparing uncertainty quantifications for material property predictions,” Machine Learning: Science and Technology 1, 025006 (2020).
  8. E. Snelson and Z. Ghahramani, “Sparse Gaussian processes using pseudo-inputs,” Advances in Neural Information Processing Systems 18 (2006).

Neurons and networks

The artificial neuron and logistic regression, multilayer networks as maps of space, backpropagation, and training deep networks in practice.

5The artificial neuron

The smallest piece of every neural network: inputs, weights, a bias and an activation function. What a neuron computes, what it looks like geometrically, where the idea came from, and what one neuron alone can and cannot do.

about 11 min read

Where the idea came from

In 1943 the neurophysiologist Warren McCulloch and the logician Walter Pitts asked a bold question: could a brain cell be described as a tiny logical device? A biological neuron receives signals from thousands of others through synapses, some exciting it and some inhibiting it; if the combined stimulation crosses a threshold, it fires a pulse down its axon. McCulloch and Pitts wrote this as arithmetic: add up the inputs, each multiplied by a strength, and output 1 if the total exceeds a threshold, 0 otherwise. In 1958 Frank Rosenblatt built a machine, the perceptron, that could learn those strengths from examples.

Modern artificial neurons are only loosely inspired by biology, but the arithmetic has hardly changed. Everything in this volume, up to graph networks and transformers, is built from millions of copies of the unit in this chapter.

What a neuron computes

A neuron takes several numbers in, x1,x2,…,xnx_1, x_2, \dots, x_n, and gives one number out. It does two things.

A positive weight means “more of this input makes me fire more”; a negative weight means “more of this input makes me fire less”; a weight near zero means “I ignore this input”. The bias shifts the threshold: a large positive bias makes the neuron fire even with weak input.

One neuron, two inputs

Loading the interactive figure…

Left: the neuron as a diagram, with edge thickness showing the size of each weight (teal positive, pink negative). Right: its output for every input in the plane. Move the sliders and drag the white input point.

Seeing a neuron as geometry

With two inputs, every possible input is a point in a plane, and the neuron gives each point a value: the colours in the lab. Three facts are visible there and are worth understanding exactly.

The boundary is a straight line. The output is halfway (for the sigmoid, y=0.5y = 0.5) where z=0z = 0, that is where w1x1+w2x2+b=0w_1x_1 + w_2x_2 + b = 0. That equation describes a straight line; with three inputs it is a flat plane, and in nn dimensions a hyperplane. Whatever activation you choose, a single neuron divides input space with a flat cut.

The weights point across the line. The weight vector w=(w1,w2)\mathbf w = (w_1, w_2) is perpendicular to the boundary and points towards higher output. Moving the input in the direction of w\mathbf w raises zz fastest; moving along the boundary does not change it at all. Rotating w\mathbf w in the lab rotates the boundary.

The bias slides the line; the size of the weights sets its sharpness. The distance from the origin to the boundary is ∣b∣/∥w∥|b|/\lVert\mathbf w\rVert. Doubling both weights and the bias leaves the boundary where it is but makes the transition from blue to orange twice as abrupt.

The activation function

The activation turns the weighted sum into a response. The common choices, all in the lab:

NameFormulaShapeUsed for
step11 if z≥0z \ge 0, else 00a cliffthe original perceptron; cannot be trained by gradients
sigmoid11+e−z\dfrac{1}{1+e^{-z}}smooth S from 0 to 1outputs that are probabilities
tanhez−e−zez+e−z\dfrac{e^{z}-e^{-z}}{e^{z}+e^{-z}}smooth S from −1 to 1hidden layers; interatomic potentials
ReLUmax⁡(0,z)\max(0, z)zero, then a ramphidden layers of most image and language models

Why not leave the activation out? Then y=w⋅x+by = \mathbf w\cdot\mathbf x + b is just linear regression (the chapter on fitting), and, as later chapters show, stacking any number of linear neurons still gives only a linear function. The bend in the activation is what lets networks of neurons represent curved relationships. Why smooth curves rather than the step? Because learning, the subject of the next chapter, works by nudging the weights in the direction that improves the output, and a step function gives no hint of which direction that is: its slope is zero everywhere except at the cliff.

A neuron as a logic gate, and its limit

With suitable weights a single step-function neuron can compute simple logic. With inputs 0 or 1:

  • AND: w1=w2=1w_1 = w_2 = 1, b=−1.5b = -1.5. Only (1,1)(1, 1) gives z>0z > 0.
  • OR: w1=w2=1w_1 = w_2 = 1, b=−0.5b = -0.5. Any single 1 suffices.
  • NOT (one input): w=−1w = -1, b=0.5b = 0.5.

But no choice of weights computes XOR (output 1 when exactly one input is 1). Plot the four corners: (0,1)(0,1) and (1,0)(1,0) must be on one side of the line, (0,0)(0,0) and (1,1)(1,1) on the other, and no straight line does that. Minsky and Papert’s 1969 book Perceptrons made this limitation famous, and it contributed to a long decline of interest in neural networks. The way out, layers of neurons, is the subject of the chapter after next, where you will watch a small network solve XOR by bending the plane.

A neuron in science

A single neuron with a sigmoid output is exactly logistic regression, one of the most used models in science: will this compound be stable or not, is this spectrum from phase A or B. Its weights are directly interpretable as the influence of each feature. A neuron with no activation is linear regression. Everything more powerful is built by connecting many of them.

Check yourself

  1. A neuron has weights w1=2w_1 = 2, w2=−1w_2 = -1 and bias b=0.5b = 0.5. What is zz for the input (1,3)(1, 3)?
  2. How is the weight vector w\mathbf w oriented relative to the neuron’s decision boundary?
  3. Why can a single neuron not compute XOR?
  4. You multiply both weights and the bias by 10. What changes?

References and further reading

  1. W. S. McCulloch and W. Pitts, “A logical calculus of the ideas immanent in nervous activity,” Bulletin of Mathematical Biophysics 5, 115–133 (1943).
  2. F. Rosenblatt, “The perceptron: a probabilistic model for information storage and organization in the brain,” Psychological Review 65, 386–408 (1958).
  3. M. Minsky and S. Papert, Perceptrons: An Introduction to Computational Geometry, MIT Press (1969).
  4. G. Sanderson (3Blue1Brown), Neural networks, video series (2017–2024), 3blue1brown.com: chapter 1, “But what is a neural network?”
  5. M. A. Nielsen, Neural Networks and Deep Learning, Determination Press (2015), chapter 1, free online.
  6. C. M. Bishop, Pattern Recognition and Machine Learning, Springer (2006), §4.1 (discriminant functions) and §4.3.2 (logistic regression).

6How a neuron learns

Learning as walking downhill: measuring error with a loss function, deriving cross-entropy from probability, computing its gradient in one line, and watching a neuron find its boundary on a loss landscape.

about 10 min readBuilds on The artificial neuron

From choosing weights to learning them

In the previous chapter you set the weights by hand. With two inputs that is a game; with two hundred it is impossible. Learning means letting the data choose the weights. The recipe has three parts, and every network in this volume uses the same three:

  1. a loss function that turns “how wrong are we?” into a single number;
  2. its gradient, which says how the loss changes when each weight is nudged;
  3. a rule that moves the weights a little way downhill, repeated many times.

Measuring error: from probability to cross-entropy

Suppose each training example is a point xi\mathbf x_i with a label yiy_i that is 1 (orange class) or 0 (blue class). A sigmoid neuron outputs a number between 0 and 1, and the natural reading is a probability: y^i=σ(w⋅xi+b)\hat y_i = \sigma(\mathbf w\cdot\mathbf x_i + b) is the neuron’s belief that example ii is orange.

How good is that belief? A principled answer is the probability the model assigns to the labels that actually occurred, the likelihood. For one example it is y^i\hat y_i if the label is 1 and 1−y^i1 - \hat y_i if it is 0, which can be written in one expression as y^i yi(1−y^i)1−yi\hat y_i^{\,y_i}(1-\hat y_i)^{1-y_i}. Multiplying over all examples and taking the negative logarithm (so that products become sums and maximising becomes minimising) gives the cross-entropy loss:

Look at what one term does. If the label is 1 and the neuron says y^=0.99\hat y = 0.99, the cost is −ln⁡0.99≈0.01-\ln 0.99 \approx 0.01. If it says 0.50.5, the cost is 0.690.69. If it confidently says 0.010.01, the cost is 4.64.6, and it grows without limit as the prediction approaches 0. Cross-entropy punishes confident mistakes severely, which is exactly what we want from a classifier.

The gradient, in one line

To go downhill we need the slope of L\mathcal L with respect to every weight. Here the sigmoid and the logarithm cancel in a way that is almost too neat. Use the chain rule on one example, with z=w⋅x+bz = \mathbf w\cdot\mathbf x + b:

The meaning is intuitive. If the neuron says 0.9 for an example that is really 0, the error y^−y=0.9\hat y - y = 0.9 is large and positive, so every weight whose input was positive is pushed down. If the prediction is right, the error is near zero and nothing changes. Learning is driven by mistakes.

Walking downhill

Gradient descent repeats one update: compute the average gradient over the data and step against it,

w←w−η 1N∑i(y^i−yi) xi,b←b−η 1N∑i(y^i−yi), \textcolor{#12a594}{\mathbf w} \leftarrow \textcolor{#12a594}{\mathbf w} - \eta\, \frac1N\sum_i (\hat y_i - y_i)\,\mathbf x_i, \qquad \textcolor{#845ef7}{b} \leftarrow \textcolor{#845ef7}{b} - \eta\,\frac1N\sum_i (\hat y_i - y_i),

where η\eta, the learning rate, sets the step size.

A neuron learning to separate two classes

Loading the interactive figure…

Left: the data and the neuron’s current boundary and probabilities. Right: the loss for every pair of weights (darker is lower, at the current bias), with the path the weights have taken. Press Train. Then add points of either class by clicking, or try a learning rate of 3.

The right-hand panel is the most important picture in machine learning. Every pair of weights is a point on the loss landscape; training is a walk downhill on it. Watch the path: it starts steep and fast, then slows down as the slope flattens near the bottom. With a large learning rate the walk overshoots and zig-zags. For a single sigmoid neuron with cross-entropy, the landscape is convex (bowl-shaped, with no false valleys), so gradient descent always reaches the best weights. That stops being true once neurons are stacked into networks, which is why the optimisation chapter is needed later.

Two things that go wrong already

  • Perfectly separable data. If a straight line separates the classes exactly, the loss can always be lowered by making the weights larger and the boundary sharper, so they grow forever. In the lab, drag the classes apart and watch ∥w∥\lVert\mathbf w\rVert keep increasing. The cure is regularisation, a penalty on large weights, discussed in the generalisation chapter.
  • Unscaled inputs. If one input is in nanometres and another in kelvin, the landscape becomes a long thin valley and gradient descent zig-zags across it. Standardising inputs (subtract the mean, divide by the standard deviation) makes the bowl round.

The perceptron rule

Rosenblatt’s original perceptron used a step function and an even simpler update: for each misclassified example, add η xi\eta\,\mathbf x_i to the weights if it should have fired, and subtract it if it should not. He proved that if the classes can be separated by a line at all, this rule finds one in a finite number of steps (the perceptron convergence theorem). Logistic regression replaces the hard step by a smooth sigmoid so that every example, not only misclassified ones, contributes a gradient, and the output becomes a calibrated probability.

Check yourself

  1. A neuron predicts y^=0.02\hat y = 0.02 for an example whose true label is 1. What is that example’s cross-entropy cost?
  2. What is ∂ℓ/∂wj\partial\ell/\partial w_j for a sigmoid neuron with cross-entropy loss?
  3. Training on perfectly separable data, the loss keeps falling but the weights keep growing. Why?

References and further reading

  1. C. M. Bishop, Pattern Recognition and Machine Learning, Springer (2006), §4.3 (probabilistic discriminative models, logistic regression).
  2. I. Goodfellow, Y. Bengio and A. Courville, Deep Learning, MIT Press (2016), §5.5 (maximum likelihood) and §6.2.2 (output units and cross-entropy).
  3. F. Rosenblatt, Principles of Neurodynamics: Perceptrons and the Theory of Brain Mechanisms, Spartan Books (1962).
  4. A. B. J. Novikoff, “On convergence proofs on perceptrons,” Proceedings of the Symposium on the Mathematical Theory of Automata 12, 615–622 (1962).
  5. G. Sanderson (3Blue1Brown), Neural networks, chapter 2, “Gradient descent, how neural networks learn” (2017).

7Layers and representations

Why stacking neurons into layers creates power that one neuron lacks: each layer is a linear transformation followed by a bend, a network untangles data until a straight line can separate it, and topology sets the limits of how narrow a layer can be.

about 11 min readBuilds on How a neuron learns

Many neurons side by side

A single neuron draws one straight cut (chapter on the neuron). Put several neurons side by side, all reading the same inputs, and you have a layer: each neuron computes its own weighted sum and activation, so a layer of mm neurons turns an input vector into an output vector of mm numbers. In matrix form, a whole layer is one line:

A network feeds the output of one layer into the next, h(1)=σ(W(1)x+b(1))\mathbf h^{(1)} = \sigma(\mathbf W^{(1)}\mathbf x + \mathbf b^{(1)}), h(2)=σ(W(2)h(1)+b(2))\mathbf h^{(2)} = \sigma(\mathbf W^{(2)}\mathbf h^{(1)} + \mathbf b^{(2)}), and so on, and a final neuron reads the last representation. The layers between input and output are hidden layers: nobody tells them what to compute; they discover it during training.

A layer is a transformation of space

There are two ways to look at a network. The first, from the last chapter, asks what boundary each neuron draws in input space. The second, made famous by Christopher Olah and by 3Blue1Brown’s videos, is more revealing: a layer moves every point of space somewhere else. Think of the input plane as a rubber sheet with a grid drawn on it.

  1. The matrix W\mathbf W is a linear transformation: it rotates, stretches, shears or reflects the sheet. Straight grid lines stay straight and parallel, and the origin stays put.
  2. Adding b\mathbf b translates the whole sheet.
  3. The activation tanh⁡\tanh bends it: it squashes each coordinate into (−1,1)(-1, 1), pulling far-away regions in towards the edges of a square while leaving the middle almost unchanged. Grid lines now curve.

The final neuron then draws a single straight line through the transformed sheet. The whole network succeeds if the hidden layers have deformed the sheet so that the two classes end up on opposite sides of some straight line.

A network with two neurons per hidden layer, seen as a transformation of the plane

Loading the interactive figure…

Each dot is a training example and the grid is the original input plane, carried along by the network. Train, then drag “View the data after…” from the input to the final layer, or press Play. The dashed line is the output neuron’s boundary in the final space.

Train the network on two moons, then play the transformation. The first linear map tilts and stretches the plane; the first tanh squeezes it so that the tips of the moons fold inwards; the second pair finishes the job, and in the final space a straight line separates the classes cleanly. Nothing about the classifier is curved. The curved boundary you would see in input space is a straight line in a bent space.

XOR, impossible for one neuron, falls to the same trick: the hidden layer folds the plane so that opposite corners land on the same side.

What layers cannot do: a lesson from topology

Now try the circle inside a ring with two neurons per layer. However long you train, the accuracy stalls. This is not a failure of the optimiser. Each layer here maps the plane continuously into the plane (the linear map and tanh are both continuous, and with two neurons the output is two-dimensional, and for these invertible weights, one-to-one). A continuous, one-to-one map of the plane can stretch and fold the rubber sheet but never tear it, and there is no way to get the inner disc outside the ring without tearing. The two classes stay linked, so no straight line in the final space can separate them.

The fix is a third neuron, a third dimension: with room to move upwards, a layer can lift the centre of the sheet out of the plane like a tent, and a flat plane can then slice between the tent’s peak and the ring. The general lesson, which Olah set out with pictures, is that width gives a network room to untangle; depth gives it more folds. Real networks use tens to thousands of neurons per layer for exactly this reason.

How expressive are layered networks?

The universal approximation theorem (Cybenko 1989; Hornik, Stinchcombe and White 1989) says that a network with one hidden layer of enough neurons and a non-polynomial activation can approximate any continuous function on a bounded region as closely as you like. The intuition is visible in the practical neural-network chapter: pairs of sigmoid neurons make “bumps”, and enough bumps can trace any curve.

The theorem says nothing about how many neurons are needed, and the answer can be astronomically many for one layer. Depth is what makes networks practical. A function made of repeated simple pieces, such as a sum over many atoms, or the layered structure of images, can need exponentially fewer neurons when represented by several layers each building on the last. Early layers learn simple features; later layers combine them into complex ones.

Why the nonlinearity is essential

Remove the activations and every layer is h=Wx+b\mathbf h = \mathbf W\mathbf x + \mathbf b. Composing two such layers gives

W(2)(W(1)x+b(1))+b(2)=W(2)W(1)⏟one matrix x+W(2)b(1)+b(2)⏟one vector, \mathbf W^{(2)}\big(\mathbf W^{(1)}\mathbf x + \mathbf b^{(1)}\big) + \mathbf b^{(2)} = \underbrace{\mathbf W^{(2)}\mathbf W^{(1)}}_{\text{one matrix}}\,\mathbf x + \underbrace{\mathbf W^{(2)}\mathbf b^{(1)} + \mathbf b^{(2)}}_{\text{one vector}},

a single linear layer again. A hundred linear layers are no more powerful than one: they can only rotate and stretch the sheet, never bend it. The bend is the whole point.

Check yourself

  1. In the transformation view, what does the matrix W\mathbf W of a layer do to the grid?
  2. Why can a network with two neurons in every hidden layer not separate a disc from a surrounding ring, however long it trains?
  3. A network has ten layers but no activation functions. What can it represent?

References and further reading

  1. C. Olah, “Neural networks, manifolds, and topology,” colah.github.io (2014), the source of the transformation picture used in this chapter.
  2. G. Sanderson (3Blue1Brown), Neural networks, chapter 1 (2017), and Essence of Linear Algebra, chapter 3, “Linear transformations and matrices” (2016).
  3. G. Cybenko, “Approximation by superpositions of a sigmoidal function,” Mathematics of Control, Signals and Systems 2, 303–314 (1989).
  4. K. Hornik, M. Stinchcombe and H. White, “Multilayer feedforward networks are universal approximators,” Neural Networks 2, 359–366 (1989).
  5. I. Goodfellow, Y. Bengio and A. Courville, Deep Learning, MIT Press (2016), §6.1 (learning XOR) and §6.4 (architecture design, depth).
  6. M. Telgarsky, “Benefits of depth in neural networks,” Proceedings of COLT, PMLR 49, 1517–1539 (2016).

8Backpropagation

How a network learns which of its weights to blame: computational graphs, the chain rule as a relay of local derivatives, a full forward and backward pass with numbers, why reverse mode costs the same as one extra forward pass, and the history of the idea.

about 12 min readBuilds on Layers and representations

The credit-assignment problem

For one neuron, the gradient was a single neat formula. A network has layers of neurons, and a weight deep inside it affects the loss only through everything downstream of it. If the prediction is wrong, how much is each of a million weights to blame, and in which direction should each move? This is the credit-assignment problem, and its solution, backpropagation, is the algorithm that made deep learning possible.

The solution rests on one idea from school calculus, the chain rule, organised cleverly.

Computations as graphs

Write any calculation as a computational graph: a chain (or more generally a network) of simple operations, each node computing one value from the values flowing into it. Here is the smallest interesting network, one input, one hidden sigmoid neuron and one output neuron, with a squared-error loss, as a graph of seven nodes:

x  → ×w1   a  → +b1   z  → σ   h  → ×w2   m  → +b2   y^  → (⋅−y)2   L x \;\xrightarrow{\ \times \textcolor{#12a594}{w_1}\ }\; a \;\xrightarrow{\ +\textcolor{#845ef7}{b_1}\ }\; z \;\xrightarrow{\ \sigma\ }\; h \;\xrightarrow{\ \times \textcolor{#12a594}{w_2}\ }\; m \;\xrightarrow{\ +\textcolor{#845ef7}{b_2}\ }\; \hat y \;\xrightarrow{\ (\cdot - y)^2\ }\; \textcolor{#e64980}{L}

Each node knows two things about itself: how to compute its output from its input (the forward rule) and how its output changes when its input changes (its local derivative). That is all backpropagation needs.

A computational graph, forward and backward

Loading the interactive figure…

Press Forward pass to compute every value from left to right (blue). Then Backward pass sends the gradient ∂L/∂(node) from right to left (pink), multiplying by each local derivative on the way. Compare the backprop gradient with the finite-difference check, then press Update the weights a few times and watch the loss fall.

The chain rule as a relay

The chain rule says that if LL depends on hh, which depends on zz, then ∂L/∂z=(∂L/∂h)(∂h/∂z)\partial L/\partial z = (\partial L/\partial h)(\partial h/\partial z). Applied along the whole chain, the gradient with respect to the first weight is a product of local derivatives:

Backpropagation evaluates this product from the right. Call vˉ=∂L/∂v\bar v = \partial L/\partial v the gradient arriving at node vv. Start with Lˉ=1\bar L = 1 and pass it leftwards, each node multiplying by its local derivative:

y^ˉ=2(y^−y),mˉ=y^ˉ,hˉ=mˉ w2,zˉ=hˉ h(1−h),aˉ=zˉ,wˉ1=aˉ x. \bar{\hat y} = 2(\hat y - y), \quad \bar m = \bar{\hat y}, \quad \bar h = \bar m\, w_2, \quad \bar z = \bar h\, h(1-h), \quad \bar a = \bar z, \quad \bar w_1 = \bar a\, x .

The parameter gradients fall out along the way: bˉ2=y^ˉ\bar b_2 = \bar{\hat y}, wˉ2=mˉ h\bar w_2 = \bar m\, h, bˉ1=zˉ\bar b_1 = \bar z. A worked example with the lab’s starting values (x=1.5x = 1.5, y=0.2y = 0.2, w1=0.8w_1 = 0.8, b1=−0.4b_1 = -0.4, w2=1.3w_2 = 1.3, b2=0.1b_2 = 0.1):

NodeForward valueBackward gradient ∂L/∂(⋅)\partial L/\partial(\cdot)
a=w1xa = w_1x1.2000.4432
z=a+b1z = a + b_10.8000.4432
h=σ(z)h = \sigma(z)0.6902.0721
m=w2hm = w_2h0.8971.5939
y^=m+b2\hat y = m + b_20.9971.5939
L=(y^−y)2L = (\hat y - y)^20.6351

so ∂L/∂w1=0.4432×1.5=0.665\partial L/\partial w_1 = 0.4432 \times 1.5 = 0.665, which the lab confirms against a finite difference.

Why it is cheap: reverse mode

Notice what the backward pass did. Each node was visited once going forward and once coming back, and every parameter’s gradient was produced along the way. The cost of all the gradients together is a small multiple (typically two to three times) of the cost of computing the loss once, whether the network has seven parameters or seventy billion.

Compare the obvious alternative: nudge each weight in turn and re-run the network to see how the loss changes (finite differences). That needs one extra forward pass per parameter, a million passes for a million weights. Backpropagation is a special case of reverse-mode automatic differentiation: for a function with many inputs (the weights) and one output (the loss), sweeping derivatives backwards from the output is the efficient direction. Forward-mode differentiation, which carries derivatives along with the values from the inputs, is efficient in the opposite situation, few inputs and many outputs; you will meet it in differentiable molecular dynamics.

In a real network each node is a whole layer, the values are vectors and the local derivatives are matrices, but nothing else changes. For a layer h=σ(Wx+b)\mathbf h = \sigma(\mathbf W\mathbf x + \mathbf b) the backward rules are:

Vanishing and exploding gradients

The backward pass multiplies many factors together, one per layer. If they are typically smaller than 1, the gradient shrinks exponentially with depth and early layers stop learning (vanishing gradients); if larger than 1, it grows exponentially (exploding gradients). The sigmoid’s derivative is at most 0.25, so deep sigmoid networks suffered badly. The cures are now standard: activations whose slope does not shrink (ReLU and its relatives), careful initialisation of the weights (the practical chapter), normalisation layers, and residual connections h(l+1)=h(l)+f(h(l))\mathbf h^{(l+1)} = \mathbf h^{(l)} + f(\mathbf h^{(l)}), which give the gradient a direct path backwards through every layer.

A short history

Reverse-mode differentiation was described by Seppo Linnainmaa in his 1970 master’s thesis, applied to neural networks by Paul Werbos in his 1974 doctoral thesis, and popularised by David Rumelhart, Geoffrey Hinton and Ronald Williams in a 1986 Nature paper, which showed that hidden layers trained this way learn useful internal representations. Today every deep-learning framework (PyTorch, JAX, TensorFlow) builds the computational graph as your code runs and performs the backward pass automatically when you call loss.backward().

Check yourself

  1. In the backward pass, how is the gradient at a node computed?
  2. A network has 10 million weights. Roughly how many forward passes would finite differences need to get all gradients, and how much does backpropagation cost?
  3. Why did deep networks of sigmoid neurons train poorly before about 2010?

References and further reading

  1. D. E. Rumelhart, G. E. Hinton and R. J. Williams, “Learning representations by back-propagating errors,” Nature 323, 533–536 (1986).
  2. S. Linnainmaa, The representation of the cumulative rounding error of an algorithm as a Taylor expansion of the local rounding errors, master’s thesis, University of Helsinki (1970).
  3. P. J. Werbos, Beyond Regression: New Tools for Prediction and Analysis in the Behavioral Sciences, PhD thesis, Harvard University (1974).
  4. A. G. Baydin, B. A. Pearlmutter, A. A. Radul and J. M. Siskind, “Automatic differentiation in machine learning: a survey,” Journal of Machine Learning Research 18(153), 1–43 (2018).
  5. G. Sanderson (3Blue1Brown), Neural networks, chapters 3 and 4, “What is backpropagation really doing?” and “Backpropagation calculus” (2017).
  6. C. Olah, “Calculus on computational graphs: backpropagation,” colah.github.io (2015).
  7. K. He, X. Zhang, S. Ren and J. Sun, “Deep residual learning for image recognition,” Proceedings of CVPR, 770–778 (2016).
  8. I. Goodfellow, Y. Bengio and A. Courville, Deep Learning, MIT Press (2016), §6.5.

9Training neural networks

Neurons, layers and activation functions; why networks can approximate any smooth function; backpropagation derived with the chain rule; and the choices that matter when the output is an energy whose derivative is a force.

about 15 min readBuilds on Backpropagation, Gradient descent

From fixed features to learned features

A linear model predicts with fixed features: y^=∑kwk ϕk(x)\hat y = \sum_k w_k\,\phi_k(x). Someone has to choose the ϕk\phi_k, and for a complex relationship (the energy of a configuration of 200 atoms, say) nobody knows the right ones. A neural network keeps the same form but makes the features themselves adjustable:

y^(x)=∑k=1Hvk σ(wkx+bk)+c. \hat y(x) = \sum_{k=1}^{H} v_k\,\sigma\big(w_k x + b_k\big) + c .

Each term is a neuron: it takes a weighted sum of its inputs, adds a bias, and passes the result through a fixed nonlinear activation function σ\sigma. The weights wk,bkw_k, b_k decide where and how sharply each neuron bends; the output weights vkv_k decide how much each one contributes. Everything is fitted together by gradient descent. This is a network with one hidden layer of HH neurons, a multilayer perceptron (MLP).

Train a small network and watch what each neuron does

Loading the interactive figure…

Press Train. The thick curve is the network; the faint curves (one hidden layer only) are the contributions of individual neurons. Try 2 neurons, then 4, then 16 on the bond-energy curve; try ReLU on the same curve and switch the view to the slope; then try the step with tanh and with ReLU.

With one tanh neuron the network can only draw one smooth step. Two neurons with opposite signs can draw a bump. Many bumps placed side by side can trace any curve, which is the intuition behind the theorem below. Watch the faint curves during training: the neurons move, stretch and flip until their sum matches the data.

Layers, in matrix form

For a vector input x∈Rd\mathbf x \in \mathbb R^{d}, a layer with HH neurons computes

a=σ(Wx+b),W∈RH×d, b∈RH, \mathbf a = \sigma(\mathbf W\mathbf x + \mathbf b), \qquad \mathbf W \in \mathbb R^{H\times d},\ \mathbf b\in\mathbb R^{H},

with σ\sigma applied element-wise. A deep network stacks LL such layers, each feeding the next:

a(0)=x,z(l)=W(l)a(l−1)+b(l),a(l)=σ(z(l)),y^=W(L)a(L−1)+b(L). \mathbf a^{(0)} = \mathbf x, \qquad \mathbf z^{(l)} = \mathbf W^{(l)}\mathbf a^{(l-1)} + \mathbf b^{(l)}, \qquad \mathbf a^{(l)} = \sigma\big(\mathbf z^{(l)}\big), \qquad \hat y = \mathbf W^{(L)}\mathbf a^{(L-1)} + \mathbf b^{(L)} .

Without the nonlinearity the whole stack would collapse into a single linear map, W(L)⋯W(1)x\mathbf W^{(L)}\cdots\mathbf W^{(1)}\mathbf x, no more expressive than linear regression. The activation is what makes depth useful.

Common activations.

Nameσ(z)\sigma(z)Smooth?Where it is used
tanhtanh⁡z\tanh zyesclassic MLPs; Behler–Parrinello potentials
ReLUmax⁡(0,z)\max(0, z)no (kink at 0)image and language models
SiLU (swish)z/(1+e−z)z/(1+e^{-z})yesSchNet-style and equivariant interatomic potentials
softplusln⁡(1+ez)\ln(1+e^{z})yessome potentials and density models

Why networks can represent almost anything

The universal approximation theorem (Cybenko 1989; Hornik, Stinchcombe and White 1989) states that a network with a single hidden layer and a non-polynomial activation can approximate any continuous function on a bounded domain to any accuracy, given enough neurons. It is an existence result: it says nothing about how many neurons are needed, or whether gradient descent will find the weights. In practice, depth is far more efficient than width: functions with repeated structure, such as sums over many atoms, can need exponentially fewer neurons when represented by several layers.

Backpropagation: the gradient for the price of one extra pass

Training needs ∂L/∂θ\partial L/\partial\theta for every weight. Consider a single data point and squared loss L=(y^−y)2L = (\hat y - y)^2. The chain rule, applied from the output backwards, gives a recursion. Define the error signal of layer ll as δ(l)=∂L/∂z(l)\boldsymbol\delta^{(l)} = \partial L/\partial\mathbf z^{(l)}. Then

δ(L)=2(y^−y),δ(l)=(W(l+1)⊤δ(l+1))⊙σ′(z(l)), \boldsymbol\delta^{(L)} = 2(\hat y - y), \qquad \boldsymbol\delta^{(l)} = \Big(\mathbf W^{(l+1)\top}\boldsymbol\delta^{(l+1)}\Big) \odot \sigma'\big(\mathbf z^{(l)}\big),

and the gradients with respect to the parameters of each layer are outer products of what flowed in and what flows back:

∂L∂W(l)=δ(l) a(l−1)⊤,∂L∂b(l)=δ(l). \frac{\partial L}{\partial \mathbf W^{(l)}} = \boldsymbol\delta^{(l)}\,\mathbf a^{(l-1)\top}, \qquad \frac{\partial L}{\partial \mathbf b^{(l)}} = \boldsymbol\delta^{(l)} .

The cost of the backward pass is about the same as the forward pass: each weight is touched once going forward and once coming back. So the gradient with respect to all parameters, whether there are ten or ten billion, costs roughly two to three forward evaluations. This is what made deep learning possible. The algorithm was popularised for neural networks by Rumelhart, Hinton and Williams (1986). It is a special case of reverse-mode automatic differentiation, which modern frameworks (PyTorch, JAX, TensorFlow) apply automatically to any program built from differentiable operations.

Here is the whole algorithm in NumPy, a one-hidden-layer network fitted to sin⁡3x\sin 3x:

import numpy as np

rng = np.random.default_rng(0)
x = np.linspace(-1, 1, 64)[:, None]               # inputs, shape (N, 1)
y = np.sin(3 * x)                                  # targets

H = 16                                             # hidden neurons
W1 = rng.normal(0, 1.0, (1, H)); b1 = np.zeros(H)
W2 = rng.normal(0, 1/np.sqrt(H), (H, 1)); b2 = np.zeros(1)
lr = 0.05

for step in range(5001):
    # forward pass
    z = x @ W1 + b1                    # (N, H) pre-activations
    a = np.tanh(z)                     # (N, H) activations
    yhat = a @ W2 + b2                 # (N, 1) output
    loss = np.mean((yhat - y) ** 2)
    # backward pass: chain rule, from the loss back to every weight
    d_yhat = 2 * (yhat - y) / len(x)   # dL/dyhat
    dW2 = a.T @ d_yhat;  db2 = d_yhat.sum(0)
    d_a = d_yhat @ W2.T                # dL/da
    d_z = d_a * (1 - a ** 2)           # tanh'(z) = 1 - tanh(z)^2
    dW1 = x.T @ d_z;     db1 = d_z.sum(0)
    # plain gradient-descent update
    W1 -= lr * dW1; b1 -= lr * db1; W2 -= lr * dW2; b2 -= lr * db2
    if step % 1000 == 0:
        print(f"step {step:5d}   loss {loss:.5f}")

The loss falls from 0.25 to 0.0044 in 5000 steps of plain gradient descent. The same network in PyTorch, where loss.backward() does the backward pass for you and Adam replaces the update, reaches 4×10−64\times10^{-6} in 2000 steps:

import torch

torch.manual_seed(0)
x = torch.linspace(-1, 1, 64).unsqueeze(1)
y = torch.sin(3 * x)

model = torch.nn.Sequential(torch.nn.Linear(1, 16), torch.nn.Tanh(), torch.nn.Linear(16, 1))
opt = torch.optim.Adam(model.parameters(), lr=1e-2)

for step in range(2001):
    opt.zero_grad()
    loss = torch.mean((model(x) - y) ** 2)
    loss.backward()            # reverse-mode autodiff fills .grad for every parameter
    opt.step()
    if step % 500 == 0:
        print(f"step {step:5d}   loss {loss.item():.2e}")

Initialisation and training in practice

  • Initial weights must be random, or every neuron in a layer receives the same gradient and they stay identical forever. Their scale matters too: too large and tanh neurons saturate (their gradient vanishes); too small and the signal dies out through the layers. Glorot (Xavier) initialisation uses variance 1/din1/d_{\text{in}} or 2/(din+dout)2/(d_{\text{in}}+d_{\text{out}}) for tanh; He initialisation uses 2/din2/d_{\text{in}} for ReLU. Press New initial weights in the lab: different starting points reach different, comparably good fits, a reminder that the loss surface of a network has many minima.
  • Standardise inputs and outputs, for the conditioning reasons in the gradient-descent lesson.
  • Monitor a validation set and stop or decay the learning rate when it stops improving (the lesson on generalisation).

When the output is an energy

In molecular science a network often predicts an energy EE, and the forces are its negative gradient with respect to atomic positions, Fi=−∂E/∂ri\mathbf F_i = -\partial E/\partial\mathbf r_i, obtained by the same automatic differentiation. That has consequences you can see in the lab.

  • The activation must be smooth. A ReLU network is piecewise linear, so its derivative is piecewise constant and jumps at every kink. Train ReLU on the bond-energy curve, then switch the lab to Slope dy/dx: the energy fit can look good, but its slope, the force, is a staircase, and molecular dynamics with such forces does not conserve energy. Interatomic potentials use tanh, SiLU or similar smooth activations.
  • Fitting forces as well as energies gives far more information per calculation: one DFT run of NN atoms yields one energy but 3N3N force components. This is the subject of the lesson on interatomic potentials.
  • The step function is a warning in the other direction: smooth networks round off discontinuities, and a network asked to fit one will overshoot (try tanh on the step with few neurons).

Check yourself

  1. What would a deep network be equivalent to if every activation function were removed?
  2. A network has 10 million parameters. Roughly how expensive is it to compute the gradient of the loss with respect to all of them by backpropagation?
  3. Why are ReLU activations a poor choice for a network that predicts potential energy for molecular dynamics?
  4. All weights of a hidden layer are initialised to the same value. What happens during training?

References and further reading

  1. I. Goodfellow, Y. Bengio and A. Courville, Deep Learning, MIT Press (2016), chapter 6 (deep feedforward networks) and §6.5 (back-propagation).
  2. C. M. Bishop, Pattern Recognition and Machine Learning, Springer (2006), chapter 5 (neural networks).
  3. M. A. Nielsen, Neural Networks and Deep Learning, Determination Press (2015), free online at neuralnetworksanddeeplearning.com; chapter 4 gives a visual proof of universal approximation.
  4. G. Cybenko, “Approximation by superpositions of a sigmoidal function,” Mathematics of Control, Signals and Systems 2, 303–314 (1989).
  5. K. Hornik, M. Stinchcombe and H. White, “Multilayer feedforward networks are universal approximators,” Neural Networks 2, 359–366 (1989).
  6. D. E. Rumelhart, G. E. Hinton and R. J. Williams, “Learning representations by back-propagating errors,” Nature 323, 533–536 (1986).
  7. A. G. Baydin, B. A. Pearlmutter, A. A. Radul and J. M. Siskind, “Automatic differentiation in machine learning: a survey,” Journal of Machine Learning Research 18(153), 1–43 (2018).
  8. X. Glorot and Y. Bengio, “Understanding the difficulty of training deep feedforward neural networks,” Proceedings of AISTATS, 249–256 (2010).
  9. K. He, X. Zhang, S. Ren and J. Sun, “Delving deep into rectifiers: surpassing human-level performance on ImageNet classification,” Proceedings of ICCV, 1026–1034 (2015).
  10. S. Elfwing, E. Uchibe and K. Doya, “Sigmoid-weighted linear units for neural network function approximation in reinforcement learning,” Neural Networks 107, 3–11 (2018) (SiLU).

Architectures

Convolutional networks, graph neural networks and transformers.

10Graph neural networks

Molecules and crystals as graphs; the message-passing framework that learns its own descriptors; why sum aggregation gives permutation invariance; receptive fields; and the line from SchNet to equivariant networks.

about 13 min readBuilds on Symmetry and atomic descriptors

From hand-made descriptors to learned ones

The descriptors of the previous lesson are designed by hand and then fixed. Graph neural networks (GNNs) keep the same symmetries but learn the description of each atom’s environment, end to end, from the training data. Since about 2017 they have become the dominant architecture for molecular and materials property prediction and for machine-learned interatomic potentials.

A graph G=(V,E)\mathcal G = (\mathcal V, \mathcal E) has nodes and edges. A molecule is a graph with atoms as nodes and bonds as edges; for 3D models the edges usually connect every pair of atoms closer than a cut-off radius, labelled with their distance (and, in periodic crystals, connecting atoms to periodic images of their neighbours, see the lesson on crystal graphs). Each node ii carries a feature vector hi\mathbf h_i, initially an embedding of the element, and each edge may carry features eij\mathbf e_{ij} such as the distance expanded in radial basis functions.

The message-passing framework

Gilmer et al. (2017) showed that most graph networks for chemistry share one template. Each layer tt performs three steps for every node ii:

mij(t)=Mt(hi(t),hj(t),eij)(compute a message along each edge)mi(t)=∑j∈N(i)mij(t)(aggregate the incoming messages)hi(t+1)=Ut(hi(t),mi(t))(update the node’s state) \begin{aligned} \mathbf m_{ij}^{(t)} &= M_t\big(\mathbf h_i^{(t)}, \mathbf h_j^{(t)}, \mathbf e_{ij}\big) && \text{(compute a message along each edge)}\\ \mathbf m_i^{(t)} &= \sum_{j \in \mathcal N(i)} \mathbf m_{ij}^{(t)} && \text{(aggregate the incoming messages)}\\ \mathbf h_i^{(t+1)} &= U_t\big(\mathbf h_i^{(t)}, \mathbf m_i^{(t)}\big) && \text{(update the node's state)} \end{aligned}

where MtM_t and UtU_t are small neural networks shared by all nodes and edges. After TT layers a readout combines the node states into a prediction for the whole graph, for example y^=R(∑ihi(T))\hat y = R\big(\sum_i \mathbf h_i^{(T)}\big), or, for an energy, E=∑iε(hi(T))E = \sum_i \varepsilon\big(\mathbf h_i^{(T)}\big).

Message passing on aspirin

Loading the interactive figure…

The heavy-atom graph of aspirin. Select an atom and step through the layers: the shaded receptive field grows by one bond per layer, and the feature strips change as information flows in. Renumbering the atoms changes nothing in the computed readout.

Why it respects permutation symmetry

Every operation above is either applied identically to each node or edge, or is a sum over neighbours, and a sum does not depend on the order of its terms. Renumbering the atoms therefore renumbers the node states in exactly the same way (the layer is permutation-equivariant), and summing over nodes in the readout makes the prediction permutation-invariant. The lab recomputes the whole network in the new atom order when you press Renumber the atoms; the readout agrees to machine precision. Sum aggregation is also what makes an energy extensive. Mean or max aggregation also respect permutations but lose information about how many neighbours there are; Xu et al. (2019) showed that sum aggregation gives the most expressive networks of this family.

If the edge features depend only on distances, the network is also invariant to rotations and translations. That is the design of SchNet (Schütt et al., 2018), whose message uses a learned “continuous filter” of the interatomic distance, mij=hj⊙W(rbf(rij))\mathbf m_{ij} = \mathbf h_j \odot W\big(\text{rbf}(r_{ij})\big). DimeNet adds bond angles to the messages, gaining three-body information.

Receptive field and the cost of depth

After TT layers, a node’s state can depend on atoms up to TT edges away: its receptive field, shaded in the lab. With a geometric cut-off rcr_c the receptive field is roughly TrcT r_c; four layers with rc=5r_c = 5 Å see 20 Å, which is useful for capturing longer-range effects but makes the energy less local (and harder to parallelise across processors in large MD runs). Very deep message passing also suffers from over-smoothing: repeated averaging makes all node states similar. Practical models use three to six layers, residual connections and, where long-range physics matters, explicit electrostatic or dispersion terms.

Equivariant message passing

Invariant networks throw away directional information early: once the messages see only distances, the network has to rebuild angular information indirectly. Equivariant networks carry vector and tensor features that rotate with the structure. Node features are organised by their angular momentum ll (scalars l=0l = 0, vectors l=1l = 1, rank-2 tensors l=2l = 2, …), edge directions enter through spherical harmonics Ylm(r^ij)Y_{lm}(\hat{\mathbf r}_{ij}), and features are combined with Clebsch–Gordan tensor products, which are guaranteed to produce correctly rotating outputs. NequIP showed that such networks reach a given accuracy with one to three orders of magnitude less training data than invariant ones. MACE combines equivariant messages with the many-body structure of ACE, so that each layer captures high body-order correlations and only two layers are needed. PaiNN and Allegro are other widely used designs. These are the architectures behind most current interatomic potentials, the subject of the lesson on interatomic potentials.

In practice

A message-passing layer in PyTorch, with a numerical check of permutation equivariance. Libraries such as PyTorch Geometric, DGL and e3nn provide optimised layers, neighbour lists and equivariant operations.

import torch

class MessagePassing(torch.nn.Module):
    """h_i' = h_i + MLP([h_i, sum_j MLP_msg(h_j, e_ij)])  (sum aggregation)."""
    def __init__(self, dim, edim):
        super().__init__()
        self.msg = torch.nn.Sequential(torch.nn.Linear(dim + edim, dim), torch.nn.SiLU(), torch.nn.Linear(dim, dim))
        self.upd = torch.nn.Sequential(torch.nn.Linear(2 * dim, dim), torch.nn.SiLU(), torch.nn.Linear(dim, dim))

    def forward(self, h, edge_index, e):
        src, dst = edge_index                              # messages flow src -> dst
        m = self.msg(torch.cat([h[src], e], dim=-1))       # one message per edge
        agg = torch.zeros_like(h).index_add_(0, dst, m)    # sum the messages arriving at each node
        return h + self.upd(torch.cat([h, agg], dim=-1))

torch.manual_seed(0)
n, dim = 6, 16
h = torch.randn(n, dim)
edges = torch.tensor([[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 0]]).T
edge_index = torch.cat([edges, edges.flip(0)], dim=1)     # undirected: both directions
e = torch.randn(edge_index.shape[1], 4)                    # e.g. expanded distances
layer = MessagePassing(dim, 4)
out = layer(h, edge_index, e)

perm = torch.randperm(n); inv = torch.argsort(perm)        # renumber the nodes
out_p = layer(h[perm], inv[edge_index], e)
print("node outputs permute with the nodes:", torch.allclose(out_p, out[perm], atol=1e-6))
print("summed readout unchanged:", torch.allclose(out_p.sum(0), out.sum(0), atol=1e-5))
node outputs permute with the nodes: True
summed readout unchanged: True

Check yourself

  1. After three message-passing layers on a molecular graph with bonds as edges, which atoms can influence the state of atom ii?
  2. Why is sum aggregation over neighbours permutation-invariant?
  3. What do equivariant networks such as NequIP and MACE carry that SchNet does not?
  4. A GNN predicts an energy as E=∑iε(hi(T))E = \sum_i \varepsilon(\mathbf h_i^{(T)}). Doubling the system by placing two well-separated copies side by side gives what energy?

References and further reading

  1. J. Gilmer, S. S. Schoenholz, P. F. Riley, O. Vinyals and G. E. Dahl, “Neural message passing for quantum chemistry,” Proceedings of ICML, 1263–1272 (2017).
  2. K. T. Schütt, H. E. Sauceda, P.-J. Kindermans, A. Tkatchenko and K.-R. Müller, “SchNet – a deep learning architecture for molecules and materials,” Journal of Chemical Physics 148, 241722 (2018).
  3. J. Gasteiger (Klicpera), J. Groß and S. Günnemann, “Directional message passing for molecular graphs,” International Conference on Learning Representations (2020) (DimeNet).
  4. K. Xu, W. Hu, J. Leskovec and S. Jegelka, “How powerful are graph neural networks?,” International Conference on Learning Representations (2019).
  5. T. N. Kipf and M. Welling, “Semi-supervised classification with graph convolutional networks,” International Conference on Learning Representations (2017).
  6. K. T. Schütt, O. T. Unke and M. Gastegger, “Equivariant message passing for the prediction of tensorial properties and molecular spectra,” Proceedings of ICML, 9377–9388 (2021) (PaiNN).
  7. S. Batzner, A. Musaelian, L. Sun, M. Geiger, J. P. Mailoa, M. Kornbluth, N. Molinari, T. E. Smidt and B. Kozinsky, “E(3)-equivariant graph neural networks for data-efficient and accurate interatomic potentials,” Nature Communications 13, 2453 (2022) (NequIP).
  8. I. Batatia, D. P. Kovács, G. N. C. Simm, C. Ortner and G. Csányi, “MACE: higher order equivariant message passing neural networks for fast and accurate force fields,” Advances in Neural Information Processing Systems 35 (2022).
  9. M. Geiger and T. Smidt, “e3nn: Euclidean neural networks,” arXiv:2207.09453 (2022).
  10. W. L. Hamilton, Graph Representation Learning, Morgan & Claypool (2020), free online.
  11. P. Reiser, M. Neubert, A. Eberhard, L. Torresi, C. Zhou, C. Shao, H. Metni, C. van Hoesel, H. Schopmans, T. Sommer and P. Friederich, “Graph neural networks for materials science and chemistry,” Communications Materials 3, 93 (2022).

11Attention and transformers

Scaled dot-product attention derived step by step, why it is divided by √d, multi-head attention, positional encodings including rotary ones, the transformer block, and where transformers appear in chemistry and materials science.

about 13 min readBuilds on Training neural networks

Attention as a soft lookup

In a sequence, a protein, a SMILES string, a sentence, the meaning of one element depends on others, sometimes far away. Recurrent networks pass information along the sequence one step at a time and struggle with long distances. Attention lets every element look directly at every other element and decide how much each matters.

Think of it as a soft dictionary lookup. Each position ii emits a query qi\mathbf q_i (“what am I looking for?”); every position jj offers a key kj\mathbf k_j (“what do I contain?”) and a value vj\mathbf v_j (“what do I pass on?”). Position ii compares its query with every key, turns the similarities into weights that sum to one, and takes the weighted average of the values:

αij=exp⁡ ⁣(qi⋅kj/dk)∑j′exp⁡ ⁣(qi⋅kj′/dk),oi=∑jαij vj. \alpha_{ij} = \frac{\exp\!\big(\mathbf q_i\cdot\mathbf k_j/\sqrt{d_k}\big)}{\sum_{j'}\exp\!\big(\mathbf q_i\cdot\mathbf k_{j'}/\sqrt{d_k}\big)}, \qquad \mathbf o_i = \sum_j \alpha_{ij}\,\mathbf v_j .

The queries, keys and values are learned linear projections of the input vectors xi\mathbf x_i: qi=WQxi\mathbf q_i = \mathbf W_Q\mathbf x_i, ki=WKxi\mathbf k_i = \mathbf W_K\mathbf x_i, vi=WVxi\mathbf v_i = \mathbf W_V\mathbf x_i. Stacking all positions into matrices gives the form in which attention is always written (Vaswani et al., 2017):

Attention⁡(Q,K,V)=softmax⁡ ⁣(QK⊤dk)V. \operatorname{Attention}(\mathbf Q,\mathbf K,\mathbf V) = \operatorname{softmax}\!\left(\frac{\mathbf Q\mathbf K^{\top}}{\sqrt{d_k}}\right)\mathbf V .

When queries, keys and values all come from the same sequence this is self-attention; when queries come from one sequence and keys and values from another (a decoder looking at an encoder’s output, or a ligand attending to a protein) it is cross-attention.

Scaled dot-product attention in two dimensions

Loading the interactive figure…

Seven tokens of LiFePO₄ with two-dimensional embeddings. The matrix shows every query’s weights over every key; the left panel shows the selected query, its weighted connections and its output (the weighted average). Rotate the projections, sharpen the softmax, add a causal mask, and switch on rotary position encoding to make the four O tokens distinguishable.

In the lab, try:

  1. Rotate WQ\mathbf W_Q or WK\mathbf W_K. The same embeddings now produce completely different attention patterns: learning the projections is how a transformer learns what to look for.
  2. Raise the sharpness. The softmax becomes nearly a hard maximum: each query attends to one key. Low sharpness spreads attention evenly, and the output becomes the plain average.
  3. Look at the four O rows. Without positional information they are identical, because identical inputs produce identical queries, keys and outputs. Self-attention is permutation-equivariant: it treats its input as a set. Switch on RoPE and the rows differ.

Why divide by √d?

If the components of q\mathbf q and k\mathbf k are independent with zero mean and unit variance, their dot product ∑a=1dkqaka\sum_{a=1}^{d_k} q_a k_a has variance dkd_k. For dk=64d_k = 64 the scores have a standard deviation of 8, the softmax saturates (one weight near 1, the rest near 0), and its gradients vanish. Dividing by dk\sqrt{d_k} restores unit variance. It is a small detail with a large effect on trainability, and the same reasoning underlies careful weight initialisation (the lesson on neural networks in practice).

Multi-head attention

A single attention pattern can capture one kind of relationship. Multi-head attention runs HH attention operations in parallel, each with its own projections of width dk=dmodel/Hd_k = d_{\text{model}}/H, concatenates their outputs and mixes them with a final linear map:

MHA⁡(X)=[head⁡1,…,head⁡H]WO,head⁡h=Attention⁡(XWQh,XWKh,XWVh). \operatorname{MHA}(\mathbf X) = \big[\operatorname{head}_1, \dots, \operatorname{head}_H\big]\mathbf W_O, \qquad \operatorname{head}_h = \operatorname{Attention}\big(\mathbf X\mathbf W_Q^{h}, \mathbf X\mathbf W_K^{h}, \mathbf X\mathbf W_V^{h}\big).

Different heads can specialise, for example on local neighbours, on chemically similar tokens or on long-range partners, at the same total cost as one wide head.

Where am I? Positional encoding

Because attention ignores order, position must be injected. The original transformer adds fixed sinusoids of different frequencies to the input embeddings,

PE(p,2a)=sin⁡ ⁣(p/100002a/d),PE(p,2a+1)=cos⁡ ⁣(p/100002a/d). \text{PE}(p, 2a) = \sin\!\big(p/10000^{2a/d}\big), \qquad \text{PE}(p, 2a+1) = \cos\!\big(p/10000^{2a/d}\big).

Rotary position embedding (RoPE; Su et al.), used in most current language models, instead rotates each two-dimensional pair of query and key components by an angle proportional to the position, qi→R(iω)qi\mathbf q_i \to \mathbf R(i\omega)\mathbf q_i, kj→R(jω)kj\mathbf k_j \to \mathbf R(j\omega)\mathbf k_j. Since R(iω)⊤R(jω)=R((j−i)ω)\mathbf R(i\omega)^\top\mathbf R(j\omega) = \mathbf R\big((j-i)\omega\big), the score depends only on the relative position j−ij - i, a property that generalises better to sequences longer than those seen in training. The lab’s RoPE switch implements exactly this in two dimensions.

For atoms in space the analogous information is geometry, not order: equivariant transformers (for example Equiformer) build attention weights from interatomic distances and directions instead of sequence positions.

The transformer block

A transformer stacks identical blocks, each made of two sub-layers with residual connections and layer normalisation:

X′=X+MHA⁡(LN⁡(X)),X′′=X′+MLP⁡(LN⁡(X′)). \mathbf X' = \mathbf X + \operatorname{MHA}\big(\operatorname{LN}(\mathbf X)\big), \qquad \mathbf X'' = \mathbf X' + \operatorname{MLP}\big(\operatorname{LN}(\mathbf X')\big).

Attention mixes information between positions; the position-wise MLP (typically four times wider than the model) transforms each position independently. Residual connections let gradients flow through dozens of layers. A causal mask, which sets scores to −∞-\infty for j>ij > i (try it in the lab), stops each position from seeing the future, and turns the model into an autoregressive generator trained to predict the next token. That one change separates encoder models (BERT-like, for representation and property prediction) from decoder models (GPT-like, for generation).

A connection worth noticing. Self-attention is message passing on a complete graph whose edge weights αij\alpha_{ij} are computed on the fly from the node features. A transformer is a graph neural network that has not been told which atoms are neighbours and learns it instead. The price is the O(n2)\mathcal O(n^2) cost of comparing every pair, which limits sequence length; GNNs with a cut-off are O(n)\mathcal O(n).

Transformers in molecular and materials science

  • Protein structure and language models. AlphaFold 2’s Evoformer uses attention over multiple-sequence alignments and residue pairs; protein language models such as ESM-2 learn structure-aware representations from sequences alone.
  • Chemical language models trained on SMILES or SELFIES strings predict properties and generate molecules autoregressively.
  • Crystal generation. CrystaLLM trains a GPT-style model on millions of CIF files and generates plausible crystal structures as text.
  • Attention inside scientific models. Attention layers are often combined with other architectures; the model I used to predict polymer–surface adhesion free energies combines a convolutional encoder for the surface pattern, a recurrent (GRU) encoder for the polymer sequence and a transformer-style attention module (research).

In practice

Attention in NumPy, checked against PyTorch’s fused implementation:

import numpy as np, torch

def attention(Q, K, V, causal=False):
    """Scaled dot-product attention. Q: (n, d), K: (m, d), V: (m, dv)."""
    s = Q @ K.T / np.sqrt(Q.shape[-1])                  # similarity of every query with every key
    if causal:
        s = np.where(np.tril(np.ones_like(s)) == 1, s, -np.inf)
    w = np.exp(s - s.max(axis=-1, keepdims=True))       # numerically stable softmax
    w /= w.sum(axis=-1, keepdims=True)
    return w @ V, w

rng = np.random.default_rng(0)
X = rng.normal(size=(5, 8))                              # 5 tokens, model width 8
Wq, Wk, Wv = (rng.normal(size=(8, 8)) / np.sqrt(8) for _ in range(3))
out, w = attention(X @ Wq, X @ Wk, X @ Wv, causal=True)
print("each row of weights sums to 1:", np.allclose(w.sum(1), 1))

ref = torch.nn.functional.scaled_dot_product_attention(
    *(torch.tensor(M, dtype=torch.float64) for M in (X @ Wq, X @ Wk, X @ Wv)), is_causal=True)
print("matches PyTorch:", np.allclose(out, ref.numpy()))
each row of weights sums to 1: True
matches PyTorch: True

In real models use torch.nn.MultiheadAttention or torch.nn.functional.scaled_dot_product_attention, which dispatch to memory-efficient kernels (FlashAttention) on GPUs.

Check yourself

  1. What happens to the attention weights if the dk\sqrt{d_k} scaling is removed and dkd_k is large?
  2. A sequence contains the same token four times. Without positional encoding, how do their self-attention outputs compare?
  3. With rotary position embeddings, the attention score between positions ii and jj depends on what?
  4. Why does a GPT-style generator use a causal mask during training?

References and further reading

  1. A. Vaswani, N. Shazeer, N. Parmar, J. Uszkoreit, L. Jones, A. N. Gomez, Ł. Kaiser and I. Polosukhin, “Attention is all you need,” Advances in Neural Information Processing Systems 30 (2017).
  2. D. Bahdanau, K. Cho and Y. Bengio, “Neural machine translation by jointly learning to align and translate,” International Conference on Learning Representations (2015).
  3. J. Su, M. Ahmed, Y. Lu, S. Pan, W. Bo and Y. Liu, “RoFormer: enhanced transformer with rotary position embedding,” Neurocomputing 568, 127063 (2024); arXiv:2104.09864 (2021).
  4. M. Phuong and M. Hutter, “Formal algorithms for transformers,” arXiv:2207.09238 (2022).
  5. J. Jumper et al., “Highly accurate protein structure prediction with AlphaFold,” Nature 596, 583–589 (2021).
  6. Z. Lin et al., “Evolutionary-scale prediction of atomic-level protein structure with a language model,” Science 379, 1123–1130 (2023).
  7. L. M. Antunes, K. T. Butler and R. Grau-Crespo, “Crystal structure generation with autoregressive large language modeling,” Nature Communications 15, 10570 (2024).
  8. Y.-L. Liao and T. Smidt, “Equiformer: equivariant graph attention transformer for 3D atomistic graphs,” International Conference on Learning Representations (2023).
  9. T. Dao, D. Y. Fu, S. Ermon, A. Rudra and C. Ré, “FlashAttention: fast and memory-efficient exact attention with IO-awareness,” Advances in Neural Information Processing Systems 35 (2022).