← 返回《人工智能数学基础》
📑 查看全课大纲(第 82 / 93 节)
  1. 1.概论和集合的定义
  2. 2.逼疯康托的实数集理论
  3. 3.常用不等式与映射
  4. 4.函数及特殊函数
  5. 5.序列极限的定义
  6. 6.序列极限的性质与夹逼定理
  7. 7.重要极限
  8. 8.无穷小量,无穷大量和一组重要的阶的比较关系
  9. 9.聚点原理
  10. 10.函数极限及其性质
  11. 11.重要极限与等价无穷小
  12. 12.连续函数
  13. 13.导数的概念(那些年,扛起牛顿的胡克)
  14. 14.定义法求导
  15. 15.函数四则运算的导数与反函数求导法则
  16. 16.复合函数,隐函数,参数式求导
  17. 17.不定式求导之“洛必达与伯努利的师生情”
  18. 18.一阶微分
  19. 19.高阶导数
  20. 20.高阶微分
  21. 21.罗尔中值定理与拉格朗日中值定理
  22. 22.柯西空降科学院遭排挤
  23. 23.泰勒公式与泰勒的克妻属性
  24. 24.利用泰勒展开唯一性定理计算泰勒展开
  25. 25.泰勒公式的余项估计
  26. 26.极值问题与导数
  27. 27.函数凹凸性
  28. 28.无卵用的渐近线与函数作图
  29. 29.不定积分的定义
  30. 30.第一换元法
  31. 31.第二换元法
  32. 32.分部积分法
  33. 33.有理式积分
  34. 34.三角替换
  35. 35.定积分的概念
  36. 36.定积分的性质与积分中值定理
  37. 37.变上限定积分
  38. 38.微积分基本定理之“高斯教你如何优雅地装逼”
  39. 39.定积分的换元法
  40. 40.奇偶函数与周期函数的定积分
  41. 41.曲线求长与不可求长曲线(海岸线居然算不出长度?)
  42. 42.旋转体体积
  43. 43.旋转体侧面积
  44. 44.极坐标下图形的面积(数学系常用表白曲线)
  45. 45.欧式空间
  46. 46.点列极限,开集与闭集
  47. 47.多元函数的定义
  48. 48.多元函数的极限
  49. 49.多元连续函数
  50. 50.一阶偏导数
  51. 51.高阶偏导数
  52. 52.全微分
  53. 53.方向导数与梯度
  54. 54.链式法则
  55. 55.一阶全微分形式的不变性与高阶微分
  56. 56.多元函数的泰勒公式
  57. 57.隐函数存在定理与逆映射存在定理
  58. 58.多元函数的极值
  59. 59.矩阵基础知识
  60. 60.行列式的定义与特殊矩阵的行列式
  61. 61.行列式的性质
  62. 62.行列式按k行展开
  63. 63.线性方程组初步与高斯消元法
  64. 64.齐次线性方程组与Cramer法则
  65. 65.线性空间
  66. 66.线性相关与线性无关
  67. 67.向量组的秩
  68. 68.矩阵的秩与线性方程组有解的充要条件
  69. 69.齐次线性方程组的解集结构
  70. 70.非齐次线性方程组解集结构
  71. 71.基与维数
  72. 72.矩阵的乘法
  73. 73.特殊矩阵
  74. 74.矩阵乘积的秩与行列式
  75. 75.矩阵的逆
  76. 76.正交矩阵
  77. 77.矩阵对角化与特征值特征向量
  78. 78.实对称矩阵对角化
  79. 79.二次型与正定矩阵
  80. 80.LU分解
  81. 81.Cholesky分解
  82. 82.SVD分解
  83. 83.线搜索
  84. 84.步长
  85. 85.最速下降法和牛顿法
  86. 86.共轭梯度法
  87. 87.拟牛顿法
  88. 88.无约束优化
  89. 89.若干知识点补充(一)
  90. 90.若干知识点补充(二)
  91. 91.凸优化问题
  92. 92.对偶问题(一)
  93. 93.对偶问题(二)

SVD分解

约 31 分钟

📺 正在播放小象官方高清录播(支持倍速与清晰度调节)

SVD分解

小象实战讲义 · 人工智能数学基础

在机器学习和数据分析中,我们常常需要处理高维、大规模的矩阵数据。如何有效地压缩数据、去除噪声或提取核心特征,是实际应用中的关键问题。奇异值分解(Singular Value Decomposition, SVD)作为一种强大的矩阵分解工具,为解决这些问题提供了统一的数学框架。本节我们将深入理解SVD的定义、性质及其在图像压缩、降噪和主成分分析(PCA)中的核心应用。

💡 核心导读

  • SVD的定义与存在性:理解任意矩阵均可分解为三个特定矩阵的乘积形式,并掌握其矩阵维数的对应关系。
  • 奇异值与特征值的关系:掌握奇异值来源于矩阵 ATAA^TAAATAA^T 的特征值平方根,并理解其几何与代数意义。
  • 分解的几何解释与低秩近似:从加权和的角度理解SVD,掌握利用前 rr 个奇异值进行矩阵低秩近似的原理与方法。
  • 核心应用实践:学习SVD在图像压缩、图像降噪中的具体实现,并理解其与主成分分析(PCA)的内在等价关系。

SVD的定义与基本性质

对于任意一个 m×nm \times n 的实矩阵 AA,都存在一个奇异值分解(SVD),使得 AA 可以表示为三个矩阵的乘积:

A=UΣVTA = U \Sigma V^T

其中:

  • UU 是一个 m×mm \times m正交矩阵,即 UTU=UUT=ImU^T U = U U^T = I_m。其列向量 u1,,um\mathbf{u}_1, \dots, \mathbf{u}_m 称为 AA左奇异向量
  • VV 是一个 n×nn \times n正交矩阵,即 VTV=VVT=InV^T V = V V^T = I_n。其列向量 v1,,vn\mathbf{v}_1, \dots, \mathbf{v}_n 称为 AA右奇异向量
  • Σ\Sigma 是一个 m×nm \times n对角矩阵(更准确地说,是矩形对角矩阵),其形式取决于 mmnn 的大小关系:
    • mnm \ge n,则 Σ\Sigma 形如 [D0]\begin{bmatrix} D \ 0 \end{bmatrix},其中 D=diag(σ1,σ2,,σn)D = \text{diag}(\sigma_1, \sigma_2, \dots, \sigma_n)
    • mnm \le n,则 Σ\Sigma 形如 [D0]\begin{bmatrix} D & 0 \end{bmatrix},其中 D=diag(σ1,σ2,,σm)D = \text{diag}(\sigma_1, \sigma_2, \dots, \sigma_m)

矩阵 Σ\Sigma 主对角线(即行标与列标相等的元素)上的非零元素 σ1,σ2,,σp\sigma_1, \sigma_2, \dots, \sigma_p(其中 p=min(m,n)p = \min(m, n))称为矩阵 AA奇异值。我们约定奇异值按降序排列:

σ1σ2σp0\sigma_1 \ge \sigma_2 \ge \dots \ge \sigma_p \ge 0

奇异值与矩阵 ATAA^TAAATAA^T 的特征值有直接关系。ATAA^TA 是一个 n×nn \times n 的半正定矩阵,其特征值 λi\lambda_i 均为非负实数。奇异值 σi\sigma_i 正是这些特征值的平方根:

σi=λi(ATA),i=1,,n\sigma_i = \sqrt{\lambda_i(A^TA)}, \quad i = 1, \dots, n

同理,AATAA^T 的特征值也包含这些 λi\lambda_i(另有 mn|m-n| 个零特征值)。因此,奇异值可以看作是矩阵 AA 的“拉伸”强度的度量。

SVD的推导:与特征值分解的联系

SVD的存在性及其性质可以通过特征值分解来理解和证明。假设 A=UΣVTA = U \Sigma V^T 成立,我们来考察 ATAA^TAAATAA^T

1. 考察 ATAA^TA ATA=(UΣVT)T(UΣVT)=VΣTUTUΣVT=VΣTΣVT(因为 UTU=I)=VΛVT\begin{aligned} A^TA &= (U \Sigma V^T)^T (U \Sigma V^T) \ &= V \Sigma^T U^T U \Sigma V^T \ &= V \Sigma^T \Sigma V^T \quad (\text{因为 } U^T U = I) \ &= V \Lambda V^T \end{aligned} 其中 Λ=ΣTΣ\Lambda = \Sigma^T \Sigma 是一个 n×nn \times n 的对角矩阵。由于 Σ\Sigma 是广义对角矩阵,ΣTΣ\Sigma^T \Sigma 的计算结果是一个标准的对角方阵,其对角线元素正是 σi2\sigma_i^2。 上式 ATA=VΛVTA^TA = V \Lambda V^T 正是实对称矩阵 ATAA^TA正交对角化。因此:

  • VV 的列向量 vi\mathbf{v}_iATAA^TA单位特征向量,对应于特征值 λi=σi2\lambda_i = \sigma_i^2
  • VV 就是使得 ATAA^TA 对角化的正交矩阵。

2. 考察 AATAA^T AAT=(UΣVT)(UΣVT)T=UΣVTVΣTUT=UΣΣTUT(因为 VTV=I)=UΛUT\begin{aligned} AA^T &= (U \Sigma V^T) (U \Sigma V^T)^T \ &= U \Sigma V^T V \Sigma^T U^T \ &= U \Sigma \Sigma^T U^T \quad (\text{因为 } V^T V = I) \ &= U \tilde{\Lambda} U^T \end{aligned} 其中 Λ=ΣΣT\tilde{\Lambda} = \Sigma \Sigma^T 是一个 m×mm \times m 的对角矩阵,其前 pp 个对角线元素也是 σi2\sigma_i^2,其余为0。 同理,上式表明 UU 的列向量 ui\mathbf{u}_iAATAA^T单位特征向量,对应于非零特征值 σi2\sigma_i^2

这个推导过程也揭示了计算SVD的一种理论方法:分别计算 ATAA^TAAATAA^T 的特征值与特征向量,然后取特征值的平方根得到奇异值,并将对应的特征向量排列成 VVUU。需要注意的是,由于特征向量在符号选择(±\pm)和特征子空间内正交基选取上的自由度,SVD分解本身不是唯一的。但在奇异值降序排列的约定下,奇异值本身是唯一的。

SVD的几何解释与低秩近似

将矩阵 UUVV 按列分块:U=[u1,,um]U = [\mathbf{u}_1, \dots, \mathbf{u}_m]V=[v1,,vn]V = [\mathbf{v}_1, \dots, \mathbf{v}_n]。为简化讨论,假设 mnm \ge n,则 Σ=[D0]\Sigma = \begin{bmatrix} D \ 0 \end{bmatrix},其中 D=diag(σ1,,σn)D = \text{diag}(\sigma_1, \dots, \sigma_n)

将SVD展开为矩阵乘法的和形式: A=UΣVT=[u1,,um][σ1σn0][v1TvnT]=σ1u1v1T+σ2u2v2T++σnunvnT\begin{aligned} A &= U \Sigma V^T = [\mathbf{u}_1, \dots, \mathbf{u}_m] \begin{bmatrix} \sigma_1 & & \ & \ddots & \ & & \sigma_n \ & 0 & \end{bmatrix} \begin{bmatrix} \mathbf{v}_1^T \ \vdots \ \mathbf{v}_n^T \end{bmatrix} \ &= \sigma_1 \mathbf{u}_1 \mathbf{v}_1^T + \sigma_2 \mathbf{u}_2 \mathbf{v}_2^T + \dots + \sigma_n \mathbf{u}_n \mathbf{v}_n^T \end{aligned}

这里,每一项 uiviT\mathbf{u}_i \mathbf{v}_i^T 是一个 m×nm \times n 的矩阵,其秩为1。因此,SVD将原矩阵 AA 分解为 nn 个秩为1的矩阵的加权和,权重就是奇异值 σi\sigma_i

由于奇异值按降序排列,σ1\sigma_1 对应的分量 σ1u1v1T\sigma_1 \mathbf{u}_1 \mathbf{v}_1^TAA 的“贡献”最大,σ2\sigma_2 次之,依此类推。在许多实际矩阵(如图像数据、用户-物品评分矩阵)中,奇异值衰减得非常快,前10%甚至1%的奇异值之和就可能占到全部奇异值之和的99%以上。

这启发我们进行低秩近似:只保留前 rrrmin(m,n)r \ll \min(m, n))个最大的奇异值及其对应的左右奇异向量,用它们的加权和来近似原矩阵 AA

AAr=i=1rσiuiviT=UrΣrVrTA \approx A_r = \sum_{i=1}^{r} \sigma_i \mathbf{u}_i \mathbf{v}_i^T = U_r \Sigma_r V_r^T

其中:

  • UrU_rUU 的前 rr 列,维度为 m×rm \times r
  • Σr\Sigma_rΣ\Sigma 的左上角 r×rr \times r 子矩阵,即 diag(σ1,,σr)\text{diag}(\sigma_1, \dots, \sigma_r)
  • VrV_rVV 的前 rr 列,维度为 n×rn \times r,因此 VrTV_r^T 的维度为 r×nr \times n

这种近似在Frobenius范数意义下是最优的,即 ArA_r 是所有秩不超过 rr 的矩阵中,与 AA 的Frobenius距离最小的那个。

低秩近似带来了两大核心应用:

  1. 数据压缩:存储原始矩阵 AA 需要 m×nm \times n 个元素。存储近似矩阵 ArA_r 只需要存储 UrU_rΣr\Sigma_rVrV_r,总计 mr+r+nr=r(m+n+1)mr + r + nr = r(m + n + 1) 个元素。当 rr 很小时,存储量大幅降低。
  2. 噪声抑制:信号或数据中的主要信息往往蕴含在较大的奇异值对应的分量中,而噪声或细微扰动则体现在较小的奇异值分量里。通过舍弃这些小奇异值分量,可以达到滤除噪声、保留主体结构的目的。

应用实践:图像压缩与降噪

图像可以看作一个像素值矩阵。对图像矩阵进行SVD,并利用其低秩近似特性,可以实现图像的有损压缩和降噪。

图像压缩

压缩过程:

  1. 将灰度图像读入为一个 m×nm \times n 的矩阵 AA(每个元素代表一个像素的亮度)。
  2. AA 进行SVD分解:A=UΣVTA = U \Sigma V^T
  3. 选择截断秩 rr,计算近似矩阵 Ar=U:,1:rΣ1:r,1:rV:,1:rTA_r = U_{:, 1:r} \cdot \Sigma_{1:r, 1:r} \cdot V_{:, 1:r}^T
  4. ArA_r 的值重新缩放到合法的像素值范围(如0-255),并保存为图像。

压缩比约为:r(m+n+1)m×n\frac{r(m + n + 1)}{m \times n}rr 越小,压缩比越高,但图像质量损失也越大。

图像降噪

降噪过程与压缩类似,其核心思想是:噪声通常对应于较小的奇异值。通过选择一个合适的截断秩 rr,舍弃那些代表噪声的小奇异值分量,即可在尽量保留图像主体信息的同时抑制噪声。

选择 rr 是关键:rr 太小会损失有用信息(如图像边缘变得模糊),rr 太大则降噪效果不明显。通常需要根据具体图像和噪声特性进行权衡。

下面的代码示例演示了如何利用 numpy 对一张示例图像进行SVD压缩与重构。

import numpy as np
import matplotlib.pyplot as plt

# 1. 生成示例图像(用数学函数合成一张灰度测试图并叠加噪声,无需联网下载)
np.random.seed(42)
m, n = 200, 200
gx = np.linspace(0, 4 * np.pi, n)
gy = np.linspace(0, 4 * np.pi, m)
X, Y = np.meshgrid(gx, gy)
A = 128 + 80 * np.sin(X) * np.cos(Y) + 40 * np.sin(2 * X + Y)
A = A + 10 * np.random.randn(m, n)  # 叠加少量随机噪声,模拟真实图像
A = np.clip(A, 0, 255).astype(np.float64)
print(f"原始图像尺寸: {m} x {n}, 总像素数: {m * n}")

# 2. 对图像矩阵进行SVD分解
U, S, Vt = np.linalg.svd(A, full_matrices=False)
# 注意:np.linalg.svd返回的S是奇异值的一维向量,Vt = V^T
p = len(S)  # 奇异值个数,p = min(m, n)

# 3. 定义不同秩r进行压缩和重构
ranks = [1, 5, 20, 50]
fig, axes = plt.subplots(1, len(ranks) + 1, figsize=(15, 4))

# 显示原图
axes[0].imshow(A, cmap='gray')
axes[0].set_title(f'原图\n{m}x{n}')
axes[0].axis('off')

for idx, r in enumerate(ranks, start=1):
    # 使用前r个奇异值重构图像
    # A_r = U[:, :r] @ np.diag(S[:r]) @ Vt[:r, :]
    # 更高效的乘法方式:
    A_reconstructed = (U[:, :r] * S[:r]) @ Vt[:r, :]
    
    # 确保像素值在合理范围内
    A_reconstructed = np.clip(A_reconstructed, 0, 255)
    
    # 计算存储量
    original_size = m * n
    compressed_size = r * (m + n + 1)  # U_r: m*r, S_r: r, V_r^T: r*n
    compression_ratio = compressed_size / original_size
    
    # 显示重构图像
    axes[idx].imshow(A_reconstructed, cmap='gray')
    axes[idx].set_title(f'r={r}\n压缩比: {compression_ratio:.2%}')
    axes[idx].axis('off')

plt.tight_layout()
plt.show()

# 4. 打印奇异值衰减情况(前20个)
print("前20个奇异值:")
print(S[:20])
print(f"\n前10个奇异值之和 / 总和: {np.sum(S[:10]) / np.sum(S):.2%}")
print(f"前20个奇异值之和 / 总和: {np.sum(S[:20]) / np.sum(S):.2%}")

SVD与主成分分析(PCA)的关系

主成分分析(PCA)是一种经典的数据降维方法,其目标是为原始数据寻找一组新的正交基(主成分),使得数据在这些新方向上的方差最大。SVD是实现PCA最稳定、最常用的数值算法。

设定:假设我们有 nn 个样本,每个样本有 pp 个特征,构成数据矩阵 XRn×pX \in \mathbb{R}^{n \times p}。通常我们假设 XX 已经中心化,即每个特征的均值为0。

PCA的目标:寻找一个投影矩阵 WRp×kW \in \mathbb{R}^{p \times k}k<pk < p),其列向量是单位正交的(即 WTW=IkW^T W = I_k),使得投影后的数据 T=XWRn×kT = X W \in \mathbb{R}^{n \times k} 的方差尽可能大。TT 的列称为主成分得分

SVD的桥梁作用:对中心化后的数据矩阵 XX 进行“精简”SVD(即不计算完整的 UUVV): X=UΣVT,其中 URn×p,ΣRp×p,VRp×pX = U \Sigma V^T, \quad \text{其中 } U \in \mathbb{R}^{n \times p}, \Sigma \in \mathbb{R}^{p \times p}, V \in \mathbb{R}^{p \times p} (这里假设 npn \ge p,且 Σ\Sigma 是方阵;若 n<pn < p,形式类似,核心结论不变)。

  1. 协方差矩阵:样本协方差矩阵为 1n1XTX\frac{1}{n-1} X^T X。根据SVD,有: XTX=VΣTUTUΣVT=VΣ2VTX^T X = V \Sigma^T U^T U \Sigma V^T = V \Sigma^2 V^T 这正是 XTXX^T X 的特征值分解。VV 的列向量就是 XTXX^T X 的特征向量,即我们要求的主成分方向。因此,W=VW = V(取前 kk 列)。

  2. 主成分得分:将 W=VW = V 代入 T=XWT = X W,得到: T=XV=UΣVTV=UΣT = X V = U \Sigma V^T V = U \Sigma 因此,主成分得分矩阵 TT 就是左奇异向量矩阵 UU 与奇异值矩阵 Σ\Sigma 的乘积

  3. 方差:第 ii 个主成分的方差为 1n1σi2\frac{1}{n-1} \sigma_i^2。奇异值 σi\sigma_i 的降序排列保证了第一主成分方差最大,第二主成分次之,依此类推。

结论:对数据矩阵 XX 进行SVD,可以直接得到PCA的所有结果:

  • 右奇异矩阵 VV 的列就是主成分方向。
  • UΣU \Sigma 的列就是主成分得分。
  • σi2/(n1)\sigma_i^2 / (n-1) 就是各主成分的方差。

因此,在实际计算中,我们通常直接对数据矩阵调用SVD算法来执行PCA,这比先计算协方差矩阵再求特征值分解更数值稳定、更高效。

📝 动手练一练

  1. 手动验证SVD:给定矩阵 A=[120120]A = \begin{bmatrix} 1 & 2 \ 0 & 1 \ 2 & 0 \end{bmatrix}。 a) 计算 ATAA^TAAATAA^T。 b) 分别计算这两个矩阵的特征值和单位特征向量。 c) 根据特征值平方根得到奇异值,并根据特征向量构造 VVUU,验证 A=UΣVTA = U \Sigma V^T 是否成立(允许符号差异)。

  2. 低秩近似与误差:使用上一题中的矩阵 AA,计算其秩为1的近似矩阵 A1A_1。计算原始矩阵 AA 与近似矩阵 A1A_1之间的Frobenius范数误差 AA1F||A - A_1||_F。然后,尝试解释为什么这个误差等于 σ2\sigma_2(假设有2个奇异值 σ1,σ2\sigma_1, \sigma_2)?

参考答案:

  1. a) ATA=[5225]A^TA = \begin{bmatrix} 5 & 2 \ 2 & 5 \end{bmatrix}, AAT=[522210204]AA^T = \begin{bmatrix} 5 & 2 & 2 \ 2 & 1 & 0 \ 2 & 0 & 4 \end{bmatrix}。 b) ATAA^TA 的特征值:λ1=7,λ2=3\lambda_1=7, \lambda_2=3。对应单位特征向量可分别为 v1=12[1,1]T\mathbf{v}_1 = \frac{1}{\sqrt{2}}[1, 1]^T, v2=12[1,1]T\mathbf{v}_2 = \frac{1}{\sqrt{2}}[1, -1]^T。故 V=12[1111]V = \frac{1}{\sqrt{2}}\begin{bmatrix} 1 & 1 \ 1 & -1 \end{bmatrix},奇异值 σ1=7,σ2=3\sigma_1=\sqrt{7}, \sigma_2=\sqrt{3}。 c) 根据 Avi=σiuiA \mathbf{v}_i = \sigma_i \mathbf{u}_i 可计算 u1,u2\mathbf{u}_1, \mathbf{u}_2,并通过正交化补全 UU 的第三列。最终可验证分解成立。
  2. 秩1近似 A1=σ1u1v1TA_1 = \sigma_1 \mathbf{u}_1 \mathbf{v}_1^T。误差 AA1F2=i=2pσi2||A - A_1||F^2 = \sum{i=2}^{p} \sigma_i^2。对于本题 p=2p=2,所以 AA1F=σ2=3||A - A_1||_F = \sigma_2 = \sqrt{3}。这是因为SVD的低秩近似在Frobenius范数意义下是最优的,舍弃的分量对应的奇异值平方和就是近似误差的平方。

本章小结

本节我们系统学习了奇异值分解(SVD)这一线性代数中的核心工具。

  • 核心定义:任何矩阵 Am×nA_{m \times n} 都可分解为 A=UΣVTA = U \Sigma V^T,其中 U,VU, V 为正交矩阵,Σ\Sigma 为广义对角矩阵,其正对角线元素(奇异值)降序排列。
  • 数学本质:奇异值是 ATAA^TAAATAA^T 特征值的平方根;左右奇异向量分别是这两个矩阵的特征向量。SVD将矩阵分解为一系列秩1矩阵的加权和。
  • 关键应用:利用奇异值衰减快的特性进行低秩近似,是数据压缩和噪声抑制的数学基础。SVD也是实现主成分分析(PCA)的稳定算法。

行动清单

  1. 动手验证:运行讲义中的图像SVD示例代码,调整截断秩 rr,直观感受压缩比与图像质量的关系。
  2. 概念串联:画一张关系图,将矩阵 AAATAA^TA/AATAA^T、特征值/特征向量、奇异值/奇异向量、低秩近似、PCA主成分/得分这些概念连接起来,厘清其内在逻辑。
  3. 拓展思考:除了图像,SVD还广泛应用于推荐系统(协同过滤)、自然语言处理(潜在语义分析LSA)等领域。尝试选择一个你感兴趣的领域,搜索“SVD在[某领域]的应用”,了解其如何解决实际问题。

— 小象教研组

配套学习资源与课件
  • 第10章讲义(含板书):线性代数(PDF · 15.5MB)
    下载
🎁 免费学习资源

领取《小象 11GB VIP 课件资料包与大厂真题手册》

包含全套实战 Jupyter 源码、清洗后数据集、大厂高频面试真题与专属学员答疑交流群。

  • 完整 Python / 数据分析 Jupyter 实战源码
  • 大厂真实业务数据集与练习题
  • 微信扫码添加课程顾问,免费获取网盘下载链接
微信二维码:扫码添加课程顾问微信扫码添加顾问