从鸢尾花分类到人脸识别:手把手用Python实战PCA、LDA、ICA和因子分析
从鸢尾花分类到人脸识别:Python实战四大降维算法
在数据科学领域,我们常常会遇到"维度灾难"——当特征数量远大于样本数量时,传统机器学习算法的性能会急剧下降。想象一下,你正在处理一组人脸图像数据,每张100x100像素的图片就包含10,000个特征(像素),而你的训练样本可能只有几百张。这种情况下,降维技术就成为了数据预处理的关键步骤。
本文将带您通过两个经典案例——鸢尾花分类和人脸识别,深入实践四种核心降维算法:主成分分析(PCA)、线性判别分析(LDA)、独立成分分析(ICA)和因子分析(FA)。不同于单纯的理论讲解,我们会使用Python的scikit-learn库,在Jupyter Notebook环境中一步步实现这些算法,并通过可视化直观展示它们的效果差异。
1. 环境准备与数据加载
在开始实战之前,我们需要准备好Python环境和必要的数据集。推荐使用Anaconda创建独立的Python环境,确保所有依赖库的版本一致。
# 基础库导入 import numpy as np import matplotlib.pyplot as plt %matplotlib inline # scikit-learn相关导入 from sklearn.datasets import load_iris, fetch_lfw_people from sklearn.decomposition import PCA, FastICA, FactorAnalysis from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA # 数据预处理 from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split1.1 加载鸢尾花数据集
鸢尾花数据集是机器学习中最经典的入门数据集之一,包含三种鸢尾花的四个特征:萼片长度、萼片宽度、花瓣长度和花瓣宽度。
iris = load_iris() X_iris = iris.data y_iris = iris.target feature_names = iris.feature_names target_names = iris.target_names print(f"鸢尾花数据集形状: {X_iris.shape}") print(f"特征名称: {feature_names}") print(f"目标类别: {target_names}")1.2 加载人脸数据集
为了展示更复杂的应用场景,我们将使用LFW(Labeled Faces in the Wild)人脸数据集。这个数据集包含5749张62x47像素的灰度人脸图像,来自1680个不同人物。
lfw_people = fetch_lfw_people(min_faces_per_person=70, resize=0.4) X_faces = lfw_people.data y_faces = lfw_people.target n_samples, h, w = lfw_people.images.shape print(f"人脸数据集形状: {X_faces.shape}") print(f"图像高度: {h}, 宽度: {w}") print(f"包含人物数量: {len(set(y_faces))}")2. 数据预处理与探索
在应用降维算法之前,数据标准化是必不可少的步骤。不同特征往往具有不同的量纲和数值范围,这会影响降维算法的效果。
2.1 数据标准化
# 鸢尾花数据标准化 scaler_iris = StandardScaler() X_iris_scaled = scaler_iris.fit_transform(X_iris) # 人脸数据标准化 scaler_faces = StandardScaler() X_faces_scaled = scaler_faces.fit_transform(X_faces)2.2 可视化原始数据
为了更好地理解降维的效果,我们先可视化原始数据。对于鸢尾花数据集,我们可以绘制特征两两之间的散点图矩阵。
from pandas.plotting import scatter_matrix import pandas as pd df_iris = pd.DataFrame(X_iris_scaled, columns=feature_names) scatter_matrix(df_iris, c=y_iris, figsize=(10, 10), marker='o', hist_kwds={'bins': 20}, s=60, alpha=.8) plt.suptitle('鸢尾花特征散点图矩阵', y=1.02) plt.show()对于人脸数据,我们可以随机展示几张原始图像:
fig, axes = plt.subplots(3, 5, figsize=(10, 6), subplot_kw={'xticks':[], 'yticks':[]}) for i, ax in enumerate(axes.flat): ax.imshow(X_faces[i].reshape((h, w)), cmap='gray') ax.set_title(lfw_people.target_names[y_faces[i]]) plt.suptitle('LFW人脸数据集示例', y=1.02) plt.show()3. 主成分分析(PCA)实战
PCA是最常用的线性降维方法,它通过正交变换将一组可能存在相关性的变量转换为一组线性不相关的变量,转换后的这组变量叫主成分。
3.1 PCA在鸢尾花数据集上的应用
pca_iris = PCA(n_components=2) X_iris_pca = pca_iris.fit_transform(X_iris_scaled) plt.figure(figsize=(8, 6)) for color, i, target_name in zip(['navy', 'turquoise', 'darkorange'], [0, 1, 2], target_names): plt.scatter(X_iris_pca[y_iris == i, 0], X_iris_pca[y_iris == i, 1], color=color, alpha=.8, lw=2, label=target_name) plt.legend(loc='best', shadow=False, scatterpoints=1) plt.title('鸢尾花数据集PCA降维结果') plt.xlabel('第一主成分 (解释方差: %.2f%%)' % (pca_iris.explained_variance_ratio_[0]*100)) plt.ylabel('第二主成分 (解释方差: %.2f%%)' % (pca_iris.explained_variance_ratio_[1]*100)) plt.show()3.2 PCA在人脸识别中的应用
在人脸识别中,PCA提取的特征被称为"特征脸"(Eigenfaces)。让我们看看前几个主成分对应的"特征脸"是什么样子。
n_components = 150 # 选择150个主成分 pca_faces = PCA(n_components=n_components, svd_solver='randomized', whiten=True).fit(X_faces_scaled) # 可视化特征脸 eigenfaces = pca_faces.components_.reshape((n_components, h, w)) fig, axes = plt.subplots(3, 5, figsize=(10, 6), subplot_kw={'xticks':[], 'yticks':[]}) for i, ax in enumerate(axes.flat): ax.imshow(eigenfaces[i], cmap='gray') ax.set_title(f"特征脸 #{i+1}") plt.suptitle('前15个特征脸', y=1.02) plt.show()3.3 PCA结果分析
PCA有几个关键指标可以帮助我们理解降维效果:
- 解释方差比例:每个主成分保留了多少原始数据的方差
- 累计解释方差:前n个主成分总共保留了多少原始数据的方差
plt.figure(figsize=(10, 6)) plt.plot(np.cumsum(pca_faces.explained_variance_ratio_)) plt.xlabel('主成分数量') plt.ylabel('累计解释方差比例') plt.title('人脸数据集PCA累计解释方差') plt.axhline(y=0.95, color='r', linestyle='--') plt.text(50, 0.85, '95%方差线', color='r') plt.grid() plt.show()4. 线性判别分析(LDA)实战
LDA是一种有监督的降维方法,它在降维的同时考虑了类别信息,目标是最大化类间距离和最小化类内距离。
4.1 LDA在鸢尾花数据集上的应用
lda_iris = LDA(n_components=2) X_iris_lda = lda_iris.fit_transform(X_iris_scaled, y_iris) plt.figure(figsize=(8, 6)) for color, i, target_name in zip(['navy', 'turquoise', 'darkorange'], [0, 1, 2], target_names): plt.scatter(X_iris_lda[y_iris == i, 0], X_iris_lda[y_iris == i, 1], color=color, alpha=.8, lw=2, label=target_name) plt.legend(loc='best', shadow=False, scatterpoints=1) plt.title('鸢尾花数据集LDA降维结果') plt.xlabel('第一判别成分') plt.ylabel('第二判别成分') plt.show()4.2 LDA与PCA结果对比
让我们将PCA和LDA的结果放在一起比较:
plt.figure(figsize=(16, 6)) plt.subplot(1, 2, 1) for color, i, target_name in zip(['navy', 'turquoise', 'darkorange'], [0, 1, 2], target_names): plt.scatter(X_iris_pca[y_iris == i, 0], X_iris_pca[y_iris == i, 1], color=color, alpha=.8, lw=2, label=target_name) plt.legend(loc='best', shadow=False, scatterpoints=1) plt.title('PCA降维结果') plt.subplot(1, 2, 2) for color, i, target_name in zip(['navy', 'turquoise', 'darkorange'], [0, 1, 2], target_names): plt.scatter(X_iris_lda[y_iris == i, 0], X_iris_lda[y_iris == i, 1], color=color, alpha=.8, lw=2, label=target_name) plt.legend(loc='best', shadow=False, scatterpoints=1) plt.title('LDA降维结果') plt.tight_layout() plt.show()注意:LDA最多只能降到类别数-1的维度。对于鸢尾花数据集有3类,所以最大降维到2维。
5. 独立成分分析(ICA)实战
ICA是一种用于分离多变量信号的统计方法,它假设观测信号是多个独立非高斯信号的线性组合。
5.1 ICA在鸢尾花数据集上的应用
ica_iris = FastICA(n_components=2, random_state=42) X_iris_ica = ica_iris.fit_transform(X_iris_scaled) plt.figure(figsize=(8, 6)) for color, i, target_name in zip(['navy', 'turquoise', 'darkorange'], [0, 1, 2], target_names): plt.scatter(X_iris_ica[y_iris == i, 0], X_iris_ica[y_iris == i, 1], color=color, alpha=.8, lw=2, label=target_name) plt.legend(loc='best', shadow=False, scatterpoints=1) plt.title('鸢尾花数据集ICA降维结果') plt.xlabel('第一独立成分') plt.ylabel('第二独立成分') plt.show()5.2 ICA在人脸信号分离中的应用
ICA在人脸识别中可以用于分离不同的面部特征或光照条件:
n_components = 15 ica_faces = FastICA(n_components=n_components, random_state=42) X_faces_ica = ica_faces.fit_transform(X_faces_scaled) # 可视化独立成分 ica_components = ica_faces.components_.reshape((n_components, h, w)) fig, axes = plt.subplots(3, 5, figsize=(10, 6), subplot_kw={'xticks':[], 'yticks':[]}) for i, ax in enumerate(axes.flat): ax.imshow(ica_components[i], cmap='gray') ax.set_title(f"独立成分 #{i+1}") plt.suptitle('前15个独立成分', y=1.02) plt.show()6. 因子分析(FA)实战
因子分析是一种统计方法,用于描述观察到的变量与潜在变量(因子)之间的关系。它假设观察到的变量是潜在因子的线性组合加上噪声。
6.1 FA在鸢尾花数据集上的应用
fa_iris = FactorAnalysis(n_components=2, random_state=42) X_iris_fa = fa_iris.fit_transform(X_iris_scaled) plt.figure(figsize=(8, 6)) for color, i, target_name in zip(['navy', 'turquoise', 'darkorange'], [0, 1, 2], target_names): plt.scatter(X_iris_fa[y_iris == i, 0], X_iris_fa[y_iris == i, 1], color=color, alpha=.8, lw=2, label=target_name) plt.legend(loc='best', shadow=False, scatterpoints=1) plt.title('鸢尾花数据集FA降维结果') plt.xlabel('第一因子') plt.ylabel('第二因子') plt.show()6.2 四种降维方法对比
为了更直观地比较四种降维方法的效果,我们将它们的结果放在一起:
methods = ['PCA', 'LDA', 'ICA', 'FA'] results = [X_iris_pca, X_iris_lda, X_iris_ica, X_iris_fa] plt.figure(figsize=(16, 12)) for i, (method, result) in enumerate(zip(methods, results)): plt.subplot(2, 2, i+1) for color, j, target_name in zip(['navy', 'turquoise', 'darkorange'], [0, 1, 2], target_names): plt.scatter(result[y_iris == j, 0], result[y_iris == j, 1], color=color, alpha=.8, lw=2, label=target_name) plt.title(f'{method}降维结果') plt.legend(loc='best') if method == 'PCA': plt.xlabel(f'第一主成分 (解释方差: {pca_iris.explained_variance_ratio_[0]*100:.1f}%)') plt.ylabel(f'第二主成分 (解释方差: {pca_iris.explained_variance_ratio_[1]*100:.1f}%)') else: plt.xlabel(f'第一{method}成分') plt.ylabel(f'第二{method}成分') plt.tight_layout() plt.show()7. 降维后的分类性能评估
降维的最终目的是提高机器学习模型的性能。让我们比较原始数据和降维后数据在分类任务上的表现。
7.1 鸢尾花分类任务
from sklearn.svm import SVC from sklearn.model_selection import cross_val_score # 原始数据 svc = SVC(kernel='linear', C=1) scores_original = cross_val_score(svc, X_iris_scaled, y_iris, cv=5) # 降维后数据 datasets = { 'PCA': X_iris_pca, 'LDA': X_iris_lda, 'ICA': X_iris_ica, 'FA': X_iris_fa } results = {} for name, data in datasets.items(): svc = SVC(kernel='linear', C=1) scores = cross_val_score(svc, data, y_iris, cv=5) results[name] = scores.mean() # 添加原始数据结果 results['原始数据'] = scores_original.mean() # 展示结果 plt.figure(figsize=(10, 6)) plt.bar(results.keys(), results.values()) plt.title('不同降维方法在鸢尾花分类任务上的表现') plt.ylabel('5折交叉验证准确率') plt.ylim(0.9, 1.0) plt.show()7.2 人脸识别任务
对于人脸识别任务,我们使用更复杂的流程:
from sklearn.svm import SVC from sklearn.pipeline import make_pipeline from sklearn.model_selection import GridSearchCV # 划分训练集和测试集 X_train, X_test, y_train, y_test = train_test_split( X_faces_scaled, y_faces, test_size=0.25, random_state=42) # 定义降维方法 dim_reduction_methods = { 'PCA': PCA(n_components=150, whiten=True, random_state=42), 'ICA': FastICA(n_components=150, random_state=42), 'FA': FactorAnalysis(n_components=150, random_state=42) } # 评估每种降维方法 results_faces = {} for name, method in dim_reduction_methods.items(): pipe = make_pipeline(method, SVC(kernel='rbf', class_weight='balanced')) param_grid = { 'svc__C': [1, 5, 10], 'svc__gamma': [0.0001, 0.0005, 0.001] } grid = GridSearchCV(pipe, param_grid, cv=5) grid.fit(X_train, y_train) results_faces[name] = grid.best_score_ # 添加原始数据结果 pipe = make_pipeline(SVC(kernel='rbf', class_weight='balanced')) param_grid = { 'svc__C': [1, 5, 10], 'svc__gamma': [0.0001, 0.0005, 0.001] } grid = GridSearchCV(pipe, param_grid, cv=5) grid.fit(X_train, y_train) results_faces['原始数据'] = grid.best_score_ # 展示结果 plt.figure(figsize=(10, 6)) plt.bar(results_faces.keys(), results_faces.values()) plt.title('不同降维方法在人脸识别任务上的表现') plt.ylabel('最佳交叉验证准确率') plt.show()8. 算法选择指南与实用建议
经过上述实验,我们可以总结出一些实用的降维方法选择指南:
8.1 不同场景下的算法选择
| 场景特征 | 推荐算法 | 原因说明 |
|---|---|---|
| 无标签数据 | PCA, ICA, FA | 这些是无监督方法,不需要类别信息 |
| 有标签数据 | LDA | LDA利用类别信息最大化类间差异 |
| 高斯分布数据 | PCA, FA | 这些方法假设数据服从高斯分布 |
| 非高斯分布数据 | ICA | ICA专门用于处理非高斯信号 |
| 数据解释性要求高 | FA | 因子分析提供潜在因子的解释 |
| 计算效率要求高 | PCA | PCA计算效率高,适合大规模数据 |
| 信号分离任务 | ICA | ICA设计目的就是分离混合信号 |
8.2 实际应用中的技巧
预处理至关重要:
- 标准化数据(零均值,单位方差)
- 处理缺失值(降维方法通常不能直接处理缺失值)
确定降维维度:
- PCA:观察解释方差曲线,选择保留足够方差的最小维度
- LDA:最多降到类别数-1的维度
- ICA/FA:通常需要通过实验确定最佳维度
结合多种方法:
- 可以先使用PCA去除噪声和冗余维度,再用其他方法
- 例如:PCA → ICA 的串联使用
可视化验证:
- 降维到2D或3D后可视化检查结果是否符合预期
- 对于图像数据,可视化基向量(如特征脸)检查是否合理
# 示例:PCA+ICA串联使用 pca = PCA(n_components=50, whiten=True) ica = FastICA(n_components=10, random_state=42) X_pca_ica = ica.fit_transform(pca.fit_transform(X_faces_scaled)) print(f"串联降维后维度: {X_pca_ica.shape}")8.3 常见问题与解决方案
内存不足:
- 使用增量PCA(IncrementalPCA)处理大数据
- 随机化SVD(svd_solver='randomized')加速计算
结果不稳定:
- ICA和FA的结果可能因初始化不同而变化
- 设置固定random_state确保可重复性
类别不平衡:
- LDA对类别不平衡敏感
- 考虑使用加权LDA或先平衡数据集
非线性数据:
- 线性方法可能不适用于高度非线性数据
- 考虑核方法(KernelPCA)或流形学习(t-SNE, UMAP)
