前言
无监督学习是机器学习的核心分支之一,它不需要标签数据,能从海量无标注数据中自动发现规律和结构。本文将系统讲解无监督学习的核心算法,包含聚类分析、主分量分析、稀疏编码三大核心模块,并结合实战案例和可视化对比,让你真正理解并能动手实现这些算法。
所有代码均经过验证,可直接运行,包含详细注释,文末还附有练习题,帮助你巩固所学知识。
4.1 聚类分析
聚类分析是无监督学习的核心任务,目标是将数据集中相似的样本归为一类,不相似的样本分在不同类中。
4.1.1 划分聚类法
划分聚类法(如 K-Means)是最常用的聚类方法,核心思想是预先指定聚类数目 k,通过迭代将数据划分到 k 个簇中,使得簇内样本相似度高,簇间相似度低。
实战代码:K-Means 聚类(含可视化对比)
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
# 设置中文字体,避免可视化中文乱码
plt.rcParams['font.sans-serif'] = ['SimHei'] # 黑体
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
# 1. 生成模拟聚类数据
X, y_true = make_blobs(
n_samples=300, # 样本数量
centers=4, # 真实聚类中心数
cluster_std=0.6, # 簇内标准差
random_state=0 # 随机种子,保证结果可复现
)
# 2. 原始数据可视化
plt.figure(figsize=(12, 5))
# 子图1:原始数据(无聚类标签)
plt.subplot(1, 2, 1)
plt.scatter(X[:, 0], X[:, 1], s=50, alpha=0.7)
plt.title('原始数据分布', fontsize=12)
plt.xlabel('特征1')
plt.ylabel('特征2')
# 3. K-Means聚类实现
kmeans = KMeans(n_clusters=4, random_state=0, n_init=10) # n_init避免局部最优
y_kmeans = kmeans.fit_predict(X)
# 4. 聚类结果可视化
plt.subplot(1, 2, 2)
# 绘制样本点
plt.scatter(X[:, 0], X[:, 1], c=y_kmeans, s=50, cmap='viridis', alpha=0.7)
# 绘制聚类中心
centers = kmeans.cluster_centers_
plt.scatter(centers[:, 0], centers[:, 1], c='red', s=200, alpha=0.8, marker='X', label='聚类中心')
plt.title('K-Means聚类结果', fontsize=12)
plt.xlabel('特征1')
plt.ylabel('特征2')
plt.legend()
plt.tight_layout()
plt.show()
# 5. 聚类效果评估(轮廓系数,越接近1越好)
sil_score = silhouette_score(X, y_kmeans)
print(f"K-Means聚类轮廓系数:{sil_score:.4f}")
效果说明
运行代码后会看到两张图:左侧是无标签的原始数据分布,右侧是 K-Means 聚类后的结果(不同颜色代表不同簇,红色 X 是聚类中心)。轮廓系数越接近 1,说明聚类效果越好。
4.1.2 密度聚类法
密度聚类法(如 DBSCAN)不依赖预先指定聚类数目,核心思想是:将密度足够高的区域划分为簇,能发现任意形状的簇,还能识别噪声点。
实战代码:DBSCAN 密度聚类(与 K-Means 对比)
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_moons
from sklearn.cluster import DBSCAN, KMeans
from sklearn.preprocessing import StandardScaler
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 1. 生成非凸分布的模拟数据(月亮形数据,K-Means难以处理)
X, y_true = make_moons(n_samples=200, noise=0.05, random_state=0)
X = StandardScaler().fit_transform(X) # 标准化,提升聚类效果
# 2. 创建对比图
plt.figure(figsize=(15, 5))
# 子图1:原始数据
plt.subplot(1, 3, 1)
plt.scatter(X[:, 0], X[:, 1], s=50, alpha=0.7)
plt.title('原始月亮形数据', fontsize=12)
plt.xlabel('特征1')
plt.ylabel('特征2')
# 子图2:K-Means聚类结果(效果差)
kmeans = KMeans(n_clusters=2, random_state=0, n_init=10)
y_kmeans = kmeans.fit_predict(X)
plt.subplot(1, 3, 2)
plt.scatter(X[:, 0], X[:, 1], c=y_kmeans, s=50, cmap='viridis', alpha=0.7)
plt.title('K-Means聚类结果(效果差)', fontsize=12)
plt.xlabel('特征1')
plt.ylabel('特征2')
# 子图3:DBSCAN聚类结果(效果好)
dbscan = DBSCAN(eps=0.3, min_samples=5) # eps:邻域半径,min_samples:核心点最小样本数
y_dbscan = dbscan.fit_predict(X)
plt.subplot(1, 3, 3)
plt.scatter(X[:, 0], X[:, 1], c=y_dbscan, s=50, cmap='viridis', alpha=0.7)
plt.title('DBSCAN聚类结果(效果好)', fontsize=12)
plt.xlabel('特征1')
plt.ylabel('特征2')
plt.tight_layout()
plt.show()
# 输出噪声点数量(标签为-1的是噪声)
noise_points = np.sum(y_dbscan == -1)
print(f"DBSCAN识别的噪声点数量:{noise_points}")
效果说明
月亮形数据是非凸分布的,K-Means 会强行将数据分成两个圆形簇(效果差),而 DBSCAN 能准确识别出月亮形状的簇,还能标记噪声点,这是密度聚类的核心优势。
4.2 主分量分析(PCA)
PCA 是最经典的降维算法,核心思想是将高维数据投影到低维空间,保留数据的主要信息(方差最大的方向)。
4.2.1 基本 PCA 方法
实战代码:PCA 图像降维与重构(含对比图)
python
import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import load_digits
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 1. 加载轻量化手写数字数据集(8x8=64维,共1797个样本,本地内置无需下载)
digits = load_digits()
X = digits.data[:200] # 样本量200个
y = digits.target[:200]
# 2. 数据标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 3. 选择数字8的样本
sample_idx = np.where(y == 8)[0][0]
original_img = X_scaled[sample_idx].reshape(8, 8)
# 4. 核心修正:PCA维度不超过数据集最大维度(64)
# 只拟合一次PCA模型,最大维度设为64(数据集的特征数)
pca = PCA(n_components=64) # 修正:从200改为64
X_pca_all = pca.fit_transform(X_scaled)
# 复用PCA模型,快速重构指定维度的图像
def reconstruct_pca(pca_model, pca_data, n_components, sample_idx):
"""复用PCA模型,快速重构指定维度的图像"""
# 确保n_components不超过最大维度
n_components = min(n_components, pca_model.n_components)
# 只保留前n_components个主成分,其余置0
pca_data_reduced = pca_data.copy()
pca_data_reduced[:, n_components:] = 0
# 逆变换重构
recon = pca_model.inverse_transform(pca_data_reduced)[sample_idx]
return recon.reshape(8, 8)
# 生成不同维度的重构图像(选择10、30、64维,适配数据集维度)
recon_10 = reconstruct_pca(pca, X_pca_all, 10, sample_idx)
recon_30 = reconstruct_pca(pca, X_pca_all, 30, sample_idx) # 修正:50改为30(更贴合64维的演示)
recon_64 = reconstruct_pca(pca, X_pca_all, 64, sample_idx) # 修正:200改为64
# 5. 可视化对比
plt.figure(figsize=(10, 6))
# 子图1:原始图像
plt.subplot(2, 2, 1)
plt.imshow(original_img, cmap='gray')
plt.title('原始图像(64维)', fontsize=12)
plt.axis('off')
# 子图2:10维重构
plt.subplot(2, 2, 2)
plt.imshow(recon_10, cmap='gray')
plt.title('10维PCA重构(模糊)', fontsize=12)
plt.axis('off')
# 子图3:30维重构
plt.subplot(2, 2, 3)
plt.imshow(recon_30, cmap='gray')
plt.title('30维PCA重构(清晰)', fontsize=12)
plt.axis('off')
# 子图4:64维重构
plt.subplot(2, 2, 4)
plt.imshow(recon_64, cmap='gray')
plt.title('64维PCA重构(接近原始)', fontsize=12)
plt.axis('off')
plt.tight_layout()
plt.show()
# 6. 输出各维度保留的方差比例
def get_variance_ratio(pca_model, n_components):
"""获取前n_components个主成分的方差占比"""
n_components = min(n_components, pca_model.n_components)
return np.sum(pca_model.explained_variance_ratio_[:n_components])
print(f"10维PCA保留方差比例:{get_variance_ratio(pca, 10):.4f}")
print(f"30维PCA保留方差比例:{get_variance_ratio(pca, 30):.4f}")
print(f"64维PCA保留方差比例:{get_variance_ratio(pca, 64):.4f}")
效果说明
运行代码后会看到 4 张图:原始 784 维手写数字 8,以及 10/50/200 维 PCA 重构的图像。维度越低,重构图像越模糊,但 50 维就能保留大部分信息(方差比例约 0.8),体现了 PCA 降维的核心价值。
4.2.2 核 PCA 方法
核 PCA 是 PCA 的扩展,能处理非线性可分的数据,通过核函数将数据映射到高维空间,再进行 PCA 降维。
实战代码:核 PCA vs 普通 PCA(非线性数据对比)
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_circles
from sklearn.decomposition import PCA, KernelPCA
from sklearn.preprocessing import StandardScaler
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 1. 生成非线性环形数据
X, y = make_circles(n_samples=500, noise=0.05, factor=0.3, random_state=0)
X = StandardScaler().fit_transform(X)
# 2. 普通PCA降维(无法分离数据)
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)
# 3. 核PCA降维(使用RBF核,能分离数据)
kpca = KernelPCA(n_components=2, kernel='rbf', gamma=15)
X_kpca = kpca.fit_transform(X)
# 4. 可视化对比
plt.figure(figsize=(15, 5))
# 子图1:原始环形数据
plt.subplot(1, 3, 1)
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='viridis', alpha=0.7)
plt.title('原始非线性环形数据', fontsize=12)
plt.xlabel('特征1')
plt.ylabel('特征2')
# 子图2:普通PCA降维结果
plt.subplot(1, 3, 2)
plt.scatter(X_pca[:, 0], X_pca[:, 1], c=y, s=50, cmap='viridis', alpha=0.7)
plt.title('普通PCA降维(无法分离)', fontsize=12)
plt.xlabel('主成分1')
plt.ylabel('主成分2')
# 子图3:核PCA降维结果
plt.subplot(1, 3, 3)
plt.scatter(X_kpca[:, 0], X_kpca[:, 1], c=y, s=50, cmap='viridis', alpha=0.7)
plt.title('核PCA(RBF)降维(可分离)', fontsize=12)
plt.xlabel('核主成分1')
plt.ylabel('核主成分2')
plt.tight_layout()
plt.show()
效果说明
环形数据是典型的非线性数据,普通 PCA 降维后两类数据仍混合在一起,而核 PCA 通过 RBF 核函数映射到高维空间后,能清晰分离两类数据,这是核 PCA 的核心优势。
4.3 稀疏编码与学习
稀疏编码的核心思想是:用少量基向量(字典)的线性组合来表示数据,使得表示系数尽可能稀疏(大部分为 0),能提取数据的关键特征。
4.3.1 稀疏编码概述

稀疏编码是一种特征学习方法,广泛应用于图像特征提取、语音识别等领域。核心流程:
4.3.2 稀疏表示学习
实战代码:图像的稀疏表示与重构(含对比图)
python
import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import SparseCoder
from sklearn.feature_extraction.image import extract_patches_2d, reconstruct_from_patches_2d
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 1. 生成模拟人脸图像(64x64,无需下载任何数据集)
def generate_synthetic_face(size=(64, 64)):
"""生成模拟的人脸灰度图像(简化版,满足演示需求)"""
face = np.zeros(size, dtype=np.float32)
# 绘制脸部轮廓(圆形)
center = (size[0] // 2, size[1] // 2)
radius = size[0] // 2 - 4
y, x = np.ogrid[:size[0], :size[1]]
mask = (x - center[0]) ** 2 + (y - center[1]) ** 2 <= radius ** 2
face[mask] = 0.8 # 脸部底色
# 绘制眼睛
eye_radius = 3
# 左眼
eye1_center = (center[0] - 15, center[1] - 10)
mask1 = (x - eye1_center[0]) ** 2 + (y - eye1_center[1]) ** 2 <= eye_radius ** 2
face[mask1] = 0.1
# 右眼
eye2_center = (center[0] + 15, center[1] - 10)
mask2 = (x - eye2_center[0]) ** 2 + (y - eye2_center[1]) ** 2 <= eye_radius ** 2
face[mask2] = 0.1
# 绘制嘴巴
mouth_center = (center[0], center[1] + 15)
mouth_width = 20
mouth_height = 5
mask_mouth = (np.abs(x - mouth_center[0]) <= mouth_width // 2) & \
(np.abs(y - mouth_center[1]) <= mouth_height // 2)
face[mask_mouth] = 0.1
# 添加轻微噪声,模拟真实图像
face += np.random.normal(0, 0.05, size)
# 归一化到0-1区间
face = np.clip(face, 0, 1)
return face
# 生成模拟人脸(64x64)
face = generate_synthetic_face(size=(64, 64))
# 2. 核心修正:按补丁维度处理数据(解决维度不匹配问题)
patch_size = (8, 8)
# 提取原始图像的所有补丁(每个补丁8x8=64维)
patches = extract_patches_2d(face, patch_size)
n_patches = patches.shape[0] # 补丁总数
patches_flat = patches.reshape(n_patches, -1) # 展平补丁:(n, 64)
# 3. 构建字典(用前100个补丁作为初始字典,维度64)
dict_init = patches_flat[:100] # 字典维度:(100, 64)
# 4. 稀疏编码(输入是补丁数据,维度匹配字典)
coder = SparseCoder(
dictionary=dict_init,
transform_n_nonzero_coefs=5, # 每个补丁最多5个非零系数(适配小维度)
transform_algorithm='lasso_lars'
)
# 对所有补丁进行稀疏编码
coefs = coder.transform(patches_flat) # 输出:(n_patches, 100)
# 5. 重构所有补丁
patches_recon = coefs @ dict_init # (n_patches, 64)
patches_recon = patches_recon.reshape(-1, patch_size[0], patch_size[1]) # 恢复补丁形状
# 6. 将重构的补丁还原为完整图像(核心修正:用reconstruct_from_patches_2d)
# 计算补丁的步长(确保能完整重构64x64图像)
face_recon = reconstruct_from_patches_2d(patches_recon, face.shape)
# 7. 可视化对比
plt.figure(figsize=(10, 8))
# 子图1:原始模拟人脸
plt.subplot(1, 2, 1)
plt.imshow(face, cmap='gray')
plt.title('原始模拟人脸图像', fontsize=12)
plt.axis('off')
# 子图2:稀疏表示重构人脸
plt.subplot(1, 2, 2)
plt.imshow(face_recon, cmap='gray')
plt.title('稀疏表示重构图像', fontsize=12)
plt.axis('off')
plt.tight_layout()
plt.show()
# 8. 输出稀疏性指标(非零系数占比)
non_zero_ratio = np.sum(coefs != 0) / coefs.size
print(f"稀疏系数非零占比:{non_zero_ratio:.4f}(越小越稀疏)")
效果说明
运行代码后会看到两张图:原始 64x64 人脸图像,以及通过稀疏编码重构的图像。稀疏系数仅用 10 个非零值就能重构出人脸的核心特征,体现了稀疏表示的高效性。
4.3.3 字典学习
字典学习是稀疏编码的扩展,核心是自动学习最优字典,而不是使用固定字典,能更好地适配数据特征。
实战代码:字典学习 vs 固定字典(重构效果对比)
python
import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import DictionaryLearning
from sklearn.feature_extraction.image import extract_patches_2d
from sklearn.decomposition import SparseCoder
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 1. 生成模拟图像(替代下载的人脸数据,64x64灰度图)
def generate_synthetic_image(size=(64, 64)):
"""生成模拟的结构化图像(替代人脸,满足字典学习演示)"""
img = np.zeros(size, dtype=np.float32)
# 绘制网格纹理(模拟图像的结构化特征)
step = 8
for i in range(0, size[0], step):
img[i, :] = 0.8 # 横线
for j in range(0, size[1], step):
img[:, j] = 0.8 # 竖线
# 添加圆形特征(模拟局部纹理)
center = (size[0] // 2, size[1] // 2)
radius = 15
y, x = np.ogrid[:size[0], :size[1]]
mask = (x - center[0]) ** 2 + (y - center[1]) ** 2 <= radius ** 2
img[mask] = 0.5
# 归一化到0-1区间
img = (img - img.min()) / (img.max() - img.min())
return img
# 生成模拟图像并预处理
face = generate_synthetic_image(size=(64, 64)) # 替代人脸图像
patch_size = (8, 8)
# 提取补丁作为训练数据(核心逻辑不变)
patches = extract_patches_2d(face, patch_size)
patches = patches.reshape(patches.shape[0], -1)
# 无需除以255(生成的图像已归一化到0-1)
# 2. 字典学习(自动学习最优字典,参数与原代码一致)
dict_learner = DictionaryLearning(
n_components=100, # 字典大小
alpha=1, # 稀疏性正则化系数
max_iter=50, # 最大迭代次数
random_state=0
)
dict_learned = dict_learner.fit_transform(patches) # 学习字典并编码
dict_matrix = dict_learner.components_ # 学习到的字典
# 3. 固定字典(随机字典,逻辑不变)
dict_random = np.random.randn(100, 64) # 64=8x8,随机字典
# 4. 重构图像(对比两种字典,逻辑不变)
# 字典学习重构
face_recon_learned = dict_learned @ dict_matrix
face_recon_learned = face_recon_learned.mean(axis=0).reshape(8, 8) # 平均补丁重构
# 随机字典重构
coder = SparseCoder(dictionary=dict_random, transform_n_nonzero_coefs=10)
coefs_random = coder.transform(patches)
face_recon_random = coefs_random @ dict_random
face_recon_random = face_recon_random.mean(axis=0).reshape(8, 8)
# 5. 可视化对比(与原代码一致)
plt.figure(figsize=(15, 8))
# 子图1:原始补丁
plt.subplot(1, 3, 1)
plt.imshow(patches[0].reshape(8, 8), cmap='gray')
plt.title('原始图像补丁', fontsize=12)
plt.axis('off')
# 子图2:字典学习重构补丁
plt.subplot(1, 3, 2)
plt.imshow(face_recon_learned, cmap='gray')
plt.title('字典学习重构补丁', fontsize=12)
plt.axis('off')
# 子图3:随机字典重构补丁
plt.subplot(1, 3, 3)
plt.imshow(face_recon_random, cmap='gray')
plt.title('随机字典重构补丁', fontsize=12)
plt.axis('off')
plt.tight_layout()
plt.show()
# 6. 输出重构误差(与原代码一致)
error_learned = np.mean((patches[0] - face_recon_learned.flatten()) ** 2)
error_random = np.mean((patches[0] - face_recon_random.flatten()) ** 2)
print(f"字典学习重构误差:{error_learned:.6f}")
print(f"随机字典重构误差:{error_random:.6f}")
效果说明
字典学习能自动学习适配数据的最优字典,重构误差远小于随机字典,重构的图像补丁更清晰,这是字典学习的核心价值。
4.4 无监督学习应用
4.4.1 热点话题发现(文本聚类)
python
import numpy as np
import matplotlib.pyplot as plt
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.cluster import KMeans
from sklearn.decomposition import PCA
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 1. 生成模拟文本数据(替代下载的20newsgroups,模拟4类热点话题)
def generate_synthetic_text_data():
"""生成模拟的4类文本数据(太空、图形学、棒球、政治)"""
# 定义每类话题的核心词汇
space_vocab = ['space', 'nasa', 'rocket', 'moon', 'mars', 'orbit', 'satellite', 'astronaut', 'shuttle', 'galaxy']
graphics_vocab = ['graphics', '3d', 'render', 'image', 'pixel', 'gpu', 'opengl', 'texture', 'model', 'visual']
baseball_vocab = ['baseball', 'mlb', 'pitch', 'hit', 'home', 'run', 'bat', 'team', 'game', 'league']
politics_vocab = ['politics', 'government', 'vote', 'policy', 'law', 'president', 'congress', 'party', 'tax',
'election']
# 生成每类文本(每类50条,共200条)
texts = []
labels_true = [] # 真实标签(用于对比,非聚类用)
# 生成太空类文本
for i in range(50):
# 随机选5-8个核心词,加随机虚词
words = np.random.choice(space_vocab, size=np.random.randint(5, 8), replace=True)
fake_text = ' '.join(words) + ' this is a fake text about space exploration'
texts.append(fake_text)
labels_true.append(0)
# 生成图形学类文本
for i in range(50):
words = np.random.choice(graphics_vocab, size=np.random.randint(5, 8), replace=True)
fake_text = ' '.join(words) + ' this is a fake text about computer graphics'
texts.append(fake_text)
labels_true.append(1)
# 生成棒球类文本
for i in range(50):
words = np.random.choice(baseball_vocab, size=np.random.randint(5, 8), replace=True)
fake_text = ' '.join(words) + ' this is a fake text about baseball game'
texts.append(fake_text)
labels_true.append(2)
# 生成政治类文本
for i in range(50):
words = np.random.choice(politics_vocab, size=np.random.randint(5, 8), replace=True)
fake_text = ' '.join(words) + ' this is a fake text about political issues'
texts.append(fake_text)
labels_true.append(3)
return texts
# 生成模拟文本数据
newsgroups_data = generate_synthetic_text_data()
# 2. 文本向量化(TF-IDF,参数与原代码一致)
vectorizer = TfidfVectorizer(
stop_words='english', # 去除停用词
max_features=2000, # 保留Top2000特征
max_df=0.95, # 过滤高频词
min_df=2 # 过滤低频词
)
X = vectorizer.fit_transform(newsgroups_data)
# 3. K-Means文本聚类(热点话题发现,参数不变)
kmeans = KMeans(n_clusters=4, random_state=0, n_init=10)
y_pred = kmeans.fit_predict(X)
# 4. PCA降维可视化(高维文本→2维,逻辑不变)
pca = PCA(n_components=2, random_state=0)
X_pca = pca.fit_transform(X.toarray())
# 5. 可视化聚类结果(热点话题分布,与原代码一致)
plt.figure(figsize=(10, 8))
colors = ['red', 'green', 'blue', 'orange']
labels = ['太空', '计算机图形学', '棒球', '政治']
for i in range(4):
plt.scatter(
X_pca[y_pred == i, 0],
X_pca[y_pred == i, 1],
c=colors[i],
label=labels[i],
alpha=0.7,
s=50
)
plt.title('新闻文本聚类(热点话题发现)', fontsize=14)
plt.xlabel('PCA维度1')
plt.ylabel('PCA维度2')
plt.legend()
plt.show()
# 6. 输出每个聚类的核心关键词(与原代码一致)
print("=== 各热点话题核心关键词 ===")
order_centroids = kmeans.cluster_centers_.argsort()[:, ::-1]
terms = vectorizer.get_feature_names_out()
for i in range(4):
print(f"话题{i + 1}({labels[i]}):", end='')
for ind in order_centroids[i, :10]:
print(f"{terms[ind]} ", end='')
print()
效果说明
代码将新闻文本聚类为 4 个热点话题(太空、图形学、棒球、政治),并通过 PCA 可视化话题分布,同时输出每个话题的核心关键词,直观体现无监督学习在文本热点发现中的应用。
4.4.2 自动人脸识别(PCA 人脸特征提取)
python
import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 1. 生成模拟Olivetti人脸数据集(替代下载,40人×10张=400样本,64x64=4096维)
def generate_synthetic_olivetti_faces():
"""生成模拟人脸数据集,模拟40个人,每人10张脸"""
np.random.seed(0)
n_persons = 40 # 40个人
n_faces_per_person = 10 # 每人10张脸
img_size = (64, 64) # 64x64图像
X = []
y = []
for person_id in range(n_persons):
# 生成该人的基础人脸特征(唯一的轮廓+五官)
base_face = np.zeros(img_size, dtype=np.float32)
# 脸部轮廓(每人略有不同)
center = (img_size[0] // 2, img_size[1] // 2)
radius = img_size[0] // 2 - 4 + np.random.randn() * 1
y_grid, x_grid = np.ogrid[:img_size[0], :img_size[1]]
face_mask = (x_grid - center[0]) ** 2 + (y_grid - center[1]) ** 2 <= radius ** 2
base_face[face_mask] = 0.7 + np.random.randn() * 0.05
# 眼睛(每人位置/大小略有不同)
eye_radius = 3 + np.random.randn() * 0.2
# 左眼
eye1_center = (center[0] - 15 + np.random.randn() * 1, center[1] - 10 + np.random.randn() * 1)
eye1_mask = (x_grid - eye1_center[0]) ** 2 + (y_grid - eye1_center[1]) ** 2 <= eye_radius ** 2
base_face[eye1_mask] = 0.1 + np.random.randn() * 0.05
# 右眼
eye2_center = (center[0] + 15 + np.random.randn() * 1, center[1] - 10 + np.random.randn() * 1)
eye2_mask = (x_grid - eye2_center[0]) ** 2 + (y_grid - eye2_center[1]) ** 2 <= eye_radius ** 2
base_face[eye2_mask] = 0.1 + np.random.randn() * 0.05
# 嘴巴(每人形状略有不同)
mouth_center = (center[0], center[1] + 15 + np.random.randn() * 1)
mouth_width = 20 + np.random.randn() * 1
mouth_height = 5 + np.random.randn() * 0.5
mouth_mask = (np.abs(x_grid - mouth_center[0]) <= mouth_width // 2) & \
(np.abs(y_grid - mouth_center[1]) <= mouth_height // 2)
base_face[mouth_mask] = 0.1 + np.random.randn() * 0.05
# 为该人生成10张脸(添加不同的噪声,模拟不同角度/光照)
for _ in range(n_faces_per_person):
face = base_face + np.random.normal(0, 0.08, img_size) # 随机噪声
face = np.clip(face, 0, 1) # 归一化到0-1
X.append(face.flatten()) # 展平为4096维
y.append(person_id) # 标签为人物ID
return np.array(X), np.array(y)
# 生成模拟人脸数据(替代下载的Olivetti)
X, y = generate_synthetic_olivetti_faces()
# 2. PCA人脸特征提取(特征脸,参数与原代码一致)
n_components = 50 # 保留50个主成分(特征脸)
pca = PCA(n_components=n_components, svd_solver='randomized', whiten=True, random_state=0)
X_pca = pca.fit_transform(X)
# 3. 可视化特征脸(与原代码一致)
plt.figure(figsize=(12, 6))
for i in range(8):
plt.subplot(2, 4, i + 1)
plt.imshow(pca.components_[i].reshape(64, 64), cmap='gray')
plt.title(f'特征脸 {i + 1}', fontsize=10)
plt.axis('off')
plt.suptitle('PCA提取的人脸特征脸', fontsize=12)
plt.tight_layout()
plt.show()
# 4. 人脸识别分类(KNN,逻辑不变)
X_train, X_test, y_train, y_test = train_test_split(X_pca, y, test_size=0.2, random_state=0)
knn = KNeighborsClassifier(n_neighbors=3)
knn.fit(X_train, y_train)
accuracy = knn.score(X_test, y_test)
# 5. 可视化原始脸 vs 重构脸(与原代码一致)
sample_idx = 0
original_face = X[sample_idx].reshape(64, 64)
recon_face = pca.inverse_transform(X_pca[sample_idx]).reshape(64, 64)
plt.figure(figsize=(8, 4))
plt.subplot(1, 2, 1)
plt.imshow(original_face, cmap='gray')
plt.title('原始人脸', fontsize=12)
plt.axis('off')
plt.subplot(1, 2, 2)
plt.imshow(recon_face, cmap='gray')
plt.title('PCA重构人脸', fontsize=12)
plt.axis('off')
plt.tight_layout()
plt.show()
# 输出结果(与原代码一致)
print(f"PCA降维后人脸识别准确率:{accuracy:.4f}")
print(f"50维特征保留的方差比例:{np.sum(pca.explained_variance_ratio_):.4f}")

效果说明
代码通过 PCA 提取人脸的 "特征脸",将 4096 维人脸数据降维到 50 维,再用 KNN 实现人脸识别,准确率可达 90% 以上,体现了无监督学习在人脸识别中的核心作用。
4.5 习题
- 尝试修改 K-Means 的聚类数目 k(如 2、3、5),观察轮廓系数的变化,分析 k 值对聚类效果的影响。
- 对比不同核函数(如 poly、sigmoid)的核 PCA 在环形数据上的降维效果。
- 调整稀疏编码的非零系数数量(如 5、20),观察重构图像的清晰度变化。
- 基于热点话题发现的代码,增加更多新闻类别(如 sci.med),优化聚类效果。
- 尝试用 DBSCAN 替代 K-Means 实现文本聚类,对比两种方法的效果。
无监督学习思维导图

总结
- 聚类分析:划分聚类(K-Means)适合凸分布数据,密度聚类(DBSCAN)适合任意形状数据且能识别噪声;
- PCA:基本 PCA 适合线性数据降维,核 PCA 通过核函数处理非线性数据,降维时需平衡维度数和信息保留比例;
- 稀疏编码:核心是用稀疏系数表示数据,字典学习能自动优化字典,相比固定字典重构效果更好,广泛应用于图像、文本处理。
注意事项
- 运行代码前需安装依赖:
pip install numpy matplotlib scikit-learn; - 首次加载 MNIST/Olivetti 数据集可能需要联网,耐心等待下载完成;
- 可根据电脑性能调整样本数量、迭代次数等参数,避免运行过慢。
希望本文能帮助你掌握无监督学习的核心算法,所有代码均可直接运行,建议动手修改参数,加深理解!如果有问题,欢迎在评论区交流~








