# [데이터 전처리] 데이터 불균형

## 데이터 불균형

---

### 불균형 데이터 Imbalanced Data

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/180837_rJo3M5otRYfmTPbPW7?q=80&s=1280x180&t=outside&f=webp)

- 분류 문제에서 가장 큰 문제는 불균형 데이터를 다루는 것이다.

- 데이터 불균형은 클래스 분포를 예측해야 하는 분류 문제에서 예측 라벨 값의 분포가 100:1, 200:1 등으로 불균형하게 나타나는 상태를 말한다.

- 불균형 데이터를 해결하지 않으면 과적합 문제가 발생할 수 있다.

- 이 문제를 효율적으로 다루기 위해서는 문제를 제대로 이해하고 전략을 잘 세워야 한다.

### 불균형 데이터의 원인

**실제 세계에서의 분배**

- 실제 세계에서 불균형하게 데이터가 구성

- 하나의 클래스가 다른 클래스에 비해서 덜 발생하는 경우

- 불법적인 신용 카드 거래는 정상적인 거래에 비해 훨씬 더 적게 일어난다.

**Data Collection bias**

- 데이터 수집 편향이 데이터 불균형의 원인

- 어떤 질병이 끼치는 영향 중 극히 일부에만 집중해서 질병 발생 설문조사를
- 하는 경우, 대부분은 질병을 경험하지 못했다고 함

**Event rarity**

- 산업 환경에서 장비 고장같은 이벤트의 희귀성은 데이터 불균형을 초래함

### 불균형 데이터의 영향

**Training bias**

- 불균형 데이터에 대해 훈련된 모델은 편향된 class를 선호

- 따라서 소수 class 데이터를 분류해야 할 경우 저조한 성적을 보임

**잘못된 평가** 

- 정확도 같은 전통적인 측정 기준을 기만할 수 있음

- 높은 정확도를 보이지만 실질적으로 비효율

- 질병 발병과 그렇지 않은 경우의 데이터가 9999 : 1의 편향을 보일 경우,

    - 모델을 training할 때 거의 다 질병이 발생하지 않았다라고 판단

    - → 그렇게 되면 어짜피 정답률 높으니까. BUT 우리가 집중해야 할 것은 질병 걸린 환자

**Failure to detect rare events**

- 의료 진단이나 사기 탐지와 같은 영역에서 희귀하지만 중요한 사건을 놓치면 심각한 결과를 초래할 수 있음.

## Resample

---

- 데이터 분포를 수정하는 기술

- Oversampling과 Undersampling이 있음

### **Undersampling**

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/180948_N1WaoCrb3JlQAZm0fc?q=80&s=1280x180&t=outside&f=webp)

- Down Sampling라고도 부르며

- 데이터의 분포가 높은 값을 낮은 값으로 맞춰주는 작업

- 장점: 유의미한 데이터만 남길 수 있다.

- 단점: 정보가 유실되는 문제 발생

- 데이터셋의 크기를 크게 줄이지 않고 균형을 맞추기 위해 다른 기법과 함께 사용하는 경우가 많음

**Random Under Sampling**

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/180959_Sz1s7tmNXZjCctC31Q?q=80&s=1280x180&t=outside&f=webp)

- 무작위 샘플링

- 원하는 균형을 맞출 때까지 랜덤하게 다수 클래스의 인스턴스 제거

```javascript
n0 = 200; n1 = 20
rv1 = sp.stats.multivariate_normal([-1, 0], [[1, 0], [0, 1]])
rv2 = sp.stats.multivariate_normal([+1, 0], [[1, 0], [0, 1]])
X0 = rv1.rvs(n0, random_state=0)
X1 = rv2.rvs(n1, random_state=0)
X_imb = np.vstack([X0, X1])
y_imb = np.hstack([np.zeros(n0), np.ones(n1)])

x1min = -4; x1max = 4
x2min = -2; x2max = 2
xx1 = np.linspace(x1min, x1max, 1000)
xx2 = np.linspace(x2min, x2max, 1000)
X1, X2 = np.meshgrid(xx1, xx2)

def classification_result2(X, y, title=""):
    plt.contour(X1, X2, rv1.pdf(np.dstack([X1, X2])), levels=[0.05], linestyles="dashed")
    plt.contour(X1, X2, rv2.pdf(np.dstack([X1, X2])), levels=[0.05], linestyles="dashed")
    model = SVC(kernel="linear", C=1e4, random_state=0).fit(X, y)
    Y = np.reshape(model.predict(np.array([X1.ravel(), X2.ravel()]).T), X1.shape)
    plt.scatter(X[y == 0, 0], X[y == 0, 1], marker='x', label="0 클래스")
    plt.scatter(X[y == 1, 0], X[y == 1, 1], marker='o', label="1 클래스")
    plt.contour(X1, X2, Y, colors='k', levels=[0.5])
    y_pred = model.predict(X)
    plt.xlim(-4, 4)
    plt.ylim(-3, 3)
    plt.xlabel("x1")
    plt.ylabel("x2")
    plt.title(title)
    return model
```

```javascript
from imblearn.under_sampling import RandomUnderSampler
import matplotlib.pyplot as plt
from sklearn.svm import SVC

X_samp, y_samp = RandomUnderSampler(random_state=0).fit_resample(X_imb, y_imb)

plt.subplot(121)
classification_result2(X_imb, y_imb)
plt.subplot(122)
model_samp = classification_result2(X_samp, y_samp)
```

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/181017_cwx6D85wlx6lbrfsR0?q=80&s=1280x180&t=outside&f=webp)

**Tomek Links**

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/181026_t7WC2o7l95c11rgIwF?q=80&s=1280x180&t=outside&f=webp)

- 서로 가장 가까운 이웃인 서로 다른 클래스의 인스턴스 쌍을 식별하고 제거

- 서로 다른 클래스가 있을 때 서로 다른 클래스끼리 가장 가까운 데이터들이 토멕링크로 묶여서 토멕링크 중 분포가 높은 데이터를 제거하는 방법론

- 클래스를 나누는 TreshHold를 분포가 높은 쪽으로 밀어 붙이는 효과가 있다

- 신용카드 사기 탐지 시스템을 구축하고 싶을 떄, 실제 데이터셋에는 1000개의 합법적인 데이터셋과 20개의 부정적인 트랜잭션이 존재. → 언더샘플링을 통해서 정상 1400 불법 20정도로 재조정

- 장점: 분포가 높은 클래스의 중심분포는 어느정도 유지하면서 경계선을 조정하기 때문에 무작위로 삭제하는 샘플링보다 정보의 유실을 크게 방지

- 단점: 토멕링크로 묶이는 값이 한정적이기 때문에 큰 언더 샘플링의 효과를 얻을 수 없다

```javascript
from imblearn.under_sampling import TomekLinks
import matplotlib.pyplot as plt

tl = TomekLinks()
X_samp, y_samp = tl.fit_resample(X_imb, y_imb)

plt.subplot(121)
classification_result2(X_imb, y_imb)
plt.subplot(122)
model_samp = classification_result2(X_samp, y_samp)
```

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/181042_1hu2SsA2JQcBEsIcRZ?q=80&s=1280x180&t=outside&f=webp)

**CNN (Condensed Nearest Neighbour)**

- 최근접인 클래스 분포 데이터를 삭제하면서 샘플링하는 방법론

- 과정

-   _1. 분포가 작은 클래스르 S분포로 둡니다._

-   _2. 분포가 큰 클래스를 랜덤으로 하나 선택한 뒤 그 데이터 위치에서 가장 가까운 데이터를 선택했을 때 S 분포에 포함 되어 있지 않은 데이터라면 제거합니다._

-   _3. 가장 가까운 값이 S분포가 나올 때까지 2번을 반복합니다_

```javascript
from imblearn.under_sampling import CondensedNearestNeighbour
import matplotlib.pyplot as plt

cnn = CondensedNearestNeighbour(random_state=0)
X_samp, y_samp = cnn.fit_resample(X_imb, y_imb)

plt.subplot(121)
classification_result2(X_imb, y_imb)
plt.subplot(122)
model_samp = classification_result2(X_samp, y_samp)
```

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/181056_2KL7Mc5ecjLProUpLh?q=80&s=1280x180&t=outside&f=webp)

**One sided Selection**

- Tomek link + CNN

- 토멕링크로 먼저 데이터를 제거한 후 분포가 큰 클래스 내부에서 CNN방법으로 데이터를 데이터를 제거하는 과정을 거치는 방법

```javascript
from imblearn.under_sampling import OneSidedSelection
import matplotlib.pyplot as plt

oss = OneSidedSelection(random_state=0)
X_samp, y_samp = oss.fit_resample(X_imb, y_imb)

plt.subplot(121)
classification_result2(X_imb, y_imb)
plt.subplot(122)
model_samp = classification_result2(X_samp, y_samp)
```

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/181116_zSGfUl5qP2FcRut5lc?q=80&s=1280x180&t=outside&f=webp)

**Edited Nearest Neighbours**

- KNN방식이랑 비슷하며 소수 클래스 주변의 다중 클래스 값을 제거하는 방법론

- 토멕링크 방법론 처럼 클래스를 구분하는 임계점을 다중 클래스 쪽으로 밀어낼 수 있지만 제거 효과가 크지 않다.

```javascript
from imblearn.under_sampling import EditedNearestNeighbours
import matplotlib.pyplot as plt

enn = EditedNearestNeighbours(kind_sel="all", n_neighbors=5)

X_samp, y_samp = enn.fit_resample(X_imb, y_imb)

plt.subplot(121)
classification_result2(X_imb, y_imb)
plt.subplot(122)
model_samp = classification_result2(X_samp, y_samp)
```

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/181131_6WS2ZSuQNvoifnXD1F?q=80&s=1280x180&t=outside&f=webp)

**Neighbourhood Cleansing Rule**

- CNN방법과 +  ENN방법

- 장점: 좀 더 직관적으로 두 클래스를 나눌 수 있다

- 단점: 분포가 큰 데이터에 대한 제거 효과가 크지 않다

```javascript
from imblearn.under_sampling import NeighbourhoodCleaningRule
import matplotlib.pyplot as plt

ncr = NeighbourhoodCleaningRule(kind_sel="all", n_neighbors=5)

X_samp, y_samp = ncr.fit_resample(X_imb, y_imb)

plt.subplot(121)
classification_result2(X_imb, y_imb)
plt.subplot(122)
model_samp = classification_result2(X_samp, y_samp)
```

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/181147_kur1U0OZqw8WXOnyxZ?q=80&s=1280x180&t=outside&f=webp)

### **Oversampling**

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/181158_iL3P97VQjTgLL6YoBK?q=80&s=1280x180&t=outside&f=webp)

- Up Sampling이라고 부른다.

- 분포가 작은 클래스의 값을 분포가 큰 클래스로 맞춰주는 샘플링 방법

- 장점: 정보의 손실을 막을 수 있다.

- 단점: 여러 유형의 관측치를 다수 추가하기 때문에 오히려 오버피팅을 야기할 수 있습니다.

- 새로운 데이터, Test 데이터에서의 성능이 나빠지는 결과를 초래

**Random Oversampling**

- 소수 클래스의 인스턴스를 랜덤하게 복제해서 다수 클래스의 크기에 맞추는 것

- 소수의 클래스 데이터를 반복해서 넣는 것으로 가중치를 증가시키는 것과 비슷

```javascript
from imblearn.over_sampling import RandomOverSampler
import matplotlib.pyplot as plt

X_samp, y_samp = RandomOverSampler(random_state=0).fit_resample(X_imb, y_imb)

plt.subplot(121)
classification_result2(X_imb, y_imb)
plt.subplot(122)
model_samp = classification_result2(X_samp, y_samp)
```

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/181214_e6R46CyQ32nFOJEY0J?q=80&s=1280x180&t=outside&f=webp)

**SMOTE (Synthetic Ministry Over-sampling Technique)**

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/181223_JQXQewfF0v4zKkMrOF?q=80&s=1280x180&t=outside&f=webp)

- 정형 데이터는 주로 SMOTE라는 기법을 통해 Data Augmentation과 같은 효과

- 데이터 증강을 통해 Overfitting을 방지한다.

- SMOTE는 Imbalanced된 데이터에서 Minority한 데이터를 활용해 사이사이 데이터를 생성하는 방식이다. 기존 클래스 샘플을 보간해서 합성 데이터 포인트를 생성하고 기존 소수 클래스와 유사하지만 새로운 인스턴스를 생성한다.

- 신용카드 사기 탐지 시스템을 구축하고 싶을 떄, 
- 실제 데이터셋에는 1000개의 합법적인 데이터셋과 20개의 부정적인 트랜잭션이 존재. 
- → 오버샘플링을 통해서 정상 1000 불법 400정도로 재조정

- 데이터셋의 균형을 효과적으로 유지할 수 있지만, 신중하게 적용하지 않으면 과적합
- 으로 이어질 수 있음.

- 별도의 테스트 세트에서 모델의 성능을 평가하는 것이 필수

**Data Argumentation**

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/181303_PbEPA9aKqtW0Nk9NcH?q=80&s=1280x180&t=outside&f=webp)

- 주로 이미지 분석에서 사용되는 방법론으로 불균형 데이터 상태에서 방법론으로 사용되는 오버샘플링과 데이터를 생성하거나 증가시키는 원리는 비슷하지만 다른 부분이 존재

- 딥러닝으로 이미지 데이터로 문제를 풀 때 많이 사용되는 기법

- 이미지 분석에서 Over fitting을 방지하고 예측에 대한 신뢰성을 높이기 위한 추가적인 데이터를(이미지) 생성하는 방법론

- 원본 이미지를 회전, 반전, 확대 및 축소 → 데이터 개수 늘림

- GAN으로 데이터를 generate하는 방식과 거의 비슷

- 데이터를 의도적으로 증강시켜 모델에게 다양한 데이터로 학습할 수 있게 함

- 딥러닝에서 사용하는 것과 다르지만 정형 데이터에 활용 가능

```javascript
from imblearn.over_sampling import ADASYN
import matplotlib.pyplot as plt

X_samp, y_samp = ADASYN(random_state=0).fit_resample(X_imb, y_imb)

plt.subplot(121)
classification_result2(X_imb, y_imb)
plt.subplot(122)
model_samp = classification_result2(X_samp, y_samp)
```

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/181319_1NCgxZhaqFSqQF1GPY?q=80&s=1280x180&t=outside&f=webp)

**ADASYN (Adaptive Synthetic Sampling)**

- 분포가 작은 클래스 데이터와 그 데이터와 가장 가까운 무작위의 K개의 데이터 사이에 가상의 직선을 그려서 직선상에 존재하는 가상의 분포가 작은 클래스 데이터를 생성

```javascript
from imblearn.over_sampling import SMOTE
import matplotlib.pyplot as plt

X_samp, y_samp = SMOTE(random_state=0).fit_resample(X_imb, y_imb)

plt.subplot(121)
classification_result2(X_imb, y_imb)
plt.subplot(122)
model_samp = classification_result2(X_samp, y_samp)
```

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/181332_XMhcHgs02pjLmT6ezE?q=80&s=1280x180&t=outside&f=webp)

### Combine Sampling

- 오버샘플링과 언더샘플링을 결합한 샘플링 방법론

**SMOTE + ENN**

```javascript
from imblearn.combine import SMOTEENN
import matplotlib.pyplot as plt

X_samp, y_samp = SMOTEENN(random_state=0).fit_resample(X_imb, y_imb)

plt.subplot(121)
classification_result2(X_imb, y_imb)
plt.subplot(122)
model_samp = classification_result2(X_samp, y_samp)
```

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/181347_6E5ct2qAAk28c2r7F0?q=80&s=1280x180&t=outside&f=webp)

**SMOTE + TOMEK**

```javascript
from imblearn.combine import SMOTETomek
import matplotlib.pyplot as plt

X_samp, y_samp = SMOTETomek(random_state=0).fit_resample(X_imb, y_imb)

plt.subplot(121)
classification_result2(X_imb, y_imb)
plt.subplot(122)
model_samp = classification_result2(X_samp, y_samp)
```

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/181401_e0QDgOpMlwvf2ndFvp?q=80&s=1280x180&t=outside&f=webp)

## Cost-sensitive Learning

---

- **비용 민감 학습**은 소수 클래스를 오분류할 경우 더 높은 비용을 지불하게 만들어 소수 클래스 인스턴스를 정확하게 예측하는 데 집중하게 만든다.

    - **다수 클래스**: 대부분의 샘플이 속한 클래스

    - **소수 클래스**: 대부분의 샘플이 속하지 않는 클래스

    - **위양성 비용 FP**: 부정 클래스 샘플을 긍정 클래스 샘플로 분류해서 발생하는 비용

    - **위음성 비용 FN**: 긍정 클래스 샘플을 부정 클래스 샘플로 분류해서 발생하는 비용

    - **절대 부족**: 소수 클래스에 속한 샘플 개수가 절대적으로 부족한 상황

    - 보통 FN이 FP보다 비용이 훨씬 많이 든다.

    - 비용 민감 학습은 FN을 FP보다 크게 설정한다.
    -   FN = w * FP (w>1)

- 여러 기계 학습 알고리즘은 클래스 가중치 또는 사용자 정의 손실 함수를 통해 비용에 민감한 학습을 지원

- 정밀도와 Recall(재현율) 사이에 원하는 균형을 달성하기 위해 비용 매개 변수 미세 조정 필요

- 희귀 질병을 탐지하기 위한 모델 구축 시나리오: 데이터 세트에는 1000건의 정상클래스
- 와 10건의 질병 사례(소수 클래스)만 있음. 질병 사례를 올바르게 식별하는 것의 중요성을 강조하기 위해 질병 사례를 잘못 분류하는 데 더 높은 비용을 할당하게 되면, 비용에 민감한 학습 프레임워크는 모델이 거짓 양성(비 질병 사례를 질병 사례로 잘못 분류)보다 거짓 음성(질병 사례를 감지하지 못함)에 대해 더 많은 불이익을 받도록 보장

**확률모델**

- 로지스틱 회귀, 나이브 베이즈 등의 확률 모델은 cut-off value, c를 조정하느 방식으로 비용 민감 모델 구현

- 정확한 확률 추정은 불가능하지만 그 개념을 도입할 수 있는 모델(k-최근접 이웃, 신경망, 의사결정나무, 앙상블 모델 등)에도 적용 가능

```javascript
import numpy as np
import matplotlib.pyplot as plt
import scipy as sp
from sklearn.svm import SVC

n0 = 200; n1 = 20
rv1 = sp.stats.multivariate_normal([-1, 0], [[1, 0], [0, 1]])
rv2 = sp.stats.multivariate_normal([+1, 0], [[1, 0], [0, 1]])
X0 = rv1.rvs(n0, random_state=0)
X1 = rv2.rvs(n1, random_state=0)
X_imb = np.vstack([X0, X1])
y_imb = np.hstack([np.zeros(n0), np.ones(n1)])

x1min = -4; x1max = 4
x2min = -2; x2max = 2
xx1 = np.linspace(x1min, x1max, 1000)
xx2 = np.linspace(x2min, x2max, 1000)
X1, X2 = np.meshgrid(xx1, xx2)

def classification_result2(X, y, title=""):
    plt.contour(X1, X2, rv1.pdf(np.dstack([X1, X2])), levels=[0.05], linestyles="dashed")
    plt.contour(X1, X2, rv2.pdf(np.dstack([X1, X2])), levels=[0.05], linestyles="dashed")
    model = SVC(kernel="linear", C=1e4, random_state=0).fit(X, y)
    Y = np.reshape(model.predict(np.array([X1.ravel(), X2.ravel()]).T), X1.shape)
    plt.scatter(X[y == 0, 0], X[y == 0, 1], marker='x', label="0 클래스")
    plt.scatter(X[y == 1, 0], X[y == 1, 1], marker='o', label="1 클래스")
    plt.contour(X1, X2, Y, colors='k', levels=[0.5])
    y_pred = model.predict(X)
    plt.xlim(-4, 4)
    plt.ylim(-3, 3)
    plt.xlabel("x1")
    plt.ylabel("x2")
    plt.title(title)
    return model
```

```javascript
# data/label, 학습/평가 데이터로 구분
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X_imb, y_imb)

print("X_train.shape: {}".format(X_train.shape))
print("X_test.shape: {}".format(X_test.shape))
print("y_train.shape: {}".format(y_train.shape))
print("y_test.shape: {}".format(y_test.shape))

# 불균형 테스트
from sklearn.neighbors import KNeighborsClassifier as KNN
from sklearn.metrics import *

# Assuming you have X_train, y_train, X_test, and y_test defined
kNN_model = KNN(n_neighbors=11).fit(X_train, y_train)

pred_Y = kNN_model.predict(X_test)
print('recall_score: ', recall_score(y_test, pred_Y))
print('accuracy_score: ', accuracy_score(y_test, pred_Y))

# 비용 민감 모델 적용
from sklearn.linear_model import LogisticRegression

# 인스턴스화
model = LogisticRegression(max_iter=100000).fit(X_train, y_train)

y_pred = model.predict(X_test)
print('recall_score: ', recall_score(y_test, y_pred))
print('accuracy_score: ', accuracy_score(y_test, y_pred))

# cut off value 조정
probs = model.predict_proba(X_test)
probs = pd.DataFrame(probs, columns=model.classes_)

cut_off_value = 0.3

pred_y = 2 * (probs.iloc[:, -1] >= cut_off_value) - 1
print('recall_score: ', recall_score(y_test, y_pred))
print('accuracy_score: ', accuracy_score(y_test, y_pred))
```

```javascript
def cost_sensitive_model(model, cut_off_value, X_test, y_test):
    probs = model.predict_proba(X_test)
    probs = pd.DataFrame(probs, columns=model.classes_)
    y_pred = 2 * (probs.iloc[:, -1] >= cut_off_value) - 1
    recall = recall_score(y_test, y_pred, average='weighted')
    accuracy = accuracy_score(y_test, y_pred)

    return recall, accuracy

from matplotlib import pyplot as plt
import numpy as np
%matplotlib inline

model = LogisticRegression(max_iter=100000).fit(X_train, y_train)

cut_off_value_list = np.linspace(0, 1, 101)
recall_list = []
accuracy_list = []

for c in cut_off_value_list:
    recall, accuracy = cost_sensitive_model(model, c, X_test, y_test)
    recall_list.append(recall)
    accuracy_list.append(accuracy)

plt.figure(figsize=(12, 8))
plt.plot(cut_off_value_list, recall_list, label = 'recall')
plt.plot(cut_off_value_list, accuracy_list, label='accuracy')
plt.legend()
plt.show()
```

**비확률모델**

- 가중치를 나누어 조절하는 형태로 구현(서포트 백터 머신, 의사결정나무)

```javascript
import numpy as np
import matplotlib.pyplot as plt
import scipy as sp
from sklearn.svm import SVC

n0 = 200; n1 = 20
rv1 = sp.stats.multivariate_normal([-1, 0], [[1, 0], [0, 1]])
rv2 = sp.stats.multivariate_normal([+1, 0], [[1, 0], [0, 1]])
X0 = rv1.rvs(n0, random_state=0)
X1 = rv2.rvs(n1, random_state=0)
X_imb = np.vstack([X0, X1])
y_imb = np.hstack([np.zeros(n0), np.ones(n1)])

x1min = -4; x1max = 4
x2min = -2; x2max = 2
xx1 = np.linspace(x1min, x1max, 1000)
xx2 = np.linspace(x2min, x2max, 1000)
X1, X2 = np.meshgrid(xx1, xx2)

def classification_result2(X, y, title=""):
    plt.contour(X1, X2, rv1.pdf(np.dstack([X1, X2])), levels=[0.05], linestyles="dashed")
    plt.contour(X1, X2, rv2.pdf(np.dstack([X1, X2])), levels=[0.05], linestyles="dashed")
    model = SVC(kernel="linear", C=1e4, random_state=0).fit(X, y)
    Y = np.reshape(model.predict(np.array([X1.ravel(), X2.ravel()]).T), X1.shape)
    plt.scatter(X[y == 0, 0], X[y == 0, 1], marker='x', label="0 클래스")
    plt.scatter(X[y == 1, 0], X[y == 1, 1], marker='o', label="1 클래스")
    plt.contour(X1, X2, Y, colors='k', levels=[0.5])
    y_pred = model.predict(X)
    plt.xlim(-4, 4)
    plt.ylim(-3, 3)
    plt.xlabel("x1")
    plt.ylabel("x2")
    plt.title(title)
    return model
```

```javascript
# data/label, 학습/평가 데이터로 구분
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X_imb, y_imb)

# 불균형 테스트
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import *

KNN_model = KNeighborsClassifier(n_neighbors=11).fit(X_train, y_train)

y_pred = KNN_model.predict(X_test)
print('recall_score: ', recall_score(y_test, y_pred))
print('accuracy_score: ', accuracy_score(y_test, y_pred))

# 비용 민감 모델
from sklearn.svm import SVC
model = SVC().fit(X_train, y_train)

y_pred = model.predict(X_test)
print('recall_score: ', recall_score(y_test, y_pred))
print('accuracy_score: ', accuracy_score(y_test, y_pred))

# 조정
model = SVC(class_weight={1: 8, 0: 1}).fit(X_train, y_train)

y_pred = model.predict(X_test)
print('recall_score: ', recall_score(y_test, y_pred))
print('accuracy_score: ', accuracy_score(y_test, y_pred))
```

## Collect More Data

---

- 근본적으로 문제를 해결하기 위해서는 소수 클래스에 해당하는 데이터를 더 많이 수집하면 된다.

- 가장 효과적인 방법이다.

## Use Different Algorithms

---

- 불균형 데이터에 덜 민감한 기계 학습 알고리즘을 선택

- 다른 알고리즘에 비해 불균형 데이터를 더 잘처리한다.

- 특히 불균형 데이터를 처리하기 위한 알고리즘도 존재한다.

**불균형 데이터에 덜 민감함 알고리즘**

- **Decision Tree**
- : 각 분할에서 얻은 정보를 바탕으로 의사결정을 하기 때문에 클래스 불균형에 덜 민감

- **Naive Bayes**
- : 확률론적 원리를 기반으로 하며 클래스 멤버 자격의 조건부 확률을 계산하기 때문에 불균형 데이터로 합리적으로 잘 수행

**특수 알고리즘**

- **[Balanced Random Forest](https://imbalanced-learn.org/stable/references/generated/imblearn.ensemble.BalancedRandomForestClassifier.html)**
- : 불균형 데이터에 대해 더 나은 성능을 제공하는 기존 랜덤 포레스트의 확장. 훈련 중에 각 의사결정 트리 내에서 클래스 분포의 균형을 유지

```javascript
ensemble.BalancedRandomForestClassifier(n_estimators=100, *, criterion='gini', 
                max_depth=None, min_samples_split=2, min_samples_leaf=1, 
                min_weight_fraction_leaf=0.0, max_features='sqrt', 
                max_leaf_nodes=None, min_impurity_decrease=0.0, bootstrap='warn', 
                oob_score=False, sampling_strategy='warn', replacement='warn', 
                n_jobs=None, random_state=None, verbose=0, warm_start=False, 
                class_weight=None, ccp_alpha=0.0, max_samples=None, monotonic_cst=None)
```

```javascript
from imblearn.ensemble import BalancedRandomForestClassifier
from sklearn.datasets import make_classification

X, y = make_classification(n_samples=1000, n_classes=3,
                           n_informative=4, weights=[0.2, 0.3, 0.5],
                           random_state=0)

clf = BalancedRandomForestClassifier(
    sampling_strategy='all', replacement=True, max_depth=2, random_state=0,
    bootstrap=False
)

# Fit the classifier on your data
clf.fit(X, y)

# Now you can access the feature importances
print(clf.feature_importances_)

# Make a prediction (Note: This is just to demonstrate; you may need actual test data)
print(clf.predict([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]))
```

- **[Easy Essemble](https://www.google.com/search?q=easy+ensemble&rlz=1C1YTUH_koKR1046KR1046&oq=easy+ensemble&gs_lcrp=EgZjaHJvbWUyBggAEEUYOTIGCAEQABgeMgYIAhAAGB4yBggDEAAYHjIGCAQQABgeMgYIBRAAGB4yCAgGEAAYDxgeMgYIBxAAGB4yCAgIEAAYCBgeMggICRAAGAgYHtIBCDIzOTBqMGo0qAIAsAIA&sourceid=chrome&ie=UTF-8)**
- : 데이터의 여러 균형 잡힌 하위 집합을 만들고 이 하위 집합에 기초 분류기를 훈련하는 앙상블 학습 방법

```javascript
imblearn.ensemble.EasyEnsembleClassifier(n_estimators=10, estimator=None, *, 
            warm_start=False, sampling_strategy='auto', replacement=False, 
            n_jobs=None, random_state=None, verbose=0)
```

```javascript
from collections import Counter
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix
from imblearn.ensemble import EasyEnsembleClassifier

X, y = make_classification(n_classes=2, class_sep=2,
                           n_features=20, n_clusters_per_class=1,
                           n_samples=1000, random_state=10)
print('Original dataset shape %s' %Counter(y))

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

# Instantiate the EasyEnsembleClassifier
eec = EasyEnsembleClassifier()

# Fit the classifier on your training data
eec.fit(X_train, y_train)

# Now you can use it for predictions
y_pred = eec.predict(X_test)
print(confusion_matrix(y_test, y_pred))
```

## Anomaly Detection

---

- 특정 경우, 소수 클래스가 이상 현상이나 희귀 사건을 나타내는 경우도 있음

- 이 문제를 이상 탐지 문제로 취급

- 이상 탐지 알고리즘은 희귀한 사례와 특이한 사례를 식별하도록 특별히 설계

- 네트워크 침입은 정상 트래픽에 비해 상대적으로 드물다. 분류 접근 방식을 사용하는 대신 Isolation Forest 또는 One-Class SVM과 같은 이상 탐지 방법을 적용하여 네트워크 데이터의 비정상적인 패턴이나 이상치를 식별

## Evaluation Metrics

---

- 불균형 데이터를 처리할 때는 클래스 불균형을 고려한 적절한 평가 지표를 선택하는 것이 중요

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/181517_yCHu9x9ZDB6p9nhTnR?q=80&s=1280x180&t=outside&f=webp)

- **정밀도** : 정밀도란 모델이 True라고 분류한 것 중에서 실제 True인 것의 비율

- **재현율(recall)**: 재현율이란 실제 True인 것 중에서 모델이 True라고 예측한 것의 비율

- 둘은 trade off 관계

- 그 외: F1-score, AUC-ROC, AUC-PRC

## Threshold Adjustment 임계값 조정

---

- 분류 작업에서 모델은 확률 점수를 생성하고 이 점수에 임계값을 적용하여 이진 예측을 수행

- 정밀도와 리콜 간의 트레이드오프를 제어

- ex) 임계값이 0.5인 경우 recall이 낮다 → 임계값을 낮추고 recall 높임

For the site tree, see the [root Markdown](https://slashpage.com/yerim-devnote.md).
