知識點回顧:
- LDA線性判別
- PCA主成分分析
- 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對象時,指定降維維度為10X_train_pca = pca_manual.fit_transform(X_train_scaled_pca)
X_test_pca = pca_manual.transform(X_test_scaled_pca) # 使用在訓練集上fit的pcaprint(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 = 10if 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_featuresprint("將使用縮放后的原始特征進行后續操作。")
else:# 實際使用的組件數不能超過LDA的上限,也不能超過我們的目標(如果目標更小)actual_n_components_lda = min(n_components_lda_target, max_lda_components)if actual_n_components_lda < 1: # 這種情況理論上不會發生,因為上面已經檢查了 max_lda_components < 1print(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_featuresprint("將使用縮放后的原始特征進行后續操作。")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))
收獲心得:
講實話關于特征降維這一兩節內容學的有點麻,以后再認真看看
@浙大疏錦行