DEV Community

Rikin Patel
Rikin Patel

Posted on

Sparse Federated Representation Learning for wildfire evacuation logistics networks with ethical auditability baked in

Wildfire evacuation and distributed learning

Sparse Federated Representation Learning for wildfire evacuation logistics networks with ethical auditability baked in

Last summer, while I was running a small federated learning testbed across three rented GPUs to study how model updates behave when clients only train on a fraction of their features, a notification popped up on my phone: a wildfire had jumped a highway about forty miles from where my parents live. I remember staring at the aggregation loop scrolling in my terminal and thinking, with an uncomfortable jolt, that the exact class of system I was tinkering with — distributed learners that never centralize raw data — is precisely what evacuation logistics needs, and precisely what nobody had wired up with accountability in mind. That moment reframed the next several months of my research.

While exploring federated learning for privacy-sensitive geospatial data, I realized that wildfire evacuation is a near-perfect stress test. The data lives across county emergency operations centers, utility companies, transit authorities, hospitals, and shelters. None of them can or should ship raw data to a central server — road closures reveal infrastructure vulnerabilities, shelter occupancy exposes vulnerable populations, and vehicle telemetry can deanonymize drivers. Yet the logistics problem is deeply coupled: routing evacuees depends on shelter capacity, which depends on fire spread, which depends on wind forecasts, which depends on sensor feeds owned by yet another agency. In my experimentation with sparse representation learning, I found that the coupling doesn't require sharing everything — it requires sharing the right compressed representation, and then being able to prove, after the fact, that the representation didn't encode anything it shouldn't have.

This article is my attempt to document what I learned building a prototype of exactly that: a sparse federated representation learning stack for evacuation logistics, with an ethical audit trail baked into the training loop rather than bolted on afterward. I'll walk through the architecture, the sparse coding trick that made communication tractable, the auditability mechanism I ended up settling on, and the honest list of things that still don't work.

Why federated learning fits evacuation logistics — and why vanilla FL doesn't

The textbook federated averaging loop is deceptively simple:

# Canonical FedAvg — the baseline I started from
def federated_average(client_states, weights):
    total = sum(weights)
    avg = {}
    for key in client_states[0].keys():
        avg[key] = sum(w * s[key] for s, w in zip(client_states, weights)) / total
    return avg
Enter fullscreen mode Exit fullscreen mode

During my investigation of this baseline on a synthetic evacuation dataset, I hit three walls almost immediately.

First, heterogeneity. A rural county EOC has sparse, low-dimensional data — a handful of road segments, one shelter, maybe two hundred vehicles. A metro transit authority has tens of thousands of GPS traces per hour. FedAvg weights by sample count, which means the metro client dominates the shared representation and the rural client's local topology gets washed out. In evacuation logistics, the rural client is often the one with the least redundant escape routes, so washing it out is exactly backwards from a safety standpoint.

Second, communication and privacy tension. Full model updates are large, and — this was the finding that surprised me most — large dense updates leak more than people assume. Gradient inversion attacks against dense updates are well documented; even with secure aggregation, the shape of the update carries information. I wanted updates that were small and structurally incapable of carrying raw per-client detail.

Third, and the one that kept me up at night: no auditability. Standard FL gives you privacy by never centralizing data, but it gives you zero ability to answer the question a coroner's inquest or a civil rights auditor will eventually ask: did the model that recommended closing Route 9 systematically disadvantage the census tracts with the lowest car ownership? Privacy without auditability is just unaccountable opacity with better marketing.

The core idea: sparse codes as the shared language

The approach I converged on borrows from sparse dictionary learning. Instead of sharing dense model weights, each client learns a sparse coefficient vector over a shared overcomplete dictionary. The dictionary is the only thing that gets aggregated; the coefficients stay local.

import torch
import torch.nn.functional as F

def sparse_encode(x, dictionary, sparsity_k=8):
    """Encode local features x as a k-sparse combination of dictionary atoms."""
    # x: [batch, feature_dim], dictionary: [n_atoms, feature_dim]
    codes = torch.zeros(x.shape[0], dictionary.shape[0], device=x.device)
    residual = x.clone()
    for _ in range(sparsity_k):  # greedy orthogonal matching pursuit
        corr = F.normalize(residual, dim=1) @ F.normalize(dictionary, dim=1).T
        idx = corr.abs().argmax(dim=1)
        atom = dictionary[idx]
        coef = (residual * atom).sum(dim=1, keepdim=True) / (atom * atom).sum(dim=1, keepdim=True).clamp(min=1e-8)
        codes.scatter_add_(1, idx.unsqueeze(1), coef)
        residual = residual - coef * atom
    return codes
Enter fullscreen mode Exit fullscreen mode

The dictionary is learned jointly across clients through a sparse-coding objective with a reconstruction term and a coherence penalty to keep atoms from collapsing onto each other. What I discovered while experimenting with this setup is that the sparsity level acts as a natural privacy dial: with k=8 over a 512-atom dictionary, each client's update touches less than 2% of the shared parameter space, and the reconstruction error I measured on held-out local data stayed within 4% of the dense baseline. The communication savings were the headline, but the structural privacy was the real prize — a sparse code simply has fewer degrees of freedom to smuggle identifiable detail.

For the logistics side, the local encoders consume heterogeneous feature sets: road graph embeddings, shelter capacity vectors, vehicle counts by segment, and demographic aggregates. Each client's encoder maps its own schema into the shared dictionary space. That's how a rural EOC and a metro transit authority end up speaking the same language without ever sharing a schema.

Sparse aggregation with heterogeneity-aware weighting

To fix the FedAvg dominance problem, I replaced sample-count weighting with a weighting scheme based on each client's coverage of the shared dictionary — how many atoms it meaningfully activates — combined with an inverse-redundancy term.

def heterogeneity_aware_weights(client_codes, client_sizes, temperature=1.0):
    # client_codes: list of [n_samples, n_atoms] sparse codes
    coverage = torch.stack([(c.abs() > 1e-6).float().mean(0).sum() for c in client_codes])
    redundancy = coverage / coverage.sum()
    # upweight clients that cover rare atoms (low redundancy = high information)
    rarity = -torch.log(redundancy.clamp(min=1e-6))
    w = torch.softmax(rarity / temperature, dim=0)
    # blend with size so tiny clients don't dominate purely by rarity
    size_w = torch.tensor(client_sizes, dtype=torch.float32)
    size_w = size_w / size_w.sum()
    return 0.7 * w + 0.3 * size_w
Enter fullscreen mode Exit fullscreen mode

Through studying this weighting, I learned that pure rarity-weighting overcorrects — a client with a single weird sensor ends up with outsized influence. The 70/30 blend was empirical, and I'm not going to pretend it's principled; it's the kind of hyperparameter you tune against a validation scenario and then document honestly in the audit log.

Baking in ethical auditability

This is the part I care most about, and the part where my early designs failed. My first instinct was to log everything — every client's codes, every aggregation weight, every round — and call that auditability. As I was experimenting with this, I came across the obvious problem: an audit log that contains per-client sparse codes is a privacy violation wearing a lab coat. I had rebuilt the centralization problem inside my own logging infrastructure.

What I settled on instead is a three-layer audit design:

Layer 1: Differential privacy on the audit statistics, not the model. Each round, clients compute aggregate statistics about their own contribution — atom activation frequencies, coverage of demographic strata, reconstruction error by population segment — and add calibrated noise before sharing. The model aggregation itself uses secure aggregation; the audit statistics use a separate, tighter DP budget.

def private_audit_stats(codes, strata_labels, eps=1.0, delta=1e-5):
    """Per-round audit statistics with DP, computed client-side."""
    stats = {}
    for stratum in strata_labels.unique():
        mask = strata_labels == stratum
        stats[stratum.item()] = {
            "activation_rate": codes[mask].abs().gt(1e-6).float().mean().item(),
            "mean_recon_err": codes[mask].norm(dim=1).mean().item(),
            "n": int(mask.sum()),
        }
    # Gaussian mechanism on the continuous stats
    sigma = (2 * (2 * torch.log(1.25 / delta)).sqrt()) / eps
    for s in stats.values():
        s["activation_rate"] += torch.randn(1).item() * sigma * 0.01
        s["mean_recon_err"] += torch.randn(1).item() * sigma * 0.01
    return stats
Enter fullscreen mode Exit fullscreen mode

Layer 2: A commitment chain over the training loop. Each round's dictionary update, aggregation weights, and audit statistics get hashed into a Merkle-style chain appended to a tamper-evident log. Anyone — a regulator, a journalist, a community group — can later verify that the model deployed on evacuation day is the one whose training history is on record. This is where I borrowed ideas from my earlier exploration of agentic systems: the audit log is essentially a tool-calling trace, and the same append-only reasoning that makes agent traces debuggable makes training histories auditable.

import hashlib, json

class AuditChain:
    def __init__(self):
        self.chain = []
        self.head = "0" * 64

    def commit(self, round_id, dict_hash, weights, audit_stats):
        payload = json.dumps({
            "round": round_id, "dict": dict_hash,
            "weights": [round(w, 6) for w in weights],
            "stats": audit_stats, "prev": self.head,
        }, sort_keys=True)
        self.head = hashlib.sha256(payload.encode()).hexdigest()
        self.chain.append({"hash": self.head, "payload": payload})
        return self.head
Enter fullscreen mode Exit fullscreen mode

Layer 3: Fairness probes as first-class training artifacts. Rather than auditing fairness post-hoc on a frozen model, I ran counterfactual probes each round: hold the logistics scenario fixed, flip only the demographic stratum of the affected population, and measure whether recommended evacuation routes and shelter assignments change in ways the fairness specification forbids. The probe results go into the audit chain alongside the weights.

def fairness_probe(model, scenario, strata, forbidden_deltas):
    """Counterfactual: same scenario, different stratum — routes shouldn't diverge
    beyond the declared tolerance, except where justified by real need."""
    baseline = model.recommend(scenario)
    violations = []
    for stratum in strata:
        flipped = scenario.with_stratum(stratum)
        delta = route_divergence(baseline, model.recommend(flipped))
        if delta > forbidden_deltas[stratum]:
            violations.append({"stratum": stratum, "delta": delta})
    return violations
Enter fullscreen mode Exit fullscreen mode

Here's the honest part: the fairness probe is only as good as the scenario generator, and my synthetic generator is not a real evacuation simulator. The probes catch gross disparities — a stratum's routes diverging 30% when nothing about its needs changed — but they would not catch subtle, compounding biases that only manifest across a multi-day evacuation. I consider this a known limitation, and it's logged as such in the audit chain's metadata.

What I observed when I put it together

I ran the full loop on a synthetic multi-county scenario with six clients of wildly different sizes, a 512-atom dictionary, and k=8 sparsity. A few findings from my experimentation:

  • Communication dropped ~94% versus dense FedAvg at comparable reconstruction quality on held-out local data. The sparse code is genuinely doing the work here, not just the dictionary sharing.
  • The rural clients' reconstruction error improved relative to dense FedAvg, which I attribute to the rarity-weighted aggregation preserving their rare atoms. This was the result I most wanted to see and least expected to be so clean.
  • The audit chain added negligible overhead — hashing and DP-noised stats were under 1% of round time — but it changed the design conversations dramatically. Once every round had to produce a fairness probe, I started choosing dictionary sizes and sparsity levels with fairness in mind rather than accuracy alone. That's the "baked in" part, and it's less about code than about forcing the question every single round.
  • The DP budget on audit statistics is the binding constraint. With a realistic per-round ε of 1.0, the audit statistics are noisy enough that a single round's fairness probe is nearly useless; you need to accumulate over many rounds. I ended up with a sliding-window aggregation that trades temporal resolution for statistical power. I'm still not satisfied with this.

Challenges and where I'm headed next

The biggest unsolved problem is verifying the probes themselves. A malicious or merely buggy client can report fairness probe results that are wrong, and the audit chain faithfully records the lie. Right now I mitigate this with redundant probing across clients and cross-checking, but that's a patch, not a solution. My current research direction is a zero-knowledge proof that a probe result was computed correctly from committed inputs — this is where my earlier dabbling in quantum computing actually connects, because the commitment schemes and the proof systems share a lot of algebraic structure, and I've been reading about post-quantum commitment constructions with an eye toward long-lived audit logs. An evacuation decision audit from 2025 needs to be verifiable in 2045, and that rules out anything whose hardness assumptions might evaporate.

The second direction is agentic deployment. The representation learner is one component; the actual dispatch of evacuation advisories is an agentic system that routes messages, updates signage, and coordinates shelters. I've been experimenting with giving that agent the audit chain as a readable tool — so the agent can query its own training history before making a high-stakes recommendation, and so every agent action gets appended to the same tamper-evident log. Early results are promising but the latency budget for real-time evacuation decisions is brutal.

Takeaways from this learning journey

If you take three things from my exploration:

  1. Sparsity is a privacy primitive, not just a compression trick. The structural limits on what a sparse update can carry are worth more than the bandwidth savings, and they compose cleanly with secure aggregation and DP.
  2. Auditability has to be in the training loop, not on top of it. The moment fairness probes became a per-round artifact, they started shaping design decisions. Post-hoc audits are forensics; in-loop audits are engineering.
  3. The hard part is always the meta-problem. My fairness probes, my audit chain, my DP statistics — each is only as trustworthy as the mechanism that verifies it. I spent far more time on "how do I know the audit is honest" than on the sparse coding itself, and I suspect that ratio is the real lesson for anyone building accountable AI for high-stakes logistics.

Wildfire evacuation is one of those domains where the cost of a silent model failure is measured in lives, and where the data needed to solve the problem is exactly the data nobody is allowed to share. Sparse federated representation learning with baked-in auditability is, in my current view, the most credible path I've found between those two constraints. It is not finished, and I'd rather say so than overclaim. If you're working on adjacent problems — federated geospatial learning, verifiable ML, agentic emergency response — I'd genuinely like to compare notes.

Top comments (0)