在机器学习中,降维(Dimensionality Reduction)是指通过某种数学变换或算法,将高维数据映射到低维空间,同时尽可能保留原始数据的关键信息或结构的过程。其主要目的包括以下几个方面:
通常情况下,我们提到特征降维,很多时候默认指的是无监督降维,这种方法只需要特征数据本身。但是实际上还包含一种有监督的方法
定义:这类算法在降维过程中**不使用**任何关于数据样本的标签信息(比如类别标签、目标值等)。它们仅仅根据数据点本身的分布、方差、相关性、局部结构等特性来寻找低维表示
输入:只有特征矩阵 `X`
目标:
保留数据中尽可能多的方差(如 PCA)
保留数据的局部或全局流形结构(如 LLE, Isomap, t-SNE, UMAP)
找到能够有效重构原始数据的紧凑表示(如 Autoencoder)
找到统计上独立的成分(如 ICA)
典型算法:
PCA (Principal Component Analysis)** / **SVD (Singular Value Decomposition)
t-SNE (t-distributed Stochastic Neighbor Embedding)
UMAP (Uniform Manifold Approximation and Projection)
LLE (Locally Linear Embedding)
Isomap (Isometric Mapping)
Autoencoders (基本形式)
ICA (Independent Component Analysis)
定义:这类算法在降维过程中会利用数据样本的标签信息(通常是类别标签 `y`)。它们的目标是找到一个低维子空间,在这个子空间中,不同类别的数据点能够被更好地分离开,或者说,这个低维表示更有利于后续的分类(或回归)任务
输入:特征矩阵 `X` 和对应的标签向量 `y`
目标:
最大化不同类别之间的可分性,同时最小化同一类别内部的离散度(如 LDA)
找到对预测目标变量 `y` 最有信息量的特征组合
典型算法:
LDA (Linear Discriminant Analysis):这是最经典的监督降维算法。它寻找的投影方向能够最大化类间散度与类内散度之比
还有一些其他的,比如 NCA (Neighbourhood Components Analysis),但 LDA 是最主要的代表
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sans
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 导入心脏病项目的数据
data = pd.read_csv(r'C:\Users\ggggg\Desktop\python60-days-challenge-master (1)\python60-days-challenge-master\heart.csv')
# 查看数据
data.info()
data.head()
data.isnull().sum()
continuous_features = [col for col in data.columns
if data[col].dtype in ['float64', 'int64']
and data[col].nunique()/len(data) > 0.1] # 唯一值超过10%
# 离散特征:非数值型或低基数数值
# 原代码中 continuous_feature 变量名拼写错误,此处修正为正确的变量名
discrete_features = [col for col in data.columns
if col not in continuous_features]
print(discrete_features)
print(continuous_features)
for feature in discrete_features:
print(f"\n{feature}的唯一值:")
print(data[feature].value_counts())
nominal_features = ['sex','fbs', 'target'] # 不存在顺序关系的特征
# 独热编码
data = pd.get_dummies(data, columns=nominal_features, drop_first=True)
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)
cp_mapping = {0: 0, 1: 1, 2: 2, 3: 3}
if 'cp' in data.columns:
data['cp'] = data['cp'].map(cp_mapping)
continuous_features = data.select_dtypes(include=['int64', 'float64']).columns.tolist()
for feature in continuous_features:
mode_value = data[feature].mode()[0]
data.loc[:, feature] = data[feature].fillna(mode_value)
from sklearn.model_selection import train_test_split
X = data.drop(['target_1'], axis=1) # 特征,axis=1表示按列删除
y = data['target_1']
# 划分数据集,20%作为测试集,随机种子为42
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 训练集和测试集的形状
print(f"训练集形状:{X_train.shape}, 测试集形状:{X_test.shape}")
from sklearn.ensemble import RandomForestClassifier #随机森林分类器
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score # 用于评估分类器性能的指标
from sklearn.metrics import classification_report, confusion_matrix #用于生成分类报告和混淆矩阵
import warnings #用于忽略警告信息
warnings.filterwarnings("ignore") # 忽略所有警告信息
# --- 1. 默认参数的随机森林 ---
# 评估基准模型,这里确实不需要验证集
print("--- 1. 默认参数随机森林 (训练集 -> 测试集) ---")
import time # 这里介绍一个新的库,time库,主要用于时间相关的操作,因为调参需要很长时间,记录下会帮助后人知道大概的时长
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("\n默认随机森林 在测试集上的分类报告:")
print(classification_report(y_test, rf_pred))
print("默认随机森林 在测试集上的混淆矩阵:")
print(confusion_matrix(y_test, rf_pred))
PCA可以被看作是将SVD应用于经过均值中心化的数据矩阵,并对其结果进行特定解释的一种方法
主成分分析 (PCA) 的核心思想是识别数据中方差最大的方向(即主成分)。然后,它将数据投影到由这些最重要的主成分构成的新的、维度更低子空间上。这样做的目的是在降低数据维度的同时,尽可能多地保留原始数据中的“信息”(通过方差来衡量)。新的特征(主成分)是原始特征的线性组合,并且它们之间是正交的(不相关)
print(f"\n--- 2. PCA 降维 + 随机森林 (不使用 Pipeline) ---")
# 步骤1:特征缩放
scaler_pca = StandardScaler()
X_train_scaled_pca = scaler_pca.fit_transform(X_train)
X_test_scaled_pca = scaler_pca.transform(X_test)
#步骤2:pca降维
pca_expl = PCA(random_state=42)
pca_expl.fit(X_train_scaled_pca)
cumsum_variance = np.cumsum(pca_expl.explained_variance_ratio_)
n_components_to_keep_95_var = np.argmax(cumsum_variance >=0.95) + 1
print(f"为了保留95%的方差,需要的主成分数量: {n_components_to_keep_95_var}")
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
n_components_pca = 10
pca_manual = PCA(n_components=n_components_pca, random_state=42)
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))
PCA 的目标是保留数据的全局方差,而 t-SNE 的核心目标是在高维空间中相似的数据点,在降维后的低维空间中也应该保持相似(即彼此靠近),而不相似的点则应该相距较远。它特别擅长于将高维数据集投影到二维或三维空间进行可视化,从而揭示数据中的簇结构或流形结构
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, 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的优化更困难。
# 通常如果用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
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))
线性判别分析 (LDA) 是一种经典的有监督降维算法,也常直接用作分类器。作为降维技术时,其核心目标是找到一个低维特征子空间(即原始特征的线性组合),使得在该子空间中,不同类别的数据点尽可能地分开(类间距离最大化),而同一类别的数据点尽可能地聚集(类内方差最小化)
LDA 通过最大化“类间散布矩阵”与“类内散布矩阵”之比的某种度量(例如它们的行列式之比)来实现其降维目标。它寻找能够最好地区分已定义类别的投影方向
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
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)
# 步骤 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))
@浙大疏锦行