多层感知机与反向传播【MLP】

机器学习笔记

第一章 机器学习简介
第二章 感知机
第三章 支持向量机
第四章 朴素贝叶斯分类器
第五章 Logistic回归
第六章 线性回归和岭回归
第七章 多层感知机与反向传播【MLP】
第八章 主成分分析【PCA降维】
第九章 隐马尔可夫模型
第十章 奇异值分解


多层感知机是一种基于人工神经元网络的模型,它通过多个层次的神经元组成的网络结构来进行学习和推理。而反向传播算法则是训练多层感知机模型的核心算法之一,它通过迭代优化模型参数来最小化损失函数,从而使得模型能够更好地拟合训练数据。

在本篇博客中,我们将深入探讨多层感知机与反向传播算法的原理和应用。首先,我们将介绍多层感知机的基本原理和结构,包括神经元的组织方式、各层之间的连接方式以及激活函数的作用。随后,我们将详细解释反向传播算法的原理和实现细节,包括正向传播和反向传播的过程,以及如何使用梯度下降方法来更新模型参数。最后,我们将通过一个Python实例来展示如何使用MLP来识别手写数字,从而加深对多层感知机和反向传播算法的理解。

一、多层感知机

1 神经元

回顾感知机、线性可分支持向量机、Logistic回归模型,这些模型都涉及到 x = ( x ( 1 ) , x ( 2 ) , … , x ( n ) ) T x=(x^{(1)},x^{(2)},\ldots,x^{(n)})^T x=(x(1),x(2),,x(n))T各维特征的线性组合 w ⋅ x + b = ∑ i = 1 n w i x ( i ) + b , w\cdot x+b=\sum_{i=1}^nw_ix^{(i)}+b, wx+b=i=1nwix(i)+b,这里 w = ( w 1 , w 2 , ⋯   , w n ) T ∈ R n w=(w_1,w_2,\cdots,w_n)^T\in\mathbb{R}^n w=(w1,w2,,wn)TRn b ∈ R b\in\mathbb{R} bR为参数,我们记这个输出为 z = w ⋅ x + b z= w\cdot x+b z=wx+b.

  • 感知机在线性输出后,加上符号函数,得到最后的输出 f ( x ) = sign ⁡ ( z ) f(x)=\operatorname{sign}(z) f(x)=sign(z);
  • 线性可分支持向量机也是机上符号函数;
  • Logistic回归是引入sigmoid函数,输出 σ ( z ) \sigma(z) σ(z).

在感知机部分,我们也指出了感知机可以看做单个“神经元”,这里我们给出一般神经元的定义:

人工神经元(Artificial Neuron),简称神经元(Neuron),是构成神经网络的基本单元,其主要是模拟生物神经元的结构和特性,接收一组输入信号并产生输出.

其结构为:

截屏2023-12-26 13.33.05

这样来看,感知机、线性可分支持向量机、Logistic回归模型都可以看做一个神经元,只不过激活函数不同,优化算法不同。

2 激活函数

激活函数在神经元中非常重要的. 为了增强网络的表示能力和学习能力,激活函数需要具备以下几点性质:

  1. 连续并可导(允许少数点上不可导)的非线性函数.
  2. 可导的激活函数可以直接利用数值优化的方法来学习网络参数.
  3. 激活函数及其导函数要尽可能的简单,有利于提高网络计算效率.
  4. 激活函数的导函数的值域要在一个合适的区间内,不能太大也不能太小,否则会影响训练的效率和稳定性.

为什么要在每个线性输出后加上激活函数呢?因为激活函数是非线性函数,可以给线性网络加入非线性部分,使得网络更复杂,可表示能力更强!常见的激活函数有:线性激活函数、阈值或阶跃激活函数、S形激活函数等。感知机是用sign函数将输出值映射到1,-1。但sign函数不是处处连续,求导会出问题,神经网络中常用sigmoid函数,这个函数连续且导数性质很好。sigmoid函数如下: σ ( x ) = 1 1 + e − x , \sigma(x)=\frac{1}{1+e^{-x}}, σ(x)=1+ex1,其导数为:
d σ ( x ) d x = σ ( x ) ( 1 − σ ( x ) ) . \frac{d\sigma(x)}{dx}=\sigma(x)(1-\sigma(x)). dxdσ(x)=σ(x)(1σ(x)).
截屏2023-12-26 13.45.31

3 前馈神经网络

给定一组神经元,我们可以将神经元作为节点来构建一个网络.不同的神经网络模型有着不同网络连接的拓扑结构.一种比较直接的拓扑结构是前馈网络.前馈神经网络(Feedforward Neural Network,FNN)是最早发明的简单人工神经网络.

  • 前馈神经网络也经常称为多层感知器(Multi-Layer Perceptron,MLP).
  • 但多层感知器的叫法并不是十分合理,因为前馈神经网络其实是由多层的 Logistic 回归模型(sigmoid函数,连续的非线性函数)组成,而不是由多层的感知器(符号函数,不连续的非线性函数)组成。

截屏2023-12-26 13.51.03

给定训练数据集 D = { ( x i , y i ) } i = 1 N D=\{(x_i,y_i)\}_{i=1}^N D={(xi,yi)}i=1N, 其中 x i ∈ R n , y i ∈ R k x_i\in\mathbb{R}^n,\quad y_i\in\mathbb{R}^k xiRn,yiRk。假定我们构造如上图所示的单隐层前馈神经网络,其中

  • 输入层有n 个神经元分别用于接受数据 x x x n n n维特征向量 ( x ( 1 ) , x ( 2 ) , … , x ( n ) ) T (x^{(1)},x^{(2)},\ldots,x^{(n)})^T (x(1),x(2),,x(n))T 的每个分量;
  • 输出层有 k k k 个功能神经元分别对应于输出 y = ( y ( 1 ) , y ( 2 ) , ⋯   , y ( k ) ) T y=(y^{(1)},y^{(2)},\cdots,y^{(k)})^T y=(y(1),y(2),,y(k))T 的每个分量,且输出层第1个神经元的阈值为 θ l ; \theta_l; θl;
  • 隐层有 m m m 个功能神经元,且隐层第 t t t个神经元的阈值为 γ t ; \gamma_t; γt;
  • 输入层第 j j j 个神经元与隐层第 t t t个神经元之间的连接权值为 w j t ; w_{jt}; wjt;
  • 隐层第 t t t个神经元与输出层第1个神经元之间的连接权值为 v t l ; v_{tl}; vtl;

隐层 t t t个神经元接受到的总输入为 α t ( x ) = ∑ i = 1 n w j t x ( j ) \alpha_t(x)=\sum_{i=1}^nw_{jt}x^{(j)} αt(x)=i=1nwjtx(j),输出为
z ( t ) ( x ) = σ ( α t ( x ) − γ t ) ; z^{(t)}(x)=\sigma(\alpha_t(x)-\gamma_t); z(t)(x)=σ(αt(x)γt);输出层第 l l l个神经元接受到的总输入为 β l ( x ) = ∑ t = 1 m v t l z ( t ) ( x ) \beta_l(x)=\sum_{t=1}^mv_{tl}z^{(t)}(x) βl(x)=t=1mvtlz(t)(x),输出为

y ( l ) = σ ( β l ( x ) − θ l ) . y^{(l)}=\sigma(\beta_l(x)-\theta_l). y(l)=σ(βl(x)θl).我们以 Θ \Theta Θ表示该单隐层前馈网络中的参数集,即

Θ = ( ⋃ t = 1 m { w j t } j = 1 n ) ∪ ( ⋃ l = 1 k { v t l } t = 1 m ) ∪ { θ l } l = 1 k ∪ { γ t } t = 1 m , \Theta=(\bigcup_{t=1}^m\{w_{jt}\}_{j=1}^n)\cup(\bigcup_{l=1}^k\{v_{tl}\}_{t=1}^m)\cup\{\theta_l\}_{l=1}^k\cup\{\gamma_t\}_{t=1}^m, Θ=(t=1m{wjt}j=1n)(l=1k{vtl}t=1m){θl}l=1k{γt}t=1m,
其中共包含 ( n + k + 1 ) m + k (n+k+1)m+k (n+k+1)m+k个参数.按照单隐层神经网络的构造,可以不断的增加隐层,构成多层前馈神经网络,数据一层一层向前传递。那么问题是,这一层一层的参数怎么学习呢?求导的链式法则!

二、反向传播

反向传播算法,通常缩写为「BackProp」,是几种训练人工神经网络的方法之一。这是一种监督学习方法,即通过标记的训练数据来学习(有监督者来引导学习)。简单说来,BackProp 就像“从错误中学习”。监督者在人工神经网络犯错误时进行纠正。其主要思想是:

  1. 将训练集数据输入到ANN的输入层,经过隐藏层,最后达到输出层并输出结果;(前向传播)
  2. 由于ANN的输出结果与实际结果有误差,则计算估计值与实际值之间的误差,并将该误差从输出层向隐藏层反向传播,直至传播到输入层;(误差反向传播
  3. 在反向传播的过程中,根据误差调整各种参数的值;(链式法则更新参数
  4. 不断迭代上述过程,直至收敛。

1 单个节点

截屏2023-12-26 14.06.54

如上图所示,激活函数我们采用 σ ( x ) \sigma(x) σ(x)函数,我们假设:

  1. 输入样本为 ( x ⃗ d , t d ) d ∈ D (\vec x_d,t_d)_{d\in D} (x d,td)dD,其中输入 x d ⃗ = ( x 1 , x 2 , ⋯   , x n ) \vec{x_d}=( x_1,x_2,\cdots ,x_n) xd =(x1,x2,,xn) t d t_d td标签(已知输出);
  2. 权重为 w i w_i wi ,而 n e t d = ∑ i w i x i net_d=\sum\limits_{i} w_ix_i netd=iwixi o d = σ ( n e t d ) o_d=\sigma(net_d) od=σ(netd), o d o_d od为单个sigmoid结点的输出;
  3. 损失函数我们取平方误差, E ( w ) = 1 2 ∑ ( f w ( x d ) − t d ) 2 E(w)=\frac{1}{2}\sum (f_w(x_d)-t_d)^2 E(w)=21(fw(xd)td)2.

所以问题就转化为找到 w w w,使得损失函数最小,而这里可得
E ( w ) = 1 2 ∑ ( t d − o d ) 2 E(w)=\frac{1}{2}\sum (t_d-o_d)^2 E(w)=21(tdod)2我们想用梯度法来进行优化,对 w w w求导,则由链式法则可得:
∂ E ∂ w i = ∂ E ∂ o d ⋅ ∂ o d ∂ n e t d ⋅ ∂ n e t d ∂ w i \frac{\partial E}{\partial w_i}=\frac{\partial E}{\partial o_d}\cdot \frac{\partial o_d}{\partial net_d}\cdot \frac{\partial net_d}{\partial w_i} wiE=odEnetdodwinetd而由求导法则和sigmoid函数的导数可得:
∂ E ∂ o d = − ( t d − o d ) ∂ o d ∂ n e t d = o d ( 1 − o d ) ∂ n e t d ∂ w i = ∂ w ⋅ x d ∂ w i = x i \frac{\partial E}{\partial o_d}=-(t_d-o_d)\qquad \frac{\partial o_d}{\partial net_d}=o_d(1-o_d)\qquad \frac{\partial net_d}{\partial w_i} =\frac{\partial w\cdot x_d}{\partial w_i}=x_i odE=(tdod)netdod=od(1od)winetd=wiwxd=xi故可得:
∂ E ∂ w i = − ( t d − o d ) o d ( 1 − o d ) x i \frac{\partial E}{\partial w_i}=-(t_d-o_d)o_d(1-o_d)x_i wiE=(tdod)od(1od)xi δ d = ( t d − o d ) o d ( 1 − o d ) \delta_d=(t_d-o_d)o_d(1-o_d) δd=(tdod)od(1od),则:
∂ E ∂ w i = − δ d x i \frac{\partial E}{\partial w_i}=-\delta_d x_i wiE=δdxi设学习率为 η \eta η,则可得到更新规则:
w i , n − η ∂ E ∂ w i , n → w i , n + 1 w_{i,n}-\eta \frac{\partial E}{\partial w_{i,n}} \to w_{i,n+1} wi,nηwi,nEwi,n+1于是,我们得到了单层结点参数的更新规则,下面将单层推广到多层。

2 由单层到多层结点

清楚单个sigmoid结点的性质后,便可将其推广到多层结点,通过前向传播得到输出后,便可得到误差,然后可得到最后一层结点权重的更新值,得到最后一层w更新值后,便可继续反向得到倒数第二层,以此类推反向传播便可更新所有层的参数。下面以一个具体的两层结点为例:

截屏2023-12-26 14.13.29

这里以计算单隐层中 ∂ E ∂ w 1 \frac{\partial E}{\partial w_1} w1E为例,输出有两个结点,故可得:
E t o t a l = E o 1 + E o 2 ⇒ ∂ E t o t a l ∂ w 1 = ∂ E o 1 ∂ w 1 + ∂ E o 2 ∂ w 1 E_{total}=E_{o1}+E_{o2} \quad \Rightarrow \quad \frac{\partial E_{total}}{\partial w_1}=\frac{\partial E_{o1}}{\partial w_1}+\frac{\partial E_{o2}}{\partial w_1} Etotal=Eo1+Eo2w1Etotal=w1Eo1+w1Eo2进一步可得:
∂ E o 1 ∂ w 1 = ∂ E o 1 ∂ o u t o 1 ⋅ ∂ o u t o 1 ∂ n e t o 1 ⋅ ∂ n e t o 1 ∂ o u t h 1 ⋅ ∂ o u t h 1 ∂ n e t h 1 ⋅ ∂ n e t h 1 ∂ w 1 \frac{\partial E_{o1}}{\partial w_1}= \frac{\partial E_{o1}}{\partial out_{o1}}\cdot \frac{\partial out_{o1}}{\partial net_{o1}}\cdot \frac{\partial net_{o1}}{\partial out_{h1}}\cdot \frac{\partial out_{h1}}{\partial net_{h1}}\cdot \frac{\partial net_{h1}}{\partial w_1} w1Eo1=outo1Eo1neto1outo1outh1neto1neth1outh1w1neth1同理可得 ∂ E o 2 ∂ w 1 \frac{\partial E_{o2}}{\partial w_1} w1Eo2,进而可得:
∂ E t o t a l ∂ w 1 = ( ∑ o ∂ E t o t a l ∂ o u t o ⋅ ∂ o u t o ∂ n e t o ⋅ ∂ n e t o ∂ o u t h 1 ) ⋅ ∂ o u t h 1 ∂ n e t h 1 ⋅ ∂ n e t h 1 ∂ w 1 \frac{\partial E_{total}}{\partial w_1}= (\sum\limits_{o}\frac{\partial E_{total}}{\partial out_{o}}\cdot\frac{\partial out_{o}}{\partial net_{o}}\cdot \frac{\partial net_{o}}{\partial out_{h1}})\cdot \frac{\partial out_{h1}}{\partial net_{h1}}\cdot \frac{\partial net_{h1}}{\partial w_1} w1Etotal=(ooutoEtotalnetooutoouth1neto)neth1outh1w1neth1代入由单个结点计算的结论,可得:
∂ E t o t a l ∂ w 1 = ( ∑ o δ o ⋅ w h o ) ⋅ o u t h 1 ( 1 − o u t h 1 ) ⋅ i 1 \frac{\partial E_{total}}{\partial w_1}= (\sum\limits_{o}\delta_o\cdot w_{ho})\cdot out_{h1}(1-out_{h1})\cdot i_1 w1Etotal=(oδowho)outh1(1outh1)i1 δ h 1 = ( ∑ o δ o ⋅ w h o ) ⋅ o u t h 1 ( 1 − o u t h 1 ) \delta_{h1}=(\sum\limits_{o}\delta_o\cdot w_{ho})\cdot out_{h1}(1-out_{h1}) δh1=(oδowho)outh1(1outh1),可得:
∂ E t o t a l ∂ w 1 = δ h 1 ⋅ i 1 \frac{\partial E_{total}}{\partial w_1}= \delta_{h1}\cdot i_1 w1Etotal=δh1i1这便是反向传播单隐层的随机梯度下降法,得到倒数第二层,便可以依次反向更新所有的w,而更一般情形,写成矩阵的形式即可,可以看第3个参考资料。

三、Python实例

本节,我们手动实现一个多层感知机,数据集采用经典的手写数字(MINST)。

1 获取数据集

为了演示速度更快,我们只从中选取数字8和9进行建模训练。注意把数据集放在源文件同一目录下。

import numpy as np
import warnings
warnings.filterwarnings("ignore")
import matplotlib.pyplot as plt
from sklearn import preprocessing

f = np.load('./mnist.npz')  #加载数据集
x_train, y_train = f['x_train'], f['y_train'] 
x_test, y_test = f['x_test'], f['y_test'] 

# 获取标签为8和9的数据,分别为num1和num2个
def get_small_data(x_data, y_data, num1, num2):
    cnt1, cnt2, final_x, final_y = 0, 0, [], []
    for i in range(x_data.shape[0]):
        x = x_data[i, :].reshape(-1).tolist()
        y = y_data[i]
        if y == 8 and cnt1 < num1:
            final_x.append(x)
            final_y.append(0)
            cnt1 += 1
        elif y == 9 and cnt2 < num2:
            final_x.append(x)
            
            final_y.append(1)
            cnt2 += 1
    return np.array(final_x), np.array(final_y)

x_train, y_train = get_small_data(x_train, y_train, num1=250, num2=750)
x_test, y_test = get_small_data(x_test, y_test, num1=100, num2=100)
print(x_train.shape)
print(y_train.shape)
print(x_test.shape)
print(y_test.shape)

2 定义sigmoid函数

def sigmoid(x):
    return 1/(1+np.exp(-x))
#导数
def sigmoid_D(x):
    return sigmoid(x)*(1-sigmoid(x))

3 定义损失函数

# 平方损失函数
def loss_fn(y,y_pred):
    return np.sum((y-y_pred)**2)/len(y)

  #作图函数
def plot_loss_acc(Loss, acc,lr):
        plt.rcParams['figure.dpi'] = 150
        plt.figure(figsize=(20, 5))
        plt.subplot(121)
        plt.plot(Loss)
        plt.title("Train Loss, lr={}".format(lr), fontsize=20)
        plt.xlabel("Iteration", fontsize=15)
        plt.subplot(122)
        plt.plot(acc)
        plt.title("Train Accuracy, lr={}".format(lr), fontsize=20)
        plt.xlabel("Iteration", fontsize=15)

4 两层前馈网络

# 2层前馈网络,以sigmoid作为激活函数

def forward_pass(x_mat,y,W1,W2,b1,b2):
    
    #前向传播
    z1=x_mat @ W1+b1
    a1=sigmoid(z1)
    z2=a1 @ W2 +b2
    a2=sigmoid(z2)
    y_pred=a2
    #反向传播,倒数第一层
    grad_a2=-y+y_pred               #L对a2的导数
    grad_z2=grad_a2*sigmoid_D(z2)   #L对z2的导数,也是对b2的导数
    grad_w2=a1.T @ grad_z2          #L对W2的导数
    
    #倒数第二层
    grad_a1=grad_z2 @ W2.T          #L对a1的导数
    grad_z1=grad_a1 * sigmoid_D(z1) #L对z1的导数,也是对b1的导数
    grad_w1=x_mat.T @ grad_z1
    
    gradient=(grad_w1,grad_z1,grad_w2,grad_z2)
    
    return y_pred,gradient

5 训练

hiden=60  # 隐层神经元个数
# 初始化权重
W1=np.random.uniform(-1,1,size=(784,hiden)) 
b1=np.random.uniform(-1,1,size=(1,hiden))        
W2=np.random.uniform(-1,1,size=(hiden,1))
b2=np.random.uniform(-1,1,size=(1,1))

# 超参数
num_iter=30
lr=0.01
batchsize=200      # 设置批量大小


x_mat=preprocessing.scale(x_train,axis=1)   # 数据标准化
y=y_train.reshape(-1,1)

loss_vals,accs=[],[]

for i in range(num_iter):
    total_acc=0
    total_loss=0
    t=0
    for j in range(0,len(x_mat),batchsize):
        x_Train=x_mat[j:j+batchsize]
        y_Train=y[j:j+batchsize]
        y_pred,(grad_w1,grad_z1,grad_w2,grad_z2)=forward_pass(x_Train,y_Train,W1,W2,b1,b2)
        
        #参数更新
        W1=W1-lr*grad_w1
        W2=W2-lr*grad_w2
        b1-=-lr*grad_z1[0,:]
        b2-=lr*grad_z2[0,:]
        
        curr_loss=loss_fn(y_Train,y_pred)
        total_loss+=curr_loss
        
        total_acc+=np.sum((y_pred>=.5)==y_Train)/len(y_Train)
        t+=1
    now_acc=total_acc/t
    accs.append(now_acc)
    now_loss=total_loss/t
    loss_vals.append(now_loss)
    
    if (i%10==0):
        print("{}|{},loss={},accuracy={}".format(
            i,num_iter,curr_loss,now_acc
        ))

plot_loss_acc(loss_vals,accs,lr)

截屏2023-12-26 14.36.58
然后我们在测试集上进行测试,代码如下:


x_mat=preprocessing.scale(x_test)
y=y_test.reshape(-1,1)

y_pred,_=forward_pass(x_mat,y,W1,W2,b1,b2)
acc=np.sum((y_pred>=.5)==y)/len(y)

print(f"准确率:{acc*100} %")

最后在测试集上的准确率能达到86%!如果我们用三层前馈神经网络来拟合,在测试集上的准确率能达到90%以上。

四、参考资料

  1. 李航. 机器学习方法. 清华大学出版社, 2022.
  2. 邱锡鹏.神经网络与深度学习.机械工业出版社,2020.
  3. Henseler, J. (2005). Back propagation. Artificial Neural Networks: An Introduction to ANN Theory and Practice, 37-66.
  • 22
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
多层感知机(Multilayer Perceptron,MLP)是一种常见的人工神经网络模型,它由多个神经元层组成,每个神经元层与下一层之间存在权重连接。反向传播算法是用于训练MLP的一种常见方法,它通过计算损失函数对权重进行更新,以使得网络的输出尽可能接近目标值。 下面是一个简单的MLP反向传播代码示例: ```python import numpy as np # 定义MLP类 class MLP: def __init__(self, input_size, hidden_size, output_size): self.input_size = input_size self.hidden_size = hidden_size self.output_size = output_size # 初始化权重 self.W1 = np.random.randn(self.input_size, self.hidden_size) self.b1 = np.zeros((1, self.hidden_size)) self.W2 = np.random.randn(self.hidden_size, self.output_size) self.b2 = np.zeros((1, self.output_size)) # 前向传播 def forward(self, X): self.z1 = np.dot(X, self.W1) + self.b1 self.a1 = np.tanh(self.z1) self.z2 = np.dot(self.a1, self.W2) + self.b2 self.a2 = self.sigmoid(self.z2) return self.a2 # 反向传播 def backward(self, X, y, learning_rate): m = X.shape[0] # 计算输出层的误差 self.dz2 = self.a2 - y self.dW2 = np.dot(self.a1.T, self.dz2) / m self.db2 = np.sum(self.dz2, axis=0, keepdims=True) / m # 计算隐藏层的误差 self.dz1 = np.dot(self.dz2, self.W2.T) * (1 - np.power(self.a1, 2)) self.dW1 = np.dot(X.T, self.dz1) / m self.db1 = np.sum(self.dz1, axis=0, keepdims=True) / m # 更新权重 self.W2 -= learning_rate * self.dW2 self.b2 -= learning_rate * self.db2 self.W1 -= learning_rate * self.dW1 self.b1 -= learning_rate * self.db1 # sigmoid激活函数 def sigmoid(self, x): return 1 / (1 + np.exp(-x)) # 创建MLP对象 mlp = MLP(input_size=2, hidden_size=4, output_size=1) # 定义训练数据 X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]]) y = np.array([[0], [1], [1], [0]]) # 训练模型 epochs = 10000 learning_rate = 0.1 for i in range(epochs): output = mlp.forward(X) mlp.backward(X, y, learning_rate) # 预测新样本 new_X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]]) predictions = mlp.forward(new_X) print(predictions) ``` 这个示例代码实现了一个具有2个输入神经元、4个隐藏神经元和1个输出神经元的MLP。它使用反向传播算法来训练网络,并使用sigmoid函数作为激活函数。你可以根据需要修改输入、隐藏和输出层的大小,以及训练数据和学习率来进行实验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值