python打卡day21

发布于:2025-05-11 ⋅ 阅读:(6) ⋅ 点赞:(0)
常见的降维算法

知识点回顾:

  1. LDA线性判别
  2. PCA主成分分析
  3. t-sne降维

之前学了特征降维的两个思路,特征筛选(如树模型重要性、方差筛选)和特征组合(如SVD/PCA)。

现在引入特征降维的另一种分类:无/有监督降维。无监督降维只需要特征数据本身,在降维过程中不使用任何关于数据样本的标签信息(比如类别标签、目标值等),仅仅根据数据点本身的分布、方差、相关性、局部结构等特性来寻找低维表示,典型算法是PCA、t-SNE等

相应的,降维时使用标签信息的有监督降维,典型算法就是LDA(特征筛选可以是无监督或有监督,取决于是否使用标签,比如之前特征筛选的学习中除了方差筛选其他都是有监督降维)

1.PCA(主成分分析)

PCA这种无监督降维方法的目标是保留数据的最大方差(即主成分),将数据投影到由这些最重要的主成分构成的新的、维度更低子空间上,这些方差大的方向不一定是对分类最有用的方向。PCA本质上就是在SVD之前对数据进行了均值中心化,均值中心化就是把每个特征"挪到原点附近"的操作,比如:

都提到了最大方差,那PCA和方差筛选有什么不一样呢?假设有两个高度相关的特征:

  • 方差筛选可能只保留其中一个,每个特征来计算独立的方差
  • PCA会生成一个融合两者的主成分,操作对象是特征的线性组合

所以PCA是一种线性降维,对于数据结构高度非线性(例如“瑞士卷”、“S型曲线”),PCA会将其投影到一个线性子空间,这可能会丢失关键的非线性关系,在这种情况下,非线性降维技术(如 t-SNE, UMAP, LLE, Isomap, 核PCA, 自编码器)会是更好的选择

降维维度可以根据解释方差来选择:

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导入

# 假设 X_train, X_test, y_train, y_test 已经准备好了

# 步骤 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降维
pca_expl = PCA(random_state=42) # 创建PCA对象(暂不指定降维维度)
pca_expl.fit(X_train_scaled_pca) # 在标准化后的数据上拟合PCA模型
cumsum_variance = np.cumsum(pca_expl.explained_variance_ratio_) # 计算累计解释方差比例
n_components_to_keep_95_var = np.argmax(cumsum_variance >= 0.95) + 1 # 找到第一个使累计解释方差≥95%的维度
print(f"为了保留95%的方差,需要的主成分数量: {n_components_to_keep_95_var}")

# ----------- 打印结果 -----------
为了保留95%的方差,需要的主成分数量: 26

也可以自己手动指定降维维度,这里以降到10个特征为例:

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导入

# 假设 X_train, X_test, y_train, y_test 已经准备好了

# 步骤 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降维
n_components_pca = 10
pca_manual = PCA(n_components=n_components_pca, random_state=42) # 现在创建PCA对象时,指定降维维度为10

X_train_pca = pca_manual.fit_transform(X_train_scaled_pca)
X_test_pca = pca_manual.transform(X_test_scaled_pca) # 使用在训练集上fit的pca

print(f"PCA降维后,训练集形状: {X_train_pca.shape}, 测试集形状: {X_test_pca.shape}")
start_time_pca_manual = time.time()
# 步骤 3: 训练随机森林分类器
rf_model_pca = RandomForestClassifier(random_state=42)
rf_model_pca.fit(X_train_pca, y_train)

# 步骤 4: 在测试集上预测
rf_pred_pca_manual = rf_model_pca.predict(X_test_pca)
end_time_pca_manual = time.time()

print(f"手动PCA降维后,训练与预测耗时: {end_time_pca_manual - start_time_pca_manual:.4f} 秒")

print("\n手动 PCA + 随机森林 在测试集上的分类报告:")
print(classification_report(y_test, rf_pred_pca_manual))
print("手动 PCA + 随机森林 在测试集上的混淆矩阵:")
print(confusion_matrix(y_test, rf_pred_pca_manual))

2.t-SNE(t-分布随机邻域嵌入)

PCA 的目标是保留数据的全局方差,而 t-SNE 的核心目标是在高维空间中相似的数据点,在降维后的低维空间中也应该保持相似(即彼此靠近),而不相似的点则应该相距较远。特别擅长于将高维数据集投影到二维或三维空间进行可视化,从而揭示数据中的簇结构或流形结构

总的来说,t-SNE是一种适合非线性数据可视化的降维算法,而非数据处理中普适的降维;同时PCA同样适合聚类前的可视化,但PCA更适合一般数据预处理

  • Perplexity (困惑度):这个参数对结果影响较大。常见的取值范围是 5 到 50。较小的困惑度关注非常局部的结构,较大的困惑度则考虑更广泛的邻域。通常需要尝试不同的值
  • n_iter (迭代次数):需要足够的迭代次数让算法收敛。默认值通常是1000。如果可视化结果看起来还不稳定,可以尝试增加迭代次数
  • learning_rate (学习率):也可能影响收敛
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 # 用于可选的可视化

# 步骤 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 降维
n_components_tsne = 2    # 更典型的t-SNE用于分类的维度,如果想快速看到结果
                         # 如果你想严格对比PCA的10维,可以将这里改为10,但会很慢
# 对训练集进行 fit_transform
tsne_model_train = TSNE(n_components=n_components_tsne,
                        perplexity=30,    # 常用的困惑度值
                        n_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
# 再次强调:这是独立于训练集的变换
tsne_model_test = TSNE(n_components=n_components_tsne,
                       perplexity=30,
                       n_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))

3.LDA(线性判别)

在分类任务中,LDA通常比PCA更直接有效,会更注重类别信息,不像PCA选择方差大的方向,而是会选择能让不同类别的点尽量分开的方向,即使这个方向数据整体方差不是最大的

from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
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
from mpl_toolkits.mplot3d import Axes3D # 如果需要3D绘图
import seaborn as sns


# 步骤 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'):
    n_classes = y_train.nunique()
elif isinstance(y_train, np.ndarray):
    n_classes = len(np.unique(y_train))
else:
    n_classes = len(set(y_train))

max_lda_components = min(n_features, n_classes - 1)

# 设置目标降维维度
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:
        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}")

start_time_lda_rf = time.time()
# 步骤 3: 训练随机森林分类器
rf_model_lda = RandomForestClassifier(random_state=42)
rf_model_lda.fit(X_train_lda, y_train)

# 步骤 4: 在测试集上预测
rf_pred_lda_manual = rf_model_lda.predict(X_test_lda)
end_time_lda_rf = time.time()

print(f"LDA降维数据上,随机森林训练与预测耗时: {end_time_lda_rf - start_time_lda_rf:.4f} 秒")

print("\n手动 LDA + 随机森林 在测试集上的分类报告:")
print(classification_report(y_test, rf_pred_lda_manual))
print("手动 LDA + 随机森林 在测试集上的混淆矩阵:")
print(confusion_matrix(y_test, rf_pred_lda_manual))

收获心得:

讲实话关于特征降维这一两节内容学的有点麻,以后再认真看看

@浙大疏锦行


网站公告

今日签到

点亮在社区的每一天
去签到