본문 바로가기
AI

KMeans

by 코낄2 2024. 1. 5.

1. Clusters(클러스터)

클러스터는 비슷한 속성을 가진 데이터 포인트들의 그룹(집합)입니다. 이는 데이터를 비슷한 패턴이나 특징을 공유하는 서로 다른 부분 집합으로 나누는 과정을 말합니다. 클러스터링은 데이터 내의 숨겨진 패턴이나 구조를 찾아 데이터를 유사한 특성을 가진 그룹으로 분류할 수 있습니다. (종속 변수가 없는 비지도 학습) 예를 들어 고객 분류, 유전자 분석, 이미지 분할 등으로 활용할 수 있습니다. K-평균 클러스터링, 계층적 클러스터링, DBSCAN (Density-Based Spatial Clustering of Applications with Noise) 등 다양한 클러스터링 알고리즘이 있습니다.

import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
# 가상의 샘플 생성
X,y = make_blobs(n_samples=100, centers= 3, random_state=10)
# 100개의 샘플/ 3개의 클러스터
# random_state: 무작위성을 제어. 동일한 수를 사용하면 동일한 데이터 생성

 

- make_blobs 함수에서 생성된 X는 기본적으로 2차원 배열 (n_features로 다차원 배열 생성 가능)

X = pd.DataFrame(X)

y # 각 샘플이 속한 클러스터를 나타내는 레이블(클래스)
array([2, 2, 1, 0, 1, 1, 0, 2, 1, 0, 0, 1, 1, 2, 2, 1, 0, 1, 0, 1, 0, 2,
       1, 2, 0, 1, 1, 1, 1, 0, 2, 1, 1, 0, 2, 2, 2, 1, 1, 1, 2, 0, 2, 2,
       1, 0, 0, 0, 2, 0, 1, 2, 0, 0, 2, 0, 1, 2, 0, 0, 1, 1, 2, 2, 2, 0,
       0, 2, 2, 2, 1, 0, 1, 1, 2, 1, 1, 2, 0, 0, 0, 1, 0, 1, 2, 1, 2, 0,
       2, 2, 0, 0, 0, 2, 2, 2, 1, 0, 0, 0])
sns.scatterplot(x=X[0], y=X[1], hue=y)

3개의 클래스로 나뉜 것 확인

from sklearn.cluster import KMeans
  • 데이터를 K개의 클러스터로 그룹화하며, 각 클러스터의 중심(센터값)을 조정하여 클러스터를 형성합니다.
km = KMeans(n_clusters=3)
km.fit(X)
pred = km.predict(X)
# 원래는 예측에 넣는 값은 달라야하지만, 데이터를 하나만 만들었기때문에 일단 같은 값으로 해봄
sns.scatterplot(x=X[0], y= X[1], hue=pred)

km = KMeans(n_clusters=5) # 5개의 클러스터로 나눔
km.fit(X)
pred = km.predict(X)

sns.scatterplot(x=X[0], y= X[1], hue=pred)

  • 평가값 : 하나의 클러스트 안에 중심점으로부터 각각의 데이터 거리를 합한 값 평균. 값이 작을수록 클러스터들이 모여있다고 볼 수 있다.(응집도가 높다)
km.inertia_
// 130.45207031101725

 

- n_clusters 를 몇개로 주는 것이(몇개의 클래스로 나누는 것이) 가장 효과적일지 찾아보기.

  • 엘보우 메서드
inertia_list = []

for i in range(2, 11):
    km = KMeans(n_clusters=i)
    km.fit(X)
    inertia_list.append(km.inertia_)

inertia_list
[976.8773336900748,
 186.3658862010144,
 154.5169216889871,
 130.45207031101725,
 114.3802551241855,
 98.29150261690447,
 85.77449714447347,
 73.70078494670776,
 64.53633931881369]
sns.lineplot(x=range(2,11), y=inertia_list)

  • 군집의 수가 늘수록 평가값은 줄어들 수밖에 없음
  • 다만 최적화가 됐을 때 값이 가장 큰 폭으로 줄어듬

2. marketing 데이터셋

mkt_df = pd.read_csv('/content/drive/MyDrive/KDT/머신러닝과 딥러닝/data/marketing.csv')
mkt_df.info()

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2240 entries, 0 to 2239
Data columns (total 21 columns):
 #   Column               Non-Null Count  Dtype  
---  ------               --------------  -----  
 0   ID                   2240 non-null   int64  
 1   Year_Birth           2240 non-null   int64  
 2   Education            2240 non-null   object 
 3   Marital_Status       2240 non-null   object 
 4   Income               2216 non-null   float64
 5   Kidhome              2240 non-null   int64  
 6   Teenhome             2240 non-null   int64  
 7   Dt_Customer          2240 non-null   object 
 8   Recency              2240 non-null   int64  
 9   MntWines             2240 non-null   int64  
 10  MntFruits            2240 non-null   int64  
 11  MntMeatProducts      2240 non-null   int64  
 12  MntFishProducts      2240 non-null   int64  
 13  MntSweetProducts     2240 non-null   int64  
 14  MntGoldProds         2240 non-null   int64  
 15  NumDealsPurchases    2240 non-null   int64  
 16  NumWebPurchases      2240 non-null   int64  
 17  NumCatalogPurchases  2240 non-null   int64  
 18  NumStorePurchases    2240 non-null   int64  
 19  NumWebVisitsMonth    2240 non-null   int64  
 20  Complain             2240 non-null   int64  
dtypes: float64(1), int64(17), object(3)
memory usage: 367.6+ KB

# 필요없는 열 삭제
mkt_df.drop('ID', axis=1, inplace=True)

# 이상치 확인
mkt_df.sort_values('Year_Birth')

mkt_df.sort_values('Income', ascending=False)

Income 666666은 이상치로 판단

✔️ mkt_df = mkt_df[mkt_df['Income'] < 200000]
NaN이 있으면 NaN도 같이 날아감(저장되지 않음). 따라서 NaN 값이 있을 때는 함부로 위의 식으로 저장하면 안됨

mkt_df = mkt_df[mkt_df['Income'] != 666666]
# 결측치 삭제
mkt_df=mkt_df.dropna()

mkt_df.isna().mean()
Year_Birth             0.0
Education              0.0
Marital_Status         0.0
Income                 0.0
Kidhome                0.0
Teenhome               0.0
Dt_Customer            0.0
Recency                0.0
MntWines               0.0
MntFruits              0.0
MntMeatProducts        0.0
MntFishProducts        0.0
MntSweetProducts       0.0
MntGoldProds           0.0
NumDealsPurchases      0.0
NumWebPurchases        0.0
NumCatalogPurchases    0.0
NumStorePurchases      0.0
NumWebVisitsMonth      0.0
Complain               0.0
dtype: float64
mkt_df.info()

<class 'pandas.core.frame.DataFrame'>
Int64Index: 2215 entries, 0 to 2239
Data columns (total 20 columns):
 #   Column               Non-Null Count  Dtype  
---  ------               --------------  -----  
 0   Year_Birth           2215 non-null   int64  
 1   Education            2215 non-null   object 
 2   Marital_Status       2215 non-null   object 
 3   Income               2215 non-null   float64
 4   Kidhome              2215 non-null   int64  
 5   Teenhome             2215 non-null   int64  
 6   Dt_Customer          2215 non-null   object 
 7   Recency              2215 non-null   int64  
 8   MntWines             2215 non-null   int64  
 9   MntFruits            2215 non-null   int64  
 10  MntMeatProducts      2215 non-null   int64  
 11  MntFishProducts      2215 non-null   int64  
 12  MntSweetProducts     2215 non-null   int64  
 13  MntGoldProds         2215 non-null   int64  
 14  NumDealsPurchases    2215 non-null   int64  
 15  NumWebPurchases      2215 non-null   int64  
 16  NumCatalogPurchases  2215 non-null   int64  
 17  NumStorePurchases    2215 non-null   int64  
 18  NumWebVisitsMonth    2215 non-null   int64  
 19  Complain             2215 non-null   int64  
dtypes: float64(1), int64(16), object(3)
memory usage: 363.4+ KB
mkt_df['Dt_Customer'] = pd.to_datetime(mkt_df['Dt_Customer'])

# 마지막으로 가입된 사람을 기준으로 현재 데이터의 가입 날짜(달) 구하기
# pass_month

last_join_date = mkt_df['Dt_Customer'].max()
mkt_df['pass_month'] = (mkt_df['Dt_Customer'].max().year * 12 + mkt_df['Dt_Customer'].max().month) - (mkt_df['Dt_Customer'].dt.year * 12 + mkt_df['Dt_Customer'].dt.month)

mkt_df.drop('Dt_Customer',axis=1, inplace=True)

# Total_mnt
# 와인+과일+육류+골드+어류+단맛제품의 합계 구하기

mkt_df['Total_mnt'] = mkt_df[['MntWines', 'MntFruits', 'MntMeatProducts', 'MntFishProducts', 'MntSweetProducts', 'MntGoldProds']].sum(axis=1)
mkt_df['Children'] = mkt_df[['Kidhome','Teenhome']].sum(axis=1)

mkt_df.drop(['Kidhome','Teenhome'],axis=1, inplace=True)

mkt_df.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 2215 entries, 0 to 2239
Data columns (total 20 columns):
 #   Column               Non-Null Count  Dtype  
---  ------               --------------  -----  
 0   Year_Birth           2215 non-null   int64  
 1   Education            2215 non-null   object 
 2   Marital_Status       2215 non-null   object 
 3   Income               2215 non-null   float64
 4   Recency              2215 non-null   int64  
 5   MntWines             2215 non-null   int64  
 6   MntFruits            2215 non-null   int64  
 7   MntMeatProducts      2215 non-null   int64  
 8   MntFishProducts      2215 non-null   int64  
 9   MntSweetProducts     2215 non-null   int64  
 10  MntGoldProds         2215 non-null   int64  
 11  NumDealsPurchases    2215 non-null   int64  
 12  NumWebPurchases      2215 non-null   int64  
 13  NumCatalogPurchases  2215 non-null   int64  
 14  NumStorePurchases    2215 non-null   int64  
 15  NumWebVisitsMonth    2215 non-null   int64  
 16  Complain             2215 non-null   int64  
 17  pass_month           2215 non-null   int64  
 18  Total_mnt            2215 non-null   int64  
 19  Children             2215 non-null   int64  
dtypes: float64(1), int64(17), object(2)
memory usage: 363.4+ KB

# object 컬럼 확인하기

mkt_df['Education'].value_counts()
Graduation    1115
PhD            481
Master         365
2n Cycle       200
Basic           54
Name: Education, dtype: int64

mkt_df['Marital_Status'].value_counts()
Married     857
Together    572
Single      471
Divorced    232
Widow        76
Alone         3
Absurd        2
YOLO          2
Name: Marital_Status, dtype: int64
mkt_df['Marital_Status']=mkt_df['Marital_Status'].replace({
    'Married':'Partner',
    'Together':'Partner',
    'Single':'Single',
    'Divorced':'Single',
    'Widow':'Single',
    'Alone':'Single',
    'Absurd':'Single',
    'YOLO':'Single'
})

mkt_df['Marital_Status'].value_counts()
Partner    1429
Single      786
Name: Marital_Status, dtype: int64

# 원 핫 인코딩

mkt_df =pd.get_dummies(mkt_df, columns=['Education', 'Marital_Status'])

# 정규화

from sklearn.preprocessing import StandardScaler

ss = StandardScaler()

ss.fit_transform(mkt_df)

ss_df = pd.DataFrame(ss.fit_transform(mkt_df), columns=mkt_df.columns)

3. KMeans

클러스터링 알고리즘 중 하나인 K-Means는 데이터를 K개의 클러스터로 그룹화하는 비지도 학습 기법입니다. 이 알고리즘은 간단하면서도 효과적인 방법으로 데이터를 구분하는 데 사용됩니다.

K-Means 알고리즘의 주요 단계

1. 중심점 초기화

   - 데이터에서 임의로 K개의 중심점을 선택하여 초기 클러스터 중심을 설정합니다.

     ( 사용자가 클러스터의 수인 K값을 정해줌.)

2. 클러스터 할당
   - 각 데이터 포인트는 가장 가까운 중심점에 할당됩니다. 이때 거리 측정은 주로 유클리드 거리를 사용합니다.
   - 할당된 클러스터는 중심점에 의해 정의되며, 같은 클러스터에 속한 데이터는 서로 유사한 특성을 가지고 있습니다.

3. 중심점 업데이트
   - 각 클러스터에 속한 데이터들의 평균을 계산하여 새로운 중심점을 갱신합니다.
   - 이 단계에서 중심점은 클러스터 내 데이터 포인트들의 중심으로 이동합니다.

4. 수렴 확인
   - 중심점이 더 이상 크게 변하지 않을 때까지 클러스터 할당과 중심점 업데이트 단계를 반복합니다.
   - 알고리즘이 수렴하면 최종 클러스터링이 완료됩니다.

K-Means의 특징 및 사용 사례

- K값의 선택:
사용자가 K값을 지정해주어야 합니다. 이를 위해 여러 실험적인 방법이나 엘보우 메서드를 사용하여 적절한 K값을 찾는 것이 일반적입니다.

- 클러스터의 모양: K-Means는 원형 클러스터를 가정하고 있기 때문에, 비교적 간단한 형태의 클러스터에서 잘 동작합니다. 다른 모양의 클러스터를 갖는 데이터에 대해서는 성능이 떨어질 수 있습니다.


이러한 특성들로 인해 K-Means는 간단하면서도 효과적인 클러스터링 알고리즘이며, 데이터를 손쉽게 그룹화하고 해석할 수 있는 장점을 가지고 있습니다. 주로 고객 세분화, 이미지 압축, 텍스트 문서 분류 등 다양한 분야에서 활용됩니다.


# ss_df에 엘보우 메소드를 사용하여 최적의 k값을 찾아보기

inertia_list = []

for i in range(2, 11):
    km = KMeans(n_clusters=i, random_state=2024)
    km.fit(ss_df)
    inertia_list.append(km.inertia_)

print(inertia_list)
[42898.75475762481, 39825.85332911935, 37620.72546383063, 
36296.39316484985, 34242.24326674706, 32502.627684050876, 
30781.8055617468, 29990.604680739903, 28248.19018930947]

sns.lineplot(x=range(2,11), y=inertia_list)

눈에 띄게 꺾이는 곳이 안보임

4. 실루엣 스코어(Silhouette Score)

실루엣 분석(Silhouette Analysis)은 클러스터링 결과의 품질을 평가하기 위한 방법 중 하나로, 개별 데이터 포인트가 해당 군집 내에서 얼마나 잘 뭉쳐 있고, 다른 군집과는 얼마나 분리되어 있는지를 측정하는 지표인 실루엣 계수(Silhouette Coefficient)를 사용합니다.  실루엣 계수(스코어)는 -1에서 1 사이의 값을 가지며, 높을수록 클러스터링의 품질이 좋다고 해석됩니다. 이는 클러스터의 개수(K)를 결정하는 데 활용할 수 있습니. K값에 대해 실루엣 스코어를 계산하고, 높은 스코어를 갖는 K를 선택할 수 있습니다.

 

from sklearn.metrics import silhouette_score

score = []

for i in range(2,11):
    km = KMeans(n_clusters=i, random_state=2024)
    km.fit(ss_df)
    pred = km.predict(ss_df)
    score.append(silhouette_score(ss_df,pred))

score
[0.23022720420084983,
 0.1422826246539863,
 0.11964043510714399,
 0.1281132041299626,
 0.12161756074874215,
 0.1252612275778885,
 0.14543070406681055,
 0.13864397879387974,
 0.14911484181076592]
sns.lineplot(x=range(2,11), y=score)

8이 가장 높음

km = KMeans(n_clusters=8, random_state=2024)
km.fit(ss_df)
pred=km.predict(ss_df)

pred // array([5, 3, 2, ..., 5, 2, 0], dtype=int32)
mkt_df['label'] = pred

mkt_df['label'].value_counts()
5    518
4    449
2    409
0    265
3    260
1    239
6     54
7     21
Name: label, dtype: int64

'AI' 카테고리의 다른 글

파이토치로 구현한 선형회귀  (1) 2024.01.08
파이토치  (0) 2024.01.07
다양한 모델 적용  (1) 2024.01.05
lightGBM  (1) 2024.01.01
랜덤 포레스트  (0) 2023.12.29