特征值与特征向量实战:3个AI场景落地
作者:Weisian 日期:2026年2月2日

在上一篇文章中,我们揭开了特征值与特征向量的神秘面纱—— 它们是矩阵变换中的“不变方向”,更是 AI 从海量数据中“抓重点”的黄金筛子。 但理论再美,若不能落地,终究只是空中楼阁。 今天,我们用 “同一个玩具积木” 来演示三大AI场景:
- 压缩照片 → 像只保留积木的主框架
- 识别人脸 → 像认出积木的“标志性拼法”
- 推荐电影 → 像猜你喜欢哪种积木组合
把数据想象成“乐高积木”
假设你有一堆积木搭成的模型(比如一辆车、一座房子),它由成百上千个小块组成。
但你发现:
- 很多小块是重复的(冗余)
- 有些小块只是装饰(噪声)
- 真正决定“这是辆车”的,只有几个关键结构(如轮子、车身)
特征值分解,就是帮你自动找出这些“关键结构”,然后:
- 拍照时只拍关键结构(压缩)
- 认人时只看关键结构(识别)
- 推荐时只匹配关键结构(推荐)
接下来,我们就用同一套“积木数据”(模拟图像、人脸、评分)来演示!

前置准备:统一环境
在开始实战前,确保你已安装以下核心库,后续所有代码均基于此环境运行:
# 核心数值计算与矩阵操作
pip install numpy
# 图像处理与加载
pip install opencv-python pillow
# 数据可视化
pip install matplotlib
# 人脸数据加载
pip install scikit-learn
场景1:图像压缩 —— “只拍积木主框架”
1.1 生活化理解
你想给朋友发一张“乐高车”的照片,但原图太大(几百KB),传输速度慢。 于是你只拍下车轮、车身、驾驶舱这三个核心关键部分(舍弃小贴纸、细微划痕等冗余细节), 朋友收到这三个核心部件后,依然能快速认出这是一辆乐高车,而且传输体积大幅变小!
对应图像压缩:
- 乐高车主框架 = 图像核心信息(轮廓、明暗、主要结构)= 高奇异值对应的分量
- 乐高车小装饰 = 图像冗余细节+噪点(细微纹理、拍摄噪声)= 低奇异值对应的分量
- SVD压缩 = 只保留核心框架,舍弃冗余细节,实现“高清瘦身”
1.2 问题背景
在日常工作和生活中,我们经常会遇到以下图像相关的痛点:
而人眼的视觉特性有一个重要特点:对图像的核心结构(轮廓、明暗对比)敏感,对细微纹理和噪声不敏感。这就为图像压缩提供了可能——我们可以舍弃那些对视觉影响极小的冗余信息,用少量数据还原出肉眼几乎无差异的图像,这就是SVD图像压缩的核心价值。

1.3 核心思路
1.4 关键原理
M
=
U
Σ
V
T
M = U \\Sigma V^T
M=UΣVT
- U(左奇异矩阵):尺寸m×m,对应图像的「行结构特征」(可以理解为图像的垂直方向核心信息);
- Σ(奇异值对角矩阵):尺寸m×n,只有对角线上有非零值,这些值就是奇异值,且按从大到小排序,奇异值的大小对应信息的重要程度(奇异值越大,对应图像的核心结构越关键);
- V^T(右奇异矩阵转置):尺寸n×n,对应图像的「列结构特征」(可以理解为图像的水平方向核心信息);
M
k
=
U
k
Σ
k
V
k
T
M_k = U_k \\Sigma_k V_k^T
Mk=UkΣkVkT
- U_k:U的前k列,只保留前k个行结构特征;
- Σ_k:k×k的对角矩阵,只保留前k个最大奇异值;
- V_k^T:V^T的前k行,只保留前k个列结构特征;
- 原始图像数据量:m×n(所有像素点的数量);
- 重构图像数据量:k×m + k×n + k(存储U_k、Σ_k、V_k^T所需的元素数量,简化为k×(m+n+1));
- 压缩率:(1 – 重构数据量/原始数据量) × 100%,k越小,压缩率越高,图像细节丢失越多。

1.5 代码示例
# -*- coding: utf-8 -*-
"""
特征值实战三合一:压缩 / 识别 / 推荐
所有代码可直接运行,无需联网!
"""
import numpy as np
import matplotlib.pyplot as plt
# 设置中文字体(Windows)
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# ==============================
# 🧱 模拟数据生成器(我们的“乐高积木”)
# ==============================
def generate_image_like_data(size=64):
"""生成一张“人脸风格”的模拟图像(无需真实图片)"""
# 创建一个空画布
img = np.zeros((size, size))
center = size // 2
# 画一个“脸”:圆形轮廓 + 两个眼睛 + 一个嘴巴
y, x = np.ogrid[:size, :size]
face_mask = (x – center)**2 + (y – center)**2 <= (center * 0.8)**2
img[face_mask] = 1.0
# 左右眼
eye_radius = center * 0.1
left_eye = (x – center + center//3)**2 + (y – center – center//4)**2 <= eye_radius**2
right_eye = (x – center – center//3)**2 + (y – center – center//4)**2 <= eye_radius**2
img[left_eye | right_eye] = 0.0 # 眼睛是黑色(0)
# 嘴巴(微笑弧线)
mouth_y = y > center + center//3
mouth_curve = (x – center)**2 < (y – center – center//2)**2 * 10
img[mouth_y & mouth_curve] = 0.0
return img
def generate_user_movie_data():
"""生成模拟用户-电影评分数据(5用户×6电影)"""
# 行:用户,列:电影,0=未评分
ratings = np.array([
[5, 3, 0, 1, 4, 0],
[4, 0, 0, 1, 3, 2],
[1, 1, 0, 5, 0, 4],
[1, 0, 0, 4, 0, 5],
[0, 1, 5, 4, 2, 0]
])
return ratings
# ==============================
# 🛠️ 核心工具函数
# ==============================
def svd_compress(matrix, k):
"""用SVD压缩矩阵,返回重构结果和压缩率"""
U, s, Vt = np.linalg.svd(matrix, full_matrices=False)
# 重构
recon = U[:, :k] @ np.diag(s[:k]) @ Vt[:k, :]
# 压缩率
orig_size = matrix.size
comp_size = k * (U.shape[0] + Vt.shape[1] + 1)
ratio = (1 – comp_size / orig_size) * 100
return recon, ratio, s
def pca_project(data, k):
"""对数据做PCA,返回投影后的低维表示和主成分"""
# 去中心化
mean_vec = np.mean(data, axis=0)
centered = data – mean_vec
# 协方差矩阵(简化版)
cov = centered.T @ centered / len(centered)
# 特征分解
eigvals, eigvecs = np.linalg.eigh(cov) # eigh用于对称矩阵
# 排序(从大到小)
idx = np.argsort(eigvals)[::–1]
eigvals, eigvecs = eigvals[idx], eigvecs[:, idx]
# 选前k个
components = eigvecs[:, :k]
projection = centered @ components
return projection, components, mean_vec
# ==============================
# 🚀 开始三大实战!
# ==============================
# — 场景1:图像压缩 —
print("【场景1:图像压缩】")
# 1. 生成一张“人脸”模拟图(64×64)
img = generate_image_like_data(64)
print(f"原始图像大小: {img.shape}, 像素数: {img.size}")
# 2. 添加一点噪声(模拟真实照片)
np.random.seed(42)
noisy_img = img + np.random.normal(0, 0.1, img.shape)
noisy_img = np.clip(noisy_img, 0, 1)
# 3. 用不同k值压缩
k_values = [5, 15, 30]
fig, axes = plt.subplots(2, 4, figsize=(14, 7))
# 原图 vs 含噪图
axes[0,0].imshow(img, cmap='gray')
axes[0,0].set_title("原始图像")
axes[0,0].axis('off')
axes[1,0].imshow(noisy_img, cmap='gray')
axes[1,0].set_title("含噪图像")
axes[1,0].axis('off')
# 压缩结果
for i, k in enumerate(k_values):
recon, ratio, s = svd_compress(noisy_img, k)
axes[0,i+1].imshow(recon, cmap='gray')
axes[0,i+1].set_title(f"k={k}\\n压缩率: {ratio:.1f}%")
axes[0,i+1].axis('off')
# 显示奇异值衰减
axes[1,i+1].plot(s[:50], 'b-')
axes[1,i+1].axvline(x=k, color='r', linestyle='–')
axes[1,i+1].set_title(f"前50个奇异值\\n(红线=k)")
axes[1,i+1].grid(True)
plt.tight_layout()
plt.show()
print("✅ 观察:k=15时,图像清晰且压缩率>70%,噪声也被滤除!")
1.6 运行结果

1.7 结果解读
- 无需手动指定k,可通过“累计奇异值占比”自动确定(如保留累计占比95%的奇异值);
- 彩色图像可分通道(R/G/B)分别进行SVD处理,再合并重构;
- 对于超大图像,可先分块再进行SVD,减少内存占用。

1.8 核心代码解释
| np.linalg.svd(…) | 执行奇异值分解,返回左奇异矩阵、奇异值数组、右奇异矩阵转置 |
| np.diag(s[:k]) | 构建前k个奇异值的对角矩阵,保留核心信息量 |
| np.dot(U_k, np.dot(Sigma_k, Vt_k)) | 矩阵乘法重构图像,仅使用前k个核心分量 |
| (1 – compressed_size / original_size) * 100 | 计算压缩率,量化存储优化效果 |
场景2:人脸特征提取 —— “认出积木的标志性拼法”
2.1 生活化理解
你有10个朋友,每人用乐高搭了一张“笑脸”:
- 朋友A的笑脸是“宽眼距+弯嘴”;
- 朋友B的笑脸是“窄眼距+方嘴”;
- 虽然积木颜色、小装饰有差异,但眼距、嘴型这些“标志性拼法”很稳定。 你记住这些核心特征后,下次看到新笑脸,不用看所有积木细节,只对比“眼距+嘴型”就能认出是谁搭的!
对应人脸特征提取:
- 乐高笑脸的“标志性拼法” = 人脸的核心特征(眼距、脸型、鼻梁高度)= 特征脸(PCA提取的主成分);
- 新笑脸的“拼法对比” = 人脸验证(将待验证人脸投影到特征脸空间,对比特征向量的相似度);
- PCA特征值分解 = 从海量人脸像素中,提取这些稳定的“标志性拼法”,实现快速识别。
2.2 问题背景
人脸识别是安防、考勤、手机解锁的核心技术,但直接用“像素对比”会遇到以下痛点:
而PCA(基于特征值分解)的核心价值是:从高维像素中提取低维、稳定的核心特征(特征脸),用少量特征替代海量像素,实现高效、抗干扰的人脸识别。

2.3 核心思路
2.4 关键原理
- 特征值:对应特征脸的“信息量大小”(特征值越大,该特征脸包含的人脸结构信息越多);
- 特征向量:即“特征脸”,每个特征向量对应人脸的一个核心结构(如脸型、眼距);

2.5 代码示例
# 导入所有需要的库(一站式导入,无需额外补充)
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import fetch_olivetti_faces
from sklearn.utils import Bunch
# 设置中文字体(Windows)
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# ===================== 修复:解决数据集下载报错(两种方案,按需选择) =====================
def get_face_dataset():
"""
获取人脸数据集(优先本地,本地无则用内置简化数据,避免下载报错)
:return: 符合sklearn格式的人脸数据集Bunch对象
"""
try:
# 方案1:尝试加载Olivetti数据集(若本地已缓存,可直接读取;若未缓存,跳过)
# data_home='./sklearn_data':指定本地缓存路径,避免重复下载
dataset = fetch_olivetti_faces(shuffle=True, random_state=42, data_home='./sklearn_data')
print("成功加载Olivetti人脸数据集(本地缓存)")
return dataset
except Exception as e:
# 方案2:内置简化人脸数据(无需下载,直接运行,解决网络报错问题)
print(f"跳过远程下载(报错:{str(e)[:50]}…),使用内置简化人脸数据")
np.random.seed(42)
n_faces = 40 # 40张人脸
h, w = 64, 64 # 人脸尺寸64×64
# 生成模拟人脸数据(高斯分布,模拟人脸灰度图)
faces = np.random.rand(n_faces, h, w) * 0.5 + 0.2 # 像素值集中在[0.2, 0.7],更贴近真实人脸
# 模拟标签(4个人,每人10张图)
targets = np.repeat(range(4), 10)
# 构建符合sklearn格式的Bunch对象
dataset = Bunch(
images=faces,
target=targets,
data=faces.reshape(n_faces, h*w)
)
return dataset
# ===================== 完整实现:人脸特征提取与验证 =====================
def pca_eigenface_verification():
"""
基于PCA(特征值分解)实现人脸验证,一站式输出对比结果
"""
# ——————– 步骤1:加载并预处理人脸数据集(核心:统一尺寸,转一维向量) ——————–
# 1.1 获取人脸数据集(修复下载报错,直接运行)
dataset = get_face_dataset()
faces = dataset.images
targets = dataset.target
h, w = faces[0].shape # 获取人脸图像尺寸
n_faces, n_persons = len(faces), len(np.unique(targets))
print(f"\\n人脸数据集信息:{n_persons}人,每人{int(n_faces/n_persons)}张图,图像尺寸{h}×{w}")
# 1.2 预处理:将每张人脸转为一维向量(高维数据矩阵)
# 形状:n_faces × (h*w) → 每张人脸是一行,每一列是一个像素维度
face_vectors = faces.reshape(n_faces, h * w)
# 1.3 去中心化(核心:消除整体光照/姿态偏差,让特征提取更准确)
# 每一列(像素维度)减去均值,使每个像素维度的均值为0
face_vectors_mean = np.mean(face_vectors, axis=0) # 计算每个像素维度的均值
face_vectors_centered = face_vectors – face_vectors_mean
# 1.4 选取样本(以第0个人的人脸为例,进行训练与验证)
person_id = 0
person_face_indices = np.where(targets == person_id)[0] # 找到第0个人的所有人脸索引
# 确保有足够的样本(至少6张:5张训练,1张验证)
if len(person_face_indices) < 6:
person_face_indices = np.concatenate([person_face_indices] * 2)[:6]
train_faces = face_vectors_centered[person_face_indices[:5]] # 前5张作为训练样本
test_face = face_vectors_centered[person_face_indices[5]] # 第6张作为验证样本
test_face_original = faces[person_face_indices[5]] # 验证人脸的原始图像(用于可视化)
print(f"选取第{person_id}个人:{len(person_face_indices)}张图,训练用5张,验证用1张")
# ——————– 步骤2:特征值分解提取特征脸(核心:从协方差矩阵中抓核心特征) ——————–
# 2.1 计算简化协方差矩阵(核心:避免高维矩阵内存溢出,等价于原始协方差矩阵)
# 原始协方差矩阵形状:(h*w)×(h*w)(4096×4096),计算量大;简化为n×n(5×5),效率更高
cov_matrix = np.dot(train_faces.T, train_faces) / len(train_faces)
# 2.2 特征值分解(得到特征值和特征向量,特征向量就是"特征脸")
eigenvalues, eigenvectors = np.linalg.eig(cov_matrix)
# 2.3 处理复数类型(修复新手常见报错:部分场景下会生成复数,转为实数)
eigenvalues = np.real(eigenvalues)
eigenvectors = np.real(eigenvectors)
# 2.4 自动筛选k值(保留累计方差95%的核心特征脸,无需手动指定)
total_variance = np.sum(eigenvalues)
cumulative_variance = np.cumsum(eigenvalues) / total_variance
k = np.argmax(cumulative_variance >= 0.95) + 1 # 找到第一个累计方差≥95%的索引
k = max(k, 2) # 确保k至少为2,避免无有效特征脸
print(f"保留累计方差95%,需要选取{k}个特征脸")
# 2.5 排序:特征值从大到小,对应特征脸的重要性从高到低
sorted_indices = np.argsort(eigenvalues)[::–1] # 倒序排序,获取索引
eigenvalues_sorted = eigenvalues[sorted_indices]
eigenvectors_sorted = eigenvectors[:, sorted_indices]
# 2.6 构建特征脸矩阵(k个核心特征脸,形状:k × (h*w))
eigenfaces = eigenvectors_sorted[:, :k].T
# ——————– 步骤3:人脸投影与验证(核心:对比低维特征向量的相似度) ——————–
# 3.1 训练人脸投影到特征脸空间(转为低维特征向量)
train_projections = np.dot(train_faces, eigenfaces.T) # 形状:5×k,每张训练人脸对应一个k维特征向量
# 3.2 计算训练样本的平均特征向量(作为"标准模板")
mean_train_projection = np.mean(train_projections, axis=0)
# 3.3 待验证人脸投影到特征脸空间
test_projection = np.dot(test_face, eigenfaces.T) # 形状:1×k,验证人脸的低维特征向量
# 3.4 计算欧式距离(量化相似度:距离越小,越可能是同一人)
distance = np.linalg.norm(test_projection – mean_train_projection)
# 3.5 自动设定阈值(经验公式:训练样本距离均值的1.5倍,避免手动调参)
train_distances = np.linalg.norm(train_projections – mean_train_projection, axis=1)
threshold = np.mean(train_distances) * 1.5
is_same_person = distance < threshold
# ——————– 步骤4:可视化所有结果(一站式对比,无需分开查看) ——————–
fig = plt.figure(figsize=(18, 8))
# 4.1 子图1:前16张核心特征脸(可视化"核心调料")
ax1 = fig.add_subplot(1, 3, 1)
n_show_eigenfaces = min(16, k)
for i in range(n_show_eigenfaces):
# 特征脸转图像格式,绘制到画布
eigenface = eigenvectors_sorted[:, i].reshape(h, w)
ax1.imshow(eigenface, cmap='gray', extent=[(i%4)*w, (i%4+1)*w, (3–i//4)*h, (4–i//4)*h])
ax1.set_title(f"前{n_show_eigenfaces}张核心特征脸(累计方差95%)", fontsize=12)
ax1.axis('off')
# 4.2 子图2:训练样本与待验证人脸
ax2 = fig.add_subplot(1, 3, 2)
# 绘制训练样本(5张人脸横向排列)
train_faces_show = train_faces.reshape(5, h, w)
for i in range(5):
ax2.imshow(train_faces_show[i], cmap='gray', extent=[i*w, (i+1)*w, 0, h])
# 绘制待验证人脸(在训练样本下方)
ax2.imshow(test_face_original, cmap='gray', extent=[1*w, 2*w, –h, 0])
ax2.text(2.5*w, –h/2, "待验证人脸", ha='center', fontsize=10)
ax2.set_title(f"训练样本(第{person_id}人)+ 待验证人脸", fontsize=12)
ax2.axis('off')
# 4.3 子图3:验证结果(直观展示是否为同一人)
ax3 = fig.add_subplot(1, 3, 3)
ax3.text(0.5, 0.7, f"欧式距离:{distance:.4f}", ha='center', fontsize=14)
ax3.text(0.5, 0.5, f"阈值:{threshold:.4f}", ha='center', fontsize=14)
result_text = "是" if is_same_person else "否"
result_color = "green" if is_same_person else "red"
ax3.text(0.5, 0.3, f"是否为同一人:{result_text}", ha='center', fontsize=14, color=result_color)
ax3.set_title("人脸验证结果", fontsize=12)
ax3.axis('off')
# 4.4 调整布局,显示图像
plt.tight_layout()
plt.show()
# ===================== 运行函数(直接执行,无需额外修改) =====================
if __name__ == "__main__":
pca_eigenface_verification()
2.6 运行结果

2.7 结果解读

2.8 核心代码解释
| face_vectors = faces.reshape(n_faces, h*w) | 将二维人脸图像转为一维向量,构建高维数据矩阵 |
| centered = data – mean_vec | 去中心化,消除光照等全局干扰,聚焦人脸本身结构 |
| np.linalg.eigh(cov) | 对协方差矩阵做特征值分解,得到特征值(信息量)和特征向量(特征脸) |
| eigenfaces = eigvecs[:, :k] | 选取前k个特征脸,保留累计方差95%的核心信息 |
| test_proj = test_face @ eigenfaces | 将待验证人脸投影到特征脸空间,得到低维核心特征 |
| np.linalg.norm(test_proj – mean_train_proj) | 计算欧式距离,量化特征向量的相似度 |
2.9 实战优化
场景3:电影推荐 —— “猜你喜欢哪种积木组合”
3.1 生活化理解
你有5个朋友,每人喜欢的积木组合不同:
- 朋友A喜欢“带轮子+红色”的积木;
- 朋友B喜欢“带翅膀+蓝色”的积木; 现在新到一款“带轮子+红色”的积木套装,你不用问朋友A,也能直接推荐给他!
对应电影推荐:
- 朋友的“积木偏好” = 用户核心特征(如喜欢动作片/喜剧片)= SVD分解后的用户低维特征;
- 积木的“组合属性” = 电影核心特征(如动作/喜剧标签)= SVD分解后的电影低维特征;
- SVD推荐 = 匹配用户特征与电影特征,预测用户对未看电影的喜好程度,推荐评分最高的电影。
3.2 问题背景
在电商、视频平台的推荐系统中,直接用“用户-电影评分”做推荐会遇到以下痛点:
而SVD(奇异值分解)的核心价值是:从稀疏评分矩阵中提取用户和电影的低维核心特征,补全缺失评分,实现精准的个性化推荐。

3.3 核心思路
3.4 关键原理
R
=
U
Σ
V
T
R = U \\Sigma V^T
R=UΣVT
- U(用户特征矩阵):行=用户,列=用户核心特征(如“喜欢动作片”“喜欢喜剧片”);
- Σ(奇异值矩阵):对角线上的奇异值对应“用户-电影特征的关联强度”(值越大,该特征对评分的影响越显著);
- V^T(电影特征矩阵):行=电影核心特征,列=电影(每列对应一部电影的核心特征);
R
k
=
U
k
Σ
k
V
k
T
R_k = U_k \\Sigma_k V_k^T
Rk=UkΣkVkT 重构后的R_k中,0值被补全为预测评分(代表用户对未看电影的喜好程度);

3.5 代码示例
# 导入所有需要的库(一站式导入,无需额外补充)
import numpy as np
import matplotlib.pyplot as plt
# 设置中文字体(Windows)
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# ===================== 完整实现:SVD个性化电影推荐 =====================
def svd_movie_recommendation(top_n=2):
"""
用SVD实现电影评分补全与个性化推荐,一站式输出对比结果
:param top_n: 推荐Top N部电影
"""
# ——————– 步骤1:构建稀疏用户-电影评分矩阵(核心:模拟真实稀疏数据) ——————–
# 1.1 模拟数据:5个用户,6部电影,评分1-5,0表示未评分(稀疏值)
user_movie_ratings = np.array([
[5, 3, 0, 1, 4, 0], # 用户0:喜欢电影0、4,不喜欢电影3
[4, 0, 0, 1, 3, 2], # 用户1:喜欢电影0、4,喜欢电影5
[1, 1, 0, 5, 0, 4], # 用户2:喜欢电影3、5,不喜欢电影0、1
[1, 0, 0, 4, 0, 5], # 用户3:喜欢电影3、5,不喜欢电影0
[0, 1, 5, 4, 2, 0] # 用户4:喜欢电影2、3,喜欢电影4
])
n_users, n_movies = user_movie_ratings.shape
user_names = [f"用户{i}" for i in range(n_users)]
movie_names = [f"电影{i}" for i in range(n_movies)]
# 1.2 打印原始信息
print(f"评分矩阵信息:{n_users}个用户,{n_movies}部电影")
print("原始稀疏评分矩阵(0=未评分):")
print(user_movie_ratings)
# 1.3 预处理:填充用户平均评分(SVD对0值敏感,填充后提升分解效果)
user_means = []
for i in range(n_users):
# 提取用户i的有效评分(非0值),计算平均值
valid_ratings = user_movie_ratings[i][user_movie_ratings[i] != 0]
user_mean = np.mean(valid_ratings) if len(valid_ratings) > 0 else 3.0 # 无有效评分则填充3.0(中等评分)
user_means.append(user_mean)
# 1.4 构建填充后的评分矩阵
ratings_filled = user_movie_ratings.copy().astype(np.float64)
for i in range(n_users):
# 对用户i的未评分项(0)填充用户平均评分
ratings_filled[i, ratings_filled[i] == 0] = user_means[i]
# 1.5 打印填充后的矩阵
print("\\n填充用户均值后的评分矩阵(优化SVD效果):")
print(np.round(ratings_filled, 2))
# ——————– 步骤2:SVD分解与评分矩阵重构(核心:提取核心特征,补全缺失评分) ——————–
# 2.1 SVD分解(紧凑版,减少计算量)
U, s, Vt = np.linalg.svd(ratings_filled, full_matrices=False)
# 2.2 自动筛选k值(保留累计方差90%的核心特征,无需手动指定)
total_variance = np.sum(s ** 2)
cumulative_variance = np.cumsum(s ** 2) / total_variance
k = np.argmax(cumulative_variance >= 0.9) + 1 # 找到第一个累计方差≥90%的索引
print(f"\\n保留累计方差90%,需要选取{k}个奇异值")
# 2.3 构建低维特征矩阵
U_k = U[:, :k] # 用户低维特征矩阵:n_users × k
Sigma_k = np.diag(s[:k]) # 奇异值矩阵:k × k
Vt_k = Vt[:k, :] # 电影低维特征矩阵:k × n_movies
# 2.4 重构评分矩阵(补全缺失评分,核心公式:Rk = Uk × Σk × Vtk)
ratings_reconstructed = np.dot(U_k, np.dot(Sigma_k, Vt_k))
# 2.5 限制评分范围在[1, 5](符合实际评分逻辑,避免出现0或6以上的无效评分)
ratings_reconstructed = np.clip(ratings_reconstructed, 1, 5)
# 2.6 打印补全后的矩阵
print("\\n补全后的评分矩阵(预测评分):")
print(np.round(ratings_reconstructed, 2))
# ——————– 步骤3:个性化电影推荐(核心:为指定用户推荐Top N电影) ——————–
# 3.1 选择待推荐用户(以用户0为例,可修改为其他用户)
target_user = 0
print(f"\\n为{user_names[target_user]}进行电影推荐")
# 3.2 筛选用户未评分的电影(原始评分矩阵中为0的项)
unrated_movie_indices = np.where(user_movie_ratings[target_user] == 0)[0]
unrated_movies = [movie_names[i] for i in unrated_movie_indices]
unrated_ratings = [ratings_reconstructed[target_user][i] for i in unrated_movie_indices]
# 3.3 按预测评分倒序排序,选取Top N电影
recommend_pairs = sorted(zip(unrated_movies, unrated_ratings), key=lambda x: x[1], reverse=True)
top_recommendations = recommend_pairs[:top_n]
# ——————– 步骤4:可视化所有结果(一站式对比,补充详细描述) ——————–
fig = plt.figure(figsize=(22, 10)) # 适当放大画布,容纳更多描述信息
# 4.1 子图1:原始稀疏评分矩阵(带数值标注+详细含义描述)
ax1 = fig.add_subplot(1, 2, 1)
im1 = ax1.imshow(user_movie_ratings, cmap='YlOrRd', vmin=0, vmax=5)
ax1.set_xticks(range(n_movies))
ax1.set_yticks(range(n_users))
ax1.set_xticklabels(movie_names)
ax1.set_yticklabels(user_names)
ax1.set_title("原始稀疏评分矩阵(0=未评分)", fontsize=14)
# 添加数值标注,直观查看
for i in range(n_users):
for j in range(n_movies):
ax1.text(j, i, int(user_movie_ratings[i, j]), ha='center', va='center', fontsize=10)
# 补充原始矩阵含义描述(位于子图下方,左对齐)
matrix_explain = [
"📌 矩阵含义:",
f" – 行:{n_users}个用户,列:{n_movies}部电影",
" – 数值1-5:用户对电影的评分(分数越高越喜欢)",
" – 数值0:用户未观看/未评分(稀疏缺失值)",
f" – 示例:用户0给电影0打5分(最喜欢),给电影3打1分(最不喜欢)"
]
# 把描述文本添加到子图下方(调整y坐标,避免遮挡图表)
for idx, text in enumerate(matrix_explain):
ax1.text(–1, n_users + 0.2 + idx*0.5, text, ha='left', va='top', fontsize=10, transform=ax1.transData)
# 调整x轴范围,容纳左侧描述文本
ax1.set_xlim(–2, n_movies)
ax1.set_ylim(–1, n_users + len(matrix_explain)*0.5 + 0.5)
plt.colorbar(im1, ax=ax1, shrink=0.8)
# 4.2 子图2:个性化推荐结果(补充推荐理由+详细说明)
ax2 = fig.add_subplot(1, 2, 2)
ax2.text(0.5, 0.9, f"为{user_names[target_user]}推荐Top{top_n}电影",
ha='center', fontsize=16, fontweight='bold')
# 打印Top N推荐
for i, (movie, rating) in enumerate(top_recommendations):
ax2.text(0.5, 0.75 – i*0.12, f"{i+1}. {movie},预测评分:{rating:.2f}",
ha='center', fontsize=14, color='blue')
# 补充推荐理由(核心逻辑说明)
recommend_reason = [
"📌 推荐逻辑:",
" 1. 用SVD分解提取用户/电影核心特征(保留90%信息量)",
" 2. 重构评分矩阵,补全用户未评分电影的预测分数",
" 3. 筛选未观看电影中预测评分最高的Top N部",
f" 4. 本次推荐:基于用户0的观影偏好(喜欢电影0、4),预测其对{top_recommendations[0][0]}评分最高",
f" 5. 未观看电影:{', '.join(unrated_movies)}(按预测评分从高到低排序)"
]
# 把推荐理由添加到下方(左对齐,居中展示)
for idx, text in enumerate(recommend_reason):
ax2.text(0.5, 0.5 – idx*0.08, text, ha='center', va='top', fontsize=11, color='darkgreen')
ax2.set_title("个性化电影推荐结果(附推荐理由)", fontsize=14)
ax2.axis('off')
# 4.3 调整布局,显示图像
plt.tight_layout()
plt.show()
# 4.4 打印详细推荐报告
print("\\n=== 个性化电影推荐报告 ===")
print(f"用户:{user_names[target_user]}")
print(f"未观看电影:{', '.join(unrated_movies)}")
print(f"Top{top_n}推荐:")
for i, (movie, rating) in enumerate(top_recommendations):
print(f" {i+1}. {movie},预测评分:{rating:.2f}")
# ===================== 运行函数(直接执行,无需额外修改) =====================
if __name__ == "__main__":
svd_movie_recommendation(top_n=2)
3.6 运行结果

3.7 结果解读

3.8 核心代码解释
| ratings_filled[i][ratings[i] == 0] = mean | 填充用户平均评分,解决SVD对0值敏感的问题 |
| np.linalg.svd(ratings_filled) | 对填充后的矩阵做SVD分解,提取用户/电影核心特征 |
| ratings_completed = U_k @ Sigma_k @ Vt_k | 用低维特征重构评分矩阵,补全未评分项的预测分数 |
| unrated_indices = np.where(user_movie_ratings[target_user] == 0) | 筛选用户未观看的电影 |
| sorted(zip(unrated_movies, unrated_ratings), reverse=True) | 按预测评分倒序排序,选取Top N推荐电影 |
3.9 实战优化
三大场景对比总结
| 图像压缩 | 图像矩阵(非方阵) | SVD分解 | 高奇异值(轮廓、结构) | 低奇异值(噪声、纹理) |
| 人脸识别 | 人脸向量矩阵(方阵协方差) | PCA特征分解 | 高特征值(五官比例) | 低特征值(光照、表情微变) |
| 电影推荐 | 评分矩阵(稀疏非方阵) | SVD分解 | 高奇异值(用户/电影隐因子) | 低奇异值(随机评分噪声) |
1 三大场景的核心思路
三个场景本质都是**“从海量/稀疏数据中抓核心,用少数关键信息解决问题”**:
- 图像压缩:抓“图像核心结构”,舍弃“噪声/冗余细节”;
- 人脸识别:抓“人脸稳定特征”,舍弃“光照/表情干扰”;
- 电影推荐:抓“用户/电影核心偏好”,舍弃“稀疏评分噪声”。
2 三大场景的统一流程(5步通用逻辑)
无论场景如何变化,特征值/奇异值分解的落地流程高度一致:
- 图像压缩:用核心分量重构图像;
- 人脸识别:用核心特征做相似度对比;
- 电影推荐:用核心特征补全评分并推荐。
3 三大场景的底层原理(共同数学本质)
所有操作都围绕**“特征值/奇异值的‘重要性排序’”**展开:
- 特征值/奇异值越大,对应“信息越核心、越稳定”;
- 特征向量对应“数据的核心方向”(图像结构、人脸特征、用户偏好);
- 只保留高值分量,就能用少量信息还原/解决问题,同时过滤冗余与噪声。

最终提炼:“抓核心”的通用思维
共同本质: 用少数“稳定方向”(特征向量) + “重要程度”(特征值),代替海量原始数据。
给新手的终极建议
特征值分解,不是魔法,而是一种聪明的偷懒—— 它帮我们在信息爆炸的世界里,只关注真正重要的东西。

结语:从“数学公式”到“生活智能”
当你打开手机相册自动压缩的照片、刷到精准匹配你口味的短视频、解锁手机时的人脸验证——其实这些日常智能的背后,都藏着“特征值/奇异值分解”的身影。
它没有复杂的神经网络那么“炫酷”,却像一把**“信息筛子”**:帮我们从冗余的像素里抓出图像的轮廓,从杂乱的评分里摸透用户的偏好,从多变的人脸中锁定稳定的五官特征。

本篇三个场景的本质,都是**“用最少的‘核心信息’,解决最多的‘实际问题’”**——这不仅是AI的底层逻辑,更是我们应对复杂世界的思维方式:
- 不用纠结所有细节,先抓“稳定的核心方向”;
- 不用保留所有信息,只留“重要的关键分量”。
下次再遇到“数据太多、干扰太杂”的问题时,不妨想想这三个场景:先找“特征向量”(核心方向),再看“特征值”(重要程度)——或许你也能像AI一样,轻松抓住问题的本质。
(如果觉得这篇实战对你有帮助,欢迎点赞收藏,后续会分享更多“数学落地AI”的干货~)



