“云-边"隐私保护

Privacy‐preserving federated learning based on multi‐key homomorphic encryption

论文主要解决:边缘服务器收到来自端设备的数据后模型训练的隐私保护问题。边缘服务器之间进行联邦学习(FedAvg: 每轮选择一定比例边缘服务器使用SGD进行模型训练,将更新的梯度发给云服务器,云服务器对各更新梯度求加权平均获得全局新梯度),该文献中的方案通过加法同态加密保障各个边缘服务器的梯度隐私,并能在诚实好奇假设下抵御一定的云服务器与边缘服务器合谋攻击。



“端-边"隐私保护

端到边缘服务器不能通过简单的加解密——不符合隐私保护的要求:边缘服务器可以获取传感器发送的原始数据。
解决方向1:模型分割训练
参考文献:Privacy-preserving model training architecture for intelligent edge computing
方案可行性不高且题目要求端设备没有训练模型的能力。
解决方向2:原始数据添加差分噪声
解决方向3: leveled同态(LHF)

在密文下进行模型训练——模型输入和输出都是密文的,训练后模型的参数是明文的。但是这又存在这参数泄露攻击,不过应该有相应的解决方法。(没有梯度泄露攻击) 收敛后参数和在明文下训练会收敛到同一处,但是由于loss是密文的,即loss关于参数的分布和明文下是不一样的,梯度也是密文的,使用梯度泄露攻击还原出的也是加密后的输入。

基于区块链和联邦学习的边缘计算隐私保护方法

利用区块链赋予边缘计算防篡改和抗单点故障攻击等特性,并在共识协议中融入梯度验证和激励机制,鼓励更多的本地设备诚实地向联邦学习贡献算力和数据

联邦学习能够有效避免 本地设备向边缘节点直接传输数据造成的隐私泄露问题。而区块链作为一种分布式账本,以透明的方式记录数据处理过程,且具有去中心化、可追溯以及难以篡改等一系列特性,可以满足边缘计算的可审计性需求。它与联邦学习相结合,可以代替中央服务器执行模型参数聚合,避免单点故障攻击问题。

综上所述,当前在边缘计算中应用区块链和联邦学习方法存在以下几个问题。

  1. 区块链账本的公开透明性虽然保证了联邦训练的可审计性,但是其以明文形式存储的模型参数会被攻击者利用推测本地设备的数据隐私。
  2. 本地设备的数据集中一旦存在投毒样本就会威胁联邦学习的正确性。
  3. 本地设备的资源有限性要求区块链与联邦学习结合后的效率需要进一步提高。

共同迭代训练同一个深度学习模型。在每轮迭代中,设备将本地训练得到的模型更新上传给区块链,然后由区块链完成模型聚合和共识,得到的新区块由设备下载以更新本地模型,接着进行下一轮训练。

区块链

在区块链中,所有参与节点都可以进行事务的验证和转发,并通过共识算法维护全网一致的分类账,账本中的每个区块记录一系列事务和前一个区块的散列值,从而将当前区块链接到前一个区块。

共识机制:散列哈希值。
以太坊提出权益证明(PoS, proof of stake),节点利用持币数以及持有的时间来竞争生成新区块的权利
委托权益证明,由节点投票选举出特定数目的代理节点负责区块的生成和验证,因此在牺牲部分去中心化特性的情况下加快了区块的确认速度。
拜占庭容错算法(BFT, byzantine fault tolerance)来源于拜占庭将军问题,是考虑在有恶意节点的情况下达成共识。它要求所有节点之间两两通信,因此节点数量不能太多,可扩展性较弱。

区块链中“可扩展性、安全性和去中心化”的三角难题。其中,

  1. 可扩展性:Algorand 采用可验证随机函数(VRF, verifiable random function)选出若干个验证者,无论网络中
    有多少用户,每生成一个新区块只需要在少数验证者上进行验证,具有较高的吞吐量(TPS, transactions per second)。
  2. 安全性:只有当区块生产者和验证者确定自己被选中并广播相应的证明信息时才会被披露,攻击者无法提前预测,即使发起攻击也无法阻止新区块在网络中传播。
  3. 去中心化:Algorand在每一轮中都重新随机选取区块生产者和验证者,具有较好的去中心化性。

系统架构

验证和激励机制

由于本地设备收集的数据中可能包含用户的隐私信息,且训练模型需要消耗计算资源,因此部分设备不愿意参与联邦训练,甚至会出现部分恶意的设备上传虚假参数误导联邦训练等为了吸引更多的设备参与训练并诚实地执行计算任务,本文利用Multi-KRUM 算法[27]来检测中毒攻击,并根据区块链的特点设计了激励机制。

如训练流程步骤 3~步骤 4 所述,当矿工收到其关联设备上传的数据后,首先验证签名的合法性来确保数据传输过程中不被篡改。然后判断本地运算时间 是否与该设备的本地数据集大小 成正比,以验证梯度的可靠性,并将可靠的梯度放入事务池中。接着采用可验证随机函数(VRF, verifiable random function)[14]从矿工中选出验证委员会,通过 Multi-KRUM 算法过滤事务池中可能由中毒攻击产生的恶意更新。主要步骤如下。

共识协议

本文将矿工设定为工业物联网中的边缘服务器、移动通信网中的基站、车联网中的路边单元等,他们在提供区块链服务时需要消耗一定的计算、存储、通信等开销,因此,为了维持矿工持续性提供区块链服务的积极性,本文在原有Algorand协议的基础上增加了相应的代币奖励机制来激励矿工维护区块链。协议主要包含 3 个步骤。

Algorand协议
GILAD Y, HEMO R, MICALI S, et al. Algorand: scaling Byzantine
agreements for cryptocurrencies[C]//Proceedings of the 26th Symposium on Operating Systems Principles. New York: ACM Press, 2017:
51-68

梯度泄露攻击

梯度泄露攻击
当我们在优化伪梯度让其接近原始梯度的过程中,伪数据也会逐渐接近原始的真实训练数据。
但其实FedAvg就不是传梯度,而是传参数,虽然没有梯度泄露攻击,但是还是可能会泄露分类代表、成员属性等信息,所以传参数也要保护隐私

为了执行攻击,我们首先随机生成一对伪输入和标签,然后执行通常的前向和反向传播。在从伪数据推导出伪梯度后,不像典型训练中那样优化模型权重,而是优化伪输入和标签,以最小化伪梯度和真实梯度之间的距离,通过匹配梯度使虚拟数据接近原始的数据。当整个优化过程完成后,私有的数据(包括样本和标签)就会被恢复。

我们知道分布式训练有两种,如下图所示,分别是中心式分布训练和去中心化分布式训练。

在两种方案中,每个节点首先进行计算,更新其本地权重,然后向其他节点发送梯度。对于中心式训练,梯度首先被聚合,然后返回到每个节点。对于去中心化分布式训练,梯度在相邻节点之间交换。使用本文提出的攻击方案,对于前者而言,参数服务器虽然不存储任何训练数据,但是就可以窃取所有参与方的本地训练数据,而对于后者而言,任何参与方都可以窃取与其交互梯度的参与方的训练数据,所以都是不安全的。

设在每一步 t\mathrm{t}t ,每个节点iii会从其本地数据集采样一个minibatch,来计算梯度,如下所示
∇Wt,i=∂ℓ(F(xt,i,Wt),yt,i)∂Wt\nabla W_{t, i}=\frac{\partial \ell\left(F\left(\mathbf{x}_{t, i}, W_{t}\right), \mathbf{y}_{t, i}\right)}{\partial W_{t}} ∇Wt,i​=∂Wt​∂ℓ(F(xt,i​,Wt​),yt,i​)​
这些梯度会在 NNN 个服务器上被平均,然后用来更新权重
∇Wt‾=1N∑jN∇Wt,j;Wt+1=Wt−η∇Wt‾\overline{\nabla W_{t}}=\frac{1}{N} \sum_{j}^{N} \nabla W_{t, j} ; \quad W_{t+1}=W_{t}-\eta \overline{\nabla W_{t}} ∇Wt​​=N1​j∑N​∇Wt,j​;Wt+1​=Wt​−η∇Wt​​
给定从其他参与方 k\mathrm{k}k 获得的梯度∇Wt,k\nabla W_{t, k}∇Wt,k​。我们的目标是窃取参与方k\mathrm{k}k 的训练数据(Xt,k,yt,k)\left(\mathbf{X}_{t, k}, \mathbf{y}_{t, k}\right)(Xt,k​,yt,k​)
为了从梯度中恢复出数据,我们首先随机初始化一对伪输入 x′x'x′ 和标签 y′y'y′ 。然后将其输入模型并获取伪梯度∇W′=∂ℓ(F(x′,W),y′)∂W\nabla W^{\prime}=\frac{\partial \ell\left(F\left(\mathbf{x}^{\prime}, W\right), \mathbf{y}^{\prime}\right)}{\partial W}∇W′=∂W∂ℓ(F(x′,W),y′)​
当我们在优化梯度让其接近原始梯度的过程中,伪数据也会逐渐接近原始的真实训练数据。
x′∗,y′∗=arg⁡min⁡x′,y′∥∇W′−∇W∥2=arg⁡min⁡x′,y′∥∂ℓ(F(x′,W),y′)∂W−∇W∥2\mathbf{x}^{\prime^{*}}, \mathbf{y}^{\prime *}=\underset{\mathbf{x}^{\prime}, \mathbf{y}^{\prime}}{\arg \min }\left\|\nabla W^{\prime}-\nabla W\right\|^{2}=\underset{\mathbf{x}^{\prime}, \mathbf{y}^{\prime}}{\arg \min }\left\|\frac{\partial \ell\left(F\left(\mathbf{x}^{\prime}, W\right), \mathbf{y}^{\prime}\right)}{\partial W}-\nabla W\right\|^{2}x′∗,y′∗=x′,y′argmin​∥∇W′−∇W∥2=x′,y′argmin​​∂W∂ℓ(F(x′,W),y′)​−∇W​2
上式中的 ∥∇w′−∇w∥2\| \nabla w^{\prime}-\nabla w \| 2∥∇w′−∇w∥2 是关于伪输入 x′x^{\prime}x′ 和 y′y^{\prime}y′ 可微的,所以可以用标准的基于梯度的方法进行优化
整个形式化的攻击流程就是这么简单,我们可以来看看示意图

图中,需要更新的变量被粗体边框标记。正常参与方计算∇W,利用其私有训练数据更新参数,攻击者则更新其伪输入和标签,以最小化梯度距离。当优化完成时,攻击者可以从正常参与方那里窃取训练数据。

伪码表示如下

和其他技术比较

DP: However, the notion of differential privacy is not useful in the inference phase since at this stage, we are interested in examining a single record.
同态加密是已经有了一个模型了,如神经网络。训练这种模型的问题有时被称为隐私保护数据挖掘。在保护隐私的同时进行培训的一个可能的解决方案是差分隐私

HE

Homomorphic encryption was originally proposed by Rivest et al. (1978) as a way to encrypt data such that certain operations can be performed on it without decrypting it first. 是一种保持结构的转变。

leveled homomorphic encryption

In other words, this cryptosystem allows to compute polynomial functions of a fixed maximal degree on the encrypted data. High degree polynomial computation requires the use of large parameters in the scheme, which results in larger encrypted messages and slower computation times. Hence, a primary task in making practical use of this system is to present the desired computation as a low degree polynomial.

One line of criticism against homomorphic encryption is its inefficiency, which is commonly thought to make it impractical for nearly all applications. However, combining together techniques from cryptography, machine learning and software engineering, we show that CryptoNets may
be efficient and accurate enough for real world applications.
能够在节点处计算的:

  1. 加权和(卷积层):将其下方层的值向量乘以权值向量,然后将结果求和。在推理过程中,权重是固定的。这个函数本质上是权重向量和供给层值向量的点积
  2. 最大池值:计算进料层的某些组件的最大值。
  3. 平均池:计算进料层的某些组成部分的平均值。
  4. Sigmoid:
  5. Rectified Linear: Take the value of one of the nodes in the feeding layer and compute the function z→max(0,z)z→max (0, z)z→max(0,z).

使用固定精度的实数,通过适当的缩放将它们转换为整数(由于加密操作只支持整数),然后在模ppp下运算。
最大池化层并不是多项式的形式,但是可以把取最大值通过一个近似的多项式表示

Large overlap of features of the two data sets Horizontal FML
Large overlap of sample IDs (users)of the two data sets Vertical FML

对于一个可清晰描述的论断,一个证明方(Prover)能通过与验证方(Verifier)的交互使其确信该论断是正确的,但除此之外验证方一无所知。所谓的零知识,就是指验证方除了对论断判断的结果(错或对)之外,无法获取任何额外信息。
FedAvg:

SGD在所有数据上进行,因为要评估准确率?
NIID问题解决

  • 我们看到单点训练的模型效果(蓝色条)明显要低于联邦训练 的效果(绿色条和红色条),这也说明了仅仅通过单个客户端的数据,不能够 很好的学习到数据的全局分布特性,模型的泛化能力较差。
  • 此外,对于每一轮 参与联邦训练的客户端数目(k 值)不同,其性能也会有一定的差别,k
    值越大,每一轮参与训练的客户端数目越多,其性能也会越好,但每一轮的完成时间也会相对较长。

Construction of PEFL

  1. System Setup:
    可信密钥生成中心(KGC)为云平台(CP)生成LHE的一对非对称密钥(pkc,skc)(pk_c,sk_c)(pkc​,skc​),其中私钥skcsk_cskc​仅由CP保存。所有授权用户持有KGC生成的同一对非对称密钥(pkx,skx)(pk_x,sk_x)(pkx​,skx​)。开始时,服务提供者(SP)随机初始化全局模型参数ωinit\boldsymbol{\omega _{init}}ωinit​。
  2. Securely Training:
    包括两个阶段:局部训练阶段和鲁棒聚合阶段。
    a) Local training phase:
  • STEP 1:在ttt次迭代时,x∈[1,m]x\in [1,m]x∈[1,m],用户UxtU_x^tUxt​接收到用pkxpk_xpkx​加密的全局模型[[ωt]]pkx[[\boldsymbol{\omega ^{t}}]]_{pk_x}[[ωt]]pkx​​,客户端解密,UxtU_x^tUxt​训练并获得局部梯度向量Gxt\boldsymbol{G_{x}^t}Gxt​。
    STEP 2:客户端使用带有动量[32][33]的SGD来平滑当前梯度,即使用指数衰减因子γ(0<γ<1)\gamma (0<\gamma <1)γ(0<γ<1)来增加一系列之前的梯度:(动量不仅可以加速收敛,还可以减小方差)
    Gx≜∑ℓ∈[0,t]γt−ℓGxℓ\boldsymbol{G_{x}} \triangleq \sum_{\ell \in[0, t]} \gamma^{t-\ell} \boldsymbol{G_{x}^{\ell}}Gx​≜∑ℓ∈[0,t]​γt−ℓGxℓ​
    【这与SAGA的想法是一样的,都是为了减少随机梯度的方差】
    STEP 3:为了保护数据隐私,UxtU_x^tUxt​用公钥pkcpk_cpkc​加密的梯度向量[[Gx]]pkc[[\boldsymbol{G_{x}}]]_{pk_c}[[Gx​]]pkc​​。为了加密要求,对于i∈[1,n]i\in [1,n]i∈[1,n],梯度向量中的每一个数GxiG_{xi}Gxi​都被编码为整数,⌈a⌋∈Z\lceil a \rfloor \in \mathbb{Z}⌈a⌋∈Z表示与实数aaa最接近的整数,precprecprec代表精度位:
    Gx←{⌈2prec⋅Gxi⌋}i=1i=nG_{x} \leftarrow\left\{\lceil 2^{prec } \cdot G_{x i}\rfloor \right\}_{i=1}^{i=n}Gx​←{⌈2prec⋅Gxi​⌋}i=1i=n​

STEP 4:为了降低通信成本,利用密文打包技术,即将多个明文打包成一个密文,并使用 Single Instruction Multiple Data 单指令多数据(SIMD)技术对这些值并行执行操作[34]。
假设pkc=ppk_c=ppkc​=p,对于文本空间log2p≥2048bits\text{log}_2 p\ge2048 \,\text{bits}log2​p≥2048bits,每个用户将ddd个明文打包成一个密文:

b) Robust aggregation phase:
1)SecMed:
对于x∈[1,m]x\in [1,m]x∈[1,m],SP通过对密文乘以随机值rir_iri​来掩盖{[[Gxi]]pkc}i=1i=n\left\{[[G_{x i}]]_{p k_{c}}\right\}_{i=1}^{i=n}{[[Gxi​]]pkc​​}i=1i=n​,并将掩盖值Rxi′R_{xi}^{'}Rxi′​发送给CP。然后CP对于梯度向量的每一维计算中位数,共计算nnn次。然后,CP将加密后的中位数向量返回给SP。SP消除随机值rir_iri​以获得结果。

2)SecPear:
计算坐标方向的中位数{[[G(med)i]]pkc}i=1i=n\left\{[[ G_{(m e d) i} ]]_{p k_{c}}\right\}_{i=1}^{i=n}{[[G(med)i​]]pkc​​}i=1i=n​和各个用户上传梯度{[[Gx]]pkc}x=1x=m\left\{[[ \boldsymbol{G}_{\boldsymbol{x}} ]]_{p k_{c}}\right\}_{x=1}^{x=m}{[[Gx​]]pkc​​}x=1x=m​之间的皮尔逊相关系数ρx,y\rho_{x, y}ρx,y​。SP计算对梯度向量的密文做rrr次方,rrr为随机选择的正整数。CP解密rrr次方的密文后,计算隐藏向量之间的相关性,并将结果发送给SP。
【这里算出来的皮尔逊相关系数是明文形式,系统中相关系数也是以明文形式传输的,影响较小但有待解决!】
对于mmm个局部梯度,SP共调用mmm次SecPear协议。
CP对每个梯度的相关系数重新缩放后将μx\mu _xμx​返回给SP:
μx←max⁡{0,ln⁡(1+ρx,y1−ρx,y)−0.5}\mu_{x} \leftarrow \max \left\{0, \ln \left(\frac{1+\rho_{x, y}}{1-\rho_{x, y}}\right)-0.5\right\}μx​←max{0,ln(1−ρx,y​1+ρx,y​​)−0.5}【引入对数函数】

3)SecAgg:
根据μx\mu _xμx​,为每个梯度向量分配一个权重。SP更新密文域下的全局模型:
ωt←ωt−1−η∑x∈[1,m]μxm∑x∈[1,m]μxGx\boldsymbol{\omega^{t}} \leftarrow \boldsymbol{\omega^{t-1}}-\eta \sum_{x \in[1, m]} \frac{\mu_{x}}{m \sum_{x \in[1, m]} \mu_{x}} \boldsymbol{G_{x}}ωt←ωt−1−η∑x∈[1,m]​m∑x∈[1,m]​μx​μx​​Gx​

4)SecExch:
SP与CP通信以获得重新加密的全局参数[[ωt]]pkx[[\boldsymbol{\omega ^{t}}]]_{pkx}[[ωt]]pkx​,然后广播给所有用户。

【未加密情况下的聚合算法:】
客户端:

  1. 利用随机梯度下降算法(SGD)计算局部梯度向量Gxt\boldsymbol{G_{x}^t}Gxt​。
  2. 采用带有动量的SGD来平滑当前梯度,使用指数衰减因子γ(0<γ<1)\gamma (0<\gamma <1)γ(0<γ<1)来增加一系列之前的梯度:
    Gx≜∑ℓ∈[0,t]γt−ℓGxℓ\boldsymbol{G_{x}} \triangleq \sum_{\ell \in[0, t]} \gamma^{t-\ell} \boldsymbol{G_{x}^{\ell}}Gx​≜∑ℓ∈[0,t]​γt−ℓGxℓ​
  3. 将平滑后的局部梯度发送给服务端。
    服务端:
  4. 计算各个局部梯度向量的中位数{[[G(med)i]]pkc}i=1i=n\left\{[[ G_{(m e d) i} ]]_{p k_{c}}\right\}_{i=1}^{i=n}{[[G(med)i​]]pkc​​}i=1i=n​
  5. 计算{[[G(med)i]]pkc}i=1i=n\left\{[[ G_{(m e d) i} ]]_{p k_{c}}\right\}_{i=1}^{i=n}{[[G(med)i​]]pkc​​}i=1i=n​与各个用户上传梯度{[[Gx]]pkc}x=1x=m\left\{[[ \boldsymbol{G}_{\boldsymbol{x}} ]]_{p k_{c}}\right\}_{x=1}^{x=m}{[[Gx​]]pkc​​}x=1x=m​之间的皮尔逊相关系数ρx,y\rho_{x, y}ρx,y​
  6. 对每个梯度的相关系数重新缩放为μx\mu _xμx​:μx←max⁡{0,ln⁡(1+ρx,y1−ρx,y)−0.5}\mu_{x} \leftarrow \max \left\{0, \ln \left(\frac{1+\rho_{x, y}}{1-\rho_{x, y}}\right)-0.5\right\}μx​←max{0,ln(1−ρx,y​1+ρx,y​​)−0.5}
  7. 根据μx\mu _xμx​,为每个梯度向量分配一个权重,更新全局模型:
    ωt←ωt−1−η∑x∈[1,m]μxm∑x∈[1,m]μxGx\boldsymbol{\omega^{t}} \leftarrow \boldsymbol{\omega^{t-1}}-\eta \sum_{x \in[1, m]} \frac{\mu_{x}}{m \sum_{x \in[1, m]} \mu_{x}} \boldsymbol{G_{x}}ωt←ωt−1−η∑x∈[1,m]​m∑x∈[1,m]​μx​μx​​Gx​
  8. 将ωt\boldsymbol{\omega^{t}}ωt返回给各个客户端

【密文下的聚合流程总结】:

恶意梯度向量和良性梯度向量之间存在着明显的区别。实际上,与良性梯度的低相似性意味着梯度具有高概率的恶意性
用参数进行训练,所以还需要在SP和user间传递参数;利用梯度计算相关系数判断投毒者。
CP传给SP相关系数的一个原因就是(这篇文章里定义了协议)SP需要相关系数来更新参数。

We explored one solution to this problem in Secure Multi-Party Computation. However, if secret sharing was not an option due to the limited number of participants, what’s the alternative? Homomorphic encryption(HE)

HE里的噪声:
In practice we can use this idea to build a simple scheme that encrypts a 1 bit message m={0,1} and that operates in the ZpZ_pZp​ ring of integers modulo a large prime p that should remain hidden, it is in fact the secret key.

Let’s start with the encryption procedure, if we want to encrypt m we first choose a random but small noise term r << p and also a large q. The encryption of m will be C(m) = m + 2r + qp.

然而,要破解无噪音方案,攻击者只需掌握两个加密信息,并通过简单地计算最大公除数来进行?


任何向量的线性组合都不能容忍一个拜占庭工作者。特别是,FedAvg不能抵御拜占庭攻击。


编程

通信

  • 采用import ssl
    edge:
    # 配置SSLsock = socket.create_connection(CLOUD_ADDRESS_PORT)context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)context.load_verify_locations("cert/cert.pem")edge_client = context.wrap_socket(sock, server_hostname="Cloud")

cloud:

  # 配置TLScontext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)context.load_cert_chain("cert/cert.pem", "cert/key.pem")g_socket_server = context.wrap_socket(sock, server_side=True)
  • pickle模块中常用的方法有:

    1. pickle.dump(obj, file, protocol=None,)
      必填参数obj表示将要封装的对象
      必填参数file表示obj要写入的文件对象,file必须以二进制可写模式打开,即“wb”
  1. pickle.load(file,*,fix_imports=True, encoding=“ASCII”, errors=“strict”)
    必填参数file必须以二进制可读模式打开,即“rb”,其他都为可选参数

  2. pickle.dumps(obj):以字节对象形式返回封装的对象,不需要写入文件中

  3. pickle.loads(bytes_object): 从字节对象中读取被封装的对象,并返回

import pickle
with open("my_profile.txt", "wb") as myprofile:  pickle.dump({"name":"AlwaysJane", "age":"20+", "sex":"female"}, myprofile)
with open("my_profile.txt", "rb") as get_myprofile:print (pickle.load(get_myprofile))
dataset_train, dataset_test, dict_users_train, dict_users_test = get_data(args)
dict_save_path = os.path.join(base_dir, 'dict_users.pkl')
with open(dict_save_path, 'wb') as handle:pickle.dump((dict_users_train, dict_users_test), handle)
  • 多线程
  # 给每个客户端创建一个独立的线程调用message_handle()进行管理;message_handle又是下面定义的函数thread = Thread(target=message_handle, args=(client, info, edge_count), daemon=True)thread.start()
  • 检查上一轮是否完成:local_round != global_round 完成后,向edge发送全局模型

联邦学习:传梯度vs传参数,还是变化量?