본문 바로가기
삼정KPMG Future Academy/머신러닝&딥러닝

비지도학습 K-Means

by _이유 2025. 12. 2.

학습기법

주성분 분석(PCA)

  • 분포된 데이터들의 주성분(Principal Component)을 찾아주는 방법
  • 여러 데이터들이 모여 하나의 분포를 이룰 때 이 분포의 주성분을 분석
  • 차원 축소: 고차원데이터를 낮은 차원(평면)으로 줄이는 것
  • 정보 보존: 이 과정에서 가능한 한 원본 데이터의 분산(정보)을 최대한 보존
  • 데이터 요약: 복잡한 데이터셋을 단순화하여 핵심 패턴만 추출

PCA 원리

첫 번째 주성분 (PC1)

  • 데이터가 가장 넓게 퍼져 있는 방향 (즉, 분산이 가장 큰 방향)
  • 데이터의 구조를 가장 잘 요약하는 축
  • PC1만으로도 데이터의 주요 특성을 많이 설명할 수 있음

두 번째 주성분 (PC2)

  • PC1과 직교(90도)하면서, 그 다음으로 분산이 큰 영향
  • PC1에 담지 못한 나머지 중요한 변화를 담고 있음

PCA를 하는 이유

노이즈 제거

  • 고차원 데이터는 종종 노이즈가 많음. PCA를 사용하면 데이터 내의 노이즈 성분을 줄일 수 있음

과적합 방지

  • 모댈이 복잡한 데이터를 과적합하지 않도록 차원을 축소하여 일반화 성능을 향상시킬 수 있음

데이터 시각화

  • 차원 데이터를 2차원이나 3차원으로 축소해 직관적으로 시각화할 수 있음

장점

  • 계산 비용을 감소하는 효과, 전반적인 데이터에 대한 이해도를 높이는 효과
  • 변수 간 상관관계 제거: PCA는 변수를 상호 독립적으로 변환하므로 다중공선성 문제를 해결할 수 있음
  • 데이터의 중요한 구조 보존: 중요한 정보(분산)를 최대한 보존하면서 차원을 축소해 데이터의 특성을 유지

단점

  • 해석의 어려움: 변환 후의 주성분은 기존의 변수들과 직접적인 해석이 어려워짐
  • 비선형 구조 파악 불가: PCA는 선형적인 방법이므로 데이터가 비선형 구조일때는 적합하지 않음
  • 변수 스케일의 중요성: 변수들의 스케일 차이가 크면 PCA 결과가 왜곡될 수 있음.
  • 데이터 손실: 차원 축소 과정에서 일부 정보가 손실될 수 있음

PCA 방법

  • Scikit-learn의 차원 축소
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
# StandardScaler로 데이터 normalization 적용
data_scaled = StandardScaler().fit_transform(df_iris.iloc[:, :4])
pca_data = pca.fit_transform(data_scaled)
  • PCA 적용하기 전에 입력 데이터의 개별 피처들을 스케일링하기
    ◦ PAC는 여러 피처들의 값을 연산해야 하므로 피처들의 스케일에 영향을 받음
    ◦ 일반적으로 StandardScaler()로 변환함
    ◦ 사이킷런의 PCA 객체는 전체 변동성에서 개별 PCA 컴포넌트별로 차지하는 변동성 비율(explained_variance_ratio_) 속성을 제공

군집 분석

  • 서로 유사한 정도에 따라 다수의 객체를 군집으로 나누는 작업 또는 이에 기반한 분석
  • 유사도가 높은 데이터끼리 그룹화함
  • 소속 집단의 정보를 모르고 있는 상태

군집화 활용 분야

고객 세분화

  • 마케팅에서 고객을 비슷한 행동·특성 기준으로 그룹화
    예) 구매 패턴, 이탈 위험도, 사용 빈도 기반 분류
  • 개인화 추천, 타겟 마케팅에 활용

마켓 세분화

  • 시장을 동일한 니즈·특성의 집단으로 구분
  • 제품 전략, 가격 정책, 지역별 전략 수립에 사용

 

사용자 행동 분석

  • 기반으로 비슷한 행동 패턴을 묶음
    예) 뉴스 소비 패턴, 이동 경로, 상품 탐색 방식 등
  • UX 개선, 개인화 추천에 활용

비계층적 군집화

  • 전체 데이터를 확인하고 특정한 기준으로 데이터를 동시에 구분함
  • 각 데이터들은 사전에 정의된 개수의 군집 중 하나에 속하게 됨

종류

  • K-Means
  • DBSCAN

K-Means

학습 방법

  • 사전에 결정된 군집 수 K에 기초하여 전체 데이터를 상대적으로 유사한 K개의 군집으로 구분하는 방법
  • 동일 클러스터(군집) 내의 각 데이터들 간의 거리가 가장 짧아지도록 데이터를 주어진 개수의 클러스터로 분류하는 것

특징

  • 각 군집은 하나의 중심을 가짐
  • 다양한 모양의 군집이 형성될 수 있음
  • 같은 중심에 할당된 개체들이 모여 하나의 군집을 형성
  • 사전에 군집의 수, K가 정해져야 함.

군집화 방법

1. 데이터 중 임의로 K개의 중심점(Centroid) 설정함
2. 모든 데이터에서 설정된 각 군집의 중심점까지의 거리 계산
3. 모든 데이터를 가장 가까운 중심점이 속한 군집으로 할당
4. 각 군집의 중심점을 재설정함(할당 데이터의 중심으로 중심점 이동)
5. 군집 중심점이 변경되지 않을 때까지 2~5번의 과정을 반복함.

 

K-Means 실습

Scikit-learn의 K-Means

from sklearn.cluster import KMeans
correct_kmeans = KMeans(n_clusters=3) #cluster 분류 개수 확인
correct_kmeans.fit(data)
correct_kmeans.cluster_centers_ # 데이터와 중심간 거리
correct_kmeans.labels_ # 데이터들의 분류 labels 확인

 

주요 하이퍼 파라미터

  • n_cluster : 군집화 개수(군집의 중심점 개수)
  • init : 초기의 군집 중심점 좌표 알고리즘(k-means++)

주요 속성

  • labels_ : 각 데이터 포인트가 속한 군집 중심점 레이블임
  • cluster_centers_ : 각 군집 중심점
import pandas as pd
import numpy as np

# lib, 데이터 불러오기
data_path = 'datas_ml/Mall_Customers.csv'
df = pd.read_csv(data_path)
df.head()

df.columns

# 독립변수 추출
X = df.loc[:, 'Gender':]
X

sorted(X['Gender'].unique())

from sklearn.preprocessing import LabelEncoder
encoder = LabelEncoder()
X['Genre'] = encoder.fit_transform(X['Genre'])
X

데이터 정규화

from sklearn.preprocessing import StandardScaler
features = X.columns

# StandardScaler로 정규화
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X[features])

 

비지도학습 실습

from sklearn.cluster import KMeans

객체 생성

kmeans = KMeans(n_clusters = 3, random_state = 10)

비지도학습 실행

y_pred = kmeans.fit_predict(X_scaled)
y_pred

# 예측결과 df에 추가하기
df['Group'] = y_pred
df

# 몇개의 그룹 갯수를 정하면 좋을지 찾는 방법
wcss = []
for i in range(1, 11):
    kmeans = KMeans(n_clusters = i, random_state = 10)
    kmeans.fit(X_scaled)
    wcss.append(kmeans.inertia_)
wcss

import matplotlib.pyplot as plt
plt.plot(wcss)
plt.show()

# 클러스터(그룹)갯수를 정한다.
kmeans = KMeans(n_clusters=2, random_state=10)
y_pred = kmeans.fit_predict(X_scaled)
df['Group'] = y_pred
df

df.loc[df['Group']==1,]

import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import font_manager as fm
import warnings
warnings.filterwarnings('ignore')

# 한글 폰트 설정
plt.rcParams['font.family'] = 'Malgun Gothic'
plt.rcParams['axes.unicode_minus'] = False

# 색상 설정 (5개 그룹)
colors = ['#E63946', '#1D3557', '#F4A261', '#2A9D8F', '#E76F51', '#8338EC']
groups = sorted(df['Group'].unique())

# 1. 소득 vs 지출점수
plt.figure(figsize=(10, 6))
for group in groups:
    cluster_data = df[df['Group'] == group]
    plt.scatter(cluster_data['Annual Income (k$)'], 
               cluster_data['Spending Score (1-100)'],
               c=colors[group], 
               label=f'그룹 {group}',
               alpha=0.7,
               s=60,
               edgecolors='white',
               linewidth=0.5)

plt.xlabel('연간 소득 (천 달러)', fontsize=12, fontweight='bold')
plt.ylabel('지출 점수 (1-100)', fontsize=12, fontweight='bold')
plt.title('소득 vs 지출 점수', fontsize=14, fontweight='bold')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('cluster_income_spending.png', dpi=300, bbox_inches='tight')
plt.show()

# 2. 나이 vs 지출점수
plt.figure(figsize=(10, 6))
for group in groups:
    cluster_data = df[df['Group'] == group]
    plt.scatter(cluster_data['Age'], 
               cluster_data['Spending Score (1-100)'],
               c=colors[group], 
               label=f'그룹 {group}',
               alpha=0.7,
               s=60,
               edgecolors='white',
               linewidth=0.5)

plt.xlabel('나이', fontsize=12, fontweight='bold')
plt.ylabel('지출 점수 (1-100)', fontsize=12, fontweight='bold')
plt.title('나이 vs 지출 점수', fontsize=14, fontweight='bold')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('cluster_age_spending.png', dpi=300, bbox_inches='tight')
plt.show()

# 3. 3D 시각화 (소득, 나이, 지출점수)
from mpl_toolkits.mplot3d import Axes3D

fig = plt.figure(figsize=(12, 9))
ax = fig.add_subplot(111, projection='3d')

for group in groups:
    cluster_data = df[df['Group'] == group]
    ax.scatter(cluster_data['Annual Income (k$)'], 
              cluster_data['Spending Score (1-100)'],
              cluster_data['Age'],
              c=colors[group], 
              label=f'그룹 {group}',
              alpha=0.7,
              s=60,
              edgecolors='white',
              linewidth=0.5)

ax.set_xlabel('연간 소득 (천 달러)', fontsize=11, fontweight='bold', labelpad=10)
ax.set_ylabel('지출 점수 (1-100)', fontsize=11, fontweight='bold', labelpad=10)
ax.set_zlabel('나이', fontsize=10, fontweight='bold', labelpad=10)
ax.set_title('3D 클러스터 시각화', fontsize=14, fontweight='bold', pad=20)
ax.legend()
ax.view_init(elev=20, azim=45)
plt.tight_layout()
plt.savefig('cluster_3d.png', dpi=300, bbox_inches='tight')
plt.show()

# 그룹별 통계
print("\n그룹별 통계:")
for group in groups:
    group_data = df[df['Group'] == group]
    print(f"\n그룹 {group} (n={len(group_data)})")
    print(f"  평균 나이: {group_data['Age'].mean():.1f}세")
    print(f"  평균 소득: ${group_data['Annual Income (k$)'].mean():.1f}k")
    print(f"  평균 지출점수: {group_data['Spending Score (1-100)'].mean():.1f}")



그룹별 통계:

그룹 0 (n=103)
  평균 나이: 48.7세
  평균 소득: $60.5k
  평균 지출점수: 32.2

그룹 1 (n=97)
  평균 나이: 28.4세
  평균 소득: $60.6k
  평균 지출점수: 69.3