1. 왜 등장했는가
PCA처럼 고차원 데이터를 저차원으로 줄이고 싶지만, PCA는 분산을 기준으로 축을 찾아
클래스 구분에 최적화되지 않는 문제가 있었습니다.
LDA는 클래스 간 분산을 최대화하고 클래스 내 분산을 최소화하는 방향으로 투영해
분류에 특화된 차원 축소와 분류를 동시에 수행합니다. (Fisher, 1936)
2. 핵심 아이디어 — 클래스를 가장 잘 구분하는 축 찾기
LDA는 본질적으로 같은 반 학생끼리는 뭉치고, 다른 반 학생은 멀어지도록 투영 방향을 찾습니다.
1
2
3
4
5
6
7
8
9
10
11
12
| 2D 데이터를 1D로 투영하는 두 가지 방법:
투영 방향 A (PCA 방식): 투영 방향 B (LDA 방식):
분산이 최대인 방향 클래스 구분이 최대인 방향
○ ○ ○ ○ ○ ○
● ● ● ──→ 투영 → ● ● ● ──→ 투영 →
○ ○ ●● ○ ○ ○
1D 결과: ○●○●●○○ (섞임) 1D 결과: ○○○○ | ●●●● (분리!)
LDA는 클래스가 잘 분리되는 방향 B를 선택합니다.
|
3. 실제 예시로 보기 (분류 / 차원 축소)
예시 1 — 타이타닉 생존 예측 (분류)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| 훈련 데이터:
┌────────┬────────┬─────┬──────────┬──────────┐
│ 이름 │ 성별 │ 나이 │ 객실 등급 │ 생존 여부 │
├────────┼────────┼─────┼──────────┼──────────┤
│ Alice │ 0 │ 29 │ 1 │ ✅ │
│ Bob │ 1 │ 31 │ 3 │ ❌ │
│ Carol │ 0 │ 45 │ 3 │ ✅ │
│ Dave │ 1 │ 12 │ 2 │ ✅ │
│ Eve │ 1 │ 38 │ 3 │ ❌ │
└────────┴────────┴─────┴──────────┴──────────┘
LDA 학습:
클래스 내 분산 (SW): 같은 생존/사망 그룹 안에서 퍼짐 → 작게
클래스 간 분산 (SB): 생존 vs 사망 그룹 사이 거리 → 크게
→ SW⁻¹SB를 최대화하는 방향 w 찾기
새 승객 예측:
투영값 = w^T × [성별, 나이, 등급]
→ 투영값이 임계값보다 크면 ✅ 생존, 작으면 ❌ 사망
|
예시 2 — 차원 축소 (분류 전처리)
1
2
3
4
5
6
7
| 원본 특성: 10개 → LDA → 최대 (클래스 수 - 1)개 차원으로 축소
이진 분류 (생존/사망):
10개 특성 → LDA → 1개 축 (판별 함수)
3클래스 분류 (꽃 종류 3개):
4개 특성 → LDA → 2개 축 (최대 3-1=2)
|
4. 알고리즘 구성 요소
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| ┌─────────────────────────────────────┐
│ 클래스별 평균 μₖ 계산 │ ← 각 클래스의 중심
└─────────────────┬───────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 클래스 내 분산행렬 SW 계산 │ ← 같은 클래스 안에서 퍼짐
└─────────────────┬───────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 클래스 간 분산행렬 SB 계산 │ ← 클래스 중심 간 거리
└─────────────────┬───────────────────┘
│
▼
┌─────────────────────────────────────┐
│ SW⁻¹SB 고유벡터 계산 │ ← 최적 투영 방향
└─────────────────┬───────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 데이터 투영 및 분류 │ ← 새 샘플을 같은 방향으로 투영
└─────────────────────────────────────┘
|
| 구성 요소 | 설명 | 비유 |
|---|
| 클래스 내 분산 SW | 같은 클래스 안에서의 퍼짐 | 같은 팀 선수들 사이 거리 |
| 클래스 간 분산 SB | 클래스 중심들 사이 거리 | 두 팀 중심 사이 거리 |
| 판별 함수 | SW⁻¹SB를 최대화하는 투영 방향 | 두 팀이 가장 잘 분리되는 관점 |
| 사전 확률 πₖ | 각 클래스의 비율 | 각 팀의 인원 비율 |
5. 어떻게 최적 방향을 찾는가
5-1. 목적 함수 (Fisher’s Criterion)
\[J(w) = \frac{w^T S_B w}{w^T S_W w}\]
1
2
3
4
| 분자: 투영 후 클래스 간 분산 → 크게!
분모: 투영 후 클래스 내 분산 → 작게!
→ 이 비율을 최대화하는 w를 구하는 것이 LDA
|
\[w = S_W^{-1}(\mu_1 - \mu_2)\]
1
2
3
4
5
| 이진 분류의 경우:
w = (클래스 내 공분산 행렬)^{-1} × (두 클래스 평균의 차이)
직관: 두 클래스 중심을 잇는 방향으로 투영하되
클래스 내 분산으로 스케일 조정
|
5-3. LDA의 가정
1
2
3
4
5
6
| 1. 각 클래스가 정규분포를 따름 (Gaussian Distribution)
2. 모든 클래스가 동일한 공분산 행렬을 가짐 (Homoscedasticity)
3. 특성들이 서로 독립적 (Naive Bayes보다 완화된 가정)
가정이 맞을 때: LDA가 이론적 최적 선형 분류기
가정이 틀릴 때: Logistic Regression이나 트리 기반 모델 고려
|
6. LDA 장・단점
6-1. ✅ LDA 장점
1
2
3
4
5
6
7
8
9
10
11
| 1. 분류 + 차원 축소 동시 수행
→ 특성이 많을 때 (클래스 수 - 1)개 축으로 효율적으로 축소
2. 해석 가능성
→ 각 판별 함수의 계수로 어떤 특성이 분류에 중요한지 파악
3. 빠른 학습과 예측
→ 닫힌 형태의 해 존재 → 반복 학습 불필요
4. 소규모 데이터에 강함
→ 파라미터가 적어 적은 데이터로도 안정적
|
6-2. ❌ LDA가 약한 상황
1
2
3
4
5
6
7
8
| 1. 비선형 패턴
→ 선형 결정 경계만 가능 → QDA 또는 비선형 모델 고려
2. 클래스 불균형
→ 사전 확률이 클래스 비율에 영향 → class_weight 조정 필요
3. 가정 위반
→ 정규분포 가정, 동분산 가정이 맞지 않으면 성능 저하
|
6-2-1. 클래스 분산이 다를 때 (이분산)
LDA의 동분산 가정이 위반되는 경우입니다.
1
2
3
4
5
| 클래스 0 (사망): 특성 분산이 큼 ← 다양한 패턴
클래스 1 (생존): 특성 분산이 작음 ← 특정 패턴에 집중
LDA: 동분산 가정 → 경계가 왜곡될 수 있음
QDA: 각 클래스별 공분산 행렬 추정 → 더 정확한 경계
|
해결책 :
1
2
3
4
5
6
7
8
9
10
11
12
13
| # 방법 1: QDA (이분산 허용)
from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis
qda = QuadraticDiscriminantAnalysis()
qda.fit(X_train, y_train)
# 방법 2: LDA solver 변경
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
lda = LinearDiscriminantAnalysis(
solver='lsqr', # 'svd', 'lsqr', 'eigen'
shrinkage='auto' # 공분산 행렬 정규화 (소규모 데이터에 유용)
)
lda.fit(X_train, y_train)
print(f"설명된 분산: {lda.explained_variance_ratio_}")
|
7. 한눈에 요약
| 항목 | 내용 |
|---|
| 알고리즘 유형 | 지도학습 / 분류 & 차원 축소 |
| 핵심 아이디어 | 클래스 간 분산 ↑, 클래스 내 분산 ↓ 방향으로 투영 |
| 결정 경계 | 선형 |
| 최대 축소 차원 | 클래스 수 - 1 |
| 스케일링 필요? | ✅ 권장 (공분산 계산에 영향) |
| 핵심 파라미터 | n_components, solver, shrinkage |
| 실전 사용 | 차원 축소 전처리, 소규모 데이터, 해석 중요할 때 |
8. 다른 알고리즘과 무엇이 다른가
PCA vs LDA
1
2
3
4
| PCA (비지도): LDA (지도):
클래스 정보 없이 분산 최대 방향 클래스 정보를 이용해
→ 데이터 전체 구조 보존 클래스 구분 최대 방향
→ 분류에 최적화 안 됨 → 분류에 최적화됨
|
| 항목 | PCA | LDA |
|---|
| 학습 방식 | 비지도 | 지도 |
| 목적 | 분산 최대화 | 클래스 분리 최대화 |
| 최대 차원 | min(n, p) - 1 | 클래스 수 - 1 |
| 분류 최적화 | ❌ | ✅ |
9. 코드로 보기 — 타이타닉 생존 예측
1
2
| from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.preprocessing import StandardScaler
|
9-1. 전처리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| import pandas as pd
from sklearn.model_selection import train_test_split
titanic = pd.read_csv('./Data/Titanic.csv')
titanic['FamSize'] = titanic['SibSp'] + titanic['Parch']
use_cols = ['Survived', 'Pclass', 'Sex', 'Age', 'FamSize', 'Fare', 'Embarked']
titanic = titanic[use_cols].dropna(subset=['Age'])
titanic['Age'] = titanic['Age'].astype(int)
titanic = pd.get_dummies(titanic, columns=['Pclass', 'Sex', 'Embarked'], drop_first=True)
y = titanic['Survived']
X = titanic.drop('Survived', axis=1)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=0)
# ✅ LDA는 공분산 행렬 계산에 스케일 영향 → 스케일링 권장
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
|
Note: LDA는 공분산 행렬을 직접 계산하므로 특성 간 스케일 차이가 결과에 영향을 줍니다. StandardScaler를 사용하는 것을 권장합니다.
9-2. 모델 학습
1
2
3
4
5
6
| lda = LinearDiscriminantAnalysis(
n_components=1, # 이진 분류: 최대 1개 판별 축
solver='svd', # 'svd'(기본), 'lsqr', 'eigen'
shrinkage=None # 'auto' 또는 0~1 수치 (소규모 데이터에 유용)
)
lda.fit(X_train, y_train)
|
| Parameter | Default | 역할 | 과적합 방향 |
|---|
n_components | None | 투영 축 수 (최대: 클래스 수-1) | - |
solver | 'svd' | 공분산 행렬 계산 방법 | - |
shrinkage | None | 공분산 행렬 정규화 | None일수록 과적합 ↑ |
n_components : 차원 축소 시 유지할 축 수- 값 변화별 효과
- 이진 분류는 최대 1개, 3클래스는 최대 2개
- 분류가 목적이면 기본값(None) 유지
- 차원 축소가 목적이면 작은 값 지정
shrinkage : 공분산 행렬 정규화 강도- 값 변화별 효과
'auto' → Ledoit-Wolf 추정량으로 자동 계산 (소규모 데이터 권장)0~1 수치 → 직접 지정solver='lsqr' 또는 'eigen'과 함께 사용
9-3. 평가
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| from sklearn.metrics import (
accuracy_score, confusion_matrix,
classification_report, roc_auc_score
)
pred = lda.predict(X_test)
pred_prob = lda.predict_proba(X_test)[:, 1]
cfx = confusion_matrix(y_test, pred)
sensitivity = cfx[1, 1] / (cfx[1, 0] + cfx[1, 1])
specificity = cfx[0, 0] / (cfx[0, 0] + cfx[0, 1])
roc_auc = roc_auc_score(y_test, pred_prob)
print(f"Accuracy : {accuracy_score(y_test, pred) * 100:.2f}%")
print(f"Sensitivity : {sensitivity * 100:.2f}%")
print(f"Specificity : {specificity * 100:.2f}%")
print(f"ROC AUC : {roc_auc:.4f}")
print()
print(classification_report(y_test, pred, target_names=['Died (0)', 'Survived (1)']))
|
9-4. 차원 축소로 활용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| import matplotlib.pyplot as plt
# 1D로 투영 (이진 분류)
X_lda = lda.transform(X_train)
plt.figure(figsize=(8, 4))
plt.scatter(X_lda[y_train==0, 0], [0]*sum(y_train==0),
color='steelblue', alpha=0.6, label='Died (0)', s=40)
plt.scatter(X_lda[y_train==1, 0], [0]*sum(y_train==1),
color='tomato', alpha=0.6, label='Survived (1)', s=40)
plt.xlabel('LDA 판별 축 1')
plt.title('LDA 투영 결과 — 두 클래스 분리 시각화')
plt.legend(fontsize=10)
plt.yticks([])
plt.grid(True, alpha=0.2)
plt.tight_layout()
plt.show()
|
투영된 1D 축에서 두 클래스가 얼마나 잘 분리되는지 확인할 수 있습니다.