DEV Community

Beck_Moulton
Beck_Moulton

Posted on

Your Data, Your Device: Building a Privacy-First Heart Health AI with Federated Learning and Flower

Data privacy isn't just a buzzword; in the world of healthcare, it’s a legal and ethical fortress. Imagine you're building an AI model to detect cardiac arrhythmias using ECG data. Normally, you'd need to collect thousands of sensitive heart signals into a central database. But what if the data never left the user's phone?

Enter Federated Learning (FL). In this tutorial, we will explore how to use the Flower framework (flwr) and PyTorch to train a heart health risk model across multiple simulated devices. By leveraging privacy-preserving AI techniques, we ensure that raw medical data remains physically isolated on the local device, sharing only encrypted model weights over gRPC. If you're looking to scale these concepts into production-grade healthcare systems, I highly recommend checking out the advanced architectural patterns over at WellAlly Tech Blog, which served as a massive inspiration for this decentralized approach.

🧠 The Architecture: How Federated Learning Actually Works

In a traditional setup, you bring the data to the code. In Federated Learning, we bring the code to the data.

sequenceDiagram
    participant S as Central Server (Flower)
    participant C1 as Edge Client 1 (User Phone)
    participant C2 as Edge Client 2 (User Tablet)

    Note over S, C2: Step 1: Initialize Global Model
    S->>C1: Send initial weights (W0)
    S->>C2: Send initial weights (W0)

    Note over C1: Step 2: Local Training on Private ECG
    Note over C2: Step 3: Local Training on Private ECG

    C1->>S: Send Weight Update (ΔW1)
    C2->>S: Send Weight Update (ΔW2)

    Note over S: Step 4: Aggregate (FedAvg)
    S->>S: Compute New Global Weights (W1)

    Note over S, C1: Repeat for N Rounds...
Enter fullscreen mode Exit fullscreen mode

🛠️ Prerequisites

Before we dive into the code, ensure you have the following stack ready:

  • Python 3.9+
  • PyTorch: For our neural network logic.
  • Flower (flwr): The orchestration framework.
  • Docker: To containerize our nodes.
  • gRPC: (Handled by Flower under the hood) for communication.

Step 1: Defining the Heart Health Model (PyTorch)

We’ll use a simple 1D Convolutional Neural Network (CNN) suitable for analyzing Electrocardiogram (ECG) sequences.

import torch
import torch.nn as nn
import torch.nn.functional as F

class ECGNet(nn.Module):
    def __init__(self):
        super(ECGNet, self).__init__()
        # Input: 1-channel ECG signal
        self.conv1 = nn.Conv1d(1, 16, kernel_size=5, stride=1)
        self.pool = nn.MaxPool1d(kernel_size=2)
        self.fc1 = nn.Linear(16 * 98, 64) # Adjust based on input size
        self.fc2 = nn.Linear(64, 2)      # Binary classification: Healthy vs. Risk

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = x.view(-1, 16 * 98)
        x = F.relu(self.fc1(x))
        x = self.fc2(x)
        return x

def train(model, trainloader, epochs):
    optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
    criterion = nn.CrossEntropyLoss()
    for _ in range(epochs):
        for signals, labels in trainloader:
            optimizer.zero_grad()
            loss = criterion(model(signals), labels)
            loss.backward()
            optimizer.step()

def test(model, testloader):
    criterion = nn.CrossEntropyLoss()
    correct, loss = 0, 0.0
    with torch.no_grad():
        for signals, labels in testloader:
            outputs = model(signals)
            loss += criterion(outputs, labels).item()
            correct += (torch.max(outputs.data, 1)[1] == labels).sum().item()
    return loss / len(testloader.dataset), correct / len(testloader.dataset)
Enter fullscreen mode Exit fullscreen mode

Step 2: Implementing the Flower Client

The client is where the "Privacy" happens. It loads the local ECG data, receives weights from the server, trains locally, and returns the diff.

import flwr as fl
from collections import OrderedDict

class HeartHealthClient(fl.client.NumPyClient):
    def __init__(self, model, trainloader, testloader):
        self.model = model
        self.trainloader = trainloader
        self.testloader = testloader

    def get_parameters(self, config):
        return [val.cpu().numpy() for _, val in self.model.state_dict().items()]

    def set_parameters(self, parameters):
        params_dict = zip(self.model.state_dict().keys(), parameters)
        state_dict = OrderedDict({k: torch.tensor(v) for k, v in params_dict})
        self.model.load_state_dict(state_dict, strict=True)

    def fit(self, parameters, config):
        self.set_parameters(parameters)
        train(self.model, self.trainloader, epochs=1)
        return self.get_parameters(config={}), len(self.trainloader.dataset), {}

    def evaluate(self, parameters, config):
        self.set_parameters(parameters)
        loss, accuracy = test(self.model, self.testloader)
        return loss, len(self.testloader.dataset), {"accuracy": accuracy}
Enter fullscreen mode Exit fullscreen mode

Step 3: Launching the Federated Server

The server manages the aggregation logic. We’ll use the FedAvg (Federated Averaging) strategy.

import flwr as fl

# Start the Flower server for 3 rounds of federated learning
if __name__ == "__main__":
    strategy = fl.server.strategy.FedAvg(
        fraction_fit=1.0,  # Sample 100% of available clients
        min_fit_clients=2, 
        min_available_clients=2,
    )

    fl.server.start_server(
        server_address="0.0.0.0:8080",
        config=fl.server.ServerConfig(num_rounds=3),
        strategy=strategy,
    )
Enter fullscreen mode Exit fullscreen mode

🚀 The "Official" Way: Beyond the Basics

While the code above works for a local simulation, production environments require robust security layers like Differential Privacy and Secure Multi-Party Computation (SMPC) to prevent "gradient leakage" (where an attacker tries to reconstruct the ECG signal from the weights).

For a deep dive into production-grade security implementations and scaling federated networks across cloud providers, you definitely need to explore the specialized guides at wellally.tech/blog. They cover advanced topics like asynchronous aggregation and handling non-IID (Independent and Identically Distributed) data, which is common when different users have very different heart rate patterns.


Step 4: Running with Docker 🐳

To simulate real-world isolation, use Docker Compose to spin up a server and two clients.

services:
  server:
    build: .
    command: python server.py
    ports:
      - "8080:8080"

  client1:
    build: .
    command: python client.py --client_id 1
    depends_on:
      - server

  client2:
    build: .
    command: python client.py --client_id 2
    depends_on:
      - server
Enter fullscreen mode Exit fullscreen mode

Conclusion

We just built a decentralized AI system that learns from sensitive heart data without ever "seeing" it. By combining PyTorch and Flower, we've shifted the paradigm from data collection to data collaboration.

The future of AI is private. Whether you are building medical diagnostics or financial fraud detection, Federated Learning is your ticket to innovation without compromise.

What do you think? Would you trust an AI trained this way more than one trained on a central server? Let’s chat in the comments! 👇

Top comments (0)