DAY 21 常见降维算法

# 先运行之前预处理好的代码
import pandas as pd    #用于数据处理和分析,可处理表格数据。
import numpy as np     #用于数值计算,提供了高效的数组操作。
import matplotlib.pyplot as plt    #用于绘制各种类型的图表
import seaborn as sns   #基于matplotlib的高级绘图库,能绘制更美观的统计图形。
import warnings
warnings.filterwarnings("ignore")
 
 # 设置中文字体(解决中文显示问题)
plt.rcParams['font.sans-serif'] = ['SimHei']  # Windows系统常用黑体字体
plt.rcParams['axes.unicode_minus'] = False    # 正常显示负号
data = pd.read_csv('data.csv')    #读取数据


# 先筛选字符串变量 
discrete_features = data.select_dtypes(include=['object']).columns.tolist()
# Home Ownership 标签编码
home_ownership_mapping = {
    'Own Home': 1,
    'Rent': 2,
    'Have Mortgage': 3,
    'Home Mortgage': 4
}
data['Home Ownership'] = data['Home Ownership'].map(home_ownership_mapping)

# Years in current job 标签编码
years_in_job_mapping = {
    '< 1 year': 1,
    '1 year': 2,
    '2 years': 3,
    '3 years': 4,
    '4 years': 5,
    '5 years': 6,
    '6 years': 7,
    '7 years': 8,
    '8 years': 9,
    '9 years': 10,
    '10+ years': 11
}
data['Years in current job'] = data['Years in current job'].map(years_in_job_mapping)

# Purpose 独热编码,记得需要将bool类型转换为数值
data = pd.get_dummies(data, columns=['Purpose'])
data2 = pd.read_csv("data.csv") # 重新读取数据,用来做列名对比
list_final = [] # 新建一个空列表,用于存放独热编码后新增的特征名
for i in data.columns:
    if i not in data2.columns:
       list_final.append(i) # 这里打印出来的就是独热编码后的特征名
for i in list_final:
    data[i] = data[i].astype(int) # 这里的i就是独热编码后的特征名



# Term 0 - 1 映射
term_mapping = {
    'Short Term': 0,
    'Long Term': 1
}
data['Term'] = data['Term'].map(term_mapping)
data.rename(columns={'Term': 'Long Term'}, inplace=True) # 重命名列
continuous_features = data.select_dtypes(include=['int64', 'float64']).columns.tolist()  #把筛选出来的列名转换成列表
 
 # 连续特征用中位数补全
for feature in continuous_features:     
    mode_value = data[feature].mode()[0]            #获取该列的众数。
    data[feature].fillna(mode_value, inplace=True)          #用众数填充该列的缺失值,inplace=True表示直接在原数据上修改。

# 最开始也说了 很多调参函数自带交叉验证,甚至是必选的参数,你如果想要不交叉反而实现起来会麻烦很多
# 所以这里我们还是只划分一次数据集

data.drop(columns=['Id'], inplace=True) # 删除 Loan ID 列
data.info() # 查看数据集的信息,包括数据类型和缺失值情况

输出:

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 7500 entries, 0 to 7499
Data columns (total 31 columns):
 #   Column                        Non-Null Count  Dtype  
---  ------                        --------------  -----  
 0   Home Ownership                7500 non-null   int64  
 1   Annual Income                 7500 non-null   float64
 2   Years in current job          7500 non-null   float64
 3   Tax Liens                     7500 non-null   float64
 4   Number of Open Accounts       7500 non-null   float64
 5   Years of Credit History       7500 non-null   float64
 6   Maximum Open Credit           7500 non-null   float64
 7   Number of Credit Problems     7500 non-null   float64
 8   Months since last delinquent  7500 non-null   float64
 9   Bankruptcies                  7500 non-null   float64
 10  Long Term                     7500 non-null   int64  
 11  Current Loan Amount           7500 non-null   float64
 12  Current Credit Balance        7500 non-null   float64
 13  Monthly Debt                  7500 non-null   float64
 14  Credit Score                  7500 non-null   float64
 15  Credit Default                7500 non-null   int64  
 16  Purpose_business loan         7500 non-null   int64  
 17  Purpose_buy a car             7500 non-null   int64  
 18  Purpose_buy house             7500 non-null   int64  
 19  Purpose_debt consolidation    7500 non-null   int64  
 20  Purpose_educational expenses  7500 non-null   int64  
 21  Purpose_home improvements     7500 non-null   int64  
 22  Purpose_major purchase        7500 non-null   int64  
 23  Purpose_medical bills         7500 non-null   int64  
 24  Purpose_moving                7500 non-null   int64  
 25  Purpose_other                 7500 non-null   int64  
 26  Purpose_renewable energy      7500 non-null   int64  
 27  Purpose_small business        7500 non-null   int64  
 28  Purpose_take a trip           7500 non-null   int64  
 29  Purpose_vacation              7500 non-null   int64  
 30  Purpose_wedding               7500 non-null   int64  
dtypes: float64(13), int64(18)
memory usage: 1.8 MB

降维前有31个特征。

一、特征降维概述

1、有监督降维和无监督降维

        通常情况下,我们提到特征降维,很多时候默认指的是无监督降维,这种方法只需要特征数据本身。但是实际上还包含一种有监督的方法。

  1. 无监督降维 (Unsupervised Dimensionality Reduction)

    • 定义:这类算法在降维过程中不使用任何关于数据样本的标签信息(比如类别标签、目标值等)。它们仅仅根据数据点本身的分布、方差、相关性、局部结构等特性来寻找低维表示。

    • 输入:只有特征矩阵 X

    • 目标
      • 保留数据中尽可能多的方差(如 PCA)。

      • 保留数据的局部或全局流形结构(如 LLE, Isomap, t-SNE, UMAP)。

      • 找到能够有效重构原始数据的紧凑表示(如 Autoencoder)。

      • 找到统计上独立的成分(如 ICA)。

    • 典型算法
      • PCA (主成分分析) / SVD (奇异值分解)

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

      • UMAP(均匀流形近似和投影)

      • LLE (局部线性嵌入)

      • Isomap (等轴测映射)

      • Autoencoders (基本形式)

      • ICA (独立成分分析)

    • “只需要特征就可以对特征降维了”:你这句话描述的就是无监督降维。算法通过分析特征间的关系和分布来进行降维。

  2. 有监督降维 (Supervised Dimensionality Reduction)

    • 定义:这类算法在降维过程中会利用数据样本的标签信息(通常是类别标签 y )。它们的目标是找到一个低维子空间,在这个子空间中,不同类别的数据点能够被更好地分离开,或者说,这个低维表示更有利于后续的分类(或回归)任务。

    • 输入:特征矩阵 X  对应的标签向量

    • 目标
      • 最大化不同类别之间的可分性,同时最小化同一类别内部的离散度(如 LDA)。

      • 找到对预测目标变量 最有信息量的特征组合。y

    • 典型算法
      • LDA (Linear Discriminant Analysis):这是最经典的监督降维算法。它寻找的投影方向能够最大化类间散度与类内散度之比。

      • 还有一些其他的,比如 NCA (Neighborhood Components Analysis),但 LDA 是最主要的代表。

    • “还需要有分类标签么”:是的,对于有监督降维,分类标签(或其他形式的监督信号)是必需的。

核心差异总结:

特性无监督降维 (Unsupervised DR)有监督降维 (Supervised DR)
是否使用标签 (只使用特征X) (使用特征 X和标签 y)
主要目的保留数据固有结构、方差、可视化等提高后续监督学习任务(如分类)的性能
关注点数据本身的内在属性数据类别间的可分性/与目标变量的相关性
典型例子PCA、SVD、t-SNE、UMAP、自动编码器LDA

举个例子来说明:

  • PCA (无监督):如果你有一堆人脸图片,PCA会尝试找到那些能最好地概括所有人脸变化的“主脸”(特征向量),比如脸型、鼻子大小等,它不关心这些人脸属于谁。

  • LDA (有监督):如果你有一堆人脸图片,并且你知道每张图片属于哪个人(标签)。LDA会尝试找到那些能最好地区分不同人的人脸特征组合。比如,如果A和B的脸型很像,但眼睛差别很大,LDA可能会更强调眼睛的特征,即使脸型方差更大。PCA是利用最大化方差来实现无监督降维,而LDA则是在此基础上,加入了类别信息,其优化目标就变成了类间差异最大化和类内差异最小化。

        PCA等无监督降维方法的目标是保留数据的最大方差,这些方差大的方向不一定是对分类最有用的方向。因此,在分类任务中,LDA通常比PCA更直接有效。

默认参数随机森林模型(与后续降维后的模型进行对比)

# 划分特征和标签
X = data.drop(['Credit Default'], axis=1)
y = data['Credit Default']

# 划分训练集和测试集
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 训练默认参数随机森林
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, confusion_matrix
import time
import warnings
warnings.filterwarnings("ignore")

# --- 1. 默认参数随机森林 ---
print("--- 默认参数随机森林 ---")
start_time = time.time()
rf_model = RandomForestClassifier(random_state=42)
rf_model.fit(X_train, y_train)
rf_pred = rf_model.predict(X_test)
end_time = time.time()

print(f"训练与预测耗时:{end_time - start_time:.4f}秒。")
print("默认随机森林在测试集上的分类报告:")
print(classification_report(y_test, rf_pred))
print("默认随机森林在测试集上的混淆矩阵:")
print(confusion_matrix(y_test, rf_pred))

输出:

--- 1. 默认参数随机森林 ---
训练与预测耗时:1.3695秒。
默认随机森林在测试集上的分类报告:
              precision    recall  f1-score   support

           0       0.77      0.96      0.85      1059
           1       0.76      0.30      0.43       441

    accuracy                           0.77      1500
   macro avg       0.77      0.63      0.64      1500
weighted avg       0.77      0.77      0.73      1500

默认随机森林在测试集上的混淆矩阵:
[[1018   41]
 [ 309  132]]

# # umap-learn 是一个用于降维和可视化的库,特别适合处理高维数据。它使用了一种基于流形学习的算法,可以有效地将高维数据嵌入到低维空间中,同时保持数据的局部结构。
# !pip install umap-learn -i https://pypi.tuna.tsinghua.edu.cn/simple

二、特征降维应用

1、无监督降维

1. 主成分分析 (PCA)

在昨天的专题中已经理解了SVD(奇异值分解),那么理解PCA(主成分分析)就会非常直接。实际上,PCA可以被看作是将SVD应用于经过均值中心化的数据矩阵,并对其结果进行特定解释的一种方法。

PCA:寻找最大方差方向

主成分分析 (PCA) 的核心思想是识别数据中方差最大的方向(即主成分)。然后,它将数据投影到由这些最重要的主成分构成的新的、维度更低子空间上。这样做的目的是在降低数据维度的同时,尽可能多地保留原始数据中的“信息”(通过方差来衡量)。新的特征(主成分)是原始特征的线性组合,并且它们之间是正交的(不相关)。

PCA 与 SVD 的关系

假设你的数据矩阵是 X(行是样本,列是特征)。

  1. 步骤 0:均值中心化 (对PCA的解释至关重要)
    • 对于 中的每一个特征(列),计算其均值。

    • 从该特征列的所有值中减去这个均值。我们将这个经过均值中心化处理的矩阵称为 X_centered。

    • 为什么要做这一步?PCA的目标是找到围绕数据均值的最大方差方向。SVD本身不强制要求均值中心化,但为了将其分解结果解释为PCA的主成分,这一步是必需的。

PCA 何时适用?数据是线性还是非线性?

  • 线性性

    • PCA 是一种线性降维方法。它假设主成分是原始特征的线性组合。

    • 它寻找的是一个能够最好地捕捉数据方差的线性子空间

    • 如果你的数据的潜在结构是高度非线性的(例如,“瑞士卷”形状、螺旋形),PCA可能无法有效地在低维空间中捕捉这种结构。它可能会将这类结构“压平”或扭曲。

  • PCA 效果好的情况

    • 目标是最大化方差: 当你认为数据中方差最大的方向包含了最重要的信息时。这在去噪或特征间存在相关性时通常是成立的。

    • 数据分布大致呈椭球形或存在线性相关性: PCA 擅长找到这类分布的主轴。

    • 作为其他线性模型的预处理步骤: 不相关的主成分有时能让线性模型(如逻辑回归、线性SVM)表现更好。

    • 探索性数据分析 (EDA): 快速了解数据变异的主要模式。

    • 降噪: 假设噪声的方差低于信号的方差,PCA可以通过舍弃低方差的成分来帮助降噪。

    • 当原始特征数量非常多,且存在多重共线性时:PCA可以通过生成少数几个不相关的主成分来解决多重共线性问题,并减少特征数量。

  • PCA 可能不适用或需要谨慎使用的情况

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

    • 方差并非衡量重要性的唯一标准: 有时,方差较小的方向可能对特定任务至关重要(例如,在分类问题中,如果使用LDA,一个整体方差较小的方向可能对区分类别非常有效)。PCA是无监督的,它不考虑类别标签。

    • 主成分的可解释性: 虽然主成分是原始特征的线性组合,但与保留原始、可解释的特征相比,它们的直接物理解释有时可能更具挑战性。

    • 数据特征尺度差异巨大: 如果特征的尺度(单位或数值范围)相差悬殊(例如,一个特征以米为单位,另一个以毫米为单位),那么尺度较大的特征将在方差计算中占据主导地位,从而主导第一主成分。这就是为什么在应用PCA之前几乎总是推荐进行数据标准化(例如,将特征缩放到均值为0,方差为1)

总而言之,可以将PCA视为:

  1. 对数据进行均值中心化。

  2. 对中心化后的数据进行SVD。

  3. 使用SVD得到的右奇异向量 V 作为主成分方向。

  4. 使用奇异值 S 来评估每个主成分的重要性(解释的方差)。

  5. 使用 U*S(或 )X_centered * V 来获得降维后的数据表示。

PCA主要适用于那些你认为最重要的信息可以通过数据方差来捕获,并且数据结构主要是线性的情况。

import time  # 记录时间
import numpy as np # 确保numpy导入
from sklearn.preprocessing import StandardScaler  # 特征缩放,标准化
from sklearn.decomposition import PCA  # 主成分分析
from sklearn.ensemble import RandomForestClassifier  # 随机森林分类器
from sklearn.metrics import classification_report, confusion_matrix  # 分类报告和混淆矩阵

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

print("\n--- 2. PCA 降维 + 随机森林 (不使用 Pipeline) ---")

# 步骤 1:特征缩放
scaler_pca = StandardScaler()
X_train_scaler_pca = scaler_pca.fit_transform(X_train)
X_test_scaler_pca = scaler_pca.transform(X_test)

# 步骤 2:PCA降维
# 选择降到10维,或者你可以根据解释方差来选择,例如:
pca_expl = PCA(random_state=42)
pca_expl.fit(X_train_scaler_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}")

输出:

--- 2. PCA 降维 + 随机森林 (不使用 Pipeline) ---
为了保留95%的方差,需要的主成分数量:26

详细说明: 

  • PCA初始化 : random_state=42 保证每次运行结果一致,这对算法可复现性非常重要。
  • 模型拟合 :PCA算法会自动计算主成分方向和方差比例,这一步会得到各主成分的 explained_variance_ratio_。
  •  累计方差计算 :使用 np.cumsum 将各主成分的方差比例累加,形成[0.2, 0.4, 0.6,...]这样的累计序列。
  • 阈值判断 : np.argmax(cumsum_variance >= 0.95) 找到第一个累计值≥95%的索引位置,+1是因为索引从0开始计数。

测试下 降低到10维的效果

# 我们测试下降低到10维的效果
n_components_pca = 10
pca_manual = PCA(n_components=n_components_pca, random_state=42)  # 初始化PCA模型,设置主成分数和随机种子

X_train_pca = pca_manual.fit_transform(X_train_scaler_pca)  # 标准化后的训练集上拟合并转换PCA
X_test_pca = pca_manual.transform(X_test_scaler_pca)  # 对测试集应用训练集的PCA转换
print(f"PCA降维后,训练集形状:{X_train_pca.shape}, 测试集形状:{X_test_pca.shape}")

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

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

print(f"手动PCA降维后,训练与预测耗时:{end_time_pca - start_time_pca:.4f}秒")
print("手动PCA降维后,随机森林在测试集上的分类报告:")
print(classification_report(y_test, rf_pred_pca))
print("手动PCA降维后,随机森林在测试集上的混淆矩阵:")
print(confusion_matrix(y_test, rf_pred_pca))

输出:

PCA降维后,训练集形状:(6000, 10), 测试集形状:(1500, 10)
手动PCA降维后,训练与预测耗时:2.5792秒
手动PCA降维后,随机森林在测试集上的分类报告:
              precision    recall  f1-score   support

           0       0.77      0.94      0.85      1059
           1       0.70      0.32      0.44       441

    accuracy                           0.76      1500
   macro avg       0.73      0.63      0.64      1500
weighted avg       0.75      0.76      0.73      1500

手动PCA降维后,随机森林在测试集上的混淆矩阵:
[[997  62]
 [298 143]]

特征数量从31个减少到了10个,但是最终结果变化不大,训练时间有所增加!!!

详细说明:

  1. PCA初始化 :

    • n_components=n_components_pca :指定要保留的主成分数量(这里设为10维)

    • random_state=42 :确保每次运行结果一致,这对实验可复现性至关重要

  2. 训练集转换 :

    • fit_transform() :同时完成模型拟合(计算主成分)和数据降维转换

    • 该方法会生成两个结果:
      • 训练好的PCA模型(保存在pca_manual对象中)

      • 降维后的训练集特征矩阵X_train_pca

  3. 测试集转换 :

    • transform() :仅使用训练阶段得到的参数(主成分方向)进行转换

    • 必须使用与训练集相同的PCA参数,避免数据泄漏(data leakage)

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

这是一种与PCA截然不同的降维算法,尤其在理解其核心思想和适用场景上。

t-SNE:保持高维数据的局部邻域结构,用于可视化

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

t-SNE 与 PCA/SVD 的主要差异:

特性主成分分析 (PCA)/SVDt-SNE
类型线性非线性
主要目标保留全局方差保留局部邻域结构,用于可视化
关注点全局结构局部结构,尽可能在低维呈现高维的邻近关系
计算成本相对较低较高,尤其是对于大数据集
输出稳定性确定性(给定数据,结果唯一)随机性(优化过程通常有随机初始化,多次运行结果可能略有不同)
超参数n_componentsperplexity (困惑度)n_iter(迭代次数),learning_rate
全局结构保留较好(因为它关注方差)可能较差,t-SNE 更关注保持局部结构,可能扭曲全局距离
降维后坐标含义主成分有明确的方差含义坐标本身没有直接的物理意义,点之间的相对距离和簇的形状更重要
适用场景通用降维,去噪,数据压缩,线性模型预处理高维数据可视化,探索数据中的簇或流形结构

何时适合使用 t-SNE?

  • 当你主要目的是可视化高维数据时:t-SNE 在将复杂的高维数据结构展现在2D或3D图上时非常强大,能帮助你直观地看到数据中可能存在的簇或模式。

  • 当数据具有复杂的非线性结构时:如果数据分布在一个弯曲的流形上,t-SNE 比 PCA 更能捕捉到这种结构。

  • 探索性数据分析:帮助发现数据中未知的群体。

使用 t-SNE 时需要注意的事项:

  • 计算成本高:对于非常大的数据集(例如几十万甚至上百万样本),t-SNE 的计算会非常慢。通常建议在应用 t-SNE 之前,先用 PCA 将数据降到一个适中的维度(例如50维),这样可以显著加速 t-SNE 的计算并可能改善结果。

  • 超参数敏感
    • Perplexity (困惑度):这个参数对结果影响较大。常见的取值范围是 5 到 50。较小的困惑度关注非常局部的结构,较大的困惑度则考虑更广泛的邻域。通常需要尝试不同的值。

    • n_iter (迭代次数):需要足够的迭代次数让算法收敛。默认值通常是1000。如果可视化结果看起来还不稳定,可以尝试增加迭代次数。

    • learning_rate (学习率):也可能影响收敛。

  • 结果的解释
    • 簇的大小和密度在 t-SNE 图中没有直接意义。t-SNE 会尝试将所有簇展开到相似的密度。不要根据簇在图上的大小来判断原始数据中簇的实际大小或密度。

    • 点之间的距离在全局上没有意义。两个相距较远的簇,它们之间的距离并不代表它们在原始高维空间中的实际距离。t-SNE 主要保留的是局部邻域关系。

    • 多次运行结果可能不同:由于优化过程的随机初始化和梯度下降的性质,多次运行 t-SNE 可能会得到略微不同的可视化结果。但好的簇结构通常是稳定的。

  • 不适合作为通用的有监督学习预处理步骤:因为它的目标是可视化和保持局部结构,而不是最大化类别可分性或保留全局方差,所以它通常不直接用于提高分类器性能的降维。LDA 或 PCA (在某些情况下) 更适合这个目的。

总结一下

t-SNE 是一种强大的非线性降维技术,主要用于高维数据的可视化。它通过在低维空间中保持高维空间中数据点之间的局部相似性(邻域关系)来工作。与PCA关注全局方差不同,t-SNE 更关注局部细节。理解它的超参数(尤其是困惑度)和结果的正确解读方式非常重要。

import time  # 记录时间
import numpy as np # 确保numpy导入
from pandas.core.common import random_state
from sklearn.preprocessing import StandardScaler  # 特征缩放,标准化
from sklearn.manifold import TSNE  
from sklearn.ensemble import RandomForestClassifier  # 随机森林分类器
from sklearn.metrics import classification_report, confusion_matrix  # 分类报告和混淆矩阵
import matplotlib.pyplot as plt
import seaborn as sns

# 假设 X_train, X_test, y_train, y_test 已经准备好了
# 并且你的 X_train, X_test 是DataFrame或Numpy Array

print(f"\n--- 3. 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,  # 常用的困惑度值
                    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
# 再次强调:这是独立于训练集的变换(与SVD、PCA不同)
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. t-SNE 降维 + 随机森林  ---
       标准 t-SNE 主要用于可视化,直接用于分类器输入可能效果不佳。
正在对训练集进行 t-SNE fit_transform...
训练集 t-SNE fit_transform 完成,耗时: 13.12 秒
正在对测试集进行 t-SNE fit_transform...
测试集 t-SNE fit_transform 完成,耗时: 3.04 秒
t-SNE降维后,训练集形状: (6000, 2), 测试集形状: (1500, 2)
t-SNE降维数据上,随机森林训练与预测耗时: 1.0249 秒
t-SNE 总耗时 (包括两次fit_transform和RF): 17.18 秒

手动 t-SNE + 随机森林 在测试集上的分类报告:
              precision    recall  f1-score   support

           0       0.70      0.89      0.78      1059
           1       0.25      0.09      0.14       441

    accuracy                           0.65      1500
   macro avg       0.48      0.49      0.46      1500
weighted avg       0.57      0.65      0.59      1500

手动 t-SNE + 随机森林 在测试集上的混淆矩阵:
[[938 121]
 [400  41]]

2、有监督降维

1.线性判别分析 (LDA)

Linear Discriminant Analysis

1. 核心定义与目标: 线性判别分析 (LDA) 是一种经典的有监督降维算法,也常直接用作分类器。作为降维技术时,其核心目标是找到一个低维特征子空间(即原始特征的线性组合),使得在该子空间中,不同类别的数据点尽可能地分开(类间距离最大化),而同一类别的数据点尽可能地聚集(类内方差最小化)。

2. 工作原理简述: LDA 通过最大化“类间散布矩阵”与“类内散布矩阵”之比的某种度量(例如它们的行列式之比)来实现其降维目标。它寻找能够最好地区分已定义类别的投影方向。

3. 关键特性:

  • 有监督性 (Supervised): 这是 LDA 与 PCA 最根本的区别。LDA 在降维过程中必须使用数据的类别标签 (y) 来指导投影方向的选择,目的是优化类别的可分离性。

  • 降维目标维度 (Number of Components):LDA 降维后的维度(即生成的判别特征的数量)有一个严格的上限:min(n_features, n_class - 1)。
    • n_features:原始特征的数量。

    • n_class 类别标签 (y) 中不同类别的数量

    • 这意味着,例如,对于一个二分类问题 (n_class = 2),LDA 最多能将数据降至 1 维。如果有 5 个类别,最多能降至 4 维(前提是原始特征数不少于4)。这个特性直接源于其优化目标。

  • 线性变换 (Linear Transformation): 与 PCA 类似,LDA 也是一种线性方法。它找到的是原始特征的线性组合来形成新的、具有判别能力的低维特征(称为判别向量或判别成分)。

  • 数据假设 (Assumptions):
    • 理论上,LDA 假设每个类别的数据服从多元高斯分布。

    • 理论上,LDA 假设所有类别具有相同的协方差矩阵。

    • 在实践中,即使这些假设不完全满足,LDA 通常也能表现良好,尤其是在类别大致呈椭球状分布且大小相似时。

4. 输入要求:

  • 特征 (X): 数值型特征。如果存在类别型特征,通常需要先进行预处理(如独热编码)。

  • 标签 (y): 一维的、代表类别身份的数组或 Series (例如 [0, 1, 0, 2, 1] )。LDA 不需要标签进行独热编码。标签的类别数量直接决定了降维的上限。

5. 与特征 (X) 和标签 (y) 的关系:

  • LDA 的降维过程和结果直接由标签 y 中的类别结构驱动。它试图找到最能区分这些由 y 定义的类别的特征组合。

  • 原始特征 X 提供了构建这些判别特征的原材料。特征 X 的质量和相关性会影响 LDA 的效果,但降维的“方向盘”是由 控制的。

6. 优点:

  • 直接优化类别可分性,非常适合作为分类任务的预处理步骤,往往能提升后续分类器的性能。

  • 计算相对高效。

  • 生成的低维特征具有明确的判别意义。

7. 局限性与注意事项:

  • 降维的维度受限于 n_class -1,这可能比 PCA 能达到的降维程度低很多,尤其是在类别数较少时。

  • 作为线性方法,可能无法捕捉数据中非线性的类别结构。如果类别边界是非线性的,LDA 效果可能不佳。

  • 对数据的高斯分布和等协方差假设在理论上是存在的,极端偏离这些假设可能影响性能。

  • 如果类别在原始特征空间中本身就高度重叠,LDA 的区分能力也会受限。

8. 适用场景:

  • 当目标是提高后续分类模型的性能时,LDA 是一个强有力的降维工具。

  • 当类别信息已知且被认为是区分数据的主要因素时。

  • 当希望获得具有良好类别区分性的低维表示时,尤其可用于数据可视化(如果能降到2D或3D)。

简而言之,LDA 是一种利用类别标签信息来寻找最佳类别分离投影的降维方法,其降维的潜力直接与类别数量挂钩。

import time  # 记录时间
import numpy as np # 确保numpy导入

from sklearn.preprocessing import StandardScaler  # 特征缩放,标准化
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis  
from sklearn.ensemble import RandomForestClassifier  # 随机森林分类器
from sklearn.metrics import classification_report, confusion_matrix  # 分类报告和混淆矩阵
import matplotlib.pyplot as plt
import seaborn as sns
from mpl_toolkits.mplot3d import Axes3D  # 如果需要3D绘图

print(f"\n--- 4. LDA 降维 + 随机森林 ---")

# 步骤 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))

输出:

--- 4. LDA 降维 + 随机森林 ---
原始特征数: 30, 类别数: 2
LDA 最多可降至 1 维。
目标降维维度: 10 维。
本次 LDA 将实际降至 1 维。
LDA降维后,训练集形状: (6000, 1), 测试集形状: (1500, 1)
LDA降维数据上,随机森林训练与预测耗时: 1.1535 秒

手动 LDA + 随机森林 在测试集上的分类报告:
              precision    recall  f1-score   support

           0       0.78      0.78      0.78      1059
           1       0.47      0.47      0.47       441

    accuracy                           0.69      1500
   macro avg       0.63      0.63      0.63      1500
weighted avg       0.69      0.69      0.69      1500

手动 LDA + 随机森林 在测试集上的混淆矩阵:
[[828 231]
 [233 208]]

@浙大疏锦行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值