據(jù)集實(shí)戰(zhàn)線性判別分析:原理推導(dǎo)與Python代碼實(shí)現(xiàn))
簡(jiǎn)介本資源是一份面向機(jī)器學(xué)習(xí)初學(xué)者與實(shí)踐者的線性判別分析LDA教學(xué)實(shí)踐包聚焦特征降維與多類分類任務(wù)以經(jīng)典西瓜數(shù)據(jù)集3.0為載體完整呈現(xiàn)LDA在Python中的建模、訓(xùn)練與評(píng)估全流程。壓縮包共10個(gè)文件含2個(gè)核心Python腳本LDA.py與self_def.py、1個(gè)CSV格式的西瓜數(shù)據(jù)集watermelon_3a.csv、3個(gè)XML配置文件及輔助開(kāi)發(fā)文件如.iml、.pyc、.DS_Store總大小僅12KB輕量易部署適合嵌入課程實(shí)驗(yàn)或自學(xué)項(xiàng)目。已有584人下載學(xué)習(xí)資源結(jié)構(gòu)簡(jiǎn)潔清晰src目錄封裝主程序邏輯data目錄存放原始數(shù)據(jù).idea配置支持PyCharm快速調(diào)試。讀者可直接運(yùn)行代碼完成數(shù)據(jù)加載、訓(xùn)練集劃分、LDA模型擬合、預(yù)測(cè)及classification_report性能評(píng)估同時(shí)獲得對(duì)高斯假設(shè)、類內(nèi)/類間散度理解的實(shí)踐入口是掌握監(jiān)督式降維方法的實(shí)用入門(mén)材料。1. 項(xiàng)目概述從“西瓜數(shù)據(jù)集”到線性判別分析LDA的實(shí)戰(zhàn)之旅如果你剛接觸機(jī)器學(xué)習(xí)面對(duì)一堆算法名詞感到頭大那“線性判別分析”Linear Discriminant Analysis, LDA絕對(duì)是一個(gè)值得你花時(shí)間吃透的經(jīng)典算法。它不像深度學(xué)習(xí)那樣需要海量數(shù)據(jù)和算力也不像某些黑盒模型那樣難以解釋。LDA的核心思想非常直觀找到一個(gè)投影方向讓不同類別的數(shù)據(jù)在這個(gè)方向上投影后類間的距離盡可能大類內(nèi)的距離盡可能小。聽(tīng)起來(lái)是不是有點(diǎn)像在人群中找一條路讓不同隊(duì)伍的人站得盡量分開(kāi)而同一隊(duì)伍的人站得盡量緊湊這個(gè)經(jīng)典的“降維”兼“分類”算法在文本主題建模另一個(gè)LDALatent Dirichlet Allocation、人臉識(shí)別、生物信息學(xué)等領(lǐng)域至今仍有廣泛應(yīng)用。今天我們就用一個(gè)非常接地氣的“西瓜數(shù)據(jù)集”來(lái)手把手實(shí)現(xiàn)LDA。這個(gè)數(shù)據(jù)集在周志華老師的《機(jī)器學(xué)習(xí)》俗稱“西瓜書(shū)”里被反復(fù)使用數(shù)據(jù)量小、特征明確是理解算法原理的絕佳“教具”。我們將用Python從零開(kāi)始不依賴高級(jí)的scikit-learn封裝當(dāng)然最后也會(huì)對(duì)比一下一步步推導(dǎo)公式、編寫(xiě)代碼、可視化結(jié)果讓你不僅知道怎么調(diào)包更明白包里的每一行代碼在算什么。你會(huì)發(fā)現(xiàn)LDA.zip里裝的不僅是代碼更是一套理解監(jiān)督式降維與分類的完整思維模型。2. LDA的核心原理與數(shù)學(xué)推導(dǎo)拆解2.1 問(wèn)題定義我們要解決什么假設(shè)我們手頭有一個(gè)數(shù)據(jù)集比如我們的“西瓜數(shù)據(jù)集”每個(gè)西瓜樣本有多個(gè)特征如密度、含糖率并且每個(gè)樣本都有一個(gè)已知的類別標(biāo)簽如好瓜、壞瓜。LDA要解決的核心問(wèn)題是如何利用這些已知的類別信息找到原始特征空間的一個(gè)低維投影通常是直線或平面使得在這個(gè)新的低維空間里不同類別的樣本能最大程度地被區(qū)分開(kāi)。這帶來(lái)了兩個(gè)直接的好處1.降維將高維數(shù)據(jù)可視化到二維或三維便于我們觀察和理解。2.分類可以直接在這個(gè)投影后的低維空間里構(gòu)建一個(gè)簡(jiǎn)單的分類器比如找一個(gè)分類閾值。2.2 核心思想類內(nèi)小類間大LDA的目標(biāo)可以用一個(gè)優(yōu)化問(wèn)題來(lái)精確描述。我們目標(biāo)是找到一個(gè)投影向量w將樣本x投影到一維空間y w^T x。LDA希望投影后滿足類內(nèi)散度Within-class scatter最小化同一類別樣本的投影點(diǎn)盡可能聚集在一起方差小。類間散度Between-class scatter最大化不同類別樣本的投影點(diǎn)的中心盡可能遠(yuǎn)離。如何量化“散度”呢這里引入了散度矩陣的概念。類內(nèi)散度矩陣Sw衡量每個(gè)類別內(nèi)部樣本的分散程度。計(jì)算方法是每個(gè)類別樣本與其類別均值的協(xié)方差矩陣之和。Sw Σ_i Σ_{x∈X_i} (x - m_i)(x - m_i)^T其中m_i是第i類的均值向量。類間散度矩陣Sb衡量不同類別中心之間的分散程度。計(jì)算方法是每個(gè)類別的樣本數(shù)乘以該類均值與總體均值的差的外積之和。Sb Σ_i n_i (m_i - m)(m_i - m)^T其中m是全體樣本的總體均值向量n_i是第i類的樣本數(shù)。投影后類內(nèi)散度變?yōu)閣^T Sw w類間散度變?yōu)閣^T Sb w。我們的優(yōu)化目標(biāo)就是最大化它們的比值即尋找w使得下式最大J(w) (w^T Sb w) / (w^T Sw w)這個(gè)J(w)就是著名的“廣義瑞利商”Generalized Rayleigh Quotient。2.3 求解特征值分解的登場(chǎng)如何求解使J(w)最大的w呢在數(shù)學(xué)上可以證明最優(yōu)的投影方向w滿足方程Sb w λ Sw w這被稱為廣義特征值問(wèn)題。通常Sw是可逆的上式可以轉(zhuǎn)化為標(biāo)準(zhǔn)特征值問(wèn)題Sw^{-1} Sb w λ w也就是說(shuō)我們要求解矩陣Sw^{-1} Sb的特征值和特征向量。其中最大特征值對(duì)應(yīng)的特征向量就是我們要找的、使得類間區(qū)分度最大的投影方向。如果我們想投影到多維比如二維就取前k個(gè)最大特征值對(duì)應(yīng)的特征向量組成投影矩陣W。注意這里有一個(gè)非常重要的前提就是Sw必須是可逆的。這就要求樣本數(shù)量必須大于特征維度且特征之間不能存在完全的線性相關(guān)性。在實(shí)際應(yīng)用中如果Sw奇異不可逆需要進(jìn)行正則化處理如加上一個(gè)很小的單位矩陣Sw εI或者先使用PCA進(jìn)行降維。2.4 LDA與PCA的本質(zhì)區(qū)別很多人容易混淆LDA和主成分分析PCA。這里務(wù)必厘清PCA無(wú)監(jiān)督尋找數(shù)據(jù)方差最大的投影方向目標(biāo)是保留最多的數(shù)據(jù)信息方差完全無(wú)視樣本的類別標(biāo)簽。LDA有監(jiān)督尋找能最好區(qū)分已知類別的投影方向目標(biāo)是最大化分類潛力強(qiáng)烈依賴類別標(biāo)簽。用一個(gè)比喻PCA是給你一堆混在一起的彩色珠子它幫你找到一條線讓所有珠子在這條線上鋪得最開(kāi)方差最大不管顏色。LDA是已知哪些珠子是紅色、哪些是藍(lán)色它幫你找到一條線讓紅色珠子和藍(lán)色珠子在這條線上分別聚成兩團(tuán)且兩團(tuán)離得盡量遠(yuǎn)。3. 西瓜數(shù)據(jù)集解析與Python環(huán)境準(zhǔn)備3.1 西瓜數(shù)據(jù)集3.0α 深度解析我們采用“西瓜書(shū)”第3章最經(jīng)典的“西瓜數(shù)據(jù)集3.0α”。這個(gè)數(shù)據(jù)集雖然小但涵蓋了連續(xù)值和離散值為了聚焦LDA核心我們通常只選取兩個(gè)連續(xù)特征密度和含糖率以及一個(gè)二分類標(biāo)簽好瓜是和壞瓜否。原始數(shù)據(jù)可能如下表所示編號(hào)密度含糖率好瓜10.6970.460是20.7740.376是............170.5930.042否這個(gè)數(shù)據(jù)集非常適合教學(xué)維度低只有2個(gè)特征投影結(jié)果可以輕松在二維平面上可視化直觀理解LDA的投影直線。樣本少總共17個(gè)樣本手動(dòng)計(jì)算或調(diào)試代碼都非常方便。線性可分性數(shù)據(jù)在二維平面上大致是線性可分的LDA能發(fā)揮出很好的效果。在實(shí)際代碼中我們會(huì)將“是”標(biāo)記為類別1“否”標(biāo)記為類別0并將特征數(shù)據(jù)整理成一個(gè)(17, 2)的矩陣X標(biāo)簽整理成一個(gè)(17,)的向量y。3.2 Python環(huán)境與工具庫(kù)清單我們將使用最基礎(chǔ)的科學(xué)計(jì)算庫(kù)來(lái)實(shí)現(xiàn)LDA確保每一步都清晰可見(jiàn)。# 環(huán)境準(zhǔn)備所需庫(kù) import numpy as np import matplotlib.pyplot as plt from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as SKLDA # 用于對(duì)比 # 確??蓮?fù)現(xiàn)性 np.random.seed(42)核心庫(kù)說(shuō)明NumPy整個(gè)項(xiàng)目的基石。所有矩陣運(yùn)算均值、協(xié)方差、特征值分解都依賴它。務(wù)必理解其數(shù)組廣播和矩陣乘法。Matplotlib用于可視化。我們將繪制原始數(shù)據(jù)散點(diǎn)圖、LDA投影方向、以及投影后的數(shù)據(jù)分布直方圖。scikit-learn僅用于最后的結(jié)果驗(yàn)證和對(duì)比。我們會(huì)用它的LDA實(shí)現(xiàn)來(lái)檢驗(yàn)我們自己編寫(xiě)的算法是否正確。一個(gè)關(guān)鍵的實(shí)操心得在開(kāi)始編碼前我強(qiáng)烈建議你在Jupyter Notebook或支持交互的Python環(huán)境中進(jìn)行。這樣你可以隨時(shí)打印中間變量如Sw,Sb, 特征值等的形狀和值與手算或邏輯推導(dǎo)進(jìn)行比對(duì)這是理解算法、調(diào)試代碼最有效的方式。4. 從零手撕LDA代碼實(shí)現(xiàn)4.1 數(shù)據(jù)加載與預(yù)處理首先我們手動(dòng)輸入西瓜數(shù)據(jù)集并進(jìn)行標(biāo)準(zhǔn)化處理。雖然LDA本身不受量綱影響因?yàn)槠鋬?yōu)化目標(biāo)是一個(gè)比值但良好的數(shù)據(jù)習(xí)慣是從標(biāo)準(zhǔn)化開(kāi)始。def load_watermelon_data(): 加載西瓜數(shù)據(jù)集3.0α (密度 含糖率) # 特征X: 密度, 含糖率 X np.array([ [0.697, 0.460], [0.774, 0.376], [0.634, 0.264], [0.608, 0.318], [0.556, 0.215], [0.403, 0.237], [0.481, 0.149], [0.437, 0.211], [0.666, 0.091], [0.243, 0.267], [0.245, 0.057], [0.343, 0.099], [0.639, 0.161], [0.657, 0.198], [0.360, 0.370], [0.593, 0.042], [0.719, 0.103] ]) # 標(biāo)簽y: 1為好瓜(是), 0為壞瓜(否) y np.array([1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]) return X, y X, y load_watermelon_data() print(f數(shù)據(jù)形狀: X{X.shape}, y{y.shape}) print(f類別分布: 好瓜{sum(y1)}個(gè), 壞瓜{sum(y0)}個(gè)) # 數(shù)據(jù)標(biāo)準(zhǔn)化 (Z-score標(biāo)準(zhǔn)化) def standardize(X): mean np.mean(X, axis0) std np.std(X, axis0) return (X - mean) / std, mean, std X_std, X_mean, X_std_dev standardize(X) print(標(biāo)準(zhǔn)化后的前5個(gè)樣本:\n, X_std[:5])注意這里標(biāo)準(zhǔn)化存儲(chǔ)了均值和標(biāo)準(zhǔn)差這是一個(gè)好習(xí)慣。在后續(xù)將新樣本投影到LDA空間時(shí)你需要用同樣的均值和標(biāo)準(zhǔn)差先對(duì)新樣本進(jìn)行標(biāo)準(zhǔn)化然后再乘以我們求得的投影矩陣W。很多初學(xué)者會(huì)忘記這一步導(dǎo)致預(yù)測(cè)錯(cuò)誤。4.2 核心計(jì)算類內(nèi)與類間散度矩陣這是LDA算法的核心步驟。我們需要分別計(jì)算每個(gè)類別的均值、總體均值然后套用公式計(jì)算Sw和Sb。def compute_lda_components(X, y): 計(jì)算LDA所需的散度矩陣和投影向量 參數(shù): X: 標(biāo)準(zhǔn)化后的特征矩陣 (n_samples, n_features) y: 標(biāo)簽向量 (n_samples,) 返回: W: 投影矩陣 (n_features, n_components) eigenvalues: 特征值 n_features X.shape[1] labels np.unique(y) # 計(jì)算總體均值 overall_mean np.mean(X, axis0) # 初始化 Sw 和 Sb 為零矩陣 Sw np.zeros((n_features, n_features)) Sb np.zeros((n_features, n_features)) for label in labels: # 獲取當(dāng)前類別的樣本 X_i X[y label] # 計(jì)算當(dāng)前類別的均值 mean_i np.mean(X_i, axis0) # 計(jì)算當(dāng)前類別的類內(nèi)散度矩陣并累加到 Sw # X_i - mean_i 是中心化后的矩陣 Sw (X_i - mean_i).T (X_i - mean_i) # 等價(jià)于 np.cov(X_i.T, biasTrue) * (len(X_i)-1) # 計(jì)算當(dāng)前類別的類間散度矩陣貢獻(xiàn)并累加到 Sb n_i len(X_i) mean_diff (mean_i - overall_mean).reshape(-1, 1) # 轉(zhuǎn)換為列向量 Sb n_i * (mean_diff mean_diff.T) # 求解廣義特征值問(wèn)題 Sw^{-1} Sb w λ w # 使用 np.linalg.eig 求解但更穩(wěn)定的方式是使用 np.linalg.pinv(Sw) Sb # 因?yàn)镾w可能接近奇異使用偽逆更穩(wěn)健 Sw_inv np.linalg.pinv(Sw) # 使用偽逆代替逆提高數(shù)值穩(wěn)定性 M Sw_inv Sb # 計(jì)算特征值和特征向量 eigenvalues, eigenvectors np.linalg.eig(M) # 特征值和特征向量是復(fù)數(shù)由于數(shù)值計(jì)算我們?nèi)?shí)部 eigenvalues np.real(eigenvalues) eigenvectors np.real(eigenvectors) # 按特征值降序排序 idx eigenvalues.argsort()[::-1] eigenvalues eigenvalues[idx] eigenvectors eigenvectors[:, idx] # 我們通常選擇非零特征值對(duì)應(yīng)的特征向量。 # 對(duì)于二分類問(wèn)題Sb的秩最大為1因此最多只有一個(gè)非零特征值對(duì)應(yīng)一個(gè)投影方向。 # 我們選取前 min(n_features, len(labels)-1) 個(gè)特征向量對(duì)于二分類就是1個(gè)。 n_components min(n_features, len(labels) - 1) W eigenvectors[:, :n_components] return W, eigenvalues W, eig_vals compute_lda_components(X_std, y) print(投影向量 W (列向量):\n, W) print(對(duì)應(yīng)的特征值:\n, eig_vals) print(投影向量形狀:, W.shape)關(guān)鍵點(diǎn)解析Sw的計(jì)算(X_i - mean_i).T (X_i - mean_i)是協(xié)方差矩陣的(n-1)倍。這是計(jì)算類內(nèi)散度最直接的方式。Sb的計(jì)算mean_diff被重塑為列向量mean_diff mean_diff.T得到一個(gè)矩陣乘以樣本數(shù)n_i后累加。使用偽逆np.linalg.pinv這是實(shí)現(xiàn)中的關(guān)鍵技巧。直接求逆np.linalg.inv(Sw)在Sw接近奇異即特征間相關(guān)性很強(qiáng)或樣本數(shù)少于特征數(shù)時(shí)會(huì)報(bào)錯(cuò)。偽逆提供了數(shù)值上更穩(wěn)定的解。特征值分解結(jié)果np.linalg.eig返回的特征值和特征向量可能是復(fù)數(shù)這是浮點(diǎn)數(shù)計(jì)算中的微小誤差導(dǎo)致的我們?nèi)?shí)部即可。投影維度選擇LDA能降維到的最大維度是min(n_features, n_classes-1)。對(duì)于二分類問(wèn)題無(wú)論原始特征有多少維投影后最多只有一維一條直線。我們的西瓜數(shù)據(jù)是二維特征二分類所以W是一個(gè)(2, 1)的矩陣即一個(gè)投影方向。4.3 數(shù)據(jù)投影與結(jié)果可視化得到投影方向W后我們就可以將原始數(shù)據(jù)投影到這條直線上并直觀地看到分類效果。def project_and_visualize(X, y, W): 將數(shù)據(jù)投影到LDA方向并可視化 # 投影: Y X * W X_projected X W # 創(chuàng)建畫(huà)布 fig, axes plt.subplots(1, 2, figsize(14, 5)) # 子圖1: 原始數(shù)據(jù)及LDA投影方向 ax1 axes[0] # 繪制原始數(shù)據(jù)點(diǎn) ax1.scatter(X[y1, 0], X[y1, 1], cred, markero, label好瓜, alpha0.7, s80) ax1.scatter(X[y0, 0], X[y0, 1], cblue, markers, label壞瓜, alpha0.7, s80) # 繪制LDA投影方向直線 # 直線的方向由W決定我們過(guò)原點(diǎn)畫(huà)一條線因?yàn)閿?shù)據(jù)已標(biāo)準(zhǔn)化中心在原點(diǎn) # 生成直線上的點(diǎn) line_x np.linspace(X[:,0].min()-0.5, X[:,0].max()0.5, 100) # W是(2,1)矩陣W[1]/W[0]是斜率 if abs(W[0]) 1e-10: # 避免除零 line_y (W[1]/W[0]) * line_x ax1.plot(line_x, line_y, k--, linewidth2, labelfLDA投影方向 (斜率{W[1]/W[0]:.3f})) else: # 如果W[0]接近0說(shuō)明投影方向接近y軸 ax1.axvline(x0, colork, linestyle--, linewidth2, labelLDA投影方向 (垂直)) # 繪制樣本點(diǎn)到投影方向的垂線可選更直觀 for i in range(len(X)): x_point X[i] # 計(jì)算投影點(diǎn)坐標(biāo) # 投影到方向向量W上 proj (x·w)/(w·w) * w w_vec W.flatten() proj_coeff np.dot(x_point, w_vec) / np.dot(w_vec, w_vec) proj_point proj_coeff * w_vec ax1.plot([x_point[0], proj_point[0]], [x_point[1], proj_point[1]], gray, alpha0.3, linewidth0.5) ax1.set_xlabel(密度 (標(biāo)準(zhǔn)化后)) ax1.set_ylabel(含糖率 (標(biāo)準(zhǔn)化后)) ax1.set_title(原始數(shù)據(jù)與LDA投影方向) ax1.legend() ax1.grid(True, linestyle--, alpha0.5) ax1.axis(equal) # 子圖2: 投影后的數(shù)據(jù)分布直方圖 ax2 axes[1] # 獲取兩類投影后的值 proj_good X_projected[y1].flatten() proj_bad X_projected[y0].flatten() # 繪制直方圖 ax2.hist(proj_good, bins8, alpha0.7, colorred, label好瓜, edgecolorblack) ax2.hist(proj_bad, bins8, alpha0.7, colorblue, label壞瓜, edgecolorblack) # 計(jì)算并繪制兩類投影中心的垂線 mean_good np.mean(proj_good) mean_bad np.mean(proj_bad) ax2.axvline(xmean_good, colordarkred, linestyle-, linewidth2, labelf好瓜中心 ({mean_good:.3f})) ax2.axvline(xmean_bad, colordarkblue, linestyle-, linewidth2, labelf壞瓜中心 ({mean_bad:.3f})) # 計(jì)算一個(gè)簡(jiǎn)單的分類閾值兩類中心的平均值 simple_threshold (mean_good mean_bad) / 2 ax2.axvline(xsimple_threshold, colorgreen, linestyle--, linewidth3, labelf分類閾值 ({simple_threshold:.3f})) ax2.set_xlabel(投影值) ax2.set_ylabel(頻數(shù)) ax2.set_title(投影后數(shù)據(jù)分布直方圖) ax2.legend() ax2.grid(True, linestyle--, alpha0.5) plt.tight_layout() plt.show() return X_projected, simple_threshold X_proj, threshold project_and_visualize(X_std, y, W) print(f投影后數(shù)據(jù)形狀: {X_proj.shape}) print(f建議的分類閾值 (兩類中心均值): {threshold:.4f})可視化解讀左圖你可以清晰地看到一條黑色虛線那就是LDA找到的最佳投影方向。所有樣本點(diǎn)都沿著灰色細(xì)線“垂落”到這條黑線上。我們的目標(biāo)就是讓紅色圓圈好瓜和藍(lán)色方塊壞瓜在這條黑線上的投影點(diǎn)盡可能地各自聚集且兩團(tuán)離得遠(yuǎn)。右圖直方圖展示了投影后的結(jié)果。紅色和藍(lán)色的柱子分別代表好瓜和壞瓜投影值的分布。兩條深色豎線是它們的中心綠色虛線是取兩個(gè)中心點(diǎn)中點(diǎn)得到的簡(jiǎn)單分類閾值。可以看到在這個(gè)一維投影空間里兩類數(shù)據(jù)基本被分開(kāi)了只有極少部分重疊。這證明了LDA投影的有效性。4.4 分類器構(gòu)建與性能評(píng)估投影之后我們實(shí)際上得到了一個(gè)一維特征。在這個(gè)一維空間里構(gòu)建分類器就非常簡(jiǎn)單了最常見(jiàn)的就是用一個(gè)閾值來(lái)劃分。def lda_classifier(X_projected, threshold): 簡(jiǎn)單的閾值分類器 # 假設(shè)投影后好瓜的投影值通常更大取決于W的方向 # 如果投影值大于閾值預(yù)測(cè)為好瓜(1)否則為壞瓜(0) return (X_projected threshold).astype(int).flatten() # 在訓(xùn)練集上應(yīng)用分類器 y_pred lda_classifier(X_proj, threshold) # 計(jì)算準(zhǔn)確率 accuracy np.mean(y_pred y) print(f基于LDA投影和閾值分類的訓(xùn)練集準(zhǔn)確率: {accuracy:.2%}) # 更嚴(yán)謹(jǐn)?shù)淖龇ú榭捶诸悰Q策 print(\n樣本預(yù)測(cè)詳情 (編號(hào) 真實(shí)標(biāo)簽 投影值 預(yù)測(cè)標(biāo)簽):) for i in range(len(X)): print(f{i1:2d}: {y[i]} - {X_proj[i,0]:7.4f} - {y_pred[i]} {? if y[i]y_pred[i] else ?})注意事項(xiàng)這里我們直接在訓(xùn)練集上評(píng)估準(zhǔn)確率會(huì)偏高過(guò)擬合。在實(shí)際項(xiàng)目中必須使用未見(jiàn)過(guò)的測(cè)試集或交叉驗(yàn)證來(lái)評(píng)估模型性能。對(duì)于這個(gè)微型教學(xué)數(shù)據(jù)集我們主要是為了理解流程。5. 與scikit-learn實(shí)現(xiàn)對(duì)比及高級(jí)話題5.1 使用scikit-learn驗(yàn)證為了驗(yàn)證我們手寫(xiě)的LDA是否正確最好的辦法是調(diào)用業(yè)界標(biāo)準(zhǔn)的scikit-learn實(shí)現(xiàn)進(jìn)行對(duì)比。def compare_with_sklearn(X_std, y): 與scikit-learn的LDA進(jìn)行對(duì)比 # 使用sklearn的LDA指定降維到1維 sk_lda SKLDA(n_components1) X_proj_sk sk_lda.fit_transform(X_std, y) # 注意sklearn的fit_transform直接返回降維后的數(shù)據(jù) # 獲取sklearn計(jì)算出的投影向量 # sklearn的coef_是判別函數(shù)的系數(shù)方向與我們的W有關(guān)但不直接相等。 # 更直接的是獲取標(biāo)量化后的特征向量。 # sklearn將投影向量存儲(chǔ)在scalings_屬性中 W_sk sk_lda.scalings_[:, :1] # 取第一列 print(scikit-learn 計(jì)算出的投影向量 (scalings_):\n, W_sk) print(我們手寫(xiě)代碼計(jì)算出的投影向量 W:\n, W) # 比較方向是否一致可能差一個(gè)符號(hào)因?yàn)榉较蛳喾床挥绊懛诸?# 計(jì)算兩個(gè)向量的余弦相似度絕對(duì)值 cos_sim np.abs(np.dot(W.flatten(), W_sk.flatten()) / (np.linalg.norm(W) * np.linalg.norm(W_sk))) print(f\n投影向量方向余弦相似度 (絕對(duì)值): {cos_sim:.6f}) print((非常接近1說(shuō)明方向一致)) # 比較投影后的數(shù)據(jù)可能差一個(gè)縮放因子和符號(hào) # 將我們的投影數(shù)據(jù)與sklearn的進(jìn)行相關(guān)性比較 corr np.corrcoef(X_proj.flatten(), X_proj_sk.flatten())[0,1] print(f投影后數(shù)據(jù)的相關(guān)系數(shù): {corr:.6f}) print((非常接近1或-1說(shuō)明線性相關(guān)即投影本質(zhì)相同)) # 使用sklearn的模型進(jìn)行預(yù)測(cè) y_pred_sk sk_lda.predict(X_std) accuracy_sk np.mean(y_pred_sk y) print(f\nscikit-learn LDA 訓(xùn)練集準(zhǔn)確率: {accuracy_sk:.2%}) print(f我們手寫(xiě)LDA的準(zhǔn)確率: {np.mean(lda_classifier(X_proj, threshold)y):.2%}) compare_with_sklearn(X_std, y)運(yùn)行這段代碼你會(huì)發(fā)現(xiàn)我們的投影方向W與sklearn的scalings_可能方向相反余弦相似度接近-1但投影后數(shù)據(jù)的相關(guān)系數(shù)絕對(duì)值接近1。這是完全正常的因?yàn)橥队爸本€沒(méi)有正反方向之分w和-w是同一個(gè)子空間。分類準(zhǔn)確率也應(yīng)該是一致的。這個(gè)對(duì)比驗(yàn)證了我們手寫(xiě)LDA核心邏輯的正確性。5.2 多分類LDA與維度選擇我們的例子是二分類。對(duì)于多分類問(wèn)題C2個(gè)類別LDA的原理完全一樣。此時(shí)Sb矩陣的秩最大為C-1。這意味著我們最多可以找到C-1個(gè)有意義的投影方向?qū)?yīng)C-1個(gè)非零特征值。這些投影方向張成了一個(gè)最多C-1維的子空間。在代碼實(shí)現(xiàn)上你只需要修改n_components參數(shù)例如設(shè)置為min(n_features, n_classes-1)然后取前n_components個(gè)特征向量組成投影矩陣W。投影后的數(shù)據(jù)X_projected X W將是一個(gè)(n_samples, n_components)的矩陣。如何選擇降維后的維度k一個(gè)常用的方法是觀察特征值。特征值λ_i的大小代表了對(duì)應(yīng)投影方向的重要性區(qū)分能力。你可以計(jì)算累計(jì)貢獻(xiàn)率累計(jì)貢獻(xiàn)率 前k個(gè)特征值之和 / 所有特征值之和然后選擇一個(gè)k使得累計(jì)貢獻(xiàn)率達(dá)到一個(gè)較高的閾值如95%。對(duì)于分類任務(wù)有時(shí)直接選擇k n_classes - 1也是一個(gè)合理的默認(rèn)值。5.3 LDA的假設(shè)與局限性沒(méi)有哪個(gè)算法是萬(wàn)能的LDA也不例外它建立在幾個(gè)關(guān)鍵假設(shè)之上正態(tài)性假設(shè)每個(gè)類別的數(shù)據(jù)特征服從多元正態(tài)分布。在實(shí)際中雖然輕微偏離此假設(shè)LDA仍可工作但嚴(yán)重偏離會(huì)影響性能。同方差性假設(shè)所有類別的協(xié)方差矩陣相同即Sw矩陣。這是我們推導(dǎo)中Sw是各類別協(xié)方差矩陣之和的前提。如果不同類別數(shù)據(jù)形狀差異很大比如一個(gè)類是圓形散開(kāi)另一個(gè)類是扁橢圓形LDA的效果會(huì)打折扣。線性可分性LDA尋找的是線性投影。如果原始數(shù)據(jù)本身是非線性可分的比如環(huán)形分布線性投影無(wú)法很好地區(qū)分此時(shí)需要考慮核判別分析KDA或其他非線性方法。一個(gè)重要的實(shí)操心得在應(yīng)用LDA前快速檢查數(shù)據(jù)是否大致符合這些假設(shè)是個(gè)好習(xí)慣。例如可以分別畫(huà)出每個(gè)類別特征的分布直方圖或Q-Q圖來(lái)檢查正態(tài)性可以計(jì)算每個(gè)類別的協(xié)方差矩陣并觀察它們是否相似。當(dāng)假設(shè)被嚴(yán)重違反時(shí)樸素貝葉斯分類器對(duì)特征獨(dú)立性假設(shè)敏感或二次判別分析QDA放松同方差假設(shè)可能是更好的選擇。6. 常見(jiàn)問(wèn)題、調(diào)試技巧與擴(kuò)展應(yīng)用6.1 報(bào)錯(cuò)與問(wèn)題排查速查表問(wèn)題現(xiàn)象可能原因解決方案Sw矩陣奇異無(wú)法求逆1. 樣本數(shù)少于特征數(shù)。2. 特征之間存在完全線性相關(guān)。1. 增加樣本或使用正則化Sw λI。2. 進(jìn)行特征選擇或先用PCA降維。特征值/特征向量出現(xiàn)復(fù)數(shù)數(shù)值計(jì)算誤差Sw^{-1}Sb不是嚴(yán)格的對(duì)稱矩陣。取np.real()獲取實(shí)部通常不影響結(jié)果?;蚴褂胹cipy.linalg.eigh求解廣義特征值問(wèn)題。投影后分類效果很差1. LDA的假設(shè)正態(tài)、同方差嚴(yán)重不滿足。2. 數(shù)據(jù)本質(zhì)是非線性可分的。1. 嘗試對(duì)數(shù)據(jù)進(jìn)行變換如對(duì)數(shù)變換使其更接近正態(tài)。2. 使用QDA、核方法或非線性分類器。與sklearn結(jié)果符號(hào)相反投影方向w和-w是等價(jià)的。檢查投影后數(shù)據(jù)的相關(guān)系數(shù)如果絕對(duì)值接近1則結(jié)果一致??赏ㄟ^(guò)比較分類準(zhǔn)確率驗(yàn)證。多分類時(shí)維度超過(guò)C-1試圖獲取多于C-1個(gè)投影方向。將n_components設(shè)置為min(n_features, n_classes-1)。6.2 性能優(yōu)化與數(shù)值穩(wěn)定技巧正則化Regularization當(dāng)Sw接近奇異時(shí)在Sw上加一個(gè)小的正則化項(xiàng)λ * I單位矩陣是標(biāo)準(zhǔn)做法。這相當(dāng)于在優(yōu)化目標(biāo)中加入了對(duì)投影向量w的L2范數(shù)懲罰防止過(guò)擬合并提高數(shù)值穩(wěn)定性。lambda_reg 1e-4 # 一個(gè)很小的正數(shù) Sw_reg Sw lambda_reg * np.eye(Sw.shape[0]) Sw_inv np.linalg.inv(Sw_reg) # 此時(shí)可以直接求逆使用更穩(wěn)定的求解器對(duì)于廣義特征值問(wèn)題Sb w λ Sw w可以使用scipy.linalg.eigh函數(shù)它專門(mén)用于求解厄米特實(shí)對(duì)稱矩陣的特征值問(wèn)題并支持廣義形式數(shù)值上更穩(wěn)定。from scipy.linalg import eigh eigenvalues, eigenvectors eigh(Sb, Sw) # 直接求解廣義特征值問(wèn)題先進(jìn)行PCA預(yù)降維白化如果特征維數(shù)很高可以先使用PCA將數(shù)據(jù)降到較低維度比如保留95%方差然后再應(yīng)用LDA。這能有效緩解小樣本問(wèn)題并去除噪聲。6.3 超越西瓜數(shù)據(jù)集LDA的實(shí)際應(yīng)用場(chǎng)景理解了LDA在西瓜數(shù)據(jù)集上的運(yùn)作后你可以將其應(yīng)用到更廣泛的領(lǐng)域圖像識(shí)別與計(jì)算機(jī)視覺(jué)在人臉識(shí)別中LDA常被稱為Fisherfaces被用來(lái)提取最能區(qū)分不同人臉的線性特征。將每張人臉圖像拉成一個(gè)高維向量LDA可以找到區(qū)分不同人的低維子空間。生物信息學(xué)與醫(yī)學(xué)診斷在基因表達(dá)數(shù)據(jù)分析中樣本患者有成百上千個(gè)基因特征的表達(dá)量標(biāo)簽是疾病類型如癌癥亞型。LDA可以用于尋找對(duì)區(qū)分疾病亞型最重要的基因組合并實(shí)現(xiàn)降維可視化。文本分類與主題模型注意這里的LDA通常是潛在狄利克雷分布與線性判別分析同名但完全不同。不過(guò)線性判別分析本身也可以用于文本分類例如將文檔的詞袋模型向量投影到低維空間后再分類。故障診斷與質(zhì)量控制在工業(yè)生產(chǎn)中傳感器會(huì)采集設(shè)備的多維運(yùn)行數(shù)據(jù)。LDA可以學(xué)習(xí)正常狀態(tài)和各類故障狀態(tài)下的數(shù)據(jù)投影當(dāng)新數(shù)據(jù)投影點(diǎn)偏離正常區(qū)域時(shí)即可預(yù)警。最后再分享一個(gè)小技巧當(dāng)你使用LDA作為分類前的降維工具時(shí)不妨將它與最簡(jiǎn)單的分類器如最近鄰、線性SVM在投影后的低維空間上結(jié)合。這樣構(gòu)建的管道Pipeline往往比直接在原始高維空間使用復(fù)雜分類器更高效、更不容易過(guò)擬合且模型可解釋性更強(qiáng)。你可以用sklearn.pipeline.Pipeline輕松實(shí)現(xiàn)這一流程并利用網(wǎng)格搜索來(lái)優(yōu)化LDA的維度和分類器的參數(shù)。本文還有配套的精品資源點(diǎn)擊獲取