Federated Learning for Medical Imaging with Flower and PyTorch
Introduction
In healthcare, patient data is highly sensitive and often legally prohibited from leaving the hospital's internal network (e.g., due to HIPAA or GDPR). Federated Learning (FL) solves this by bringing the model to the data, rather than the data to the model. In FL, multiple hospitals (clients) train a model locally on their private data, and only the updated model weights are sent to a central server to be aggregated. In this tutorial, we will use the open-source FL framework Flower (flwr) and PyTorch to train a CNN across distributed clients.
Architecture Overview
graph TD
Server((Central Aggregation Server))
Server -- 1. Distribute Global Model --> Client1[Hospital A Client]
Server -- 1. Distribute Global Model --> Client2[Hospital B Client]
Server -- 1. Distribute Global Model --> Client3[Hospital C Client]
Client1 -. 2. Train on Local MRI Data .-> Client1
Client2 -. 2. Train on Local MRI Data .-> Client2
Client3 -. 2. Train on Local MRI Data .-> Client3
Client1 -- 3. Send Weight Updates --> Server
Client2 -- 3. Send Weight Updates --> Server
Client3 -- 3. Send Weight Updates --> Server
Server -. 4. Aggregate Weights: FedAvg .-> Server
Prerequisites
- Python 3.8+
- PyTorch, torchvision
- Flower (
flwr) - Basic understanding of client-server architecture
Step 1: Install Dependencies
pip install flwr torch torchvision numpy
Step 2: Defining the Central Server
The server is responsible for coordinating the clients, sending them the global model, and aggregating their updates using an algorithm like FedAvg (Federated Averaging). Create a file named server.py.
import flwr as fl
# Define strategy: Federated Averaging
strategy = fl.server.strategy.FedAvg(
fraction_fit=1.0, # Fraction of clients used during training (1.0 = all connected clients)
fraction_evaluate=0.5, # Fraction of clients used for evaluation
min_fit_clients=2, # Minimum number of clients to be sampled for training
min_evaluate_clients=2,
min_available_clients=2, # Wait until at least 2 clients are connected before starting
)
# Start Flower server
print("Starting Central Aggregation Server...")
fl.server.start_server(
server_address="0.0.0.0:8080",
config=fl.server.ServerConfig(num_rounds=3), # Run for 3 rounds of federated training
strategy=strategy,
)
Step 3: Defining the Client Node with Non-IID Data
A major challenge in medical FL is that data is often Non-IID (Independent and Identically Distributed). For example, Hospital A might only see elderly patients, while Hospital B sees pediatric patients. We simulate this by heavily skewing the dataset partitioning. Create a file named client.py.
import flwr as fl
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader, Subset
import numpy as np
import sys
# 1. Define the PyTorch Model (e.g., a simple CNN for MNIST/MedMNIST)
class SimpleCNN(nn.Module):
def __init__(self):
super(SimpleCNN, self).__init__()
self.conv1 = nn.Conv2d(1, 16, 3, 1)
self.conv2 = nn.Conv2d(16, 32, 3, 1)
self.fc1 = nn.Linear(32 * 5 * 5, 128)
self.fc2 = nn.Linear(128, 10)
def forward(self, x):
x = torch.relu(self.conv1(x))
x = torch.max_pool2d(x, 2)
x = torch.relu(self.conv2(x))
x = torch.max_pool2d(x, 2)
x = torch.flatten(x, 1)
x = torch.relu(self.fc1(x))
x = self.fc2(x)
return x
# 2. Define standard PyTorch train/test functions
def train(net, trainloader, epochs):
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.01)
net.train()
for _ in range(epochs):
for images, labels in trainloader:
optimizer.zero_grad()
criterion(net(images), labels).backward()
optimizer.step()
def test(net, testloader):
criterion = nn.CrossEntropyLoss()
correct, total, loss = 0, 0, 0.0
net.eval()
with torch.no_grad():
for images, labels in testloader:
outputs = net(images)
loss += criterion(outputs, labels).item()
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
return loss / len(testloader), correct / total
# 3. Load LOCAL Private Data (Simulating Non-IID distribution)
# Run `python client.py 0` for client A, and `python client.py 1` for client B
client_id = int(sys.argv[1]) if len(sys.argv) > 1 else 0
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,))])
full_trainset = datasets.MNIST("./data", train=True, download=True, transform=transform)
full_testset = datasets.MNIST("./data", train=False, download=True, transform=transform)
# Simulate Non-IID: Client 0 gets digits 0-4, Client 1 gets digits 5-9
if client_id == 0:
idx = (full_trainset.targets <= 4)
else:
idx = (full_trainset.targets > 4)
trainset = Subset(full_trainset, np.where(idx)[0])
trainloader = DataLoader(trainset, batch_size=32, shuffle=True)
testloader = DataLoader(full_testset, batch_size=32)
# Instantiate the model
net = SimpleCNN()
# 4. Define the Flower Client
class FlowerClient(fl.client.NumPyClient):
def get_parameters(self, config):
return [val.cpu().numpy() for _, val in net.state_dict().items()]
def set_parameters(self, parameters):
params_dict = zip(net.state_dict().keys(), parameters)
state_dict = {k: torch.tensor(v) for k, v in params_dict}
net.load_state_dict(state_dict, strict=True)
def fit(self, parameters, config):
self.set_parameters(parameters)
train(net, trainloader, epochs=1) # Train locally for 1 epoch
return self.get_parameters(config={}), len(trainloader.dataset), {}
def evaluate(self, parameters, config):
self.set_parameters(parameters)
loss, accuracy = test(net, testloader)
return float(loss), len(testloader.dataset), {"accuracy": accuracy}
# Start Client
print(f"Connecting to server as Client {client_id}...")
fl.client.start_numpy_client(server_address="127.0.0.1:8080", client=FlowerClient())
Step 4: Running the Federation
To simulate the federated learning process, you will need to open multiple terminal windows.
# Terminal 1: Start the server
python server.py
# Terminal 2: Start Client A (Hospital 1 - Digits 0-4)
python client.py 0
# Terminal 3: Start Client B (Hospital 2 - Digits 5-9)
python client.py 1
As soon as both clients connect, the server will begin the FedAvg process. Even though Hospital 1 has never seen a digit > 4, and Hospital 2 has never seen a digit <= 4, the globally aggregated model will be able to recognize all 10 digits!
Conclusion
Federated Learning with frameworks like Flower enables multi-institutional medical research without compromising patient privacy. By aggregating model weights rather than raw data, hospitals can collaboratively build highly accurate models that are robust to diverse demographic distributions and non-IID data.