【记录读论文时遇到的一些算法5】—— SVD分解

1.特征值、特征向量

如果一个向量 x x x n × n n \times n n×n 矩阵 A \mathbf{A} A 的特征向量,那么可表示成以下形式:

A x = λ x \mathbf{A}x=\lambda x Ax=λx

其中, A \mathbf{A} A 是一个 n × n n \times n n×n 的实对称矩阵, x x x 是一个 n n n 维特征向量, λ \lambda λ 是矩阵 A \mathbf{A} A 的特征值, x x x 是矩阵 A \mathbf{A} A 的特征值 λ \lambda λ 所对应的特征向量。

思考:为什么一个矩阵乘以一个向量的效果与一个实数乘以相同向量的效果是一样的呢?

事实上,矩阵是线性空间里变换的描述。 矩阵 A \mathbf{A} A 与向量相乘,本质上对向量 x x x 进行一次线性变换(旋转或者拉伸变换) ,而该转换的效果等价于常数 λ \lambda λ 乘以向量 x x x(拉伸)的效果。所以,当求解矩阵的特征值与对应的特征向量时,就是为了求矩阵 A \mathbf{A} A 能使得哪些向量只发生拉伸变换,而拉伸的程度用特征值 λ \lambda λ 来度量。

线性变换

矩阵是线性空间里变换的描述。一个矩阵乘以一个向量,实质上是对向量做线性变换。对于一个对称矩阵 M \mathbf{M} M

M = [ 3 0 0 1 ] \mathbf{M}=\left[\begin{array}{ll} 3 & 0 \\ 0 & 1 \end{array}\right] M=[3001]
对应的线性变换是下面的形式:

因为这个矩阵 M \mathbf{M} M 乘以一个向量 ( x , y ) ({x}, {y}) (x,y) 的结果是:
[ 3 0 0 1 ] [ x y ] = [ 3 x y ] \left[\begin{array}{ll} 3 & 0 \\ 0 & 1 \end{array}\right]\left[\begin{array}{l} x \\ y \end{array}\right]=\left[\begin{array}{c} 3 x \\ y \end{array}\right] [3001][xy]=[3xy]
上面的矩阵是对称的,所以这个变换是一个对 x , y x , y xy 轴的方向一个拉伸变换(每一个对角线上的元素将会对一个维度进行拉伸变换,当值大 于1时,是拉长变换,当值小于1时是缩短变换),当矩阵不是对称的时候,假如说矩阵是下面的样子:
[ 3 2 0 1 ] \left[\begin{array}{ll} 3 & 2 \\ 0 & 1 \end{array}\right] [3021]

它所描述的变换是下面的样子:

这其实是在平面上对一个轴进行的拉伸变换(如蓝色的箭头所示),在图中,蓝色的箭头是一个最主要的变化方向(变化方向可能有不止一个),如果我们想要描述好一个变换,那我们就描述好这个变换主要的变化方向就好了。

2. 特征分解

对于一个矩阵 A \mathbf{A} A,将其特征分解,得到矩阵 A \mathbf{A} A n n n 个特征值 λ 1 ≤ λ 2 ≤ … ≤ λ n \lambda_{1} \leq \lambda_{2} \leq \ldots \leq \lambda_{n} λ1λ2λn 以及对应的特征向量 { w 1 , w 2 , … , w n } \left\{w_{1}, w_{2}, \ldots, w_{n}\right\} {w1,w2,,wn} ,那么特征分解可 表示为:
A = W Σ W − 1 A=W \Sigma W^{-1} A=WΣW1
其中, W W W 是矩阵 A \mathbf{A} A的特征向量所构成的 n × n n \times n n×n 维矩阵, Σ \Sigma Σ n n n 个特征值为主对角线的 n × n n \times n n×n 维对角矩阵。
通常情况下,将得到的一组特征向量进行Schmidt正交化单位化,即 ∥ w i ∥ 2 = 1 \left\|w_{i}\right\|_{2}=1 wi2=1 ,那么这组特征向量为标准正交基,满足 W T W = I W^{T} W=I WTW=I , 即 W T = W − 1 W^{T}=W^{-1} WT=W1 ,也就是说W为酉矩阵,表达式为:
A = W Σ W T A=W \Sigma W^{T} A=WΣWT
特征分解的式子,分解得到的 Σ \Sigma Σ 矩阵是一个对角阵,里面的特征值是由大到小排列的,这些特征值所对应的特征向量就是描述这个矩阵变 化方向 (从主要的变化到次要的变化排列)。
当矩阵是高维的情况下,那么这个矩阵就是高维空间下的一个线性变换,这个变换有很多的变换方向,我们通过特征值分解得到的前 N \mathrm{N} N 个 特征向量,那么就对应了这个矩阵最主要的 N \mathrm{N} N 个变化方向。我们利用这前 N \mathrm{N} N 个变化方向,就可以近似这个矩阵(变换)。也就是之前说 的:提取这个矩阵最重要的特征。

总结一下,矩阵特征分解可以得到特征值与特征向量,特征值表示的是这个特征到底有多重要,而特征向量表示这个特征是什么。

特征分解的局限性

特征分解有一些局限性,比如变换矩阵必须是方阵,即 n ∗ n n * n nn 的矩阵。而在实际应用场景中,大部分不是这种矩阵。举个最简单的例子, 关系型数据库中的某一张表的数据存储结构就类似于一个二维矩阵,假设这个表有 m m m行,有 n n n 个字段,那么这个表数据矩阵的规模就是 m ∗ n m * n mn 。很明显,在绝大部分情况下, m \mathrm{m} m n \mathrm{n} n 并不相等。如果对这个矩阵要进行特征提取,特征值分解的方法显然就行不通了。那么这个时 候就轮到SVD登场。

3. 奇异值分解(SVD)

SVD是一种适用于任意矩阵(表述不确切,有特殊情况)的分解方法,不限于方阵。对于一个 m × n m \times n m×n 矩阵 A \mathbf{A} A,那么矩阵 A \mathbf{A} A 的奇异值分解为:
A = U Σ V T \mathbf{A}=U \Sigma V^{T} A=UΣVT
其中, U {U} U 是一个 m × m m \times m m×m 矩阵,矩阵 U {U} U 中的正交向量为左奇异向量; V \mathrm{V} V 是一个 n × n n \times n n×n 矩阵,矩阵 V {V} V 中的正交向量为右奇异向量, U {U} U V {V} V 都是酉 矩阵,满足 U T U = I , V T V = I ; Σ U^{T} U=I, V^{T} V=I ; \Sigma UTU=I,VTV=I;Σ 是一个 m × n m \times n m×n 矩阵,除了对角线元素以外都为 0 ,对角线上的元素为奇异值。下图表示SVD的分解 过程:

思考:任意矩阵可通过SVD进行分解,那么如何求解 U 、 V U 、 V UV Σ \Sigma Σ 呢?

如果用矩阵 A \mathbf{A} A乘以 A \mathbf{A} A的转置得到一个 m × m m \times m m×m 的方阵 A A T \mathbf{A}\mathbf{A}^{T} AAT ,方阵进行特征分解,得到特征值以对应的特征向量:

( A A T ) u i = λ i u i \left(\mathbf{A}\mathbf{A}^{T}\right) u_{i}=\lambda_{i} u_{i} (AAT)ui=λiui

方阵 A A T \mathbf{A}\mathbf{A}^{T} AAT 分解得到 m {m} m 个特征值和对应的特征向量 u u u ,将所有特征向量张成一个矩阵 U {U} U ,就是SVD分解公式的 U U U 矩阵。

同理,如果用矩阵 A \mathbf{A} A的转置乘以 A \mathbf{A} A得到一个 n × n n \times n n×n 的方阵 A T A A^{T} A ATA ,方阵进行特征分解,得到特征值以对应的特征向量:
( A T A ) v i = λ i v i \left( \mathbf{A}^{T}\mathbf{A}\right) v_{i}=\lambda_{i} v_{i} (ATA)vi=λivi
方阵 A A T A A^{T} AAT 分解得到 n {n} n 个特征值和对应的特征向量 v v v ,将所有特征向量张成一个矩阵 V {V} V ,就是SVD分解公式的 V {V} V 矩阵。

思考: A T A \mathbf{A}^{T}\mathbf{A} ATA 的特征向量组成的矩阵是SVD中的 V {V} V矩阵,而 A A T \mathbf{A}\mathbf{A}^{T} AAT 的特征向量组成的矩阵是SVD中的 U {U} U矩阵,这个是怎么证明的?

证明:
A = U Σ V T A T = V Σ T U T ⇒ A T A = V Σ T U T U Σ V T = V Σ 2 V T \begin{gathered} \mathbf{A}=U \Sigma V^{T} \\ \mathbf{A}^{T}=V \Sigma^{T} U^{T} \\ \Rightarrow \mathbf{A}^{T} \mathbf{A}=V \Sigma^{T} U^{T} U \Sigma V^{T}=V \Sigma^{2} V^{T} \end{gathered} A=UΣVTAT=VΣTUTATA=VΣTUTUΣVT=VΣ2VT
上式使用了 U T U = I , Σ T Σ = Σ 2 U^{T} U=I, \Sigma^{T} \Sigma=\Sigma^{2} UTU=I,ΣTΣ=Σ2 。不难看出 A T A \mathbf{A}^{T} \mathbf{A} ATA 的特征向量组成的矩阵就是SVD中的 V {V} V 矩阵。同 理, A A T \mathbf{A}\mathbf{A}^{T} AAT 的特征向量组成的矩阵就是SVD中的 U U U 矩阵。

接下来求解的是奇异值,其解法有两种:

  1. 通过上式证明可发现, A T A \mathbf{A}^{T} \mathbf{A} ATA 的特征值矩阵是奇异值矩阵的平方,也就是说特征值和奇异值满足 如下关系:
    σ i = λ i \sigma_{i}=\sqrt{\lambda_{i}} σi=λi
  2. A = U Σ V T ⇒ A V = U Σ V T V ⇒ A V = U Σ ⇒ A v i = σ u i ⇒ σ i = A v i u i \mathbf{A}=U \Sigma V^{T} \Rightarrow \mathbf{A} V=U \Sigma V^{T} V \Rightarrow \mathbf{A} V=U \Sigma \Rightarrow \mathbf{A}v_{i}=\sigma u_{i} \Rightarrow \sigma_{i}=\frac{\mathbf{A} v_{i}}{u_{i}} A=UΣVTAV=UΣVTVAV=UΣAvi=σuiσi=uiAvi

思考: 任意矩阵可进行SVD分解,那么问题又来了,一个 m ∗ n m * n mn 的矩阵 A \mathbf{A} A,你把它分解成 m ∗ m m * m mm 的矩 阵 U U U m ∗ n m * n mn 的矩阵 Σ \Sigma Σ n ∗ n n * n nn 的矩阵。这三个矩阵中任何一个的维度似乎一点也不比 A \mathbf{A} A的维度小,而且还要做两次矩阵的乘法,这不是没事找事干嘛!把简单的事情搞复杂了么!并且我们知道矩阵乘法 的时间复杂度,那奇异值分解到底要怎么做呢?

回答: 在奇异值分解矩阵中 Σ \Sigma Σ 里面的奇异值按从大到小的顺序排列,奇异值从大到小的顺序减小的特别快。在很多情况下,前10%甚至1%的奇异值的和就占了全部的奇异值之和的 99 % 99 \% 99% 以上。也就是说, 剩下的 90 % 90 \% 90% 甚至 99 % 99 \% 99% 的奇异值几乎没有什么作用。因此,我们可以用前面 个大的奇异值来近似描述矩 阵,于是奇异值分解公式可以写成如下:
A m ∗ n ≈ U m ∗ r Σ r ∗ r V r ∗ n T \mathbf{A}_{m * n} \approx U_{m * r} \Sigma_{r * r} V_{r * n}^{T} AmnUmrΣrrVrnT

其中, r < < m , r < < n r << m, r <<n r<<m,r<<n。将一个矩阵 A \mathbf{A} A分解为三个小矩阵。如果 r r r越大,与原来的矩阵相似度越大,但存储和计算成本也会越大。因此,使用SVD时,需要根据不同的业务场景、资源情况来合理选择 r r r的大小。本质上是在计算精度与空间时间成本之前做折中。

4. SVD分解意义

SVD 分解可以看成先旋转,然后进行分别缩放,然后再旋转的过程。

5. SVD举例

A = ( 0 1 1 1 1 0 ) A=\left(\begin{array}{ll} 0 & 1 \\ 1 & 1 \\ 1 & 0 \end{array}\right) A=011110
先计算 A T A A^{T} A ATA A A T A A^{T} AAT
A T A = ( 0 1 1 1 1 0 ) ( 0 1 1 1 1 0 ) = ( 2 1 1 2 ) A A T = ( 0 1 1 1 1 0 ) ( 0 1 1 1 1 0 ) = ( 1 1 0 1 2 1 0 1 1 ) \begin{gathered} A^{T} A=\left(\begin{array}{lll} 0 & 1 & 1 \\ 1 & 1 & 0 \end{array}\right)\left(\begin{array}{ll} 0 & 1 \\ 1 & 1 \\ 1 & 0 \end{array}\right)=\left(\begin{array}{ll} 2 & 1 \\ 1 & 2 \end{array}\right) \\ A A^{T}=\left(\begin{array}{ll} 0 & 1 \\ 1 & 1 \\ 1 & 0 \end{array}\right)\left(\begin{array}{lll} 0 & 1 & 1 \\ 1 & 1 & 0 \end{array}\right)=\left(\begin{array}{lll} 1 & 1 & 0 \\ 1 & 2 & 1 \\ 0 & 1 & 1 \end{array}\right) \end{gathered} ATA=(011110)011110=(2112)AAT=011110(011110)=110121011
然后求解 A T A A^{T} A ATA 的特征值及对应的特征向量:
λ 1 = 3 ; v 1 = ( 1 / 2 1 / 2 ) ; λ 2 = 1 ; v 2 = ( − 1 / 2 1 / 2 ) \lambda_{1}=3 ; v_{1}=\left(\begin{array}{l} 1 / \sqrt{2} \\ 1 / \sqrt{2} \end{array}\right) ; \lambda_{2}=1 ; v_{2}=\left(\begin{array}{c} -1 / \sqrt{2} \\ 1 / \sqrt{2} \end{array}\right) λ1=3;v1=(1/2 1/2 );λ2=1;v2=(1/2 1/2 )
同理求解 A A T A A^{T} AAT 的特征值及对应的特征向量:
λ 1 = 3 ; u 1 = ( 1 / 6 2 / 6 1 / 6 ) ; λ 2 = 1 ; u 2 = ( 1 / 2 0 − 1 / 2 ) ; λ 3 = 0 ; u 3 = ( 1 / 3 − 1 / 3 1 / 3 ) \lambda_{1}=3 ; u_{1}=\left(\begin{array}{c} 1 / \sqrt{6} \\ 2 / \sqrt{6} \\ 1 / \sqrt{6} \end{array}\right) ; \lambda_{2}=1 ; u_{2}=\left(\begin{array}{c} 1 / \sqrt{2} \\ 0 \\ -1 / \sqrt{2} \end{array}\right) ; \lambda_{3}=0 ; u_{3}=\left(\begin{array}{c} 1 / \sqrt{3} \\ -1 / \sqrt{3} \\ 1 / \sqrt{3} \end{array}\right) λ1=3;u1=1/6 2/6 1/6 ;λ2=1;u2=1/2 01/2 ;λ3=0;u3=1/3 1/3 1/3
通过 σ i = λ i \sigma_{i}=\sqrt{\lambda_{i}} σi=λi 求解奇异值为 3 \sqrt{3} 3 和1
最终矩阵A的奇异值分解为:
A = U Σ V T = ( 1 / 6 1 / 2 1 / 3 2 / 6 0 − 1 / 3 1 / 6 − 1 / 2 1 / 3 ) ( 3 0 0 1 0 0 ) ( 1 / 2 1 / 2 − 1 / 2 1 / 2 ) A=U \Sigma V^{T}=\left(\begin{array}{ccc} 1 / \sqrt{6} & 1 / \sqrt{2} & 1 / \sqrt{3} \\ 2 / \sqrt{6} & 0 & -1 / \sqrt{3} \\ 1 / \sqrt{6} & -1 / \sqrt{2} & 1 / \sqrt{3} \end{array}\right)\left(\begin{array}{cc} \sqrt{3} & 0 \\ 0 & 1 \\ 0 & 0 \end{array}\right)\left(\begin{array}{cc} 1 / \sqrt{2} & 1 / \sqrt{2} \\ -1 / \sqrt{2} & 1 / \sqrt{2} \end{array}\right) A=UΣVT=1/6 2/6 1/6 1/2 01/2 1/3 1/3 1/3 3 00010(1/2 1/2 1/2 1/2 )

5. SVD分解的应用

  1. 降维
    矩阵A的特征有n维,经过SVD分解之后,完全可以用前 r r r 个非零奇异值对应的奇异向量表示矩阵 A的主要特征。这样,就起到了j降维的作用。
  2. 压缩
    经过SVD分解之后,表示原来的矩阵A,只需要存 U , Σ , V U, \Sigma, V U,Σ,V 三个较小的矩阵即可。而这三个小矩 阵的规模加起来也远远小于原始矩阵A。这样,就达到压缩的作用。
  3. P C A \mathrm{PCA} PCA
    PCA降维需要找到样本协方差矩阵 X T X X^{T} X XTX 的最大 d \mathrm{d} d 个特征向量,然后用这些特征向量张成的矩阵 来做低维投影降维。这个过程中,需要先求出协方差矩阵 X T X X^{T} X XTX ,但是,当样本数和特征数很多 的时候,计算量是相当大的。
    SVD可以应用于PCA降维。注意到SVD可得到协方差矩阵 X T X X^{T} X XTX 最大的d个特征向量张成的矩 阵,但SVD有一个好处是先不求协方差矩阵 X T X X^{T} X XTX ,也能求解出右奇异矩阵 V \mathrm{V} V 。也就是说, P C A \mathrm{PCA} PCA 算法可以不用做特征分解,而是做SVD来完成。这个方法在样本量很大的时候很有效。实际 上,scikit-learn的PCA算法的背后真正实现就是SVD,而不是暴力特征求解。
    另一方面,PCA仅仅使用SVD的其中一个奇异矩阵,如右奇异矩阵,没有使用左奇异矩阵。假 设样本是 m × n m \times n m×n 矩阵X,如果使用SVD得到矩阵 X X T X X^{T} XXT 最大的 d \mathrm{d} d 个特征向量张成的 m × d m \times d m×d 维矩阵 U U U ,则进行如下处理:
    X d × n ′ = U d × m T X m × n X_{d \times n}^{\prime}=U_{d \times m}^{T} X_{m \times n} Xd×n=Ud×mTXm×n
    得到一个 d × n d \times n d×n 的矩阵 X ′ X^{\prime} X ,与原来的 m × n m \times n m×n 维矩阵X相比,行数由m降到d,可见对行数进行了 压缩。换句话说,左奇异矩阵用于行数的压缩;相对的,右奇异矩阵用于列数 (特征维度) 的压 缩,也就是PCA降维。

6.为什么Ax=0的解为最小奇异值对应的向量?

工程中很多问题会归结为求超定方程 A x = 0 , A \mathbf{A x}=\mathbf{0} , \mathbf{A} Ax=0A m × n m \times n m×n的矩阵,且 m > n m>n m>n 。如 SLAM中三角化地图点,PnP等一些问题都是求解这个方程。
很显然,这个方程有一个解,但这不是我们想要的,我们实际想求非零解。

为了求非零解,我们对 x \mathbf{x} x 加上一个约束 ∥ x ∥ 2 = 1 \|\mathbf{x}\|^{2}=1 x2=1 。也就是限制 x \mathbf{x} x 的长度为 1 。并构建成一 个带约束的最小二乘问题:
x ^ = arg ⁡ min ⁡ ∥ A x ∥ 2 , subject to  ∥ x ∥ 2 = 1 (1) \hat{\mathbf{x}}=\arg \min \|\mathbf{A} \mathbf{x}\|^{2} \text {, subject to }\|\mathbf{x}\|^{2}=1 \tag{1} x^=argminAx2, subject to x2=1(1)
这是一个带约束的最小二乘问题,我们把拉格朗日搬出来:
L ( x , λ ) = ∥ A x ∥ 2 + λ ( 1 − ∥ x ∥ 2 ) = x T A T A x + λ ( 1 − x T x ) (2) \begin{aligned} L(\mathbf{x}, \lambda) &=\|\mathbf{A} \mathbf{x}\|^{2}+\lambda\left(1-\|\mathbf{x}\|^{2}\right) \\ &=\mathbf{x}^{T} \mathbf{A}^{T} \mathbf{A} \mathbf{x}+\lambda\left(1-\mathbf{x}^{T} \mathbf{x}\right) \end{aligned} \tag{2} L(x,λ)=Ax2+λ(1x2)=xTATAx+λ(1xTx)(2)
为了求极值,我们分别对 x \mathbf{x} x λ \lambda λ 求偏导数,令为 0 :
∂ L ( x , λ ) ∂ x = 2 A T A x − 2 λ x = 0 ∂ L ( x , λ ) ∂ λ = 1 − x T x = 0 (3) \begin{aligned} &\frac{\partial L(\mathbf{x}, \lambda)}{\partial \mathbf{x}}=2 \mathbf{A}^{T} \mathbf{A} \mathbf{x}-2 \lambda \mathbf{x}=0 \\ &\frac{\partial L(\mathbf{x}, \lambda)}{\partial \lambda}=1-\mathbf{x}^{T} \mathbf{x}=0 \end{aligned} \tag{3} xL(x,λ)=2ATAx2λx=0λL(x,λ)=1xTx=0(3)
把(3)式整理一下:
( A T A − λ I ) x = 0 A T A x = λ x (4) \begin{array}{r} \left(\mathbf{A}^{T} \mathbf{A}-\lambda \mathbf{I}\right) \mathbf{x}=0 \\ \mathbf{A}^{T} \mathbf{A x}=\lambda \mathbf{x} \end{array} \tag{4} (ATAλI)x=0ATAx=λx(4)
可以看出 λ \lambda λ x \mathbf{x} x 分别是 A T A \mathbf{A}^{T} \mathbf{A} ATA 的特征值和特征向量。也就是说(1)式的解,就是这些特征向量 中的一个。
问题来了,那么多的特征向量,应该选择哪个作为解呢? 我们展开 ∥ A x ∥ 2 \|\mathbf{A x}\|^{2} Ax2 看一下:
∥ A x ∥ 2 = x T A T A x = x T λ x = λ x T x = λ (5) \|\mathbf{A} \mathbf{x}\|^{2}=\mathbf{x}^{T} \mathbf{A}^{T} \mathbf{A} \mathbf{x}=\mathbf{x}^{T} \lambda \mathbf{x}=\lambda \mathbf{x}^{T} \mathbf{x}=\lambda \tag{5} Ax2=xTATAx=xTλx=λxTx=λ(5)

(5)式的推导,用到了式(4)和 ∥ x ∥ 2 = 1 \|\mathbf{x}\|^{2}=1 x2=1

也就是说,我们想要 ∣ A x ∥ 2 |\mathbf{A} \mathbf{x}\|^{2} Ax2最小,就需要 λ \lambda λ最小。

那方程 (1) 的非零解就是 A T A \mathbf{A}^{T} \mathbf{A} ATA 最小特征值 λ \lambda λ 对应的特征向量。

例子:

假设 A \mathbf{A} A为8*9的矩阵,则SVD分解结果为:
A = U D V T \mathbf{A}=U D V^{T} A=UDVT

U:左奇异向量,为8×8的正交矩阵
V:右奇异向量,为9×9的正交矩阵

D:一个8*9的对角矩阵,除了对角线元素均为0,对角线元素称为奇异值,一般来说奇异值是按照从大到小的顺序降序排列。因为每一个奇异值都是一个残差项,因此最后一个奇异值最小,其含义是最优的残差。因此其对用的奇异值向量就是最优解。

参考文献

[1] https://blog.csdn.net/wangshuailpp/article/details/80209863 视觉SLAM常见的QR分解SVD分解等矩阵分解方式求解满秩和亏秩最小二乘问题(最全的方法分析总结)
[2] https://blog.csdn.net/code__online/article/details/90705000 矩阵分解SVD原理
[3] https://blog.csdn.net/jdy_lyy/article/details/104828112 矩阵(一):SVD分解
[4] https://blog.csdn.net/jdy_lyy/article/details/117171934 矩阵(二):为什么Ax=0的解为最小奇异值对应的向量?

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值