降维算法之奇异值分解SVD:7000字长文,看这一篇就够了!

引言

在2006年,Netflix曾经举办了一个奖金为100万美元的推荐系统算法比赛,最后的获奖者就使用了矩阵分解中的明星:奇异值分解SVD。SVD被广泛应用在推荐系统、图像处理等领域,是一种数据降维的经典方法。降维属于特征提取的子任务,关于降维,可以参考这篇博客

在正式阅读本文之前,不妨先看一个Youtube上Serrano Academic解释SVD的视频,再来阅读本文:这是他的B站视频&Youtube视频,可以很好地解释SVD的性质。

太长不看版:奇异值分解的通俗解释

许多数学对象可以通过将它们分解成多个组成部分或者找到它们的一些属性来更好地理解,这些属性是通用的,而不是由我们选择它们的方式所产生的。

例如,我们可以通过分解质因数来发现整数的一些内在性质。尽管我们可以用十进制或者二进制等不同方式表示整数12.但是12=2×3×3总是对的。从这个表示中我们可以获得一些有用的信息,比如12不能被5整除,或者12的倍数能够被3整除。

对应地,我们也可以通过分解矩阵来发现矩阵表示成数组元素时不明显的函数性质。特征分解是使用最广的矩阵分解之一,在这个分解中,我们将矩阵分解成一组特征向量和特征值。这样可以帮助我们分析矩阵的特定性质,就像质因数分解有助于我们理解整数。

奇异值分解是将矩阵分解为奇异向量奇异值。通过奇异值分解,我们会得到一些与特征分解相同类型的信息。然而,奇异值分解有更广泛的应用。每一个实数矩阵都有一个奇异值分解,但不一定都有特征分解(例如非方阵矩阵)。

例如对于一个矩阵\boldsymbol{A},我们使用特征分解去分析矩阵\boldsymbol{A}时,会得到特征向量构成的矩阵\boldsymbol{V}和特征值构成的向量\boldsymbol{\lambda},我们可以重新将\boldsymbol{A}写作:

\boldsymbol{A}=\boldsymbol{V}\mathrm{diag}\left( \boldsymbol{\lambda } \right) \boldsymbol{V}^{-\boldsymbol{1}}

奇异值分解是类似的,只不过这回我们将矩阵\boldsymbol{A}分解成三个矩阵的乘积:

\boldsymbol{A}\,\,=\,\,\boldsymbol{U\varSigma V}^{\boldsymbol{T}}

其中,\boldsymbol{U}m阶正交矩阵,\boldsymbol{V}n阶正交矩阵,\boldsymbol{\varSigma }是由降序排列的非负的对角线元素组成的m\times n矩形对角矩阵(不一定是方阵),对角线上的元素就叫做奇异值,既是\boldsymbol{A}^T\boldsymbol{A}特征值的平方根,也是\boldsymbol{AA}^T特征值的平方根\boldsymbol{U\varSigma V}^{\boldsymbol{T}}称为矩阵\boldsymbol{A}的奇异值分解,\boldsymbol{U}的列向量被称为左奇异向量,是\boldsymbol{AA}^T的特征向量;\boldsymbol{V}的列向量被称为右奇异向量,是\boldsymbol{A}^T\boldsymbol{A}的特征向量。

目录

太长不看版:奇异值分解的通俗解释

一、奇异值分解的定义与性质

1.1 奇异值分解简介

1.2 奇异值分解的定义和定理

1.3 奇异值分解的实例及numpy代码实现

 1.4 最有用的性质:Moore-Penrose 伪逆

二、紧奇异值分解和截断奇异值分解

2.1 紧奇异值分解

 2.2 截断奇异值分解

 2.3 截断奇异值分解的numpy代码实现

三、奇异值分解的几何解释

四、如何理解“奇异值分解是在平方损失意义下对矩阵的最优近似”?

4.1 范数及最优近似

4.2 矩阵的外积展开式

4.3 举个例子

 五、截断奇异值分解在sklearn中的体现

5.1 使用SVD进行简单矩阵降维并计算可解释方差比例

5.2 使用完全随机树的哈希特征转换

5.3 手写数字识别的嵌入(embedding)表示

六、如何使用奇异值分解进行图像压缩?

七、奇异值分解在推荐系统中的应用

扩展阅读


一、奇异值分解的定义与性质

1.1 奇异值分解简介

奇异值分解(singular value decomposition,SVD)是一种矩阵因子分解方法,是线性代数的概念,但在机器学习中被广泛应用。奇异值分解将任何给定矩阵分解为三个矩阵的乘积:一个正交的左奇异向量矩阵、一个对角的奇异值矩阵和一个正交的右奇异向量矩阵。将数据集的奇异值表征按重要性排列,舍弃不重要的特征向量,达到降维的目的,从而找出数据中的主成分。矩阵的奇异值分解可以看做是矩阵数据压缩的一种方法,即用因子分解的方式近似地表示原始矩阵,这种近似是在平方损失意义下的最优近似。它在应用如数据降维、信息检索、信号处理和图像压缩等领域具有重要作用。

1.2 奇异值分解的定义和定理

矩阵的奇异值分解是指将一个非零的实矩阵\boldsymbol{A}\boldsymbol{A}\,\,\epsilon \,\,\boldsymbol{R}^{m\times n},表示为以下三个实矩阵乘积形式的运算,即进行矩阵的因子分解:

\boldsymbol{A}\,\,=\,\,\boldsymbol{U\varSigma V}^{\boldsymbol{T}}

其中,\boldsymbol{U}m阶正交矩阵,\boldsymbol{V}n阶正交矩阵,\boldsymbol{\varSigma }是由降序排列的非负的对角线元素组成的m\times n矩形对角矩阵,对角线上的元素就叫做奇异值,满足:

\\ \boldsymbol{UU}^{\boldsymbol{T}}=\boldsymbol{I} \\ \boldsymbol{VV}^{\boldsymbol{T}}=\boldsymbol{I} \\ \boldsymbol{\varSigma }=\mathrm{diag}\left( \boldsymbol{\sigma }_1,\boldsymbol{\sigma }_2\cdots \boldsymbol{\sigma }_{\boldsymbol{p}} \right) \\ \boldsymbol{\sigma }_1\geqslant \boldsymbol{\sigma }_2\geqslant \cdots \geqslant \boldsymbol{\sigma }_{\boldsymbol{p}}\geqslant 0 \\ \boldsymbol{p}=\boldsymbol{min}\left( \boldsymbol{m},\boldsymbol{n} \right) \\

\boldsymbol{U\varSigma V}^{\boldsymbol{T}}称为矩阵\boldsymbol{A}的奇异值分解,\boldsymbol{\sigma }_{\boldsymbol{i}}称为矩阵\boldsymbol{A}的奇异值,\boldsymbol{U}的列向量被称为左奇异向量,\boldsymbol{V}的列向量被称为右奇异向量。

任意给定一个实矩阵,其奇异值分解一定存在,但不唯一,这就是奇异值分解的基本定理

1.3 奇异值分解的实例及numpy代码实现

奇异值分解的直观表现如下图所示:

例如一个5×4的矩阵\boldsymbol{A}要进行奇异值分解,则\boldsymbol{A}的奇异值分解可表示为:

\boldsymbol{A}_{5\times 4}=\boldsymbol{U}_{5\times 5}\boldsymbol{\varSigma }_{5\times 4}\boldsymbol{V}_{4\times 4}

给定矩阵\boldsymbol{A}

\boldsymbol{A}_{5\times 4}=\left[ \begin{array}{c} \begin{matrix} 1& 0& 0& 0\\ 0& 0& 0& 4\\ 0& 3& 0& 0\\ 0& 0& 0& 0\\ \end{matrix}\\ \begin{matrix} 2& 0& 0& 0\\ \end{matrix}\\ \end{array} \right]

它的奇异值分解由三个矩阵的乘积\boldsymbol{U\varSigma V}^{\boldsymbol{T}},矩阵\boldsymbol{U}\boldsymbol{\varSigma }\boldsymbol{V}分别为:

\boldsymbol{U}_{5\times 5}=\left[ \begin{array}{c} \begin{matrix} 0& 0& \sqrt{0.2}& 0\\ 1& 0& 0& 0\\ 0& 1& 0& 0\\ 0& 0& 0& 1\\ \end{matrix}\begin{array}{c} \sqrt{0.8}\\ 0\\ 0\\ 0\\ \end{array}\\ \begin{matrix} \begin{matrix} 0& 0\\ \end{matrix}& \sqrt{0.8}& 0& -\sqrt{0.2}\\ \end{matrix}\\ \end{array} \right]

\boldsymbol{\varSigma }_{5\times 4}=\left[ \begin{array}{c} \begin{matrix} 4& 0& 0& 0\\ 0& 3& 0& 0\\ 0& 0& \sqrt{5}& 0\\ 0& 0& 0& 0\\ \end{matrix}\\ \begin{matrix} 2& 0& \,\,0& \,\,0\\ \end{matrix}\\ \end{array} \right]            \boldsymbol{V}_{4\times 4}=\left[ \begin{matrix} 0& 0& 0& 1\\ 0& 1& 0& 0\\ 1& 0& 0& 0\\ 0& 0& 1& 0\\ \end{matrix} \right]

矩阵\boldsymbol{\varSigma }_{5\times 4}是对角矩阵,对角线外的元素都是0,对角线上元素非负,按降序排列。矩阵\boldsymbol{U}_{5\times 5}\boldsymbol{V}_{4\times 4}是正交矩阵,它们与各自的转置矩阵相乘是单位矩阵\boldsymbol{I}

矩阵的奇异值分解并不唯一,如果在此例中选择U为

 而\boldsymbol{\varSigma }_{5\times 4}\boldsymbol{V}_{4\times 4}不变,那么\boldsymbol{U\varSigma V}^{\boldsymbol{T}}也是\boldsymbol{A}的一个奇异值分解。

使用numpy进行上述矩阵的奇异值分解代码如下:

import numpy as np
# 创建矩阵A
A = np.array([[1, 0, 0, 0],
              [0, 0, 0, 4],
              [0, 3, 0, 0],
              [0, 0, 0, 0],
              [2, 0, 0, 0]])

# 进行奇异值分解
U, s, V = np.linalg.svd(A)
# 打印结果
print("U:\n", U)
print("s:", s)
print("V:\n", V)

运行结果如下:

U:
 [[ 0.          0.         -0.4472136   0.         -0.89442719]
 [-1.          0.          0.          0.          0.        ]
 [ 0.         -1.          0.          0.          0.        ]
 [ 0.          0.          0.          1.          0.        ]
 [ 0.          0.         -0.89442719  0.          0.4472136 ]]
s: [ 4.          3.          2.23606798 -0.        ]
V:
 [[-0. -0. -0. -1.]
 [-0. -1. -0. -0.]
 [-1. -0. -0. -0.]
 [-0. -0. -1. -0.]]

 1.4 最有用的性质:Moore-Penrose 伪逆

SVD的一个最有用的性质可能是拓展矩阵求逆到非方的矩阵上。Moore-Penrose 伪逆使我们在求解非方矩阵的线性方程中取得了一定的进展。矩阵\boldsymbol{A}的伪逆定义为:

\boldsymbol{A}^+=\underset{a\rightarrow 0}{\lim}\left( \boldsymbol{A}^T\boldsymbol{A}+\alpha I \right) ^{-1}\boldsymbol{A}^T

 计算伪逆的式计算法并没有基于这个定义,而是使用下面的公式

\boldsymbol{A}^+=\boldsymbol{V\varSigma }^+\boldsymbol{U}^T

其中\boldsymbol{V}\boldsymbol{\varSigma}\boldsymbol{U}是矩阵\boldsymbol{A}奇异值分解后得到的矩阵。对角矩阵\boldsymbol{\varSigma }的伪逆\boldsymbol{\varSigma }^+是其非零元素取倒数之后再转置得到的。当矩阵\boldsymbol{A}的列数多于行数时,使用伪逆求解线性方程是众多可能解法中的一种,行数多于列数时,可能没有解。

二、紧奇异值分解和截断奇异值分解

1.3中提到的奇异值分解被称为矩阵的完全奇异值分解。实际上常用的是奇异值分解的紧凑形式和截断形式。紧奇异值分解是与原始矩阵等秩的奇异值分解,截断奇异值分解是比原始矩阵低秩的奇异值分解。

2.1 紧奇异值分解

紧奇异值分解的举例,还是上面那个例子:

 2.2 截断奇异值分解

在矩阵的奇异值分解中,只取最大的k个奇异值(k<r,r为矩阵的秩)对应的部分,就得到矩阵的截断奇异值分解。实际应用中提到矩阵的奇异值分解时,通常指截断奇异值分解。

 2.3 截断奇异值分解的numpy代码实现

实际上,紧奇异值分解就是截断奇异值分解的一种特殊表现形式。将矩阵A进行紧奇异值分解的代码如下(设置k=2,指定要保留的前2个奇异值),如果设置k=3,即可实现紧奇异值分解:

import numpy as np
# 创建矩阵A
A = np.array([[1, 0, 0, 0],
              [0, 0, 0, 4],
              [0, 3, 0, 0],
              [0, 0, 0, 0],
              [2, 0, 0, 0]])

# 进行截断奇异值分解
k = 2  # 指定要保留的前 k 个奇异值
U, s, V = np.linalg.svd(A, full_matrices=False)

# 仅保留前 k 个奇异值及相应的奇异向量
U_k = U[:, :k]
s_k = s[:k]
V_k = V[:k, :]

# 构建紧奇异值分解的近似矩阵
A_k = U_k.dot(np.diag(s_k)).dot(V_k)

# 打印结果
print("U_k:\n", U_k)
print("s_k:", s_k)
print("V_k:\n", V_k)
print("A_k:\n", A_k)

运行结果如下所示:

U_k:
 [[ 0.  0.]
 [-1.  0.]
 [ 0. -1.]
 [ 0.  0.]
 [ 0.  0.]]
s_k: [4. 3.]
V_k:
 [[-0. -0. -0. -1.]
 [-0. -1. -0. -0.]]
A_k:
 [[0. 0. 0. 0.]
 [0. 0. 0. 4.]
 [0. 3. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]]

结果基本上与例15.3保持一致。

实际应用中,常常需要对矩阵的数据进行压缩,将其近似表示,奇异值分解提供了一种方法。奇异值分解是在平方损失意义下对矩阵的最优近似。紧奇异值分解对应无损压缩,截断奇异值分解对应有损压缩。

三、奇异值分解的几何解释

矩阵的奇异值分解也可以看做是:一个坐标系的旋转或反射变换、一个坐标轴的缩放变换、另一个坐标系的旋转或反射变换。奇异值定理保证这种分解一定存在,这就是奇异值分解的几何解释。如下图所示:

四、如何理解“奇异值分解是在平方损失意义下对矩阵的最优近似”?

4.1 范数及最优近似

奇异值分解是在平方损失(弗罗贝尼乌斯范数)意义下对矩阵的最优近似,即数据压缩。矩阵的弗罗贝尼乌斯范数是向量的\boldsymbol{L}_2范数的直接推广,对应机器学习中的平方损失函数。

矩阵\boldsymbol{A}=\left[ a_{ij} \right] _{m\times n}的弗罗贝尼乌斯范数为:

\|\mathbf{A}\|_F = \sqrt{\sum_{i=1}^{m} \sum_{j=1}^{n} |a_{ij}|^2}

在秩不超过km\times n矩阵集合中,存在矩阵\boldsymbol{A}的弗罗贝尼乌斯范数意义下的最优近似矩阵\boldsymbol{X}。秩为k的截断奇异值分解得到的矩阵\boldsymbol{A}_k能够达到这个最优值。也就是说,\boldsymbol{A}_k=\boldsymbol{U\varSigma }'\boldsymbol{V}是达到最优值的一个矩阵。即:

\left\| \boldsymbol{A}-\boldsymbol{X} \right\| _F=\left( \sigma _{k+1}^{2}+\sigma _{k+2}^{2}+\cdots +\sigma _{k+n}^{2} \right) ^{\frac{1}{2}}=\left\| \boldsymbol{A}-\boldsymbol{A}_k \right\| _F

事实上,前面提到的紧奇异值分解是在弗罗贝尼乌斯范数意义下的无损压缩,截断奇异值分解是有损压缩。截断奇异值分解得到的矩阵的秩为k,通常远小于原始矩阵的秩r,所以是由低秩矩阵实现了对原始矩阵的压缩。

4.2 矩阵的外积展开式

\boldsymbol{A}的奇异值分解也可以由外积的形式表示,可以写成下面的形式:

\boldsymbol{A}=\sum_{k=1}^n{\boldsymbol{A}_k}=\sum_{k=1}^n{\sigma _k}\boldsymbol{u}_k\boldsymbol{v}_{k}^{T}

其中,\boldsymbol{A}_k=\sigma _k\boldsymbol{u}_k\boldsymbol{v}_{k}^{T}m\times n矩阵。上式将矩阵\boldsymbol{A}分解为矩阵的有序加权和,展开可得到:

\boldsymbol{A}=\sigma _1\boldsymbol{u}_1\boldsymbol{v}_{1}^{T}+\sigma _2\boldsymbol{u}_2\boldsymbol{v}_{2}^{T}+\cdots +\sigma _n\boldsymbol{u}_n\boldsymbol{v}_{n}^{T}

n=k时,矩阵\boldsymbol{A}的秩为k,并且\boldsymbol{A}_k是秩为k的矩阵中在弗罗贝尼乌斯范数意义下\boldsymbol{A}的最优近似矩阵。矩阵\boldsymbol{A}_k就是\boldsymbol{A}的截断奇异值分解。

通常奇异值\sigma _i递减的很快,因此k取很小值时,\boldsymbol{A}_k也可以对\boldsymbol{A}有很好地近似。

4.3 举个例子

视频中的例子:例如一个4\times 4的矩阵\boldsymbol{A},可被分解成4个秩为1的外积展开形式,如下图所示

 具体到某个矩阵如下图所示

实际上,在这个例子中,k=2时的值已经非常接近原始矩阵了。这样就实现了矩阵在某种程度上的的降维。

 五、截断奇异值分解在sklearn中的体现

在sklearn中,奇异值分解主要以截断奇异值分解sklearn.decomposition.TruncatedSVD的形式体现。在官方文档中,奇异值分解用于图像压缩总共有三个示例:使用SVD进行简单矩阵降维、使用完全随机树的哈希特征转换,以及手写数字识别的嵌入表示。

5.1 使用SVD进行简单矩阵降维并计算可解释方差比例

 代码如下所示:

#计算可解释方差比例的代码
from sklearn.decomposition import TruncatedSVD
from scipy.sparse import csr_matrix
import numpy as np
np.random.seed(0)
X_dense = np.random.rand(100, 100)
X_dense[:, 2 * np.arange(50)] = 0
X = csr_matrix(X_dense)
svd = TruncatedSVD(n_components=5, n_iter=7, random_state=42)
svd.fit(X)

print(svd.explained_variance_ratio_)

print(svd.explained_variance_ratio_.sum())

print(svd.singular_values_)

输出结果如下:

[0.01570766 0.05122679 0.04998062 0.04795064 0.04539933]
0.21026503465070345
[35.24105443  4.5981613   4.54200434  4.44866153  4.32887456]

5.2 使用完全随机树的哈希特征转换

RandomTreesEmbedding提供了一种将数据映射到非常高维、稀疏表示的方法,这可能有利于分类。这种映射是完全无监督的,而且非常有效。这个转换可以用于特征选择、数据压缩、非线性降维等任务,它可以帮助提取和表示数据中的结构信息,用于后续的机器学习任务。相邻的点往往共享树的同一片叶子,因此共享其哈希表示的大部分内容。这样就可以简单地根据截断的SVD转换后的数据的主成分来分离两个同心圆。

5.3 手写数字识别的嵌入(embedding)表示

嵌入的目的是将高维数据(在这里是64维的手写数字图像数据)映射到低维空间(通常是2维或3维),以便更好地理解和可视化数据的结构和特征。通过降低数据的维度,我们可以在更低的维度下观察和解释数据之间的关系,发现潜在的聚类、分布或其他模式。这可以帮助我们进行数据分析、可视化和模型构建等任务。在这个例子中,通过使用不同的嵌入方法,可以将手写数字数据集投影到二维空间,并观察不同数字之间的分布和关系。

在这里,截断SVD(TruncatedSVD)被用作一种降维方法来进行嵌入。通过将原始特征矩阵进行降维,将64维的手写数字图像数据映射到2维空间。降维后的结果可以用于可视化手写数字数据集,观察数字之间的分布和关系。

六、如何使用奇异值分解进行图像压缩?

这里参考了牛客网上红名大佬的专栏

奇异值分解(SVD)可以有效地应用于图像压缩,具体步骤如下:

  • 导入图像:将图像文件导入程序中,并将其转换为数字矩阵。对于彩色图像,它通常有三个通道(红、绿、蓝),需要分别对每个通道进行处理。
  • 计算奇异值分解:对每个颜色通道的矩阵执行 SVD 操作,得到对应的 U、Σ 和 Vᵀ 矩阵。这需要单独对红、绿、蓝通道执行 SVD。
  • 选择奇异值个数:选择要使用的奇异值数量 k,这将直接影响压缩率和图像质量。较低的 k 值会产生更高的压缩率,但图像质量可能降低;较高的 k 值会保留更多图像细节,但压缩率较低。
  • 截断奇异值矩阵:截取每个通道的前 k 个奇异值,同时截取相应的 U 和 Vᵀ 矩阵的前 k 列,从而获得截断后的 U、Σ 和 Vᵀ 矩阵。
  • 图像重建:将截断后的 U、Σ 和 Vᵀ 矩阵相乘,得到近似的颜色通道矩阵。将每个通道的近似矩阵组合,形成近似的图像矩阵。这相当于只保留了原始图像数据中最重要的 k 个特征。
  • 导出压缩后的图像:将近似的图像矩阵转换为相应的图像格式(如 JPEG、PNG 等)并导出。

使用 SVD 进行图像压缩的一个主要优点是它可以自适应地找到能够最大程度保留图像信息的最佳低维表示。然而,选择合适的 k 值在很大程度上取决于需要保留的图像质量和压缩率的权衡。

需要注意的是,SVD 压缩不是一种无损压缩方法,因为丢弃一部分奇异值会导致信息丢失。然而,在许多情况下,SVD 能够提供令人满意的图像质量和压缩率。

这里是Github上一个大佬写的SVD进行图像压缩的代码,也使用图像的方式对4.3节的内容进行了生动的演示,比较清晰易懂。

七、奇异值分解在推荐系统中的应用

奇异值分解在推荐系统中的应用主要侧重于基于模型的协同过滤。SVD 通过对用户项目评分矩阵进行分解,捕获用户和项目之间的潜在关系,从而预测用户对未评分项目的潜在评分,进行个性化推荐。

以下是在推荐系统中应用 SVD 的具体步骤:

  • 构建评分矩阵:首先构建一个用户-项目评分矩阵,其中每行表示一个用户,每列表示一个项目,矩阵中的元素表示用户对项目的评分。通常,这个矩阵是稀疏的,因为用户只对部分项目进行了评分。
  • 计算奇异值分解:对评分矩阵进行奇异值分解,得到 U, Σ 和 Vᵀ。U 和 V 分别表示左奇异向量和右奇异向量矩阵,而 Σ 是一个对角矩阵,其对角线上的元素表示奇异值。
  • 选择潜在因子数量:选择一个减小维度的值 k,用于表示潜在因子的数量。选择较小的 k 值可以实现压缩评分矩阵,但过小的 k 可能损失较多信息。选择合适的 k 值需要在压缩程度和损失信息之间取得平衡。
  • 重构低秩评分矩阵:通过截取 U、Σ 和 Vᵀ 中前 k 个奇异值及相应的向量,重新构建近似评分矩阵。这个近似矩阵将捕获用户和项目之间的主要潜在关系,但使用较少的存储空间。
  • 预测未评分项目:使用重构的评分矩阵来预测用户对尚未评分项目的评分。通过比较重构矩阵中用户行和项目列的值,可以估计用户对每个项目的潜在评分。
  • 计算推荐:根据预测的评分,为每个用户推荐具有最高潜在评分的项目。

SVD 能有效找到原始评分矩阵中的潜在关系,从而实现高效的推荐。不过,在推荐系统实际应用中,奇异值分解可能需要面临大规模且稀疏的矩阵。

具体示例可参见《机器学习实战》第14章:260~266.

扩展阅读

有兴趣亦可以看看关于SVD的维基百科,更加全面但是可能不一定能用到。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值