特征值与特征向量
特征值和特征向量揭示了矩阵最本质的性质——它在哪些方向上只做拉伸而不改变方向(Av = λv)。在 AI 中:PCA 降维对协方差矩阵做特征值分解,取最大特征值对应的主成分,广泛用于图像压缩、人脸识别(Eigenface)、高维数据可视化(t-SNE 预处理常先 PCA 到 50 维);谱聚类对图拉普拉斯矩阵求最小特征向量实现非凸聚类;Google PageRank 求转移矩阵的主特征向量来排序网页;协方差矩阵的特征值分布判断特征共线性——条件数(最大/最小特征值之比)过高意味着梯度不稳定;奇异值分解(SVD)与特征值分解紧密关联,用于推荐系统矩阵分解、NLP 词向量训练(GloVe)。本节从 2×2 特征多项式出发,到完整 PCA 流程,每一步用具体数字说明。
一、定义:什么是特征值与特征向量
对于方阵 A(n×n),如果存在非零向量 v 和标量 λ,使得:
Av = λv
则 λ 是 A 的特征值(eigenvalue),v 是对应的特征向量(eigenvector)。
直觉理解:
矩阵 A 本质上是一个线性变换。大多数向量经过 A 变换后会改变方向。
但特征向量很特殊——A 作用在它身上,方向不变,只是被拉伸了 λ 倍。
λ > 0:同方向拉伸(λ > 1 放大,0 < λ < 1 缩小)
λ < 0:反方向拉伸(翻转)
λ = 0:被压缩成零向量(该方向的信息全部丢失)
λ = 1:不变(恒等变换方向)
Av = λv
则 λ 是 A 的特征值(eigenvalue),v 是对应的特征向量(eigenvector)。
直觉理解:
矩阵 A 本质上是一个线性变换。大多数向量经过 A 变换后会改变方向。
但特征向量很特殊——A 作用在它身上,方向不变,只是被拉伸了 λ 倍。
λ > 0:同方向拉伸(λ > 1 放大,0 < λ < 1 缩小)
λ < 0:反方向拉伸(翻转)
λ = 0:被压缩成零向量(该方向的信息全部丢失)
λ = 1:不变(恒等变换方向)
二、手动计算特征值与特征向量(完整推导)
方法:求解 det(A - λI) = 0(特征方程),然后代入求特征向量。
A = [[4, 1],
[2, 3]]
第1步:构造 A - λI
A - λI = [[4-λ, 1],
[2, 3-λ]]
第2步:计算行列式 det(A - λI) = 0
det = (4-λ)(3-λ) - 1×2
= 12 - 4λ - 3λ + λ² - 2
= λ² - 7λ + 10 = 0
第3步:解二次方程
λ = (7 ± √(49-40)) / 2 = (7 ± 3) / 2
λ₁ = 5,λ₂ = 2
第4步:求特征向量(代入 (A-λI)v = 0)
对于 λ₁ = 5:
(A - 5I) = [[4-5, 1], [2, 3-5]] = [[-1, 1], [2, -2]]
方程组:-v₁ + v₂ = 0 → v₁ = v₂
取 v₁ = (1, 1)(或任意倍数,如 (2,2)、(-1,-1) 都是)
验证:Av₁ = [[4,1],[2,3]] × [1,1]ᵀ
= [4×1+1×1, 2×1+3×1]ᵀ = [5, 5]ᵀ = 5 × [1,1]ᵀ ✓
对于 λ₂ = 2:
(A - 2I) = [[2, 1], [2, 1]]
方程:2v₁ + v₂ = 0 → v₂ = -2v₁
取 v₂ = (1, -2)
验证:Av₂ = [[4,1],[2,3]] × [1,-2]ᵀ
= [4×1+1×(-2), 2×1+3×(-2)]ᵀ = [2, -4]ᵀ = 2 × [1,-2]ᵀ ✓
[2, 3]]
第1步:构造 A - λI
A - λI = [[4-λ, 1],
[2, 3-λ]]
第2步:计算行列式 det(A - λI) = 0
det = (4-λ)(3-λ) - 1×2
= 12 - 4λ - 3λ + λ² - 2
= λ² - 7λ + 10 = 0
第3步:解二次方程
λ = (7 ± √(49-40)) / 2 = (7 ± 3) / 2
λ₁ = 5,λ₂ = 2
第4步:求特征向量(代入 (A-λI)v = 0)
对于 λ₁ = 5:
(A - 5I) = [[4-5, 1], [2, 3-5]] = [[-1, 1], [2, -2]]
方程组:-v₁ + v₂ = 0 → v₁ = v₂
取 v₁ = (1, 1)(或任意倍数,如 (2,2)、(-1,-1) 都是)
验证:Av₁ = [[4,1],[2,3]] × [1,1]ᵀ
= [4×1+1×1, 2×1+3×1]ᵀ = [5, 5]ᵀ = 5 × [1,1]ᵀ ✓
对于 λ₂ = 2:
(A - 2I) = [[2, 1], [2, 1]]
方程:2v₁ + v₂ = 0 → v₂ = -2v₁
取 v₂ = (1, -2)
验证:Av₂ = [[4,1],[2,3]] × [1,-2]ᵀ
= [4×1+1×(-2), 2×1+3×(-2)]ᵀ = [2, -4]ᵀ = 2 × [1,-2]ᵀ ✓
三、特征值分解(对角化)
如果 n×n 矩阵 A 有 n 个线性无关的特征向量,则:
A = PDP⁻¹
P = [v₁, v₂, ..., vₙ](特征向量组成的矩阵)
D = diag(λ₁, λ₂, ..., λₙ)(特征值组成的对角矩阵)
用途:Aⁿ = PDⁿP⁻¹,对角矩阵的 n 次幂只需每个元素取 n 次幂。
这把矩阵幂运算从 O(n³×k) 降到 O(n³)(k 是幂次数)。
A = PDP⁻¹
P = [v₁, v₂, ..., vₙ](特征向量组成的矩阵)
D = diag(λ₁, λ₂, ..., λₙ)(特征值组成的对角矩阵)
用途:Aⁿ = PDⁿP⁻¹,对角矩阵的 n 次幂只需每个元素取 n 次幂。
这把矩阵幂运算从 O(n³×k) 降到 O(n³)(k 是幂次数)。
接上例的数值验证:
P = [[1, 1], [1, -2]]
D = [[5, 0], [0, 2]]
P⁻¹:det(P) = 1×(-2) - 1×1 = -3
P⁻¹ = (1/-3) × [[-2, -1], [-1, 1]] = [[2/3, 1/3], [1/3, -1/3]]
验证 PDP⁻¹ = A:
PD = [[1×5, 1×2], [1×5, -2×2]] = [[5, 2], [5, -4]]
PDP⁻¹ = [[5, 2], [5, -4]] × [[2/3, 1/3], [1/3, -1/3]]
(1,1) = 5×2/3 + 2×1/3 = 10/3 + 2/3 = 12/3 = 4 ✓
(1,2) = 5×1/3 + 2×(-1/3) = 5/3 - 2/3 = 3/3 = 1 ✓
(2,1) = 5×2/3 + (-4)×1/3 = 10/3 - 4/3 = 6/3 = 2 ✓
(2,2) = 5×1/3 + (-4)×(-1/3) = 5/3 + 4/3 = 9/3 = 3 ✓
PDP⁻¹ = [[4,1],[2,3]] = A ✓
P = [[1, 1], [1, -2]]
D = [[5, 0], [0, 2]]
P⁻¹:det(P) = 1×(-2) - 1×1 = -3
P⁻¹ = (1/-3) × [[-2, -1], [-1, 1]] = [[2/3, 1/3], [1/3, -1/3]]
验证 PDP⁻¹ = A:
PD = [[1×5, 1×2], [1×5, -2×2]] = [[5, 2], [5, -4]]
PDP⁻¹ = [[5, 2], [5, -4]] × [[2/3, 1/3], [1/3, -1/3]]
(1,1) = 5×2/3 + 2×1/3 = 10/3 + 2/3 = 12/3 = 4 ✓
(1,2) = 5×1/3 + 2×(-1/3) = 5/3 - 2/3 = 3/3 = 1 ✓
(2,1) = 5×2/3 + (-4)×1/3 = 10/3 - 4/3 = 6/3 = 2 ✓
(2,2) = 5×1/3 + (-4)×(-1/3) = 5/3 + 4/3 = 9/3 = 3 ✓
PDP⁻¹ = [[4,1],[2,3]] = A ✓
四、对称矩阵的特殊性质
对称矩阵(A = Aᵀ)有非常好的性质:
① 所有特征值都是实数(不会出现复数)
② 不同特征值对应的特征向量相互正交(垂直)
③ 一定可以对角化,且可以用正交矩阵对角化:A = QΛQᵀ
(Q 是正交矩阵,QᵀQ = I,即 Q⁻¹ = Qᵀ)
协方差矩阵就是对称矩阵 → PCA 依赖的就是这些性质。
① 所有特征值都是实数(不会出现复数)
② 不同特征值对应的特征向量相互正交(垂直)
③ 一定可以对角化,且可以用正交矩阵对角化:A = QΛQᵀ
(Q 是正交矩阵,QᵀQ = I,即 Q⁻¹ = Qᵀ)
协方差矩阵就是对称矩阵 → PCA 依赖的就是这些性质。
对称矩阵示例:
S = [[5, 2],
[2, 2]](注意 S = Sᵀ)
特征方程:(5-λ)(2-λ) - 4 = 0
λ² - 7λ + 6 = 0
λ₁ = 6,λ₂ = 1
对于 λ₁ = 6:(S-6I)v = 0 → [[-1,2],[2,-4]]v = 0 → v₁ = 2v₂ → v₁ = (2,1)
对于 λ₂ = 1:(S-I)v = 0 → [[4,2],[2,1]]v = 0 → v₁ = -v₂/2 → v₂ = (1,-2)
验证正交性:v₁ · v₂ = 2×1 + 1×(-2) = 0 ✓ 互相垂直!
归一化(使长度=1):
|v₁| = √(4+1) = √5,归一化 q₁ = (2/√5, 1/√5) = (0.894, 0.447)
|v₂| = √(1+4) = √5,归一化 q₂ = (1/√5, -2/√5) = (0.447, -0.894)
S = [[5, 2],
[2, 2]](注意 S = Sᵀ)
特征方程:(5-λ)(2-λ) - 4 = 0
λ² - 7λ + 6 = 0
λ₁ = 6,λ₂ = 1
对于 λ₁ = 6:(S-6I)v = 0 → [[-1,2],[2,-4]]v = 0 → v₁ = 2v₂ → v₁ = (2,1)
对于 λ₂ = 1:(S-I)v = 0 → [[4,2],[2,1]]v = 0 → v₁ = -v₂/2 → v₂ = (1,-2)
验证正交性:v₁ · v₂ = 2×1 + 1×(-2) = 0 ✓ 互相垂直!
归一化(使长度=1):
|v₁| = √(4+1) = √5,归一化 q₁ = (2/√5, 1/√5) = (0.894, 0.447)
|v₂| = √(1+4) = √5,归一化 q₂ = (1/√5, -2/√5) = (0.447, -0.894)
五、AI 核心应用——PCA 主成分分析
PCA 是最经典的降维方法,核心就是对协方差矩阵做特征值分解。
完整 PCA 示例(学生考试成绩数据):
5 个学生的数学和物理成绩:
第1步:中心化(减均值)
x̄₁ = (90+70+80+60+50)/5 = 350/5 = 70
x̄₂ = (85+60+75+55+45)/5 = 320/5 = 64
第2步:计算协方差矩阵 Σ
Σ₁₁ = Var(x₁) = (20²+0²+10²+(-10)²+(-20)²)/(5-1) = (400+0+100+100+400)/4 = 1000/4 = 250
Σ₂₂ = Var(x₂) = (21²+(-4)²+11²+(-9)²+(-19)²)/4 = (441+16+121+81+361)/4 = 1020/4 = 255
Σ₁₂ = Cov(x₁,x₂) = (20×21+0×(-4)+10×11+(-10)×(-9)+(-20)×(-19))/4
= (420+0+110+90+380)/4 = 1000/4 = 250
Σ = [[250, 250],
[250, 255]]
第3步:对 Σ 做特征值分解
特征方程:(250-λ)(255-λ) - 250² = 0
λ² - 505λ + (250×255-62500) = 0
λ² - 505λ + (63750-62500) = 0
λ² - 505λ + 1250 = 0
λ = (505 ± √(255025-5000))/2 = (505 ± √250025)/2 = (505 ± 500.02)/2
λ₁ ≈ 502.5,λ₂ ≈ 2.5
第4步:解释方差比
第1主成分解释的方差比 = 502.5/(502.5+2.5) = 502.5/505 = 99.5%
第2主成分解释的方差比 = 2.5/505 = 0.5%
结论:第 1 个主成分就包含了 99.5% 的信息!可以把 2 维数据降为 1 维,几乎不损失信息。
这是因为数学和物理成绩高度正相关(Cov=250 接近 Var),数据基本分布在一条直线附近。
5 个学生的数学和物理成绩:
| 学生 | 数学(x₁) | 物理(x₂) |
|---|---|---|
| A | 90 | 85 |
| B | 70 | 60 |
| C | 80 | 75 |
| D | 60 | 55 |
| E | 50 | 45 |
x̄₁ = (90+70+80+60+50)/5 = 350/5 = 70
x̄₂ = (85+60+75+55+45)/5 = 320/5 = 64
| 学生 | x₁-x̄₁ | x₂-x̄₂ |
|---|---|---|
| A | 20 | 21 |
| B | 0 | -4 |
| C | 10 | 11 |
| D | -10 | -9 |
| E | -20 | -19 |
Σ₁₁ = Var(x₁) = (20²+0²+10²+(-10)²+(-20)²)/(5-1) = (400+0+100+100+400)/4 = 1000/4 = 250
Σ₂₂ = Var(x₂) = (21²+(-4)²+11²+(-9)²+(-19)²)/4 = (441+16+121+81+361)/4 = 1020/4 = 255
Σ₁₂ = Cov(x₁,x₂) = (20×21+0×(-4)+10×11+(-10)×(-9)+(-20)×(-19))/4
= (420+0+110+90+380)/4 = 1000/4 = 250
Σ = [[250, 250],
[250, 255]]
第3步:对 Σ 做特征值分解
特征方程:(250-λ)(255-λ) - 250² = 0
λ² - 505λ + (250×255-62500) = 0
λ² - 505λ + (63750-62500) = 0
λ² - 505λ + 1250 = 0
λ = (505 ± √(255025-5000))/2 = (505 ± √250025)/2 = (505 ± 500.02)/2
λ₁ ≈ 502.5,λ₂ ≈ 2.5
第4步:解释方差比
第1主成分解释的方差比 = 502.5/(502.5+2.5) = 502.5/505 = 99.5%
第2主成分解释的方差比 = 2.5/505 = 0.5%
结论:第 1 个主成分就包含了 99.5% 的信息!可以把 2 维数据降为 1 维,几乎不损失信息。
这是因为数学和物理成绩高度正相关(Cov=250 接近 Var),数据基本分布在一条直线附近。
PCA 在图像压缩中的应用(MNIST):
MNIST 每张图片 28×28 = 784 个像素(784 维向量)。
对 60000 张训练图片做 PCA:
从 784 维降到 150 维,准确率几乎不变(97.1%→97.0%),但计算快 5 倍!
这说明手写数字图片中大量像素是冗余的(背景全是 0,笔画模式高度重复)。
MNIST 每张图片 28×28 = 784 个像素(784 维向量)。
对 60000 张训练图片做 PCA:
| 保留主成分数 | 解释方差比 | 分类准确率(KNN) | 速度提升 |
|---|---|---|---|
| 784(原始) | 100% | 97.1% | 1x |
| 150 | 95% | 97.0% | 5x |
| 50 | 85% | 96.3% | 15x |
| 20 | 70% | 94.5% | 40x |
| 10 | 55% | 90.1% | 78x |
这说明手写数字图片中大量像素是冗余的(背景全是 0,笔画模式高度重复)。
六、其他 AI 应用
谱聚类(SpectralClustering):
对图(Graph)的拉普拉斯矩阵做特征值分解来发现聚类结构。
有 6 个数据点,构建相似度图后得到拉普拉斯矩阵 L。
计算 L 的最小几个特征值:λ₁=0, λ₂=0.1, λ₃=0.8, λ₄=2.3, ...
λ₂ 和 λ₃ 之间有明显的跳跃(0.1 → 0.8),说明数据有 2 个聚类。
取前 2 个特征向量对应的坐标,然后用 K-Means 聚类。
对图(Graph)的拉普拉斯矩阵做特征值分解来发现聚类结构。
有 6 个数据点,构建相似度图后得到拉普拉斯矩阵 L。
计算 L 的最小几个特征值:λ₁=0, λ₂=0.1, λ₃=0.8, λ₄=2.3, ...
λ₂ 和 λ₃ 之间有明显的跳跃(0.1 → 0.8),说明数据有 2 个聚类。
取前 2 个特征向量对应的坐标,然后用 K-Means 聚类。
Google PageRank 算法(简化版):
有 3 个网页 A、B、C 互相链接:
A→B, A→C, B→A, C→A, C→B
转移矩阵 M:
M = [[0, 1, 1/2],
[1/2, 0, 1/2],
[1/2, 0, 0]]
M 的主特征值 λ₁=1(Markov 矩阵一定有),对应的特征向量就是 PageRank 分数。
求解后:r = [0.4, 0.4, 0.2](A 和 B 并列最重要,C 最不重要)
因为 A 被 B 和 C 两个页面指向,B 被 A 和 C 指向,C 只被 A 指向。
有 3 个网页 A、B、C 互相链接:
A→B, A→C, B→A, C→A, C→B
转移矩阵 M:
M = [[0, 1, 1/2],
[1/2, 0, 1/2],
[1/2, 0, 0]]
M 的主特征值 λ₁=1(Markov 矩阵一定有),对应的特征向量就是 PageRank 分数。
求解后:r = [0.4, 0.4, 0.2](A 和 B 并列最重要,C 最不重要)
因为 A 被 B 和 C 两个页面指向,B 被 A 和 C 指向,C 只被 A 指向。
七、代码验证(C# / Rust)
C#(.NET 10)
// dotnet run 即可执行
// 2×2 特征值分解: 特征多项式 λ²-trace·λ+det=0
void Eig2(double a, double b, double c, double d,
out double l1, out double l2, out double[] v1, out double[] v2)
{
double tr = a + d, det = a * d - b * c;
double disc = Math.Sqrt(tr * tr - 4 * det);
l1 = (tr + disc) / 2; l2 = (tr - disc) / 2;
// 特征向量: (A-λI)v=0, v ∝ [b, λ-a]
if (Math.Abs(b) > 1e-12) {
v1 = new[] { b, l1 - a }; v2 = new[] { b, l2 - a };
} else { v1 = new[] { 1.0, 0.0 }; v2 = new[] { 0.0, 1.0 }; }
double n1 = Math.Sqrt(v1[0]*v1[0]+v1[1]*v1[1]);
double n2 = Math.Sqrt(v2[0]*v2[0]+v2[1]*v2[1]);
v1[0] /= n1; v1[1] /= n1; v2[0] /= n2; v2[1] /= n2;
}
// ===== 基本特征值分解 =====
Eig2(4, 1, 2, 3, out double lam1, out double lam2, out double[] ev1, out double[] ev2);
Console.WriteLine($"特征值: [{lam1}, {lam2}]");
Console.WriteLine($"特征向量: [{ev1[0]:F4},{ev1[1]:F4}], [{ev2[0]:F4},{ev2[1]:F4}]");
Console.WriteLine($"Av1 = [{4*ev1[0]+1*ev1[1]:F4}, {2*ev1[0]+3*ev1[1]:F4}]");
Console.WriteLine($"λ1·v1= [{lam1*ev1[0]:F4}, {lam1*ev1[1]:F4}]");
Console.WriteLine($"Av2 = [{4*ev2[0]+1*ev2[1]:F4}, {2*ev2[0]+3*ev2[1]:F4}]");
Console.WriteLine($"λ2·v2= [{lam2*ev2[0]:F4}, {lam2*ev2[1]:F4}]");
// ===== 完整 PCA 流程 =====
double[,] data = {{90,85},{70,60},{80,75},{60,55},{50,45}};
int n = 5;
double[] mu = new double[2];
for (int j = 0; j < 2; j++) { for (int i = 0; i < n; i++) mu[j] += data[i,j]; mu[j] /= n; }
double[,] cen = new double[5,2];
for (int i = 0; i < n; i++) for (int j = 0; j < 2; j++) cen[i,j] = data[i,j] - mu[j];
// 样本协方差 (ddof=1)
double[,] cov = new double[2,2];
for (int j1 = 0; j1 < 2; j1++) for (int j2 = 0; j2 < 2; j2++) {
for (int i = 0; i < n; i++) cov[j1,j2] += cen[i,j1]*cen[i,j2];
cov[j1,j2] /= n - 1;
}
Console.WriteLine($"\n协方差矩阵:\n [{cov[0,0]:F2}, {cov[0,1]:F2}]\n [{cov[1,0]:F2}, {cov[1,1]:F2}]");
Eig2(cov[0,0],cov[0,1],cov[1,0],cov[1,1], out double e1, out double e2, out double[] pc1, out _);
Console.WriteLine($"特征值: [{e1:F2}, {e2:F2}]");
Console.WriteLine($"方差解释比: [{e1/(e1+e2):F4}, {e2/(e1+e2):F4}]");
Console.Write("第1主成分得分: ");
for (int i = 0; i < n; i++) Console.Write($"{cen[i,0]*pc1[0]+cen[i,1]*pc1[1]:F2} ");
Console.WriteLine();
// ===== 对称矩阵验证正交性 =====
Eig2(5, 2, 2, 2, out _, out _, out double[] u1, out double[] u2);
Console.WriteLine($"\n对称矩阵 [[5,2],[2,2]] 特征向量内积: {u1[0]*u2[0]+u1[1]*u2[1]:F10}");
Rust
// 2×2 特征值分解
fn eig2(a: f64, b: f64, c: f64, d: f64) -> (f64, f64, [f64;2], [f64;2]) {
let (tr, det) = (a + d, a.mul_add(d, -(b * c)));
let disc = tr.mul_add(tr, -4.0*det).sqrt();
let (l1, l2) = ((tr+disc)/2.0, (tr-disc)/2.0);
let (mut v1, mut v2) = if b.abs() > 1e-12 {
([b, l1-a], [b, l2-a])
} else { ([1.0, 0.0], [0.0, 1.0]) };
let n1 = v1[0].mul_add(v1[0], v1[1]*v1[1]).sqrt();
let n2 = v2[0].mul_add(v2[0], v2[1]*v2[1]).sqrt();
v1[0]/=n1; v1[1]/=n1; v2[0]/=n2; v2[1]/=n2;
(l1, l2, v1, v2)
}
fn main() {
// ===== 基本特征值分解 =====
let (l1, l2, v1, v2) = eig2(4.0, 1.0, 2.0, 3.0);
println!("特征值: [{l1}, {l2}]");
println!("特征向量: [{:.4},{:.4}], [{:.4},{:.4}]", v1[0],v1[1], v2[0],v2[1]);
println!("Av1 = [{:.4}, {:.4}]", 4.0_f64.mul_add(v1[0], v1[1]), 2.0_f64.mul_add(v1[0], 3.0*v1[1]));
println!("λ1·v1= [{:.4}, {:.4}]", l1*v1[0], l1*v1[1]);
println!("Av2 = [{:.4}, {:.4}]", 4.0_f64.mul_add(v2[0], v2[1]), 2.0_f64.mul_add(v2[0], 3.0*v2[1]));
println!("λ2·v2= [{:.4}, {:.4}]", l2*v2[0], l2*v2[1]);
// ===== PCA =====
let data = [[90.0,85.0],[70.0,60.0],[80.0,75.0],[60.0,55.0],[50.0,45.0_f64]];
let n = 5;
let mut mu = [0.0_f64; 2];
for j in 0..2 { for i in 0..n { mu[j] += data[i][j]; } mu[j] /= n as f64; }
let mut cen = [[0.0_f64;2];5];
for i in 0..n { for j in 0..2 { cen[i][j] = data[i][j]-mu[j]; } }
let mut cov = [[0.0_f64;2];2];
for j1 in 0..2 { for j2 in 0..2 {
for i in 0..n { cov[j1][j2] += cen[i][j1]*cen[i][j2]; }
cov[j1][j2] /= (n-1) as f64;
}}
println!("\n协方差矩阵:\n [{:.2}, {:.2}]\n [{:.2}, {:.2}]",
cov[0][0],cov[0][1],cov[1][0],cov[1][1]);
let (e1, e2, pc1, _) = eig2(cov[0][0],cov[0][1],cov[1][0],cov[1][1]);
println!("特征值: [{e1:.2}, {e2:.2}]");
println!("方差解释比: [{:.4}, {:.4}]", e1/(e1+e2), e2/(e1+e2));
print!("第1主成分得分: ");
for i in 0..n { print!("{:.2} ", cen[i][0].mul_add(pc1[0], cen[i][1]*pc1[1])); }
println!();
// ===== 对称矩阵正交性 =====
let (_, _, u1, u2) = eig2(5.0, 2.0, 2.0, 2.0);
println!("\n对称矩阵 [[5,2],[2,2]] 特征向量内积: {:.10}",
u1[0].mul_add(u2[0], u1[1]*u2[1]));
}