CS231n课程笔记——反向传播

介绍

目的 在本节中,我们将通过对反向传播的直观理解来形成专业知识,反向传播是一种通过递归应用链式法则来计算表达式梯度的方法。了解这个过程及其微妙之处对你理解、有效开发、设计和调试神经网络至关重要。

问题陈述 本节研究的核心问题如下:给定函数 f ( x ) f(x) f(x),其中 x x x是一个输入向量,计算 f f f x x x处的梯度(即 ∇ f ( x ) \nabla f(x) f(x))。

目的 回想一下,我们对这个问题感兴趣的主要原因是,在神经网络这个特定情况下, f f f对应于损失函数( L L L),输入 x x x由训练数据和神经网络的权重组成。例如,损失可以是SVM损失函数,输入是训练数据 ( x i , y i ) (x_i,y_i) (xi,yi) i = 1 … N i=1…N i=1N,以及权重 W W W和偏差 b b b。请注意,我们认为训练数据是给定且固定的,权重是我们可以控制的变量(在机器学习中通常是这样)。因此,即使我们可以轻松地使用反向传播来计算输入示例 x i x_i xi上的梯度,但在实践中,我们通常只计算参数的梯度(例如 W W W b b b),以便我们可以用其来执行参数更新。然而,正如我们将在后面的课程中将看到的那样, x i x_i xi上的梯度在有些情况下仍然有用,例如可以用于可视化和解释神经网络可能在做的事情。

如果你来上这门课时对利用链式法则推导梯度已经很熟悉,我们仍然鼓励你至少浏览一下这一节,因为其中展现了一个很少被提及的反向传播的观点——实值电路中的反向流,你获得的任何见解可能会对整个课程有所帮助。

简单表达式和其梯度解释

让我们从简单的开始,这样就可以为更复杂的表达式阐释符号和约定。考虑一个简单的两数相乘函数 f ( x , y ) = x y f(x,y)=xy f(x,y)=xy。对其任意一个输入求偏导都是简单的微积分问题:

f ( x , y ) = x y → ∂ f ∂ x = y ∂ f ∂ y = x f(x,y) = x y \hspace{0.5in} \rightarrow \hspace{0.5in} \frac{\partial f}{\partial x} = y \hspace{0.5in} \frac{\partial f}{\partial y} = x f(x,y)=xyxf=yyf=x

解释 导数表示函数在某一特定点附近的无限小区域内相对于某一变量的变化率:

d f ( x ) d x = lim ⁡ h   → 0 f ( x + h ) − f ( x ) h \frac{df(x)}{dx} = \lim_{h\ \to 0} \frac{f(x + h) - f(x)}{h} dxdf(x)=h 0limhf(x+h)f(x)

与等式右边的除号不同,等式左边的除号不表示除法,而是表示操作符 d d x \frac{d}{dx} dxd作用于函数 f f f,并返回一个不同的函数(导数)。考虑上述表达式的一种很好的方式是,当 h h h很小时,函数很好地近似为一条直线,而导数就是其斜率。换句话说,每个变量的导数告诉你整个表达式对其值的敏感度。例如,如果 x = 4 x=4 x=4 y = − 3 y=−3 y=3,那么 f ( x , y ) = − 12 f(x,y)=−12 f(x,y)=12 x x x的导数是 ∂ f ∂ x = − 3 \frac{\partial f}{\partial x} = -3 xf=3。这告诉我们,如果稍微增加这个变量的值,整个表达式将减小(由于有负号),并且是原来的3倍。这可以通过变形上面的方程看出( f ( x + h ) = f ( x ) + h d f ( x ) d x f(x + h) = f(x) + h \frac{df(x)}{dx} f(x+h)=f(x)+hdxdf(x))。类似地,由于 ∂ f ∂ y = 4 \frac{\partial f}{\partial y} = 4 yf=4,若 y y y的值增加一个非常小的量 h h h,则函数的输出增加 4 h 4h 4h(由于其正号)。

每个变量的导数表示整个表达式对其值的敏感度。

如前所述,梯度 ∇ f \nabla f f是偏导数组成的向量,所以有 ∇ f = [ ∂ f ∂ x , ∂ f ∂ y ] = [ y , x ] \nabla f = [\frac{\partial f}{\partial x}, \frac{\partial f}{\partial y}] = [y, x] f=[xf,yf]=[y,x]。尽管梯度严格来说是一个向量,但为了简单起见,我们经常使用“ x x x上的梯度”这样的术语,而不是严格意义上正确的“ x x x上的偏导数”。

我们还可以推导加法运算的导数:

f ( x , y ) = x + y → ∂ f ∂ x = 1 ∂ f ∂ y = 1 f(x,y) = x + y \hspace{0.5in} \rightarrow \hspace{0.5in} \frac{\partial f}{\partial x} = 1 \hspace{0.5in} \frac{\partial f}{\partial y} = 1 f(x,y)=x+yxf=1yf=1

也就是说,无论 x x x y y y的值是多少, x x x y y y的导数都是 1 1 1。这是有道理的,因为增加 x x x y y y中的任何一个都会增加 f f f的输出,并且这种增加的速度与 x x x y y y的实际值无关(与上面的乘法不同)。本次课中介绍的最后一个经常使用的函数是 m a x max max操作:

f ( x , y ) = max ⁡ ( x , y ) → ∂ f ∂ x = 1 ( x > = y ) ∂ f ∂ y = 1 ( y > = x ) f(x,y) = \max(x, y) \hspace{0.5in} \rightarrow \hspace{0.5in} \frac{\partial f}{\partial x} = \mathbb{1}(x >= y) \hspace{0.5in} \frac{\partial f}{\partial y} = \mathbb{1}(y >= x) f(x,y)=max(x,y)xf=1(x>=y)yf=1(y>=x)

也就是说,(子)梯度在较大的输入上为 1 1 1,在另一个输入上为 0 0 0。直观地来看,如果输入是 x = 4 x=4 x=4 y = 2 y=2 y=2,那么最大值是 4 4 4,函数对 y y y的设置不敏感。也就是说,如果我们将 y y y增加一个微小的 h h h,函数将继续输出 4 4 4,因此梯度为零:其不产生影响。当然,如果 y y y增加很大(例如大于 2 2 2),那么 f f f的值也会发生变化,但是导数无法告诉我们如此大的变化对函数输入的影响;它们只对输入的微小以致无限小的变化提供信息,正如导数定义中的 lim ⁡ h → 0 \lim_{h \rightarrow 0} limh0所示。

对复合表达式应用链式法则

考虑涉及多个复合函数的更复杂的表达式,例如 f ( x , y , z ) = ( x + y ) z f(x,y,z)=(x+y)z f(x,y,z)=(x+y)z。这个表达式仍足够简单,可以直接微分,但我们将采用一种特殊的方法来处理,这将有助于理解反向传播背后的原理。特别注意,这个表达式可以分解为两个表达式: q = x + y q=x+y q=x+y f = q z f=qz f=qz。此外,如前一节所示,我们知道如何计算这两个表达式的导数。 f f f q q q z z z的乘积,所以 ∂ f ∂ q = z \frac{\partial f}{\partial q} = z qf=z ∂ f ∂ z = q \frac{\partial f}{\partial z} = q zf=q q q q x x x y y y的和,所以 ∂ q ∂ x = 1 \frac{\partial q}{\partial x} = 1 xq=1 ∂ q ∂ y = 1 \frac{\partial q}{\partial y} = 1 yq=1。然而,我们不一定关心中间值 q q q的梯度—— ∂ f ∂ q \frac{\partial f}{\partial q} qf的值是没有用的。我们最终感兴趣的是 f f f相对于输入 x x x y y y z z z的梯度。链式法则告诉我们,将这些梯度表达式串联在一起的正确方法是使用乘法。例如, ∂ f ∂ x = ∂ f ∂ q ∂ q ∂ x \frac{\partial f}{\partial x} = \frac{\partial f}{\partial q} \frac{\partial q}{\partial x} xf=qfxq。在实践中,只需将两个表示梯度的数字相乘。例如:

# set some inputs
x = -2; y = 5; z = -4

# perform the forward pass
q = x + y # q becomes 3
f = q * z # f becomes -12

# perform the backward pass (backpropagation) in reverse order:
# first backprop through f = q * z
dfdz = q # df/dz = q, so gradient on z becomes 3
dfdq = z # df/dq = z, so gradient on q becomes -4
dqdx = 1.0
dqdy = 1.0
# now backprop through q = x + y
dfdx = dfdq * dqdx  # The multiplication here is the chain rule!
dfdy = dfdq * dqdy  

[dfdx,dfdy,dfdz]保存梯度,表示变量x,y,zf上的灵敏度!这是反向传播最简单的例子。接下来,我们将使用更简洁的表示法,省略df前缀。例如,我们将简单地记为dq而不是dfdq,并始终假设是最终的输出上的梯度。

这个计算过程也可以通过电路图很好地可视化:


backprop_1

上图的实值“电路”展示了可视化的计算过程。前向传递计算从输入到输出的所有值(如绿色标记所示)。然后反向传递执行从末尾开始的反向传播,并递归地应用链式法则来计算梯度(如红色标记所示),直到电路的输入处。梯度可以被认为在电路中反向流动。


反向传播的直观理解

反向传播是一个漂亮的局部过程。电路图中的每个门获得一些输入,并可以立即计算出两个值:1. 它的输出值,2. 其输出对于其输入的局部梯度。请注意,这些门可以完全独立地执行此操作,而不需要知道其所嵌入的整个电路的其他细节。然而,一旦前向传递结束,在反向传播期间,电路门将最终得到其输出值在整个电路的最终输出上的梯度。根据链式法则,电路门应该将该梯度分别乘以其计算好的每个输入的梯度。

由于链式法则而产生的(对每个输入的)额外乘法,可以将单个或相对无用的门转化为复杂电路(如整个神经网络)中的一个齿轮。

让我们再次参考示例,直观了解其如何运作。加法门接收输入 [ − 2 , 5 ] [-2,5] [2,5]并计算得到输出 3 3 3。由于此门执行加法操作,它的两个输入的局部梯度都为 + 1 +1 +1。电路的其余部分计算出最终值 − 12 -12 12。在反向传递过程中,链式法则在电路中递归地向后应用,加法门(乘法门的输入之一)得到其输出的梯度为 − 4 -4 4。如果将整个电路拟人化为其希望输出更高的值(这有助于直观感受),那么我们可以认为电路“想要”加法门的输出更低(由于其梯度的负号),并具有 4 4 4。为了继续递归并将梯度链接起来,加法门将其获得的梯度乘到其所有输入的局部梯度上( x x x y y y的梯度都计算为 1 ∗ − 4 = − 4 1 * -4 = -4 14=4)。其符合预期效果:如果 x x x y y y减小,那么加法门的输出会减小,乘法门的输出会增加,这响应了它们的负梯度。

因此,可以认为反向传播是电路门之间的通信(通过梯度信号),交流它们是否希望其输出增加或减少(以及有多强),以便使最终的输出值更高。

模块化:Sigmoid示例

上面介绍的电路门是相对随意的。任何类型的可微函数都可以充当电路门,并且可以将多个门组合为单个门,或者在允许的情况下将函数分解为多个门。能说明这一点的一个表达式:
f ( w , x ) = 1 1 + e − ( w 0 x 0 + w 1 x 1 + w 2 ) f(w,x) = \frac{1}{1+e^{-(w_0x_0 + w_1x_1 + w_2)}} f(w,x)=1+e(w0x0+w1x1+w2)1
稍后我们会看到,上述表达式描述一个使用了sigmoid激活函数的二维神经元(输入为 x \mathbf{x} x,权重为 w \mathbf{w} w)。但此时先将其简单地视为一个从输入 x \mathbf{x} x w \mathbf{w} w映射到单个数字的函数。该函数由多种电路门组成。除了上面已经描述过的三种门 ( a d d , m u l , m a x ) (add, mul, max) (add,mul,max),还有四种如下:
f ( x ) = 1 x → d f d x = − 1 / x 2 f c ( x ) = c + x → d f d x = 1 f ( x ) = e x → d f d x = e x f a ( x ) = a x → d f d x = a f(x) = \frac{1}{x} \hspace{1in} \rightarrow \hspace{1in} \frac{df}{dx} = -1/x^2 \\ f_c(x) = c + x \hspace{1in} \rightarrow \hspace{1in} \frac{df}{dx} = 1 \\ f(x) = e^x \hspace{1in} \rightarrow \hspace{1in} \frac{df}{dx} = e^x \\ f_a(x) = ax \hspace{1in} \rightarrow \hspace{1in} \frac{df}{dx} = a f(x)=x1dxdf=1/x2fc(x)=c+xdxdf=1f(x)=exdxdf=exfa(x)=axdxdf=a
其中函数 f c f_c fc将输入平移常数 c c c f a f_a fa将输入缩放常数 a a a。准确来说,它们是加法和乘法的特殊情况,但我们在此将其引入为(新的)一元门,因为不需要常数 c c c a a a的梯度。完整的电路如下所示:


backprop_2

使用sigmoid激活函数的2D神经元的示例电路。输入是 [ x 0 , x 1 ] [x0,x1] [x0,x1],神经元的(可学习的)权重是 [ w 0 , w 1 , w 2 ] [w0,w1,w2] [w0,w1,w2]。稍后我们将看到,神经元计算输入的点积,然后sigmoid激活函数会压缩它,使其位于 0 0 0 1 1 1之间。


在上面的例子中,我们看到有一长串函数应用,它们作用于 w \mathbf{w} w x \mathbf{x} x之间的点积。这些操作实现的函数称为sigmoid函数 σ ( x ) \sigma(x) σ(x)。事实证明,对sigmoid函数求导可以简化如下(在经历了有趣而棘手的分子加 1 1 1 1 1 1之后):
σ ( x ) = 1 1 + e − x → d σ ( x ) d x = e − x ( 1 + e − x ) 2 = ( 1 + e − x − 1 1 + e − x ) ( 1 1 + e − x ) = ( 1 − σ ( x ) ) σ ( x ) \sigma(x) = \frac{1}{1+e^{-x}} \\ \rightarrow \hspace{0.3in} \frac{d\sigma(x)}{dx} = \frac{e^{-x}}{(1+e^{-x})^2} = \left( \frac{1 + e^{-x} - 1}{1 + e^{-x}} \right) \left( \frac{1}{1+e^{-x}} \right) = \left( 1 - \sigma(x) \right) \sigma(x) σ(x)=1+ex1dxdσ(x)=(1+ex)2ex=(1+ex1+ex1)(1+ex1)=(1σ(x))σ(x)
如上所示,其梯度变得非常简单。例如,sigmoid表达式接收到输入 1.0 1.0 1.0,并在正向传递期间计算得到输出 0.73 0.73 0.73。根据上面的推导,局部梯度将简单地计算为 ( 1 − 0.73 ) ∗ 0.73   = 0.2 (1 - 0.73)* 0.73 ~= 0.2 (10.73)0.73 =0.2,与之前电路的计算一致(参见上图),只是这种方法使用了单一、简单和有效的表达式(并且具有较少的数值问题)。因此,在实际应用中,将这些操作组合成单个电路门是非常有用的。用代码说明这个神经元的反向传播:

w = [2,-3,-3] # assume some random weights and data
x = [-1, -2]

# forward pass
dot = w[0]*x[0] + w[1]*x[1] + w[2]
f = 1.0 / (1 + math.exp(-dot)) # sigmoid function

# backward pass through the neuron (backpropagation)
ddot = (1 - f) * f # gradient on dot variable, using the sigmoid gradient derivation
dx = [w[0] * ddot, w[1] * ddot] # backprop into x
dw = [x[0] * ddot, x[1] * ddot, 1.0 * ddot] # backprop into w
# we're done! we have the gradients on the inputs to the circuit

应用的高级技巧:分段反向传播 如上面代码所示,在实践中,将正向传递分解为容易实现的阶段总是有帮助的。例如,这里创建了中间变量dot,保存wx的点积输出。在反向传递期间,接着(按相反的顺序)计算对应的梯度变量(例如ddot,最终是dwdx)。

本节的重点是,反向传播如何执行,以及将向前函数的哪些部分视为电路门更方便。了解表达式的哪些部分具有简单局部梯度是有帮助的,以便用最少的代码和工作将它们链接在一起。

反向传播实战:分段计算

让我们用另一个例子来说明。假设有如下函数:
f ( x , y ) = x + σ ( y ) σ ( x ) + ( x + y ) 2 f(x,y) = \frac{x + \sigma(y)}{\sigma(x) + (x+y)^2} f(x,y)=σ(x)+(x+y)2x+σ(y)
这个函数是完全无用的,也不清楚为什么要计算其梯度,只是因为它在实践中是反向传播的一个很好的例子。如果一开始就对 x x x y y y求导,你会得到非常庞大而复杂的表达式。而事实证明,这样做是完全没有必要的,因为不需要写出一个显式的函数来计算梯度,只需要知道如何计算它。下面构造这个表达式的正向传递:

x = 3 # example values
y = -4

# forward pass
sigy = 1.0 / (1 + math.exp(-y)) # sigmoid in numerator   #(1)
num = x + sigy # numerator                               #(2)
sigx = 1.0 / (1 + math.exp(-x)) # sigmoid in denominator #(3)
xpy = x + y                                              #(4)
xpysqr = xpy**2                                          #(5)
den = sigx + xpysqr # denominator                        #(6)
invden = 1.0 / den                                       #(7)
f = num * invden # done!                                 #(8)

我们完成了直到表达式结束的正向传递。代码包含了多个中间变量,其中每个变量都只是简单的表达式,并已知其局部梯度。因此,很容易计算出反向传播:向后移动,对正向传播过程中的每个变量(sigy, num, sigx, xpy, xpysqr, den, invden),都有相同的以d开头的变量,它们保存电路的输出相对于该变量的梯度。此外,反向传播的每一部分都涉及计算该表达式的局部梯度,并将其与该表达式上的梯度通过乘法链接起来。对于每一行,下面还突出显示了它指向前向传递的哪个部分:

# backprop f = num * invden
dnum = invden # gradient on numerator                             #(8)
dinvden = num                                                     #(8)
# backprop invden = 1.0 / den 
dden = (-1.0 / (den**2)) * dinvden                                #(7)
# backprop den = sigx + xpysqr
dsigx = (1) * dden                                                #(6)
dxpysqr = (1) * dden                                              #(6)
# backprop xpysqr = xpy**2
dxpy = (2 * xpy) * dxpysqr                                        #(5)
# backprop xpy = x + y
dx = (1) * dxpy                                                   #(4)
dy = (1) * dxpy                                                   #(4)
# backprop sigx = 1.0 / (1 + math.exp(-x))
dx += ((1 - sigx) * sigx) * dsigx # Notice += !! See notes below  #(3)
# backprop num = x + sigy
dx += (1) * dnum                                                  #(2)
dsigy = (1) * dnum                                                #(2)
# backprop sigy = 1.0 / (1 + math.exp(-y))
dy += ((1 - sigy) * sigy) * dsigy                                 #(1)
# done! phew

注意:

缓存正向传递的变量 在计算向后传递时,一些在向前传递中使用过的变量是非常有用的。实践中代码能够缓存这些变量,以便在反向传播期间使用它们。如果难以实现,也可以重新计算(但有些浪费)。

分叉处的梯度累加 正向表达式多次涉及变量 x x x y y y,因此当执行反向传播时,必须小心地使用+=而不是=来累积这些变量的梯度(否则会覆盖它)。这遵循了微积分中的多变量链式法则,即如果一个变量分支到电路的不同部分,那么流回它的梯度就会相加。

反向流模式

有趣的是,在许多情况下,反向流动的梯度可以直观地解释。例如,神经网络中最常用的三种门( a d d , m u l , m a x add,mul,max add,mul,max),都对其反向传播中的行为有非常简单的解释。考虑下面的电路示例:


backprop_3

一个示例电路,其直观展示了反向传播为计算输入的梯度而执行的操作。 s u m sum sum操作将梯度均等地分布到所有输入。 m a x max max操作将梯度传递到更大的输入上。乘法门接收输入,交换它们并乘以其梯度。


以上图为例可以看到:

加法门总是接受其输出的梯度,并将其同等分配给所有输入,无论它们在正向传递期间的值是什么。因为加法操作的局部梯度都是 + 1.0 +1.0 +1.0,所以所有输入的梯度都等于输出的梯度,因为它将 × 1.0 \times 1.0 ×1.0(保持不变)。在上面的示例中, + + +门将梯度 2.00 2.00 2.00同等地路由到它的两个输入上,保持不变。

最大门路由梯度。与加法门将梯度不变地分布到其所有输入上不同,最大门仅将梯度(不变地)分布到其一个输入上(正向传递期间具有最大值的那个输入)。这是因为最大门对最大值的局部梯度是 1.0 1.0 1.0,而其他值为 0.0 0.0 0.0。在上面的示例中, m a x max max操作将梯度 2.00 2.00 2.00路由到变量 z z z,因为该变量的值比 w w w高,而 w w w上的梯度为零。

乘法门有点不太容易解释。它的局部梯度就是其输入值(只是交换了),并在链式法则期间乘上其输出的梯度。在上面的例子中, x x x上的梯度是 − 8.00 -8.00 8.00,也就是 − 4.00 × 2.00 -4.00 \times 2.00 4.00×2.00

不直观的影响及其后果 若乘法门的一个输入非常小,而另一个输入非常大,那么乘法门将做一些稍微不直观的事情:它将给小输入分配大梯度,给大输入分配小梯度。在线性分类器中,权重点积(乘)输入 w T x i w^Tx_i wTxi,这意味着数据的大小对权重的梯度大小有影响。例如,如果在预处理期间将所有输入数据 x i x_i xi乘上 1000 1000 1000,那么权重的梯度也将增大 1000 1000 1000倍,则必须将学习率也降低该倍数来补偿。这就是为什么预处理非常重要,其有时会以微妙的方式存在!对梯度如何流动有了直观理解,可以帮助调试这些情况。

矢量操作的梯度

虽然上面几节都是关于单变量的,但是所有的概念都可以直接扩展到矩阵和向量运算中,只不过必须更加关注维度和转置运算。

矩阵-矩阵相乘的梯度 最棘手的操作可能就是矩阵-矩阵乘法了(其泛化了矩阵-向量乘法和向量-向量乘法):

# forward pass
W = np.random.randn(5, 10)
X = np.random.randn(10, 3)
D = W.dot(X)

# now suppose we had the gradient on D from above in the circuit
dD = np.random.randn(*D.shape) # same shape as D
dW = dD.dot(X.T) #.T gives the transpose of the matrix
dX = W.T.dot(dD)

提示:使用维度分析! 不需要记住dWdX的表达式,因为它们很容易根据维度重新推导。例如,计算得到的权重上的梯度dW必须与W具有相同的大小,而且必须取决于XdD的矩阵乘法(就像当XW都是单个数字时一样)。总有一种方法可以满足上述的尺寸要求。例如,X的大小为 [ 10 × 3 ] [10 \times 3] [10×3]dD的大小为 [ 5 × 3 ] [5 \times 3] [5×3],如果想要dWW的形状为 [ 5 × 10 ] [5 \times 10] [5×10],那么实现这一目标的唯一方法是使用dD.dot(X.T),如上所示。

使用小且明确的例子 一开始可能会很难推导出一些向量化表达式的梯度更新式。建议先明确地写出一个最小向量化示例,然后在纸上推导其梯度,最后将模式推广到其高效的向量化形式。

Erik Learned-Miller还写了一篇关于求矩阵或向量导数的更长的相关文档,可能会有所帮助。链接

总结

  • 我们对梯度意味着什么、它们如何在电路中反向流动、它们如何沟通电路的哪些部分应该增加或减少,以及用多大力使最终输出更高形成了直观的认识。
  • 我们讨论了分段计算对于反向传播在实践中的重要性。总是期望将函数分解为可以轻松推导出局部梯度的模块,然后使用链式法则将它们串联起来。至关重要的是,永远不用把这些表达式写在纸上,并完全符号化地微分它们,因为不需要输入变量的梯度的显式数学方程。因此,将表达式分解为多个阶段就可以独立地微分每个阶段(矩阵向量乘法、最大值运算或求和运算等),然后一步一步地通过变量进行反向传播。

在下一节中,我们将开始定义神经网络,而且将利用反向传播有效地计算损失函数相对于其参数的梯度。换句话说,我们现在已经准备好了训练神经网络,本课程中最难的概念部分已经结束!卷积网络将是一小步。

参考文献

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值