深度学习100问之深入理解Batch Normalization(批归一化)

这几天正在看BN的论文,最近也经常看到关于BN理解的文章,在目前研究的课题中BN也是极其重要的,所以就抽出一段时间认真地研究了一下Batch Normalization的原理,以下为参考网上的几篇文章总结得出的。

一、初识BN

我们今天的主角:批归一化,或者说是批标准化,英文名字 “ B a t c h N o r m a l i z a t i o n Batch Normalization BatchNormalization”,出自一篇非常值得学习的文献————Sergey Ioffe,Christian Szegedy在2015年发表的论文《Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift》也就是 G o o g L e n e t − I n c e p t i o n v 2 GoogLenet - Inception v2 GoogLenetInceptionv2网络中使用的一种 N o r m a l i z a t i o n Normalization Normalization,常用缩写为 B N BN BN

近年来深度学习异常火爆,并且在视觉、语音和其他诸多领域屡创佳绩,随机梯度下降( S G D SGD SGD)成了训练深度神经网络的主流方法。虽然 S G D SGD SGD对于训练深度神经网络来说是简单高效的,但它被人诟病的地方是,需要对模型的超参数进行仔细地选择,比如:权重衰减系数、 D r o p o u t Dropout Dropout比例、特别是优化中使用的学习率以及模型的参数初始化。由于每个层的输入都受到前面所有层的参数影响,因此,当网络变得更深时,网络参数的微小变化也会被逐渐放大,这使得训练变得越来越复杂,收敛越来越慢。这是一个深度学习领域的接近本质的问题,已经有很多论文提出了解决方法,比如: R E L U RELU RELU激活函数, R e s i d u a l N e t w o r k Residual Network ResidualNetwork残差网络等等。 B N BN BN本质上也是其中一种目前被大量使用的解决方法。

B N BN BN是一个深度神经网络的训练技巧,它不仅可以加快了模型的收敛速度,而且更重要的是在一定程度缓解了深层网络中“梯度弥散”的问题,从而使得深层网络模型的训练更加容易和稳定。

在开始讲解算法之前,我们先来思考几个问题:

我们都知道在神经网络训练开始之前,都需要对输入数据做一个归一化处理,那么为什么要归一化呢?BN的作用到底是什么?

  • 深度神经网络的学习过程的本质是什么呢,这个问题现阶段没办法给出准确的答案,知乎上曾经有人讨论过这个问题,《关于深度学习的本质和优缺点大家说下自己的理解?》,有兴趣的童鞋可以去看一看,个人比较赞成这位仁兄的看法。
    在这里插入图片描述
    在我目前的水平看来,深度学习的本质上就是刻画问题的内部复杂结构特征,进行任意的非线性变换,简单来说可以看成是学习数据的分布(个人看法,勿喷)。机器学习领域有个很重要的假设: I I D IID IID独立同分布假设【数据的独立同分布(Independent Identically Distributed)】————假设训练数据和测试数据是满足相同分布的,这是通过训练数据获得的模型能够在测试集获得好的效果的一个基本保障。如果训练数据与测试数据的分布不同,那么网络的泛化能力就会大大降低。另一方面,如果每批训练数据的分布各不相同,网络就要在每次迭代时都去学习适应不同的分布,那么网络的训练速度就会大大降低,这就是为什么我们需要对输入数据做一个归一化处理。

  • B N BN BN的作用到底是什么呢?根据刚才提到的机器学习领域的 I I D IID IID独立同分布假设,我们可以发现 B N BN BN的作用就是在深度神经网络训练过程中使得每一层神经网络的输入均保持相同分布。

二、与BN相知

1、“Internal Covariate Shift”问题

从论文的名字《Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift》中我们可以看出 B N BN BN是提出的一种解决“ I n t e r n a l C o v a r i a t e S h i f t Internal Covariate Shift InternalCovariateShift”问题的算法,那么什么是“ I n t e r n a l C o v a r i a t e S h i f t Internal Covariate Shift InternalCovariateShift”?

我们在最开始的时候提到过 S G D SGD SGD,论文也是首先提到了 M i n i − B a t c h S G D Mini-Batch SGD MiniBatchSGD相对于 O n e E x a m p l e S G D One Example SGD OneExampleSGD的两个优点:梯度更新方向更准确;并行计算速度快。为什么要说 S G D SGD SGD,因为我们的 B N BN BN是基于 M i n i − B a t c h S G D Mini-Batch SGD MiniBatchSGD的一种解决方法, S G D SGD SGD的训练缺点是:超参数调试困难。(那是不是说如果 B N BN BN S G D SGD SGD结合就perfect了(✧◡✧))

大家都知道在统计机器学习中的一个经典假设是“源空间(source domain)和目标空间(target domain)的数据分布(distribution)是一致的”。如果不一致,那么就出现了新的机器学习问题,如,transfer learning/domain adaptation等。而 c o v a r i a t e s h i f t covariate shift covariateshift就是分布不一致假设之下的一个分支问题,它是指源空间和目标空间的条件概率是一致的,但是其边缘概率不同,即:对所有,
在这里插入图片描述
但是,
在这里插入图片描述
大家细想便会发现,的确,如果深度神经网络开始训练,那么各个参数就都要发生更新,除了输入层的数据外(因为我们已经人为地为输入层数据的每个样本都进行了归一化),后面网络每一层的输入数据的分布是不停地在发生变化的,因为在训练的时候,前面层训练参数的更新将导致后面层输入数据的分布产生变化。对于深度神经网络这种包含许多隐层的网络结构来说,在训练过程中,由于各层参数在不停地变化,所以每个隐层都会面临 c o v a r i a t e s h i f t covariate shift covariateshift的问题,由于是对隐层间的信号的分析,也即是“ i n t e r n a l internal internal”的来由。也就是说在训练过程中,隐层的输入分布老是变来变去,这就是所谓的“ I n t e r n a l C o v a r i a t e S h i f t Internal Covariate Shift InternalCovariateShift

2、BN的基本思想

B N BN BN算法的基本思想其实相当地直观:因为深层神经网络在做非线性变换前的激活输入值(就是那个 x = W U + B x=WU+B x=WU+B,U是输入),随着网络结构深度的增加,在训练过程中其分布逐渐发生偏移或者变动。训练速度变慢的原因,一般是整体分布逐渐往非线性函数的取值区间的上下限两端靠近(对于 s i g m o i d sigmoid sigmoid函数来说,意味着激活输入值 W U + B WU+B WU+B ∣ x ∣ |x| x绝对值大的正值或负值,如下图两个红色剪头的指向),所以这导致反向传播时低层神经网络的梯度消失(数学导数),这是训练深层神经网络收敛越来越慢的本质原因
在这里插入图片描述
B N BN BN就是通过一定的规范化手段,把每层神经网络任意神经元这个输入值的分布强行拉回到均值为0方差为1的标准正态分布,其实就是把越来越偏的分布强制拉回比较标准的分布,这样使得激活输入值落在非线性函数对输入比较敏感的区域,这样输入的小变化就会导致损失函数较大的变化,意思是这样让梯度变大,避免梯度消失问题产生,而且梯度变大意味着学习收敛速度快,能大大加快训练速度。

简单来说就是说,对于每个隐层神经元,把逐渐向非线性函数映射后向取值区间极限饱和区靠拢的输入分布强制拉回到均值为0方差为1的比较标准的正态分布,使得非线性变换函数的输入值落入对输入比较敏感的区域,以此避免梯度消失问题。保持梯度在一直比较大的位置,很明显这样的参数调整效率更高,也就是损失函数最优化的步子更大,速度更快。(这也是在很多情况下为什么 R E L U RELU RELU s i g m o i d sigmoid sigmoid好一些的原因,因为 R E L U RELU RELU大于0时的梯度一直是1)

下面我们通过数学方法来分析这种调整有什么意义。
在这里插入图片描述
上图对应几个正态分布。

假设某个隐层神经元原先的激活输入 x x x,符合均值是-2,方差是0.5的正态分布(在上图中对应最左端的浅绿色曲线),通过 B N BN BN后转换为均值为0,方差是1的正态分布(在上图中对应的深红色图形)。

这意味着什么?意味着输入x的正态分布整体向右平移2个单位(对应正态分布中均值平移的变化),图形曲线更扁平了(对应正态分布中方差增大的变化)。通过这个图我们可以看出, B N BN BN其实就是把每个隐层神经元的激活输入分布从非【均值为0方差为1的正态分布】通过平移(增大/减小均值)、压缩/拉伸(增大/减小方差)曲线尖锐程度,调整为【均值为0方差为1的正态分布】。

那么把激活输入 x x x调整到这个【均值为0方差为1的正态分布】有什么用嘛?首先我们看下均值为0,方差为1的标准正态分布代表什么含义:
在这里插入图片描述
上图对应均值为0方差为1的标准正态分布图。

这意味着68%概率分布在 [-1,1] 的范围内,95%概率分布在 [-2,2] 的范围。假设非线性函数是 s i g m o i d sigmoid sigmoid,那么看下 s i g m o i d ( x ) sigmoid(x) sigmoid(x)其函数图像:
在这里插入图片描述
s i g m o i d sigmoid sigmoid导数具体的推导过程如下:
在这里插入图片描述
因为 f ( x ) = s i g m o i d ( x ) f(x)=sigmoid(x) f(x)=sigmoid(x)在0到1之间,所以 s i g m o i d ( x ) sigmoid(x) sigmoid(x)的导数 f ′ ( x ) f′(x) f(x)在0到0.25之间,其对应的函数图像如下:
在这里插入图片描述
现在我们假设没有经过 B N BN BN调整前x的原先正态分布均值是6,方差是1,在 [均值 - 方差2,均值 + 方差2] 范围内的概率是95%,那么也就意味着落在 [4,8] 之间概率是95%,那么对应的 s i g m o i d ( x ) sigmoid(x) sigmoid(x)函数的值明显接近于1,在上面我们说过这个位置是典型的梯度饱和区,所以在这个区域里梯度变化很慢。

为什么这个位置是梯度饱和区?请看下 s i g m o i d ( x ) sigmoid(x) sigmoid(x)函数图像,如果横坐标( s i g m o i d ( x ) sigmoid(x) sigmoid(x)函数值)取值接近0或者接近于1的时候对应纵坐标( s i g m o i d ( x ) sigmoid(x) sigmoid(x)导数函数值)取值,接近于0,意味着梯度变化很小甚至消失。而假设经过 B N BN BN后,均值是0,方差是1,那么意味着95%的 x x x值落在了 [-2,2] 区间内,很明显这一段是 s i g m o i d ( x ) sigmoid(x) sigmoid(x)函数接近于线性变换的区域,意味着 x x x的小变化会导致非线性函数值产生较大的变化,也即是梯度变化较大,对应导数函数图中明显大于0的区域,就是梯度非饱和区。

从上面提到的过程中学到了什么呢?BN的作用其实简单来说就是把隐层神经元激活输入从各种奇怪的非【均值为0方差为1的正态分布】拉回到【均值为0方差为1的正态分布】。

但是又出现了一个问题,如果都通过 B N BN BN实现归一化的话,不就是把非线性函数替换成线性函数了?这意味着什么呢?这就要说到我们深度学习的学习过程是怎么实现的了,即通过多层非线性实现一个泛化的过程。如果是多层的线性函数其实这个深层就没有任何的意义,因为多层线性网络和一层线性网络是等价的。这意味着网络的表达能力下降了,也就是意味着深度的意义就没有了。所以 B N BN BN为了保证非线性的获得,对变换后的满足【均值为0方差为1的正态分布】的x又进行了 s c a l e + s h i f t scale + shift scale+shift操作。每个神经元增加了两个参数 s c a l e scale scale s h i f t shift shift,这两个参数通过训练学习到的,关键就是在线性和非线性之间找到一个比较好的平衡点,既能享受非线性的较强表达能力的好处,又避免太靠非线性区两头使得网络收敛速度太慢。最不济的情况,这两个参数可以等效成最开始的状态,也就是训练数据集本来的特征,这只是需要简单的计算就可以实现。

3、预处理操作

论文中提到了需要对神经网络输入数据进行预处理操作,众所周知最好的算法莫过于白化预处理。如果对网络的输入进行白化预处理,网络的训练将会收敛地更快——即输入线性变换为具有零均值和单位方差,并且去除了相关性。然而白化计算量太大了,代价昂贵,还有就是白化不是处处可微的,所以在深度学习中,其实很少用到白化,因此使用了下面的公式进行预处理,可以理解为对深层神经网络每个隐层神经元的激活值做简化版本的白化操作。
在这里插入图片描述
要注意,这里某一层的某个神经元的x(k)不是指该层的原始输入,即不是上一层每个神经元的输出,而是该层这个神经元的线性激活 x = W U + B x=WU+B x=WU+B,这里的 U U U才是上一层神经元的输出。变换的意思是:某个神经元对应的原始的激活x通过减去 m i n i − B a t c h mini-Batch miniBatch m m m个实例获得的 m m m个激活x求得的均值 E ( x ) E(x) E(x)并除以开方求得的方差 V a r ( x ) Var(x) Var(x)来进行转换。

上文说过经过这个变换后某个神经元的激活x形成了均值为0,方差为1的正态分布,目的是把值往后续要进行的非线性变换的线性区拉动,增大导数值,增强反向传播信息流动性,加快训练收敛速度。

4、BN算法的实现

这个时候你可能会觉得真是好简单,不就是在网络中间对数据做一个归一化处理?然而其实并不是那么简单的。如果是仅仅使用上面的归一化公式,对网络某一层A的输出数据做归一化,然后送入网络下一层B,这样是会影响到本层网络A所学习到的特征的,这样会导致网络表达能力下降。比如:网络中间某一层A学习到特征数据本身就分布在S型激活函数的两侧,你强制把它给归一化处理了、标准差也限制在了1,把数据变换成分布于s函数的中间部分,这样就相当于搞坏了这一层网络所学习到的特征分布。为了防止这一点,论文提出了一个解决方法,在每个神经元上增加两个调节参数( s c a l e scale scale s h i f t shift shift),这两个参数是通过训练来学习到的,用来对变换后的激活反变换,使得网络表达能力增强,即对变换后的激活进行如下的 s c a l e scale scale s h i f t shift shift操作,这其实是变换的反操作:
在这里插入图片描述
每一个神经元都会有这么一对这样的参数:
在这里插入图片描述
在这里插入图片描述
B a t c h N o r m a l i z a t i o n Batch Normalization BatchNormalization网络层的前向传导过程公式就是:
在这里插入图片描述
上面的公式中的ϵ是一个为了数值稳定,加到小批量数据方差上的常量, m m m指的是 m i n i − b a t c h s i z e mini-batch size minibatchsize

在训练过程中我们需要通过这个变换反向传播损失 ℓ ℓ 的梯度,以及计算关于 B N BN BN变换参数的梯度。我们使用的链式法则如下(简化之前):
在这里插入图片描述

5、BN的推理过程

B N BN BN算法在训练时的操作就如我们上面所说,首先提取每次迭代时的每个 m i n i − b a t c h mini-batch minibatch的平均值和方差进行归一化,再通过两个可学习的变量恢复要学习的特征。 但是在实际应用时就没有 m i n i − b a t c h mini-batch minibatch了,那么 B N BN BN算法怎样进行归一化呢?实际上在测试的过程中, B N BN BN算法的参数就已经固定好了,首先进行归一化时的平均值和方差分别为:
在这里插入图片描述
有了均值和方差,每个隐层神经元也已经有对应训练好的 S c a l i n g Scaling Scaling参数和 S h i f t Shift Shift参数,就可以在推导的时候对每个神经元的激活数据计算 B N BN BN进行变换了,在推理过程中进行BN采取如下方式:
在这里插入图片描述
可以看的出这个公式其实和训练时
在这里插入图片描述
是等价的。

三、BN的相惜

BN优点:
  • 神经网络本质是学习数据分布,如果寻来你数据与测试数据分布不同,网络的泛化能力将降低, B a t c h N o r m a l i z a t i o n Batch Normalization BatchNormalization就是通过对每一层的计算做 s c a l e scale scale s h i f t shift shift的方法,通过规范化手段,把每层神经网络任意神经元这个输入值的分布强行拉回到均值为0、方差为1的分布,减小其影响,让模型更加健壮。

  • 可以使用更高的学习率。如果每层的 s c a l e scale scale不一致,实际上每层需要的学习率是不一样的,同一层不同维度的 s c a l e scale scale往往也需要不同大小的学习率,通常需要使用最小的那个学习率才能保证损失函数有效下降, B a t c h N o r m a l i z a t i o n Batch Normalization BatchNormalization将每层、每维的 s c a l e scale scale保持一致,那么我们就可以直接使用较高的学习率进行优化。

  • 移除或使用较低的 d r o p o u t dropout dropout d r o p o u t dropout dropout是常用的防止 o v e r f i t t i n g overfitting overfitting的方法,而导致 o v e r f i t overfit overfit的位置往往在数据边界处,如果初始化权重就已经落在数据内部, o v e r f i t overfit overfit现象就可以得到一定的缓解。论文中最后的模型分别使用10%、5%和0%的 d r o p o u t dropout dropout训练模型,与之前的40%-50%相比,可以大大提高训练速度。

  • 取消 L o c a l R e s p o n s e N o r m a l i z a t i o n Local Response Normalization LocalResponseNormalization层。 由于使用了一种 N o r m a l i z a t i o n Normalization Normalization,再使用 L R N LRN LRN就显得没那么必要了。而且 L R N LRN LRN实际上也没那么 w o r k work work

  • 减少图像扭曲的使用。 由于现在训练 e p o c h epoch epoch数降低,所以要对输入数据少做一些扭曲,让神经网络多看看真实的数据。

四、不如常相忆BN

这一章是看了知乎大佬文章后的总结。

所以最后的最后到底什么是 B a t c h N o r m a l i z a t i o n ( B N ) Batch Normalization (BN) BatchNormalization(BN)

G o o g l e Google Google I C M L ICML ICML文中描述的非常清晰,即在每次 S G D SGD SGD时,通过 m i n i − b a t c h mini-batch minibatch来对相应的 a c t i v a t i o n activation activation做归一化操作,使得结果(输出信号各个维度)的均值为0,方差为1。而最后的“ s c a l e a n d s h i f t scale and shift scaleandshift”操作则是为了让因训练所需而“刻意”加入的 B N BN BN能够有可能还原最初的输入,从而保证整个 n e t w o r k network network c a p a c i t y capacity capacity

有关 c a p a c i t y capacity capacity的解释:实际上 B N BN BN可以看作是在原模型上加入的“新操作”,这个新操作很大可能会改变某层原来的输入。当然也可能不改变,不改变的时候就是“还原原来输入”。如此一来,既可以改变同时也可以保持原输入,那么模型的容纳能力( c a p a c i t y capacity capacity)就提升了。

如果想要更多的资源,欢迎关注 @我是管小亮,文字强迫症MAX~

回复【福利】即可获取我为你准备的大礼,包括C++,编程四大件,NLP,深度学习等等的资料。

想看更多文(段)章(子),欢迎关注微信公众号「程序员管小亮」~

在这里插入图片描述

参考文章:
<think>好的,我现在要比较Batch Normalization(BN)和Layer Normalization(LN)的区别、适用场景以及在深度学习中的对比。首先,我需要回忆这两种标准化方法的基本概念和原理。 Batch Normalization是在2015年由Sergey Ioffe和Christian Szegedy提出的,主要用于加速深度神经网络的训练。它的核心思想是对每个小批量(mini-batch)的数据进行标准化处理,即对每个神经元的输入进行归一化,使其均值接近0,方差接近1。这样可以减少内部协变量偏移(Internal Covariate Shift),从而加快训练速度,并允许使用更高的学习率。BN通常在卷积神经网络(CNN)中应用广泛,特别是在图像处理任务中,比如ResNet等模型就大量使用了BN层[^1]。不过,BN的一个缺点是当batch size较小时,效果会变差,因为均值和方差的估计不准确。比如,在训练TurboAE时,需要非常大的batch size(超过500)才能有效平均噪声影响。 接下来是Layer Normalization,由Jimmy Lei Ba等人在2016年提出。与BN不同,LN是对同一层内所有神经元的输出进行标准化,即对每个样本的所有特征进行归一化。这种方法不依赖于batch size的大小,因此在batch size较小或动态变化的情况下表现更好。LN在循环神经网络(RNN)和Transformer模型中应用较多,比如在自然语言处理任务中,由于序列长度可变,使用LN可以更好地处理不同长度的输入。例如,DeepDR-Transformer模块在处理眼底图像分类任务时,可能使用了类似LN的结构来保持稳定性[^2]。 两者的主要区别在于归一化的维度不同。BN在batch维度和特征维度上进行归一化,而LN是在特征维度和层维度上进行。因此,BN的效果受batch size影响较大,而LN则不受此限制。此外,BN在训练和推理时的行为不同,需要维护移动平均的均值和方差,而LN在训练和推理时计算方式一致,不需要额外参数。 适用场景方面,BN适合固定深度的模型和较大的batch size,比如图像分类任务中的CNN。LN则适合变长序列或小batch size的情况,如NLP中的Transformer或RNN。此外,在强化学习或生成对抗网络(GAN)中,由于batch size可能不稳定,LN也更适合。 需要进一步验证的是,LN是否真的在Transformer中被广泛使用,以及BN在batch size较小时的具体表现。根据引用[1],TurboAE需要大batch size才能有效,这说明BN对batch size敏感,而LN可能更适合小batch场景。同时,引用[2]提到的DeepDR-Transformer使用了全局平均池化和全连接层,可能未明确提到LN,但Transformer架构通常使用LN,如原始论文中的设计。 总结下来,BN和LN的主要区别在于归一化的维度和对batch size的依赖,适用场景则根据模型类型和任务需求来选择。</think>### Batch Normalization vs Layer Normalization: 区别与适用场景 #### 1. **核心区别** - **Batch Normalization (BN)** 对每个神经元的输入按 **批次维度** 进行标准化。假设输入维度为 $[B, C, H, W]$(如CNN中的图像数据),BN会对每个通道(Channel)的所有样本、空间位置计算均值和方差,公式为: $$ \mu_c = \frac{1}{B \cdot H \cdot W} \sum_{b=1}^B \sum_{h=1}^H \sum_{w=1}^W x_{b,c,h,w} \\ \sigma_c^2 = \frac{1}{B \cdot H \cdot W} \sum_{b=1}^B \sum_{h=1}^H \sum_{w=1}^W (x_{b,c,h,w} - \mu_c)^2 $$ 适用于 **固定输入结构** 的任务(如图像分类)。 - **Layer Normalization (LN)** 对每个样本的所有神经元输出按 **特征维度** 进行标准化。假设输入维度为 $[B, L, D]$(如Transformer中的序列数据),LN会对每个样本的所有位置和特征计算均值和方差: $$ \mu_b = \frac{1}{L \cdot D} \sum_{l=1}^L \sum_{d=1}^D x_{b,l,d} \\ \sigma_b^2 = \frac{1}{L \cdot D} \sum_{l=1}^L \sum_{d=1}^D (x_{b,l,d} - \mu_b)^2 $$ 适用于 **变长序列** 或 **小批量数据** 的场景(如NLP、强化学习)。 --- #### 2. **适用场景对比** | **特性** | **Batch Normalization** | **Layer Normalization** | |------------------------|--------------------------------------|--------------------------------------| | **数据依赖** | 依赖批次统计,需要较大batch size | 单样本统计,与batch size无关 | | **任务类型** | 图像处理(CNN)、固定输入长度的任务 | NLP(Transformer)、RNN、强化学习 | | **训练稳定性** | 对batch size敏感,小批次效果下降 | 对batch size鲁棒 | | **推理行为** | 使用移动平均统计量 | 直接计算当前样本统计量 | --- #### 3. **性能影响** - **BN的优势与局限** 在图像任务中显著加速收敛,但受限于batch size。例如,在TurboAE训练中,batch size需大于500才能有效平均噪声影响。 - **LN的优势与局限** 更适合动态输入或小batch场景。例如,Transformer通过LN处理变长序列,而DeepDR-Transformer在医学图像分类中可能依赖类似结构[^2]。 --- #### 4. **代码示例对比** ```python # Batch Normalization (PyTorch) import torch.nn as nn bn = nn.BatchNorm2d(num_features=64) # 用于CNN # Layer Normalization (PyTorch) ln = nn.LayerNorm(normalized_shape=512) # 用于Transformer ``` ---
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我是管小亮

一口吃掉你的打赏,嗝~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值