The Story That Explains LDAवह कहानी जो LDA समझाती है一个解释 LDA 的故事
A naive guard looks only at one measurement, or at whatever varies the most between people. A smart guard asks a better question: "If I mix the two measurements into one score, which mix puts visitors and thieves as far apart as possible, while keeping each group tightly packed?"
That is the whole idea behind Linear Discriminant Analysis. LDA finds a direction — called a discriminant — that maximises the gap between classes while minimising the spread inside each class. It does not look for any variance; it looks for variance that separates the classes.
एक भोला guard सिर्फ़ एक माप देखता है, या वह जो लोगों में सबसे ज़्यादा बदलता है। एक समझदार guard बेहतर सवाल पूछता है: "अगर मैं दोनों मापों को मिलाकर एक score बनाऊँ, तो कौन सा मेल visitors और चोरों को जितना हो सके उतना दूर रखेगा, और हर group को कसकर एक साथ रखेगा?"
यही Linear Discriminant Analysis का पूरा विचार है। LDA एक दिशा खोजता है — जिसे discriminant कहते हैं — जो classes के बीच की दूरी सबसे ज़्यादा और हर class के अंदर का फैलाव सबसे कम करे। यह कोई भी variance नहीं खोजता; यह वह variance खोजता है जो classes को अलग करे।
天真的保安只看其中一个测量值,或者只看人与人之间差别最大的那个。 聪明的保安会问一个更好的问题:"如果把两个测量值混合成一个分数,哪种混合能让参观者和小偷离得最远, 同时让每一组内部都紧紧聚在一起?"
这就是线性判别分析(Linear Discriminant Analysis,LDA)的全部思想。LDA 寻找一个方向 —— 叫作判别方向(discriminant)—— 让类与类之间的距离最大,同时让每个类内部的散布最小。 它要找的不是随便什么方差,而是能把类别分开的方差。
Linear Discriminant Analysis (LDA) is a supervised method for dimensionality reduction and classification, introduced by Ronald Fisher in 1936. PCA keeps the directions with the most total variance and never looks at labels. LDA uses the labels and maximises the ratio of between-class scatter to within-class scatter, so it finds the projection that makes the classes easiest to tell apart.
Linear Discriminant Analysis (LDA) dimensionality reduction और classification का एक supervised तरीका है, जिसे Ronald Fisher ने 1936 में दिया। PCA सबसे ज़्यादा कुल variance वाली दिशाएँ रखता है और labels कभी नहीं देखता। LDA labels इस्तेमाल करता है और between-class scatter और within-class scatter के अनुपात को सबसे बड़ा करता है, इसलिए यह वह projection खोजता है जिसमें classes को अलग पहचानना सबसे आसान हो।
线性判别分析(LDA)是一种用于降维和分类的有监督(supervised)方法,由 Ronald Fisher 于 1936 年提出。 PCA 保留总方差最大的方向,从不看标签;LDA 使用标签,最大化类间散布(between-class scatter)与 类内散布(within-class scatter)之比,从而找到最容易区分各类别的投影。
LDA wears two hats. As a dimensionality reducer it projects data into at most C − 1 dimensions, where C is the number of classes — a good preprocessing step before another classifier. As a classifier it assumes each class is a Gaussian with the same covariance, and assigns a new point to the class with the highest probability. In practice this means: the nearest class mean in the LDA space, adjusted by the class priors.
LDA दो काम करता है। Dimensionality reducer के रूप में यह data को ज़्यादा से ज़्यादा C − 1 dimensions में project करता है, जहाँ C classes की संख्या है — किसी दूसरे classifier से पहले यह अच्छा preprocessing step है। Classifier के रूप में यह मानता है कि हर class एक Gaussian है जिसकी covariance एक जैसी है, और नए point को सबसे ज़्यादा probability वाली class देता है। व्यवहार में: LDA space में सबसे पास का class mean, class priors के हिसाब से थोड़ा बदला हुआ।
LDA 有两种用途。作为降维工具,它把数据投影到最多 C − 1 维(C 为类别数)—— 很适合作为其他分类器之前的预处理。作为分类器,它假设每个类都是协方差相同的高斯分布, 把新点分给概率最高的类。实际效果就是:在 LDA 空间中离哪个类均值最近,再按类别先验(priors)做一点调整。
The Core Intuition — Two Objectives at Onceमुख्य समझ — एक साथ दो लक्ष्य核心直觉 — 同时实现两个目标
| What it doesयह क्या करता है它做什么 | Resultनतीजा结果 |
|---|---|
| Maximises total varianceकुल variance सबसे ज़्यादा करता है最大化总方差 | Classes can overlap badlyClasses बुरी तरह मिल सकती हैं类别可能严重重叠 |
| Follows the longest side of the dataData की सबसे लंबी तरफ़ चलता है沿着数据最长的方向 | Red & blue may mixलाल और नीला मिल सकते हैं红蓝可能混在一起 |
| Ignores class labelsClass labels नहीं देखता忽略类别标签 | Not built for classificationClassification के लिए नहीं बना不是为分类设计的 |
| What it doesयह क्या करता है它做什么 | Resultनतीजा结果 |
|---|---|
| Maximises between-class gapClasses के बीच की दूरी सबसे ज़्यादा最大化类间距离 | Class means far apartClass means दूर-दूर类均值相距很远 |
| Minimises within-class spreadClass के अंदर फैलाव सबसे कम最小化类内散布 | Each class tightly packedहर class कसी हुई每类紧凑 |
| Uses class labelsClass labels इस्तेमाल करता है使用类别标签 | Ideal for classificationClassification के लिए बढ़िया非常适合分类 |
Two classes, red and blue. Drag on the picture (or use the slider) to turn the amber line. The bars below are the "shadows" of each class on the line. Press PCA, then LDA, and compare the shadows.
दो classes, लाल और नीली। Amber line घुमाने के लिए picture पर drag करें (या slider इस्तेमाल करें)। नीचे की bars line पर हर class की "परछाई" हैं। पहले PCA, फिर LDA दबाएँ और परछाइयों की तुलना करें।
两个类别,红色和蓝色。在图上拖动(或使用滑块)转动琥珀色直线。下方的柱子是每个类别在直线上的"影子"。先点 PCA,再点 LDA,比较两种影子。
J = (gap between class means)² ÷ (spread inside the classes). A big gap is good, but only if each class is narrow. PCA maximises the total spread; LDA maximises J. In the simulator they pick very different lines.
J = (class means के बीच दूरी)² ÷ (classes के अंदर का फैलाव)। बड़ी दूरी अच्छी है, पर तभी जब हर class पतली हो। PCA कुल फैलाव सबसे ज़्यादा करता है; LDA, J को। Simulator में दोनों बहुत अलग lines चुनते हैं।
J =(类均值之间的距离)² ÷(类内散布)。距离大是好事,但前提是每一类都很窄。 PCA 最大化总散布;LDA 最大化 J。在模拟器里,它们选出的直线截然不同。
The LDA Words, in PicturesLDA के शब्द, तस्वीरों में用图片解释 LDA 术语
3-D Visualisation — PCA View vs LDA View3-D Visualisation — PCA View vs LDA View三维可视化 — PCA 视图与 LDA 视图
Here are three classes in 3-D. All three share a big "noise" direction (along x₃). Both PCA and LDA are computed live in your browser from the data. PCA keeps the two directions with the most total spread. LDA keeps the two directions that separate the class means. Switch between them and watch the right panel.
यहाँ 3-D में तीन classes हैं। तीनों में एक बड़ी "noise" दिशा (x₃ के साथ) साझा है। PCA और LDA दोनों आपके browser में data से live निकाले जाते हैं। PCA सबसे ज़्यादा कुल फैलाव वाली दो दिशाएँ रखता है। LDA वे दो दिशाएँ रखता है जो class means को अलग करती हैं। दोनों के बीच बदलें और दाईं panel देखें।
这里是三维空间中的三个类别。它们共有一个很大的"噪声"方向(沿 x₃)。PCA 和 LDA 都由你的浏览器根据数据实时计算。 PCA 保留总散布最大的两个方向;LDA 保留能把类均值分开的两个方向。在两者之间切换,观察右侧面板。
Left: the 3-D data (drag to rotate). Arrows show the two axes of the chosen method. Right: the data projected onto those two axes.
बाएँ: 3-D data (घुमाने के लिए drag करें)। तीर चुने गए तरीके के दो axes दिखाते हैं। दाएँ: उन दो axes पर project किया गया data.
左:三维数据(拖动旋转)。箭头表示所选方法的两条轴。右:投影到这两条轴上的数据。
Step-by-Step Maths — A Worked ExampleStep-by-Step गणित — एक हल किया हुआ Example逐步推导 — 一个完整例题
Numerical Mini-Example (2 Features, 2 Classes, 4 Points)छोटा Numerical Example (2 Features, 2 Classes, 4 Points)数值小例题(2 个特征、2 个类别、4 个点)
| SampleSample样本 | Alcohol % | Malic Acid | ClassClass类别 |
|---|---|---|---|
| 1 | 14.2 | 1.7 | Barolo (C₁) |
| 2 | 13.8 | 1.9 | Barolo (C₁) |
| 3 | 12.4 | 3.1 | Barbera (C₂) |
| 4 | 11.9 | 3.5 | Barbera (C₂) |
C₂ deviations [+0.25, −0.2] and [−0.25, +0.2] → S_W₂ = [[0.125, −0.1], [−0.1, 0.08]]
S_W = [[0.205, −0.14], [−0.14, 0.10]] C₁ के विचलन [+0.2, −0.1] और [−0.2, +0.1] → S_W₁ = [[0.08, −0.04], [−0.04, 0.02]]
C₂ के विचलन [+0.25, −0.2] और [−0.25, +0.2] → S_W₂ = [[0.125, −0.1], [−0.1, 0.08]]
S_W = [[0.205, −0.14], [−0.14, 0.10]] C₁ 的偏差 [+0.2, −0.1] 和 [−0.2, +0.1] → S_W₁ = [[0.08, −0.04], [−0.04, 0.02]]
C₂ 的偏差 [+0.25, −0.2] 和 [−0.25, +0.2] → S_W₂ = [[0.125, −0.1], [−0.1, 0.08]]
S_W = [[0.205, −0.14], [−0.14, 0.10]]
S_B = 2·(μ₁−μ)(μ₁−μ)ᵀ + 2·(μ₂−μ)(μ₂−μ)ᵀ
S_B ≈ [[3.42, −2.78], [−2.78, 2.25]] μ₁ − μ = [0.925, −0.75] और μ₂ − μ = [−0.925, 0.75]
S_B = 2·(μ₁−μ)(μ₁−μ)ᵀ + 2·(μ₂−μ)(μ₂−μ)ᵀ
S_B ≈ [[3.42, −2.78], [−2.78, 2.25]] μ₁ − μ = [0.925, −0.75],μ₂ − μ = [−0.925, 0.75]
S_B = 2·(μ₁−μ)(μ₁−μ)ᵀ + 2·(μ₂−μ)(μ₂−μ)ᵀ
S_B ≈ [[3.42, −2.78], [−2.78, 2.25]]
LD1 direction (unit length): [−0.46, −0.89] w = S_W⁻¹(μ₁ − μ₂) = S_W⁻¹·[1.85, −1.50] ≈ [−27.8, −53.9]
LD1 दिशा (unit length): [−0.46, −0.89] w = S_W⁻¹(μ₁ − μ₂) = S_W⁻¹·[1.85, −1.50] ≈ [−27.8, −53.9]
LD1 方向(单位长度):[−0.46, −0.89]
The same four bottles as in the table. Press Play, or use Next and Back.
Table वाली वही चार bottles. Play दबाएँ, या आगे और पीछे इस्तेमाल करें।
和表格中相同的四瓶酒。点击播放,或使用"下一步/上一步"。
The class means differ more in alcohol (1.85) than in malic acid (1.5), yet w puts more weight on malic acid. The reason is S_W⁻¹: inside each class, the two features move in opposite directions (negative covariance), and LDA "undoes" that shared within-class pattern. With only four points S_W is tiny and almost singular, so in real work you would use more data or shrinkage (Section 07).
Class means में alcohol (1.85) का फ़र्क malic acid (1.5) से ज़्यादा है, फिर भी w malic acid को ज़्यादा weight देता है। वजह है S_W⁻¹: हर class के अंदर दोनों features उल्टी दिशा में चलते हैं (negative covariance), और LDA उस साझा within-class pattern को "हटा" देता है। सिर्फ़ चार points के साथ S_W बहुत छोटा और लगभग singular है, इसलिए असली काम में आप ज़्यादा data या shrinkage (Section 07) इस्तेमाल करेंगे।
两类均值在酒精度上的差(1.85)比苹果酸(1.5)大,但 w 却给苹果酸更大的权重。原因在于 S_W⁻¹: 在每个类内部,两个特征朝相反方向变化(负协方差),LDA 会"抵消"这种类内共同的模式。 只有四个点时 S_W 非常小且几乎奇异,所以实际工作中应使用更多数据或收缩(第 07 节)。
LDA can produce at most C − 1 discriminant components, where C is the number of classes. A 3-class problem gives at most 2 LDA components. PCA can give up to min(n − 1, d) components. For classification, a 10-class dataset projected to 9 LDA dimensions is usually far more useful than 9 PCA dimensions, because LDA used the labels.
LDA ज़्यादा से ज़्यादा C − 1 discriminant components दे सकता है, जहाँ C classes की संख्या है। 3-class problem में ज़्यादा से ज़्यादा 2 LDA components. PCA, min(n − 1, d) तक components दे सकता है। Classification के लिए, 10-class dataset के 9 LDA dimensions आमतौर पर 9 PCA dimensions से कहीं ज़्यादा काम के होते हैं, क्योंकि LDA ने labels इस्तेमाल किए।
LDA 最多只能产生 C − 1 个判别分量(C 为类别数)。三分类问题最多 2 个 LDA 分量;PCA 最多可以有 min(n − 1, d) 个。 就分类而言,把 10 类数据投影到 9 个 LDA 维度,通常比 9 个 PCA 维度有用得多,因为 LDA 使用了标签。
LDA Assumptions — The Fine PrintLDA की मान्यताएँ — बारीक बातेंLDA 的假设 — 细则
If there are about as many features as samples, or more (d ≥ n), S_W becomes singular and cannot be inverted.
Fixes: apply PCA first, use solver='lsqr' or solver='eigen' with shrinkage='auto' in scikit-learn,
or use Regularised LDA, which adds a shrinkage term to S_W. (The default 'svd' solver does not crash, but it can still overfit badly.)
अगर features लगभग samples जितने या उनसे ज़्यादा हों (d ≥ n), तो S_W singular हो जाता है और उसका inverse नहीं निकलता।
इलाज: पहले PCA लगाएँ, scikit-learn में shrinkage='auto' के साथ solver='lsqr' या solver='eigen' इस्तेमाल करें,
या Regularised LDA इस्तेमाल करें जो S_W में shrinkage जोड़ता है। (Default 'svd' solver crash नहीं होता, पर फिर भी बुरी तरह overfit कर सकता है।)
如果特征数接近或超过样本数(d ≥ n),S_W 会变成奇异矩阵,无法求逆。
解决办法:先做 PCA;在 scikit-learn 中使用 solver='lsqr' 或 solver='eigen' 并设置 shrinkage='auto';
或者使用正则化 LDA,给 S_W 加上收缩项。(默认的 'svd' 求解器不会报错,但仍可能严重过拟合。)
Python Implementation — Wine DatasetPython Implementation — Wine DatasetPython 实现 — 葡萄酒数据集
LDA as a Dimensionality Reducer + VisualiserDimensionality Reducer + Visualiser के रूप में LDALDA 作为降维与可视化工具
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.datasets import load_wine
from sklearn.preprocessing import StandardScaler
import numpy as np
import matplotlib.pyplot as plt
# Load the UCI Wine dataset — 178 samples, 13 chemical features, 3 classes
wine = load_wine()
X, y = wine.data, wine.target
labels = wine.target_names # ['class_0', 'class_1', 'class_2']
# Standardise features (recommended for LDA)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# Fit LDA — project down to 2 components (C-1 = 3-1 = 2 max)
lda = LinearDiscriminantAnalysis(n_components=2)
X_lda = lda.fit_transform(X_scaled, y)
# Explained variance ratio of each discriminant
print("Explained variance ratio:", lda.explained_variance_ratio_)
print(f"LD1 explains: {lda.explained_variance_ratio_[0]*100:.1f}%")
print(f"LD2 explains: {lda.explained_variance_ratio_[1]*100:.1f}%")
# Plot the 2D projection
colors = ['#f87171', '#60a5fa', '#34d399']
plt.figure(figsize=(8,6))
for i, label in enumerate(labels):
mask = y == i
plt.scatter(X_lda[mask, 0], X_lda[mask, 1],
label=label, color=colors[i], alpha=0.8, edgecolors='white', s=60)
plt.xlabel('LD1'); plt.ylabel('LD2')
plt.title('Wine Dataset — LDA 2D Projection')
plt.legend(); plt.tight_layout(); plt.show()
LDA as a ClassifierClassifier के रूप में LDALDA 作为分类器
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.model_selection import cross_val_score, StratifiedKFold
from sklearn.metrics import classification_report, confusion_matrix
from sklearn.datasets import load_wine
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
# Build a proper pipeline: scale → LDA classifier
pipe = Pipeline([
('scaler', StandardScaler()),
('lda', LinearDiscriminantAnalysis()) # n_components=None → uses all C-1
])
wine = load_wine()
X, y = wine.data, wine.target
# 5-fold stratified cross-validation
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(pipe, X, y, cv=cv, scoring='accuracy')
print(f"CV Accuracy: {scores.mean():.4f} +/- {scores.std():.4f}")
# Full fit + detailed report
from sklearn.model_selection import train_test_split
X_tr, X_te, y_tr, y_te = train_test_split(
X, y, test_size=0.25, stratify=y, random_state=42
)
pipe.fit(X_tr, y_tr)
y_pred = pipe.predict(X_te)
print(classification_report(y_te, y_pred, target_names=wine.target_names))
print("Confusion Matrix:")
print(confusion_matrix(y_te, y_pred))
98.3% accuracy with 5-fold cross-validation, and 96% on a held-out test set (2 mistakes out of 45). LDA has only 13 features and 3 classes to work with, so its decisions live in just 2 discriminant dimensions. When LDA's assumptions roughly hold — as they do for chemical measurements — those 2 dimensions keep almost all the information needed to classify.
5-fold cross-validation के साथ 98.3% accuracy, और अलग रखे test set पर 96% (45 में से 2 गलतियाँ)। LDA के पास सिर्फ़ 13 features और 3 classes हैं, इसलिए उसके फ़ैसले सिर्फ़ 2 discriminant dimensions में होते हैं। जब LDA की मान्यताएँ मोटे तौर पर सही हों — जैसे chemical मापों में — तो ये 2 dimensions classify करने के लिए लगभग सारी ज़रूरी जानकारी रखते हैं।
5 折交叉验证准确率 98.3%,在留出的测试集上为 96%(45 个中错 2 个)。数据只有 13 个特征和 3 个类别, 所以 LDA 的决策只发生在 2 个判别维度中。当 LDA 的假设大致成立时(化学测量数据通常如此), 这 2 个维度就保留了分类所需的几乎全部信息。
Regularised LDA — Handling Small Samples & Many FeaturesRegularised LDA — कम Samples और ज़्यादा Features正则化 LDA — 应对小样本与高维特征
Standard LDA struggles when there are many features and few samples: S_W is estimated badly (or is singular). Regularised LDA adds a shrinkage penalty that pulls the within-class covariance toward a simple diagonal matrix:
जब features बहुत और samples कम हों, तो साधारण LDA मुश्किल में पड़ता है: S_W ठीक से estimate नहीं होता (या singular होता है)। Regularised LDA एक shrinkage penalty जोड़ता है जो within-class covariance को एक सरल diagonal matrix की ओर खींचती है:
特征多、样本少时,标准 LDA 会遇到困难:S_W 估计得很差(甚至是奇异的)。 正则化 LDA 加入一个收缩惩罚,把类内协方差拉向一个简单的对角矩阵:
shrinkage='auto' with solver='lsqr' or solver='eigen' to estimate the best λ automatically with the Ledoit-Wolf formula.solver='lsqr' या solver='eigen' के साथ shrinkage='auto' रखें, ताकि Ledoit-Wolf formula से सबसे अच्छा λ अपने आप मिले।solver='lsqr' 或 solver='eigen' 设置 shrinkage='auto',即可用 Ledoit-Wolf 公式自动估计最佳 λ。Two classes, 30 features, only 10 of them useful. Every point on the curves is a real experiment run in your browser: train on n samples per class, test on 600 new points, average of 8 repeats. Move the slider.
दो classes, 30 features, जिनमें सिर्फ़ 10 काम के हैं। Curves का हर point आपके browser में चला एक असली experiment है: हर class के n samples पर train, 600 नए points पर test, 8 बार का औसत। Slider चलाएँ।
两个类别、30 个特征,其中只有 10 个有用。曲线上的每个点都是在你浏览器中真实运行的实验:每类用 n 个样本训练,在 600 个新点上测试,重复 8 次取平均。拖动滑块。
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.datasets import make_classification
from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
# Simulate high-dimensional, small-sample scenario
# 200 samples, 150 features — classic small-n-large-p regime
X, y = make_classification(
n_samples=200, n_features=150, n_informative=30,
n_classes=3, n_clusters_per_class=1, random_state=42
)
# Standard LDA — likely to fail or be unstable
std_pipe = Pipeline([
('sc', StandardScaler()),
('lda', LinearDiscriminantAnalysis(solver='svd')) # SVD-based, no shrinkage
])
# Regularised LDA — Ledoit-Wolf automatic shrinkage
reg_pipe = Pipeline([
('sc', StandardScaler()),
('lda', LinearDiscriminantAnalysis(
solver='lsqr',
shrinkage='auto' # Ledoit-Wolf optimal shrinkage
))
])
std_scores = cross_val_score(std_pipe, X, y, cv=5, scoring='accuracy')
reg_scores = cross_val_score(reg_pipe, X, y, cv=5, scoring='accuracy')
print(f"Standard LDA CV: {std_scores.mean():.4f} +/- {std_scores.std():.4f}")
print(f"Regularised LDA CV: {reg_scores.mean():.4f} +/- {reg_scores.std():.4f}")
# Manual shrinkage tuning (grid search over λ)
from sklearn.model_selection import GridSearchCV
param_grid = {'lda__shrinkage': [0.0, 0.1, 0.3, 0.5, 0.7, 0.9, 1.0, 'auto']}
gs = GridSearchCV(reg_pipe, param_grid, cv=5, scoring='accuracy')
gs.fit(X, y)
print(f"Best shrinkage: {gs.best_params_}")
print(f"Best CV accuracy: {gs.best_score_:.4f}")
With 150 features and only 160 training rows per fold, plain LDA scores 48% (barely above the 33% of guessing among 3 classes).
Ledoit-Wolf shrinkage lifts it to 80% with one extra argument. The grid search agrees: 'auto' is as good as the best hand-picked value.
150 features और हर fold में सिर्फ़ 160 training rows के साथ, साधारण LDA 48% पर है (3 classes में अंदाज़े के 33% से बस थोड़ा ऊपर)।
Ledoit-Wolf shrinkage सिर्फ़ एक argument से इसे 80% तक ले जाता है। Grid search भी यही कहता है: 'auto' हाथ से चुनी सबसे अच्छी value जितना अच्छा है।
150 个特征、每折只有 160 行训练数据时,普通 LDA 只有 48%(三分类随机猜测是 33%,只高一点点)。
加一个参数使用 Ledoit-Wolf 收缩,准确率就升到 80%。网格搜索也给出同样结论:'auto' 与手工挑选的最佳值一样好。
Real-World Example — Face Recognition (Fisherfaces)Real-World Example — चेहरा पहचानना (Fisherfaces)实战案例 — 人脸识别(Fisherfaces)
from sklearn.datasets import fetch_olivetti_faces
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.decomposition import PCA
from sklearn.pipeline import Pipeline
from sklearn.model_selection import cross_val_score, StratifiedKFold
from sklearn.preprocessing import StandardScaler
# Olivetti Faces: 400 images, 4096 features, 40 classes
data = fetch_olivetti_faces(shuffle=True, random_state=42)
X, y = data.data, data.target # X shape: (400, 4096)
# Fisherfaces pipeline: PCA (Eigenfaces) → LDA (Fisherfaces)
fisherfaces = Pipeline([
('scale', StandardScaler()),
('pca', PCA(n_components=150, whiten=True, random_state=42)),
('lda', LinearDiscriminantAnalysis()) # max 39 components (40 classes - 1)
])
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(fisherfaces, X, y, cv=cv, scoring='accuracy')
print("=== Fisherfaces (PCA → LDA) ===")
print(f"CV Accuracy: {scores.mean()*100:.1f}% +/- {scores.std()*100:.1f}%")
print(f"Input dims: 4096 → PCA: 150 → LDA: 39")
# Compare: PCA-only nearest centroid
from sklearn.neighbors import NearestCentroid
pca_only = Pipeline([
('scale', StandardScaler()),
('pca', PCA(n_components=150, whiten=True, random_state=42)),
('clf', NearestCentroid())
])
pca_scores = cross_val_score(pca_only, X, y, cv=cv, scoring='accuracy')
print("\n=== PCA-only nearest centroid ===")
print(f"CV Accuracy: {pca_scores.mean()*100:.1f}% +/- {pca_scores.std()*100:.1f}%")
fetch_olivetti_faces() downloads the images the first time (about 1.5 MB), so run it on your own machine.
Our test machine had no internet access, so below we run the same PCA → LDA pipeline on the handwritten digits that ship with scikit-learn. These numbers are real.
fetch_olivetti_faces() पहली बार images download करता है (लगभग 1.5 MB), इसलिए इसे अपनी machine पर चलाएँ।
हमारी test machine पर internet नहीं था, इसलिए नीचे हम वही PCA → LDA pipeline scikit-learn के साथ आने वाले हाथ से लिखे digits पर चलाते हैं। ये numbers असली हैं।
fetch_olivetti_faces() 第一次运行时会下载图片(约 1.5 MB),请在你自己的电脑上运行。
我们的测试机器无法联网,所以下面在 scikit-learn 自带的手写数字数据上运行同样的 PCA → LDA 流水线。这些数字是真实的。
from sklearn.datasets import load_digits
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.decomposition import PCA
from sklearn.neighbors import NearestCentroid
from sklearn.pipeline import Pipeline
from sklearn.model_selection import cross_val_score, StratifiedKFold
from sklearn.preprocessing import StandardScaler
# Same idea, runs offline: 1,797 digit images (8×8 = 64 pixels), 10 classes
X, y = load_digits(return_X_y=True)
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
pca_only = Pipeline([
('scale', StandardScaler()),
('pca', PCA(n_components=30, random_state=42)),
('clf', NearestCentroid()), # PCA alone, then nearest class mean
])
pca_lda = Pipeline([
('scale', StandardScaler()),
('pca', PCA(n_components=30, random_state=42)),
('lda', LinearDiscriminantAnalysis()), # max 9 components (10 classes - 1)
])
for name, model in [("PCA-only nearest centroid", pca_only), ("PCA -> LDA (Fisher)", pca_lda)]:
s = cross_val_score(model, X, y, cv=cv, scoring='accuracy')
print(f"{name:26s}: {s.mean()*100:.1f}% +/- {s.std()*100:.1f}%")
print("Dims: 64 -> PCA: 30 -> LDA: 9")
PCA finds the directions of maximum variance, not maximum separability. Adding LDA after PCA raises accuracy from 87.9% to 93.6% on the digits, because LDA knows which class each image belongs to and rotates the space to separate the 10 digits. On faces the same idea is the famous Fisherfaces method (Belhumeur et al., 1997).
PCA सबसे ज़्यादा variance की दिशाएँ खोजता है, सबसे ज़्यादा अलगाव की नहीं। Digits पर PCA के बाद LDA जोड़ने से accuracy 87.9% से 93.6% हो जाती है, क्योंकि LDA जानता है कि हर image किस class की है और space को घुमाकर 10 digits को अलग करता है। चेहरों पर यही विचार मशहूर Fisherfaces तरीका है (Belhumeur et al., 1997)।
PCA 找的是方差最大的方向,而不是区分度最大的方向。在手写数字上,在 PCA 之后加上 LDA,准确率从 87.9% 提高到 93.6%, 因为 LDA 知道每张图属于哪个类别,并旋转空间把 10 个数字分开。用在人脸上,这就是著名的 Fisherfaces 方法(Belhumeur 等,1997)。
LDA vs PCA — When to Use EachLDA vs PCA — कब कौन साLDA 与 PCA — 各自何时使用
| Propertyगुण属性 | PCA | LDA |
|---|---|---|
| Typeप्रकार类型 | UnsupervisedUnsupervised无监督 | SupervisedSupervised有监督 |
| Objectiveलक्ष्य目标 | Maximise total varianceकुल variance सबसे ज़्यादा最大化总方差 | Maximise class separabilityClasses का अलगाव सबसे ज़्यादा最大化类别可分性 |
| Uses class labels?Class labels इस्तेमाल करता है?使用类别标签? | Noनहीं否 | Yesहाँ是 |
| Max componentsअधिकतम components最大分量数 | min(n−1, d) | C−1 (number of classes minus 1)C−1 (classes की संख्या − 1)C−1(类别数减 1) |
| Best forकिसके लिए सबसे अच्छा最适合 | Visualisation, noise reduction, no labelsVisualisation, noise कम करना, बिना labels可视化、降噪、无标签 | Classification preprocessing, supervised DRClassification preprocessing, supervised DR分类预处理、有监督降维 |
| Assumes Gaussian?Gaussian मानता है?假设高斯分布? | Noनहीं否 | Yes (within each class, as a classifier)हाँ (हर class के अंदर, classifier के रूप में)是(作为分类器时,每类内部) |
| Sensitive to outliers?Outliers से प्रभावित?对异常值敏感? | Moderatelyकुछ हद तक中等 | Moderatelyकुछ हद तक中等 |
| Works with many classes?कई classes के साथ?适用于多类别? | Yes — labels not usedहाँ — labels इस्तेमाल नहीं होते是 —— 不使用标签 | Yes — but only C−1 axesहाँ — पर सिर्फ़ C−1 axes是 —— 但只有 C−1 条轴 |
| When to chooseकब चुनें何时选择 | Feature compression, exploring dataFeature compression, data को समझना特征压缩、探索数据 | Classification, face recognition, biometricsClassification, चेहरा पहचान, biometrics分类、人脸识别、生物识别 |
PCA and LDA work well together. Use PCA first to remove noise and fix the singularity problem (especially when d > n), then LDA to find the class-separating axes. This PCA → LDA chain is used in face recognition, medical imaging and text classification. The PCA step typically keeps ~100–300 components; LDA then extracts the C − 1 truly class-separating directions from those.
PCA और LDA साथ मिलकर अच्छा काम करते हैं। Noise हटाने और singularity समस्या ठीक करने के लिए पहले PCA (ख़ासकर जब d > n), फिर classes को अलग करने वाले axes के लिए LDA. यह PCA → LDA chain चेहरा पहचानने, medical imaging और text classification में इस्तेमाल होती है। PCA step आमतौर पर ~100–300 components रखता है; फिर LDA उनमें से C − 1 असली अलग करने वाली दिशाएँ निकालता है।
PCA 和 LDA 配合得很好。先用 PCA 去除噪声、解决奇异性问题(尤其当 d > n 时), 再用 LDA 找出能分开类别的坐标轴。这条 PCA → LDA 流水线广泛用于人脸识别、医学影像和文本分类。 PCA 一般保留约 100–300 个分量,LDA 再从中提取 C − 1 个真正区分类别的方向。
LDA vs QDA — Covariance AssumptionsLDA vs QDA — Covariance की मान्यताएँLDA 与 QDA — 协方差假设
When LDA's equal-covariance assumption fails, Quadratic Discriminant Analysis (QDA) can help. QDA estimates a separate covariance matrix Σ_c for each class, which gives quadratic (curved) decision boundaries instead of straight ones.
जब LDA की बराबर-covariance वाली मान्यता टूटती है, तो Quadratic Discriminant Analysis (QDA) मदद कर सकता है। QDA हर class के लिए अलग covariance matrix Σ_c निकालता है, जिससे सीधी की जगह quadratic (मुड़ी हुई) decision सीमाएँ बनती हैं।
当 LDA 的等协方差假设不成立时,二次判别分析(QDA)可以帮忙。QDA 为每个类单独估计协方差矩阵 Σ_c, 得到的是二次(弯曲)的决策边界,而不是直线。
Red class A is a small round blob. Change the size and tilt of blue class B. The background shows which class the chosen model predicts; the bright line is its boundary. Accuracies are measured on 600 new points.
लाल class A एक छोटा गोल ढेर है। नीली class B का आकार और झुकाव बदलें। Background दिखाता है कि चुना गया model कौन सी class predict करता है; चमकीली line उसकी सीमा है। Accuracy 600 नए points पर मापी गई है।
红色 A 类是一个小圆团。改变蓝色 B 类的大小和倾斜。背景颜色表示所选模型预测的类别,亮线是它的决策边界。准确率在 600 个新点上测量。
Try size 2.5: class B becomes a wide cloud around A. QDA draws a closed curve around A and pulls ahead; LDA's straight line cannot.
आकार 2.5 आज़माएँ: class B, A के चारों ओर एक चौड़ा बादल बन जाता है। QDA, A के चारों ओर एक बंद curve बनाकर आगे निकल जाता है; LDA की सीधी line ऐसा नहीं कर सकती।
试试大小 2.5:B 类变成包围 A 的一大片点云。QDA 会在 A 周围画出一条闭合曲线并明显领先,而 LDA 的直线做不到。
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis, QuadraticDiscriminantAnalysis
from sklearn.datasets import make_classification
from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
# Generate data with UNEQUAL class covariances — LDA assumption violated
import numpy as np
np.random.seed(42)
X_c1 = np.random.multivariate_normal([0,0], [[1,0],[0,1]], 200) # circular
X_c2 = np.random.multivariate_normal([3,3], [[4,2],[2,4]], 200) # elongated ellipse
X_c3 = np.random.multivariate_normal([6,0], [[0.5,0],[0,3]],200) # narrow tall
X = np.vstack([X_c1, X_c2, X_c3])
y = np.array([0]*200 + [1]*200 + [2]*200)
lda_pipe = Pipeline([('sc', StandardScaler()), ('clf', LinearDiscriminantAnalysis())])
qda_pipe = Pipeline([('sc', StandardScaler()), ('clf', QuadraticDiscriminantAnalysis())])
lda_cv = cross_val_score(lda_pipe, X, y, cv=5).mean()
qda_cv = cross_val_score(qda_pipe, X, y, cv=5).mean()
print(f"LDA CV Accuracy: {lda_cv:.4f} (assumption violated)")
print(f"QDA CV Accuracy: {qda_cv:.4f} (no equal-covariance assumption)")
QDA wins, but only by about 2 points (91.2% vs 89.0%), because the three classes still overlap a lot. When class shapes differ more, or classes overlap less, the gap grows — try it in the simulator above. QDA pays for its flexibility with many more parameters, so it needs more data.
QDA जीतता है, पर सिर्फ़ लगभग 2 points से (91.2% vs 89.0%), क्योंकि तीनों classes अब भी काफ़ी मिली हुई हैं। जब classes के आकार ज़्यादा अलग हों, या classes कम मिली हों, तो फ़र्क बढ़ता है — ऊपर simulator में आज़माएँ। QDA अपनी लचक की कीमत बहुत ज़्यादा parameters से चुकाता है, इसलिए उसे ज़्यादा data चाहिए।
QDA 赢了,但只领先约 2 个百分点(91.2% 对 89.0%),因为三个类别仍有大量重叠。类别形状差异越大、重叠越少,差距就越大 —— 在上面的模拟器里试试。 QDA 的灵活性是用更多参数换来的,所以它需要更多数据。
| Aspectपहलू方面 | LDA | QDA |
|---|---|---|
| Decision boundaryDecision सीमा决策边界 | Linear hyperplaneLinear hyperplane线性超平面 | Quadratic surfaceQuadratic surface二次曲面 |
| CovarianceCovariance协方差 | One shared Σएक साझा Σ一个共享的 Σ | One Σ per classहर class का अलग Σ每类一个 Σ |
| BiasBias偏差 | Higher (if covariances differ)ज़्यादा (अगर covariances अलग हों)较高(如果协方差不同) | Lowerकम较低 |
| VarianceVariance方差 | Lower (fewer parameters)कम (कम parameters)较低(参数少) | Higherज़्यादा较高 |
| Needs more data?ज़्यादा data चाहिए?需要更多数据? | No — fewer parametersनहीं — कम parameters不 —— 参数较少 | Yes — d(d+1)/2 covariance values per classहाँ — हर class के d(d+1)/2 covariance values是 —— 每类 d(d+1)/2 个协方差参数 |
| Dimensionality reduction?Dimensionality reduction?能降维吗? | Yes — up to C−1 componentsहाँ — C−1 components तक能 —— 最多 C−1 个分量 | Noनहीं不能 |
| Choose when…कब चुनें…何时选择… | Classes have roughly the same shapeClasses का आकार लगभग एक जैसा हो各类形状大致相同 | Classes have very different spreads/shapes and you have enough dataClasses के फैलाव/आकार बहुत अलग हों और data काफ़ी हो各类散布/形状差异很大且数据充足 |
Medical Diagnosis Example — Breast Cancer LDAMedical Diagnosis Example — Breast Cancer LDA医学诊断案例 — 乳腺癌 LDA
from sklearn.datasets import load_breast_cancer
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.model_selection import cross_val_score, StratifiedKFold
from sklearn.metrics import roc_auc_score, classification_report
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
import numpy as np
bc = load_breast_cancer()
X, y = bc.data, bc.target # 569 samples, 30 features, 2 classes
pipe = Pipeline([
('sc', StandardScaler()),
('lda', LinearDiscriminantAnalysis())
])
# 10-fold stratified CV — important for medical data
cv = StratifiedKFold(n_splits=10, shuffle=True, random_state=42)
acc_scores = cross_val_score(pipe, X, y, cv=cv, scoring='accuracy')
auc_scores = cross_val_score(pipe, X, y, cv=cv, scoring='roc_auc')
print(f"CV Accuracy: {acc_scores.mean():.4f} +/- {acc_scores.std():.4f}")
print(f"CV AUC-ROC: {auc_scores.mean():.4f} +/- {auc_scores.std():.4f}")
# Full fit for feature weights (discriminant coefficients)
pipe.fit(X, y)
coef = pipe.named_steps['lda'].coef_[0]
top_idx = np.argsort(np.abs(coef))[::-1][:5]
print("\nTop 5 coefficients - plain LDA (signed):")
for i in top_idx:
print(f" {bc.feature_names[i]:25s}: {coef[i]:+8.2f}")
# Same model with shrinkage: much smaller, more stable weights
shr = Pipeline([('sc', StandardScaler()),
('lda', LinearDiscriminantAnalysis(solver='lsqr', shrinkage='auto'))]).fit(X, y)
coef_s = shr.named_steps['lda'].coef_[0]
print("\nTop 5 coefficients - shrinkage LDA (signed):")
for i in np.argsort(np.abs(coef_s))[::-1][:5]:
print(f" {bc.feature_names[i]:25s}: {coef_s[i]:+8.2f}")
print(f"\nShrinkage LDA CV accuracy: {cross_val_score(shr, X, y, cv=cv).mean():.4f}")
Look at plain LDA: worst radius −17.9 and mean radius +14.5. These two features measure almost the same thing, so their huge weights with opposite signs mostly cancel out. That is a sign of multicollinearity, not of importance. With shrinkage the weights become small and stable (top weight 2.6) while accuracy stays the same (95.8%). (Class 0 = malignant here, so a negative weight on worst radius means "bigger radius → more malignant".) For importance, use shrinkage weights, permutation importance or SHAP.
साधारण LDA देखें: worst radius −17.9 और mean radius +14.5. ये दोनों features लगभग एक ही चीज़ मापते हैं, इसलिए उल्टे signs वाले उनके बड़े weights ज़्यादातर एक-दूसरे को काट देते हैं। यह multicollinearity का संकेत है, importance का नहीं। Shrinkage के साथ weights छोटे और stable हो जाते हैं (सबसे बड़ा weight 2.6) और accuracy वैसी ही रहती है (95.8%)। (यहाँ class 0 = malignant है, इसलिए worst radius पर negative weight का मतलब "बड़ा radius → ज़्यादा malignant"।) Importance के लिए shrinkage weights, permutation importance या SHAP इस्तेमाल करें।
看普通 LDA:worst radius 为 −17.9,mean radius 为 +14.5。这两个特征几乎在测量同一件事, 所以它们符号相反的巨大权重大部分互相抵消了。这是多重共线性的信号,而不是重要性。 使用收缩后,权重变得小而稳定(最大权重 2.6),准确率不变(95.8%)。(这里类别 0 = 恶性, 所以 worst radius 的负权重表示"半径越大 → 越可能恶性"。)要看特征重要性,请用收缩后的权重、置换重要性或 SHAP。
LDA Solvers in Scikit-LearnScikit-Learn में LDA SolversScikit-Learn 中的 LDA 求解器
shrinkage, including 'auto' (Ledoit-Wolf). Efficient for classification. It cannot be used for dimensionality reduction: transform() is not available.shrinkage चलता है, 'auto' (Ledoit-Wolf) समेत। Classification के लिए कुशल। Dimensionality reduction के लिए इस्तेमाल नहीं हो सकता: transform() उपलब्ध नहीं।shrinkage,包括 'auto'(Ledoit-Wolf)。用于分类时很高效,但不能用于降维:没有 transform()。| Solver | Shrinkage?Shrinkage?支持收缩? | predict_proba? | transform()? | Best useसबसे अच्छा इस्तेमाल最佳用途 |
|---|---|---|---|---|
svd (default) | Noनहीं否 | Yesहाँ是 | Yesहाँ是 | Standard LDA, many featuresसाधारण LDA, बहुत features标准 LDA、特征多 |
lsqr | Yesहाँ是 | Yesहाँ是 | Noनहीं否 | Classification with small n (shrinkage)कम n पर classification (shrinkage)小样本分类(收缩) |
eigen | Yesहाँ是 | Yesहाँ是 | Yesहाँ是 | Need shrinkage and dimensionality reductionShrinkage और dimensionality reduction दोनों同时需要收缩和降维 |
LDA for Multi-Class — IrisMulti-Class के लिए LDA — Iris多类别 LDA — 鸢尾花
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import cross_val_score
from sklearn.pipeline import Pipeline
import numpy as np
iris = load_iris()
X, y = iris.data, iris.target # 150 samples, 4 features, 3 classes
pipe = Pipeline([
('sc', StandardScaler()),
('lda', LinearDiscriminantAnalysis(n_components=2))
])
# Cross-validated accuracy
scores = cross_val_score(pipe, X, y, cv=10)
print(f"CV Accuracy: {scores.mean():.4f} +/- {scores.std():.4f}")
# Fit and show explained variance per discriminant
pipe.fit(X, y)
lda = pipe.named_steps['lda']
print(f"\nLD1 explains {lda.explained_variance_ratio_[0]*100:.1f}% of between-class variance")
print(f"LD2 explains {lda.explained_variance_ratio_[1]*100:.1f}% of between-class variance")
# Class separation quality: Mahalanobis-like distances between centroids in LDA space
X_lda = lda.transform(pipe.named_steps['sc'].transform(X))
centroids = [X_lda[y==i].mean(axis=0) for i in range(3)]
for i in range(3):
for j in range(i+1, 3):
d = np.linalg.norm(np.array(centroids[i])-np.array(centroids[j]))
print(f"Distance {iris.target_names[i]} ↔ {iris.target_names[j]}: {d:.3f}")
On Iris, a single discriminant (LD1) captures 99.1% of the between-class variance. In the LDA space setosa is far from the other two (distances 9.6 and 13.5), while versicolor and virginica are much closer (4.2) — exactly where the few mistakes happen. One dimension would classify almost as well as two.
Iris पर एक अकेला discriminant (LD1) between-class variance का 99.1% पकड़ता है। LDA space में setosa बाकी दोनों से बहुत दूर है (दूरी 9.6 और 13.5), जबकि versicolor और virginica काफ़ी पास हैं (4.2) — गलतियाँ ठीक यहीं होती हैं। एक dimension लगभग दो जितना अच्छा classify करेगा।
在鸢尾花数据上,单个判别方向(LD1)就捕获了 99.1% 的类间方差。在 LDA 空间中,setosa 离另外两类很远(距离 9.6 和 13.5), 而 versicolor 与 virginica 很近(4.2)—— 少数错误正好发生在这里。只用一维分类,效果几乎和二维一样好。
When to Use (and Avoid) LDALDA कब इस्तेमाल करें (और कब नहीं)什么时候用(以及不用)LDA
priors deliberately (see the simulator below).priors सोच-समझकर set करें (नीचे simulator देखें)।priors(见下方模拟器)。Two classes on one LDA axis, same spread. Make class B rare and watch the cut move. Then switch to equal priors.
एक LDA axis पर दो classes, एक जैसा फैलाव। Class B को दुर्लभ बनाएँ और cut को खिसकते देखें। फिर बराबर priors पर जाएँ।
一条 LDA 轴上的两个类别,散布相同。让 B 类变得稀少,观察切点如何移动;然后切换到相等先验。
Golden Rulesसुनहरे नियम黄金法则
StandardScaler in a Pipeline: it never hurts and makes shrinkage fair across features.StandardScaler को Pipeline में रखें: इससे कभी नुकसान नहीं होता और shrinkage सभी features पर बराबर लगता है।StandardScaler 放进 Pipeline:没有坏处,还能让收缩对所有特征一视同仁。solver='lsqr' (classification) or solver='eigen' (if you also need transform()).solver='lsqr' (classification) या solver='eigen' (अगर transform() भी चाहिए) इस्तेमाल करें।solver='lsqr',如果还需要 transform() 就用 solver='eigen'。priors=[0.5, 0.5] (or values that match the real world) and check the recall of each class.priors=[0.5, 0.5] (या असली दुनिया से मेल खाती values) set करें और हर class का recall जाँचें।priors=[0.5, 0.5](或符合真实情况的值),并检查每个类别的召回率。lda.predict(X) directly. It is very fast and often competitive with much heavier models. Always try LDA (and logistic regression) as a baseline before random forests or gradient boosting.lda.predict(X) चला सकते हैं। यह बहुत तेज़ है और अक्सर बहुत भारी models से टक्कर लेता है। Random forests या gradient boosting से पहले हमेशा LDA (और logistic regression) को baseline के रूप में आज़माएँ।lda.predict(X)。它非常快,常常能与重得多的模型一较高下。在用随机森林或梯度提升之前,先用 LDA(和逻辑回归)做基线。