Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Supervised-Learning for Creation of a Quantitative Precipitation Estimation Model

ARM Logo

Supervised-Learning for Creation of a Quantitative Precipitation Estimation Model

Open In Colab

Overview

Supervised learning requires paired inputs (X) and targets (Y). For radar quantitative precipitation estimates (QPE), good training pairs are hard to obtain because reliable rain-rate labels are limited. Disdrometer is a ground-based device that measures raindrops concentration and sizes, and calculates what a radar would “see”. The key variables are:

  • Zh (reflectivity, dBZ): How strongly radar signal bounces back. More/bigger drops = higher value

  • ZDR (differential reflectivity, dB): Shape of drops. Flatter/bigger drops have higher ZDR

  • KDP (specific differential phase, deg/km): How drops slow the radar signal. Sensitive to high rain rate

  • Specific Attenuation: How much the signal weakens as it travels through rain.

Hardin et al. (2019) provides quality controlled drop size distributions from ARM’s laser (LDQUANTS) and video disdrometers (VDISQUANTS) value-added products. Utilizing T-matrix scattering and additional wavelength, temperature, and drop shape assumptions, both products are well-suited for supervised learning because they provide radar-like variables derived from drop-size distributions (DSD) along with rain rates for training and testing.

The ML Model: Multi-Layer Perceptron (MLP)

A Multilayer Perceptron (MLP) is a type of feedforward artificial neural network. It consists of at least three layers of nodes—an input layer, a hidden layer, and an output layer—where each node (except in the input layer) is a neuron that uses a nonlinear activation function. Each neuron does a weighted sum of its inputs and applies an activation.

This notebook builds and trains a flexible multilayer perceptron (MLP) regression model for radar-based quantitative precipitation estimation. It introduces a reusable FlexMLP class with configurable activations and bundled defaults (Adam, MSE, ReduceLROnPlateau) for easy reuse.

The MLP regression model utilizes estimated C-band radar parameters from SGP LDQUANTS (Hardin et al. (2016)) for training. For validation, utilizing inference load[1], the saved model weights from the MLP regression will be used to predict on the AMF-3 Bankhead National Forest LDQUANTS (Hardin et al. (2024)).

Because training spans only a few months, adding more historical data should improve the performance of preciptiation estimates within heavy-rain.

Prerequisites

ConceptsImportanceNotes
ACT BasicsNecessary
Scikit-learnHelpfulMachine learning library for clustering and preprocessing
PyTorch TutorialsNecessaryMachine learning library for building and training deep learning models
  • Time to learn: 60 min.

  • System requirements: pytorch dependency

Outline

This notebook is organized into 3 main parts:

  • Part 1: Data Acquisition & Preparation Set up paths, download ARM data from two sites, explore the data and relationships, and preprocessing.

  • Part 2: Model Building, Training & Validation
    Build a neural network, understand its hyperparameters, train on SGP data with early stopping, and save the best weights.

  • Part 3: Inference & Validation Load the trained model and test it on independent BNF data to assess generalization.

By the end, you’ll understand supervised learning, neural networks, and how to validate on independent data in a real-world meteorological context.

Loads all libraries:

  • numpy/xarray — data manipulation

  • torch — building and training the neural network

  • sklearn — for standardization and computing accuracy metrics

  • act — Toolkit for downloading and reading ARM data

  • matplotlib — plotting

✓ All packages imported successfully

Part 1: Data Acquisition & Preparation

1.1 Setup and Download Data from ARM

When prompted, enter your ARM username and token manually.

Define quality thresholds. Any observation with reflectivity below 5 dBZ is likely noise (light drizzle or non-precipitation), so it gets filtered out. Same logic for ZDR and KDP — negative values are unphysical for rain.

Data root: /Users/bhupendra/projects/arm-bnf-amf3/data
SGP directory: /Users/bhupendra/projects/arm-bnf-amf3/data/sgpldquantsC1.c1
BNF directory: /Users/bhupendra/projects/arm-bnf-amf3/data/bnfldquantsM1.c1

1.1.1 Download SGP training data

Skip download if files already exist

Found 362 training files in /Users/bhupendra/projects/arm-bnf-amf3/data/sgpldquantsC1.c1. Skipping download.

1.1.2 Download BNF validation data

Found 228 BNF files in /Users/bhupendra/projects/arm-bnf-amf3/data/bnfldquantsM1.c1. Skipping download.

1.2 Filter & Clean Data

Remove very low rain/reflecitivty observations before analysis.

Found 362 training data files
Training Dataset dimensions: {'time': 512236}
Training Dataset variables: ['base_time', 'time_offset', 'rain_rate', 'reflectivity_factor_sband20c', 'reflectivity_factor_cband20c', 'reflectivity_factor_xband20c', 'reflectivity_factor_kaband20c', 'reflectivity_factor_wband20c', 'differential_reflectivity_sband20c', 'differential_reflectivity_cband20c', 'differential_reflectivity_xband20c', 'differential_reflectivity_kaband20c', 'specific_differential_phase_sband20c', 'specific_differential_phase_cband20c', 'specific_differential_phase_xband20c', 'specific_differential_phase_kaband20c', 'specific_attenuation_sband20c', 'specific_attenuation_cband20c', 'specific_attenuation_xband20c', 'specific_attenuation_kaband20c', 'gammapsd_slope', 'gammapsd_shape', 'num_concen', 'norm_num_concen', 'med_diameter', 'mass_weighted_mean_diameter', 'lwc', 'total_droplet_concentration', 'exppsd_slope', 'mean_doppler_vel_sband20c', 'mean_doppler_vel_cband20c', 'mean_doppler_vel_xband20c', 'mean_doppler_vel_kaband20c', 'mean_doppler_vel_wband20c', 'bringi_conv_stra_flag', 'specific_differential_attenuation_sband20c', 'specific_differential_attenuation_cband20c', 'specific_differential_attenuation_kaband20c', 'specific_differential_attenuation_xband20c', 'lat', 'lon', 'alt']
/var/folders/c0/hb5cyy892hqdjht868lw3qk80000gp/T/ipykernel_39525/1598926314.py:8: FutureWarning: The return type of `Dataset.dims` will be changed to return a set of dimension names in future, in order to be more consistent with `DataArray.dims`. To access a mapping from dimension names to lengths, please use `Dataset.sizes`.
  print(f"Training Dataset dimensions: {dict(ds_training_raw.dims)}")

1.3 Explore Data

It is recommended to do detail analysis before any AI/ML study. Therefore, we will plot variable distributions or their relationships to understand the radar variables.

Note: The dataset contains variables for many radar-bands. We will use C-band.

<Figure size 1200x900 with 4 Axes>
✓ plots created successfully

All 4 variables have a positive correlation with rain rate The datapoints for higher rainrates are fewer. The relationship of all the variables with rainrate has significant scatter. Together they can provide better estimates of rain rate.

1.4 Preprocessing data

This cell defines 3 reusable functions. Think of them as a pipeline: Functions to Rename variables, apply thresholds, standardize features, and build DataLoaders. We will use them later during training and testing.

Features are scaled to mean=0, std=1. Without this, Zh (values like 30 dBZ) would dominate over KDP (values like 0.5 deg/km) just because of scale.

Note: This function wraps everything in PyTorch DataLoaders that feed the model in mini-batches during training.

✓ Data loading functions defined

Part 2: Model Building, Training & Validation

We now build the neural network, review hyperparameters, and train the model.

2.1 FlexMLP Neural Network Class

This class defines a flexible Multi-Layer Perceptron with the following features:

  • Configurable architecture: Input → Hidden Layers → Output

  • Bundled defaults: Uses Adam optimizer, MSE loss, and adaptive learning rate scheduler (ReduceLROnPlateau)

  • Auto device detection: Automatically uses GPU if available; otherwise CPU

You don’t need to understand every line of this class, but you should understand the parameters given to the class in the following cells.

2.2 Understanding Model Hyperparameters

Before training, let’s understand the hyperparameters

ParameterValueExplanation
hidden_dims[256, 128, 64, 32]Network layer sizes. Each layer progressively gets smaller to avoid overfitting.
activation_fn‘relu’Activation function (Use relu and you can also try sigmoid). Which of them is fast and works well for regression tasks.
dropout0.2Randomly disable 20% of neurons during training to prevent overfitting.
learning_rate1e-3Step size for weight updates. Too high = unstable; too low = slow convergence.
batch_size16Process 16 samples per gradient update. Small batch = noisy updates; large batch = slower but stable.
epochs50Train for up to 50 passes through the dataset. Early stopping may end sooner.
early_stop_patience5Stop training if validation loss doesn’t improve for 5 consecutive epochs.

Tip: Try changing these values to see how they affect training (e.g., larger learning_rate → faster but riskier, smaller batch_size → more frequent updates).

Set Hyperparameters
We will save model to: mlp_radar_qpe_weights.pt

2.3 Load & Preprocess Training Data

Load training data, apply quality thresholds, standardize, and prepare for training. Using funcrions from 1.4

======================================================================
PREPROCESSING TRAINING DATA
======================================================================

1. Loading TRAINING data from trained files...
   Loaded 14145 raw samples

2. Splitting into train/validation...
   Train batches: 708
   Validation batches: 177
   Target (rain rate) standardization: mean=3.86, std=9.77

We scale the data using these values, so after the inference we convert them to mm/hr.


3. Creating FlexMLP model...
Using device: mps
   ✓ Model created with 45505 parameters

2.4 Training the Model with Early Stopping

The training loop below performs the following steps each epoch:

  1. Forward pass: Send training data through the network to get predictions

  2. Compute loss: Measure prediction error (MSE = Mean Squared Error)

  3. Backward pass: Calculate gradients via backpropagation

  4. Update weights: Adjust weights using Adam optimizer

  5. Validate: Check performance on held-out validation set

  6. Early stopping: Save best model and stop if validation loss plateaus

This process repeats for up to EPOCHS passes or until early stopping is triggered.

======================================================================
STARTING TRAINING
======================================================================
Epoch   1/50 | Train Loss: 0.3387 | Val Loss: 0.0377 ← BEST
Epoch   2/50 | Train Loss: 0.1819 | Val Loss: 0.0278 ← BEST
Epoch   3/50 | Train Loss: 0.1349 | Val Loss: 0.0660
Epoch   4/50 | Train Loss: 0.1112 | Val Loss: 0.0781
Epoch   5/50 | Train Loss: 0.1220 | Val Loss: 0.1182
Epoch   6/50 | Train Loss: 0.0819 | Val Loss: 0.0796
Epoch   7/50 | Train Loss: 0.0863 | Val Loss: 0.0298

Early stopping triggered at epoch 7.
Validation loss plateaued. Best val loss: 0.0278
======================================================================
TRAINING COMPLETE
Model weights saved to: mlp_radar_qpe_weights.pt
======================================================================

2.5 Save the Model

During training, we saved the best model weights (when validation loss was lowest). We save only the weights (state_dict), not the entire object. This avoid compatibility issue of Pytorch versions but we need to create the same model again.

The weights have been saved to MODEL_WEIGHTS_FILE. Next, we’ll load these weights to make predictions on validation data.

✓ Model weights saved to: mlp_radar_qpe_weights.pt
  File exists: True

Part 3: Inference & Validation

In this section, we load the trained model and validate it on independent data from the BNF site.

3.1 Inference & Validation on Independent Data (BNF)

Test the trained model on data from the BNF site. This demonstrates whether the model generalizes to independent observations from a different location.

First, let’s standardize BNF inputs using the training set’s mean/std — not BNF’s own stats. This is critical: the model was trained on SGP-scaled data, so you must apply the same transformation

======================================================================
INFERENCE ON VALIDATION DATA (BNF)
======================================================================

1. Loading BNF validation data...
   Found 228 BNF files
/var/folders/c0/hb5cyy892hqdjht868lw3qk80000gp/T/ipykernel_39525/1906513253.py:13: FutureWarning: The return type of `Dataset.dims` will be changed to return a set of dimension names in future, in order to be more consistent with `DataArray.dims`. To access a mapping from dimension names to lengths, please use `Dataset.sizes`.
  print(f"   Dimensions: {dict(ds_bnf.dims)}")
   Dimensions: {'time': 323241}
   After QC filtering: 15919 valid samples

Run the forward pass — no training, just prediction


2. Loading trained model...
   Using device: mps
Using device: mps
   ✓ Loaded weights from mlp_radar_qpe_weights.pt
FlexMLP( (net): Sequential( (0): Linear(in_features=4, out_features=256, bias=True) (1): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): ReLU() (3): Dropout(p=0.2, inplace=False) (4): Linear(in_features=256, out_features=128, bias=True) (5): BatchNorm1d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (6): ReLU() (7): Dropout(p=0.2, inplace=False) (8): Linear(in_features=128, out_features=64, bias=True) (9): BatchNorm1d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (10): ReLU() (11): Dropout(p=0.2, inplace=False) (12): Linear(in_features=64, out_features=32, bias=True) (13): BatchNorm1d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (14): ReLU() (15): Dropout(p=0.2, inplace=False) (16): Linear(in_features=32, out_features=1, bias=True) ) (loss_fn): MSELoss() )

3. Generating predictions...
   ✓ Generated 15919 predictions

Note: To De-standardize the output we multiply by training rain rate std and add mean to get back to mm/hr


4. Rescaling predictions to original units...
   Note: Network was trained on standardized targets (mean=0, std=1).
   Using training-set target scalers for de-standardization.
   Denormalization formula: rescaled = pred * 9.77 + 3.86
   ✓ Predictions rescaled to mm/hr

Two plots are generated:

  1. Scatter plot (predicted vs observed): points near the red line are good predictions

  2. Residuals histogram: centered near zero means unbiased and narrow histogram means more precise.


5. Computing performance metrics...
   MAE (Mean Absolute Error):  0.758 mm/hr
   RMSE (Root Mean Sq Error):  1.600 mm/hr
   R² (Coefficient of Determ): 0.938

   Interpretation:
   - On average, predictions are off by 0.8 mm/hr
   - Model explains 93.8% of variance in rain rate

6. Creating visualizations...
<Figure size 1300x500 with 2 Axes>
   ✓ Visualizations created

Note: 1. The model was trained on SGP data (2025) and validated using BNF data (Nov 2025 - Apr 2026).

  1. There were fewer than 10 datapoint for rain_rate above 120 mm/hr in the training data.


Summary & Next Steps

We trained a neural network on labeled pairs of (radar parameters to rain rate). We tested on independent data (BNF) to show that the model learned meaningful patterns. The model achieves low MAE and explains a large fraction of rain rate variance, but struggles with heavy rain because those events are rare in the training data.

Extending This Tutorial

To improve the model:

  • Add more historical training data (improves heavy-rain prediction)

  • Try different activation functions (tanh/sigmoid)

  • Adjust learning_rate, batch_size, or dropout

  • Train longer or with different data splits

References

  • Ladino-Rincon, A., Nesbitt, S. W., Di Girolamo, L., Rauber, R. M., McFarquhar, G. M., & Lawson, R. P. (2025). Droplet size distribution retrieval from dual-frequency precipitation radar measurement using a deep neural network. Journal of Atmospheric and Oceanic Technology, 42(11), 1549-1566.

  • Vulpiani, G., Giangrande, S., & Marzano, F. S. (2009). Rainfall estimation from polarimetric S-band radar measurements: Validation of a neural network approach. Journal of Applied Meteorology and Climatology, 48(10), 2022-2036.

Footnotes
  1. The process of running live data points into a trained machine-learning algorithm to calculate an output.

References
  1. Hardin, J., Giangrande, S., & Zhou, A. (2019). ldquants. 10.5439/1432694
  2. Hardin, J., Giangrande, S., & Zhou, A. (2016). Laser Disdrometer Quantities (LDQUANTS), 2016-11-02 to 2026-04-09, Southern Great Plains (SGP), Central Facility, Lamont, OK (C1). In Atmospheric Radiation Measurement (ARM) user facility. 10.5439/1432694
  3. Hardin, J., Giangrande, S., & Zhou, A. (2024). Laser Disdrometer Quantities (LDQUANTS), 2024-10-01 to 2026-04-09, Bankhead National Forest, AL, USA; Long-term Mobile Facility (BNF), Bankhead National Forest, AL, AMF3 (Main Site) (M1). In Atmospheric Radiation Measurement (ARM) user facility. 10.5439/1432694