[论文解读]Boosting Operational DNN Testing Efficiency through Conditioning

Boosting Operational DNN Testing Efficiency through Conditioning

简介

论文标题

  • Boosting Operational DNN Testing Efficiency through Conditioning
  • 通过调节提高DNN的操作测试效率
  • 2019.6.27

核心内容

  • 在给定预算下,使用基于交叉熵的采样方法可以减少成本.
  • 使用方差可以概括结构性覆盖
  • 模型的输出(置信度)未必可靠

贡献

  • 将操作性DNN测试问题的表述表示为使用少量样本进行性能评估,并提出通过条件减少有效减少方差的测试,作为结构覆盖范围的概括。
  • 一种有效的DNN测试操作方法,该方法利用了被测DNN所学习的高维表示形式,以及一种基于交叉熵最小化的采样算法来实现该方法。
  • 系统的经验评估。 LeNet,VGG和ResNet的实验表明,与简单的随机采样相比,此方法仅需要大约一半的标记输入即可达到相同的精度水平

:通过小样本采样labeled data来准确的预估DNN在样本所在上下文的表现。文章的立足点就是:在分层明确的情况下,分层抽样的估计方差比随机抽样的估计方差小。

实验/工具地址

github

本文主要是从方差,期望,有偏无偏,置信度,熵等统计的角度出发,用采样的思想来最小化测试集

作者认为覆盖率是很难引入到神经网络当中的

摘要

DNN测试通常需要以非常有限的预算来产生准确的结果,以标记现场收集的数据

通过统计抽样可以对软件测试进行可靠性估计,将传统结构覆盖范围背后的思想重新解释为减少方差的条件。有了这一见识,我们提出了一种有效的DNN测试方法,该方法基于对被测DNN模型所学习的表示形式的条件。该表示由模型最后一个隐藏层中神经元输出的概率分布定义。为了从操作数据稀疏分布的这种高维分布中采样,我们设计了一种利用交叉熵最小化的算法。

结果表明,与简单的随机采样相比,此方法仅需要大约一半的标记输入即可达到相同的精度水平。

DNNS的操作测试

DNN测试定义

DNN的测试必须是:

  • Statistical 与具有某些预期行为的人类书面程序相反,作为一种统计机器学习模型,DNN仅提供了一些概率保证,即,可以对它涉及的大多数输入做出正确的预测。实际上,为了避免过度拟合和最大化通用性,对一小部分输入的错误预测是预期的,并且在某种意义上是故意的。
  • Holistic 到目前为止,在神经元水平上对单个输入的DNN内部行为尚无可行的理论解释。这意味着,尽管DNN的计算步骤是可见的,但它们实际上是黑盒。同样,检测到的具有特定输入的故障几乎无助于“调试”
    DNN。
  • Operational 此外,不考虑DNN的操作上下文而进行的测试是没有意义的。

DNN测试操作过程

图2说明了有效的DNN测试操作过程。目的是通过一些复杂的测试数据选择,只需标记一小部分操作数据即可获得足够的精度以估计操作精度。

图2说明了有效的DNN测试操作过程。目的是通过一些复杂的测试数据选择,只需标记一小部分操作数据即可获得足够的精度以估计操作精度。

覆盖率及其困难之处

覆盖率

  • 覆盖程序同一部分的输入的同质性-全部或全部不引入错误,因此可以通过避免重复来提高测试效率
  • 覆盖率指标所指示的输入的多样性,因此可以通过强制执行高覆盖率来解决可能隐藏罕见错误的极端情况,从而提高测试的完整性。

覆盖率的挑战性

白盒软件测试中结构覆盖率是很常见的。然而,事实证明,将这种思想应用于DNN的测试具有挑战性,因为

  • DNN的黑盒性质。没有明显的结构特征(例如程序分支或人类编写程序的执行路径)可以直观地提供所需的同质性,此外,
  • 维度的诅咒。对于DNN,模型精度的强大解释因子Z通常是高维向量,这使得很难用小样本来表示,但是不确定性很大。

目前的覆盖准则

覆盖准则大致可分为三类:

  • 神经元激活覆盖率 (Neuron-Activation coverage):DeepXplore[40]将神经网络的神经元覆盖率定义为给定测试集激活的神经元的百分比。它利用这种覆盖率,结合基于梯度的优化,来搜索对抗性的例子。这个标准的缺点是它可以用少量的测试输入饱和。
  • 神经元输出覆盖 (Neuron-Output coverage):DeepGauge[30]提出了更细粒度的标准来克服这一弱点。它将每个元素的输出将神经元分成k个相等的部分,并将k-多部分覆盖定义为测试集已经覆盖的组块的数量。此外,作者还考虑了具有神经元边界覆盖的k个部分的输出接触。
  • 神经元组合覆盖 (Neuron-Combination coverages):神经元状态的组合也可以解决覆盖饱和的问题。DeepCover[44]和DeepCT[32]使用这一策略。受MC/DC覆盖的启发,DeepCover提出了一系列非常细粒度的覆盖指标。
  • 另有sa指标用于衡量用例的惊讶度

高效的DNN测试方法

数学定义

预备定义

θ = E [ H ( X ) ] \theta=\mathbb{E}[H(\boldsymbol{X})] θ=E[H(X)] 是假设存在一个需要估计的固定参数

X : Ω → D ⊂ R d X: \Omega \rightarrow \mathcal{D} \subset \mathbb{R}^{d} X:ΩDRd 是对应于观测数据的随机变量

H : H: H: D → R \mathcal{D} \rightarrow \mathbb{R} DR 是感兴趣的模型

举例 : 当估计DNN模型的精度时,DNN正确预测x的标签则 H ( x ) = 1 H(x)=1 H(x)=1,否则将其定义为0. H ( x ) H(x) H(x)也是一个随机变量。

θ θ θ的估计量由符号 θ ^ \hat{\theta} θ^表示

DNN测试问题

数学定义 给定 M M M个训练有素的DNN模型,给 S S S个从操作上下文中收集的 N N N个未标记示例的集合,而不是标记所有这 N N N个示例,而是选择并标记具有给定大小预算 n = ∣ T ∣ ≪ N n=|T| \ll N n=TN的子集 T ⊆ S T \subseteq S TS。,并使用 T T T来估计 M M M S S S上的精度,并且估计误差尽可能小

利用M和S提供的信息,我们尝试通过调节来实现有效的估计。我们首先讨论基于置信度的分层抽样(CSS),它很简单,但很不幸,它很脆弱,并且仅限于分类器。然后,我们提出了以M所学习的表示为条件的交叉熵采样(CES),并通过最小化交叉熵来近似估计S的分布。

三种采样方法

  • SRS: Simple Random Sampling 简单随机抽样
  • CSS: Confidence-based Stratified Sampling 基于置信度的分层抽样
  • CES: Cross Entropy-based Sampling 交叉熵采样

后两者是第一种方式引入Z后的改进

SRS 简单随机抽样
定义

θ ^ = H ( x 1 ) + ⋯ + H ( x n ) n \hat{\theta}=\frac{H\left(x_{1}\right)+\cdots+H\left(x_{n}\right)}{n} θ^=nH(x1)++H(xn)

Z的引入及条件化

尽管SRS很简单,但如果我们能画出足够大的I.I.D.,它在实践中是相当有效的。样本。如果没有关于H(X)的进一步信息,我们很难改进SRS。通常使用的策略是条件化,即找到理想情况下H(X)强烈依赖的随机变量或向量Z,并利用总方差定律:
Var ⁡ [ H ( X ) ] = E [ Var ⁡ [ H ( X ∣ Z ) ] ] + Var ⁡ [ E [ H ( X ∣ Z ) ] ] \operatorname{Var}[H(X)]=\mathbb{E}[\operatorname{Var}[H(X | Z)]]+\operatorname{Var}[\mathbb{E}[H(X | Z)]] Var[H(X)]=E[Var[H(XZ)]]+Var[E[H(XZ)]]
直观地说,定律说,如果我们用Z“解释”H(X),H(X)的方差可以分解成不被Z解释的方差(右手边的第一项)和由Z解释的方差(第二项)。注意,E[H(X|Z)]本身是Z上的函数,并且
E [ H ( X ) ] = E [ E [ H ( X ∣ Z ] ) ] \mathbb{E}[H(X)]=\mathbb{E}[\mathbb{E}[H(X | Z])] E[H(X)]=E[E[H(XZ])]
因此,我们可以从Z的分布中抽样,估计E[H(X|Z)]而不是H(X),因为前者的方差比后者小

如果我们可以对Z进行完全抽样,即覆盖Z的所有可能的值Zi,则我们估计的方差将只是在估计每个Zi的E[H(X|zi)]时引入的方差。这正是分层抽样的作用所在。此外,如果H(X)的值完全由Z决定,我们将有零方差。

Z的特点

要提高测试作为评估的效率,我们需要

  1. 为了识别尽可能多地影响H(X)性能(精度)的可观察因素Z,使得在每个ZI下条件的H(X)的方差最小,以及
  2. 为Z抽取尽可能有代表性的样本,以便更好地处理由Z引起的不确定性

这两个方面在实践中可能会相互冲突。直观地说,对H(X)的解释越“精确”,它就必须越细粒度,越难由小样本充分代表。通过精心选择的Z在它们之间取得良好的平衡是至关重要的。

原有样本比如图片的向量为X,softmax层的输出向量为Z,引入Z来更好的解释X

CSS 基于置信度的分层抽样
定义

如前所述,提高估计效率的关键是找到一个随机变量Z,该变量与模型M的准确性密切相关,并且其分布易于采样。自然的选择是某些分类器模型在预测x的标签时提供的置信度值c(x)。显然,如果分类器可靠,则具有较高置信度的预测将更有可能是正确的

由于置信度是有界标量,因此我们可以将其范围划分为多个k个部分,并相应地将总体S划分为k个分层 { S 1 , ⋯   , S k } \left\{S_{1}, \cdots, S_{k}\right\} {S1,,Sk}。因此,属于 S j S_j Sj的示例的概率为 P j = ∣ S j ∣ ∣ S ∣ P_{j}=\frac{\left|S_{j}\right|}{|S|} Pj=SSj, 1 ≤ j ≤ k 1 \leq j \leq k 1jk。从每个阶层 S j S_j Sj中,随机抽取 n j n_{j} nj个元素,使得 ∑ j k n j = n \sum_{j}^{k} n_{j}=n jknj=n。则M在S上的精度估计为
a c ^ c = ∑ j = 1 k P j E [ H ( x ) ∣ x ∈ s j ] = ∑ j = 1 k P j ( 1 n j ∑ i = 1 n j H ( x j , i ) ) a \hat{c} c=\sum_{j=1}^{k} P_{j} \mathbb{E}\left[H(\boldsymbol{x}) | \boldsymbol{x} \in s_{j}\right]=\sum_{j=1}^{k} P_{j}\left(\frac{1}{n_{j}} \sum_{i=1}^{n_{j}} H\left(\boldsymbol{x}_{j, i}\right)\right) ac^c=j=1kPjE[H(x)xsj]=j=1kPj(nj1i=1njH(xj,i))
一种简单的策略是使用比例分配,即 n j = n_{j}= nj= P j ⋅ n P_{j} \cdot n Pjn,然后准确度的估算变为 a c ^ c p r o p = a \hat{c} c_{\mathrm{prop}}= ac^cprop= 1 n ∑ H ( x j , i ) \frac{1}{n} \sum H\left(\boldsymbol{x}_{j, i}\right) n1H(xj,i)然而,尽管安全但比例分配可能不是最佳的。

分层估计量的方差是每个层中的方差之和: Var ⁡ ( a c ^ c ) = ∑ j = 1 k Var ⁡ ( H ( x ∣ x ∈ s j ) ) \operatorname{Var}(a \hat{c} c)=\sum_{j=1}^{k} \operatorname{Var}\left(H\left(\boldsymbol{x} | \boldsymbol{x} \in s_{j}\right)\right) Var(ac^c)=j=1kVar(H(xxsj))。如果我们在层中分配更多不均匀的示例,则可以进一步减少。具体来说,我们可以猜测具有较低置信度的层的准确性应该波动更大。因此,我们应该从那些低信度阶层中拿出更多例子。

最优分层和样本分配依赖于H(X)方差的实际分布,该分布是以置信度为条件的,而置信度是未知的。它们必须根据经验和中试实验来确定

CSS弱点

不幸的是,CSS对于操作DNN测试并不健壮。当模型针对操作上下文进行了完美的训练时,它执行得非常好。然而,正如我们在第4节报告的实验中所显示的那样,当模型不能很好地拟合到操作数据集时,其性能会急剧下降。请注意,这种不利情况是操作DNN测试的动机。原因不难看出-当模型在操作上下文中预测不佳时,它产生的置信度值不能被信任。此外,回归任务中不容易获得置信度。因此,我们提出了另一种基于神经元行为的方差缩减方法

CES 基于交叉熵的采样
SoftMax层的选择理由

条件随机变量Z的较好选择是最后隐层神经元的输出。它通常被视为训练数据的学习表示,使预测更容易。这一选择背后的理由是多方面的。

  • 首先,虽然人类不一定能理解,但当操作上下文漂移时,表示比预测更稳定。这得到了众所周知的迁移学习实践的支持,在这些实践中,只针对不同的任务对SoftMax层进行再培训。
  • 其次,DNN预测是由这一层的输出的线性组合直接得出的,因此它必须与预测精度高度相关。
  • 最后,这一层中神经元之间的相关性被认为比前几层中的神经元之间的相关性要小,这便于我们的算法中将使用的它们的联合分布的近似。
定义

对于一个训练好的DNN模型 M M M,我们考虑它的最后一个隐层 L L L,它由 m m m个神经元组成,记为 e i , i = 1 , … , m e_{i}, i=1, \dots, m ei,i=1,,m.我们将每个神经元 e i e_{i} ei的输出范围 D e i D_{e_{i}} Dei分成K个相等的部分 { D e i , 1 , … , D e i , K } \left\{D_{e_{i}, 1}, \ldots, D_{e_{i}, K}\right\} {Dei,1,,Dei,K},并且如果 e i e_{i} ei对于输入 x x x的输出属于 D e i , j , 1 ≤ j ≤ K D_{e_{i}, j}, 1 \leq j \leq K Dei,j,1jK则定义函数 f e i ( x ) = j f_{e_{i}}(\boldsymbol{x})=j fei(x)=j

因此,条件变量Z是向量 ( Z 1 , … , Z m ) , Z i ∈ \left(Z_{1}, \dots, Z_{m}\right), Z_{i} \in (Z1,,Zm),Zi { 1 , … , k } , 1 ≤ i ≤ m \{1, \ldots, k\}, 1 \leq i \leq m {1,,k},1im

最后一层,m个神经元,将每个神经元输出离散化到k个范围中的一个,并将所有神经元离散后的值组成向量

S z 1 , … , z m = { x ∈ S ∣ f e i ( x ) = z i , 1 ≤ i ≤ m } S_{z_{1}, \ldots, z_{m}}=\left\{\boldsymbol{x} \in S | f_{e_{i}}(\boldsymbol{x})=z_{i}, 1 \leq\right.i \leq m\} Sz1,,zm={xSfei(x)=zi,1im} S S S的一个子集,其元素映射到 z = ( z 1 , … , z m ) z=\left(z_{1}, \dots, z_{m}\right) z=(z1,,zm)。Z的概率分布 P S ( z ) P_{S}(z) PS(z)用操作数据集 S S S定义为
P S ( z 1 , … , z m ) = ∣ S z 1 , … , z m ∣ ∣ S ∣ P_{S}\left(z_{1}, \ldots, z_{m}\right)=\frac{\left|S_{z_{1}, \ldots, z_{m}}\right|}{|S|} PS(z1,,zm)=SSz1,,zm
将上述向量的各类的数目除以总数目获得其离散分布

然而,考虑到Z的高维性,根据Z的分布从整个测试集中抽取典型样本T是有挑战性的,更不用说采用分层抽样了。请注意,我们不能使用根据Z分布生成的人工示例,因为我们需要在给定的操作上下文中评估模型对真实数据的准确性。现在的问题是如何从本身在高维空间中稀疏分布的有限总体中选择一个小尺寸样本,以使样本对总体尽可能具有代表性。

为此,我们建议通过最小化 P S ( Z ) P_{S}(Z) PS(Z) P T ( Z ) P_{T}(Z) PT(Z)之间的交叉熵来选择典型的样本T:
min ⁡ T ⊂ S , ∣ T ∣ = n C E ( T ) = H ( P S , P T ) = − ∑ z ∈ { 1 , … , K } m P S ( z ) log ⁡ P T ( z ) \begin{aligned} \min _{T \subset S,|T|=n} C E(T) &=H\left(P_{S}, P_{T}\right) \\ &=-\sum_{z \in\{1, \ldots, K\}^{m}} P_{S}(z) \log P_{T}(z) \end{aligned} TS,T=nminCE(T)=H(PS,PT)=z{1,,K}mPS(z)logPT(z)
其中
P T ( z 1 , … , z m ) = ∣ T z 1 , … , z m ∣ ∣ T ∣ P_{T}\left(z_{1}, \ldots, z_{m}\right)=\frac{\left|T_{z_{1}, \ldots, z_{m}}\right|}{|T|} PT(z1,,zm)=TTz1,,zm
在这种高维情况下,极小化很难直接计算,部分原因是S和T在Z空间中的稀疏性。幸运的是,观察到DNN通常会降低最后一个隐层中神经元之间的相关性,因此我们可以通过假设它们在计算最小化时彼此独立来进行近似。在这种情况下,我们可以通过最小化每个维度上 P S ( Z ) P_{S}(Z) PS(Z) P T ( Z ) P_{T}(Z) PT(Z)之间的交叉熵的平均值 C E ‾ ( T ) \overline{C E}(T) CE(T)来最小化 C E ( T ) C E(T) CE(T)
min ⁡ T ⊂ S , ∣ T ∣ = n C E ‾ ( T ) = − ∑ i = 1 m ∑ z i = 1 K P S e i ( z i ) log ⁡ P T e i ( z i ) m \min _{T \subset S,|T|=n} \overline{C E}(T)=-\frac{\sum_{i=1}^{m} \sum_{z_{i}=1}^{K} P_{S}^{e_{i}}\left(z_{i}\right) \log P_{T}^{e_{i}}\left(z_{i}\right)}{m} TS,T=nminCE(T)=mi=1mzi=1KPSei(zi)logPTei(zi)
其中
P S e i ( z i ) = ∣ { x ∈ S ∣ f e i ( x ) = z i } ∣ ∣ S ∣ P_{S}^{e_{i}}\left(z_{i}\right)=\frac{\left|\left\{x \in S | f_{e_{i}}(x)=z_{i}\right\}\right|}{|S|} PSei(zi)=S{xSfei(x)=zi}
P T e i ( z i ) P_{T}^{e_{i}}\left(z_{i}\right) PTei(zi)定义类似

类似于朴素贝叶斯,这里假设每个维度独立,则联合概率分布等于每个维度的乘积

进而得到CE(T)的最优解 P S ( z ) = P T ( z ) , z ∈ { 1 , … , K } m P_{S}(z)=P_{T}(z), z \in\{1, \ldots, K\}^{m} PS(z)=PT(z),z{1,,K}m

因此模型精度E[H(X)]的估计为:
a c ^ c = ∑ z ∈ { 1 , … , K } m P T ( z ) E [ H ( x ) ∣ Z = z ] = ∑ z ∈ { 1 , … , K } m P T ( z ) ∑ z ∈ T z 1 , … , z m H ( x ) ∣ T z 1 , … , z m ∣ = ∑ x ∈ T H ( x ) ∣ T ∣ = ∑ i = 1 n H ( x i ) n \begin{aligned} a \hat{c} c &=\sum_{z \in\{1, \ldots, K\}^{m}} P_{T}(z) \mathbb{E}[H(x) | Z=z] \\ &=\sum_{z \in\{1, \ldots, K\}^{m}} P_{T}(z) \frac{\sum_{z \in T_{z_{1}, \ldots, z_{m}}} H(x)}{\left|T_{z_{1}, \ldots, z_{m}}\right|} \\ &=\frac{\sum_{x \in T} H(x)}{|T|}=\frac{\sum_{i=1}^{n} H\left(x_{i}\right)}{n} \end{aligned} ac^c=z{1,,K}mPT(z)E[H(x)Z=z]=z{1,,K}mPT(z)Tz1,,zmzTz1,,zmH(x)=TxTH(x)=ni=1nH(xi)
上述为无偏估计

为了上述 C E ‾ ( T ) \overline{C E}(T) CE(T)优化问题,我们提出了一种类似于随机游走的算法.我们首先随机选取 p p p个样本作为初始样本集 T T T,然后通过q个样本的一组 Q ∗ Q^{*} Q重复扩大集合,直到耗尽n的预算.每一步从ℓ个随机选择的组中选择q个样本,使交叉熵最小。

算法流程

初始时随机选择p个样本放入T,每次从L个组中选择,每个组中选择q个样本,计算T与每个Q交叉熵,选择最小的并入T,直到T的个数为n

最后,在方程 min ⁡ T ⊂ S , ∣ T ∣ = n C E ( T ) \min _{T \subset S,|T|=n} C E(T) minTS,T=nCE(T)中,结构覆盖和交叉熵之间存在内在联系。结构覆盖实际上假定 P S ( z ) P_{S}(z) PS(z)的概率为常数。在这种情况下,最小化 C E ( T ) CE(T) CE(T)变成最大化 ∑ z ∈ { 1 , … , K } m log ⁡ P T ( z ) \sum_{\boldsymbol{z} \in\{1, \ldots, K\}^{m}} \log P_{T}(\boldsymbol{z}) z{1,,K}mlogPT(z),这等于最大化˛ ∏ z ∈ { 1 , … , K } m P T ( z ) \prod_{z \in\{1, \ldots, K\}^{m}} P_{T}(z) z{1,,K}mPT(z)。由于 ∑ z ∈ { 1 , … , K } m P T ( z ) = 1 \sum_{\boldsymbol{z} \in\{1, \ldots, K\}^{m}} P_{T}(\boldsymbol{z})=1 z{1,,K}mPT(z)=1,所以它是为了均匀分布 P T ( z ) P_{T}(z) PT(z),这实际上是为了最大化覆盖,以便覆盖更多的z实例。

若psz为常数,则可推断出最大化ptz等价于使ptz为均匀分布(最大熵原理),就是在T中,每个Z都尽可能均匀

实验

通常,操作DNN测试是检测DNN模型在特定操作上下文中使用时的性能损失。这里我们假设模型用它的训练集进行了很好的训练,并且没有明确地考虑训练过程中通常解决的问题,如拟合不足或过度拟合。因此,性能损失很可能是由以下原因引起的

  • Polluted training set. 训练集因意外或恶意攻击而发生变异,从而产生变异模型
  • Different system settings. 例如,该模型可能使用高分辨率示例进行训练,但由于系统中配备的摄像机的限制,该模型用于对低分辨率图像进行分类。
  • Different physical environment. 例如,操作环境中的闪电条件可能会有所不同。

其余实验部分略

现有工作

用于其他目的的DNN测试

  • TensorFuzz开发了一种覆盖引导模糊(CGF)方法来快速发现神经网络中的数值错误。
  • DeepMutation构建了一个DNN突变测试框架。它通过训练数据变异和程序变异来模拟DNNs的潜在缺陷。
  • MODE进行模型状态差分分析,以确定模型是过拟合还是欠拟合。然后,它执行类似于回归测试中的程序输入选择的训练输入选择。
  • DeepRoad使用生成性对抗性网络(GANS)自动生成不同天气条件下的自动驾驶图像,用于DNN测试。

总结

训练好的DNN模型要在特定的操作上下文中很好地工作,一个重要的前提是它从训练数据中学习到的分布与操作上下文是一致的。在采用该模型之前,必须进行操作测试以验证这一前提。虽然深度学习通常被认为是一种依赖于“大数据”的方法,但DNN的操作测试往往受到标记操作示例的有限预算的限制,因此它必须采用统计上有效的“小”数据方法

在本文中,我们利用DNN模型学习的表示来提高操作DNN测试的效率。有趣的是,尽管我们不能相信模型事先产生的任何结果(回想3.1节中置信度的不可靠性),我们仍然可以利用它的“推理”,尽管它是不透明的。实证评估证实了我们基于表示条件的方法的总体效果,它减少了大约一半的标记操作示例的数量。

另一个有趣的观察是,结构复盖率指导测试的同质性-多样性智慧可以推广到可靠性估计中减少方差的条件。这种推广的重要性在于它在测试由DNN和人类编写的程序组成的混合系统中的潜在应用。

还有许多可能的优化措施等待将来的工作,例如通过自适应重要性采样进一步减少方差,以及减少未标记的操作数据的数量。然而,对我们来说,最重要的是如何以DNNs所需的统计、整体和可操作的方式重新表述 “bug” 和 “debug”的概念

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值