配置客户端#
除了模型参数,Flower 还可以向客户端发送配置值。配置值有多种用途。它们是一种从服务器控制客户端超参数的常用方法。
配置值#
配置值以字典的形式表示,字典的键为 str
,值的类型为 bool
、bytes
、``double``(64 位精度浮点型)、``int``或 ``str`(或不同语言中的等效类型)。下面是一个 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 将这些配置字典(简称 config dict)序列化为 ProtoBuf 表示形式,使用 gRPC 将其传输到客户端,然后再反序列化为 Python 字典。
备注
目前,还不支持在配置字典中直接发送作为值的集合类型(例如,Set`, List, Map`)。有几种变通方法可将集合转换为支持的值类型之一(并在客户端将其转换回),从而将集合作为值发送。
例如,可以将浮点数列表转换为 JSON 字符串,然后使用配置字典发送 JSON 字符串,再在客户端将 JSON 字符串转换回浮点数列表。
通过内置策略进行配置#
向客户端发送配置值的最简单方法是使用内置策略,如 FedAvg
。内置策略支持所谓的配置函数。配置函数是内置策略调用的函数,用于获取当前轮的配置字典。然后,它会将配置字典转发给该轮中选择的所有客户端。
让我们从一个简单的例子开始。想象一下,我们想要发送给客户端(a)应该使用的批次大小,(b)当前联邦学习的全局轮次,以及(c)客户端训练的遍历数。我们的配置函数可以是这样的:
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
为了让内置策略使用这个函数,我们可以在初始化时使用参数 on_fit_config_fn
将它传递给 FedAvg
:
strategy = FedAvg(
..., # Other FedAvg parameters
on_fit_config_fn=fit_config, # The fit_config function we defined earlier
)
在客户端,我们在 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)
还有一个 on_evaluate_config_fn 用于配置评估,其工作方式相同。它们是不同的函数,因为可能需要向 evaluate 发送不同的配置值(例如,使用不同的批量大小)。
内置策略每轮都会调用此函数(即每次运行 Strategy.configure_fit 或 Strategy.configure_evaluate 时)。每轮调用 on_evaluate_config_fn 允许我们在连续几轮中改变配置指令。例如,如果我们想实现一个超参数时间表,以增加后几轮的本地遍历次数,我们可以这样做:
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
代码:`FedAvg`策略*每轮*都会调用该函数。
配置个别客户端#
在某些情况下,有必要向不同的客户端发送不同的配置值。
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)