FL Client over Secure RPC#

In this notebook, we will present how to launch a gRPC client as an FL client with an authenticator. To pair with the server notebook, we consider only one client.

[10]:
num_clients = 1

Import dependencies#

Everything is the same as for the gRPC server. But here, we need to import appfl.run_grpc_client module.

[11]:
import numpy as np
import math
import torch
import torch.nn as nn
import torchvision
from torchvision.transforms import ToTensor

from appfl.config import Config
from appfl.misc.data import Dataset
import appfl.run_grpc_client as grpc_client
from omegaconf import OmegaConf

Training datasets#

Each client needs to create Dataset object with the training data. Here, we create the objects for all the clients.

[12]:
train_data_raw = torchvision.datasets.MNIST(
    "./_data", train=True, download=True, transform=ToTensor()
)
split_train_data_raw = np.array_split(range(len(train_data_raw)), num_clients)
train_datasets = []
for i in range(num_clients):

    train_data_input = []
    train_data_label = []
    for idx in split_train_data_raw[i]:
        train_data_input.append(train_data_raw[idx][0].tolist())
        train_data_label.append(train_data_raw[idx][1])

    train_datasets.append(
        Dataset(
            torch.FloatTensor(train_data_input),
            torch.tensor(train_data_label),
        )
    )

Model#

We should use the same model used in the server. See the notebook for server.

[13]:
class CNN(nn.Module):
    def __init__(self, num_channel=1, num_classes=10, num_pixel=28):
        super().__init__()
        self.conv1 = nn.Conv2d(
            num_channel, 32, kernel_size=5, padding=0, stride=1, bias=True
        )
        self.conv2 = nn.Conv2d(32, 64, kernel_size=5, padding=0, stride=1, bias=True)
        self.maxpool = nn.MaxPool2d(kernel_size=(2, 2))
        self.act = nn.ReLU(inplace=True)

        X = num_pixel
        X = math.floor(1 + (X + 2 * 0 - 1 * (5 - 1) - 1) / 1)
        X = X / 2
        X = math.floor(1 + (X + 2 * 0 - 1 * (5 - 1) - 1) / 1)
        X = X / 2
        X = int(X)

        self.fc1 = nn.Linear(64 * X * X, 512)
        self.fc2 = nn.Linear(512, num_classes)

    def forward(self, x):
        x = self.act(self.conv1(x))
        x = self.maxpool(x)
        x = self.act(self.conv2(x))
        x = self.maxpool(x)
        x = torch.flatten(x, 1)
        x = self.act(self.fc1(x))
        x = self.fc2(x)
        return x

model = CNN()

Loss and metric#

We should use the same loss function

[14]:
loss_fn = torch.nn.CrossEntropyLoss()

and validation metric as the server.

[ ]:
def accuracy(y_true, y_pred):
    '''
    y_true and y_pred are both of type np.ndarray
    y_true (N, d) where N is the size of the validation set, and d is the dimension of the label
    y_pred (N, D) where N is the size of the validation set, and D is the output dimension of the ML model
    '''
    if len(y_pred.shape) == 1:
        y_pred = np.round(y_pred)
    else:
        y_pred = y_pred.argmax(axis=1)
    return 100*np.sum(y_pred==y_true)/y_pred.shape[0]

Set configurations#

We run the appfl training with the data and model defined above. A number of parameters can be easily set by changing the configuration values.

[ ]:
cfg = OmegaConf.structured(Config)
# print(OmegaConf.to_yaml(cfg))

Here, we set the number of local epochs to 1 and the local learning rate to 0.01

[ ]:
cfg.fed.args.num_local_epochs = 1
cfg.fed.args.optim_args.lr = 0.01

Create secure SSL channel and authenticator#

The client requires a root certificate to verify the server certificate. In this example, we provide that root certificate, assuming that the server uses self-signed certificate and key provided by gRPC official documentation.

To use the provided root certificate, user just to need to set the following. If the user would like to use his own root certificate, just change this to the file path to the local root certificate.

[16]:
cfg.client.root_certificates = "default"

Then to use the NaiveAuthenticator, user needs to set the following as the NaiveAuthenticator does not take any argument.

[ ]:
cfg.client.authenticator = "Naive"
cfg.client.authenticator_kwargs = {}

Run with configurations#

And, we can start a secure training with the configuration cfg.

[17]:
grpc_client.run_client(cfg, 0, model, loss_fn, train_datasets[0], metric=accuracy)