在传统的机器学习中,数据同在一个中心,不会出现什么非独立同分布的问题;然而在联邦学习中,每个客户端(client)都拥有自己的数据集,大家各不相同,所以数据不独立同分布是常态。因此我们在做实验时,需要模拟真实的场景,对一个数据集进行 Non-IID 的划分。

这里参考网上的资料,按 Dirichlet 分布划分 Non-IID 数据集。首先处理常见的非结构化数据集,之后再处理结构化数据集。

文章目录

  • 什么是Non-IID
  • 如何进行划分
    • numpy中的dirichlet函数
    • 函数使用案例
    • 划分代码实现
    • 测试
  • 划分结构化数据集
    • 划分实现
    • 测试
  • 最后的封装
  • 参考

什么是Non-IID

非独立同分布包括以下三种:

  • 不独立但同分布
  • 独立但不同分布
  • 不独立也不同分布

也就是说,除了独立同分布(independent and identically distributed),其余都是 Non-IID

独立没什么好说的,关键在于同分布

举个栗子,对于标准数据集 cifar-10 ,该数据集有 6w 张图片,分为 10 类,每类均为6k张图片。在做传统的图像分类实验中,数据集采用均匀划分的 5w 个作为训练集,1w 个样本作为测试集。我们把训练集和测试集看作两个数据集,他们各自有 10 个类别,每类都占有 1/10 ,也就是他们的标签分布都为 1:1:1……,对于两个数据集的类别数量比相同,这就叫同分布(IID)。

此时我们再想想,为什么多个客户端提供的数据集具有 Non-IID 的性质,因为不同数据集的不同类数量比一般都不同,你提供一个 100:150:200 的数据集,我提供一个 200:301:402的数据集,看起来近似,但分布就是不一样。

如何进行划分

这里先对非结构化的数据集进行划分,之后再处理结构化数据集

非结构化:图像、文本、 视频

结构化:类似于 json 格式的或数据库表那样格式的数据

比较常见的是根据样本的标签分布进行 Non-IID划分

思路如下:

尽量让每个 client 上的样本标签分布不同。我们设有 K 个类别标签, N 个 client,每个类别标签的样本需要按照不同的比例划分在不同的 client 上。我们需要一个类别标签分布的矩阵,其行向量表示类别 k 在不同 client 上的概率分布向量(显然每个行向量的和为1),该随机向量就采样自 Dirichlet 分布。

numpy中的dirichlet函数

def dirichlet(alpha, size=None):

参数:
alpha: 对应分布函数中的参数向量 α ,长度为 k 。
size: 为输出形状大小,因为采出的每个样本是一个随机向量,默认最后一维会自动加上 k ,如果给定形状为 (m,n) ,那么 m×n 个维度为 k 的随机向量会从中抽取。默认为 None,即返回一个一个 k 维的随机向量。

返回:
out: ndarray   采出的样本,大小为 (size,k) 。

这里的 α 越小,得到的差异越大;α 越大,差异越小,也就是越平均。

K 其实就对应着 client 的数量。

函数使用案例

设 α=(10,5,3) (意味着 k=3 ), size=(2,2) ,则采出的样本为 2×2 个维度为 k=3 的随机向量

import numpy as nps = np.random.dirichlet((10, 5, 3), size=(2, 2))
print(s)

因为调用了 random,所以每次打印的结果都是不同的。在实验时应该设置随机数种子,保证相同的数据拆分,即结果的可复现

划分代码实现

首先再来理一理思路,我们的目的是:将每个类别划分为 N 个子集。

  1. 拿到数据集对应标签的所有下标
  2. 分类
  3. 得到每个类别的所有下标
  4. 将每个类别用 dirichlet 函数划分为 N 份
  5. 得到划分后的标签下标
import numpy as np# 设置随机数种子,保证相同的数据拆分,可获得结果的复现
np.random.seed(42)def dirichlet_split_noniid(train_labels, alpha, n_clients):'''参数为 alpha 的 Dirichlet 分布将数据索引划分为 n_clients 个子集'''# 总类别数n_classes = train_labels.max()+1# [alpha]*n_clients 如下:# [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]# 得到 62 * 10 的标签分布矩阵,记录每个 client 占有每个类别的比率label_distribution = np.random.dirichlet([alpha]*n_clients, n_classes)# 记录每个类别对应的样本下标# 返回二维数组class_idcs = [np.argwhere(train_labels==y).flatten()for y in range(n_classes)]# 定义一个空列表作最后的返回值client_idcs = [[] for _ in range(n_clients)]# 记录N个client分别对应样本集合的索引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]client_idcs = [np.concatenate(idcs) for idcs in client_idcs]return client_idcs

有很多方法对我这 AI 小白并不友好。。记录如下:

  • np.argwhere(a > 1):返回数组中大于 1 的下标。
  • zip:将对应的元素打包成一个个元组,然后返回由这些元组组成列表
  • enumerate:将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。
  • np.cumsum:特别抽象的累加

  • np.concatenate:连接数组

测试

EMNIST 数据集上调用该函数进行测试,并进行可视化呈现。

import torch
from torchvision import datasets
import numpy as np
import matplotlib.pyplot as plt
from main import dirichlet_split_noniidtorch.manual_seed(42)
if __name__ == "__main__":# 初始化客户端数、参数αN_CLIENTS = 10DIRICHLET_ALPHA = 1.0train_data = datasets.EMNIST(root=".", split="byclass", download=True, train=True)test_data = datasets.EMNIST(root=".", split="byclass", download=True, train=False)# num_cls 为类别总数num_cls =  len(train_data.classes)# 所有数据对应的标签下标train_labels = np.array(train_data.targets)# 我们让每个client不同label的样本数量不同,以此做到Non-IID划分client_idcs = dirichlet_split_noniid(train_labels, alpha=DIRICHLET_ALPHA, n_clients=N_CLIENTS)# 展示不同client的不同label的数据分布plt.figure(figsize=(20,3))plt.hist([train_labels[idc] for idc in client_idcs], stacked=True,bins=np.arange(min(train_labels)-0.5, max(train_labels) + 1.5, 1),label=["Client {}".format(i) for i in range(N_CLIENTS)], rwidth=0.5)plt.xticks(np.arange(num_cls), train_data.classes)plt.legend()plt.show()

这个图画得也是远超我的水准。。。

打印一堆变量方才看出所以然来。

    print(train_labels)print(len(train_labels))for idc in client_idcs:print(train_labels[idc])print(len(train_labels[idc]))print(np.arange(min(train_labels)-0.5, max(train_labels) + 1.5, 1))

至此,非结构化数据集的划分完毕。

划分结构化数据集

首先得搜集结构化数据集,这里使用 UCI 上的数据集,可参考以下博客:

  • UCI数据集整理(附论文常用数据集)
  • UCI数据集的简单介绍和使用Python保存UCI数据集为.mat文件

因需求原因,选择的是红葡萄酒数据集Wine Quality Data Set

红葡萄酒介绍:Red Wine——红葡萄酒各指标相关性分析

有部分数据集是可以直接通过调用函数载入的,例如:

Data = datasets.load_wine()

这类数据集有很多属性,便于使用,可大部分数据集又是无法直接 load 的,例如红葡萄酒

下载到本地:

划分实现

这里并没有改动主要的划分函数 dirichlet_split_noniid,而是对数据集做了一定的预处理

最后的划分结果会在原有 dataframe 的基础上新增一列 ”client“,表示该行数据分给哪个客户端,

需要使用数据集时进行 group 即可。

import numpy as np# 设置随机数种子,保证相同的数据拆分,可获得结果的复现
np.random.seed(42)def dirichlet_split_noniid(train_labels, alpha, n_clients):'''参数为 alpha 的 Dirichlet 分布将数据索引划分为 n_clients 个子集'''# 总类别数n_classes = train_labels.max()+1# [alpha]*n_clients 如下:# [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]# 得到 62 * 10 的标签分布矩阵,记录每个 client 占有每个类别的多少label_distribution = np.random.dirichlet([alpha]*n_clients, n_classes)print(label_distribution)# 记录每个类别对应的样本下标class_idcs = [np.argwhere(train_labels==y).flatten()for y in range(n_classes)]# 定义变量作最后的返回值client_idcs = [[] for _ in range(n_clients)]# 记录N个client分别对应样本集合的索引for c, fracs in zip(class_idcs, label_distribution):# split_indexs 记录划分时的间断点split_indexs = (np.cumsum(fracs)[:-1] * len(c)).astype(int)# for i, idcs 为遍历第i个client对应样本集合的索引# np.split按照比例将类别为k的样本划分为了N个子集for i, idcs in enumerate(np.split(c,split_indexs)):client_idcs[i] += [idcs]client_idcs = [np.concatenate(idcs) for idcs in client_idcs]return client_idcs

测试

import torch
from torchvision import datasets
import numpy as np
import matplotlib.pyplot as plt
from main import dirichlet_split_noniid
import pandas as pdtorch.manual_seed(42)
if __name__ == "__main__":# 初始化客户端数、参数αN_CLIENTS = 5DIRICHLET_ALPHA = 2.0df = pd.read_csv('winequality-red.csv', sep=';')# 取最后一列,即所有的标签labels = df.iloc[:,-1]# 获取所有标签名称target_name = labels.unique()# 获取所有标签对应的下标target = [np.argwhere(target_name == name).flatten()for name in labels]target = np.concatenate(np.array(target))# target = []# for name in labels:#     for index,item in enumerate(target_name) :#         if name == item:#             target.append(index)# target = np.array(target)# num_cls 为类别总数num_cls =  len(target_name)# 我们让每个client不同label的样本数量不同,以此做到Non-IID划分client_idcs = dirichlet_split_noniid(target, alpha=DIRICHLET_ALPHA, n_clients=N_CLIENTS)# 展示不同client的不同label的数据分布plt.figure(figsize=(14,8))plt.hist([target[idc] for idc in client_idcs], stacked=True,bins=np.arange(min(target)-0.5, max(target) + 1.5, 1),label=["Client {}".format(i) for i in range(N_CLIENTS)], rwidth=0.5)plt.xticks(np.arange(num_cls), target_name)plt.legend()plt.show()df['client'] = -1for i, indexs in enumerate(client_idcs):for index in indexs:df.loc[index, "client"] = iprint(df)

最后的封装

为了方便使用,当然得封装到一个类里,最后还加了一些注释。

这里还顺手加上了独立同分布的划分。

代码如下:

import numpy as np
import matplotlib.pyplot as plt
from pandas import DataFrameclass Partitioner(object):# 设置随机数种子,保证相同的数据拆分,可获得结果的复现np.random.seed(42)def __init__(self, dataset:DataFrame, n_clients: int,split_policy: str = "non-iid",alpha:int = 1.0,show_img: bool = True):'''初始化数据划分的类:param dataset: DataFrame类型的数据集:param n_clients: 客户端数量:param split_policy: 划分策略, 传入字符串 non-iid 或 iid:param alpha: 狄利克雷分布函数的参数:param show_img: 是否展示划分后的可视化图片'''self.dataset = datasetself.n_clients = n_clientsself.split_policy = split_policyself.alpha = alphaself.show_img = show_imgdef partition(self):'''划分实现:return: 划分后的数据集索引'''# 取最后一列,即所有的标签labels = self.dataset.iloc[:, -1]# 获取所有标签名称target_name = labels.unique()# 获取所有标签对应的下标target = [np.argwhere(target_name == name).flatten()for name in labels]target = np.concatenate(np.array(target))# num_cls 为类别总数num_cls = len(target_name)# 我们让每个client不同label的样本数量不同,以此做到Non-IID划分client_idcs = self.split_index(target, split_policy=self.split_policy,alpha=self.alpha, n_clients=self.n_clients)if self.show_img:self.show_split_img(target,client_idcs,num_cls,target_name)return client_idcsdef split_index(self,target, n_clients, split_policy, alpha):'''具体的划分函数:param target: 数据集对应的标签下标:param n_clients::param split_policy::param alpha::return:'''# 记录总类别数n_classes = target.max() + 1# 记录每个类别对应的样本下标class_idcs = [np.argwhere(target == y).flatten()for y in range(n_classes)]if split_policy == "iid":label_distribution = [[1.0 / n_clients for _ in range(n_clients)]for _ in range(n_classes)]elif split_policy == "non-iid":label_distribution = np.random.dirichlet([alpha] * n_clients, n_classes)# 定义变量作最后的返回值client_idcs = [[] for _ in range(n_clients)]# 记录N个client分别对应样本集合的索引for c, fracs in zip(class_idcs, label_distribution):# split_indexs 记录划分时的间断点split_indexs = (np.cumsum(fracs)[:-1] * len(c)).astype(int)# for i, idcs 为遍历第i个client对应样本集合的索引# np.split按照比例将类别为k的样本划分为了N个子集for i, idcs in enumerate(np.split(c, split_indexs)):client_idcs[i] += [idcs]client_idcs = [np.concatenate(idcs) for idcs in client_idcs]return client_idcsdef show_split_img(self,target,client_idcs,num_cls,target_name):# 展示不同client的不同label的数据分布plt.figure(figsize=(14, 8))plt.hist([target[idc] for idc in client_idcs], stacked=True,bins=np.arange(min(target) - 0.5, max(target) + 1.5, 1),label=["Client {}".format(i+1) for i in range(self.n_clients)], rwidth=0.5)plt.xticks(np.arange(num_cls), target_name)plt.legend()plt.show()

测试:

from non_iid4.dataset_util import Partitioner
import pandas as pddf = pd.read_csv('winequality-red.csv', sep=';')# 测试集使用的公共数据集
non_iid_partitioner = Partitioner(df,5,"non-iid")
indexs = non_iid_partitioner.partition()client1_index = list(indexs[0])
client1_data = df.loc[client1_index]print(client1_data)

参考

什么是非独立同分布

狄利克雷分布参考资料:

什么是狄利克雷分布?狄利克雷过程又是什么?

Dirichlet distribution狄利克雷分布

浅谈狄利克雷分布——Dirichlet Distribution

代码实现摘于知乎大佬:联邦学习:按Dirichlet分布划分Non-IID数据集

划分非独立同分布(Non-IID)数据集相关推荐

  1. 非独立同分布数据孤岛的联邦学习:一项实验研究

    关注公众号,发现CV技术之美 本篇分享论文『Federated Learning on Non-IID Data Silos: An Experimental Study』,非独立同分布数据孤岛的联邦 ...

  2. 操龙兵:非独立同分布学习

    原文地址:非独立同分布 AIDL简介 "人工智能前沿讲习班"(AIDL)由中国人工智能学会主办,旨在短时间内集中学习某一领域的基础理论.最新进展和落地方向,并促进产.学.研相关从业 ...

  3. FedIC: 通过校准蒸馏对非独立同分布和长尾数据进行联合学习(ICME 2022)

    ‍ 关注公众号,发现CV技术之美 本篇分享 ICME 2022 论文『FEDIC: Federated Learning on Non-IID and Long-Tailed Data via Cal ...

  4. 【机器学习】独立同分布(IID)数据集

    文章目录 独立同分布(I.I.D.)是什么? 参考链接: 独立同分布(I.I.D.)是什么? 机器学习领域有个很重要的假设:IID独立同分布假设,就是假设训练数据和测试数据是满足相同分布的, 这是通过 ...

  5. 联邦学习中常见的Clients数据Non-IID非独立同分布总结

    联邦学习 写在前面 联合概率分布 联邦学习中客户端数据Non-IID分布的五种类型: 类型1:Feature distribution skew (convariate shift) 类型2:Labe ...

  6. 西瓜书+实战+吴恩达机器学习(一)机器学习基础(数据集划分、分类回归评估指标)

    文章目录 0. 前言 1. 数据集划分方法 2. 模型性能度量 2.1. 回归评估指标 2.2. 分类评估指标 3. 非均等代价 如果这篇文章对你有一点小小的帮助,请给个关注,点个赞喔,我会非常开心的 ...

  7. 通过无数据知识蒸馏优化全局模型,实现非iid联邦学习

    现有的大多数方法仅通过限制客户端本地模型更新来解决异构挑战,而忽略了直接全局模型聚合所导致的性能下降.相反,我们提出了一种无数据的知识蒸馏方法来微调服务器中的全局模型(FedFTG),这缓解了直接模型 ...

  8. 机器学习 数据集划分 训练集 验证集 测试集

    版权声明:本文为博主原创文章,转载请注明转自 Scofield's blog[http://blog.csdn.net/scotfield_msn] https://blog.csdn.net/Sco ...

  9. 【联邦学习】IID与非IID数据

    文章目录 一.引言 二.什么是IID.非IID 三.联邦学习下的IID与非IID 参考链接 一.引言 在联邦学习系统中,来自不同参与方的数据可能会导致出现 非独立同分布 的情况.并且不同的参与方可能有 ...

最新文章

  1. java 随机数 平均值_从平均值,变异系数生成对数正态分布随机数
  2. 集成测试_渐增与非渐增模式优缺点
  3. MyBatis 三种批量插入方式的比较,我推荐第3个!
  4. 回归模型和时间序列模型中的MAPE指标是什么?MAPE指标解读、MAPE越大越好还是越小越好、使用MAPE指标的注意事项
  5. 每天一个linux命令(46):vmstat命令
  6. 基于形态学操作提取水平和垂直线条(五线谱中音符和乐谱线的分离)
  7. MySQL学习笔记02-数据库概述及MySQL简介 .
  8. poj-1659-Frogs Neighborhood-(图论-是否可图)
  9. Android语音录入与邮件发送
  10. 【Liunx】Linux 系统目录结构
  11. mysql 三表inner join_MySql的join(连接)查询 (三表 left join 写法)
  12. 最简单的三层实例【插入据
  13. Android测试点和测试工具介绍
  14. windows7系统iis安装不了应该怎么办
  15. 怎么安装google nik collection 1.2.11.win插件
  16. java基础-宇宙第一YWM:数组数算题目记录
  17. Maven中的LastUpdated文件生成原因
  18. IIS7用FastCGI运行PHP配置
  19. C语言中的与、或、非
  20. AST反混淆插件|如何还原Array对象里的元素

热门文章

  1. 怎么提取抖音里的音乐制作手机铃声
  2. 计算机专业英语电池,电池分为哪几种?英文缩写?
  3. 30岁转行做程序员是一种怎样的体验?
  4. 【异常分析】Springboot中使用测试类报空指针(at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs)
  5. getch()和getche()
  6. 超实用的Excel自动排序小技巧,原来这么简单,早点知道就好了!
  7. Educoder Python 计算思维训练——文件操作与异常处理
  8. Linux内存之Slab与slabtop命令
  9. mac上用Homebrew安装redis并启动
  10. python随机库函数_python标准库中的随机分布函数