AI/머신러닝

머신러닝의 이해와 라이브러리 활용 심화 3주차(군집화, RFM)

edcrfv458 2025. 2. 14. 11:41

학습 목표

  • 정답이 없는 데이터를 다루는 비지도 학습인 군집화 알고리즘 학습

비지도 학습

  • 답을 알려주지 않고 데이터 간 유사성을 이용해 답을 지정하는 방법

 

예시

  • 고객 특성에 따른 그룹화 ➡️ 헤비 유저, 일반 유저
  • 구매 내역별로 데이터 그룹화 ➡️ 생필품 구매
    • 비지도 학습은 데이터를 기반으로 레이블링을 하는 작업
    • 정답이 없으므로 지도 학습보다 조금 어렵고 주관적인 판단이 개입하게 됨

K-평균 알고리즘(군집화)

 

iris 데이터

  • 꽃 받침의 길이, 꽃 받침의 너비, 꽃 잎의 길이, 꽃 잎의 너비, 붓꽃 종(Y) 5개의 컬럼으로 되어있는 데이터
    • 여기서 꽃에 대한 정보로 Y를 맞추는 문제는 지도 학습임
  • 반면, Y가 없다면 정보에 따라 데이터를 분류 가능
  • 그룹화할 때 2개, 3개 등등으로 가능하다

 

K-Means 군집화

  • 수행 순서
    • K개 군집 수 설정
    • 임의의 중심을 선정
    • 해당 중심점과 거리가 가까운 데이터를 그룹화
    • 데이터의 그룹의 무게 중심으로 중심점을 이동
    • 중심점 이동했기 때문에 다시 거리가 가까운 데이터를 그룹화(위 과정 반복)
  • 장점
    • 일반적이고 적용하기 쉬움
  • 단점
    • 거리 기반으로 가까움 측정하기 때문에 차원이 많을 수록 정확도가 떨어짐
    • 반복 횟수 많을 수록 느림
    • 몇 개의 군집을 선정할지 주관적임
    • 평균 이용하기 때문에 이상치에 취약
  • Python 라이브러리
    • sklearn.cluster.KMeans
      • 함수 입력 값 
        • n_cluster: 군집화 개수
        • max_iter: 최대 반복 회수
      • 메소드
        • labels_: 각 데이터 포인트가 속한 군집 중심점 레이블
        • cluster_centers_: 각 군집 중심점의 좌표

군집평가 지표

 

실루엣 계수

  • 비지도 학습 특성 상 답이 없기 때문에 평가가 쉽지 않음
  • 다만, 군집화가 잘 되어있다는 것은 다른 군집간의 거리는 멀고, 동일한 군집끼리는 가까이 있다는 것을 의미

 

실루엣 분석

  • 각 군집 간의 거리가 얼마나 효율적으로 분리되어있는지 측정하는 것으로 실루엣 계수를 정량화한 것 
  • 좋은 군집화의 조건
    • 실루엣 값이 높을수록(1에 근접)
    • 개별 군집의 평균 값의 편차가 크지 않아야 함
  • Python 라이브러리
    • sklearn.metrics.sihouette_score: 전체 데이터의 실루엣 계수 평균 값 반환
      • 함수 입력 값
        • X: 데이터 셋
        • labels: 레이블
        • metrics: 측정 기준(기본은 euclidean)

(실습) iris 데이터 이용한 군집화

  • hue 파라미터로 각 species의 색을 다르게 표현 가능
sns.scatterplot(data=data, x='sepal_length', y='sepal_width', hue='species')

hue 파라미터 적용

from sklearn.cluster import KMeans

# 군집화 위해 데이터에서 label 제거
data2 = data.drop('species', axis=1)

# 군집화
kmeans = KMeans(n_clusters=3, init='k-means++', max_iter=300, random_state=42)
kmeans.fit(data2)

# 비교 위해 기존 데이터에서 label 가져옴
data2['target'] = data['species']
data2['cluster'] = kmeans.labels_

plt.figure(figsize=(12,6))
plt.subplot(1, 2, 1)
sns.scatterplot(x='sepal_length', y='sepal_width', hue='target', data=data2)
plt.title("Original")

plt.subplot(1, 2, 2)
sns.scatterplot(x='sepal_length', y='sepal_width', hue='cluster', data=data2, palette='viridis')
plt.title("Cluster")

기존 label과 클러스터링 결과 비교


(실습) 고객 세그먼테이션

  • 다양한 기준으로 고객을 분류하는 기법
  • 주로 타겟 마케팅이라 불리는 고객 특성에 맞게 세분화하여 유형에 따라 맞춤형 마케팅이나 서비스를 제공하는 것이 목표

 

RFM의 개념

  • Recency: 가장 최근 구입 일에서 오늘까지의 시간
  • Frequency: 상품 구매 횟수
  • Monetary value: 총 구매 금액

 

UCI 데이터 셋 호출

  • 컬럼 정보
    • InvoiceNo: 6자리  주문번호(취소된 주문은 c로 시작)
    • StockCode: 5자리 제품 코드
    • Description: 제품 이름
    • Quantity: 제품 수량
    • InvoiceDate: 주문 일자, 날짜 자료형
    • UnitPrice: 제품 단가
    • CustomerID: 5자리의 고객 번호
    • Country: 국가명

컬럼정보

  • EDA

컬럼별 결측치 수
통계 정보

➡️ customerID에 결측치 다수 존재, 가격과 수량에 음수가 존재함

  • 데이터 전처리
    • customerID 결측치 제거
    • InvoiceNo, UnitPrice, Quantity 데이터확인 및 삭제
    • 영국데이터만 취함
# 주문 수량이 음수인 것을 확인 ➡️ InovoiceNo가 C로 시작하는 경우
cond1 = data['Quantity'] < 0
data[cond1]

# 따라서
# 1. customerID 결측치 제거
cond_cust = (data['CustomerID'].notnull())

# 2. Invoice가 C로 시작
cond_invo = (data['InvoiceNo'].astype(str).str[0] != 'C')

# 3. quantity가 음수 | unitprice가 음수인 것 제거
cond_minus = (data['Quantity'] > 0) & (data['UnitPrice'] > 0)

data2 = data[cond_cust & cond_invo & cond_minus]

# 각 국적의 수 ➡️ 대부분 영국
data2.value_counts('Country')

# 따라서
# 영국의 데이터만 가져옴
cond_uk = (data['Country'] == 'United Kingdom')
data3 = data2[cond_uk]
  • RFM 기반 데이터 가공
    • 날짜 데이터 가공
    • 최종목표: 손님별로 가장 최근 구매하고 오늘까지의 기간, 구매 횟수, 총 구매 금액 표시
    • StandardScaler 적용
import datetime as dt
# 2011.12.10 기준으로 각 날짜 빼고 + 1
# 추후 CustomerID 기준으로 최소와 Period 구하면 그것이 Recency

data3['Period'] = (dt.datetime(2011,12,10) - data3['InvoiceDate']).apply(lambda x: x.days + 1)

rfm = data3.groupby('CustomerID').agg({
    'Period' : 'min',
    'InvoiceNo' : 'count',
    'Amt' : 'sum'
})

rfm.columns = ['Recency', 'Frequency', 'Monetary']
# RFM 기반 데이터 가공

# 1. 구매 수량과 구매 가격을 곱함
data3['Amt'] = (data3['Quantity'] * data3['UnitPrice']).astype(int)
data3

# 2. 각 손님 별 총 구매 가격 구하고 구매 가격으로 내림차순 정렬
data3[['CustomerID']].drop_duplicates()
data3.pivot_table(index='CustomerID', values='Amt', aggfunc='sum').sort_values('Amt', ascending=False)
# 분포 확인 ➡️ 스케일링 작업 필요
fig, ax = plt.subplots(2, 2,  figsize=(20,20))

sns.histplot(rfm['Recency'], ax=ax[0,0], kde=True)
sns.histplot(rfm['Frequency'], ax=ax[0,1], kde=True)
sns.histplot(rfm['Monetary'], ax=ax[1,0], kde=True)

# 데이터 정규화
from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
X_features = sc.fit_transform(rfm[['Recency', 'Frequency', 'Monetary']])

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

kmeans = KMeans(n_clusters=3, random_state=42)
labels = kmeans.fit_predict(X_features)
rfm['label'] = labels

silhouette_score(X_features, labels)  # 0.5930190673437187

# log스케일링 통한 추가전처리

rfm['Reccency_log'] = np.log1p(rfm['Recency'])
rfm['Frequency_log'] = np.log1p(rfm['Frequency'])
rfm['Monetary_log'] = np.log1p(rfm['Monetary'])

X_features2 = rfm[['Reccency_log', 'Frequency_log', 'Monetary_log']]
sc2 = StandardScaler()
X_features2_sc = sc2.fit_transform(X_features2)
  • 정규화를 진행하고 k-means를 진행해 실루엣 계수를 확인해봤을때 k가 5일때 실루엣 계수는 좋게 나왔지만 시각화를 하였을때 3개의 군집밖에 보이지 않았음
  • 그래서 log스케일링을 적용하고 k-means를 진행했을때 실루엣 계수는 전보다 작아졌지만 시각화를 진행했을 때 각 군집들이 균등하게 표시됨

 

+ 추가적으로 Gaussian Mixture Model, DBSCAN, PCA 등이 있음