본문 바로가기

공부 일지 #36 | 머신러닝: 나이브베이즈와 군집분석(비지도)

@studying:)2025. 9. 14. 09:40

학습날짜: 2025.09.10


0. 확률 vs 우도

  • 확률: 모수(파라미터)가 주어졌을 때 사건이 발생할 가능성
  • 우도: 특정 데이터를 관측했을 때, 해당 모수가 타당할 가능성
  • ⇒ 확률은 “데이터가 주어졌을 때 사건 발생”, 우도는 “데이터가 주어졌을 때 모수 추정”

1. Naïve Bayes Classification

  • 조건부 확률 기반 분류 알고리즘
  • 전제조건
    • Feature 간 독립성 가정 → 실제론 위배되는 경우 많음
    • 베이즈 정리에 기반함
  • 보정 기법 (Laplace smoothing)
    • 기본적인 아이디어: "아예 안 나온 사건에도 작은 확률을 부여하자.“
    • 모든 단어에 +1 더하고, 분모에 V(특징 값 종류 개수) 더해서 정규화
  • Underflow 막기 위해 log 취함
  • 우도 테이블(빈도교차표) 활용함
  • 장점
    • 단순하고 빠름
    • 적은 데이터로도 가능
    • 이산형 변수에 강함
  • 단점
    • 독립 가정이 현실 세계와 맞지 않음
    • 연속형 변수는 전처리 필요
    • 데이터 개수 너무 작으면 과적합(Overfitting) 발생
'''
나이브 베이즈 분류 알고리즘 동작 예시

문제 설정:
병원을 방문한 고객의 정보를 기반으로,
이 고객이 '남성(1)'인지 '여성(0)'인지 분류하는 문제임.

핵심 아이디어:
- 사건(방문자 특성)이 관측된 후,
  이 사람이 남성일 확률과 여성일 확률을 계산 → 사후확률 (Posterior Probability)
- 종속변수(y)는 반드시 범주형이어야 함 (0/1 분류 문제)
- 독립변수(X)는 Frequency Table 형태로 조건부 확률을 구할 수 있어야 함
  (즉, 각 feature 값이 "셀 수 있어야" 확률을 계산할 수 있음)
- 알고리즘 내부에서는 Frequency Table → Likelihood(우도) Table을 만듦
'''

from sklearn.naive_bayes import GaussianNB
from sklearn.preprocessing import binarize
from sklearn.metrics import classification_report

# height가 연속형이므로 구간으로 나눠서 범주형으로 만들어줌
H_arr = np.array(df['Height'])
H_arr = H_arr.reshape(-1, 1)  # 1D → 2D로 변환 (scikit-learn 입력 형태 맞추기)

# 기준(threshold=165cm)으로 binarize
# → 165 이상이면 1, 미만이면 0
df['H_cd'] = binarize(H_arr, threshold=165.0)

# 독립변수(feature) 선택
cols = ['hair', 'time', 'H_cd']

# GaussianNB 사용 (연속형 변수도 처리 가능)
model = GaussianNB()
model.fit(df[cols], df['gen_cd'])

# 예측
y_pred = model.predict(df[cols]) # hair, time, h_cd

# 성능 평가
print(classification_report(df['gen_cd'], y_pred))
'''
    precision    recall  f1-score   support

           0       0.81      0.82      0.81       209
           1       0.81      0.80      0.81       203

    accuracy                           0.81       412
   macro avg       0.81      0.81      0.81       412
weighted avg       0.81      0.81      0.81       412
'''

2. 비지도 학습

  • 정답 라벨 없는 데이터 기반 학습
  • 데이터 사이 관계/유사성 통해 패턴 탐색
  • 라벨 없는 데이터가 많아 실무에서도 활용 多
  • 예시: 세포 분류, 고객 세분화 등
  • 장점
    • 범주화/특징 추출에 도움
    • 라벨 없는 데이터 확보 용이
    • 새로운 데이터 실시간 처리 가능

2.1. 군집분석 (Clustering)

  • 유사한 데이터끼리 그룹화
  • 군집 내 유사도 ↑, 군집 간 유사도 ↓ ⇒ 좋은 군집
  • 거리 기반 유사도 측정
    • 유클리디안 (L2)
    • 맨해튼 (L1)
    • 민코프스키 (일반화된 거리)
    • 코사인 유사도 (벡터 방향 유사성)


2.1.1. 계층적 군집분석

  • 데이터 각각을 군집으로 시작 → 가까운 것부터 합치며 최종 하나 남음

  • Early stopping 없음 → cut-tree로 중간을 잘라줘야 함
  • 군집 간 거리 측정 방식
    • Single link(최단 연결법): 두 군집에서 가장 가까운 두 데이터 간의 거리
    • Complete link(최장 연결법): 두 군집에서 가장 멀리 떨어진 두 데이터 간의 거리
    • Average link(평균 연결법): 두 군집 간의 모든 데이터들의 거리를 평균내어 두 군집의 거리 결정
    • Centroid link(중심 연결법): 두 군집의 중심 간 거리
    • Ward: SSE(오차 제곱합) 증가량 최소화 기준
  • 시각화: 덴드로그램
  • 특징: 마지막까지 혼자 남는 데이터 = 이상치 가능성 높음
'''
iris 데이터 셋 사용

거리 기반 알고리즘(KNN, 계층적 군집, K-means 등)은 
변수의 단위 차이에 민감하기 때문에 반드시 스케일링 필요!!
'''
from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
scaled_data = scaler.fit_transform(df) # 평균=0, 분산=1로 표준화된 데이터 반환

# 계층적 군집분석 (Linkage)
from scipy.cluster.hierarchy import linkage, fcluster

distance_matrix = linkage(scaled_data, method='complete') # 최장연결법

# clustering: dendrogram 시각화
from scipy.cluster.hierarchy import dendrogram

plt.figure(figsize=(40, 20))
dendrogram(distance_matrix, labels=iris['target'], leaf_rotation=90, leaf_font_size=20)
plt.show()

'''
클러스터 라벨 추출
fcluster((linkage return 값), t: 클러스터 계수, criterion=['distance', 'maxclust'])
distance: 특정 거리 기준으로 자름
maxclust: 하는 최대 군집 개수 지정
'''
df['cluster_labels'] = fcluster(distance_matrix, 3, criterion="distance")

# 군집 결과 vs 실제 라벨 비교
ct = pd.crosstab(df["cluster_labels"], iris["target"])
'''
col_0	0	1	2
cluster_labels			
1	0	29	37
2	0	0	11
3	1	4	0
4	0	17	2
5	7	0	0
6	42	0	0
'''

2.1.2. 비계층적 군집분석

  • 군집 수(K)를 먼저 정하고 시작
  • K-means
    • 주어진 데이터를 K 개의 군집으로 나누는 알고리즘
    • 군집 중심(centroid) 업데이트하며 수렴할 때까지 반복
    • 장점: 대용량 데이터에도 가능, 단순하고 빠름
    • 단점:
      • k값 사전 결정 필요
      • 초기 중심값에 민감
      • 이상치에 취약(→ 전처리 필요)
    • 적절한 K 선택법: Elbow method (군집 내 SSE 그래프 활용)

출처: https://medium.com/@draj0718/implementation-of-k-means-clustering-c90642e02bdb

from sklearn.cluster import KMeans

# 마지막 열(label)을 제외한 4개 feature만 사용
# iris의 sepal length, sepal width, petal length, petal width
points = df.iloc[:, :-1] 

# 모델 생성 및 학습 
kmeans = KMeans(n_clusters=3).fit(points)

# 각 클러스터의 중심 좌표
kmeans.cluster_centers_ # 열 개수가 변수 개수 만큼 나옴
'''
array([[6.85      , 3.07368421, 5.74210526, 2.07105263],
       [5.006     , 3.428     , 1.462     , 0.246     ],
       [5.9016129 , 2.7483871 , 4.39354839, 1.43387097]])
'''

# 러스터 결과 시각화
plt.scatter(df["sepal length (cm)"], df["sepal width (cm)"],
            c = df['label'])

center_x = kmeans.cluster_centers_[:, 0]
center_y = kmeans.cluster_centers_[:, 1]

plt.scatter(center_x, center_y, c = "red", marker="D")
plt.show()

'''
적합한 k값 찾기 (Elbow Method)
- KMeans에서 k(클러스터 수)는 직접 정해야 함
- 평가 지표: inertia (군집 내 거리 제곱합, 응집도)
  * 값이 클수록 군집 내 데이터들이 흩어져 있음 (distortion 큼)
  * 값이 작을수록 군집 내 데이터들이 조밀하게 모여 있음 (응집도 ↑)
- inertia 값 감소 그래프를 그리면, 감소 폭이 꺾이는 지점(elbow)이 적절한 k
'''

# Elbow Method 적용
dist = []

for k in range(1, 11):
  k_means = KMeans(n_clusters=k, random_state=42)
  k_means.fit(points)
  dist.append(k_means.inertia_) # 군집 내 거리 제곱합 저장
  
  plt.figure(figsize=(8,5))

plt.plot(range(1, 11), dist, marker = "o")
plt.xlabel("number of Cluster")
plt.ylabel("Distortion")
plt.show()

# k=3일 때 Elbow 지점이 나타남 → 최적의 클러스터 수로 판단 가능

 

studying:)
@studying:) :: what i studied

studying:) 님의 학습 여정을 기록하는 블로그입니다.

목차