关于联邦学习构造Non-IID数据集的记录

原文:个人主页原文

简单来说,Non-IID就是指每个设备中的数据分布不能代表全局数据分布。本文简单记录一下自己在研究联邦学习过程中对Non-IID数据的思考和处理

Dateset Shift(数据集偏移)

联邦学习中客户端之间数据的Non-IID分布,和在做机器学习任务时可能遇到的训练集与测试集分布不一致是一个道理

训练集和测试集分布不一致被称作数据集偏移(Dataset Shift),有3种类型:

  1. 协变量偏移(Covariate Shift): 独立变量的偏移,指训练集和测试集的输入服从不同分布,但背后是服从同一个函数关系,即:
    P t r a i n ( y ∣ x ) = P t e s t ( y ∣ x ) P t r a i n ( x ) ≠ P t e s t ( x ) P_{train}(y|x) = P_{test}(y|x)\\ P_{train}(x) \neq P_{test}(x) Ptrain(yx)=Ptest(yx)Ptrain(x)=Ptest(x)

  2. 先验概率偏移(Prior Probability Shift): 目标变量的偏移,即:
    P t r a i n ( x ∣ y ) = P t e s t ( x ∣ y ) P t r a i n ( y ) ≠ P t e s t ( y ) P_{train}(x|y) = P_{test}(x|y)\\ P_{train}(y) \neq P_{test}(y) Ptrain(xy)=Ptest(xy)Ptrain(y)=Ptest(y)

  3. 概念偏移(Concept Shift): 独立变量和目标变量之间关系的偏移,即:
    P t r a i n ( y ∣ x ) ≠ P t e s t ( y ∣ x )   a n d   P t r a i n ( x ) = P t e s t ( x )   i n   X → Y   p r o b l e m s P t r a i n ( x ∣ y ) ≠ P t e s t ( x ∣ y )   a n d   P t r a i n ( y ) = P t e s t ( y )   i n   Y → X   p r o b l e m s P_{train}(y|x) \neq P_{test}(y|x)\ and\ P_{train}(x) = P_{test}(x)\ in\ X\rightarrow Y\ problems \\ P_{train}(x|y) \neq P_{test}(x|y)\ and\ P_{train}(y) = P_{test}(y)\ in\ Y\rightarrow X\ problems Ptrain(yx)=Ptest(yx) and Ptrain(x)=Ptest(x) in XY problemsPtrain(xy)=Ptest(xy) and Ptrain(y)=Ptest(y) in YX problems

联邦学习中客户端数据Non-IID分布的五种类型

  1. Feature distribution skew (convariate shift)

    与数据集偏移中的协变量偏移同理

    以MNIST数据集为例,不同的人写同一个数字,写法不同(即不同客户端的 P i ( x ) P_i(x) Pi(x)分布不同),但是不同客户端用这个特征x预测得到该标签y的概率是相近的,即 P ( y ∣ x ) P(y|x) P(yx)分布相同

  2. Label distribution skew (prior probability shift)

    与数据集偏移中的先验概率偏移同理

    以MNIST数据集为例,不同的客户端内,各个标签所占的比例不是平均的,如A有50%的标签1,B有50%的标签2(即不同客户端的 P i ( y ) P_i(y) Pi(y)分布不同),但是当标签给定时,A和B中对应的特征都大概率相似,即 P ( x ∣ y ) P(x|y) P(xy)分布相同

  3. Same label, different features (concept shift)

    与数据集偏移中的概念偏移同理, P t r a i n ( x ∣ y ) ≠ P t e s t ( x ∣ y )   a n d   P t r a i n ( y ) = P t e s t ( y )   P_{train}(x|y) \neq P_{test}(x|y)\ and\ P_{train}(y) = P_{test}(y)\ Ptrain(xy)=Ptest(xy) and Ptrain(y)=Ptest(y) 

    通俗理解:同样是车(标签y),迈凯伦和花冠完全是两回事(特征x

  4. Same features, different label (concept shift)

    与数据集偏移中的概念偏移同理,

    P t r a i n ( y ∣ x ) ≠ P t e s t ( y ∣ x )   a n d   P t r a i n ( x ) = P t e s t ( x ) P_{train}(y|x) \neq P_{test}(y|x)\ and\ P_{train}(x) = P_{test}(x) Ptrain(yx)=Ptest(yx) and Ptrain(x)=Ptest(x)

    通俗理解:同样是要买交通工具(特征x),A想买跑车,B想买机车(标签y

  5. Quantity skew or unbalancedness

    即不同客户端持有的数据量差异较大

实践中遇到的具体实现方式

A. 第二类Non-IID的第一种分配方式

​ 以MNIST为例,共10个客户端。分配后的数据表示方式:建立一个字典,对于每个客户ii作为键,数据的索引存在一个列表中作为值。如:

{
	0: [111, 333, 555]
	1: [222, 444, 666]
}

​ 数据分配思路:根据客户ID的奇偶性分为两类(当然也可以分为更多类,此处以两类为例)。同时将数据集根据标签分类,奇数、偶数各为一类。奇数数据在0~4客户端中平均分配,偶数数据在5~9客户端中平均分配。

​ 当然,本例只是分为了2组,实际上也可以分为5组、10组。

​ 代码及注释如下:

def mnist_noniid(dataset, num_users):
    """
    Sample non-I.I.D client data from MNIST dataset
    :param dataset: Dataset
    :param num_users: Number of clients
    :return:
    """
    dict_users = {i: np.array([], dtype='int64') for i in range(num_users)}	# 初始化字典
    idxs = np.arange(len(dataset))						# 所有数据的索引
    labels = dataset.train_labels.numpy()					# 所有数据的标签

    # sort labels
    idxs_labels = np.vstack((idxs, labels))			# 将索引和标签对应起来
    idxs_labels = idxs_labels[:, idxs_labels[1, :].argsort()]	# 根据第2维的数据进行排序
    tmp = []
    for i in range(len(idxs_labels[0])):
        if idxs_labels[1, i] % 2 != 0:		# 将标签为奇数的数据分到一组去
            tmp.append(idxs_labels[0, i])
    idxs = idxs_labels[0, :]			# 取出第1维的索引
    group1 = set(tmp)
    group2 = set(set(idxs) - group1)		# 剩下的为标签为偶数的数据,分到另一组
    group1 = list(group1)
    group2 = list(group2)
    shard1 = len(group1) // (num_users // 2)	# 每一组中每个客户端应该持有多少个数据
    shard2 = len(group2) // (num_users // 2)

    # divide and assign
    for i in range(num_users):
        if i < (num_users // 2):	# 0~4客户端,在奇数组中随机取出shard1个数据
            rand_set = set(np.random.choice(group1, shard1, replace=False))
            group1 = list(set(group1) - rand_set)
            dict_users[i] = rand_set
        else:				# 5~9客户端,在偶数组中随机取出shard2个数据
            rand_set = set(np.random.choice(group2, shard2, replace=False))
            group2 = list(set(group2) - rand_set)
            dict_users[i] = rand_set
    if dict_users == {}:
        return "Error"
    return dict_users

B. 第二类Non-IID的第二种分配方式

​ 数据分配思路:将排序后的数据分为2 * num_users个碎片,每个客户随机取2个碎片,那么该客户拥有的数据就只包含这2个碎片里的标签(比如说60000个数据分成200个碎片,每个碎片300个数据,假设某个客户取到了前面的600个数据,那么他取到的数据含有的标签就只可能包含0,1,2,但是不可能包含9),从而构成了Non-IID

​ 代码及注释如下:

def mnist_noniid(dataset, num_users):
    """
    Sample non-I.I.D client data from MNIST dataset
    :param dataset: Dataset
    :param num_users: Number of clients
    :return:
    """
    num_shards, num_imgs = num_users * 2, int(len(dataset) / (num_users * 2))	# 计算碎片数、每个碎片包含的数据条数
    idx_shard = [i for i in range(num_shards)]					# 每个碎片的索引
    dict_users = {i: np.array([], dtype='int64') for i in range(num_users)}	# 初始化字典
    idxs = np.arange(len(dataset))						# 所有数据的索引
    labels = dataset.train_labels.numpy()					# 所有数据的标签

    # sort labels
    idxs_labels = np.vstack((idxs, labels))			# 将索引和标签对应起来
    idxs_labels = idxs_labels[:, idxs_labels[1, :].argsort()]	# 根据第2维的数据进行排序
    idxs = idxs_labels[0, :]					# 取出第1维的索引

    # divide and assign
    for i in range(num_users):
        rand_set = set(np.random.choice(idx_shard, 2, replace=False))	# 随机取2个碎片
        idx_shard = list(set(idx_shard) - rand_set)
        for rand in rand_set:
        dict_users[i] = np.concatenate((dict_users[i], idxs[rand * num_imgs:(rand + 1) * num_imgs]), axis=0)
    if dict_users == {}:
        return "Error"
    return dict_users

附:Python中[m, n]的用法

x[m, n]是通过numpy引用数组或矩阵中的某一段数据集的一种写法。m表示第几维,n表示第几个数据。

在一个2维矩阵中,通俗来理解,x[m, n]就是第m行第n列的数据,例如:

x = np.array([
    [1, 2, 3]
    [4, 5, 6]
])

那么x[1, 2]得到的数据就是6

其他常用的用法:

x[n, :]		# 取第n维的全部数据
x[:, n]		# 在全部维中取第n个数据
x[:, m:n]	# 在全部维中取第m~(n-1)个数据

参考

https://blog.csdn.net/qq_43827595/article/details/120661931

https://blog.csdn.net/u014159143/article/details/80307717

https://zhuanlan.zhihu.com/p/263959892

  • 14
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值