AI Safety and Ethics

Federated learning

Federated learning trains one shared model by sending weights instead of data — which changes where the data sits, and does not by itself make anything private.

On this page 9
  1. The short answer
  2. The analogy you have already lived
  3. Why this was invented
  4. How one round works
  5. The part that gets misunderstood constantly
  6. Where you have already used it
  7. What is honestly hard here
  8. Remember this
  9. What to learn next

One lesson, three depths. Pick the one that fits you today — you can switch any time.

Beginner — No maths. Plain English.

The short answer

Federated learning trains one model across many devices, by moving the model to the data instead of the data to the model.

The analogy you have already lived

Think of three cooks in three different cities perfecting one shared recipe.

Nobody ships their groceries anywhere. Each cook makes the dish at home, notes what they changed, and posts only the notes. A coordinator merges the notes into a new recipe and sends it back out. Round after round, the shared recipe improves.

The ingredients never travelled. The recipe did.

Your keyboard's next-word prediction learns this way. Your typing stays on your phone. Only weight updates go to the server.

Why this was invented

Some data cannot be collected into one place, for reasons that are not negotiable.

The law restricts moving it. In many countries health records cannot leave the hospital, or the country, without consent and safeguards that are hard to obtain.

It is too big or too expensive. A billion phones cannot upload every photo.

Users would refuse. Nobody wants to hand over their full message history to improve autocomplete.

Before federated learning, the answer to all three was "then we cannot train a model". Now the answer is "the model visits the data".

How one round works

     server holds the current model
                 │
      ┌──────────┼──────────┐
      ▼          ▼          ▼
   phone 1    phone 2    phone 3        each trains on ITS OWN data
      │          │          │
      └──────────┼──────────┘
                 ▼
      average the updated weights        ← this averaging step is "FedAvg"
                 │
        new, better shared model
                 │
              repeat

Two things never move: the raw data, and the labels. Only numbers describing the model go up and down.

The part that gets misunderstood constantly

Federated learning is not privacy. It is data locality — a statement about where files live.

The weight update sent from a phone was computed from that phone's data, so it carries information about that data. Researchers have reconstructed recognisable training images from nothing but the shared updates.

Federated learning is a useful ingredient in a private system. On its own it is not one. Real deployments combine it with differential privacy and with secure aggregation, which lets the server see only the sum of many updates and never one device's.

Where you have already used it

  • Gboard's next-word and emoji prediction on Android.
  • Apple's on-device improvements to Siri and QuickType.
  • Hospital networks training a shared imaging model without pooling patient scans.

What is honestly hard here

Every device has different data. One person types in Marathi, another in English, a third mostly sends emojis.

When the local worlds disagree strongly, the averaged model can end up worse than any of the individual ones. This is called client drift, and it is the central research problem in the field.

Then there is the engineering: phones go offline, batteries die, and networks drop. A round has to complete anyway.

Remember this

  • The model travels, the data stays put.
  • Averaging the updates is the whole core algorithm, and it is called FedAvg.
  • Federated learning is not a privacy guarantee — combine it with one.

What to learn next

Developer — Code and libraries.

FedAvg is smaller than you expect

The algorithm from McMahan et al. (2017) is four steps:

  1. Server broadcasts the current weights.
  2. Each client runs a few epochs of ordinary training on its own data.
  3. Each client sends back its updated weights.
  4. Server averages them, weighted by how much data each client holds.

That is all. Everything else in the field is a refinement of step 2 or step 4.

Setup

bash
pip install numpy

No framework, no GPU, under a second to run. Real deployments use Flower or TensorFlow Federated; the arithmetic is what is below.

FedAvg, written out

fedavg.py
import numpy as np

rng = np.random.default_rng(1)
D = 5

def make_client(n, shift):
    X = rng.normal(shift, 1.0, (n, D))
    w_true = np.array([1.5, -2.0, 0.8, 0.0, -1.2])
    y = (X @ w_true + rng.normal(0, 0.5, n) > 0).astype(float)
    return X, y

# three hospitals whose patient populations are NOT identical
clients = [make_client(300, 0.0), make_client(300, 0.6), make_client(400, -0.5)]
Xte, yte = make_client(2000, 0.05)

def sigmoid(z): return 1.0 / (1.0 + np.exp(-z))

def local_train(w, X, y, epochs=5, lr=0.5):
    w = w.copy()
    for _ in range(epochs):
        grad = X.T @ (sigmoid(X @ w) - y) / len(y)     # plain logistic-regression gradient
        w -= lr * grad
    return w

def accuracy(w): return float(((sigmoid(Xte @ w) > 0.5).astype(float) == yte).mean())

def fedavg(parts, rounds=50, epochs=5, show=(1, 10, 50)):
    w = np.zeros(D)
    total = sum(len(y) for _, y in parts)
    for rnd in range(1, rounds + 1):
        updates = [local_train(w, X, y, epochs=epochs) for X, y in parts]
        # weighted by how much data each client holds - that weighting IS FedAvg
        w = sum(len(y) / total * u for (X, y), u in zip(parts, updates))
        if rnd in show:
            print(f"  round {rnd:>3}  test accuracy = {accuracy(w):.4f}")
    return w

Xall = np.vstack([c[0] for c in clients]); yall = np.concatenate([c[1] for c in clients])
w_central = local_train(np.zeros(D), Xall, yall, epochs=250)
print(f"CENTRALISED baseline (all data pooled)  test accuracy = {accuracy(w_central):.4f}")

print("\nFEDAVG, realistic split (each hospital has its own population)")
w_fed = fedavg(clients)

# pathological split: sort by label, so one client sees only negatives, another only positives
parts = [(Xall[p], yall[p]) for p in np.array_split(np.argsort(yall), 3)]
print("\nFEDAVG, pathological split. Positive-label share per client:",
      [round(float(y.mean()), 2) for _, y in parts])
w_skew = fedavg(parts)

print(f"\nbytes sent per round: {w_fed.nbytes * len(clients)} (weights only) "
      f"vs {Xall.nbytes} to ship the raw data once")
Output
CENTRALISED baseline (all data pooled)  test accuracy = 0.9465

FEDAVG, realistic split (each hospital has its own population)
  round   1  test accuracy = 0.9395
  round  10  test accuracy = 0.9455
  round  50  test accuracy = 0.9470

FEDAVG, pathological split. Positive-label share per client: [0.0, 0.39, 1.0]
  round   1  test accuracy = 0.9370
  round  10  test accuracy = 0.9455
  round  50  test accuracy = 0.9470

bytes sent per round: 120 (weights only) vs 40000 to ship the raw data once

Read the second experiment carefully — it is not the result people expect

FedAvg matched the pooled baseline. 0.9470 against 0.9465. Nothing was lost by keeping the data in three places.

Even the pathological split matched it. One client saw only negative labels, another only positive labels, and the averaged model still landed on 0.9470.

That result is real, and it is specific to this model. Logistic regression has a convex loss surface with a single basin, so averaging three points that are each heading toward that basin lands you inside it. There is nowhere else to go.

Deep networks are not convex. Their loss surfaces have many basins, and the average of two good solutions in different basins can be a bad solution in neither. That is client drift, and it is why the literature (Zhao et al., 2018; Karimireddy et al., 2020) reports large non-IID penalties for neural networks and mild ones for convex models.

Do not generalise a convex result to a deep model. Showing you where a demonstration stops being evidence is more useful than showing you a demonstration.

Line by line

sum(len(y) / total * u for ...) is the weighting. A client with 400 records counts more than one with 300. Averaging unweighted is a real bug that quietly biases the model toward small clients.

epochs=5 inside local_train is the knob that trades communication against drift. More local epochs means fewer rounds and less network traffic, and more room for each client to wander toward its own optimum.

w = np.zeros(D) matters more than it looks. Every client must start each round from the same weights. Averaging models that were initialised differently produces nonsense, because there is no reason weight 3 in one model plays the same role as weight 3 in another.

The byte count — 120 against 40,000 — shows the actual reason many teams adopt this. Communication is per model, not per record, so the saving grows with dataset size and shrinks with model size. For a large model the arithmetic reverses.

Common mistakes

Calling it private. Zhu et al. (2019) reconstructed training images from shared gradients. Add differential privacy on the client side, plus secure aggregation, or make no privacy claim at all.

Averaging batch-norm statistics. Running means and variances are data-dependent statistics, not learned parameters, and averaging them across clients with different distributions degrades the model. Use group normalisation, or keep normalisation statistics local.

Assuming clients are available. In cross-device settings a large fraction of clients drop mid-round. Sample far more clients than you need and accept the first k that report back.

Evaluating only the global average. Report the accuracy distribution across clients, not the mean. A model that works well for 90% of users and badly for 10% has a mean that looks fine.

Trusting client updates. A malicious client can send a poisoned update. Robust aggregation — coordinate-wise median, trimmed mean, Krum — costs accuracy and is worth it in open settings.

Try it yourself

Change epochs=5 to epochs=1 in the fedavg call and count how many rounds are needed to reach 0.9455. Multiply rounds by bytes per round to get total traffic. That product is what an engineer on a metered mobile connection actually pays.

What to learn next

Researcher — Mathematics and papers.

The objective

Federated learning minimises a weighted sum of client objectives:

$$ \min_{w} \; F(w) = \sum_{k=1}^{K} \frac{n_k}{n} F_k(w), \qquad F_k(w) = \frac{1}{n_k}\sum_{i \in \mathcal{P}_k} \ell(w; x_i, y_i) $$

  • $K$ — number of clients; $\mathcal{P}_k$ the index set held by client $k$; $n_k = |\mathcal{P}_k|$; $n = \sum_k n_k$.
  • $F_k$ — the local empirical risk; $\ell$ the per-example loss.

When client data is IID, $\mathbb{E}[F_k(w)] = F(w)$ and local steps are unbiased estimates of global descent. Under non-IID data this fails, and the bias is the source of every difficulty below.

FedAvg and its convergence

McMahan et al. (2017): each round, sample a fraction $C$ of clients, run $E$ local epochs of SGD with batch size $B$, and average the resulting weights with weights $n_k/n$.

Li et al. (2020), On the Convergence of FedAvg on Non-IID Data, give the first rigorous analysis for strongly convex, smooth $F_k$ with partial participation. The rate is $O(1/T)$, with an additive term proportional to the heterogeneity

$$ \Gamma = F^\star - \sum_k \tfrac{n_k}{n} F_k^\star $$

where $F^\star$ is the global optimum value and $F_k^\star$ the local optimum values. $\Gamma = 0$ under IID data. Crucially, the bound degrades as $E$ grows: more local work increases the drift term, which is the formal statement of the trade-off in the developer block.

Client drift and its fixes

FedProx (Li et al., 2020) adds a proximal term $\frac{\mu}{2}|w - w^t|^2$ to each local objective, restraining clients from moving too far from the broadcast weights. Cheap, and effective mainly for mild heterogeneity.

SCAFFOLD (Karimireddy et al., 2020) maintains control variates $c_k$ per client and $c$ globally, correcting the local gradient to $g_k - c_k + c$. This provably removes the drift term and achieves convergence independent of heterogeneity, at the cost of doubling communication and requiring stateful clients — which rules it out for cross-device settings where a client is seen once.

FedNova (Wang et al., 2020) normalises updates by the number of local steps, correcting the objective inconsistency introduced when clients perform different amounts of local work.

The distinction that organises this literature is cross-silo (tens of persistent, reliable organisations, stateful methods viable) versus cross-device (millions of intermittent, stateless clients, only stateless methods viable). Kairouz et al. (2021), Advances and Open Problems in Federated Learning, is the standard survey.

Permutation invariance

Naive weight averaging is questionable for deep networks because the loss surface has permutation symmetry: hidden units can be reordered without changing the function. Two clients converging to permuted versions of the same solution average to something worse than either.

FedAvg avoids the worst of this by re-broadcasting a common starting point each round, keeping clients in one basin. Probabilistic Federated Neural Matching (Yurochkin et al., 2019) matches neurons before averaging. Entezari et al. (2022) present evidence for the linear mode connectivity conjecture modulo permutation, which explains both why weight averaging works as well as it does and why it breaks when clients drift into different basins.

Privacy: what is and is not guaranteed

Federated learning provides no formal privacy guarantee. The relevant attacks:

  • Gradient inversion. Zhu et al. (2019) recover training images by optimising a synthetic input to match observed gradients. Geiping et al. (2020) extend this to trained networks and realistic batch sizes.
  • Membership and property inference from updates. Melis et al. (2019) infer properties of a client's data from its contributions.
  • Model poisoning. Bagdasaryan et al. (2020) show a small number of malicious clients can install a targeted backdoor while leaving main-task accuracy intact.

The composed defence stack:

  1. Secure aggregation (Bonawitz et al., 2017) — cryptographic protocol so the server learns only $\sum_k \Delta_k$, tolerating dropouts. Removes the per-client view; does not bound what the sum reveals.
  2. DP-FedAvg (McMahan et al., 2018) — clip each client update to norm $S$, add Gaussian noise to the aggregate. Gives user-level DP, a stronger and more meaningful unit than record-level for this setting.
  3. Robust aggregation — Krum, coordinate-wise median, trimmed mean, against poisoning. Note that robust aggregation and secure aggregation conflict, since the former needs to inspect individual updates.

Systems constraints that shape the algorithms

Bonawitz et al. (2019) document the production system: device eligibility requires idle, charging and unmetered network; round completion targets tolerate large dropout rates; and the same devices must not be repeatedly selected. These constraints, not the optimisation theory, dictate most design choices in cross-device deployments.

Papers

What to learn next