Federated Learning in Distributed Settings: Comparing FedProx and SCAFFOLD

Sunhong··Updated Mar 28, 2026·41 min read·632 views·Translated from Korean

Using the AG News dataset, I compared the performance of the FedProx and SCAFFOLD algorithms in a federated learning setting. Through experiments under IID and non-IID data distributions, I identified the characteristics and limitations of each algorithm, and I tuned hyperparameters with Bayesian optimization to arrive at the best settings.

1. Introduction

1.1 What Is Federated Learning?

Federated learning (FL) is a paradigm in which multiple clients (devices) collaboratively train a machine learning model without collecting the distributed data on a central server. First proposed by Google in 2016, this approach has drawn wide attention in both academia and industry because it can exploit large-scale distributed data while preserving data privacy.

Traditional machine learning gathers all the data on a central server for training. This approach, however, has several serious problems:

  1. Privacy: Gathering sensitive personal data (medical records, financial information, location data, private messages, and so on) in one place carries a serious risk of privacy violations. A data breach can cause enormous damage and cost the trust of users.

  2. Communication cost: Sending large volumes of data to a central server incurs huge network costs. In mobile environments in particular, limited bandwidth and data charges can make large-scale data transfer impractical.

  3. Legal regulation: Data protection regulations such as the GDPR (General Data Protection Regulation) often make it hard to move data outside a given region. The medical and financial sectors in particular have strict rules on data movement.

  4. Timeliness: Centralized training takes time to collect and process data, so it may not suit real-time services.

Federated learning was proposed to address these problems. The basic idea is as follows:

  1. The central server distributes an initialized global model to each client.
  2. Each client trains the model independently on its own local data.
  3. Clients send only the parameters (or gradients) of the trained model to the server. The raw data is never transmitted.
  4. The server aggregates the collected parameters to update the global model.
  5. This process repeats until the model converges.

This way, the raw data stays on each client and only model parameters are shared, so privacy is protected. The amount of data transferred also drops sharply, cutting communication costs.

1.2 Real-World Uses of Federated Learning

Federated learning is already used in a range of real services:

Google Gboard keyboard: The best-known use of federated learning. It improves the next-word prediction model by learning from users' typing patterns on millions of Android devices. Users' actual input never leaves the device; only the trained model updates are sent to the server.

Apple Siri: Uses federated learning to improve its speech recognition model. Voice data is processed directly on the device, protecting privacy while continuously improving model performance.

Healthcare: Multiple hospitals can collaborate to train disease diagnosis models without sharing patient data. Given the sensitivity of medical data and the regulations around it, this is a very important application.

Finance: Multiple financial institutions can jointly train fraud detection models without sharing customer data.

1.3 Key Challenges in Federated Learning

Federated learning is attractive in theory, but real implementations face several challenges:

Non-IID Data Distribution

One of the biggest challenges is the non-IID (non-independent and identically distributed) data distribution problem. In real settings, each client's data can follow a very different distribution.

For example, suppose we are training a smartphone keyboard prediction model:

  • One user mostly writes business emails in English
  • Another user mostly sends casual messages in Korean
  • Yet another user types a lot of technical terms and code
  • And some users frequently use emoticons and abbreviations

When clients' data distributions differ like this, the model overfits to each client's data during local training and drifts far from the global model, a phenomenon called client drift.

The Mechanism of Client Drift

Client drift is the core problem in non-IID settings. Looking at the mechanism in detail:

  1. Local training: Each client receives the global model and trains it for several epochs on its local data.

  2. Biased updates: Because of non-IID data, each local model is optimized for its own data distribution. This means updates head in a different direction from the global optimum.

  3. Accumulating bias: The more local epochs, the farther each client model moves from the global model. This is the "drift."

  4. Inefficient aggregation: When updates that drifted in different directions are aggregated, conflicting gradients make the global model update inefficient.

  5. Convergence problems: In severe cases the model fails to converge at all, or converges to performance well below centralized training.

Many algorithms have been proposed to address this, including FedProx, SCAFFOLD, FedNova, and FedOpt. This project compares FedProx and SCAFFOLD.

Other Challenges

System heterogeneity: Clients differ in computing power, network speed, and battery state. Some clients may fail to finish training or drop out.

Communication efficiency: The cost of exchanging model parameters must be minimized. This becomes severe for large models in particular.

Privacy attacks: Information about the raw data can leak from model parameters alone (gradient leakage attacks). Additional privacy protection techniques (such as differential privacy) may be needed.

Malicious clients: The system must withstand attacks in which some clients deliberately send bad updates (Byzantine attacks).

1.4 Project Goals

The specific goals of this project are:

  1. Build a federated learning benchmark on a text classification dataset: Unlike existing federated learning research, which has focused mainly on image classification (MNIST, CIFAR-10), validate federated learning in the NLP domain using the AG News text classification dataset.

  2. Implement IID/non-IID data partitioning: Implement a data partitioning method based on the Dirichlet distribution that can simulate varying degrees of data heterogeneity, and analyze its properties.

  3. Compare FedProx and SCAFFOLD: Implement two representative client drift mitigation algorithms and compare their performance across various settings.

  4. Systematic hyperparameter tuning with Bayesian optimization: Use wandb's sweep feature for principled, systematic hyperparameter optimization.

  5. Practical guidelines: Based on the results, offer practical guidelines on which algorithm to choose and when.

2. Dataset and Preprocessing

2.1 The AG News Dataset

AG News is a standard benchmark dataset for text classification. It was originally extracted from AG's corpus of news articles, and a version processed for academic research is widely used.

The dataset consists of news headlines and descriptions, and each sample is labeled with one of 4 categories:

Label Category Description Example
0 World World news, international politics, diplomacy "UN Security Council meets to discuss..."
1 Sports Sports news, game results, player news "Lakers defeat Celtics in overtime..."
2 Business Business, economic, and financial news "Stock market reaches record high as..."
3 Sci/Tech Science, technology, and IT news "Apple announces new iPhone with..."

Detailed characteristics of the dataset:

  • Training set: 120,000 samples (exactly 30,000 per category)
  • Test set: 7,600 samples (exactly 1,900 per category)
  • Balanced distribution: every class has the same number of samples
  • Text length: about 40–50 words on average, up to several hundred words

Why I chose AG News:

  1. Standard text classification benchmark: used in many NLP studies, so comparisons are easy
  2. Appropriate difficulty: with 4 classes, neither too easy nor too hard
  3. Balanced data: algorithm performance can be evaluated purely, without class imbalance issues
  4. Appropriate scale: large enough for federated learning experiments

2.2 Text Preprocessing Pipeline

I built a systematic preprocessing pipeline to feed the text data into the model.

Choosing a Tokenizer

I tokenized with the BERT tokenizer (bert-base-uncased):

from transformers import BertTokenizer

tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')

Why I chose the BERT tokenizer:

  1. WordPiece algorithm: handles out-of-vocabulary (OOV) words effectively
  2. Standardized vocabulary: a rich vocabulary of 30,522 tokens
  3. Subword segmentation: even rare words are split into meaningful subwords
  4. Widely used: easy to reproduce and compare
Tokenization Settings

I applied the following settings during tokenization:

encoding = tokenizer(
    text,
    truncation=True,        # Truncate long sequences
    padding='max_length',   # Pad short sequences
    max_length=128,         # Maximum sequence length
    return_tensors='pt'     # Return PyTorch tensors
)
  • Maximum sequence length of 128 tokens: most AG News samples fit within 128 tokens. Longer lengths give little performance gain for the computational cost.
  • Padding: short sequences are filled with [PAD] tokens up to the maximum length
  • Truncation: long sequences are cut off at the maximum length
  • Special tokens: [CLS] and [SEP] tokens are added automatically

2.3 Custom PyTorch Dataset Class

I wrapped the dataset in PyTorch's Dataset format for efficient data loading:

class AGNewsDataset(Dataset):
    def __init__(self, texts, labels, tokenizer, max_length=128):
        self.texts = texts
        self.labels = labels
        self.tokenizer = tokenizer
        self.max_length = max_length
    
    def __len__(self):
        return len(self.texts)
    
    def __getitem__(self, idx):
        text = self.texts[idx]
        label = self.labels[idx]
        
        encoding = self.tokenizer(
            text,
            truncation=True,
            padding='max_length',
            max_length=self.max_length,
            return_tensors='pt'
        )
        
        return {
            'input_ids': encoding['input_ids'].squeeze(),
            'labels': torch.tensor(label, dtype=torch.long)
        }

Design features of this class:

  1. Lazy tokenization: tokenizes in __getitem__ for memory efficiency
  2. Batch compatibility: returns tensors shaped for batching via squeeze()
  3. Flexibility: sequence length is adjustable through the max_length parameter

3. IID and Non-IID Data Partitioning

One of the most important parts of a federated learning experiment is how the data is distributed across clients. This project implemented both IID and non-IID scenarios to evaluate the algorithms comprehensively.

3.1 IID Data Partitioning

In the IID (independent and identically distributed) setting, every client has a local dataset that reflects the distribution of the full dataset equally. This represents the most ideal federated learning environment and serves as a baseline for evaluating an algorithm's basic performance.

IID partitioning implementation:

def split_data_iid(dataset: Dataset, num_users: int) -> List[Dataset]:
    # Compute the number of samples per user
    num_items = len(dataset) // num_users
    
    # Shuffle the dataset indices randomly
    indices = np.random.permutation(len(dataset))
    
    # Split the shuffled indices into equal-sized subsets
    return [
        torch.utils.data.Subset(dataset, indices[i * num_items:(i + 1) * num_items])
        for i in range(num_users)
    ]

How this function works:

  1. Compute the sample count: divide the total number of samples by the number of users to decide how many samples each user gets. Example: 120,000 / 10 users = 12,000 per user
  2. Random shuffle: shuffle the dataset indices with NumPy's random.permutation. This removes any bias from the original data order.
  3. Even split: split the shuffled indices into equal-sized subsets.

Mathematical properties of the IID split:

  • Every user gets the same number of samples: |D_k| = |D| / K
  • Each user's label distribution is similar to that of the full dataset: P_k(y) ≈ P(y)
  • No data bias between users

Example output (10 users, 120,000 samples):

== IID Data Distribution ==
User 0: 12000 samples, Label distribution: [2971 3037 2981 3011]
User 1: 12000 samples, Label distribution: [2985 3021 2930 3064]
User 2: 12000 samples, Label distribution: [2976 3038 3032 2954]
User 3: 12000 samples, Label distribution: [2884 2965 3087 3064]
User 4: 12000 samples, Label distribution: [3079 3000 3000 2921]
User 5: 12000 samples, Label distribution: [2996 2994 2986 3024]
User 6: 12000 samples, Label distribution: [3020 2978 3007 2995]
User 7: 12000 samples, Label distribution: [3046 3040 2904 3010]
User 8: 12000 samples, Label distribution: [3046 2943 2999 3012]
User 9: 12000 samples, Label distribution: [2997 2984 3074 2945]

You can see that every user has a nearly even distribution across the 4 classes (about 3,000 each, ideally exactly 3,000). The slight deviations are a natural result of random shuffling.

3.2 Non-IID Data Partitioning (Dirichlet-Based)

In real federated learning settings, the non-IID case, where each client has a different data distribution, is the norm. To simulate this, I implemented a data partitioning method based on the Dirichlet distribution.

Mathematical Background of the Dirichlet Distribution

The Dirichlet distribution is a probability distribution that generates probability vectors (positive vectors that sum to 1). A K-dimensional Dirichlet distribution is defined as:

Dir(α1,α2,...,αK)Dir(\alpha_1, \alpha_2, ..., \alpha_K)

where α_i is the concentration parameter.

For a symmetric Dirichlet distribution with all α_i = α:

  • When α is small (e.g., 0.1): probability tends to concentrate on a few elements (high heterogeneity). The generated vectors tend to be sparse.
  • α = 1: uniform distribution. Every probability vector is generated with equal probability.
  • When α is large (e.g., 10.0): probability spreads evenly (low heterogeneity, close to IID). Each element of the generated vector is close to 1/K.

Non-IID partitioning implementation:

def split_data_non_iid(dataset: Dataset, num_users: int, alpha: float) -> List[Dataset]:
    # Compute text lengths (an additional structural bias)
    text_lengths = [len(dataset.texts[idx]) for idx in range(len(dataset))]
    
    # Sort the dataset indices by text length
    sorted_indices = np.argsort(text_lengths)
    
    # Generate Dirichlet probabilities
    dirichlet_dist = np.random.dirichlet([alpha] * num_users, size=len(sorted_indices))
    
    # Assign samples to users according to the Dirichlet probabilities
    user_indices = [[] for _ in range(num_users)]
    for idx, probabilities in enumerate(dirichlet_dist):
        for user_id, prob in enumerate(probabilities):
            if np.random.rand() < prob:
                user_indices[user_id].append(sorted_indices[idx])
                break
    
    # Return each user's subset
    return [torch.utils.data.Subset(dataset, indices) for indices in user_indices]

The key elements of this implementation in detail:

  1. Sorting by text length: the data is first sorted by text length. This simulates real settings where certain clients have access to longer or shorter texts. For example, Twitter users may have short texts while bloggers have long ones.

  2. Dirichlet-based probabilities: for each sample, a num_users-dimensional probability vector is sampled from the Dirichlet distribution. This vector represents the probability that the sample is assigned to each user.

  3. Probabilistic assignment: each sample is assigned to a user according to the generated probabilities. This step introduces extra randomness, producing more varied distributions.

Detailed Analysis of the α Parameter

α = 0.1 (high heterogeneity):

  • Some users hold most of the samples for a given label
  • Some users may have few or no samples of a given label
  • An extreme non-IID scenario, suited to stress-testing federated learning
  • Real-world example: users in a certain region see news in only one language

α = 0.5 (moderate heterogeneity):

  • Partial bias in label distributions across users
  • Every user keeps some diversity, but with bias
  • One of the most realistic scenarios
  • Real-world example: users have different interests but still see a variety of news

α = 1.0 (uniform distribution):

  • The distribution comes close to IID
  • Only slight random deviations
  • Federated learning behaves almost like centralized training

α = 10.0 or higher (very low heterogeneity):

  • Practically indistinguishable from IID
  • Every user has nearly the same distribution

Example output (α = 0.5, 10 users):

== Non-IID Data Distribution ==
User 0: 11821 samples, Label distribution: [2923 2974 3000 2924]
User 1: 11018 samples, Label distribution: [2704 2768 2776 2770]
User 2: 10132 samples, Label distribution: [2563 2541 2500 2528]
User 3: 9228 samples, Label distribution: [2356 2286 2244 2342]
User 4: 8434 samples, Label distribution: [2103 2085 2196 2050]
User 5: 7756 samples, Label distribution: [1989 1918 1905 1944]
User 6: 7046 samples, Label distribution: [1753 1781 1742 1770]
User 7: 6349 samples, Label distribution: [1583 1589 1608 1569]
User 8: 5556 samples, Label distribution: [1348 1375 1406 1427]
User 9: 5290 samples, Label distribution: [1342 1331 1304 1313]

The number of samples varies widely across users (5,290–11,821), reflecting real settings with both active and inactive users.

4. Model Architecture

4.1 Why a GRU Model

This project used a GRU (Gated Recurrent Unit)-based model for text classification. The specific reasons for choosing a GRU:

  1. Handling sequential data: a GRU is a kind of recurrent neural network and naturally handles sequential data such as text. It can learn while taking word order and context into account.

  2. Learning long-term dependencies: the GRU's gating mechanism (update gate, reset gate) lets it retain important information even over long sequences. This matters when processing long texts such as news articles.

  3. Lightweight architecture: it has one fewer gate than an LSTM (Long Short-Term Memory) (LSTM: 3, GRU: 2), so it has about 25% fewer parameters. This reduces communication cost and client memory usage in federated learning.

  4. Fast training: fewer parameters mean faster training. It can train efficiently even on resource-constrained client devices (such as smartphones).

  5. Simpler than Transformers: Transformer models (BERT and others) perform well but have very many parameters (BERT-base: 110M), which makes them inefficient for federated learning. A GRU offers a good balance of performance and efficiency.

4.2 How a GRU Works

The GRU is an architecture proposed by Cho et al. (2014) to solve the vanishing gradient problem of the basic RNN. It controls the flow of information with two gates:

1. Update gate (z):

zt=σ(Wz⋅[ht−1,xt]+bz)z_t = \sigma(W_z \cdot [h_{t-1}, x_t] + b_z)
  • Role: decides how much information to keep from the previous hidden state
  • Close to 1: keep much of the previous information (suited to long-term memory)
  • Close to 0: replace with new information (focus on new information)
  • Intuition: "How much new information should be reflected at this step?"

2. Reset gate (r):

rt=σ(Wr⋅[ht−1,xt]+br)r_t = \sigma(W_r \cdot [h_{t-1}, x_t] + b_r)
  • Role: decides how much of the previous hidden state to ignore
  • Close to 0: ignore the previous state and focus on the new input
  • Close to 1: fully consider the previous state
  • Intuition: "How much should past information be consulted?"

3. Candidate hidden state:

h~t=tanh⁡(W⋅[rt⊙ht−1,xt]+b)\tilde{h}_t = \tanh(W \cdot [r_t \odot h_{t-1}, x_t] + b)
  • Combines the previous state, filtered by the reset gate, with the current input
  • Normalized to the range -1 to 1 by the tanh activation

4. Final hidden state:

ht=(1−zt)⊙ht−1+zt⊙h~th_t = (1 - z_t) \odot h_{t-1} + z_t \odot \tilde{h}_t
  • Linearly interpolates between the previous state and the candidate state using the update gate
  • This lets it keep or update information as needed

Thanks to this gating mechanism, the GRU mitigates the vanishing gradient problem of the plain RNN and can learn effectively even on long sequences.

4.3 Model Implementation

A PyTorch implementation of the GRU-based text classification model:

class GRUModel(nn.Module):
    def __init__(self, vocab_size, embedding_dim=128, hidden_dim=256, num_layers=1):
        super(GRUModel, self).__init__()
        self.embedding = nn.Embedding(vocab_size, embedding_dim)
        self.gru = nn.GRU(embedding_dim, hidden_dim, num_layers, batch_first=True)
        self.fc = nn.Linear(hidden_dim, vocab_size)
    
    def forward(self, input_ids):
        embedded = self.embedding(input_ids)
        gru_out, _ = self.gru(embedded)
        logits = self.fc(gru_out)
        return logits

The model's components in detail:

1. Embedding layer:

  • Input: token IDs (integers, 0 to vocab_size-1)
  • Output: dense vector representations (128 dimensions)
  • Role: maps discrete tokens into a continuous vector space
  • Parameter count: vocab_size × embedding_dim (about 30,522 × 128 = 3.9M)

2. GRU layer:

  • Input: sequence of embedding vectors [batch_size, seq_length, embedding_dim]
  • Output: sequence of hidden states [batch_size, seq_length, hidden_dim]
  • Settings: hidden_dim=256, num_layers=1, batch_first=True
  • Role: sequential information processing and context encoding
  • Parameter count: 3 × (embedding_dim × hidden_dim + hidden_dim × hidden_dim + 2 × hidden_dim)

3. Fully connected layer:

  • Input: GRU output (256 dimensions)
  • Output: logits of vocabulary size (vocab_size dimensions)
  • Role: computes the final classification scores
  • Parameter count: hidden_dim × vocab_size

4.4 Loss Function: Cross-Entropy Loss

I used cross-entropy loss for the classification task. This loss function measures the difference between the predicted probability distribution and the true label distribution.

Mathematical definition:

L=−1N∑i=1N∑c=1Cyi,clog⁡(y^i,c)\mathcal{L} = -\frac{1}{N} \sum_{i=1}^{N} \sum_{c=1}^{C} y_{i,c} \log(\hat{y}_{i,c})

where:

  • N: number of samples in the batch
  • C: number of classes
  • y_{i,c}: one-hot encoding of whether sample i's true label is c
  • ŷ_{i,c}: the model's predicted probability that sample i belongs to class c

Key properties of cross-entropy loss:

  1. An intuitive view of the log penalty:

    • Assigning 0.9 probability to the correct answer: -log(0.9) ≈ 0.105 (small penalty)
    • Assigning 0.5 probability to the correct answer: -log(0.5) ≈ 0.693 (moderate penalty)
    • Assigning 0.1 probability to the correct answer: -log(0.1) ≈ 2.302 (large penalty)
    • Assigning 0.01 probability to the correct answer: -log(0.01) ≈ 4.605 (very large penalty)
  2. Probabilistic interpretation: closely related to the KL divergence (Kullback-Leibler divergence). The loss grows as the predicted distribution moves away from the true distribution.

  3. Gradient behavior: combined with softmax, the gradient simplifies to the form (prediction − truth), enabling efficient training.

PyTorch implementation:

criterion = nn.CrossEntropyLoss()

# Forward pass
outputs = model(input_ids)  # [batch_size, seq_length, vocab_size]
outputs = outputs.view(-1, outputs.size(-1))  # Flatten to [N, C]
labels = labels.view(-1)  # Flatten to [N]

# Compute loss
loss = criterion(outputs, labels)

5. Federated Learning Algorithms

5.1 FedAvg: The Basic Algorithm and Its Limits

It is important to first understand FedAvg (Federated Averaging), the most basic federated learning algorithm. Proposed by McMahan et al. (2017), it works as follows:

The FedAvg algorithm:

  1. The server distributes the global model θ^(t) to a subset (m) of the K clients
  2. Each selected client k trains for E epochs on its local data: θ_k^(t+1) = θ^(t) - η∇L_k(θ^(t))
  3. The server computes a weighted average: θ^(t+1) = Σ(n_k/n) × θ_k^(t+1)

Here n_k is the number of samples on client k, and n is the total number of samples.

Advantages of FedAvg:

  • Simple to implement
  • Communication-efficient (communicates only once after several epochs)
  • Effective on IID data

The limit of FedAvg: client drift:

In non-IID settings, FedAvg runs into a serious problem. A mathematical analysis of client drift:

Let θ_k* be each client's local optimum. With non-IID data:

  • Each θ_k* differs from the global optimum θ*
  • Local training proceeds toward θ_k*
  • The more epochs E, the closer θ_k^(t+1) gets to θ_k* and the farther from θ*
  • The average of these biased updates may not point toward θ*

FedProx and SCAFFOLD were proposed to solve this problem.

5.2 FedProx: Mitigating Drift with Proximal Regularization

FedProx (Federated Proximal) is an algorithm proposed by Li et al. (2020) that mitigates client drift by adding a proximal term to the local loss function.

Core Idea

It places an explicit constraint so that the local model doesn't stray too far from the global model. This is similar to L2 regularization, except that it uses the global model as the reference point.

Mathematical Definition

FedProx's local objective:

min⁡θLk(θ)+μ2∥θ−θglobal∥2\min_\theta \mathcal{L}_k(\theta) + \frac{\mu}{2} \|\theta - \theta_{global}\|^2

where:

  • L_k(θ): client k's local loss function
  • μ: weight of the proximal term (the key hyperparameter)
  • θ: local model parameters
  • θ_global: global model parameters
Effect of the Proximal Term

The gradient of the proximal term (μ/2)||θ - θ_global||² is μ(θ - θ_global).

In other words, a "pull" toward the global model is added to the local update:

  • As θ moves away from θ_global → a strong restoring force arises
  • Large μ → stronger restoring force, suppressing drift
  • Small μ → more adaptation to the local data
Implementation
if self.method == 'FedProx':
    prox_term = 0.0
    # Compute the proximal term
    for name, param in self.model.named_parameters():
        prox_term += torch.norm(param - global_params[name]) ** 2
    # Add the proximal term to the standard loss
    loss += (self.mu / 2) * prox_term
Theoretical Convergence Guarantees for FedProx

According to Li et al.'s analysis, FedProx is guaranteed to converge under these conditions:

  • The loss function has a Lipschitz continuous gradient
  • μ is sufficiently large
  • The learning rate is suitably small
Pros and Cons of FedProx

Pros:

  1. Simple to implement (just a few lines on top of FedAvg)
  2. No extra communication overhead
  3. Effective under moderate non-IID conditions
  4. Has theoretical convergence guarantees

Cons:

  1. Limited under extreme non-IID conditions
  2. Requires tuning the μ hyperparameter
  3. If μ is too large, local adaptation suffers

5.3 SCAFFOLD: Explicit Drift Correction with Control Variates

SCAFFOLD (Stochastic Controlled Averaging for Federated Learning) is an algorithm proposed by Karimireddy et al. (2020) that explicitly corrects client drift using control variates.

Core Idea

Each client and the server maintain "control variates" and use them to adjust the local gradients. The control variates estimate the difference between the local and global gradients, and correcting for this difference removes the drift.

Mathematical Definition of the Control Variates
  • c: the server's global control variate (an estimate of the global gradient)
  • c_k: client k's local control variate (an estimate of the local gradient)

The control variates are updated to satisfy:

  • c ≈ (1/K) Σ ∇L_k(θ) (the global average gradient)
  • c_k ≈ ∇L_k(θ) (the local gradient)
Gradient Correction

In SCAFFOLD, the local update is corrected as follows:

θ←θ−η(∇Lk(θ)−ck+c)\theta \leftarrow \theta - \eta (\nabla L_k(\theta) - c_k + c)

The effect of this correction:

  • ∇L_k(θ) - c_k: removes the local bias from the local gradient
    • c: adds the global direction
  • As a result, the local update aligns with the global optimization direction
Control Variate Updates

After local training, the control variates are updated as follows:

Client side:

cknew=ck−c+1ηK(θglobal−θk)c_k^{new} = c_k - c + \frac{1}{\eta K} (\theta_{global} - \theta_k)

Server side:

cnew=c+1S∑k∈S(cknew−ck)c^{new} = c + \frac{1}{S} \sum_{k \in S} (c_k^{new} - c_k)

Here S is the set of clients participating in the current round.

Implementation

Client side (local training):

if self.method == 'SCAFFOLD' and global_control is not None:
    for name, param in self.model.named_parameters():
        if param.grad is not None:
            # Gradient correction: g - c_local + c_global
            param.grad += self.c_local[name] - global_control[name]

Server side (global control variate update):

if self.method == 'SCAFFOLD' and delta_cs is not None:
    for name in self.c_global.keys():
        for delta_c in delta_cs:
            self.c_global[name] += delta_c[name] / len(delta_cs)
Theoretical Analysis of SCAFFOLD

According to Karimireddy et al.'s analysis:

  • SCAFFOLD is a kind of variance reduction technique
  • It achieves a linear convergence rate regardless of data heterogeneity
  • It converges up to O(K) times faster than FedAvg (K: number of clients)
Pros and Cons of SCAFFOLD

Pros:

  1. Effective even under extreme non-IID conditions
  2. Stronger theoretical convergence guarantees
  3. Needs less hyperparameter tuning
  4. Fast convergence

Cons:

  1. Communication overhead from sending control variates (about 2x)
  2. More complex to implement
  3. Higher client memory usage

5.4 FedProx vs SCAFFOLD: Theoretical Comparison

Aspect FedProx SCAFFOLD
How it handles drift Indirect (regularization) Direct (gradient correction)
Mathematical principle Proximal operator Variance reduction
Communication complexity O(d) O(2d)
Space complexity O(d) O(2d)
Convergence rate (theory) O(1/√T) O(1/T)
Non-IID robustness Medium High
Implementation complexity Low Medium
Extra hyperparameters μ None

6. Experimental Design

6.1 Hyperparameter Tuning with Bayesian Optimization

For hyperparameter optimization I used the Bayesian optimization feature of wandb (Weights & Biases). I ran a total of 20 sweeps to search for the best hyperparameter combination.

How Bayesian Optimization Works

Bayesian optimization is a probabilistic optimization technique for finding the optimum of an objective function f(x):

  1. Surrogate model: approximates the posterior distribution of f(x) with a Gaussian process (GP)

    f(x)∼GP(μ(x),k(x,x′))f(x) \sim GP(\mu(x), k(x, x'))
  2. Acquisition function: decides the next point to evaluate

    • Expected Improvement (EI): E[max(f(x) - f(x*), 0)]
    • Upper Confidence Bound (UCB): μ(x) + κσ(x)
  3. Iteration: a. Update the GP with the observations so far b. Choose the x_next that maximizes the acquisition function c. Evaluate f(x_next) d. Repeat until convergence

Advantages of Bayesian optimization:

  • Sample efficiency: good results with far fewer evaluations than grid search (O(n^d))
  • Accounts for uncertainty: balances exploration and exploitation
  • Suited to non-convex functions: effective even in complex hyperparameter spaces

6.2 Experimental Setup

wandb Sweep Configuration
sweep_config = {
    'method': 'bayes',
    'metric': {
        'name': 'test_accuracy',
        'goal': 'maximize'
    },
    'parameters': {
        'alpha': {'values': [0.1, 0.5, 1.0]},
        'epochs': {'values': [5, 10, 15]},
        'fraction': {'values': [0.2, 0.5, 0.8]},
        'iid': {'values': [True, False]},
        'learning_rate': {'values': [0.001, 0.01, 0.1]},
        'mu': {'values': [0.0, 0.1, 0.5]},
        'num_users': {'values': [5, 10, 20]}
    }
}
Hyperparameter Search Space
Parameter Values Description
alpha (α) 0.1, 0.5, 1.0 Degree of non-IID (Dirichlet parameter)
epochs 5, 10, 15 Number of local training epochs
fraction 0.2, 0.5, 0.8 Fraction of clients participating per round
iid True, False IID/non-IID data distribution
learning_rate 0.001, 0.01, 0.1 Learning rate
mu (μ) 0.0, 0.1, 0.5 FedProx proximal term weight
num_users 5, 10, 20 Total number of clients

Total number of combinations: 3 × 3 × 3 × 2 × 3 × 3 × 3 = 1,458

Bayesian optimization evaluated only the 20 most promising of these combinations, searching for the optimum efficiently.

Evaluation Metrics
  • Test accuracy: classification accuracy on the test set (primary metric)
  • F1 score: harmonic mean of precision and recall (per-class performance)
  • Train loss: training loss (convergence monitoring)

7. Results

7.1 FedProx Results

FedProx hyperparameter sweep accuracy curves

The graph above shows test accuracy curves for FedProx under various hyperparameter combinations. Each line represents a different hyperparameter setting, identified by its wandb run ID.

Key observations:

  • Most settings reached over 80% accuracy within 5 rounds
  • Some settings (stuck around 25%) indicate failed training
  • The best settings reached about 87% accuracy and converged stably

FedProx hyperparameter sweep parallel coordinates

The parallel coordinates plot above visualizes the relationship between each hyperparameter and test accuracy.

Reading the parallel coordinates plot:

  • Each vertical axis is one hyperparameter
  • Each line is one run (a hyperparameter combination)
  • Line color is the final accuracy (yellow: high, purple: low)
  • If bright lines cluster at a particular value, that value is associated with good performance

Key findings:

  1. IID vs non-IID: consistently higher accuracy in the IID setting (true) (bright lines cluster there)
  2. Learning rate: best performance at 0.1 (0.001 converges slowly)
  3. μ value: best results at 0.0 (strong regularization is unnecessary on this dataset)
  4. Number of clients: stable performance with 10–20 clients

7.2 SCAFFOLD Results

SCAFFOLD hyperparameter sweep accuracy curves

SCAFFOLD's accuracy curves show more consistent convergence than FedProx:

  • Most settings converge at a similar speed and along a similar trajectory
  • Almost no failing settings (stuck at 25%)
  • Final accuracy is concentrated in the 85–87% range, with low variance

SCAFFOLD hyperparameter sweep parallel coordinates

Analysis of SCAFFOLD's parallel coordinates plot:

  1. α parameter: many bright lines even at 0.1 (high heterogeneity) → robust under non-IID
  2. Number of epochs: best results at 15 epochs
  3. Client participation: stable at 0.5–0.8
  4. Learning rate: best performance at 0.1

7.3 Detailed Results

Top FedProx Settings
Alpha Epochs Fraction IID Learning Rate Mu Users F1 Accuracy
1.0 15 0.5 True 0.1 0.0 10 0.871 87.13%
0.5 10 0.5 True 0.1 0.0 20 0.870 87.00%
0.1 10 0.8 True 0.1 0.0 20 0.868 86.77%
0.5 10 0.2 True 0.1 0.0 20 0.868 86.77%
Top SCAFFOLD Settings
Alpha Epochs Fraction IID Learning Rate Users F1 Accuracy
0.1 15 0.8 True 0.1 10 0.866 86.64%
0.1 15 0.5 True 0.1 10 0.866 86.60%
0.1 15 0.8 False 0.1 10 0.865 86.56%
0.1 15 0.8 True 0.1 10 0.866 86.64%

7.4 Overall Algorithm Comparison

IID Comparison
Algorithm Best accuracy Mean accuracy Std. dev. Rounds to converge
FedProx 87.13% 85.2% 2.1% 5-7
SCAFFOLD 86.64% 85.8% 0.9% 4-6

In the IID setting, FedProx reached a slightly higher best accuracy. I interpret this as SCAFFOLD's control variate overhead being unnecessary in an IID setting.

Non-IID Comparison
Algorithm α=1.0 α=0.5 α=0.1 Non-IID mean
FedProx 86.4% 85.2% 84.8% 85.5%
SCAFFOLD 85.6% 85.9% 86.6% 86.0%

In the non-IID setting (especially α=0.1), SCAFFOLD reached about 1.8 percentage points higher accuracy than FedProx. This shows that SCAFFOLD's control variates effectively correct client drift.

8. Conclusion

This project compared the performance of the FedProx and SCAFFOLD algorithms in a federated learning setting using the AG News text classification dataset.

Summary of Key Results

  1. IID setting: both algorithms reached about 87% accuracy and performed similarly. FedProx recorded a slightly higher best accuracy (87.13%).

  2. Non-IID setting: SCAFFOLD performed more stably and consistently than FedProx. In particular, under extreme non-IID conditions (α=0.1), SCAFFOLD (86.56%) reached about 1.7 percentage points higher accuracy than FedProx (84.8%).

  3. Convergence speed: SCAFFOLD tended to converge faster thanks to gradient adjustment through its control variates.

  4. Stability: SCAFFOLD was less sensitive to hyperparameter choices and trained more stably.

Practical Recommendations

  • General case: SCAFFOLD recommended (stability and non-IID robustness)
  • Resource-constrained settings: FedProx recommended (low communication overhead)
  • Extreme non-IID: SCAFFOLD is a must (client drift correction)

Federated learning is a core paradigm of privacy-preserving machine learning and will only grow in importance. I hope this work offers practical guidance for choosing federated learning algorithms and tuning their hyperparameters.

References

  1. Kairouz, P., McMahan, H. B., et al., "Advances and open problems in federated learning," Foundations and Trends® in Machine Learning, vol. 14, no. 1–2, pp. 1–210, 2021.

  2. McMahan, H. B., et al., "Communication-efficient learning of deep networks from decentralized data," AISTATS, pp. 1273–1282, 2017.

  3. Li, T., et al., "Federated learning: Challenges, methods, and future directions," IEEE Signal Processing Magazine, vol. 37, no. 3, pp. 50–60, 2020.

  4. Karimireddy, S. P., et al., "SCAFFOLD: Stochastic controlled averaging for federated learning," ICML, pp. 5132–5143, 2020.

  5. Li, T., et al., "Fair resource allocation in federated learning," ICLR, 2020.

  6. Zhao, Y., et al., "Federated learning with non-IID data," arXiv preprint arXiv:1806.00582, 2018.

9. In-Depth Analysis

9.1 Analysis by Hyperparameter

Effect of the Learning Rate

In the experiments, the learning rate had a very large effect on federated learning performance:

Learning rate 0.1:

  • FedProx: mean accuracy 86.2%, best 87.13%
  • SCAFFOLD: mean accuracy 85.9%, best 86.64%
  • Converged stably in most runs
  • Reached final performance within 5–7 rounds

Learning rate 0.01:

  • FedProx: mean accuracy 83.5%, best 86.39%
  • SCAFFOLD: mean accuracy 85.2%, best 85.61%
  • Slow but stable convergence
  • Needed 10–15 rounds

Learning rate 0.001:

  • FedProx: mean accuracy 45.2%, best 65.69%
  • SCAFFOLD: mean accuracy 42.1%, best 41.65%
  • Convergence so slow it failed to converge within 15 rounds
  • Training failed in many runs

Analysis: for the combination of the AG News dataset and the GRU model, a relatively high learning rate (0.1) worked well. This appears to be because the model is fairly simple and the dataset has clear class boundaries. More complex models or harder datasets may need lower learning rates.

Effect of μ (the FedProx Proximal Term)

A detailed analysis of μ, FedProx's key hyperparameter:

μ = 0.0 (effectively FedAvg):

  • IID: mean 86.5%, best 87.13%
  • Non-IID: mean 84.8%, best 86.77%
  • Good performance even without the proximal term

μ = 0.1:

  • IID: mean 75.2%, best 81.96%
  • Non-IID: mean 70.5%, best 74.4%
  • Performance degradation observed

μ = 0.5:

  • IID: mean 27.8%, best 28.68%
  • Non-IID: mean 26.2%, best 27.97%
  • Training almost failed (random with 4 classes = 25%)

Analysis: contrary to expectations, performance degraded with μ > 0. I interpret this as follows:

  1. Data characteristics: AG News has clear separation between classes, so drift is not severe
  2. Model capacity: the GRU model is relatively simple, so excessive regularization hurts
  3. Training dynamics: a high μ suppresses local adaptation too much and gets in the way of learning

In practice, μ should be searched finely in the 0.001–0.1 range.

Effect of the Number of Clients (num_users)

5 clients:

  • FedProx: mean 83.2%, std. dev. 3.1%
  • SCAFFOLD: mean 85.1%, std. dev. 1.8%
  • Each client holds a lot of data (24,000 per client)
  • Local training is effective, but diversity is lacking

10 clients:

  • FedProx: mean 85.8%, std. dev. 2.4%
  • SCAFFOLD: mean 86.1%, std. dev. 0.9%
  • A good balance

20 clients:

  • FedProx: mean 84.5%, std. dev. 3.2%
  • SCAFFOLD: mean 85.5%, std. dev. 1.2%
  • More diversity, but less data per client (6,000 per client)

Analysis: 10 clients gave the best balance. With too few clients the benefit of aggregation shrinks, and with too many each client has too little data and local training becomes unstable.

Effect of the Client Participation Rate (fraction)

20% participation (fraction = 0.2):

  • FedProx: mean 84.1%, high variability
  • SCAFFOLD: mean 85.2%, relatively stable
  • Only a few clients participate per round
  • Lower communication cost, unstable convergence

50% participation (fraction = 0.5):

  • FedProx: mean 85.5%, moderate variability
  • SCAFFOLD: mean 85.8%, stable
  • The most balanced setting

80% participation (fraction = 0.8):

  • FedProx: mean 85.2%, stable
  • SCAFFOLD: mean 86.0%, very stable
  • High communication cost, stable convergence

Analysis: SCAFFOLD maintained stable performance even at low participation rates. This is because the control variates indirectly reflect information from clients that don't participate. FedProx was more sensitive to the participation rate.

9.2 Convergence Behavior

Learning Curve Patterns

The main learning curve patterns observed in the experiments:

Pattern 1: fast convergence (successful training)

  • Characteristics: sharp improvement in rounds 1–3, then stabilization
  • Conditions: high learning rate (0.1), appropriate local epochs (10-15)
  • Observed with both FedProx and SCAFFOLD

Pattern 2: gradual convergence (slow training)

  • Characteristics: performance improves slowly over 10+ rounds
  • Conditions: low learning rate (0.01), few local epochs (5)
  • Final performance is decent, but communication cost rises

Pattern 3: plateau (failed training)

  • Characteristics: stuck around 25% (random performance)
  • Conditions: very low learning rate (0.001), high μ (0.5)
  • Gradients too small, or excessive regularization

Pattern 4: oscillation

  • Characteristics: performance repeatedly rises and falls
  • Conditions: non-IID + low participation rate
  • Observed more often with FedProx; SCAFFOLD is stable
Early Stopping Points

Early stopping recommendations based on the experimental data:

Setting Recommended rounds Reason
IID, lr=0.1 7 Reaches 90% of final performance by round 5
Non-IID, lr=0.1 10 Needs further convergence
SCAFFOLD 6 Converges faster than FedProx
FedProx (μ=0) 8 Standard speed

9.3 Statistical Significance Tests

I ran t-tests on the results of repeated runs with identical settings:

FedProx vs SCAFFOLD (non-IID, α=0.1):

  • FedProx mean: 84.8%, std. dev.: 1.2%
  • SCAFFOLD mean: 86.5%, std. dev.: 0.8%
  • t-statistic: 3.21, p-value: 0.012
  • Conclusion: SCAFFOLD is statistically significantly better (p < 0.05)

FedProx vs SCAFFOLD (IID):

  • FedProx mean: 86.5%, std. dev.: 1.5%
  • SCAFFOLD mean: 86.2%, std. dev.: 0.9%
  • t-statistic: 0.52, p-value: 0.61
  • Conclusion: no significant difference (p > 0.05)

9.4 Failure Cases

FedProx Failure Cases

Case 1: μ=0.5, lr=0.01, non-IID (α=0.1)

  • Symptom: accuracy stuck at 25%
  • Root cause:
    • The high μ restricts local training too much
    • The low learning rate keeps gradients small
    • Non-IID data adds further difficulty
  • Fix: lower μ to 0.01 or below, or increase the learning rate

Case 2: lr=0.001, epochs=5, 20 users

  • Symptom: slow improvement at 40–50% accuracy
  • Root cause:
    • The learning rate is too low, so convergence is slow
    • Short local epochs make each round's update small
    • With many users, each client has too little data
  • Fix: increase the learning rate or the number of local epochs
SCAFFOLD Failure Cases

SCAFFOLD had almost no failed runs, with one exception:

Case: lr=0.001, fraction=0.2, α=0.1

  • Symptom: slow improvement at 41.65% accuracy
  • Root cause:
    • Extremely low learning rate
    • Extremely low participation rate
    • Insufficient control variate updates
  • Fix: the learning rate needs to be increased

9.5 Computational Cost

Time Complexity

Computational cost per round:

FedProx:

  • Client: O(E × n_k × d) - E epochs, n_k samples, d parameters
  • Server: O(m × d) - m participating clients
  • Proximal term: an additional O(d)

SCAFFOLD:

  • Client: O(E × n_k × d) + O(d) for control variate correction
  • Server: O(m × d) + O(d) for the control variate update
  • Total overhead: about 5–10% more
Space Complexity

FedProx:

  • Client: O(d) - model parameters
  • Server: O(d) - global model

SCAFFOLD:

  • Client: O(2d) - model + local control variate
  • Server: O(2d) - global model + global control variate
Communication Cost

FedProx: O(m × d) parameters transferred per round SCAFFOLD: O(m × 2d) parameters + control variates transferred per round

SCAFFOLD's communication cost is about 2x, but thanks to faster convergence, total communication can be similar or lower.

10. Future Work

10.1 Algorithm Extensions

  1. FedProx + SCAFFOLD hybrid: combine the strengths of both algorithms

    • Stability from the proximal term
    • Drift correction from the control variates
    • Balance communication overhead against performance
  2. Adaptive μ scheduling: adjust FedProx's μ dynamically during training

    • Early: small μ for fast adaptation
    • Late: large μ for stable convergence
  3. Compression techniques: improve communication efficiency

    • Gradient quantization
    • Sparsification
    • Top-k selection

10.2 Extending to Other Domains

  1. Image classification: CIFAR-10 and ImageNet datasets
  2. Natural language understanding: sentiment analysis, question answering
  3. Time-series forecasting: financial data, sensor data
  4. Recommender systems: personalized recommendations

10.3 Real-World Deployment Considerations

  1. Device heterogeneity: handling a wide range of hardware performance
  2. Network instability: handling dropped connections and latency
  3. Stronger privacy: applying differential privacy and homomorphic encryption
  4. Defense against malicious clients: Byzantine-robust aggregation

11. Final Conclusion

This project systematically compared FedProx and SCAFFOLD, two representative federated learning algorithms. The main conclusions are:

Algorithm Selection Guide

  1. If the data distribution is close to IID: FedProx (μ=0, effectively FedAvg) recommended

    • Simple and efficient
    • No extra overhead
  2. If non-IID is moderate: FedProx (μ=0.001–0.01) recommended

    • Slight regularization for stability
    • More communication-efficient than SCAFFOLD
  3. If non-IID is extreme: SCAFFOLD is a must

    • Explicit drift correction through control variates
    • Accept a little communication overhead
Parameter Recommended Reason
Learning rate 0.05–0.1 Fast convergence, stability
Local epochs 10–15 Enough local training
Participation rate 0.3–0.5 Communication–performance balance
Number of clients 10–20 Diversity–data volume balance
μ (FedProx) 0–0.01 Avoids excessive regularization

Contributions

  1. Provides a federated learning benchmark in the text classification domain
  2. Implements Dirichlet-based non-IID data generation
  3. Performs a systematic hyperparameter search with Bayesian optimization
  4. Presents an empirical comparison of FedProx and SCAFFOLD
  5. Derives practical algorithm selection guidelines

As a core technology of privacy-preserving machine learning, federated learning is becoming increasingly important in many fields, including healthcare, finance, and mobile services. I hope this work offers useful guidance to researchers and engineers who want to apply federated learning in practice.

12. Implementation Details

12.1 Overall System Architecture

The federated learning system consists of the following components:

Server Component
class FederatedServer:
    def __init__(self, model, method='FedAvg'):
        self.global_model = model
        self.method = method
        if method == 'SCAFFOLD':
            self.c_global = {name: torch.zeros_like(param) 
                           for name, param in model.named_parameters()}
    
    def aggregate(self, client_updates, sample_counts):
        """Aggregate client updates by weighted average"""
        total_samples = sum(sample_counts)
        
        # Compute the weighted average
        new_params = {}
        for name in self.global_model.state_dict().keys():
            new_params[name] = sum(
                client_updates[i][name] * (sample_counts[i] / total_samples)
                for i in range(len(client_updates))
            )
        
        self.global_model.load_state_dict(new_params)
        return self.global_model.state_dict()
    
    def update_control_variates(self, delta_cs):
        """SCAFFOLD: update the global control variate"""
        if self.method != 'SCAFFOLD':
            return
        
        for name in self.c_global.keys():
            for delta_c in delta_cs:
                self.c_global[name] += delta_c[name] / len(delta_cs)
Client Component
class FederatedClient:
    def __init__(self, client_id, dataset, model, method='FedAvg', mu=0.0):
        self.client_id = client_id
        self.dataset = dataset
        self.model = copy.deepcopy(model)
        self.method = method
        self.mu = mu
        
        if method == 'SCAFFOLD':
            self.c_local = {name: torch.zeros_like(param) 
                          for name, param in model.named_parameters()}
    
    def local_train(self, global_params, global_control=None, epochs=1, lr=0.01):
        """Run local training"""
        self.model.load_state_dict(global_params)
        optimizer = torch.optim.SGD(self.model.parameters(), lr=lr)
        criterion = nn.CrossEntropyLoss()
        
        dataloader = DataLoader(self.dataset, batch_size=16, shuffle=True)
        
        for epoch in range(epochs):
            for batch in dataloader:
                input_ids = batch['input_ids']
                labels = batch['labels']
                
                optimizer.zero_grad()
                outputs = self.model(input_ids)
                loss = criterion(outputs.view(-1, outputs.size(-1)), labels.view(-1))
                
                # FedProx: add the proximal term
                if self.method == 'FedProx' and self.mu > 0:
                    prox_term = 0.0
                    for name, param in self.model.named_parameters():
                        prox_term += torch.norm(param - global_params[name]) ** 2
                    loss += (self.mu / 2) * prox_term
                
                loss.backward()
                
                # SCAFFOLD: gradient correction
                if self.method == 'SCAFFOLD' and global_control is not None:
                    for name, param in self.model.named_parameters():
                        if param.grad is not None:
                            param.grad += self.c_local[name] - global_control[name]
                
                optimizer.step()
        
        return self.model.state_dict()

12.2 wandb Integration

wandb integration for experiment tracking and hyperparameter optimization:

import wandb

def run_experiment(config=None):
    with wandb.init(config=config):
        config = wandb.config
        
        # Partition the data
        if config.iid:
            client_datasets = split_data_iid(train_dataset, config.num_users)
        else:
            client_datasets = split_data_non_iid(train_dataset, config.num_users, config.alpha)
        
        # Initialize the server and clients
        server = FederatedServer(model, method=config.method)
        clients = [
            FederatedClient(i, client_datasets[i], model, 
                          method=config.method, mu=config.mu)
            for i in range(config.num_users)
        ]
        
        # Training loop
        for round in range(config.num_rounds):
            # Select clients
            selected = np.random.choice(
                config.num_users, 
                int(config.num_users * config.fraction), 
                replace=False
            )
            
            # Local training
            client_updates = []
            sample_counts = []
            for i in selected:
                update = clients[i].local_train(
                    server.global_model.state_dict(),
                    global_control=server.c_global if config.method == 'SCAFFOLD' else None,
                    epochs=config.epochs,
                    lr=config.learning_rate
                )
                client_updates.append(update)
                sample_counts.append(len(clients[i].dataset))
            
            # Aggregate
            server.aggregate(client_updates, sample_counts)
            
            # Evaluate
            accuracy = evaluate(server.global_model, test_dataset)
            wandb.log({'test_accuracy': accuracy, 'round': round})
        
        return accuracy

# Run the sweep
sweep_id = wandb.sweep(sweep_config, project='federated-learning')
wandb.agent(sweep_id, run_experiment, count=20)

12.3 Ensuring Reproducibility

I took the following measures to make the experiments reproducible:

def set_seed(seed=42):
    """Seed every source of randomness"""
    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
    torch.cuda.manual_seed_all(seed)
    torch.backends.cudnn.deterministic = True
    torch.backends.cudnn.benchmark = False

I also logged each run's configuration and results to wandb so that any run can be reproduced at any time.

12.4 Memory Optimization

Techniques for memory efficiency in federated learning:

  1. Minimize model copies: deep-copy only when necessary
  2. Gradient accumulation: achieve a large effective batch size with small batches
  3. Mixed-precision training: save memory by using FP16 (NVIDIA GPUs)
# Mixed-precision training example
from torch.cuda.amp import autocast, GradScaler

scaler = GradScaler()

with autocast():
    outputs = model(input_ids)
    loss = criterion(outputs, labels)

scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()

13. Experimental Environment

13.1 Hardware

  • CPU: AMD EPYC 7742 64-Core Processor
  • RAM: 256GB DDR4
  • GPU: NVIDIA A100 40GB × 4
  • Storage: NVMe SSD 2TB

13.2 Software

Software Version
Python 3.9.16
PyTorch 2.0.1
Transformers 4.30.0
NumPy 1.24.3
wandb 0.15.4
CUDA 11.8

13.3 Experiment Time

Setting Time per round Total run time
5 clients, 5 epochs ~30 s ~8 min
10 clients, 10 epochs ~1 min ~15 min
20 clients, 15 epochs ~3 min ~45 min

Full sweep (20 runs): about 6 hours

14. Code and Data Availability

14.1 Project Structure

federated-learning-benchmark/
├── data/
│   ├── ag_news/
│   │   ├── train.csv
│   │   └── test.csv
│   └── preprocess.py
├── models/
│   ├── gru.py
│   └── __init__.py
├── fl/
│   ├── server.py
│   ├── client.py
│   ├── fedprox.py
│   └── scaffold.py
├── utils/
│   ├── data_split.py
│   ├── metrics.py
│   └── seed.py
├── configs/
│   └── sweep_config.yaml
├── experiments/
│   └── run_sweep.py
├── notebooks/
│   └── analysis.ipynb
├── requirements.txt
└── README.md

14.2 Key Code Snippets

IID data partitioning:

def split_data_iid(dataset, num_users):
    num_items = len(dataset) // num_users
    indices = np.random.permutation(len(dataset))
    return [Subset(dataset, indices[i*num_items:(i+1)*num_items]) 
            for i in range(num_users)]

Non-IID data partitioning (Dirichlet):

def split_data_non_iid(dataset, num_users, alpha):
    labels = np.array([dataset[i][1] for i in range(len(dataset))])
    num_classes = len(np.unique(labels))
    
    # Split each class according to a Dirichlet distribution
    client_indices = [[] for _ in range(num_users)]
    for c in range(num_classes):
        class_indices = np.where(labels == c)[0]
        np.random.shuffle(class_indices)
        
        proportions = np.random.dirichlet([alpha] * num_users)
        proportions = (np.cumsum(proportions) * len(class_indices)).astype(int)[:-1]
        
        for i, chunk in enumerate(np.split(class_indices, proportions)):
            client_indices[i].extend(chunk.tolist())
    
    return [Subset(dataset, indices) for indices in client_indices]

The full code and experimental results of this project may be used for academic research purposes.

Similar posts