AI SCHOOL

[ML] Clustering

moru_xz 2023. 3. 12. 20:21

사이킷런 cheat-sheet

  • 머신러닝, 딥러닝에서 추상화된 도구(Scikit-learn, TensorFlow, PyTorch, Transformer, FastAI 등) 를 사용했을 때의 장점과 단점

- 장점

  • 개발 시간 단축
  • 정확성 향상(추상화된 도구는 일반적으로 더욱 정확한 모델을 구출 할 수 있도록 도움)
  • 재사용성

- 단점

  • 일반성 부족(추상화된 도구는 특정 프레임워크나 라이브러리에 종속될 수 있기 때문에 일반성 부족)
  • 실제 동작의 이해 부족(추상화된 도구를 사용하면 모델의 동작 메커니즘을 완전히 이해하지 못할 수 있음)
  • 일부 원하는 기능이 구현되어있지 않을 수 있으며 개발자가 구성할 수 있는 옵션이 적을 수 있음

 

  • 지도학습과 비지도학습

- 지도학습
: 정답 (Label) 있는 데이터를 학습

  • 분류 : 범주형 데이터를 각 class별로 나누는 것 (범주형 변수)
  • 회귀 : 하나의 가설에 미치는 다양한 수치형 변수들과의 인과성 분석 (수치형 변수)

- 비지도학습

: 정답 (Label) 없는 데이터를 학습

  • 군집화 : 유사도가 높은 범주끼리 모아주는 것, 분류와는 다르게 정답이 없다. (범주형 변수)
  • 차원축소 : 고차원 데이터를 차원을 축소해서 분석할 특성을 줄이고 한눈에 볼 수 있게 해줌 (수치형 변수)

- 강화학습

: 당근과 채찍을 번갈아 사용하면서 모델이 스스로 정답을 찾아가도록 하는 알고리즘

chat GPT

어떤 알고리즘이 좋다기보다는 적당한 알고리즘을 선택해야 한다

 

- 대표적인 비지도 학습

- 주어진 데이터들의 특성을 고려해 데이터 집단(클러스터)을 정의하고 데이터 집단의 대표할 수 있는 대표점을 찾는 것으로 데이터 마이닝의 한 방법

-  클러스터란 비슷한 특성을 가진 데이터들의 집단

- 반대로 데이터의 특성이 다르면 다른 클러스터에 속해야 함 

 

  • 클러스터링 알고리즘 비교

-> 수많은 사이킷런 클러스터링 알고리즘

 

  • K-means

- 머신러닝 > 비지도학습 > 군집화 > K-means

- 주어진 데이터를 k개의 클러스터로 묶는 알고리즘으로, 각 클러스터와 거리 차이의 분산을 최소화하는 방식으로 동작

 
 
  • 입력값
    - k: 클러스터 수
    - D: n개의 데이터 오브젝트를 포함하는 집단
  • 출력값: k개의 클러스터
  • 알고리즘
    1) 데이터 오브젝트 집단 D에서 k개의 데이터 오브젝트를 임의로 추출 -> 이 데이터 오브젝트들을 각 클러스터의 중심으로 설정(초기값 설정)
    2) 집합 D의 각 데이터 오브젝트들에 대해 k개의 클러스터 중심 오브젝트와의 거리를 각각 그리고, 각 데이터 오브젝트는 어느 중심점과 가장 유사도 높은지 알아낸다 -> 그렇게 찾아낸 중심점으로 각 데이터 오브젝트들을 할당
    3) 클러스터의 중심점을 다시 계산 -> 2에서 재할당된 클러스터들을 기준으로 중심점 다시 계산
    4) 각 데이터 오브젝트의 소속 클러스터가 바뀌지 않을 때까지 2, 3 반복
  • Elbow Method

- 데이터 세트의 클러스터의 개수 결정하는 단계 

- Cluster간의 거리의 합을 나타내는 inertiar가 급격히 떨어지는 지점에서 K값을 군집의 개수로 사용

- 너무 급격하게 떨어지는 구간 -> 랜덤하게 설정하여서 어쩔 수 없음(오차를 구한 것)

- 점차 오차가 줄어드는 구간 생김 

 

  • Silhouette analysis

- 많이 겹치는 구간 있음 -> 너무 겹쳐있으면 잘 군집화되어 있다고 보기 어려움 -> 이를 수치화해서 시각화 한 것 

 

  • Dendrogram

- 하나가 될 때까지 계속해서 묶음

 

  • DBSCAN

- 이상치탐지할 때 많이 사용

- 아예 다른 데이터는 군집에 포함시키지 않음 

 

  • Yellowbrick

- 머신러닝 시각화

- 모델 선택 프로세스를 조정할 수 있도록 하는 기계학습용 진단 시각화 플랫폼

 


rfm = pd.read_csv('data/rfm.csv').set_index('CustomerID')
rfm_cluster= rfm.iloc[:, :3]
rfm_cluster.head(3)

rfm_cluster.hist(bins = 50)

-> 이렇게 한쪽으로 치우쳐 있으면 회귀, 군집화 알고리즘은 잘 학습 못함 -> 정규분포형태로 변환 => 로그 변환

 

  • 로그 변환을 하면 어떻게 될까? 

로그:  로그(log)는 지수 함수 역함수이다. 어떤 수를 나타내기 위해 고정된 을 몇 번 곱하여야 하는지를 나타낸다고 볼 수 있다. 

* [로그 (수학) - 위키백과, 우리 모두의 백과사전]

* [자연로그 - 위키백과, 우리 모두의 백과사전]: e를 밑으로 하는 로그

* [자연로그의 밑 - 위키백과, 우리 모두의 백과사전]

* [상용로그 - 위키백과, 우리 모두의 백과사전]: 10진 로그 혹은 밑이 10인 로그

rfm_cluster_log  = np.log(rfm_cluster + 1)
rfm_cluster_log.hist(bins = 50) ;

-> log를 사용하여 정규분포 형태로

-> 그런데 왜 +1 하고 로그를 해주었을까? ⇒ x 값이 1보다 작을 때 마이너스 무한대로 수렴을 하기 때문에 가장 작은 값인 1을 더해준다

- x 값이 1보다 작을 때 마이너스 무한대로 수렴을 하기 때문에 가장 작은 값인 1을 더해준다
- np.log1p 를 사용하게 되면 1을 더하고 로그를 적용하는 것과 같음

 

  • 음수가 있는데 분포가 한쪽으로 너무 치우쳐져있어서 로그를 적용해서 변환하고자 하는데 음수에는 로그를 적용할 수 없음 이때는 어떻게 해야하는가?
Min-max scaling (normalizing)
: 데이터를 최소값을 0으로, 최대값을 1로 변환합니다. 그 후, 변환된 값을 다시 로그를 적용합니다.

Box-Cox 변환
: Box-Cox 변환은 양수 데이터에 대해 사용되는 데이터 변환 방법 중 하나입니다. 음수 값을 변환하기 위해서는 먼저 상수를 더해서 모든 값이 양수가 되도록 만들어줍니다. 그 후에 Box-Cox 변환을 적용하고, 변환된 값을 다시 원래 상수를 빼서 원래 음수 값을 얻어낼 수 있습니다.

Yeo-Johnson 변환
: Yeo-Johnson 변환은 Box-Cox 변환과 비슷한 방법으로 음수 값을 처리합니다. Box-Cox 변환에서는 음수 값을 처리하지 못하는데 비해 Yeo-Johnson 변환은 모든 값을 처리할 수 있습니다.
  • 이 방법 말고 음수에 로그를 적용하는 방법은?
로그 함수는 0보다 큰 값에 대해서만 정의되기 때문에 음수에는 적용할 수 없습니다. 음수 값에 로그를 적용하는 대안으로는 다음과 같은 방법들이 있습니다.

절댓값으로 변환 후 로그 적용
음수 값을 양수 값으로 바꾸기 위해 절댓값으로 변환한 뒤 로그를 적용하는 방법입니다. 이 방법은 데이터의 분포가 한쪽으로 치우쳐져 있을 때 효과적일 수 있습니다. 하지만 음수 값의 크기가 크게 작용하는 경우, 로그 변환 후 값의 차이가 크게 줄어들어 원래 데이터의 특성이 완전히 바뀔 수 있습니다.

적절한 상수를 더한 후 로그 적용
음수 값에 로그를 적용할 수 없지만, 0 이상의 값에 로그를 적용할 수 있습니다. 따라서 음수 값에 상수를 더해준 후 로그를 적용하는 방법이 있습니다. 상수는 1보다 크면서 음수 값의 절댓값보다 큰 값을 사용합니다. 이 방법은 음수 값이 상대적으로 작을 때 효과적일 수 있습니다.

Box-Cox 변환
Box-Cox 변환은 데이터를 정규분포에 가깝게 만들기 위해 사용하는 방법으로, 양수와 음수 모두에 대해 적용할 수 있습니다. 이 방법은 데이터가 양수이거나 0일 때 적용 가능하며, 데이터가 음수인 경우에는 일반적으로 다른 방법을 사용합니다. Box-Cox 변환은 파이썬의 scipy.stats 모듈의 boxcox 함수를 사용하여 구현할 수 있습니다.

-> 분포 그렸을 때 한쪽으로 치우쳐있으면 제대로 학습하기 어려움 -> 골고루 퍼져있으면 골고루 학습할 수 있어서 -> 정규분포 형태로 변환하여 학습

 

  • 변수 스케일링

- Feature의 범위를 조정하여 정규화하는 것을 의미

- 일반적으로 Feature의 분산과 표준편차를 조정하여 정규분포 형태를 띄게하는 것이 목표

- 왜 중요할까? 

  • Feature의 범위가 다르면 Feature끼리 비교하기 어려워서 머신러닝 모델에서 제대로 작동하지 않음
  • Feature Scaling이 잘 되어 있으면 서로 다른 변수끼리 비교하는 것이 편리(우리나라 KOSPI 주식 데이터를 시가총액을 기준으로 그룹화하여 나눈다고 가정 -> 시가총액 비슷한 주식일지라도 주식 한 주의 가격은 크게 다를 수 있음 -> 시장 변화에 따라 시가총액이 비슷한 회사의 주식들의 등락폭을 비교하고 싶다고 가정 -> 이 경우 1주에 10만원 이상인 주식과 1주에 1만원 이하인 주식을 한 그래프에서 비교한다면 비교하기 쉽지 않음 => Feature Scaling이 되어 있으면 등락폭 한 그래프 안에서 비교 가능)

- StandardScaler -> 평균을 빼고 표준편차(std)로 나눠주기 때문에 이름을 표준편차로 외우는 것이 좋음

- MinMaxScaler -> 최소값을 0, 최대값을 1로 만듦

- RobustScaler -> 중앖값을 0 으로 만듦. IQR 값으로 나눠주는 공식

- 모든 알고리즘에 적용한다고 다 성능이 나아지는 것은 아님
- 회귀, KNN, K-means 등의 거리기반 알고리즘에 사용하면 좀 더 나은 성능을 보인다

 

<지금까지 한 것들>

1) 고객ID 기준 RFM 데이터셋 로드
2) RFM 값만 따로 분리
3) 로그를 적용해서 정규화(정규분포 형태로 변환)
4) StandardScaler 를 통해 평균이 0, 표준편차가 1이 되게 변환 -> 앞으로 할 것

`Scikit-learn API 사용법`
- fit() : 학습(만약, StandardScaler 라면 주어진 데이터의 평균, 표준편차 등을 학습하게 됨)
- transform() : 비지도학습에서 변환
- fit_transform() : 학습과 변환을 한번에 한다
- predict() : 지도학습의 분류, 회귀 등의 예측

 

from sklearn.preprocessing import StandardScaler

ss = StandardScaler()
ss.fit(rfm_cluster_log)
X = ss.transform(rfm_cluster_log)
X

pd.DataFrame(X).describe().round(2)

 KMeans

from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score

kmeans = KMeans(n_clusters= 3, random_state=42, n_init = 'auto')
kmeans.fit(X)
kmeans.cluster_centers_, kmeans.inertia_

cluster_centers_ : 
클러스터 중심 좌표. 알고리즘이 완전히 수렴하기 전에 멈추는 경우(tol 및 max_iter 참조), 이는 labels_와 일치하지 않습니다.
https://hanawithdata.tistory.com/entry/K-means-Clustering-with-Python
inertia_ : 
가장 가까운 클러스터 중심까지의 샘플 제곱 거리의 합계이며, 제공된 경우 샘플 가중치에 따라 가중치가 부여됩니다.

각 군집에서 평균과의 차이가 작을 수록 좋은 분류이다. 그래서 inertia가 작을수록 잘 분류되었다고 볼 수 있다.

https://ysyblog.tistory.com/84

 

inertia = []
silhouettes = []
range_n_clusters = range(2, 20)

for n_cluster in range_n_clusters:
    kmeans = KMeans(n_clusters=n_cluster, random_state=42, n_init = 'auto')
    kmeans.fit(X)
    inertia.append(kmeans.inertia_)
    silhouettes.append(silhouette_score(X , labels=kmeans.labels_))
    print(n_cluster, end=',')

 

Elbow Method

plt.figure(figsize=(15,4))
plt.plot(range_n_clusters, inertia)
plt.title('The Elbow Method')
plt.xlabel('Number of clusters')
plt.ylabel('Within-Cluster Sum of Square')
plt.xticks(range_n_clusters)
plt.show()

-> 뾰족하게 구부러진 부분이나 특정 지점이 팔처럼 굽어지는 부분을 K로 지정합니다.

-> Within-Cluster Sum of Square: 클러스터 내 제곱 합계는 각 클러스터 내 관측값의 변동성을 측정하는 척도입니다. 일반적으로 제곱합이 작은 클러스터는 제곱합이 큰 클러스터보다 더 콤팩트합니다. = 클러스터 안에 있는 점들을 다 제곱을 해서 더했는데 거리가 가까울 수록 군집화가 잘 되었다는 의미 

-> 처음에는 군집이 랜덤하게 지정되어서 거리값이 크게 나옴 

Elbow Method
 Inertia : 각 군집별 오차의 제곱의 합, 군집 내의 분산
k가 증가하면 inertia가 줄어들게 됨

inertia가 빠르게 변하는 지점이 최적의 k
 

inertia는 얼마나 잘 뭉쳐있느냐를 본다면 silhouette은 뭉쳐있는 것 중에 얼마나 다른 애들이 있냐

Silhouette Score

- 실루엣 분석은 결과 클러스터 간의 분리 거리를 연구하는 데 사용할 수 있음

- 실루엣 플롯은 한 클러스터의 각 포인트가 인접한 클러스터의 포인트에 얼마나 가까운지 측정값을 표시하므로 클러스터 수와 같은 매개 변수를 시각적으로 평가할 수 있는 방법을 제공

- 이 측정값의 범위는 [-1, 1] (계산하는 공식에 따라 -1 ~ 1 & 0~ 1 사이의 값을 가질 수 있음)

- 실루엣 계수가 +1에 가까울수록 샘플이 인접한 클러스터에서 멀리 떨어져 있음을 나타냄

- 값이 0이면 샘플이 인접한 두 클러스터 사이의 결정 경계에 있거나 매우 가깝다는 것을 나타내며, 음수 값은 해당 샘플이 잘못된 클러스터에 할당되었을 수 있음을 나타낸다

- 효율적으로 잘 분리됐다는 것은 다른 군집과의 거리는 떨어져 있고 동일 군집끼리의 데이터는 서로 가깝게 잘 뭉쳐 있다는 의미

plt.figure(figsize=(15, 4))
plt.title('Silhouette Score')
plt.plot(range_n_clusters, silhouettes)
plt.xticks(range_n_clusters)
plt.show()

 

KElbowVisualizer

- 실루엣 계수는 데이터 세트에 대한 실측을 알 수 없는 경우에 사용되며 모델에서 계산한 클러스터의 밀도를 계산

- 점수는 각 샘플에 대한 실루엣 계수를 평균화하여 계산되며, 최대값으로 정규화된 각 샘플에 대한 평균 클러스터 내 거리와 평균 가장 가까운 클러스터 거리 간의 차이로 계산됩

- 이것은 1에서 -1 사이의 점수를 생성합니다. 여기서 1은 고밀도 클러스터이고 -1은 완전히 잘못된 클러스터링

 

-> 실루엣 시각화 도우미는 클러스터별로 각 샘플에 대한 실루엣 계수를 표시하여 어떤 클러스터가 조밀하고 그렇지 않은지를 시각화 이는 클러스터 불균형을 결정하거나 다음 값을 선택하는 데 특히 유용

# yellowbrick.cluster 예시

from sklearn.cluster import KMeans

from yellowbrick.cluster import SilhouetteVisualizer
from yellowbrick.datasets import load_nfl

# Load a clustering dataset
X, y = load_nfl()

# Specify the features to use for clustering
features = ['Rec', 'Yds', 'TD', 'Fmb', 'Ctch_Rate']
X = X.query('Tgt >= 20')[features]

# Instantiate the clustering model and visualizer
model = KMeans(5, random_state=42, n_init = 'auto')
visualizer = SilhouetteVisualizer(model, colors='yellowbrick')

visualizer.fit(X)        # Fit the data to the visualizer
visualizer.show()        # Finalize and render the figure

from yellowbrick.cluster import KElbowVisualizer

KEV = KElbowVisualizer(kmeans, k=10, n_init="auto")
KEV.fit(X)
KEV.show()

 

-> k 4인 것을 추천

참고: https://jimmy-ai.tistory.com/52

 

[Sklearn] K-means 클러스터링 (K-평균 알고리즘) 파이썬 구현 + 시각화, Elbow Method

이번 글에서는 비지도 학습의 대표적 알고리즘인 K-means Clustering을 파이썬 사이킷런에서 구현해보는 예제를 다루어보겠습니다. 클러스터링 데이터 불러오기 먼저, 데이터를 불러오도록 하겠습

jimmy-ai.tistory.com

distortion score: 각 클러스터의 클러스터 중심으로부터의 제곱 거리의 평균 - > k값에 따른 distortion score 시각화
https://www.scikit-yb.org/en/latest/api/cluster/elbow.html

- KElbowVisualizer는 "Elbow" 방법을 구현하여 데이터 과학자가 다음에 대한 다양한 값으로 모델을 맞춤으로써 최적의 클러스터 수를 선택할 수 있도록 지원

- 꺾은선형 차트의 '팔꿈치'(곡선의 굴곡 지점)는 기본 모델이 해당 지점에 가장 잘 맞는다는 좋은 표시

- 시각화에서 'Elbow'는 선으로 주석이 표시됩니다.

 

 

분석한 내용을 바탕으로 KMeans n_clusters 값을 정해 학습하기

ss = StandardScaler()
ss.fit(rfm_cluster_log)
X = ss.transform(rfm_cluster_log)
X

 

n_clusters = 5
kmeans = KMeans(n_clusters=n_clusters, random_state=42, n_init="auto" )
kmeans.fit(X)

kmeans.labels_
pd.Series(kmeans.labels_).value_counts()

kmeans.cluster_centers_

 

 

rfm['Cluster'] = kmeans.labels_
rfm.head(2)

 

 

pd.crosstab(rfm['RFM_class'], rfm['Cluster'])

 

- 1, 2, 3이 gold 랑 겹침

- 1 이 silver가 많다

-> 클래스를 qcut()으로 직접 구간화한 것과 머신러닝으로 분류된 클러스터가 어떻게 다른지 살펴보기

 

sns.pairplot(data = rfm.sample(100), hue = 'Cluster')

sns.violinplot(data = rfm.sample(1000), x = 'Cluster', y = 'RFM_score', hue = 'RFM_class')

💡
기존 상대평가(qcut) 방법으로 고객을 군집화 할 수도 있고 변수가 엄청 많다면 qcut 으로 군집화 할 때 전처리 등이 까다로울 수도 있습니다. 예를 들어 설문조사, 사용자 행동정보, 인구통계학적 정보 등의 다양한 변수를 사용하고자 할 때 군집화 기법을 사용하면 비교적 간단하게 고객을 군집화 할 수도 있습니다
# cluster.KMeans, metrics.silhouette_score 불러오기
# rfm_norm 값을 학습하고
# elbow 값을 보기위해 inertia_ 값을 리스트로 만듭니다.
# silhouette_score(X, kmeans.labels_) 값도 리스트로 만듭니다.
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score

kmeans = KMeans(n_clusters=3, random_state=42) 
# scikit-learn 라이브러리에서 KMeans 알고리즘을 불러옵니다.
# 클러스터 개수 3개
kmeans.fit(X)
# rfm_cluster_log값을 Standard-Scaler를 통해 스케일링한 값인 X를 fit해줍니다.
kmeans.cluster_centers_, kmeans.inertia_
# kmeans로 불러온 KMeans 함수의 cluster center(클러스터 중심 좌표)과 inertia(가장 가까운 클러스터 중심까지의 샘플 제곱 거리의 합계)를 확인합니다.
 

inertia = []
silhouettes = []
range_n_clusters = range(2, 20)
# inertia, silhouette를 담아줄 빈 리스트 생성
# n_cluster에 2~19를 순차적으로 담아주기 위해 range함수로 숫자 생성

for n_cluster in range_n_clusters:
    kmeans = KMeans(n_clusters=n_cluster, random_state=42) # 반복문으로 클러스터 개수에 2~19 대입
    kmeans.fit(X)
    inertia.append(kmeans.inertia_) # inertia 리스트에 클러스터 개수별 inertia를 담아줍니다.
    silhouettes.append(silhouette_score(X, labels=kmeans.labels_)) #silhouette 리스트에 실루엣 스코어를 담아줍니다.
    print(n_cluster, end=",")