Lesson 07: ML Basics with PyTorch¶
Machine learning has become a standard tool in high-energy physics, neutrino astronomy, and gravitational-wave analysis. This lesson covers the concepts and PyTorch mechanics you need to start using it, and to understand what it is actually doing.
Where ML fits in physics research¶
ML is not a replacement for physics understanding. It is a function approximator that is useful when:
- The relationship between inputs and outputs is too complex to write by hand (e.g. classifying signal from background in a high-dimensional feature space).
- You need a surrogate model: a neural network trained to emulate an expensive simulation so you can call it millions of times during inference.
- You want to reconstruct a physical quantity (energy, direction, vertex) from raw detector data.
- You are doing parameter estimation and need a differentiable likelihood.
Prometheus uses ML-based photon
propagation (the olympus module) as a surrogate for the full optical simulation,
exactly the surrogate model use case. Instead of running the slow photon transport code
for every event, it calls a trained neural network that gives the same answer in
microseconds.
Tensors¶
A PyTorch Tensor is conceptually the same as a NumPy array, but it can live on a GPU
and tracks gradient information for automatic differentiation.
pip install torch
import torch
x = torch.tensor([1.0, 2.0, 3.0]) # from a list
y = torch.zeros(100) # zeros
z = torch.randn(32, 10) # random normal, shape (32, 10)
# Operations look identical to NumPy
x ** 2 # element-wise square
z.shape # torch.Size([32, 10])
z.mean(), z.std()
Converting between NumPy and PyTorch:
a = z.numpy() # tensor → numpy (CPU only)
b = torch.from_numpy(a) # numpy → tensor
Moving to GPU when available:
device = "cuda" if torch.cuda.is_available() else "cpu"
z = z.to(device)
A simple neural network¶
PyTorch models are classes that inherit from torch.nn.Module.
Define the layers in __init__, and the forward computation in forward.
import torch
import torch.nn as nn
class FluxRegressor(nn.Module):
"""Predicts log-flux from log-energy using a small fully-connected network."""
def __init__(self, n_hidden: int = 64):
super().__init__()
self.net = nn.Sequential(
nn.Linear(1, n_hidden),
nn.ReLU(),
nn.Linear(n_hidden, n_hidden),
nn.ReLU(),
nn.Linear(n_hidden, 1),
)
def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.net(x)
model = FluxRegressor(n_hidden=128)
The training loop¶
Training a neural network follows the same four steps every iteration:
- Forward pass: run the model on a batch of inputs to get predictions.
- Compute loss: measure how wrong the predictions are.
- Backward pass: compute gradients via backpropagation.
- Update weights: move parameters in the direction that reduces the loss.
import torch
import torch.nn as nn
from torch.optim import Adam
# Toy training data: power-law spectrum
E_train = torch.logspace(2, 6, 2000).unsqueeze(1) # shape (2000, 1)
phi_true = E_train ** -2.37
log_E = torch.log10(E_train)
log_phi = torch.log10(phi_true)
model = FluxRegressor(n_hidden=128)
optimizer = Adam(model.parameters(), lr=1e-3)
loss_fn = nn.MSELoss()
for epoch in range(1000):
optimizer.zero_grad() # 1. clear old gradients
log_phi_pred = model(log_E) # 2. forward pass
loss = loss_fn(log_phi_pred, log_phi) # 3. compute loss
loss.backward() # 4. compute gradients
optimizer.step() # 5. update weights
if epoch % 200 == 0:
print(f"Epoch {epoch:5d} Loss {loss.item():.4e}")
After training, the model evaluates new energies in microseconds, even if the training data took hours to generate.
Evaluation and saving¶
Switch the model to evaluation mode before making predictions (this disables dropout and batch normalisation layers if present):
model.eval()
with torch.no_grad(): # disable gradient tracking
log_phi_pred = model(log_E_test)
phi_pred = 10 ** log_phi_pred.squeeze()
# Save
torch.save(model.state_dict(), "flux_model.pt")
# Load
loaded = FluxRegressor(n_hidden=128)
loaded.load_state_dict(torch.load("flux_model.pt", weights_only=True))
loaded.eval()
Physics use-cases¶
| Task | ML approach |
|---|---|
| Signal/background classification | Binary classifier (sigmoid output + BCELoss) |
| Energy / direction reconstruction | Regression (MSELoss) |
| Fast surrogate for slow simulation | Regression or normalising flow |
| Posterior estimation | Neural posterior estimation (see sbi) |
| Anomaly detection | Autoencoder or flow-based density model |
Libraries worth knowing¶
| Library | Purpose |
|---|---|
| pytorch-lightning | Reduces training loop boilerplate |
| sbi | Simulation-based inference |
| zuko | Normalising flows in PyTorch |
| torch-geometric | Graph neural networks (event reconstruction) |
Further reading¶
The official PyTorch tutorials go considerably deeper than this lesson, covering data loading, custom datasets, transfer learning, and model deployment. The "Learn the Basics" section is the right starting point and directly extends what is covered here.
What to read next¶
Lesson 08 starts the best-practices block: how to structure a Python project so the code you have been writing can be shared, tested, and maintained.