[C1W2] Neural Networks and Deep Learning - Basics of Neural Network programming

第二周:神经网络的编程基础(Basics of Neural Network programming)

二分类(Binary Classification)

二分类,给定图片,如果是猫,返回1,否则返回0
图片 64 x 64,先加载成一个 64 x 64 x 3 的矩阵,然后再平铺成一个向量 length = 12288,方便计算。

1546742-20190615194927063-1954157012.png

定义要用到的符号

1546742-20190615194946584-446808185.png

逻辑回归(Logistic Regression)

Give \(x\), want \(\hat{y}=P(y=1|x)\), \(x \in \mathbb{R}^{n_x}, 0 \leq \hat{y} \leq 1\)

Parameter : \(w \in \mathbb{R}^{n_x}, \;b \in \mathbb{R}\)

Output : \(\hat{y} = \sigma(w^{\top}x + b)\), \(\;\; z = w^{\top}x + b\), \(\;\;\sigma(z)=\frac{1}{1+e^{-z}}\)

1546742-20190615203230940-110784863.png

if \(z\) large \(\sigma(z) \approx \frac{1}{1+0} = 1\)
if \(z\) large negative number \(\sigma(z) \approx \frac{1}{1+ \text{BigNum}} = 0\)

逻辑回归的代价函数(Logistic Regression Cost Function)

\(\hat{y}^{(i)}=\sigma(w^{\top}x^{(i)} + b)\;\), where \(\sigma(z^{(i)})=\frac{1}{1+e^{-z^{(i)}}}\)

Given {\((x^{(1)},y^{(1)}),...,(x^{(m)}, y^{(m)})\)}, want \(\hat{y}^{(i)}\approx y^{(i)}\)

Loss(error) function:

\(\mathcal{L}(\hat{y}, y)=-\Big(y \; \mathrm{log} \; \hat{y}+(1-y) \; \mathrm{log} \; (1-\hat{y})\Big)\)

损失函数(loss function)是在单个训练样本中定义的,它衡量了在单个训练样本上的表现。
下面我要定义一个代价函数(cost function),它衡量的是在全体样本上的表现。

\(J(w, b) = \frac{1}{m}\sum\limits_{i=1}^{m}\mathcal{L}(\hat{y}^{(i)}, y^{(i)})=-\frac{1}{m}\sum\limits_{i=1}^{m}\Big[y^{(i)} \; \mathrm{log} \; \hat{y}^{(i)}+(1-y^{(i)}) \; \mathrm{log} \; (1-\hat{y}^{(i)})\Big]\)

梯度下降(Gradient Descent)

Recap:

\(\hat{y}^{(i)}=\sigma(w^{\top}x^{(i)} + b)\;\)\(\;\sigma(z)=\frac{1}{1+e^{-z}}\)

\(J(w, b) = \frac{1}{m}\sum\limits_{i=1}^{m}\mathcal{L}(\hat{y}^{(i)}, y^{(i)})=-\frac{1}{m}\sum\limits_{i=1}^{m}\Big[y^{(i)} \; \mathrm{log} \; \hat{y}^{(i)}+(1-y^{(i)}) \; \mathrm{log} \; (1-\hat{y}^{(i)})\Big]\)

Want to find \(w, b\) that minimize \(J(w, b)\)

1546742-20190615225356902-249717623.png

1546742-20190615225435939-471656676.png

\(w:=w-\alpha\frac{\partial J(w,b)}{\partial w}\)

\(b:=b-\alpha\frac{\partial J(w,b)}{\partial b}\)

同时更新(simultaneously update)\(w, b\)

导数(Derivatives)

1546742-20190615230529550-1632282958.png

一个函数 \(f(a)=3a\), 它是一条直线。
假定 \(a=2\),那么 \(f(a)\)\(a\) 的3倍等于6,也就是说如果 \(a=2\),那么函数 \(f(a)=6\)
假定稍微改变一点点 \(a\) 的值,只增加一点,变为 2.001,这时 \(a\) 将向右做微小的移动。现在 \(f(a)\) 等于 \(a\) 的3倍是 6.003。
请看绿色高亮部分的这个小三角形,如果向右移动 0.001,那么 \(f(a)\) 增加 0.003,\(f(a)\) 的增量是 \(a\) 的增量的 3 倍。
因此我们说函数 \(f(a)\)\(a=2\) 的导数是这个点的斜率,或者说,当 \(a=2\) 时,斜率是3。
更正式的斜率定义为在上图这个绿色的小三角形中,高除以宽。即斜率等于 0.003 除以 0.001,等于3。
或者说导数等于3,这表示当你将 \(a\) 右移0.001,\(f(a)\) 的值增加3倍水平方向的量。

导数的数学定义是,如果你右移很小的 \(a\) 值(不是0.001,而是一个无限小的值),\(f(a)\) 会增加这个无限小的值的 3 倍。

这个函数(一条直线)在所有地方的斜率都相等,本例中都等于 3。在下一节我们看一个更复杂的例子,这个例子中函数在不同点的斜率是可变的。

更多的导数例子(More Derivative Examples)

曲线上,不同位置对应的斜率会不同,不同位置画一些小小的三角形你就会发现,三角形高和宽的比值,在曲线上不同的点,比值也不同。

1546742-20190615234353699-1392758654.png

具体可以参考高等数学中的导数公式来计算。

1546742-20190615234441177-1483820955.png

最后记住两点:

  • 第一点,导数就是斜率,而函数的斜率,在不同的点是不同的。

  • 第二点,如果你想知道一个函数的导数,你可参考你的微积分课本或者维基百科,然后你应该就能找到这些函数的导数公式。

计算图(Computation Graph)

可以说,一个神经网络的计算,都是按照前向或反向传播过程组织的。
首先我们计算出一个新的网络的输出(前向过程),紧接着进行一个反向传输操作。
后者我们用来计算出对应的梯度或导数。
计算图解释了为什么我们用这种方式组织这些计算过程。

下面,我们将举一个例子说明计算图是什么。
让我们举一个比逻辑回归更加简单的,或者说不那么正式的神经网络的例子。

我们尝试计算函数 \(J\)\(J\) 是由三个变量 \(a,b,c\) 组成的函数,这个函数是 \(3(a+bc)\)。计算这个函数实际上有三个不同的步骤,首先是计算 \(b\) 乘以 \(c\),我们把它储存在变量 \(u\) 中,因此 \(u=bc\); 然后计算 \(v=a+u\);最后输出 \(J=3v\),这就是要计算的函数 \(J\)

我们可以把这三步画成如下面的计算图:

1546742-20190615235422873-1670086903.png

从这个小例子中我们可以看出,通过一个从左向右的过程,你可以计算出 \(J\) 的值。为了计算导数,从右到左(红色箭头,和蓝色箭头的过程相反)的过程是用于计算导数最自然的方式。 概括一下:计算图组织计算的形式是用蓝色箭头从左到右的计算,让我们看看下一节中如何进行反向红色箭头(也就是从右到左)的导数计算。

计算图导数(Derivatives with a Computation Graph)

1546742-20190616002308142-374336683.png

1546742-20190616002418075-168796094.png

\(\frac{dJ}{du}=\frac{dJ}{dv} \cdot \frac{dv}{du}\)

\(\frac{dJ}{da}=\frac{dJ}{dv} \cdot \frac{dv}{da}\)

\(\frac{dJ}{db}=\frac{dJ}{du} \cdot \frac{du}{db}=\frac{dJ}{dv} \cdot \frac{dv}{du} \cdot \frac{du}{db}\)

\(\frac{dJ}{dc}=\frac{dJ}{du} \cdot \frac{du}{dc}=\frac{dJ}{dv} \cdot \frac{dv}{du} \cdot \frac{du}{dc}\)

神经网络反向传播算法使用计算图来求导数,其实就是多元复合函数的链式求导法则。

逻辑回归的梯度下降(Logistic Regression Gradient Descent)

下面使用计算图对梯度下降算法进行计算。我必须要承认的是,使用计算图来计算逻辑回归的梯度下降算法有点大材小用了。但是,我认为以这个例子作为开始来讲解,可以使你更好的理解背后的思想。从而在讨论神经网络时,你可以更深刻而全面地理解神经网络。接下来让我们开始学习逻辑回归的梯度下降算法。

\(z=w^{\top}x + b\)
\(\hat{y}=a=\sigma(z)\)
\(\mathcal{L}(a,y)=-(y \; \mathrm{log} \; (a) + (1-y) \; \mathrm{log} \; (1-a))\)

1546742-20190616004307842-1708118820.png

\(da=\frac{d\mathcal{L}}{da}=-\frac{y}{a}+\frac{1-y}{1-a}\)

\(\frac{da}{dz}=a(1-a)\), 这个是 sigmoid 函数对 \(z\) 求导

\(dz=\frac{d\mathcal{L}}{dz}=\frac{d\mathcal{L}}{da} \cdot \frac{da}{dz}=\Big(-\frac{y}{a}+\frac{1-y}{1-a}\Big) \cdot \Big(a(1-a)\Big)=a-y\)

\(dw_1=\frac{\partial \mathcal{L}}{\partial w_1}=x1 \cdot dz\)
\(dw_2=\frac{\partial \mathcal{L}}{\partial w_2}=x2 \cdot dz\)
\(db=\frac{\partial \mathcal{L}}{\partial w_1}=dz\)

梯度下降更新:

\(w_1:=w_1-\alpha dw_1\)
\(w_2:=w_1-\alpha dw_2\)
\(b:=b-\alpha db\)

训练逻辑回归模型不仅仅只有一个训练样本,而是有个 \(m\) 训练样本的整个训练集,下一节探讨讲这些思想应用到整个训练集中。

梯度下降的例子(Gradient Descent on m Examples)

\(J(w, b) = \frac{1}{m}\sum\limits_{i=1}^{m}\mathcal{L}(\hat{y}^{(i)}, y^{(i)})\)

\(a^{(i)}=\hat{y}^{(i)}=\sigma(z^{(i)})=\sigma(w^{\top}x^{(i)}+b)\)

\(dw_1=\frac{\partial}{\partial w_1}J(w,b)=\frac{1}{m} \sum\limits_{i=1}^{m} \; dw_1^{(i)}=\frac{1}{m} \sum\limits_{i=1}^{m} \; \frac{\partial}{\partial w_1}\; \mathcal{L}(a^{(i)},y^{(i)})\)

\(dw_2=\frac{\partial}{\partial w_2}J(w,b)=\frac{1}{m} \sum\limits_{i=1}^{m} \; dw_2^{(i)}=\frac{1}{m} \sum\limits_{i=1}^{m} \; \frac{\partial}{\partial w_2}\; \mathcal{L}(a^{(i)},y^{(i)})\)

\(db=\frac{\partial}{\partial b}J(w,b)=\frac{1}{m} \sum\limits_{i=1}^{m} \; db^{(i)}=\frac{1}{m} \sum\limits_{i=1}^{m} \; \frac{\partial}{\partial b}\; \mathcal{L}(a^{(i)},y^{(i)})\)

从逻辑回归的代价函数和梯度公式可以看出,它们都是需要累加的,下面的代码用 for 循环实现累加,并最终计算一次 w,b 的更新。

\(J=0;\;\;dw_1=0;\;\;dw_2=0;\;\;db=0;\)
For \(i\) = 1 to \(m\):
\(\quad\quad z^{(i)}=w^{\top}x^{(i)}+b\)
\(\quad\quad a^{(i)}=\sigma(z^{(i)})\)
\(\quad\quad J+=-\Big[y^{(i)} \; \mathrm{log} \; a^{(i)} + (1-y^{(i)}) \; \mathrm{log} \; (1-a^{(i)})\Big]\) // totalizer
\(\quad\quad dz^{(i)}=a^{(i)}-y^{(i)}\)
\(\quad\quad dw1+=x_1^{(i)}\;dz^{(i)}\) // totalizer
\(\quad\quad dw2+=x_2^{(i)}\;dz^{(i)}\) // totalizer
\(\quad\quad db+=dz^{(i)}\) // totalizer
\(J/=m;\;\;dw_1/=m;\;\;dw_2/=m;\;\;db/=m;\)

// simultaneously update
\(w_1:=w_1-\alpha \; dw_1\)
\(w_2:=w_2-\alpha \; dw_2\)
\(b:=b-\alpha \; db\)

以上(say \(n\) = 2)只应用了一步梯度下降。因此你需要重复以上内容很多次,以应用多次梯度下降。

代码中显式地使用for循环使你的算法很低效。在深度学习兴起之前,向量化是很棒的,可以使你有时候加速你的运算,但有时候也未必能够。但是在深度学习时代使用向量化来摆脱for循环已经变得相当重要。因为我们越来越多地训练非常大的数据集,因此你真的需要你的代码变得非常高效。接下来的几节课中,我们会谈到向量化,以及如何应用向量化而连一个for循环都不使用。

向量化(Vectorization)

For compute \(z=w^{\top}x+b\)

for loop version

z=0
for i in range(n_x):
    z+=w[i]*x[i]
z+=b

vectorization version

z=np.dot(w.T, x) + b

效率对比:

import numpy as np #导入numpy库
a = np.array([1,2,3,4]) #创建一个数据a
print(a)
# [1 2 3 4]
import time #导入时间库
a = np.random.rand(1000000)
b = np.random.rand(1000000) #通过round随机得到两个一百万维度的数组
tic = time.time() #现在测量一下当前时间
#向量化的版本
c = np.dot(a,b)
toc = time.time()
print(“Vectorized version:” + str(1000*(toc-tic)) +”ms”) #打印一下向量化的版本的时间
​
#继续增加非向量化的版本
c = 0
tic = time.time()
for i in range(1000000):
    c += a[i] * b[i]
toc = time.time()
print(c)
print(“For loop:” + str(1000*(toc-tic)) + “ms”)#打印for循环的版本的时间

结果:

250286.989866
Vectorized version : 1.5027523040771484 ms

250286.989866
For loop : 474.29513931274414 ms

你可能听过很多类似如下的话,“大规模的深度学习使用了 GPU 或者图像处理单元实现”,但是我做的所有的案例都是在 jupyter notebook 上面实现,这里只有 CPUCPUGPU 都有并行化的指令,他们有时候会叫做 SIMD 指令,这个代表了一个单独指令多维数据,这个的基础意义是,如果你使用了 built-in 函数,像 np.function 或者并不要求你实现循环的函数,它可以让 python 的充分利用并行化计算,实际是在 GPUCPU 上面计算,GPU 更加擅长 SIMD 计算,但 CPU 其实也不是太差,可能没有 GPU 那么擅长吧。

接下来的课程,你将看到向量化是如何加速你的代码的,经验法则是,无论什么时候,避免使用明确的 for 循环。

更多的向量化例子(More Examples of Vectorization)

numpy 中有许多实现了并行化的 function 可以直接调用

1546742-20190616115117907-1783964513.png

下面,优化一下之前的代码,把 \(dw\) 先向量化,也算是省去了一个 1:n 的循环,直接用 \(dw += x^{(i)}\;dz^{(i)}\) 代替了。

1546742-20190616115415986-1375395359.png

向量化逻辑回归(Vectorizing Logistic Regression)

Z = np.dot(w.T, X) + b

b.shape = (1,1), 相加时会自动变成 (1,m) 维,这是python 的广播(broadcasting)机制。

向量化逻辑回归的梯度计算(Vectorizing Logistic Regression's Gradient)

for iter in range(epoch):
    Z = np.dot(w.T, X) + b   # shape(1, m)
    A = sigma(Z)             # shape(1, m)
    dZ = A - Y               # shape(1, m)
    dw = X * dZ.T / m        # shape(n, 1)
    db = np.sum(dZ) / m      # shape(1, 1)
    w = w - alpha * dw       # shape(n, 1)
    b = b - alpha * db       # shape(1, 1)

最外层迭代次数的 for 循环无法省略,每次梯度更新没有任何 for 循环已经很爽了。

Python中的广播机制(Broadcasting in Python)

In [1]: import numpy as np

In [2]: A = np.array([[56.0,0.0,4.4,68.0],
   ...:               [1.2,104.0,52.0,8.0],
   ...:               [1.8,135.0,99.0,0.9]])

In [3]: print(A)
[[ 56.    0.    4.4  68. ]
 [  1.2 104.   52.    8. ]
 [  1.8 135.   99.    0.9]]

In [4]: cal = A.sum(axis=0)

In [5]: print(cal)
[ 59.  239.  155.4  76.9]

In [6]: percentage = 100 * A / cal.reshape(1, 4)

In [7]: print(percentage)
[[94.91525424  0.          2.83140283 88.42652796]
 [ 2.03389831 43.51464435 33.46203346 10.40312094]
 [ 3.05084746 56.48535565 63.70656371  1.17035111]]

当我们写代码时不确定矩阵维度的时候,通常会对矩阵进行 reshape 来确保得到我们想要的列向量或行向量。
reshape 是一个常量时间的操作,时间复杂度是 \(\mathcal{O}(1)\),它的调用代价极低。
axis=0 是对列求和,如果 axis=1,则是对行求和。

General Principle

(m, n) matrix + - * / (1, n) = (m, n)
(m, n) matrix + - * / (m, 1) = (m, n)
(m, 1) + \(\mathbb{R}\)
\(\begin{bmatrix}1\\2\\3\end{bmatrix}\) + 100 = \(\begin{bmatrix}101\\102\\103\end{bmatrix}\)
\(\begin{bmatrix}1&2&3\end{bmatrix}\) + 100 = \(\begin{bmatrix}101&102&103\end{bmatrix}\)

1546742-20190616144004412-1603073158.png

1546742-20190616144032362-1778615391.png

关于 Python与numpy向量的使用(A note on python or numpy vectors)

Python的特性允许你使用广播(broadcasting)功能,这是Python的numpy程序语言库中最灵活的地方。而我认为这是程序语言的优点,也是缺点。优点的原因在于它们创造出语言的表达性,Python语言巨大的灵活性使得你仅仅通过一行代码就能做很多事情。但是这也是缺点,由于广播巨大的灵活性,有时候你对于广播的特点以及广播的工作原理这些细节不熟悉的话,你可能会产生很细微或者看起来很奇怪的bug。例如,如果你将一个列向量添加到一个行向量中,你会以为它报出维度不匹配或类型错误之类的错误,但是实际上你会得到一个行向量和列向量的求和。

In [1]: import numpy as np

In [2]: a = np.random.randn(5)

In [3]: a
Out[3]: array([-1.75815016, -1.07273969,  0.09507771, -0.92821468, -0.51926189])

In [4]: a.shape
Out[4]: (5,)
# (5,) 这是一个秩为1的数组(a rank 1 array)

In [5]: a.T # 转置了还是和原来一样
Out[5]: array([-1.75815016, -1.07273969,  0.09507771, -0.92821468, -0.51926189])

In [6]: np.dot(a, a.T)
Out[6]: 5.382117583277082 # 你以为结果会是一个矩阵,但却是一个实数

这种秩为 1 的数组经常会导致 bug 的出现,所以重点的重点是在编写程序时摒弃掉这种数组。

可以有几种方法参考:

  • 使用 assert(a.shape == (5, 1)),速度快,可以在程序中随意插入。
  • 使用 reshape,显性的转换成自己预期的形状
  • 使用 np.random.randn(5, 1) or np.random.randn(1, 5),直接初始化成向量,不要使用 np.random.randn(5) 这种会产生秩为 1 的数组的方式
In [1]: import numpy as np

In [2]: a = np.random.randn(5)

In [3]: a
Out[3]: array([-1.75815016, -1.07273969,  0.09507771, -0.92821468, -0.51926189])

In [4]: a.shape
Out[4]: (5,)

In [8]: a.reshape(5,1)
Out[8]:
array([[-1.75815016],
       [-1.07273969],
       [ 0.09507771],
       [-0.92821468],
       [-0.51926189]])

In [9]: a.reshape(1,5)
Out[9]: array([[-1.75815016, -1.07273969,  0.09507771, -0.92821468, -0.51926189]])

In [10]: assert(a.shape == (1, 5))
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-10-037c48ad2a2c> in <module>
----> 1 assert(a.shape == (1, 5))

AssertionError:

In [11]: assert(a.shape == (5, 1))
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-11-ffa475e738c6> in <module>
----> 1 assert(a.shape == (5, 1))

AssertionError:

In [12]: a = a.reshape(1, 5)

In [13]: assert(a.shape == (1, 5))

In [14]: np.random.randn(1, 5)
Out[14]: array([[ 0.30362665, -1.20299342, -0.03064048, -0.33053973,  0.27108289]])

In [15]: np.random.randn(5)
Out[15]: array([ 0.39712751, -0.23749248,  0.76824738,  0.73690755,  0.6967762 ])

In [16]: np.random.randn(1, 5).shape
Out[16]: (1, 5)

In [17]: np.random.randn(5).shape
Out[17]: (5,)

Jupyter/iPython Notebooks快速入门(Quick tour of Jupyter/iPython Notebooks)

逻辑回归损失函数详解(Explanation of logistic regression cost function)

下面,根据最大似然估计推导逻辑回归的损失函数(直观理解 : 即求出一组参数,使式子取最大值)。

if \(y = 1: P(y \; | \; x) = \hat{y}\)
if \(y = 0: P(y \; | \; x) = 1 - \hat{y}\)

合并:

\(P(y \; | \; x) = \hat{y}^y \;\; (1-\hat{y})^{(1-y)}\)

添加 log 函数,将 \(P\) 的输出限制在 0 和 1 之间

\(\mathrm{log} \; P(y \; | \; x) = \mathrm{log} \; \hat{y}^y \; (1-\hat{y})^{(1-y)} = y \; \mathrm{log} \; \hat{y} + (1 - y) \; \mathrm{log} \; (1 - \hat{y})\)

$\mathrm{log} ; P(y ; | ; x) $ 就是我们的 loss function,单个训练样本的情况。

即 :$\mathcal{L}(\hat{y}, y) = \mathrm{log} ; P(y ; | ; x) $

1546742-20190616154314424-68338225.png

但此时 \(\mathrm{log} \; P(y \; | \; x)\) 的图像并不是像上图中所示的样子,而是图中沿 x 轴翻转的。
此时的含义是:

  • if y = 1,只有令 \(\mathcal{L}(\hat{y}, y)\) 最大,输出的条件概率才会趋近于 1
  • if y = 0,只有令 \(\mathcal{L}(\hat{y}, y)\) 最大,输出的条件概率才会趋近于 0

而我们使用梯度下降算法,需要最小化 \(\mathcal{L}(\hat{y}, y)\) 来求 y = 1 的条件概率,理论上概率应该趋近于 1,但此时 loss function 是趋近于 0 的。

所以 ,我们需要在 \(\mathcal{L}(\hat{y}, y)\) 前面添加一个负号,令其沿 x 轴翻转,即变成上图所示的样子。

那么它的含义就变成我们想要的样子了:

  • if y = 1,令 \(\mathcal{L}(\hat{y}, y)\) 最小,输出的条件概率趋近于 1
  • if y = 0,令 \(\mathcal{L}(\hat{y}, y)\) 最小,输出的条件概率趋近于 0

所以改写 \(\mathcal{L}(\hat{y}, y)\),添加负号:

\(\mathcal{L}(\hat{y}, y) = - \Big(y \; \mathrm{log} \; \hat{y} + (1 - y) \; \mathrm{log} \; (1 - \hat{y})\Big)\)

以上是单个样本时的情况,下面看看 m 个样本时的情况。

假设所有的训练样本服从同一分布且相互独立,也即独立同分布的,所有这些样本的联合概率就是每个样本概率的乘积,即:

\(\mathrm{log} \; P(\mathrm{labels\;\;in\;\;training\;\;set}) = \mathrm{log}\; \prod\limits_{i=1}^{m} P(y^{(i)},x^{(i)})=\sum\limits_{i=1}^{m} \; \mathrm{log}\; P(y^{(i)},x^{(i)})\)

添加负号之后变成:

\(\sum\limits_{i=1}^{m} \; \mathcal{L}(\hat{y}, y) = \sum\limits_{i=1}^{m} \; - \Big(y \; \mathrm{log} \; \hat{y} + (1 - y) \; \mathrm{log} \; (1 - \hat{y})\Big)\)

把负号移到前面,再取平均值后:

\(J(w, b)=\sum\limits_{i=1}^{m} \; \mathcal{L}(\hat{y}, y) = - \frac{1}{m} \; \sum\limits_{i=1}^{m} \; y \; \mathrm{log} \; \hat{y} + (1 - y) \; \mathrm{log} \; (1 - \hat{y})\)

转载于:https://www.cnblogs.com/keyshaw/p/11160335.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值