机器学习算法数学原理手把手推导之主成分分析(PCA)

机器学习算法数学原理手把手推导之主成分分析(PCA)


本文旨在提供对主成分分析方法全面通俗的解释,并且会在几个知识需求层面进行分类,每个部分所需要的先修知识我会先说出来,如果需要查缺补漏的话可以移步传送门(如果原作者介意请联系我删除),尽可能地让各种不同深度需求的小伙伴都能够尽可能看懂。如有谬误,欢迎各路大佬指出。

一、基本理论(无纯数学推导,均为感性理解)

1. 数据降维?降维能干什么?

我们都知道,无论是表数据,还是图像、声音等数据,都可能是一个非常庞大的数据集合。就比如一张金融犯罪侦测表中,包含有几百上千个特征(某项支出,收入和各种金融交易的记录之类的)。面对大量的特征,本来样本量就非常大的情况下(比如我们调查了几百万人),如果特征再特别多的话,别说是我们人眼,就算是机器有时候也会不太好处理。因此,数据降维就是用来解决这个问题的。关于数据降维一般有两种方式:

  • 特征选择:从一个包含大量特征的数据特征集中选出一个适当的特征子集。比如一个调查某种疾病的数据集中包含*{身高,体重,BMI,血型,抽烟习惯,上一代疾病率}* 这几个特征,我们发现BMI貌似就能够代表身高和体重两项参数了,那么我们可以把这两项剔除,选择剩下的来进行另外的工作。剩下的特征集就是原来数据集中的特征集的子集,我们不会去对保存下来的特征集做大量的增删改操作。
  • 特征提取:(以下是个人的理解)我们都知道,函数可以视作一个有头有尾的箱子,我们在这个箱子的头部放入一个或者多个参数 x 1 , x 2 , . . . , x n x_1, x_2,...,x_n x1,x2,...,xn,这个箱子内部会经过一系列操作,最终从尾部给我们输出一个或多个结果 ( y 1 , y 2 , . . . , y m ) (y_1, y_2,...,y_m) (y1,y2,...,ym)。在数学上就是一个非常常见的表示: f ( x 1 , x 2 , . . . , x n ) = ( y 1 , y 2 , . . . , y m ) f(x_1, x_2,...,x_n)=(y_1, y_2,...,y_m) f(x1,x2,...,xn)=(y1,y2,...,ym)。当我们把这些输入的参数和输出的结果看做是特征集的话,这整个过程便可以看作一个特征提取的过程,只不过特征提取的过程一定要满足两个要求:1. 输出结果数量要尽可能少;2. 结果虽然并不是像特征选择那样是原特征集的子集,但是仍然需要尽可能保留原特征集的信息。PCA就属于特征提取,这个过程可以在下一小节的直观理解中进一步体会。

举个例子,什么叫做尽可能保证原特征集的信息呢?学过计算机的小伙伴应该能很快理解:比如我们在购房的时候,不少人会关注两个东西:是否有西晒?水电是否齐全?放到数据集中可能就是这样的:两个特征是{西晒,水电},然后两个特征的可能的值均为“是”和“否”。那么如果我们把这两个特征合并一下呢?改成一个由两位二进制数构成的特征:00,01,10,11。00代表既没有西晒且水电不齐全,01代表没有西晒但是水电齐全,以此类推。这样就在某种程度上保留了原来两个特征的信息且让特征数量也少了。

这个例子只是能帮助你更加直观的理解,但是其实数据降维的好处还有很多很多。

我们都知道欧氏距离,即高中学的点到点的距离公式: d ( O 1 , O 2 ) = ( x 1 − x 2 ) 2 + ( y 1 − y 2 ) 2 d(O_1,O_2)=\sqrt{(x_1-x_2)^2+(y_1-y_2)^2} d(O1,O2)=(x1x2)2+(y1y2)2 ,放到三维空间就是 ( x 1 − x 2 ) 2 + ( y 1 − y 2 ) 2 + ( z 1 − z 2 ) 2 \sqrt{(x_1-x_2)^2+(y_1-y_2)^2+(z_1-z_2)^2} (x1x2)2+(y1y2)2+(z1z2)2 , 再高维就以此类推。如果我们的特征数量特别的大,那么我们在计算这个欧氏距离的时候计算量也会变得越来越大,我们在训练模型的计算开销也会变大。

另外,维度灾难也是一个需要好好考虑的问题。什么是维度灾难?这里有一个介绍维度灾难非常详细的帖子,感兴趣的朋友可以来看看:如何正确理解维度灾难-CSDN博客。大致就是说,从低维空间到高维空间,点和点之间的距离会变得越来越没有意义,因为在相同差异的情况下,相比于低维空间,在高维的空间中点的分布会非常稀疏,距离非常难以度量两点之间的差异。下面有一副图很好地展示了这个现象:

这里的横坐标是维度数量,纵坐标是一个度量值: l o g 10 M a x − M i n M i n log_{10}\frac{Max-Min}{Min} log10MinMaxMin。意思是随机在这些指定维度的特征空间中生成500个点,计算这些点之间的距离最大值 M a x Max Max和最小值 M i n Min Min,然后代入这个度量计算公式计算。这个度量计算公式可以这么理解:当这个度量值逼近0的时候,就说明 M a x − M i n M i n \frac{Max-Min}{Min} MinMaxMin越来越接近1,最大值与最小值的差异就越难体现出来。打个比方,假设 M a x = 4   M i n = 1 Max=4 \space Min=1 Max=4 Min=1, 算出来 M a x − M i n M i n = 3 \frac{Max-Min}{Min}=3 MinMaxMin=3, 而当 M a x = 5   M i n = 2 Max=5 \space Min=2 Max=5 Min=2, 算出来 M a x − M i n M i n = 3 2 \frac{Max-Min}{Min}=\frac{3}{2} MinMaxMin=23, 相同极差下这个值会越来越小,就说明越高维度下,数据之间的差异就会越来越不明显。

第二,无论是特征选择还是特征提取,我们都是会尽可能保留原数据的信息的,那么就说明我们去掉的大多都是一些冗余无用的东西,这其实能够为机器学习模型“排除干扰项”,因为某些机器学习模型会把数据都“一视同仁”。

另外,在一些领域中可能会有可视化的需求,那么降维就能够把高维空间的数据映射为能够被人理解的三维或更加低维的数据,以便可视化,这里就不细说了。

PCA的直观理解

PCA中强调的是主成分,那么什么才是PCA中最关注的主成分呢?下面这张常用的图其实就能解释了。

在这张图中, X 1 , X 2 X_1, X_2 X1,X2表示原来的数据坐标体系。我们令红色圈的点是A,黄色圈点为B,绿色圈的点是C。接下来直观的去思考:在这个坐标图中,透过 X 1 X_1 X1轴,我们可以发现 D ( A , B ) < D ( B , C ) < D ( A , C ) D(A,B)<D(B,C)<D(A,C) D(A,B)<D(B,C)<D(A,C)(D是距离),透过 X 2 X_2 X2轴我们能发现 D ( A , B ) = D ( B , C ) < D ( A , C ) D(A,B)=D(B,C)<D(A,C) D(A,B)=D(B,C)<D(A,C),综合两个轴直观的来说,关于三个点的距离可以概括为——A到B点的距离是最短的,而A到C的距离是最长的,同时AB两点的距离和BC两点的距离相差非常小。在 X 1 , X 2 X_1,X_2 X1,X2坐标体系下,我们需要两个轴才能描述清楚上述关系,那有没有办法能够只用一个轴就能描述清楚呢?

就是 V 1 , V 2 V_1,V_2 V1,V2这个坐标体系,如果我们把所有点都投影到 V 1 V_1 V1轴上,那么是不是我们就只用一个轴可以描述清楚了?( V 2 V_2 V2它只是与 V 1 V_1 V1垂直的轴,在描述这个信息的时候可以直接忽略)这便是PCA的主要思想:**在现有的特征空间 X 1 , X 2 , . . . X n X_1, X_2,...X_n X1,X2,...Xn中寻找一个新的特征轴组合 V 1 , V 2 , . . . V n V_1, V_2,...V_n V1,V2,...Vn,这些组合中有一部分轴能够很好的描述点之间的分布距离信息,而有一些轴并不能做到,我们删除掉后者只保留前者,那么就能构成一个新的低维坐标体系,这个体系的坐标虽然会跟原体系的坐标大相径庭,但是他较好的保留了数据点之间的分布距离信息。**这个描述只是一个直观的描述,具体分布距离信息的量化指标为数据点投影的方差(严格来说是散度),该部分深入的讨论会在数学推导过程中体现。

那是不是所有数据都能用这个方法?并不是,因为PCA看重的是数据点之间的距离分布信息,这只是数据能体现的信息中的一部分,而这一部分可能是一些模型训练时很重要的信息,并不能代表数据全部信息。当我们对数据信息的需求改变时,PCA也许就不是那么好使了。更多的数据降维方法还有比如LDA,MDS等,他们也是为了保留不同种数据信息需求而准备的。

算法过程(适合依葫芦画瓢式做题与实战代码调用)

该部分需要一定的数学先修知识:线性代数中矩阵加、减、乘法计算,求协方差矩阵以及特征值和特征向量的求解。

算法过程

过程及例题

假定一组无标签数据集 D = ( x 1 , x 2 , . . . , x n ) D=(x_1, x_2, ..., x_n) D=(x1,x2,...,xn), 将其写成矩阵形式:
X = ( x 11 x 12 … x 1 n x 21 x 22 … x 2 n ⋮ ⋮ ⋱ ⋮ x m 1 x m 2 … x m n ) X = \begin{pmatrix} x_{11} & x_{12} & \dots & x_{1n} \\ x_{21} & x_{22} & \dots & x_{2n} \\ \vdots & \vdots & \ddots & \vdots \\ x_{m1} & x_{m2} & \dots & x_{mn} \end{pmatrix} X= x11x21xm1x12x22xm2x1nx2nxmn
注意:这个矩阵中n表示样本数量,m表示维度数量;

首先要对于每一个特征,求出每个特征内的均值,并写成向量形式:
μ = ( μ 1 μ 2 ⋮ μ m ) \mu = \begin{pmatrix} \mu_{1} \\ \mu_{2} \\ \vdots \\ \mu_{m} \end{pmatrix} μ= μ1μ2μm
接下来,对数据矩阵做去中心化,即矩阵中每个数减去所在特征的平均值:
X ′ = X − μ = ( x 11 − μ 1 x 12 − μ 1 … x 1 n − μ 1 x 21 − μ 2 x 22 − μ 2 … x 2 n − μ 2 ⋮ ⋮ ⋱ ⋮ x m 1 − μ m x m 2 − μ m … x m n − μ m ) X'=X-\mu= \begin{pmatrix} x_{11}-\mu_{1} & x_{12}-\mu_{1} & \dots & x_{1n}-\mu_{1} \\ x_{21}-\mu_{2} & x_{22}-\mu_{2} & \dots & x_{2n}-\mu_{2} \\ \vdots & \vdots & \ddots & \vdots \\ x_{m1}-\mu_{m} & x_{m2}-\mu_{m} & \dots & x_{mn}-\mu_{m} \end{pmatrix} X=Xμ= x11μ1x21μ2xm1μmx12μ1x22μ2xm2μmx1nμ1x2nμ2xmnμm
X ′ X' X即为去中心化的数据矩阵。对该矩阵求协方差矩阵:
Σ = 1 n − 1 X ′ X ′ T \varSigma = \frac{1}{n-1}X'X'^{T} Σ=n11XXT
这一步中的 1 n − 1 \frac{1}{n-1} n11是无偏的,如果题目要求有偏的写,将其改成 1 n \frac{1}{n} n1即可。

接下来对协方差矩阵求解对应的特征值,m*m的协方差矩阵最多有m个实数特征值(出现虚根的情况在后面的部分会提到)。

将这些特征值以及特征向量按照以下方式排列:

其实就是将所有特征值从大到小排列,然后将对应的特征向量(列)按照特征值排列顺序从左到右放到一个矩阵中,并针对每个特征向量进行归一化处理,即 x ′ = x ∣ ∣ x ∣ ∣ x'=\frac{x}{||x||} x=∣∣x∣∣x

最后,将去中心化的数据点按照要求跟特征向量组成的矩阵做乘法即可。比如我需要将数据降维到2维,那么就用 X ′ T X'^{T} XT特征向量矩阵最左边两列做乘法即可。

至此,算法结束。下面有一个PCA计算的例题可以参考:

将下列二维数据集用PCA方法降维至1维(协方差矩阵求有偏)。

X = (x1, x2) = {(1,2),(3,3),(3,5),(5,4),(5,6),(6,5),(8,7),(9,8)}

解:写成上述算法的形式
X = ( 1 3 3 5 5 6 8 9 2 3 5 4 6 5 7 8 ) X= \begin{pmatrix} 1 & 3 & 3 & 5 & 5 & 6 & 8 & 9 \\ 2 & 3 & 5 & 4 & 6 & 5 & 7 & 8 \\ \end{pmatrix} X=(1233355456658798)
对其进行去中心化并求得协方差矩阵
Σ = ( 6.25 4.25 4.25 3.5 ) \varSigma = \begin{pmatrix} 6.25 & 4.25 \\ 4.25 & 3.5 \\ \end{pmatrix} Σ=(6.254.254.253.5)
根据特征向量的特性 Σ x = λ x \varSigma x=\lambda x Σx=λx求得特征值及对应特征向量 λ 1 = 9.34 , x 1 = ( 0.81 , 0.59 ) T ; λ 2 = 0.41 , x 2 = ( − 0.59 , 0.81 ) T \lambda_{1}=9.34,x_1=(0.81,0.59)^T;\lambda_{2}=0.41,x_2=(-0.59,0.81)^T λ1=9.34,x1=(0.81,0.59)T;λ2=0.41,x2=(0.59,0.81)T,由于我们要降维至1维,所以只选取 x 1 x_1 x1作为特征向量矩阵即可。将其与去中心化得矩阵相乘就能得到目标结果。

一些解释

如果只是应付计算题,上面的过程和例题已经足够,下面是对上述过程的一些解释。

  • 无监督:通过算法过程可以发现,PCA主要是通过数据特征之间的关系来完成降维的,并不需要用到数据标签,因此这是一个无监督学习算法;

  • 矩阵维度对应 X ′ X' X是m×n的矩阵,由于有m个特征,协方差矩阵就是m×m,特征向量前2列矩阵是m×2的矩阵,相乘得n×2得矩阵( X ′ T X'^{T} XT要转置),即变成了二维的数据集;

  • 为什么要对原数据做去中心化处理(谷歌技术面试原题):这个问题其实是有数学推导的,但是其实解释起来不需要这么复杂,一个直观的例子就足够了。

    我们把上面计算例题的数据可视化一下,如下图所示:

​ 我们首先看左边这幅图,这幅图的数据点我们把它去中心化了,使得整个数据点的中心在原点上,这样我们能求出 v 1 v_1 v1这个最能代表原数据主成分的向量(这里的向量其实是一个方向,也就是说我把所有点都投到该方向上,该方向能最大程度保留数据的主成分),一切都没问题。但是如果我们不做去中心化呢?请看右边那幅图,假设蓝色箭头是我们不去中心化所求得得方向向量(如果不信的话可以自己求一下,然后画出图来看看),红色则是从左边保留得特征向量,我们会发现这两个向量是有交点的。也就是说,蓝色箭头所在的方向并不是最优解。总的来说其实就是坐标线性平移下,方向向量也会跟着改变的意思。

  • 为什么要对特征向量做归一化处理:被归一化的向量组合起来其实可以看作是一个新的坐标基(传送门:什么是坐标基),所以这些特征向量仅代表方向,为了让投影过程是同比例的,必须这么做。反之,如果我让一个特征向量的模为1,另一个为2,我们假设以上面那个网格图为例,那么其中一个坐标的一格代表1,另一个坐标的一格可就不是1了,这么做会破坏数据保存的信息。

代码使用

Python中sklearn有PCA的包,直接对numpy array调用即可,注意横纵列别搞混了就行(对二维数组而言,行是array里一个array元素,比如[1,2,3])。由于本文主要讲的是理论,所以只提供一个最简单的样例,更复杂的样例可以参考其他帖子。

from sklearn.decomposition import PCA
import numpy as np

# 创建示例数据集
X = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

# 创建 PCA 模型并拟合数据
pca = PCA(n_components=2)  # 保留的特征维度数
X_pca = pca.fit_transform(X)

print(X_pca)

数学推导及常见算法细节的解释

该部分涉及到较多纯数学推导,需要的先修知识有:方差的意义和计算公式,向量内外积计算及对应的意义,向量和矩阵乘法运算律,优化问题中的拉格朗日乘子法以及算法过程中所涉及到的所有线性代数知识。

提出关于算法细节的问题

关于上述算法过程,其实还有一些问题没有解决:

  • 如何量化数据点投影到某个方向上之后关于数据离散度和距离分布信息的保存程度?
  • 为什么是取前k大特征值所对应的特征向量来投影数据点?
  • 为什么是对 X ′ X' X的协方差矩阵的求特征值与特征向量?

在接下来的推导过程中,这些问题都将一一解决。

推导与解释

本节主要针对数据点的一维投影情况。

我们的目标是找出原数据转移到新特征空间内的时候,要尽可能地保证数据的离散度以及距离分布信息。那么我们可以换个角度去想:在统计学中,我们一般用什么东西来度量一组数据的离散程度?

方差(标准差)。而我们在等长的新坐标基上投影,基本上还是做的是线性变换,所以距离分布信息是很容易保存的(这里请参见特征值与特征向量的意义:特征值和特征向量的意义)。重点就是我们要最大化投影后数据的离散程度以便能与原来数据分布差不多。根据投影的知识,我们可以知道一个向量 x x x在另一个向量 v v v的投影是:
x v ⃗ = x ⃗ ⋅ v ⃗ \vec{x_v}= \vec{x} \cdot \vec{v} xv =x v
而方差的公式是 ∑ i = 1 n ( x i − μ ) 2 n \frac{\sum_{i=1}^{n}(x_i-\mu)^2}{n} ni=1n(xiμ)2,我们要最大化数据点在该方向的投影的离散程度,即最大化该方向上数据的方差。假设每个数据点的向量表示为 x i x_i xi(这里的数据点必须是经过去中心化的),投影的方向向量是 u u u,数据的样本量是 n n n,由PCA的算法可以得出投影过后的数据点均值一定是0, μ \mu μ是一个零向量,可以直接忽略。这个问题就可以转化为一个优化问题:
m a x i m i z e    f ( u ) = ∑ i = 1 n ( x i ⃗ u ⃗ ) 2 n s . t .   u T u = 1 maximize \space \space f(u)=\frac{\sum_{i=1}^{n}(\vec{x_i}\vec{u})^2}{n} \\ s.t. \space u^Tu=1 maximize  f(u)=ni=1n(xi u )2s.t. uTu=1

  • 为什么 u T u = 1 u^Tu=1 uTu=1得限制为1?因为变换过后的特征向量得做归一化处理,第二就是为了限制后面的 u T Σ u u^T\varSigma u uTΣu大到起飞。

继续推导需要最大化的目标函数:
f ( u ) = ∑ i = 1 n ( x i T u ) 2 n = ∑ i = 1 n ( x i T u ) ( u T x i ) n = ∑ i = 1 n u T x i x i T u n = u T ( ∑ i = 1 n x i x i T n ) u \begin{align*} f(u) &= \frac{\sum_{i=1}^{n}(x_i^T u)^2}{n} \\ &= \frac{\sum_{i=1}^{n}(x_i^T u)(u^T x_i)}{n} \\ &= \frac{\sum_{i=1}^{n}u^T x_i x_i^T u}{n} \\ &= u^T(\frac{\sum_{i=1}^{n} x_i x_i^T}{n}) u \end{align*} f(u)=ni=1n(xiTu)2=ni=1n(xiTu)(uTxi)=ni=1nuTxixiTu=uT(ni=1nxixiT)u
由于u在求和符号内相当于一个常数向量,所以可以直接提出来,那么括号内的东西是不是变成了很熟悉的方差公式?即括号内的东西表示原数据的协方差矩阵 Σ \varSigma Σ

那么整个优化问题就变成了:
m a x i m i z e    f ( u ) = u t Σ u s . t .   u T u − 1 = 0 maximize \space \space f(u)=u^t \varSigma u \\ s.t. \space u^Tu-1=0 maximize  f(u)=utΣus.t. uTu1=0
对该问题使用拉格朗日乘子法后的对偶问题表达式为(传送门:优化问题-拉格朗日乘子法):
L ( u , λ ) = u T Σ u − λ ( u T u − 1 ) L(u,\lambda)=u^T \varSigma u - \lambda(u^Tu-1) L(u,λ)=uTΣuλ(uTu1)
u , λ u,\lambda u,λ分别求偏导:
δ L δ u = 2 Σ u − 2 λ u = 0 δ L δ λ = − u T u + 1 = 0 \frac{\delta L}{\delta u} = 2\varSigma u - 2\lambda u = 0 \\ \frac{\delta L}{\delta \lambda} = -u^Tu + 1 = 0 δuδL=2Σu2λu=0δλδL=uTu+1=0
整理第一个式子可得:
Σ u = λ u \varSigma u = \lambda u Σu=λu
我们可以惊讶的发现,这个式子跟特征值的特性不谋而合,所以这个优化问题的解一定与协方差矩阵 Σ \varSigma Σ的特征值有关。另外,对这个式子的两边左边同时乘一个 u T u^T uT,神奇的事情发生了:
u T Σ u = u T λ u = λ u T u u^T \varSigma u = u^T \lambda u = \lambda u^T u uTΣu=uTλu=λuTu
又因为 u T u = 1 u^T u=1 uTu=1这个限制条件,那么整个目标函数就可以转化为:
f ( u ) = u T Σ u = λ u T u = λ f(u) = u^T \varSigma u = \lambda u^T u = \lambda f(u)=uTΣu=λuTu=λ
即我们要最大化数据投影后的离散程度,就需要最大化协方差矩阵的特征值;由于特征值是固定存在的,所以我们只需要找出尽可能大的特征值对应的特征向量即可!这个过程就可以解释上面提出的所有问题了。

补充:可行性推导

整个推导过程上一部分已经讲的差不多了,这里只是补充一下可信性的证明,该过程涉及到二次型的性质,但是了解性质本身即可,无需做深入探究就能搞懂推导过程。

回到上一节对优化函数的化简过程中,我们换一个方式看这个化简得式子:
f ( u ) = ∑ i = 1 n u T x i x i T u n = 1 n u T ( ∑ i = 1 n x i T x i ) u \begin{align*} f(u) &= \frac{\sum_{i=1}^{n}u^T x_i x_i^T u}{n} \\ &= \frac{1}{n} u^T (\sum_{i=1}^nx_i^T x_i) u \\ \end{align*} f(u)=ni=1nuTxixiTu=n1uT(i=1nxiTxi)u
这个 ∑ i = 1 n x i T x i \sum_{i=1}^nx_i^T x_i i=1nxiTxi其实就是没有除以样本数的协方差矩阵,联系协方差矩阵公式 1 n X T X \frac{1}{n}X^TX n1XTX可得:
f ( u ) = 1 n u T ( X T X ) u f(u) = \frac{1}{n} u^T (X^TX) u f(u)=n1uT(XTX)u
这个式子很明显是个二次型形式,所以我们可以根据二次型的形式继续推导:
X X T ξ = λ ξ ( X X T ξ ) T ξ = ( λ ξ ) T ξ ξ T X X T ξ = λ ξ T ξ \begin{align*} XX^T \xi &= \lambda \xi \\ (XX^T \xi)^T \xi &= (\lambda \xi)^T \xi \\ \xi^T XX^T \xi &= \lambda \xi^T \xi \\ \end{align*} XXTξ(XXTξ)TξξTXXTξ=λξ=(λξ)Tξ=λξTξ
等式左边可得:
ξ T X X T ξ = ( X T ξ ) T ( X T ξ ) = ∣ ∣ X T ξ ∣ ∣ 2 = λ ξ T ξ = λ ∣ ∣ ξ ∣ ∣ 2 ≥ 0 \begin{align*} \xi^T XX^T \xi &= (X^T \xi)^T (X^T \xi) \\ &= ||X^T \xi||^2 \\ &= \lambda \xi^T \xi \\ &= \lambda ||\xi||^2 \ge 0 \end{align*} ξTXXTξ=(XTξ)T(XTξ)=∣∣XTξ2=λξTξ=λ∣∣ξ20

所以该目标函数一定存在最大值,即该问题一定有解,可行;

谢谢观看!本人会不定期更新关于数据挖掘&机器学习算法的内容。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值