2.25.3.22. pycsamt.ai.processing.uncertainty#
UncertaintyCalibrator — learned per-cell error-floor re-estimation.
Why this is not EMQCScorer#
EMQCScorer answers “is this
(station, frequency) cell trustworthy?” with a score in [0, 1]. It
does not answer “how large should the error bar on this cell be?” –
yet that second question is exactly what an inversion error floor
needs, and today pyCSAMT (like most MT software) uses whatever
z_err field the processing software wrote, or a flat
percentage-of-|Z| floor. Both are frequently miscalibrated: field
error propagation is often too optimistic in quiet bands and too
pessimistic near noise spikes. UncertaintyCalibrator re-estimates
that error directly, using the same per-cell diagnostic features
EMQCScorer already extracts (SNR,
Swift skew, off-diagonal amplitude asymmetry, both off-diagonal
phases) as regression inputs instead of classification inputs.
Training target: recalibration mode#
There is no pre-existing “correct error bar” to regress against, the
way EMQCScorer’s rule-based labels or DimensionalityClassifier’s
skew/ellipticity thresholds exist. Two options were considered:
Recalibration mode (implemented here) – train against the field-processing
z_erritself, expressed as a fractional error \(|z_\text{err}| / |Z|\) rather than an absolute value, so the target is comparable across stations and frequencies with very different \(|Z|\) (the rawz_errcolumn on L18PLT spans three decades, 1.3 to 1625, purely from \(|Z|\) scale; the fractional error is a tight, physically meaningful 1.1% to 54%, median 4.9%, genuinely varying across 1428 distinct values – confirmed non-degenerate before committing to it, the same check that caught Groom-Bailey’s always-1.0gaincolumn being unsafe as a feature inDistortionTypeClassifier). The network is not memorisingz_errverbatim – it only sees the five diagnostic features, so its output is a smoothed, internally consistent re-estimate driven by signal quality, not a pass-through copy.Empirical mode – train against a genuine bootstrap or repeat-measurement spread computed from raw cross-power spectra (
pycsamt.seg.spectra) when available, rather than a recalibrated copy of the field estimate. Not implemented here: none of pyCSAMT’s bundled field-survey EDI data carries an>SPECTRAsection (only four small vendor demo files underdata/MT/SPECTRA/do, with placeholderCOUNTRY="EXAMPLE"metadata – not a real profile, so not a fair verification target for this package’s real-data standard).fit()still accepts anyyexplicitly, so an empirical target computed independently – frompycsamt.seg.spectra, or anywhere else – plugs into the exact same regressor without an API change; only the automaticy=Nonedefault is recalibration-mode.
Feature vector (per station-frequency cell)#
[swift_skew, asym, phase_xy, phase_yx] – four of
EMQCScorer’s five features, built
by the same _extract_qc_features().
Note
EMQCScorer’s fifth feature, snr, is deliberately not
used as a model input here, even though
build_uncertainty_features_table() still reports it as a
diagnostic column. snr = amp / (z_err + eps) and the
recalibration target z_err_frac = z_err / (amp + eps) are built
from the exact same pooled amp/z_err terms – verified on
real L18PLT data, corr(1/snr, z_err_frac) = 1 - 2e-16, i.e.
identical to floating-point precision. Feeding snr in would let
the network “recalibrate” by trivially inverting one input column
rather than genuinely re-estimating error from signal shape – a
target-leakage pitfall (a feature that deterministically encodes
the answer), distinct from the degenerate-feature pitfall caught
in
DistortionTypeClassifier
(its original, always-1.0 gain feature carried no information
at all), but both were caught the same way – by checking a
candidate feature’s actual behaviour against real data before
trusting it.
build_uncertainty_features_table() builds this table (plus the
z_err_frac recalibration target) directly from a site collection.
Working directly with site collections#
UncertaintyCalibrator.apply() accepts and returns a site
collection, sites-in / sites-out like
apply() – but it rescales
Z.z_err rather than replacing it outright, preserving whatever
per-component structure the field error already has (e.g. a larger
diagonal-term error) while shifting the overall level to match the
calibrated estimate.
Functions
|
Build the per-(station, frequency) feature table |
Classes
|
Learned per-(station, frequency) uncertainty / error-floor estimator. |
- class pycsamt.ai.processing.uncertainty.UncertaintyCalibrator(n_features=4, *, hidden=(64, 32), dropout=0.1, lr=0.001, device=None)[source]
Bases:
BaseEMProcessorLearned per-(station, frequency) uncertainty / error-floor estimator.
- Parameters:
n_features (int, default 4) – Length of the per-cell feature vector.
fit()/transform()on abuild_uncertainty_features_table()DataFrame always use the 4-column[swift_skew, asym, phase_xy, phase_yx]layout – deliberately excludingsnr(see the module docstring); raise this only when fitting on a custom feature matrix with more columns of your own.hidden (tuple of int, default (64, 32)) – Hidden-layer widths of the regressor backbone.
dropout (float, default 0.1) – Dropout probability in each hidden layer.
lr (float, default 1e-3) – Default learning rate; can be overridden in
fit().device (str or None) –
"cpu","cuda","mps","/GPU:0", orNonefor auto-detect via the active backend.
Notes
Falls back to a random-forest regressor (scikit-learn) when neither PyTorch nor TensorFlow is available.
Examples
>>> from pycsamt.ai.processing.uncertainty import ( ... UncertaintyCalibrator, build_uncertainty_features_table, ... ) >>> feats = build_uncertainty_features_table(sites) >>> cal = UncertaintyCalibrator.from_features_table( ... feats, epochs=150, ... ) >>> table = cal.predict_table(sites)
- classmethod from_features_table(df, *, log_target=True, **fit_kwargs)[source]
Construct and train a calibrator from a
build_uncertainty_features_table()DataFrame.- Parameters:
df (DataFrame) – Must contain a
z_err_fraccolumn (self-training recalibration target); passy=directly tofit()instead for an empirical/bootstrap target.log_target (bool, default True) – Train in log10 space (recommended – fractional error is right-skewed);
transform()always returns the linear-space value regardless.**fit_kwargs – Passed to
fit()(e.g.epochs=150).
- Return type:
UncertaintyCalibrator
- fit(X, y=None, *, log_target=True, epochs=150, batch_size=64, lr=None, val_frac=0.15, seed=None, verbose=True)[source]
Train the uncertainty calibrator.
- Parameters:
X (ndarray (n_samples, n_features) or DataFrame) – A DataFrame from
build_uncertainty_features_table()self-trains against its ownz_err_fraccolumn whenyis not given.y (ndarray (n_samples,) or None) – Target error/uncertainty values, any positive-valued scale (recalibration fractional error, an empirical bootstrap spread, …). Required when
Xis a plain ndarray – there is no rule to synthesise a numeric target from features alone the way the classifiers in this package synthesise class labels.log_target (bool, default True) – Train in log10 space.
transform()output is always de-logged back to the original target’s linear scale.epochs (int) – Training hyper-parameters.
batch_size (int) – Training hyper-parameters.
lr (float | None) – Training hyper-parameters.
val_frac (float) – Training hyper-parameters.
seed (int | None) – Training hyper-parameters.
verbose (bool) – Training hyper-parameters.
- Return type:
self
- Raises:
ValueError –
yisNoneand no target can be recovered fromX(see above), or no finite training rows remain.
- transform(X)[source]
Predict calibrated per-cell uncertainty.
- Parameters:
X (ndarray (n_samples, n_features) or DataFrame)
- Returns:
y_pred – Calibrated error/uncertainty estimate, de-logged back to the same linear scale as the
fit()target (e.g. fractional error, in[0, 1]-ish units, for the default recalibration target).- Return type:
- predict_table(sites, *, recursive=True, on_dup='replace', strict=False, verbose=0)[source]
Calibrate uncertainty for an entire site collection.
- Parameters:
sites (SiteCollection or compatible)
recursive (bool) – Passed to
ensure_sites().on_dup (str) – Passed to
ensure_sites().strict (bool) – Passed to
ensure_sites().verbose (int) – Passed to
ensure_sites().
- Returns:
df –
build_uncertainty_features_table()’s columns plusz_err_frac_calibrated.- Return type:
DataFrame
- apply(sites, *, inplace=False, floor_frac=None, recursive=True, on_dup='replace', strict=False, verbose=0)[source]
Rescale
Z.z_errto the calibrated level, sites-in / sites-out.Unlike
apply(), this does not replacez_erroutright: it computes the ratio between the calibrated fractional error and the station’s own original pooled fractional error at each frequency, then scales all four tensor components’ existingz_errby that one ratio – preserving whatever per-component structure the field error already carries (e.g. a larger diagonal-term error) while shifting the overall level to the calibrated estimate.- Parameters:
sites (SiteCollection or compatible)
inplace (bool, default
False) – WhenFalse(the default), sites is left untouched and a corrected copy is returned. WhenTrue, sites is modified in place and returned.floor_frac (float or None, default None) – Minimum fractional error to enforce after calibration (e.g.
0.02for a 2% floor) – applied withnp.maximumafter the network prediction, before rescaling.Noneapplies no floor.recursive (bool) – Passed to
ensure_sites().on_dup (str) – Passed to
ensure_sites().strict (bool) – Passed to
ensure_sites().verbose (int) – Passed to
ensure_sites().
- Returns:
corrected – A site collection with the same stations;
Z.z_erris rescaled station-by-station, frequency-by-frequency. Stations with no usablez_errto begin with are left untouched (there is nothing to rescale from).- Return type:
Examples
>>> cal = UncertaintyCalibrator().fit(feats) >>> calibrated = cal.apply(sites)
- property history_: dict[str, list][source]
Training history recorded by the last
fit()call.- Returns:
history –
{"train_loss": [...], "val_loss": [...]}, one value per epoch, in normalised log-target loss units. Empty when the random-forest fallback was used (no epoch loop) or beforefit()has been called. Pass directly toplot_training_history().- Return type:
- pycsamt.ai.processing.uncertainty.build_uncertainty_features_table(sites, *, recursive=True, on_dup='replace', strict=False, verbose=0)[source]
Build the per-(station, frequency) feature table
UncertaintyCalibratorexpects.- Parameters:
sites (SiteCollection or compatible)
recursive (bool) – Passed to
ensure_sites().on_dup (str) – Passed to
ensure_sites().strict (bool) – Passed to
ensure_sites().verbose (int) – Passed to
ensure_sites().
- Returns:
df – Columns:
station,freq,snr,swift_skew,asym,phase_xy,phase_yx(identical toEMQCScorer’s feature table –snris reported for inspection but is not one ofUncertaintyCalibrator’s own model inputs, see the module docstring) plusz_err_frac– the field-processing fractional error \(|z_\text{err}|/|Z|\), pooled the same way as thesnrcolumn’s own numerator/denominator. Rows whose site has noz_errat all are dropped (z_err_fraccannot be computed); empty (but correctly columned) when no station has error estimates.- Return type:
DataFrame