今日任务:
- LDA线性判别
- PCA主成分分析
- t-sne降维
- 自由作业:探索下什么时候用到降维?降维的主要应用?或者让ai给你出题,群里的同学互相学习下。可以考虑对比下在某些特定数据集上t-sne的可视化和pca可视化的区别。
在day 20 学习了SVD,在今天需要学习另外三种常见的降维算法:LDA线性判别、PCA主成分分析及t-sne降维。
特征降维
特征降维方法,包括有监督降维和无监督降维两种方法,一般来说,很多时候默认为无监督降维。
无监督降维,即在降维过程中不适用任何标签信息(回归中的目标值、分类中的类别),仅输入特征矩阵X,根据数据特征的特点来降维。主要是为了保留数据固有结构、方差、可视化等,下面是常用的方法及其目标:
- PCA:保留数据中方差大的作为主成分
- t-SNE,UMAP,Isomap:保留数据的局部或全局流形结构
- Autoencoder:能够有效重构原始数据的紧凑表示
- ICA:统计上的独立成分
有监督降维,即在降维过程中使用标签信息,通过输入特征矩阵X和标签向量y,找到一个使得不同类别的数据点能被更好地分开的低维子空间。主要是为了提高后续监督学习的性能。
- LDA:最大化不同类别之间的可分性,同时最小化同一类别内部的离散度
-
找到对预测目标变量 y 最有信息量的特征组合
举个例子,PCA(无监督)就像是按身高排队,不考虑这些人会做什么。只要使得相邻的人身高接近就行(总体的差异化),但是兴趣爱好可能完全不同。而LDA(有监督)就像是按运动项目分组,目的是为了参加特定比赛。所以最终的结果是同一组的人适合相同的项目(适合某运动的技能点突出)。
PCA
PCA(Principal Component Analysis,主成分分析),即将一组可能存在相关性的变量,通过线性变换,转换成一组线性不相关的变量(方差最大,称为“主成分”),同时保留数据中最重要的特征,并舍弃次要的、冗余的特征。
对于PCA的计算过程,理论上,主要有以下几个步骤:特征中心化、协方差矩阵计算、特征分解、主成分选择及数据转换。实际上,更常用的方法是使用SVD进行PCA计算。
- 特征中心化,得到矩阵X
- 对矩阵X进行奇异值分解:X=UΣ
- 右奇异向量V为主成分方向
- 奇异值评估主成分的重要性(解释方差)
- 降维后的数据:Xpca=XVk(Vk 是 V 的前k列)
下面是用代码来实现上述过程:
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, confusion_matrix
import time
import numpy as np # 确保numpy导入
#根据累积解释方差确定主成分数量
# 步骤 1: 特征缩放
scaler_pca = StandardScaler() #标准化,方差受特征尺度的影响
X_train_scaled_pca = scaler_pca.fit_transform(X_train) #训练并转换训练集
X_test_scaled_pca = scaler_pca.transform(X_test) # 使用在训练集上fit的scaler,转换测试集
# 步骤 2: PCA降维
# 选择降到10维,或者你可以根据累积解释方差来选择,例如:
pca_expl = PCA(random_state=42) #实例化
pca_expl.fit(X_train_scaled_pca) #训练
#计算累积解释方差
#pca_expl.explained_variance_ratio_: 每个主成分所能解释的方差占总方差的百分比
cumsum_variance = np.cumsum(pca_expl.explained_variance_ratio_) #np.cumsum()计算累积和
#确定主成分的数量,保证捕获原数据中95%的方差
n_components_to_keep_95_var = np.argmax(cumsum_variance >= 0.95) + 1 #返回索引,但主成分数量从1开始
print(f"为了保留95%的方差,需要的主成分数量: {n_components_to_keep_95_var}") #26个
对于PCA降维,它适合以下场景:
- 数据可视化与探索:比如在之前的聚类算法中使用PCA降维到2维进行可视化
- 多重共线性:特征高度相关,PCA生产的新特征是彼此正交(不相关)
- 多维数据但样本有限
- 数据存储与压缩、降噪声
但是,在以下场景中,不适合使用PCA方法:
- 关键信息存在于低方差维度中:PCA默认‘高方差=高信息量’
- 非线性数据:如流形、螺旋形等;PCA是线性变换,此时选用t-SNE等非线性降维
- 解释特征重要性:PCA是原始特征的线性组合
- 特征尺度不一致:PCA处理前需要标准化
t-SNE
t-SNE(T-distributed stochastic neighbor embedding,t-分布随机邻域嵌入),即将高维空间中的数据点之间的“相似性关系”,尽可能忠实地在低维空间(通常是2D或3D)中保留下来。
具体来说,就是让“在高维空间里靠近的点,在低维空间里也靠近;在高维空间里远离的点,在低维空间里也远离”。属于非线性降维方法,关注局部邻域结构,主要用于高维数据可视化。
在使用t-SNE进行降维时,要关注以下几点:
- 计算成本高昂:计算成对相似度,复杂度为 O(n2),不适合大型数据集(>10000个样本)。一般先使用PCA降维至适中的维度,再使用t-SNE。
- 仅用于可视化:结果不能用于下游任务!低维坐标是随机的,没有物理意义。不能对新的测试数据进行变换(没有
.transform()方法)。通常不直接用于提高分类器性能的降维,LDA或PCA(某些情况下)更适合。 - 结果具有随机性:每次运行都会产生略有不同的结果(需设置
random_state复现)。簇的大小和密度在 t-SNE 图中没有直接意义。点之间的距离在全局上没有意义 - 对超参数敏感:不同参数可能导致完全不同的解释。
perplexity(困惑度)对结果影响巨大,需要尝试不同值。困惑大小与邻域大小成正比。此外,还有learning_rate(学习率)、max_iter(迭代次数)。 - 只保留局部结构:可能会扭曲全局结构。例如,集群之间的距离和大小没有意义。
下面是代码:
from sklearn.manifold import TSNE
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, confusion_matrix
import time
import numpy as np
import matplotlib.pyplot as plt # 用于可选的可视化
import seaborn as sns # 用于可选的可视化
# 确保 X_train, X_test 是DataFrame或Numpy Array
# 简单测试t-SNE降维并重新训练模型
print(" 标准 t-SNE 主要用于可视化,直接用于分类器输入可能效果不佳。")
# 步骤 1: 特征缩放
scaler_tsne = StandardScaler()
X_train_scaled_tsne = scaler_tsne.fit_transform(X_train)
X_test_scaled_tsne = scaler_tsne.transform(X_test) # 使用在训练集上fit的scaler
# 步骤 2: t-SNE 降维
# 我们将降维到与PCA相同的维度(例如10维)或者一个适合分类的较低维度。
# t-SNE通常用于2D/3D可视化,但也可以降到更高维度。
# 然而,降到与PCA一样的维度(比如10维)对于t-SNE来说可能不是其优势所在,
# 并且计算成本会显著增加,因为高维t-SNE的优化更困难。
# 为了与PCA的 n_components=10 对比,我们这里也尝试降到10维。
# 但请注意,这可能非常耗时,且效果不一定好。
# 通常如果用t-SNE做分类的预处理(不常见),可能会选择非常低的维度(如2或3)。
# n_components_tsne = 10 # 与PCA的例子保持一致,但计算量会很大
n_components_tsne = 2 # 更典型的t-SNE用于分类的维度,如果想快速看到结果
# 如果你想严格对比PCA的10维,可以将这里改为10,但会很慢
# 对训练集进行 fit_transform
tsne_model_train = TSNE(n_components=n_components_tsne,
perplexity=30, # 常用的困惑度值
max_iter=1000, # 足够的迭代次数
init='pca', # 使用PCA初始化,通常更稳定
learning_rate='auto', # 自动学习率 (sklearn >= 1.2)
random_state=42, # 保证结果可复现
n_jobs=-1) # 使用所有CPU核心
print("正在对训练集进行 t-SNE fit_transform...")
start_tsne_fit_train = time.time()
X_train_tsne = tsne_model_train.fit_transform(X_train_scaled_tsne)
end_tsne_fit_train = time.time()
print(f"训练集 t-SNE fit_transform 完成,耗时: {end_tsne_fit_train - start_tsne_fit_train:.2f} 秒")
# 对测试集进行 fit_transform
# 再次强调:这是独立于训练集的变换,由于t-SNE本身没有transform方法
tsne_model_test = TSNE(n_components=n_components_tsne,
perplexity=30,
max_iter=1000,
init='pca',
learning_rate='auto',
random_state=42, # 保持参数一致,但数据不同,结果也不同
n_jobs=-1)
print("正在对测试集进行 t-SNE fit_transform...")
start_tsne_fit_test = time.time()
X_test_tsne = tsne_model_test.fit_transform(X_test_scaled_tsne) # 注意这里是 X_test_scaled_tsne
end_tsne_fit_test = time.time()
print(f"测试集 t-SNE fit_transform 完成,耗时: {end_tsne_fit_test - start_tsne_fit_test:.2f} 秒")
print(f"t-SNE降维后,训练集形状: {X_train_tsne.shape}, 测试集形状: {X_test_tsne.shape}")
start_time_tsne_rf = time.time()
# 步骤 3: 训练随机森林分类器
rf_model_tsne = RandomForestClassifier(random_state=42)
rf_model_tsne.fit(X_train_tsne, y_train)
# 步骤 4: 在测试集上预测
rf_pred_tsne_manual = rf_model_tsne.predict(X_test_tsne)
end_time_tsne_rf = time.time()
print(f"t-SNE降维数据上,随机森林训练与预测耗时: {end_time_tsne_rf - start_time_tsne_rf:.4f} 秒")
total_tsne_time = (end_tsne_fit_train - start_tsne_fit_train) + \
(end_tsne_fit_test - start_tsne_fit_test) + \
(end_time_tsne_rf - start_time_tsne_rf)
print(f"t-SNE 总耗时 (包括两次fit_transform和RF): {total_tsne_time:.2f} 秒")
print("\n手动 t-SNE + 随机森林 在测试集上的分类报告:")
print(classification_report(y_test, rf_pred_tsne_manual))
print("手动 t-SNE + 随机森林 在测试集上的混淆矩阵:")
print(confusion_matrix(y_test, rf_pred_tsne_manual))
LDA
LDA(Linear Discriminant Analysis,即线性判别分析),找到一个线性组合的轴(投影平面),将高维数据投影到低维空间,同时满足类间方差最小化和类内方差最小化。换句话说,就是找一个方向,让数据在这个方向上投影后,“不同类的点离得越远越好,同类的点聚得越紧越好”。
虽然与PCA都是线性降维方法,但不同的是LDA属于有监督降维,明确使用标签y来指导学习过程,最终寻找到能最好区分不同类别的方向。
下面是使用LDA时的注意点:
- 降维目标维度存在上限:min(n_features, n_classes - 1)。n_class - 1
:如果有K个类别,最多只需要K-1维空间,就可以将这K个点(各类别的均值点)完美地放置并区分开来。如对于二分类,只需要一条线(1维)就可分开两个点。n_features:因为LDA是在原始特征空间里寻找投影方向,无法创造出比原始特征更多的线性无关的方向。 - 线性变换:寻找最佳分界线。对于二分类问题,LDA找到的是一条直线(判别轴),当所有数据点投影到这条直线上时,两个类别的重叠部分最小。对于多分类问题,LDA找到的是一组相互正交的判别轴(类似于PCA的主成分),共同张成一个低维的“判别子空间”。
- 数据假设:理论上,LDA 假设每个类别的数据服从多元高斯分布,所有类别具有相同的协方差矩阵。在实践中,即使这些假设不完全满足,LDA 通常也能表现良好,尤其是在类别大致呈椭球状分布且大小相似时。
- 标签y不需要独热编码:保证为一维数组或Series类型。标签的类别数量直接决定降维上限。
关于LDA的适用场景:
- 当目标是提高后续分类模型的性能时,LDA 是一个强有力的降维工具。
- 当类别信息已知且被认为是区分数据的主要因素时。
- 当希望获得具有良好类别区分性的低维表示时,尤其可用于数据可视化(如果能降到2D或3D)。
关于LDA实现的代码:
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.preprocessing import StandardScaler
import time
import numpy as np
#import matplotlib.pyplot as plt
#from mpl_toolkits.mplot3d import Axes3D # 如果需要3D绘图
#import seaborn as sns
# LDA降维
# 代码看上去很多,主要是为了确保降维目标限度是合理的(if判断语句)
# 步骤 1: 特征缩放
scaler_lda = StandardScaler()
X_train_scaled_lda = scaler_lda.fit_transform(X_train)
X_test_scaled_lda = scaler_lda.transform(X_test) # 使用在训练集上fit的scaler
# 步骤 2: LDA 降维,得到特征数与类别数
n_features = X_train_scaled_lda.shape[1] #原始特征数量
if hasattr(y_train, 'nunique'): #hasattr()是否有该属性,用于判断y_train的类型
n_classes = y_train.nunique() #y_train为pandas Series
elif isinstance(y_train, np.ndarray):
n_classes = len(np.unique(y_train)) #y_train为numpy数组
else:
n_classes = len(set(y_train)) #其他情况(如列表)
max_lda_components = min(n_features, n_classes - 1) #得到最大可能降维维度
# 设置目标降维维度,检查LDA适用性
n_components_lda_target = 10
if max_lda_components < 1:
print(f"LDA 不适用,因为类别数 ({n_classes})太少,无法产生至少1个判别组件。")
X_train_lda = X_train_scaled_lda.copy() # 使用缩放后的原始特征
X_test_lda = X_test_scaled_lda.copy() # 使用缩放后的原始特征
actual_n_components_lda = n_features
print("将使用缩放后的原始特征进行后续操作。")
else:
# 实际使用的组件数不能超过LDA的上限,也不能超过我们的目标(如果目标更小)
actual_n_components_lda = min(n_components_lda_target, max_lda_components)
if actual_n_components_lda < 1: # 这种情况理论上不会发生,因为上面已经检查了 max_lda_components < 1
#额外安全检查
print(f"计算得到的实际LDA组件数 ({actual_n_components_lda}) 小于1,LDA不适用。")
X_train_lda = X_train_scaled_lda.copy()
X_test_lda = X_test_scaled_lda.copy()
actual_n_components_lda = n_features
print("将使用缩放后的原始特征进行后续操作。")
else:
#正常执行LDA
print(f"原始特征数: {n_features}, 类别数: {n_classes}")
print(f"LDA 最多可降至 {max_lda_components} 维。")
print(f"目标降维维度: {n_components_lda_target} 维。")
print(f"本次 LDA 将实际降至 {actual_n_components_lda} 维。")
lda_manual = LinearDiscriminantAnalysis(n_components=actual_n_components_lda, solver='svd')
X_train_lda = lda_manual.fit_transform(X_train_scaled_lda, y_train) #有监督,需要标签
X_test_lda = lda_manual.transform(X_test_scaled_lda)
print(f"LDA降维后,训练集形状: {X_train_lda.shape}, 测试集形状: {X_test_lda.shape}")
下面是三种特征降维方法的简单对比:
| 特性 | PCA (找视角) | t-SNE (画地图) | LDA (找分界线) |
|---|---|---|---|
| 核心目标 | 保留数据最大方差,最大化信息 | 保留局部结构与相似性,可视化 | 最大化类间区分度 |
| 学习方式 | 无监督 | 无监督 | 有监督 |
| 输入需求 | 特征 (X) | 特征 (X) | 特征 (X) 和 标签 (y) |
| 降维维度 | 无理论限制 (≤ n_features) | 无理论限制 (通常降至2/3维) | 有严格上限:≤ min(n_features, n_classes-1) |
| 输出特性 | 线性变换,成分可解释 | 非线性变换,成分不可解释 | 线性变换,成分可解释 |
| 保留信息 | 全局结构 | 局部结构(近邻关系) | 判别信息(分类边界) |
| 主要应用 | 数据压缩、去噪、探索性分析 | 高维数据可视化(如细胞分群) | 分类任务前的特征提取、数据可视化 |
| 优点 | 计算高效、结果稳定、全局特征 | 对复杂流形结构效果好、可视化直观 | 专为分类优化、降维后特征判别力强 |
| 缺点 | 对非线性关系无效、忽略类别信息 | 计算慢、结果不稳定、难以解释 | 需标签、依赖线性假设、受维度上限限制 |

被折叠的 条评论
为什么被折叠?



