Welcome to Braedyn Thompson's portfolio!
See the roles I'm targeting →

Now playing
Berkeley Lab SeismicSoCal BearLM
PROJECT 02 · DEEP LEARNING · LIVE

Seismic
SoCal

Deep-learning seismology for Southern California. It detects an earthquake, sizes it and estimates shaking on a live 10-station waveform stream, then push-alerts nearby subscribers.

Deployed livePyTorchCNN → Transformer · GNNOracle Cloud
PSsynthetic trace · 30 s window
SeismicSoCal live site
Live siteseismicsocal.duckdns.org: three deep models on real held-out waveforms (1,126 events, 10 stations, M 3.5–7.1), each tested against the classic seismology baseline, with the live SeedLink status in the corner.

Overview01

0.992Detection ROC-AUCvs 0.550 STA/LTA
0.840Magnitude R²vs 0.749 amp+distance
0.760Alert MCCvs 0.655 GMPE-style
10SeedLink stationsCI / SCEDC network, live

Detect → Size → Warn. Each step is a model, each is compared with the classic seismology baseline on held-out real waveforms, and the whole chain runs live, 24/7, on a free cloud VM.

SeismicSoCal trains on 5,800+ multi-station SCEDC waveform windows and 1,126 magnitude events from 2000–2025. Every split is chronological (70/15/15 by event time), because a random split leaks the future into training. A daemon streams 10 SoCal stations over SeedLink, runs detection continuously, confirms events across stations, sizes them, and sends Firebase push alerts to the Android app.

Scope: this is a research prototype, not an official warning system. It does detection, characterization and rapid shaking estimation. It does not predict earthquakes, because short-term prediction is an unsolved problem.

Results vs. classical seismology02

Deep models beat every classical baseline. Detection: 0.992 vs 0.550.

Held-out test set, chronological split (higher is better)0.0000.2500.5000.7501.000Classical seismology baselineDeep model (5-seed ensemble)Detect · ROC-AUCDetect · ROC-AUC — Classical seismology baseline: 0.5500.550Detect · ROC-AUC — Deep model (5-seed ensemble): 0.9920.992Size · R²Size · R² — Classical seismology baseline: 0.7490.749Size · R² — Deep model (5-seed ensemble): 0.8400.840Warn · alert MCCWarn · alert MCC — Classical seismology baseline: 0.6550.655Warn · alert MCC — Deep model (5-seed ensemble): 0.7600.760Baselines: STA/LTA trigger · amplitude + distance regression · GMPE-style shaking estimate
FigureAll three models beat the classical baseline on a held-out chronological test set. Detection: 880 windows / 255 events. Magnitude: 5-seed ensemble. Alerts: threshold tuned on validation, not test.
TaskArchitectureDeep modelBaseline
Detect: is it a quake?CNN → Transformer, single stationAUC 0.992, MCC 0.930STA/LTA 0.550
Size: how big?CNN → GNN → Transformer, multi-stationR² 0.840, MAE 0.12amp + distance 0.749
Warn: how hard will it shake?CNN → GNN → Transformer on first ~8 sMCC 0.760, recall 0.76 @ precision 0.82GMPE-style 0.655

Why MCC and AUC, not accuracy

Earthquake windows are rare, so an "always no" detector looks very accurate. I report ROC-AUC and MCC for detection, R²/MAE against a physics baseline for magnitude, and MCC rather than recall for alerts. Recall is easy to game once you can move the threshold. The baseline reaches similar recall only by false-alarming about twice as often.

STA/LTA scored 0.550. Isn't that basically a coin flip?

Pretty much. The deep detector hit 0.992 on the same held-out windows.

And that's on quakes it had never seen?

Yes, a chronological split, so it never trained on the future.

Detect, in plain English

Give it 30 seconds of shaking recorded by a sensor and it decides whether a real earthquake is happening, or whether it's just ordinary background noise like traffic or wind. It has learned what genuine quakes look like, so it spots ones the older, simpler alarm would miss. On earthquakes it had never seen before, it makes the right call about 99% of the time.

Detection ROC, per-seed AUC, and example event and noise windows
FigureHeld-out test set. Top left: ROC curve, deep detector vs. STA/LTA (0.550). Top right: test AUC for each of 5 seeds, mean 0.992 ± 0.007. Bottom: a real event at CI.TOW2 the deep model caught (P = 1.00) and STA/LTA missed, and a noise window where STA/LTA false-alarmed but the deep model correctly said no (P = 0.00).

It catches the quake STA/LTA missed and ignores the noise STA/LTA flagged.

Size, technically

Network-magnitude regression. Per-station 3-component waveforms feed a CNN feature extractor, then a graph convolution across the 10-station network, then a transformer, then a magnitude head. On 1,126 SoCal events (2000–2025), the 5-seed ensemble scores R² = 0.840 (MAE 0.12) against an amplitude + distance linear baseline at R² = 0.749. A nearest-single-station ablation drops to R² +0.42, which isolates the multi-station graph fusion as the source of the skill.

Predicted vs true magnitude scatter, deep ensemble vs baseline
FigurePredicted vs. USGS catalog magnitude on held-out California events: deep multi-station ensemble (R² 0.840, left) vs. the amplitude + distance physics baseline (R² 0.749, right). Both under-predict the largest event (M5.8 → M5.2 deep, M5.1 baseline), the known limit noted below.

The three models03

All three models share one idea: a 1-D CNN turns raw waveform into local features, and a Transformer encoder reasons over time. Where several stations matter, a graph network fuses them along the real station geometry.

Detect · CNN → Transformer

A 30-second, 100 Hz vertical window (3,000 samples) goes through four strided Conv1d blocks (16→64 channels), then a 2-layer, 4-head Transformer encoder, then mean pooling and a detection head. It learns waveform shape that a threshold-on-energy detector like STA/LTA can't see. Mean AUC across 5 seeds is 0.992 ± 0.007.

Size · CNN → GNN → Transformer

Each station's 3-component trace is embedded by a CNN, tagged with log-distance, and passed through two graph-convolution layers over a normalized adjacency built from station coordinates. A masked Transformer then attends across the stations that actually recorded the event, and a hybrid head adds network amplitude features.

Ablation: what the graph network earns0.000.501.00Nearest single stationMulti-station GNN fusionMagnitude R²Magnitude R² — Nearest single station: 0.420.42Magnitude R² — Multi-station GNN fusion: 0.840.84
FigureUsing only the nearest station drops R² from 0.840 to 0.42. The multi-station graph fusion is what beats the baseline.

Take away the graph network and R² falls from 0.84 to 0.42.

Warn · early-warning ensemble

The same backbone sees only the first ~8 seconds after the P-wave and predicts the peak ground velocity that arrives later. On the continuous value it ties the physics baseline (R² 0.728 vs 0.720). On the decision that matters, whether shaking will cross a notable threshold, it wins clearly: MCC 0.760 vs 0.655.

Known limits: the magnitude regressor under-predicts the very largest events, and a five-variant fine-tune search (augmentation, cosine LR, Huber, dropout, physics blend) found no reliable gain over 0.840. That points to a real ceiling for this data.

Live detection daemon04

The models run continuously on a live stream. USGS isn't in the loop, so the detector fires on the raw waveforms.

01SeedLink10 CI/SCEDC stations stream into rolling per-station buffers
02Quality gatedrop gap-fill zeros, flat runs, clipping and glitch spikes before scoring
03DetectCNN→Transformer on sliding 30 s windows, all stations
04Confirmgraded multi-station coincidence + geographic move-out check
05SizeGNN magnitude ensemble on confirmed events
06Alertone combined FCM push per subscribed device

This check is what stops phones buzzing on sensor noise.

Killing false alarms: graded coincidence

  • Confirmed: at least 2 stations trigger (p ≥ 0.60) within 12 s, cluster within 150 km of the strongest one, and pass a move-out check. The arrival-time differences must be physically possible given the distance between stations (slowest wave 2 km/s, plus 4 s of pick jitter). Only confirmed events are sized and pushed.
  • Tentative: a lone station above a higher 0.85 bar is logged as a possible false alarm but not pushed, because lone live triggers are almost always telemetry noise.
  • Two far-apart stations glitching in the same 12-second window are independent noise, not one source. The 150 km coherence check catches what move-out alone can't.
  • Every declaration goes to an audit log, and a scheduled job cross-checks it against the official catalog.

Alerts by station, not location

Users subscribe to sensor stations, not coordinates. Signup ranks the 10 stations by distance and auto-selects the nearest 3 within 150 km. Each one can be toggled. No latitude or longitude is stored, only station codes and a push token. A shaking model turns magnitude and distance into the plain-language intensity in the alert text.

Alert me near me station subscription screen
ScreenshotAlert me near me, in the Android app: find sensors by city or your location, then subscribe or unsubscribe station by station. It's labelled as rapid detection, not an official warning.

Subscribe to sensors, not a location.

Deployment & architecture05

The whole service runs on an Oracle Cloud Always-Free Ampere A1 (ARM64) VM. Caddy provides automatic HTTPS and the static site, and a systemd unit runs the stdlib-Python backend, which auto-spawns the SeedLink daemon and respawns it if the stream drops. A separate systemd timer runs the catalog cross-check.

BackendPython stdlib HTTP server: stations, geocode, push registration, status, contact, biggest SoCal quakes.
FrontendReact + Vite console: Detect/Size/Warn carousel with evidence, quake browser, near-me signup.
MobileCapacitor Android app built against the live backend, downloadable from /app.
PushFirebase Cloud Messaging, with one combined message per device personalised by station distance.
SeismicSoCal architecture diagram
ArchitectureOffline: SCEDC waveforms labelled against the USGS catalog train the detection, EEW and magnitude models. Online: the live watcher streams SeedLink, runs detection and sizing, and sends FCM alerts. The React console and mobile clients talk to the API server.
SeismicSoCal Detect card with evidence
Live siteThe live console's Detect card with its evidence open: the headline 0.992 ROC-AUC vs. STA/LTA, the plain-English explanation, and the same held-out evidence figure shown above.