Small convolutional neural networks (CNNs) that classify 60 s of vertical ground motion recorded around Anchorage, Alaska, as Noise, Earthquake, Traffic, Train or Aircraft. The models have 9k–94k parameters, so they run in a web browser on a classroom Chromebook. They are built for the SeismicML curriculum with the Concord Consortium (CLUE) and with Romig Middle School, which hosts two of the Raspberry Shake seismometers we use.
Project pages (GitHub Pages, from docs/):
| Page | What it shows |
|---|---|
| docs/report.html | Illustrated report: the datasets, a map of Anchorage with Romig Middle School, the stations and the earthquakes, a catalog of signal types and of the 550 earthquakes, how the CNN predicts a class, and the limitations |
| docs/index_draft.html | Every trained model: data, grouped split, test and held-out scores, training curves |
| docs/review/index.html | Review site: agree or disagree with labeled windows and download your decisions |
| docs/label_verification_2026-09-14.html | Labeling rules, their parameters, counts and check figures |
- Collect labeled 60 s windows from public seismic data (FDSN): AK network broadbands and strong-motion sensors through EarthScope/IRIS, and Raspberry Shake geophones (network AM) at Anchorage schools.
- Label every window with a rule that can be rerun, never by hand. Earthquakes come from USGS ComCat P picks, traffic from the time of day at school Shakes, trains from the Alaska Railroad timetable, and aircraft from high-frequency bursts seen at two Shakes near ANC runway 15/33.
- Train a compact (9.4k parameter) or standard (94k parameter) 1-D CNN on a subset of classes. The training, validation and test split is grouped by earthquake or by station-day.
- Evaluate on a held-out set of 485 windows recorded after every training date.
- Deploy each model as a
models/<id>/folder (metadata.json+ TensorFlow.jsweights.json) that a browser loads directly.
| Label | Class | Data source | Status |
|---|---|---|---|
| 0 | Noise | quiet hours (01–05 local) at every station; pre-P windows for earthquakes | trained |
| 1 | Traffic | 6 Raspberry Shakes at schools, incl. Romig (AM.R1796, R3130), week of 2026-09-07 | trained |
| 2 | Earthquake | 477 ComCat events, M 3–6, 2020–2025, at AK.FIRE, RC01, SSN | trained |
| 3 | Avalanche | — | planned, #9 |
| 4 | Train | AK.K222 at Potter Marsh, 100 m from the railroad, May–Sep 2026 | trained |
| 5 | Aircraft | AM.RFD97, R9286 in Turnagain, Aug–Sep 2026 | trained |
Python 3.10 or newer is recommended. Either environment manager works.
pip + venv
git clone https://github.com/Denolle-Lab/tiny-cnn-seismicML.git
cd tiny-cnn-seismicML
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
pip install jupyter ipykernel # for the notebooks
python -m ipykernel install --user --name=seismic-cnnconda (simpler if usgs-libcomcat fails to build, because it needs GDAL)
conda create -n seismic-cnn -c conda-forge python=3.11 fiona obspy pytorch jupyter
conda activate seismic-cnn
pip install -r requirements.txtCheck the install:
python -c "from src.models import CompactSeismicCNN; print(CompactSeismicCNN(num_classes=2, input_channels=1).count_parameters())"The explainer app also needs Node.js 20.19 or newer (see Deploy).
All commands run from the repository root. Waveform files (*.npy) are never committed. They are rebuilt from public archives, and datasets/metadata/ records exactly which windows each set holds.
python scripts/collect_ak_events.py --config configs/ak_events.yaml # Earthquake + Noise, ~16 min
python scripts/collect_continuous_windows.py --config configs/am_stations.yaml # Traffic week, school Shakes
python scripts/collect_continuous_windows.py --config configs/train_stations.yaml # Train, K222 passenger season
python scripts/collect_continuous_windows.py --config configs/aircraft_stations.yaml # Aircraft, Turnagain, 4 weeks
python scripts/export_metadata.py --check # every set should print "identical" against datasets/metadata/Output goes to notebooks/02_labeling/labeled_data/ as <prefix>_waveforms_<stamp>.npy, _labels_, _metadata_.csv and _summary_.txt. Add --max-events 5 to the first command for a quick test. Details of each collector are in Data collection details.
Open notebooks/03_training/train_cnn_multiclass.ipynb and set:
DATA_SOURCE = 'ak'
EXTRA_SOURCES = ['AM_traffic_*', 'AK_train_*', 'AM_aircraft_*']
MODEL_CLASSES_KEY = 'human' # or 'earthquake', 'rule_based', 'human_traffic', or a list of class namesRunning the notebook end to end band-passes, crops, z-scores and balances the windows, then makes the grouped split (written to datasets/splits/). It trains both architectures and saves the checkpoints, training_summary_*.txt and the models/<id>/ packages. A script version for a config file is:
python train.py --config configs/compact_config.yaml --save-dir modelsClass subsets (src/data/labels.py, MODEL_CLASSES):
| Key | Output classes |
|---|---|
earthquake |
Noise / Earthquake |
["Noise", "Traffic"] |
Noise / Traffic (a list works too) |
rule_based |
Noise / Traffic / Earthquake |
human |
Noise / Train / Aircraft |
human_traffic |
Noise / Traffic / Train / Aircraft |
natural |
Noise / Earthquake / Avalanche (once avalanche data exist) |
python scripts/run_heldout_inference.py # every models/<id> on datasets/heldout_inference/
python scripts/plot_training_curves.py
python scripts/plot_heldout_examples.pyResults are written to datasets/heldout_inference/heldout_predictions.csv, one row per model and window.
jupyter notebook notebooks/04_inference/predict_on_new_station.ipynbSet NETWORK, STATION, CHANNEL, START_TIME and END_TIME. The notebook downloads the data, slides a 60 s window with 50 % overlap, and plots the class probabilities over time. From the command line:
python predict.py --model-path models/<checkpoint>.pth --config configs/standard_config.yamlThe input must match training: vertical component, 100 Hz, band-pass 2–20 Hz, each 60 s window z-scored.
A deployable model is a folder models/<id>/ with metadata.json (class names, sampling rate, window length, accuracy) and weights.json (TensorFlow.js format). Eleven are in the repo (compact-v1…v6, standard-v1…v5). To export a new checkpoint:
python scripts/export_compact_weights_for_tfjs.py --model_path models/seismic_cnn_compact_<stamp>.pth --output_dir /tmp/export
python scripts/export_standard_weights_for_tfjs.py --model_path models/seismic_cnn_standard_<stamp>.pth --output_dir /tmp/exportThen follow docs/generating-model-weights.md to assemble the folder. CLUE loads the folder by URL. To run the CNN explainer locally:
python scripts/export_waveforms_for_explainer.py
python scripts/export_compact_weights_for_tfjs.py
cd explainer-app && npm install && npm run dev # http://localhost:5173browser_demo/ is a plain-HTML demo, and docs/BROWSER_DEPLOYMENT.md covers block-coding integration and hosting.
python scripts/make_report_page.py # docs/report.html from scripts/report_template.html + USGS ComCatBoth take (batch, 1, 6000): one vertical channel, 60 s at 100 Hz.
CompactSeismicCNN |
SeismicCNN |
|
|---|---|---|
| Conv blocks | 3 (16, 32, 64 filters; kernels 7, 5, 3) | 4 (32, 64, 128, 128 filters) |
| Head | global average pool → 1 linear layer | global average pool → 2 linear layers, dropout |
| Parameters | ~9.4k | ~94k |
| Held-out accuracy | 92–98 % depending on class subset | 94–98 % |
from src.models import CompactSeismicCNN, SeismicCNN
model = CompactSeismicCNN(num_classes=4, input_channels=1, input_length=6000)Deployed models in models/ (test = grouped test split, held-out = datasets/heldout_inference/; full details on the model page):
| Classes | Compact | Standard |
|---|---|---|
| Noise / Earthquake | compact-v5: 99.2 % test, 97.5 % held-out |
standard-v4: 98.9 % test, 97.5 % held-out |
| Noise / Traffic | compact-v6: 95.9 % test, 95.0 % held-out |
standard-v5: 97.4 % test, 94.0 % held-out |
| Noise / Traffic / Earthquake | compact-v3: 94.5 % test, 95.3 % held-out |
standard-v2: 96.0 % test, 95.7 % held-out |
| Noise / Traffic / Train / Aircraft | compact-v4: 94.3 % test, 92.5 % held-out |
standard-v3: 96.1 % test, 95.1 % held-out |
| Noise / Earthquake, July 2026 (earlier split) | compact-v2, compact-v1 |
standard-v1 |
Each conv block is convolution → batch norm → ReLU → max-pool. A last-layer feature sees about 0.45 s of signal (compact model), and the global average pool reports how much of each learned pattern occurs in the minute. docs/report.html explains this step by step.
- Weak labels. Labels come from rules (time of day, timetable, catalog), so some windows are mislabeled. Use the review site to flag them.
- One site per class. Trains come only from K222, aircraft only from Turnagain, and earthquakes only from AK broadbands. A model may partly learn the sensor or the site. The held-out set uses the same stations, so it cannot rule this out.
- No amplitude, short memory. Per-window z-scoring removes loudness, and global pooling removes timing within the window.
- Closed set. Every window is forced into one known class. There is no "unknown" output and no handling of mixed windows.
- Limited coverage. M ≥ 3 only, one week of traffic in September, no winter data, no avalanches yet. Held-out Train windows come from a test split, not from new days.
src/ models (cnn.py), labels and class subsets, collectors (data/collect.py), training utilities
scripts/ collectors, export, held-out inference, plotting, report and review-site builders
configs/ collection configs (ak_events, am/train/aircraft_stations, arr_schedule) and training configs
datasets/ committed metadata: per-set window lists, split manifests, held-out set and predictions
models/ deployable models/<id>/ folders and training summaries
notebooks/ 01_data_exploration, 02_labeling, 03_training, 04_inference
docs/ GitHub Pages site, figures, station tables, deployment guides
explainer-app/ React + TensorFlow.js CNN explainer
browser_demo/ minimal browser classifier
See ORGANIZATION.md and notebooks/README.md for more.
Earthquakes (scripts/collect_ak_events.py, configs/ak_events.yaml). The collector queries USGS for M 3–7 events within 150 km of Anchorage (2020-01-01 to 2025-12-01) and takes the reviewed P picks from ComCat (usgs-libcomcat). It downloads vertical broadband data from IRIS and cuts a 120 s earthquake window (P at 30 s) plus a 60 s pre-event noise window per station, band-passed 2–20 Hz at 100 Hz. The 98 noise windows rejected by eye in July 2026 are dropped (docs/ak_dropped_windows.csv). Command-line flags override the config. --zscore reproduces the July 2026 AK_* files byte for byte.
Continuous classes (scripts/collect_continuous_windows.py). This collector writes 60 s / 100 Hz windows in the same layout with a provisional label, per-window features and blank reviewed / review_label columns. --review-sheet N adds a PNG grid and a CSV for review by eye. Label modes:
--label-from timeofday: daytime = Traffic, 01–05 local = Noise. A station-day is kept only if its daytime 5–30 Hz level is at least 2× its night level.--label-from events --events file.csv: windows around listed times. The Train set uses the Alaska Railroad timetable (configs/arr_schedule.yaml→scripts/make_train_events.py→configs/events/arr_summer_2026.csv), searched ±15 min and kept where the 5–30 Hz rms stands out.--label-from rule: rms threshold against a quiet reference. The Aircraft set uses this with a 20–45 Hz band on both Turnagain Shakes.
python scripts/collect_continuous_windows.py --network AM --station R4017 \
--start 2026-09-09 --end 2026-09-10 --class-name Traffic --label-from timeofday --review-sheet 24Station-to-school matches are in docs/am_station_school_matches.csv and distances to the railroad and runways in docs/station_rail_runway_distances.csv. ADS-B flight times for aircraft can be fetched with scripts/fetch_flights_opensky.py (needs an OpenSky account). The shared FDSN client, preprocessing chain and file layout live in src/data/collect.py, which imports without torch.
Label scheme. src/data/labels.py (LABEL_MAP) is append-only: integers on disk never change. select_classes(X, y, key) keeps one subset and remaps it to outputs 0…K−1, with Noise always at 0.
@software{tiny-cnn-seismicML,
title = {tiny-cnn-seismicML: Lightweight CNNs for Seismic Signal Classification in Anchorage},
author = {Denolle Lab},
year = {2026},
url = {https://github.com/Denolle-Lab/tiny-cnn-seismicML}
}See LICENSE. Questions and contributions: open an issue or a pull request on GitHub.