Configure clients#

En plus des paramètres du modèle, Flower peut envoyer des valeurs de configuration aux clients. Les valeurs de configuration peuvent être utilisées à diverses fins. Elles constituent, par exemple, un moyen populaire de contrôler les hyperparamètres côté client à partir du serveur.

Valeurs de configuration#

Les valeurs de configuration sont représentées sous forme de dictionnaire avec des clés str` et des valeurs de type bool, bytes, double (float de précision 64 bits), int, ou str (ou des types équivalents dans d’autres langages). Voici un exemple de dictionnaire de configuration en Python :

config_dict = {
    "dropout": True,        # str key, bool value
    "learning_rate": 0.01,  # str key, float value
    "batch_size": 32,       # str key, int value
    "optimizer": "sgd",     # str key, str value
}

Flower sérialise ces dictionnaires de configuration (ou config dict en abrégé) dans leur représentation ProtoBuf, les transporte vers le client à l’aide de gRPC, puis les désérialise à nouveau en dictionnaires Python.

Note

Actuellement, il n’est pas possible d’envoyer directement des types de collections (par exemple, Set, List, Map) en tant que valeurs dans les dictionnaires de configuration. Il existe plusieurs solutions pour envoyer des collections en tant que valeurs en les convertissant en l’un des types de valeurs pris en charge (et en les reconvertissant du côté client).

On peut, par exemple, convertir une liste de nombres à virgule flottante en une chaîne JSON, puis envoyer la chaîne JSON à l’aide du dictionnaire de configuration, et enfin reconvertir la chaîne JSON en une liste de nombres à virgule flottante sur le client.

Configuration par le biais de stratégies intégrées#

La façon la plus simple d’envoyer des valeurs de configuration aux clients est d’utiliser une stratégie intégrée comme FedAvg. Les stratégies intégrées prennent en charge ce que l’on appelle les fonctions de configuration. Une fonction de configuration est une fonction que la stratégie intégrée appelle pour obtenir le dictionnaire de configuration pour le tour en cours. Elle transmet ensuite le dictionnaire de configuration à tous les clients sélectionnés au cours de ce tour.

Commençons par un exemple simple. Imaginons que nous voulions envoyer (a) la taille du lot que le client doit utiliser, (b) le cycle global actuel de l’apprentissage fédéré et (c) le nombre d’époques à former du côté client. Notre fonction de configuration pourrait ressembler à ceci :

def fit_config(server_round: int):
    """Return training configuration dict for each round."""
    config = {
        "batch_size": 32,
        "current_round": server_round,
        "local_epochs": 2,
    }
    return config

Pour que les stratégies intégrées utilisent cette fonction, nous pouvons la passer à FedAvg lors de l’initialisation en utilisant le paramètre on_fit_config_fn :

strategy = FedAvg(
    ...,                          # Other FedAvg parameters
    on_fit_config_fn=fit_config,  # The fit_config function we defined earlier
)

Côté client, nous recevons le dictionnaire de configuration dans fit :

class FlowerClient(flwr.client.NumPyClient):
    def fit(parameters, config):
        print(config["batch_size"])  # Prints `32`
        print(config["current_round"])  # Prints `1`/`2`/`...`
        print(config["local_epochs"])  # Prints `2`
        # ... (rest of `fit` method)

Il existe également une fonction on_evaluate_config_fn pour configurer l’évaluation, qui fonctionne de la même manière. Ce sont des fonctions séparées car on peut vouloir envoyer différentes valeurs de configuration à evaluate (par exemple, pour utiliser une taille de lot différente).

Les stratégies intégrées appellent cette fonction à chaque tour (c’est-à-dire à chaque fois que Strategy.configure_fit ou Strategy.configure_evaluate s’exécute). Appeler on_evaluate_config_fn à chaque tour nous permet de varier/changer le dict de config au cours de tours consécutifs. Si nous voulions mettre en place un calendrier d’hyperparamètres, par exemple, pour augmenter le nombre d’époques locales au cours des derniers tours, nous pourrions faire ce qui suit :

def fit_config(server_round: int):
    """Return training configuration dict for each round."""
    config = {
        "batch_size": 32,
        "current_round": server_round,
        "local_epochs": 1 if server_round < 2 else 2,
    }
    return config

La stratégie FedAvg appellera cette fonction à chaque tour.

Configuration des clients individuels#

Dans certains cas, il est nécessaire d’envoyer des valeurs de configuration différentes à des clients différents.

This can be achieved by customizing an existing strategy or by implementing a custom strategy from scratch. Here’s a nonsensical example that customizes FedAvg by adding a custom "hello": "world" configuration key/value pair to the config dict of a single client (only the first client in the list, the other clients in this round to not receive this « special » config value):

class CustomClientConfigStrategy(fl.server.strategy.FedAvg):
    def configure_fit(
        self, server_round: int, parameters: Parameters, client_manager: ClientManager
    ) -> List[Tuple[ClientProxy, FitIns]]:
        client_instructions = super().configure_fit(server_round, parameters, client_manager)

        # Add special "hello": "world" config key/value pair,
        # but only to the first client in the list
        _, fit_ins = client_instructions[0]  # First (ClientProxy, FitIns) pair
        fit_ins.config["hello"] = "world"  # Change config for this client only

        return client_instructions

# Create strategy and run server
strategy = CustomClientConfigStrategy(
    # ... (same arguments as plain FedAvg here)
)
fl.server.start_server(strategy=strategy)