주제
0. 비지도 학습
1. K-means 클러스터링
2. 이상 탐지
3. Isolation Forest
4. K-means 클러스터링과 이상 탐지 실습
0. 비지도 학습(Un-supervised Learning)
- 정답 레이블이 지정되지 않은 데이터로부터 구조나 패턴을 자동으로 탐색하는 학습 방법론
- 고객 세분화, 이상 탐지, 대규모 데이터셋의 구조 파악 등의 사용
- 특징 및 장점 :
- 수동으로 데이터의 정답을 생성할 필요가 없음. 비용과 시간 절약
- 데이터 내부의 구조를 탐색 → 다양한 통찰
- 다양한 데이터 유형과 복잡한 구조에도 적용 가능
- 단점 :
- 결과를 해석하기 어려울 수 있음
- 명확한 정답이 없으므로 모델 성능의 객관적인 평가가 어려움
- 노이즈에 매우 민감함
대표 문제
- 군집화(Clustering) : 데이터를 유사한 특성을 가진 하위 그룹(sub-group) 또는 클러스터로 분할
- 차원 축소(Dimensionality Reduction) : 고차원 데이터의 특성을 줄여 더 낮은 차원의 표현으로 만드는 과정
- 이상 탐지(Anomaly Detection) : 데이터에서 비정상적인 패턴, 이상치, 또는 예외적인 사례를 탐지
대표 알고리즘
- 군집화(Clustering)
- K-means : 데이터를 K개의 클러스터로 세분화
- 계층적 군집화(Hierarchical Clustering) : 데이터 포인트를 개별 클러스터로 가정하여 점차 유사한 클러스터를 병합하거나 큰 클러스터를 세분화하는 방식
- DBSCAN(Density-Based Spatial Clustering of Applications with Noise) : 데이터가 모여있는 밀도를 기반으로 클러스터 형성
- 차원 축소(Dimensionailty Reduction)
- 주성분 분석(PCA) : 데이터의 분산을 보존하는 방향의 축을 찾아 해당 축을 기준으로 고차원 데이터를 저차원으로 변환
- t-SNE : 고차원의 데이터를 보존하며 저차원으로 매핑. 시각화 과정에서 매우 유용
- 오토인코더(Autoencoder) : 신경망(딥러닝)을 이용하여 저차원으로 압축 후, 다시 원래 차원으로 복원하는 방식
- 이상 탐지(Anomaly Detection)
- Isolation Forest : Tree를 기반으로 특정 데이터 포인트를 격리시키는 데 필요한 분할 수를 기준으로 이상치 탐지
- One-Class SVM : 정상 데이터만을 활용해 "정상"이라는 클래스로 SVM을 학습하고, 정상 패턴에서 벗어나는 데이터를 이상치로 판단
- LOF(Local Outlier Factor) : 데이터 밀도를 계산하여 정상 데이터는 주변에 높은 데이터 밀도를 갖고, 이상치 데이터는 낮은 밀도를 갖고 있음을 활용
1. K-means 클러스터링
- 전체 데이터를 K개의 덩어리(클러스터)로 나누는 비지도 학습법
- 사용되는 알고리즘 :
- 로이드(Lloyd) 알고리즘
- 엘칸(Elkan) 알고리즘
- 로이드 알고리즘이 가장 기본적 :
- 초기화 → 할당 → 업데이트 → 반복
- 엘칸 알고리즘 :
- 데이터 포인트와 클러스터 중심 거리를 계산하는 삼각 부등식을 사용
- |a + b| ≤ |a| + |b|
1. 초기화
- K개의 클러스터 중심점을 임의로 선택
- 초기 위치는 최종 결과에 큰 영향을 미칠 수 있음
- k-means++ 초기화 방법을 사용 → 초기 중심점의 위치를 멀리 떨어지게 설정. 임의의 랜덤 위치보다 좋은 결과를 보임

2. 할당
- 각 데이터 포인트를 가장 가까운 클러스터 중심에 할당
- 유클리드 거리(Euclidean Distance)를 사용하여 거리 계산 진행
- 대안은 코사인 유사도, 맨해튼 거리가 있음

3. 업데이트
- 각 클러스터에 속한 데이터들의 평균점 위치로 클러스터 중심의 위치를 업데이트

4. 반복
- 클러스터 중심의 변화가 미미할 때까지 할당 과정과 업데이트 단계 반복
- 변화가 미미함?
- 위치의 변화가 없거나
- 클러스터에 할당되는 데이터 포인트의 변화가 없거나
- 동일한 데이터 포인트 할당 과정이 반복되거나
- 지정된 횟수에 도달하거나 등

1.1 엘보우 방법(Elbow Method)
- K를 적절하게 선택한다면 좋은 결과를 보여줄 수 있음
- 하지만 K가 너무 작거나 크다면 여러 문제에 봉착함
- 따라서 적절한 K를 골라야 하는데, 이에 대한 근거를 제공할 수 있는 방법이 엘보우 방법
- 엘보우 방법 : 클러스터 수를 늘려가며 각각에 대한 클러스터링 성능을 측정해, 클러스터에 따른 성능 변화를 분석
- K는 일반적으로 극히 작은 값(1)에서부터 매우 큰 값까지 사용 가능
- 클러스터링 성능 평가 척도 :
- SSE(Sum of Squared Errors) : 각 클러스터 내의 데이터 포인트와 클러스터 중심 간의 거리의 제곱 합
- 실루엣 계수(Silhouette Coefficient) : 응집도와 분리도를 동시에 고려해 군집화의 품질을 평가
- 그래프 상 SSE의 감소율이 급격히 줄어드는 지점이 최적 클러스터 수
1.1.1 실루엣 계수
- 응집도(Cohesion) : a(i)
- 특정 데이터 i에 대해, 동일한 클러스터 안에 들어있는 다른 데이터들과의 평균 거리
- 클러스터 내부의 데이터가 얼마나 모여있는지를 나타냄
- 분리도(Separation) : b(i)
- 특정 데이터 i에 대해, i가 들어있는 클러스터 말고 다른 클러스터 중 가장 가까운 클러스터 중심까지의 거리
- 다른 클러스터와 얼마나 떨어져있는지를 나타냄
- 실루엣 계수 : s(i)
- 최대값 : 1 → a(i)가 거의 0에 근접해 b(i)만 남는 상황 : 제일 좋은 상황
- 최소값 : -1 → b(i)가 값이 작아지고 오히려 a(i)가 커지는 경우 : 제일 나쁜 상황

실습
- sklearn의 make_blobs로 가사의 군집 데이터 생성
from sklearn.datasets import make_blobs
# 가상 데이터 생성
X, y_true = make_blobs(n_samples=300,
centers=4, # Cluster의 수 혹은 Cluster의 중심 위치 좌표
cluster_std=0.60, # 각 클러스터가 얼마나 퍼져있게 할건지
n_features=2, # 사용할 feature의 수, 시각화를 위해 2차원 사용
random_state=0)
# 데이터 시각화
plt.scatter(X[:, 0], X[:, 1], s=20)
plt.title("Generated Data")
plt.show()

- K-means 클러스터링 진행하여 중심점과 같이 시각화
from sklearn.cluster import KMeans
# K-Means 클러스터링 수행 (k=4)
kmeans = KMeans(n_clusters=4,
init='k-means++', # 초기 클러스터 중심 위치를 잡는 초기화 방법
n_init=10, # 초기 중심점 잡기를 얼마나 많은 다른 방법으로 설정할지를 결정 / 10번의 시도를 하겠다는 것이고, 10번 중 최고의 SSE 성능을 보이는 모델을 최종 모델로 사용
random_state=0)
kmeans.fit(X)
y_kmeans = kmeans.predict(X)

- 엘보우 방법으로 최적의 K 찾기
# 1~10 까지의 K를 설정하고 각 경우에 맞춰 학습을 진행
inertia = []
K_range = range(1, 10)
for k in K_range:
kmeans = KMeans(n_clusters=k, init='k-means++', n_init=10, random_state=0)
kmeans.fit(X)
inertia.append(kmeans.inertia_) # SSE 값을 저장

- 실루엣 계수 이용하기
from sklearn.metrics import silhouette_score
silhouette_avg = silhouette_score(X, y_kmeans)
print("Silhouette Score: {:.2f}".format(silhouette_avg))

2. 이상 탐지(Anomaly Detection)
- 데이터에서 비정상적인 패턴, 이상치, 또는 예외적인 사례를 탐지하는 과정
| 전통적인 통계 기반 이상 탐지 | 머신 러닝을 활용한 이상 탐지 |
| 계산이 복잡하지 않고 해석이 직관적 | 복잡한 데이터의 패턴에 적응적, 지속적인 학습과 개선이 가능 |
| 데이터가 대칭이 아니거나 복잡한 분포를 갖고 있다면 오인식이 늘어남 | 처리할 모델이 복잡할 수 있고, 사람이 해석하기 어려운 패턴이 존재할 가능성 있음 |
3. Isolation Forest(고립숲)
- 개념
- 정상 데이터는 비슷한 특성 패턴을 갖는 비슷한 데이터와 밀도 있게 모여있을 것.
- 반면 이상치는 밀도가 낮은 공간에 존재
- 하나의 데이터를 '고립'하도록 어떤 특성과 그것의 분할 값을 기준으로 나눔
- 정상 데이터는 밀도가 ↑, 많은 분할 과정이 지나야 고립됨
- 이상치 데이터는 밀도가 ↓, 적은 분할 과정으로도 쉽게 고립됨
3.1 작동 과정
- 데이터 준비 : 전체 데이터에서 무작위로 서브 데이터셋을 선택(어떤 데이터는 여러 서브 데이터셋에 속할 수 있음)

- 트리 생성 : 하나의 서브 데이터셋마다 개별 Isolation Tree를 준비

- 고립 시작 : 각 트리에 소속된 데이터셋을 하나씩 고립
- 임의의 특성과 임의의 특성 분할 값을 선택
- 데이터셋이 리프 노드에 도달할 때까지 반복
- 각 데이터 포인트는 리프 노드까지 도달한 깊이를 저장함

- 고립 완료 : 하나의 트리에 소속된 모든 데이터셋을 전부 고립
- 트리 계산 완료 : 모든 트리에서 고립 과정이 완료됨. 여러 트리에 소속된 데이터 포인트는 서로 다른 트리에서 계산된 깊이가 저장됨

- 이상치 점수 계산 : 평균 깊이를 바탕으로 각 데이터 포인트의 이상치 점수를 계산
- 이상치 추출 : 이상치 점수와 특정 임계값을 비교해 이상치 추출

실습
- IsolationForest() 사용해보기
- n_estimators : 고립 트리의 수. 서브 데이터셋의 수와 같음. 더 많은 트리를 사용할수록 탐지의 안정성과 정확성 증가
- max_samples : 서브 데이터셋에 포함될 최대 데이터 포인트의 수
- contamination : 전체 데이터셋에서 이상치 데이터가 차지할 비율
- 이는 데이터 내의 이상치가 어느 정도 있음을 알고 있는 상황에서 유용
- 만약 모른다면 'auto' 사용
- 이상치 데이터 정답을 알고 있다면 즉, 어떤 데이터가 이상치인지 알고 있다면 이상 데이터를 양성으로 놓고 분류 형태의 문제로 평가 가능
- 정확도, 정밀도, 재현율, F1값으로 평가 진행
- 만약 정답이 없다면 시각화를 통한 전문가 검토를 진행
# Isolation Forest 모델 생성을 위한 매개 변수
n_estimators = 100 # 사용할 트리의 수
max_samples = 'auto' # 각 트리당 최대 샘플 수
contamination = 0.15 # 전체 데이터셋에서 이상치가 차지하는 비율의 추정치
# 이 값을 이용해 추후 이상치를 찾아냄
# 이상치 점수가 이 임계값보다 높은 데이터 포인트는 이상치로 간주
# Isolation Forest 생성 및 학습
from sklearn.ensemble import IsolationForest
IForest = IsolationForest(n_estimators=n_estimators,
max_samples=max_samples,
contamination=contamination,
random_state=seed)
IForest.fit(X)

- 이상치 추정한 후 시각화
# 모든 데이터에 대한 이상치 점수 계산
# 이 값은 트리 내에서의 평균 경로 depth를 기반으로 계산됨
# 정규화 된 값으로,
# 점수가 낮으면 평균 길이가 짧다고 보면 되며 이상치일 가능성이 ↑
scores_pred = IForest.decision_function(X)

- 이상치 여부 결정한 후 정상 데이터와 이상치 데이터 분리
# 이상치 여부 결정
IForest.predict(X)
is_outlier = IForest.predict(X) == -1
is_outlier
# 정상 데이터와 이상치 데이터 분리
X_normal_detected = X[~is_outlier]
X_outliers_detected = X[is_outlier]
4. K-means 클러스터링과 Isolation Forest 실습
4.1 K-means clustering
- 케글의 Mall Customers 데이터셋을 이용
- 데이터 셋 : https://www.kaggle.com/datasets/kandij/mall-customers/
Mall customers
We use K-means clustering algorithm majorly in this kernel.
www.kaggle.com
- 문제 : 주어진 고객 데이터를 바탕으로 고객을 세분화 군집화
※ 군집화는 어떤 문제의 결론이 되어서는 안된다!! 군집화는 결론에 도달하기 위한 수단일 뿐. 해당 데이터셋의 경우 군집화를 통한 타겟 마케팅 전략 수립, 신규 고객 유치 등의 결론으로 이어질 수 있다.
4.1.1 데이터 전처리 및 EDA
- 데이터 생김새 확인

- 고객 ID는 고유 식별자로 학습에서 제외
- 성별은 인코딩 필요
- 나이, 연간 소득, 쇼핑 점수는 각자 서로 다른 스케일을 갖고 있음
- 성별 인코딩
# 성별을 이진 변수로 변환
encoding_map = {'Female': 1, 'Male': 0}
categori_data_encode = pd.DataFrame(customers['Genre'].replace(encoding_map))
categori_data_encode.columns = ['Gender']
categori_data_encode
- 결측치 없음, 유의미한 이상치 없음
- 상관관계 확인 → 유의미한 관계 없음
- 분포 확인

- 수치형 데이터 스케일링 → 세 컬럼 모두 정규 분포와 비슷한 모습을 갖고 있어 StandardScaler 사용 결정
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
# 수치형 데이터 스케일링
numeric_data = customers[numeric_columns]
numeric_data_scaled = scaler.fit_transform(numeric_data)
numeric_data_scaled = pd.DataFrame(numeric_data_scaled)
numeric_data_scaled.columns = numeric_columns
numeric_data_scaled
4.1.2 모델 구축 및 결과 확인
- 엘보우 방법을 이용한 K value 서칭(시각화 부분은 편의상 생략)
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
inertia, silhouette = [], []
K_range = range(2, 11)
for k in K_range:
kmeans = KMeans(n_clusters=k, init='k-means++', n_init=10, random_state=0)
kmeans.fit(customers_combined)
inertia.append(kmeans.inertia_)
y_kmeans_silhouette = kmeans.predict(customers_combined)
silhouette.append(silhouette_score(customers_combined, y_kmeans_silhouette))

- 4와 6이 가장 높지만 이 경우 K=6을 선택
- 실루엣 계수의 변동이 있는 것은 매우 일반적인 현상
- 데이터 구조의 복잡성, 잡음과 이상치, 균일하지 않은 밀도 & 데이터 간 거리
- 실루엣 계수 간 차이가 크지 않다면 작은 K를 기준으로 하는 게 좋은 선택(이번 실습은 예외)
- 학습 진행
kmeans = KMeans(n_clusters=6,
init='k-means++',
n_init=10,
random_state=0)
kmeans.fit(customers_combined)
- 평가 진행
- 0.36 정도는 어느 정도 군집화가 의미있게 진행됐다고 볼 수 있음
- 개선의 여지는 있음
y_pred = kmeans.predict(customers_combined)
silhouette_avg = silhouette_score(customers_combined, y_pred)
print("SSE Value : {:.2f}".format(kmeans.inertia_))
print("Silhouette Score: {:.2f}".format(silhouette_avg))

- 결과 해석을 위해 차원 축소
- 4차원 데이터를 시각화하기 위해 t-SNE를 이용하여 2차원으로 축소 과정 진행
# 우리가 사용한 4차원 데이터를 시각화하기 위해 2차원 데이터로 변환 (t-SNE 알고리즘 활용)
from sklearn.manifold import TSNE
tsne = TSNE(n_components=2, random_state=seed)
customers_tsne = tsne.fit_transform(customers_combined)
# 2차원으로 변화된 데이터에 feature 이름을 넣어주고
# K-means가 예측한 각 데이터의 클러스터링 인덱스를 제공
customers_tsne_df = pd.DataFrame(data=customers_tsne, columns=['TSNE1', 'TSNE2'])
customers_tsne_df['Cluster'] = y_pred
customers_tsne_df
# 시각화
np.random.seed(seed)
plt.figure(figsize=(10, 8))
for idx in range(kmeans.n_clusters):
_color = (np.random.random(), np.random.random(), np.random.random())
cluster_data = customers_tsne_df[customers_tsne_df['Cluster'] == idx]
plt.scatter(cluster_data['TSNE1'],
cluster_data['TSNE2'],
color=_color,
label=f'Cluster {idx+1}',
marker='o')
plt.title('Customer Segments - t-SNE 2D')
plt.legend()
plt.show()

- 클러스터 별로 의미를 찾으려면 원래 데이터로 재건하는 과정을 거쳐야 함(지금은 스케일링 데이터이기 때문)
# 원래 데이터에서 재건하기
customers_combined['Cluster'] = kmeans.labels_
# 수치형 데이터 스케일링 작업을 역으로 수행하기
original_numeric_data = pd.DataFrame(scaler.inverse_transform(customers_combined[numeric_columns]))
original_numeric_data.columns = numeric_columns
# 범주형 데이터 역 인코딩 (필요시)
# 수치형으로 되어있던 데이터를 다시 범주형으로 바꾸면 특성 파악이 눈에 안들어올 수 있음!
reverse_encoding_map = {v: k for k, v in encoding_map.items()}
original_category_data = pd.DataFrame(customers_combined['Gender'].replace(reverse_encoding_map))
original_category_data.columns = category_columns
# pandas로 원래 데이터 다시 만들기
labeled_origin_date = pd.concat([customers_combined['Cluster'],
original_numeric_data,
customers_combined['Gender']],
# original_category_data], # 범주형 데이터 필요시 사용
axis=1)
labeled_origin_date

4.1.3 결과 해석
- 실습에서는 포함되지 않았지만 타겟 마케팅 전략 도출이라는 문제로 개인적인 해석 도출을 해보자면 :
- 2번 클러스터 : 젊은 나이대(평균 나이 20대)의 고객들은 연간 소득이 높지 않음에도 소비를 많이 하는 것(77점)으로 나타났다
- 4번 클러스터 : 반면, 중년(평균 나이 45세) 고객들은 연간 소득이 2번 그룹과 비슷함에도 소비를 하지 않는다(19점)
- 1번 클러스터 : 평균 연간 소득이 높으면서 평균 나이가 32세인 그룹은 소비도 많이 한다
- 5번 클러스터 : 평균 연간 소득이 가장 높으면서 평균 나이가 41세인 그룹은 반대로 소비도 가장 적게 하는 모습이다.
- 이에 따르면 연령이 20-30대로 젊은 고객들은 연간 소득에 구애받지 않고 높은 소비율을 보이는데
- 반면 40대 이상의 고객들은 연간 소득에 상관없이 낮은 소비율을 보인다.
- 충성 고객이 될 확률이 높은 젊은층 고객들을 위한 아이템을 더 선보이는 방안을 제시해볼 수 있다
- 높은 연령대의 고객들의 경우, 그들의 소비 아이템의 트렌드를 파악하여 소비를 격려할 수 있는 쿠폰이나 할인 이벤트를 진행해볼 수 있다.
4.2 Isolation Forest를 이용한 이상 탐지 실습
- 케글의 신용카드 거래에서의 사기 탐지를 위해 설계된 데이터셋 사용
- 총 284,807건의 거래 중 492건(0.172%)의 사기 거래가 있음
- 데이터 셋 : https://www.kaggle.com/datasets/mlg-ulb/creditcardfraud
Credit Card Fraud Detection
Anonymized credit card transactions labeled as fraudulent or genuine
www.kaggle.com
- 문제 : 주어진 거래 관련 데이터를 바탕으로 이상 거래 데이터 탐지
- 입력 :
- Time : 첫 거래 후 각 거래 사이 경과 시간(초)
- Amount : 거래 금액
- V1 ~ V28 : PCA로 얻은 수치형 입력 변수
- 출력 :
- 각 데이터 포인트마다 할당된 이상치 점수
4.2.1 데이터 전처리 및 EDA
- Time은 주기성을 나타내어 주기 함수를 적용하는 것이 좋지만 이 경우에는 큰 이상치가 없어보이므로 MinMaxScaling 사용
- Amount은 치우침이 있어 log 스케일링 진행

# Amount에 Log scaling 적용
credit['Log_Amount'] = np.log(credit['Amount'] + 1)
credit
# Time 변수를 Min Max Scaling
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
credit_time = scaler.fit_transform(credit[['Time']])
credit_time = pd.DataFrame(credit_time)
credit_time.columns = ['Time']
credit_time
- V1 ~ V28은 분포가 대부분 정규분포를 따름. 따라서 스케일링을 진행하지 않고 그대로 사용
- 스케일링을 한 데이터를 합친 데이터 프레임 생성
4.2.2 모델 구축 및 결과 확인
- Isolation Forest 학습 진행
- 원본 데이터에서 이상치의 비율을 이미 알려주었기 때문에 contamination은 해당 값을 사용
n_estimators = 100
max_samples = 'auto'
# contamination = 'auto'
contamination = num_Fraud/(num_Normal+num_Fraud)
# Isolation Forest 생성 및 학습
from sklearn.ensemble import IsolationForest
IForest = IsolationForest(n_estimators=n_estimators,
max_samples=max_samples,
contamination=contamination,
random_state=seed)
IForest.fit(credit_combined)
- 모델 평가 진행
y_true = credit['Class']
y_pred = IForest.predict(credit_combined)
y_pred = np.where(y_pred == 1, 0, 1)
# 성능 평가
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
accuracy = accuracy_score(y_true, y_pred)
precision = precision_score(y_true, y_pred)
recall = recall_score(y_true, y_pred)
f1 = f1_score(y_true, y_pred)
print(f'정확도 Accuracy : {accuracy*100:.2f} %')
print(f'정밀도 Precision : {precision*100:.2f} %')
print(f'재현율 Recall : {recall*100:.2f} %')
print(f'F1 : {f1*100:.2f} %')

- 이상치 데이터는 전체 데이터에서 극 소수(0.172%)이므로 정밀도, 재현율, F1 값이 높지 않음
- Confusion Matrix를 통해 확인
- 실제 사기 거래인 34건의 거래 중 11건만 True Positive로 탐지

느낀점
최근에 개인적으로 RFM Segmentation을 이용한 고객 세분화 프로젝트를 진행중이었는데 다른 사람들의 노트북을 보니 K-means가 자주 눈에 띄었다. 그래서 궁금했는데 이번 강의를 통해서 어떤 것인지 알게 되었고 해당 프로젝트에 오늘 배운 내용을 적용해볼 수 있을 것 같아서 기대된다.
'Data Science > TIL (Today I Learned)' 카테고리의 다른 글
| 프로그래머스 데이터분석 데브코스 1기 - 51일차 (0) | 2024.02.05 |
|---|---|
| 프로그래머스 데이터분석 데브코스 1기 - 50일차 (0) | 2024.02.02 |
| 프로그래머스 데이터분석 데브코스 1기 - 48일차 (0) | 2024.01.31 |
| 프로그래머스 데이터분석 데브코스 1기 - 47일차 (1) | 2024.01.30 |
| 프로그래머스 데이터분석 데브코스 1기 - 46일차 (1) | 2024.01.29 |