《机器学习》第四章-无监督学习

前言

无监督学习是机器学习的核心分支之一,它不需要标签数据,能从海量无标注数据中自动发现规律和结构。本文将系统讲解无监督学习的核心算法,包含聚类分析、主分量分析、稀疏编码三大核心模块,并结合实战案例和可视化对比,让你真正理解并能动手实现这些算法。

所有代码均经过验证,可直接运行,包含详细注释,文末还附有练习题,帮助你巩固所学知识。

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 习题

  1. 尝试修改 K-Means 的聚类数目 k(如 2、3、5),观察轮廓系数的变化,分析 k 值对聚类效果的影响。
  2. 对比不同核函数(如 poly、sigmoid)的核 PCA 在环形数据上的降维效果。
  3. 调整稀疏编码的非零系数数量(如 5、20),观察重构图像的清晰度变化。
  4. 基于热点话题发现的代码,增加更多新闻类别(如 sci.med),优化聚类效果。
  5. 尝试用 DBSCAN 替代 K-Means 实现文本聚类,对比两种方法的效果。

无监督学习思维导图

总结

  1. 聚类分析:划分聚类(K-Means)适合凸分布数据,密度聚类(DBSCAN)适合任意形状数据且能识别噪声;
  2. PCA:基本 PCA 适合线性数据降维,核 PCA 通过核函数处理非线性数据,降维时需平衡维度数和信息保留比例;
  3. 稀疏编码:核心是用稀疏系数表示数据,字典学习能自动优化字典,相比固定字典重构效果更好,广泛应用于图像、文本处理。

注意事项

  1. 运行代码前需安装依赖:pip install numpy matplotlib scikit-learn
  2. 首次加载 MNIST/Olivetti 数据集可能需要联网,耐心等待下载完成;
  3. 可根据电脑性能调整样本数量、迭代次数等参数,避免运行过慢。

希望本文能帮助你掌握无监督学习的核心算法,所有代码均可直接运行,建议动手修改参数,加深理解!如果有问题,欢迎在评论区交流~

相关推荐
NAGNIP1 小时前
一文搞懂深度学习中的通用逼近定理!
人工智能·算法·面试
冬奇Lab2 小时前
一天一个开源项目(第36篇):EverMemOS - 跨 LLM 与平台的长时记忆 OS,让 Agent 会记忆更会推理
人工智能·开源·资讯
冬奇Lab2 小时前
OpenClaw 源码深度解析(一):Gateway——为什么需要一个"中枢"
人工智能·开源·源码阅读
AngelPP6 小时前
OpenClaw 架构深度解析:如何把 AI 助手搬到你的个人设备上
人工智能
宅小年6 小时前
Claude Code 换成了Kimi K2.5后,我再也回不去了
人工智能·ai编程·claude
九狼6 小时前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS6 小时前
Kimi Chat Completion API 申请及使用
前端·人工智能
天翼云开发者社区8 小时前
春节复工福利就位!天翼云息壤2500万Tokens免费送,全品类大模型一键畅玩!
人工智能·算力服务·息壤
知识浅谈8 小时前
教你如何用 Gemini 将课本图片一键转为精美 PPT
人工智能
Ray Liang8 小时前
被低估的量化版模型,小身材也能干大事
人工智能·ai·ai助手·mindx