安全聚合#

以下代码仅作为演示用,请勿直接在生产环境使用。

推荐使用 jupyter 运行教程里的代码。

安全聚合可以表述为:多个参数方在不暴露各自私有数据的前提下合作完成聚合值计算(比如求和)。

安全聚合是联邦学习中的重要概念。学术界对此已经有较多研究。隐语在水平联邦学习(梯度/权重聚合)、数据处理(比如数据探查、预处理)等方面使用了安全聚合。

下面将介绍隐语使用的安全聚合方案。

数据准备#

初始化SecretFlow。

[ ]:
import secretflow as sf

# In case you have a running SecretFlow runtime already.
sf.shutdown()

sf.init(['alice', 'bob'], address='local')

准备测试数据。

[2]:
import numpy as np

arr0, arr1 = np.random.rand(2, 3), np.random.rand(2, 3)
print('arr0:\n', arr0, '\narr1:\n', arr1)

print('Sum:\n', np.sum([arr0, arr1], axis=0))
print('Average:\n', np.average([arr0, arr1], axis=0))
print('Min:\n', np.min([arr0, arr1], axis=0))
print('Max:\n', np.max([arr0, arr1], axis=0))
arr0:
 [[0.53867365 0.69040348 0.42628929]
 [0.76128941 0.5444343  0.7680543 ]]
arr1:
 [[0.74303296 0.7274792  0.47244091]
 [0.88295957 0.80091356 0.82681861]]
Sum:
 [[1.28170662 1.41788268 0.8987302 ]
 [1.64424898 1.34534786 1.59487291]]
Average:
 [[0.64085331 0.70894134 0.4493651 ]
 [0.82212449 0.67267393 0.79743646]]
Min:
 [[0.53867365 0.69040348 0.42628929]
 [0.76128941 0.5444343  0.7680543 ]]
Max:
 [[0.74303296 0.7274792  0.47244091]
 [0.88295957 0.80091356 0.82681861]]

创建alice和bob两个参与方。

[3]:
alice, bob = sf.PYU('alice'), sf.PYU('bob')

聚合操作#

隐语提供了多种 Aggregator 供用户选择,每种 Aggregator 均提供了sum(求和)和average(求平均)方法。

基于SPU的安全聚合。#

SPU 是隐语中的一种安群设备,其基本原理是 MPC 。隐语基于SPU实现了安全聚合,下面将介绍如何使用。

[4]:
# Create an spu device.
spu = sf.SPU(sf.utils.testing.cluster_def(['alice', 'bob']))

# Create an aggregator instance using this spu.
spu_aggr = sf.security.aggregation.SPUAggregator(spu)
[5]:
# Simulate that alice and bob hold data respectively
a = alice(lambda: arr0)()
b = bob(lambda: arr1)()
[6]:
# Sum the data.
sf.reveal(spu_aggr.sum([a, b], axis=0))
[6]:
array([[1.2817066 , 1.4178827 , 0.89873016],
       [1.644249  , 1.3453479 , 1.594873  ]], dtype=float32)
[7]:
# Average the data.
sf.reveal(spu_aggr.average([a, b], axis=0))
[7]:
array([[0.6408533 , 0.70894134, 0.44936508],
       [0.8221245 , 0.67267394, 0.7974364 ]], dtype=float32)

Masking with One-Time Pads#

Masking with One-Time Pads 的原理描述如下:参与方两两之间分别协商掩码值,然后使用掩码值来掩藏各自的私有数据,每个参与方输出

\[y_u = x_u + \sum_{u < v}s_{u,v} - \sum_{u > v}s_{u,v}\ mod\ R\]

聚合之后掩码值正好互相抵消,从而可以得到正确的结果。

\[\sum y = \sum x\]
举个例子,假设参与方Alice、Bob、Carol分别持有 \(x_1, x_2, x_3\) ,分别协商出掩码值 \(s_{a,b}, s_{a,c}, s_{b,c}\),然后各自输出 \(y_1 = x_1 + s_{a,b} + s_{a,c}\)
\(y_2 = x_2 - s_{a,b} + s_{b,c}\)
\(y_3 = x_3 - s_{a,c} - s_{b,c}\)
容易得到
\[y_1 + y_2 + y_3 = x_1 + s_{a,b} + s_{a,c} + x_2 - s_{a,b} + s_{b,c} + x_3 - s_{a,c} - s_{b,c} = x_1 + x_2 + x_3\]

Masking with One-Time Pads 基于半诚实假设且不支持掉线,更多信息可以参考 Practical Secure Aggregation for Privacy-Preserving Machine Learning

警告: SecureAggregator使用了 numpy.random.PCG64。对于PCG是否是CSPRNG有很多讨论(比如 https://crypto.stackexchange.com/questions/77101/is-the-pcg-prng-a-csprng-or-why-not),我们倾向于保守看待此类讨论,因此我们建议用户在真正工业场景中使用标准的CSPRNG。

[8]:
# Create a secure aggregator instance with alice and bob,
# where alice is responsible for performing aggregate computing operations.
secure_aggr = sf.security.aggregation.SecureAggregator(device=alice, participants=[alice, bob])
[9]:
# Sum the data.
sf.reveal(secure_aggr.sum([a, b], axis=0))
[9]:
array([[1.28170395, 1.41788101, 0.89872742],
       [1.64424515, 1.34534454, 1.59486771]])
[10]:
# Average the data.
sf.reveal(secure_aggr.average([a, b], axis=0))
[10]:
array([[0.64085197, 0.70894051, 0.44936371],
       [0.82212257, 0.67267227, 0.79743385]])

明文聚合(仅用作测试,请勿在生产中使用)#

PlainAggregator仅用作测试,请勿在生产中使用。

为了方便本地模拟,隐语也提供了明文聚合方式。

[12]:
# Create a plaintext aggregator instance and alice is responsible for performing aggregation.
plain_aggr = sf.security.aggregation.PlainAggregator(alice)
[13]:
# Sum the data.
sf.reveal(plain_aggr.sum([a, b], axis=0))
[13]:
array([[1.2817066 , 1.4178827 , 0.89873016],
       [1.644249  , 1.3453479 , 1.594873  ]], dtype=float32)
[14]:
# Average the data.
sf.reveal(plain_aggr.average([a, b], axis=0))
[14]:
array([[0.6408533 , 0.70894134, 0.44936508],
       [0.8221245 , 0.67267394, 0.7974365 ]], dtype=float32)

比较#

隐语还提供了多种 Comparator 用来进行安全比较,比如求最大最小值。举个例子,比如在水平联邦场景下,数据处理时全局的极值会通过安全比较来获得,不会暴露参与方的私有信息。

基于SPU的安全比较#

隐语基于SPU实现了安全比较,下面将展示如何使用。

[15]:
# Create an spu comparator instance.
spu_com = sf.security.compare.SPUComparator(spu)
[16]:
# Get the minimum.
sf.reveal(spu_com.min([a, b], axis=0))
[16]:
array([[0.53867364, 0.69040346, 0.4262893 ],
       [0.7612894 , 0.5444343 , 0.7680543 ]], dtype=float32)
[18]:
# Get the maximum.
sf.reveal(spu_com.max([a, b], axis=0))
[18]:
array([[0.743033  , 0.7274792 , 0.4724409 ],
       [0.88295954, 0.8009136 , 0.8268186 ]], dtype=float32)

明文比较(仅作为测试用,请勿在生产中使用)#

PlainComparator仅作为测试用,请勿在生产中使用。

为了方便本地模拟,隐语也提供了明文比较方式。

[19]:
# Create a plaintext comparator instance and alice is responsible for performing the comparison.
plain_com = sf.security.compare.PlainComparator(alice)
[21]:
# Get the minimum.
sf.reveal(plain_com.min([a, b], axis=0))
[21]:
array([[0.53867364, 0.69040346, 0.4262893 ],
       [0.7612894 , 0.5444343 , 0.7680543 ]], dtype=float32)
[22]:
# Get the maximum.
sf.reveal(plain_com.max([a, b], axis=0))
[22]:
array([[0.743033  , 0.7274792 , 0.4724409 ],
       [0.88295954, 0.8009136 , 0.8268186 ]], dtype=float32)

收尾#

[23]:
sf.shutdown()

总结#

本文讲述了隐语中的安全局和方案。隐语提供了多种安全聚合方法,用户可以根据需要自行选择合适的方案。其中明文聚合和明文比较仅作为测试目的,请勿在生产中使用。