campus-weather / README.md
citysyntaxlab's picture
Upload README.md with huggingface_hub
d6c3a89 verified
|
Raw
History Blame Contribute Delete
7.56 kB
---
license: cc-by-4.0
tags:
- weather
- microclimate
- campus
- singapore
- variational-autoencoder
- embeddings
---
# 🌀️ NUS-40: Dense Campus Weather Embeddings
> **40 weather stations Β· 1 year hourly Β· 6 variables Β· NUS Singapore**
> *A single 6-dimensional VAE embedding supports spatial interpolation, forecasting, clustering, anomaly detection, and future prediction.*
---
## πŸ”¬ Overview
A **variational autoencoder (VAE)** that compresses 6-variable weather observations from 40 campus stations into a compact **6-dimensional embedding**. The embedding achieves RΒ² > 0.99 reconstruction and supports **5 downstream tasks** with no retraining:
| # | Task | Result |
|---|---|---|
| 1️⃣ | **Spatial Interpolation** β€” predict weather at unmeasured locations | AirTemp MAE = 0.39Β°C |
| 2️⃣ | **Temporal Forecasting** β€” predict future weather vs persistence baseline | +15.7% skill at T+6h |
| 3️⃣ | **Microclimate Clustering** β€” discover climate zones without labels | 4 zones (silhouette=0.23) |
| 4️⃣ | **Anomaly Detection** β€” flag unusual weather from reconstruction error | 5% flagged, storm-linked |
| 5️⃣ | **24h Future Prediction** β€” rolling forecast across full diurnal cycle | +42% peak skill at T+14h |
---
## πŸ“Š The NUS-40 Dataset
40 stations deployed across the National University of Singapore Kent Ridge campus (~2 kmΒ²), recording at hourly resolution for all of 2025.
### Variables
| Variable | Unit | Mean | Std | Range |
|---|---|---|---|---|
| 🌑️ Air Temperature | Β°C | 28.64 | 2.56 | 21.5 – 39.5 |
| πŸ’§ Relative Humidity | % | 80.49 | 10.11 | 37.6 – 99.5 |
| πŸ”΅ Atmospheric Pressure | hPa | 1006.4 | 2.72 | 994.6 – 1016.4 |
| πŸ’¨ Wind Speed | m/s | 0.65 | 0.67 | 0.0 – 17.0 |
| 🧭 Wind Direction | Β° | 185.4 | 108.0 | 0 – 360 |
| β˜€οΈ Solar Radiation | W/mΒ² | 141.1 | 233.2 | 0 – 1500 |
### At a Glance
- **40 stations**, mean spacing ~100–200 m
- **8,760 hours** (Jan–Dec 2025)
- **2.0 km Γ— 1.4 km** campus footprint
- **Tropical climate** (KΓΆppen Af) β€” minimal seasons, strong diurnal cycle
- **4.7% missing**, with imputation flags provided
- WS17 has no pressure sensor (filled with campus mean)
---
## πŸ—οΈ Model
A standard VAE with MLP encoder and decoder.
```
Input (6 vars) β†’ Encoder (3-layer MLP, 128 hidden) β†’ z ~ N(ΞΌ, σ²) [6 dims]
↓
Output (6 vars) ← Decoder (3-layer MLP, 128 hidden) β†β”€β”€β”€β”€β”˜
```
| Property | Value |
|---|---|
| Parameters | 70,930 |
| Latent dimensions | 6 |
| Encoder/Decoder | 3-layer MLP, LayerNorm, GELU |
| Loss | MSE + Ξ²Β·KL (Ξ² = 0.001) |
| Training | 100 epochs, AdamW, cosine schedule |
| Training time | ~20 min on CPU |
---
## πŸ“ˆ Results
### Reconstruction (RΒ² on held-out test set)
| AirTemp | RelHum | AtmPress | WindSpeed | WindDir | GlobalRad |
|---|---|---|---|---|---|
| **0.9997** | **0.9997** | **0.9995** | 0.9429 | **0.9994** | **0.9998** |
### Spatial Interpolation (5 held-out stations, reconstructed from neighbours)
| Variable | MAE | RΒ² |
|---|---|---|
| 🌑️ Air Temperature | **0.39°C** | **0.949** |
| πŸ’§ Relative Humidity | **1.80%** | **0.944** |
| πŸ”΅ Atmospheric Pressure | **0.21 hPa** | **0.987** |
| πŸ’¨ Wind Speed | 0.33 m/s | βˆ’0.52 |
| β˜€οΈ Solar Radiation | 35.5 W/mΒ² | 0.19 |
> Temperature and humidity interpolate within sensor accuracy (Β±0.3Β°C). Wind and radiation depend too strongly on local building geometry.
### Forecasting Skill (vs persistence baseline)
| Horizon | AirTemp | RelHum |
|---|---|---|
| T+1h | βˆ’6.0% ❌ | βˆ’8.0% ❌ |
| T+6h | **+15.7%** βœ… | **+13.8%** βœ… |
| T+12h | **+37.9%** βœ… | ~+25% βœ… |
| T+24h | +2.5% | +5.0% |
> Persistence wins at 1h in the tropics. Embeddings outperform at 6–15h horizons.
### Anomaly Detection
- **438 hours** flagged (5.0% of year)
- Anomalous hours have **54% less solar radiation** β†’ storm/cloud association
- Bimodal temporal pattern: **peaks at 07:00 (sunrise) and 18:00 (sunset)** transitions
- Station WS17 flagged automatically (missing pressure sensor)
---
## πŸ“ Repository Structure
```
πŸ“¦ citysyntaxlab/campus-weather
β”œβ”€β”€ πŸ“„ README.md
β”œβ”€β”€ πŸ“„ paper/paper.md ← Full manuscript (~4,300 words, 16 references)
β”‚
β”œβ”€β”€ πŸ’» code/
β”‚ β”œβ”€β”€ model.py ← VAE architecture (90 lines)
β”‚ β”œβ”€β”€ train.py ← Data loading, training, embedding extraction
β”‚ β”œβ”€β”€ evaluate.py ← All 5 downstream evaluations
β”‚ └── figures.py ← Figure generation
β”‚
β”œβ”€β”€ πŸ“Š figures/ ← 6 figures (PDF + PNG)
β”‚ β”œβ”€β”€ fig1_campus.{pdf,png} ← Station map + discovered clusters
β”‚ β”œβ”€β”€ fig2_reconstruction.{pdf,png} ← Reconstruction RΒ² bar chart
β”‚ β”œβ”€β”€ fig3_spatial.{pdf,png} ← Spatial interpolation results
β”‚ β”œβ”€β”€ fig4_forecasting.{pdf,png} ← Forecast MAE comparison
β”‚ β”œβ”€β”€ fig5_anomaly.{pdf,png} ← Anomaly timeseries + hour distribution
β”‚ └── fig6_future.{pdf,png} ← 24h forecast skill curves
β”‚
β”œβ”€β”€ πŸ§ͺ results/
β”‚ β”œβ”€β”€ all_results.json ← All numerical results
β”‚ β”œβ”€β”€ anomaly_errors.npy ← Hourly reconstruction errors
β”‚ └── checkpoints/
β”‚ β”œβ”€β”€ best.pt ← Trained model weights
β”‚ └── embeddings.npz ← All embeddings: (8760, 40, 6) + data + coords
β”‚
β”œβ”€β”€ πŸ“‘ raw/ ← 40 station CSVs (original measurements)
β”‚ └── NUS_CAMPUS_WS{01-40}_2025_Hourly.csv
β”‚
└── πŸ“‘ imputed/ ← 40 station CSVs (gap-filled, with flags)
└── NUS_CAMPUS_WS{01-40}_2025_Hourly_imputed.csv
```
---
## πŸš€ Quick Start
### Load pre-trained model and embeddings
```python
import torch, numpy as np
from model import WeatherVAE
# Load checkpoint
ckpt = torch.load('results/checkpoints/best.pt', map_location='cpu')
model = WeatherVAE(**ckpt['config'])
model.load_state_dict(ckpt['model'])
model.set_normalisation(ckpt['mean'], ckpt['std'])
model.eval()
# Get embedding for a weather observation
# [WindSpeed, WindDir, AirTemp, RelHum, AtmPress, GlobalRad]
x = torch.tensor([[0.5, 180.0, 29.0, 80.0, 1007.0, 300.0]])
z = model.get_embedding(x) # shape: (1, 6)
# Load pre-computed embeddings for all data
npz = np.load('results/checkpoints/embeddings.npz', allow_pickle=True)
embeddings = npz['embeddings'] # (8760, 40, 6)
data = npz['data'] # (8760, 40, 6)
coords = npz['coords'] # (40, 2) β€” [lat, lng]
```
### Train from scratch
```bash
python code/train.py --data imputed/ --epochs 100 --config base
```
### Run all evaluations
```bash
python code/evaluate.py
```
### Generate figures
```bash
python code/figures.py
```
---
## πŸ“ Paper
Full manuscript at [`paper/paper.md`](paper/paper.md).
**Title:** Learning Dense Weather Embeddings for Campus-Scale Microclimate Analysis
**Target venue:** Building and Environment
**Words:** ~4,300 | **References:** 16 (all verified)
---
## πŸ“Ž Citation
```bibtex
@article{nus40weather2025,
title={Learning Dense Weather Embeddings for Campus-Scale Microclimate Analysis},
author={City Syntax Lab, National University of Singapore},
year={2025}
}
```
---
## πŸ“œ License
Dataset and code released under CC-BY-4.0. Please cite the paper if you use this work.