目录
def localUpdate()---在本地更新模型的参数,模型训练
第三块:class ClientsGroup() 管理客户端
def clients_distribution():遍历客户端,收集每个样本分布信息
第一块:库和自定义模块的引入
from utils import *
from torch.utils.data import DataLoader
from getData import GetDataSet, ClientDataset
from utils import *(自定义的模块)
from getData import GetDataSet, ClientDataset
第二块:class client()模拟客户端
class client(object):
def __init__(self, idx, trainDataSet, dev, distribution, nclass):
self.id = idx
self.train_ds = trainDataSet
self.dev = dev
self.train_DataLoader = None '''初始化'''
self.local_parameters = None '''初始化'''
self.distribution = distribution
self.nclass = nclass
def localUpdate(self, localEpoch, localBatchSize, Net, lossFun, opti, global_parameters):
# 加载当前通信中最新全局参数
# 传入网络模型,并加载global_parameters参数的
Net.load_state_dict(global_parameters, strict=True)'''加载全局模型参数到本地模型。'''
# 载入Client自有数据集
# 加载本地数据
self.train_DataLoader = DataLoader(self.train_ds, batch_size=localBatchSize,
shuffle=True)'''初始化数据加载器以批量加载训练数据。'''
''' # 设置迭代次数.本地训练轮次内,遍历数据加载器中的每个批次'''
for epoch in range(localEpoch):
for data, label in self.train_DataLoader:
'''# 加载到GPU上,数据和标签移至指定设备'''
data, label = data.to(self.dev), label.to(self.dev)
'''# 模型上传入数据,模型前向传播得到预测'''
preds = Net(data)
# 计算损失函数
'''
这里应该记录一下模型得损失值 写入到一个txt文件中
'''
loss = lossFun(preds, label.long())
# 反向传播
loss.backward()
# 计算梯度,并更新梯度
opti.step()
''' 将梯度归零,初始化梯度'''
opti.zero_grad()
# 返回当前Client基于自己的数据训练得到的新的模型参数
return Net.state_dict()
def local_val(self):
pass
client
类是一个模拟单个客户端行为的类。
def __init__属性
id
: 客户端的唯一标识符。train_ds
: 客户端的训练数据集。dev
: 设备标识符,通常用于指定是在CPU还是GPU上进行计算。train_DataLoader
: 数据加载器,用于批量加载训练数据。这个属性在localUpdate
方法中被初始化。local_parameters
: 客户端本地的模型参数(尽管在提供的代码中没有直接看到它的使用)。distribution
: 可能用于表示数据分布或其他与客户端相关的特定分布信息,具体用途取决于上下文。nclass
: 数据集中的类别数。
def localUpdate()---在本地更新模型的参数,模型训练
本地训练的轮次(localEpoch
)、批量大小(localBatchSize
)、网络模型(Net
)、损失函数(lossFun
)、优化器(opti
)以及全局模型参数(global_parameters
)。:
def local_val()
: 执行本地验证
(例如,在本地测试集上评估模型性能)。在提供的代码中,这个方法体为空,意味着它还没有被实现。在实际应用中,你可能需要在这个方法中编写代码来评估模型在客户端本地验证集上的性能。
第三块:class ClientsGroup() 管理客户端
class ClientsGroup(object):
def __init__(self, dataSetName, isIID, num_clients, beta=0.4, datadir="./data/", dev=torch.device("cuda")):
self.dev = dev
self.data_set_name = dataSetName
self.is_iid = isIID
self.beta = beta
self.datadir = datadir
self.num_of_clients = num_clients
self.clients_set = {}
self.test_data_loader = None
self.clients_distributions = None
self.partition_data()
self.clients_distribution()
def partition_data(self):
dataSet = GetDataSet(self.data_set_name, self.datadir)'''GetDataSet自定义的'''
nclass = np.max(dataSet.train_label) + 1'''训练数据中的类别总数'''
# 加载测试数据
test_data = dataSet.test_data
test_label = dataSet.test_label
transform_test = dataSet.transform_test
transform_train = dataSet.transform_train
# 全局测试集
self.test_data_loader = DataLoader(ClientDataset(test_data, test_label, transform_test), batch_size=256,
shuffle=False)
'''判断是否IID,如果不是,按照dirichlet的beta划分数据 '''
if self.is_iid:'''随机划分为多个客户端'''
'''这行代码设置了随机数生成器的种子,以确保每次运行代码时都能得到相同的随机划分结果'''
np.random.seed(12)
idxs = np.random.permutation(dataSet.train_data_size)'''对训练数据的索引进行随机排列'''
batch_idxs = np.array_split(idxs, self.num_of_clients)'''随机排列后的索引数组 idxs 划分成 self.num_of_clients 个子数组'''
'''为客户端分配数据'''
for i in range(self.num_of_clients):
client_train_data = dataSet.train_data[batch_idxs[i]]
client_train_label = dataSet.train_label[batch_idxs[i]]
'''计算类别分布'''
distribution = [client_train_label.tolist().count(i) for i in range(nclass)]
''' # 为每一个clients 设置一个名字 client10'创建客户端对象'''
self.clients_set['client{}'.format(i)] = client('client{}'.format(i), ClientDataset(client_train_data, client_train_label, transform_train), self.dev, distribution, nclass)
else:
n_clients = self.num_of_clients'''初始化变量'''
train_label = dataSet.train_label
np.random.seed(123)
label_distribution = np.random.dirichlet([self.beta] * n_clients, nclass)
class_idcs = [np.argwhere(train_label == y).flatten() for y in range(nclass)]
client_idcs = [[] for _ in range(n_clients)]
for c, fracs in zip(class_idcs, label_distribution):
# np.split按照比例将类别为k的样本划分为了N个子集
# for i, idcs 为遍历第i个client对应样本集合的索引
for i, idcs in enumerate(np.split(c, (np.cumsum(fracs)[:-1] * len(c)).astype(int))):
client_idcs[i] += [idcs]
for i in range(self.num_of_clients):
idcs = client_idcs[i]
distribution = [len(c) for c in idcs]
client_train_data = dataSet.train_data[np.concatenate(idcs)]
client_train_label = dataSet.train_label[np.concatenate(idcs)]
self.clients_set['client{}'.format(i)] = client('client{}'.format(i), ClientDataset(client_train_data, client_train_label, transform_train), self.dev, distribution, nclass)
def clients_distribution(self):
distributions = {}
for i in range(self.num_of_clients):
distributions['client{}'.format(i)] = self.clients_set['client{}'.format(i)].distribution
self.clients_distributions = distributions
def __init__():
dataSetName
: 数据集名称,用于指定要使用的数据集。isIID
: 一个布尔值,指示数据是否独立同分布(IID)。在联邦学习中,这是一个重要的考虑因素,因为非IID数据(即不同客户端的数据分布不同)可能会增加训练的难度。num_clients
: 模拟的客户端总数。beta
: 用于控制数据划分非IID程度的参数(在某些划分方法中可能用到)。beta
值越接近1,数据分布越倾向于非IID;越接近0,则越接近IID。datadir
: 数据集的存储目录。dev
: 设备标识符,指定是在CPU还是GPU上进行计算。这里使用了torch.device("cuda")
,意味着默认使用GPU(如果可用)。- self.clients_set = {}
self.test_data_loader = None
self.clients_distributions = None
self.partition_data()
self.clients_distribution() -
self.partition_data(): 将数据集划分为多个部分,每个部分分配给一个客户端。划分的方式取决于
isIID
参数和beta
参数。如果isIID
为True
,则数据可能以某种方式均匀划分给每个客户端;如果为False
,则可能使用beta
参数来控制划分的非IID程度。 -
self.clients_distribution():计算或确定每个客户端的数据分布。在联邦学习中,了解每个客户端的数据分布对于设计有效的算法和策略非常重要。
-
self.clients_set = {}: 一个字典,可能用于存储每个客户端的实例。每个键可能是一个客户端的标识符,每个值是一个
client
类的实例。 self.test_data_loader
: 可能用于加载测试数据的DataLoader
实例。在联邦学习中,通常有一个全局的测试集来评估模型的性能。self.clients_distributions
: 用于存储每个客户端数据分布的某种表示(可能是概率分布、直方图或其他形式)
def partition_data(): 划分数据
self.clients_set['client{}'.format(i)] = client('client{}'.format(i),
ClientDataset(client_train_data,
client_train_label,
transform_train),
self.dev,
distribution,
nclass)
为每个客户端创建一个 client 类的实例,并将它存储在 self.clients_set 字典中。这个实例包含客户端的名称、一个封装了训练数据和标签的 ClientDataset 实例、设备标识符(self.dev)、类别分布和类别总数。
label_distribution = np.random.dirichlet([self.beta] * n_clients, nclass)
:使用狄利克雷分布生成一个形状为 (n_clients, nclass)
的数组,其中每行代表一个客户端的类别分布。self.beta
是一个参数,控制分布的集中程度(beta
越小,分布越集中;beta
越大,分布越均匀)。
class_idcs = [np.argwhere(train_label == y).flatten() for y in range(nclass)]
client_idcs = [[] for _ in range(n_clients)]
处理和存储与训练数据集的类别分布和客户端划分相关的索引信息。下面是对这两行代码的详细解读:
列表 class_idcs
,其中包含了 nclass
个元素(即数据集中类别的总数)。对于每个类别 y
(从 0 到 nclass-1
),它执行以下操作:
train_label == y
:这是一个布尔数组,其中与类别y
相匹配的标签位置为True
,其他位置为False
。np.argwhere(train_label == y)
:这个函数返回上述布尔数组中所有True
值的索引。注意,np.argwhere
返回的是一个二维数组,即使只有一个索引也会是这样。.flatten()
:这个方法将二维数组转换为一维数组,只包含索引值。
因此,class_idcs
列表中的每个元素都是一个一维数组,包含了属于对应类别的所有样本的索引。
client_idcs
创建一个新的列表 client_idcs ,其中包含 n_clients 个空列表。这里的 _ 是一个常用的占位符,表示在循环中我们不关心当前的迭代变量。
client_idcs
的目的是在后续步骤中存储每个客户端的样本索引。每个空列表都将被填充为包含一组索引,这些索引指向了分配给该客户端的样本。
总结
class_idcs
是一个列表,其中每个元素都是一个一维数组,包含了属于特定类别的所有样本的索引。client_idcs
是一个列表,初始时包含n_clients
个空列表,用于后续存储每个客户端的样本索引。
在后续的代码中,你会看到这些索引如何被用来根据客户端的标签分布(通过狄利克雷分布生成)来划分数据,以便每个客户端获得一个非独立同分布(non-IID)的数据子集。
在一个循环中,为每个客户端分配数据,并创建表示这些客户端的client
对象
def clients_distribution():遍历客户端,收集每个样本分布信息
这段代码定义了一个名为 clients_distribution
的方法,其目的是遍历所有客户端(存储在 self.clients_set
字典中),并收集每个客户端的样本分布信息。这里假设每个客户端对象(即 self.clients_set
字典中的值)都有一个 distribution
属性,该属性表示了该客户端内部数据的某种分布信息(可能是每个类别的样本数量、每个子集的样本数量,或者是其他与分布相关的指标)。
没有改变对象的状态或数据,只读取属性,并将信息汇总到了一个新的字典 distributions
中,然后将这个字典赋值给了 self.clients_distributions
属性。
最后,self.clients_distributions
属性现在包含了所有客户端的分布信息,可以在类的其他部分中使用这个属性来获取这些信息。例如,你可以在类的其他方法中使用 self.clients_distributions
来分析客户端之间的数据分布差异,或者根据这些分布信息来做出某些决策。
from utils import *
from torch.utils.data import DataLoader
from getData import GetDataSet, ClientDataset
class client(object):
def __init__(self, idx, trainDataSet, dev, distribution, nclass):
self.id = idx
self.train_ds = trainDataSet
self.dev = dev
self.train_DataLoader = None
self.local_parameters = None
self.distribution = distribution
self.nclass = nclass
def localUpdate(self, localEpoch, localBatchSize, Net, lossFun, opti, global_parameters):
# 加载当前通信中最新全局参数
# 传入网络模型,并加载global_parameters参数的
Net.load_state_dict(global_parameters, strict=True)
# 载入Client自有数据集
# 加载本地数据
self.train_DataLoader = DataLoader(self.train_ds, batch_size=localBatchSize, shuffle=True)
# 设置迭代次数
for epoch in range(localEpoch):
for data, label in self.train_DataLoader:
# 加载到GPU上
data, label = data.to(self.dev), label.to(self.dev)
# 模型上传入数据
preds = Net(data)
# 计算损失函数
'''
这里应该记录一下模型得损失值 写入到一个txt文件中
'''
loss = lossFun(preds, label.long())
# 反向传播
loss.backward()
# 计算梯度,并更新梯度
opti.step()
# 将梯度归零,初始化梯度
opti.zero_grad()
# 返回当前Client基于自己的数据训练得到的新的模型参数
return Net.state_dict()
def local_val(self):
pass
class ClientsGroup(object):
def __init__(self, dataSetName, isIID, num_clients, beta=0.4, datadir="./data/", dev=torch.device("cuda")):
self.dev = dev
self.data_set_name = dataSetName
self.is_iid = isIID
self.beta = beta
self.datadir = datadir
self.num_of_clients = num_clients
self.clients_set = {}
self.test_data_loader = None
self.clients_distributions = None
self.partition_data()
self.clients_distribution()
def partition_data(self):
dataSet = GetDataSet(self.data_set_name, self.datadir)
nclass = np.max(dataSet.train_label) + 1
# 加载测试数据
test_data = dataSet.test_data
test_label = dataSet.test_label
transform_test = dataSet.transform_test
transform_train = dataSet.transform_train
# 全局测试集
self.test_data_loader = DataLoader(ClientDataset(test_data, test_label, transform_test), batch_size=256,
shuffle=False)
'''
判断是否IID,如果不是,按照dirichlet的beta划分数据
'''
if self.is_iid:
np.random.seed(12)
idxs = np.random.permutation(dataSet.train_data_size)
batch_idxs = np.array_split(idxs, self.num_of_clients)
for i in range(self.num_of_clients):
client_train_data = dataSet.train_data[batch_idxs[i]]
client_train_label = dataSet.train_label[batch_idxs[i]]
distribution = [client_train_label.tolist().count(i) for i in range(nclass)]
# 为每一个clients 设置一个名字 client10
self.clients_set['client{}'.format(i)] = client('client{}'.format(i),
ClientDataset(client_train_data,
client_train_label,
transform_train),
self.dev, distribution, nclass)
else:
n_clients = self.num_of_clients
train_label = dataSet.train_label
np.random.seed(123)
label_distribution = np.random.dirichlet([self.beta] * n_clients, nclass)
class_idcs = [np.argwhere(train_label == y).flatten() for y in range(nclass)]
client_idcs = [[] for _ in range(n_clients)]
for c, fracs in zip(class_idcs, label_distribution):
# np.split按照比例将类别为k的样本划分为了N个子集
# for i, idcs 为遍历第i个client对应样本集合的索引
for i, idcs in enumerate(np.split(c, (np.cumsum(fracs)[:-1] * len(c)).astype(int))):
client_idcs[i] += [idcs]
for i in range(self.num_of_clients):
idcs = client_idcs[i]
distribution = [len(c) for c in idcs]
client_train_data = dataSet.train_data[np.concatenate(idcs)]
client_train_label = dataSet.train_label[np.concatenate(idcs)]
self.clients_set['client{}'.format(i)] = client('client{}'.format(i),
ClientDataset(client_train_data,
client_train_label,
transform_train),
self.dev, distribution, nclass)
def clients_distribution(self):
distributions = {}
for i in range(self.num_of_clients):
distributions['client{}'.format(i)] = self.clients_set['client{}'.format(i)].distribution
self.clients_distributions = distributions