#!/usr/bin/env python3


clustering valuation(군집 모델 평가하기)


agglomerative algorithm군집 알고리즘을 적용하는데 어려운 점은 

algorithm이 잘 작동하는지 평가하거나 여러 algorithm의 출력을 비교하기가 매우 어려움


1. target value로 clustering 평가하기


agglomerative algorithm의 결과를 실제 정답 cluster와 비교하여 평가할 수 있는 지표

1. ARIadjusted rand index : 0과 1사이의 값을 제공(그러나 음수가 될 수 있음)

2. NMInormalized mutual information를 가장 많이 사용


# library import

import matplotlib

import matplotlib.pyplot as plt

import numpy as np

import mglearn

from sklearn.datasets import make_moons

from sklearn.preprocessing import StandardScaler

from sklearn.cluster import KMeans, AgglomerativeClustering, DBSCAN

from sklearn.metrics.cluster import adjusted_rand_score


# matplotlib 설정

matplotlib.rc('font', family='AppleGothic'# 한글출력

plt.rcParams['axes.unicode_minus'] = False # 축 -표시


# dataset

x, y = make_moons(n_samples=200, noise=0.05, random_state=0)


# pre-processing

scaler = StandardScaler().fit(x)

x_scaled = scaler.transform(x)


# model list

algorithms = [KMeans(n_clusters=2),

              AgglomerativeClustering(n_clusters=2),

                      DBSCAN()]

kmean_pred = algorithms[0].fit_predict(x_scaled)

# random cluster

random_state = np.random.RandomState(seed=0)

random_clusters = random_state.randint(low=0, high=2, size=len(x))


### visualization

fig, axes = plt.subplots(2, 2,

                         subplot_kw={'xticks':(), 'yticks':()})



axes[0,0].scatter(x_scaled[:, 0], x_scaled[:, 1], c=random_clusters,

                cmap=mglearn.cm3, s=60, edgecolors='k')

axes[0,0].set_title('random assign - ARI: {:.3f}'.format(

    adjusted_rand_score(y, random_clusters))) # 실제, 모델로 구한 클러스터


axes[0,1].scatter(x_scaled[:, 0], x_scaled[:, 1], c=kmean_pred,

                  cmap=mglearn.cm3, s=60, edgecolors='k')

axes[0,1].set_title('{} - ARI: {:.3f}'.format(algorithms[0].__class__.__name__, #__class__.__name__ ==> 클래스에서 이름 속성

                                              adjusted_rand_score(y, kmean_pred)))


for ax, algorithm in zip(axes.ravel()[2:], algorithms[1:]):

    clusters = algorithm.fit_predict(x_scaled)

    ax.scatter(x_scaled[:, 0], x_scaled[:, 1], c=clusters,

               cmap=mglearn.cm3, s=60, edgecolors='k')

    ax.set_title('{} - ARI: {:.3f}'.format(algorithm.__class__.__name__,

                                           adjusted_rand_score(y, clusters)))

plt.show()

two_moons 데이터셋에 적용한 random assign, KMeans, AgglomerativeClustering, DBSCAN의 ARI Score



2. no target value로 clustering 평가하기

ARI 같은 방법은 반드시 target value가 있어야하지만 실제로는 결과와 비교할 target value가 없음

따라서 ARI나 NMI 같은 지표는 algorithm을 개발할 때 도움이 많이 됨


silhouette coefficient실루엣 계수: 실제로 잘 동작하지 않음, cluster의 밀집 정도를 계산, max = 1

모양이 복잡할때는 잘 들어맞지가 않음


# library import

from sklearn.metrics.cluster import silhouette_score


# datasets

x, y = make_moons(n_samples=300, noise=0.05, random_state=0)


# pre-processing

scaler = StandardScaler().fit(x)

x_scaled = scaler.transform(x)


# random cluster

random_state = np.random.RandomState(seed=0)

random_clusters = random_state.randint(0, 2, size=len(x)) # randint(low, high, size)


# K-Means 모델

kmeans = KMeans(n_clusters=2)

kmeans_pred = kmeans.fit_predict(x_scaled)


### visualization

fig, axes = plt.subplots(2, 2,

                         subplot_kw={'xticks':(), 'yticks':()})

ax = axes.ravel()


# random cluster 그리기

ax[0].scatter(x_scaled[:, 0], x_scaled[:,1], c=random_clusters,

            cmap=mglearn.cm2, edgecolors='k', s=60)

ax[0].set_title('random assign - silhouette score: {:.3f}'.format(silhouette_score(x_scaled, random_clusters))) # silhouette_score(훈련데이터, 예측한 클러스터)


# K-Means 그리기

ax[1].scatter(x_scaled[:, 0], x_scaled[:, 1], c=kmeans_pred,

           cmap=mglearn.cm2, edgecolors='k', s=60)

ax[1].set_title('{} - silhouette score: {:.3f}'.format(kmeans.__class__.__name__,

                                                 silhouette_score(x_scaled, kmeans_pred)))


# AgglomerativeClustering과 DBSCAN 그리기

algorithms = [AgglomerativeClustering(n_clusters=2), DBSCAN()]

for ax, model in zip(ax[2:], algorithms):

    cluster = model.fit_predict(x_scaled)

    ax.scatter(x_scaled[:, 0], x_scaled[:, 1], c=cluster,

               cmap=mglearn.cm2, edgecolors='k', s=60)

    ax.set_title('{} - silhouette score: {:.3f}'.format(model.__class__.__name__,

                                                  silhouette_score(x_scaled, cluster)))

plt.show()

two_moons 데이터셋에 적용한 random_assign, KMeans, AgglomerativeClustering, DBSCAN의 silhouette score


DBSCAN의 결과가 더 보기 좋지만 K-mean의 silhouette score가 더 높음

Agglomerative Model이 매우 안정적이거나 silhouette score가 더 높더라도 cluster에 어떤 유의미한 것이 있는지 알 수가 없음


#!/usr/bin/env python3


DBSCAN



0. 살펴보기

DBSCANdensity-based spatial clustering of application with noise은 클러스터의 갯수를 미리 지정하지 않는 군집 알고리즘

DBSCAN은 병합 군집이나 k-평균보다는 다소 느리지만 비교적 큰 데이터셋에도 적용


데이터의 밀집지역이 한 클러스터를 구성하며 비교적 비어있는 지역을 경계로 다른 클러스터와 구분함


DBSCAN은 특성 공간에서 가까이 있는 데이터가 많아 붐비는 지역의 포인트를 찾음

이런 지역을 밀집 지역dense region이라 함

밀집 지역에 있는 포인트를 핵심 포인트core point라고함


핵심 포인트: min_samples, epsepsilon

한 데이터 포인트에서 eps 거리 안에 데이터가 min_samples 갯수만큼 들어 있으면 이 데이터 포인트를 핵심 포인트로 분류

eps(거리)보다 가까운 핵심 샘플은 동일한 클러스터로 분류


알고리즘

1. 무작위로 데이터 포인트를 선택


2. 그 포인트에서 eps 거리안의 모든 포인트를 찾음

2-1 eps 거리 안에 있는 데이터 포인트 수가 min_samples보다 적다면 어떤 클래스에도 속하지 않는 잡음noise로 레이블

2-2 eps 거리 안에 있는 데이터 포인트 수가 min_samples보다 많으면 핵심 포인트로 레이블하고 새로운 클러스터 레이블할당


3. 2-2의 핵심 포인트의 eps거리안의 모든 이웃을 살핌

3-1 만약 어떤 클러스터에도 아직 할당되지 않았다면 바로 전에 만든 클러스터 레이블을 할당

3-2 만약 핵심 포인트면 그 포인트의 이웃을 차례로 확인


4. eps 거리안에 더이상 핵심 포인트가 없을 때까지 진행


핵심포인트, 경계포인트(핵심 포인트에서 eps거리 안에 있는 포인트), 잡음 포인트가 핵심


DBSCAN을 한 데이터셋에 여러 번 실행하면 핵심 포인트의 군집은 항상 같고, 매번 같은 포인트를 잡음으로 레이블함

그러나 경계포인트는 한 개 이상의 핵심 포인트 샘플의 이웃일 수 있음


# library import

import mglearn

import matplotlib.pyplot as plt

from matplotlib import rc


# matplotlib 설정

rc('font', family='AppleGothic'

plt.rcParams['axes.unicode_minus'] = False


# 예제

mglearn.plots.plot_dbscan()

plt.show()


min_samples와 eps 매개변수를 바꿔가며 DBSCAN으로 계산한 클러스터


이 그래프에서 클러스터에 속한 포인트는 색을 칠하고 잡음 포인트는 하얀색으로 남겨둠

핵심포인트는 크게 표시하고 경계 포인트는 작게 나타냄

eps를 증가시키면(왼쪽 -> 오른쪽) 하나의 클러스터에 더 많은 포인트가 포함

이는 클러스터를 커지게 하지만 여러 클러스터를 하나로 합치게도 함

min_samples를 키우면(위 -> 아래) 핵심 포인트 수가 줄어들며 잡음 포인트가 늘어남




1. make_blobs(인위적 데이터)로 DBSCAN 적용

eps 매개변수가까운 포인트의 범위를 결정하기 때문에 매우 중요

eps를 매우 작게 하면 어떤 포인트도 핵심 포인트가 되지 못하고, 모든 포인트가 잡음 포인트가 될 수 있음

eps를 매우 크게 하면 모든 포인트가 단 하나의 클러스터에 속하게 됨


min_samples 설정은 덜 조밀한 지역에 있는 포인트들이 잡은 포인트가 될 것인지, 아니면 하나의 클러스터가 될 것인지를 결정

min_samples보다 작은 클러스터들은 잡음포인트가 되기 때문에 클러스터의 최소 크기를 결정


DBSCAN은 클러스터의 갯수를 지정할 필요가 없지만 eps의 값은 간접적으로 클러스터의 갯수 제어

적절한 eps 값을 찾기 위해 전처리preprocessing(StandardScaler, MinMaxScaler)로 조정하는 것이 좋음


### DBSCAN은 새로운 테스트 데이터에 대해 예측

### 할 수 없으므로(병합군집, 매니폴드학습과 동일) fit_predict를 사용

# library import

from sklearn.cluster import DBSCAN

from sklearn.datasets import make_blobs


# dataset

x, y = make_blobs(n_samples=12, random_state=0)


# 모델생성 및 클러스터 레이블

dbscan = DBSCAN()

clusters = dbscan.fit_predict(x)


# 클러스터 레이블 출력

print('clusters \n클러스터레이블: {}'.format(clusters))

### 모든 포인트에 잡은 포인트를 의미하는 '-1'이 할당

### eps와 min_samples 기본값이 작은 데이터셋에 적절하지 않음



2. 전처리 후 make_blobs(인위적 데이터)에 DBSCAN적용

# library import

from sklearn.datasets import make_moons

from sklearn.preprocessing import StandardScaler

from sklearn.preprocessing import MinMaxScaler

import numpy as np


# dataset

x, y = make_moons(n_samples=200, noise=0.05, random_state=0)


# MinMaxScaler 메소드로 전처리

scaler_MMS = MinMaxScaler().fit(x)

x_scaled_MMS = scaler_MMS.transform(x) # 전처리 메소드를 훈련데이터에 적용

dbscan = DBSCAN() # 모델생성

clusters_MMS = dbscan.fit_predict(x_scaled_MMS) # 모델 학습

print('np.unique(clusters_MMS)\n예측한 레이블: {}'.format(np.unique(clusters_MMS))) # [0]

### 예측한 레이블이 0으로 전부 하나의 클러스터로 표현

### MinMaxScaler전처리가 적합하지 않음


scaler_ss = StandardScaler().fit(x)

x_scaled_ss = scaler_ss.transform(x) 

dbscan = DBSCAN()

clusters_ss = dbscan.fit_predict(x_scaled_ss)

print('np.unique(clusters_ss)\n예측한 레이블:{}'.format(np.unique(clusters_ss))) # [0 ,1]

### 2차원 데이터셋을 0과 1로 구분했기 때문에 전처리가 잘되었음을 확인


# visualization

df = np.hstack([x_scaled_ss, clusters_ss.reshape(-1, 1)]) # x_scaled_ss 오른쪽에 1열 붙이기


df_ft0 = df[df[:,2]==0, :] # 클러스터 0 추출

df_ft1 = df[df[:,2]==1, :] # 클러스터 1 추출


# matplotlib로 그래프 그리기

plt.scatter(df_ft0[:, 0], df_ft0[:, 1], label='cluster 0', cmap='Pairs'# x, y, label, 색상

plt.scatter(df_ft1[:, 0], df_ft1[:, 1], label='cluster 1', cmap='Pairs')

plt.xlabel('feature 0')

plt.ylabel('feature 1')

plt.legend()

plt.show()

기본값 eps=0.5를 사용해 DBSCAN으로 찾은 클러스터



#!/usr/bin/env python3


병합 군집(agglomerative clustering)


0. 살펴보기


병합 군집agglomerative clustering 알고리즘은 

시작할 때 각 포인트를 하나의 클러스터로 지정하고, 그다음 종료 조건을 만족할 때까지 가장 비슷한 두 클러스터를 합침

종료조건 : 클러스터 갯수, 지정된 갯수의 클러스터가 남을 때까지 비슷한 클러스터를 합침


linkage: 옵션에서 가장 비슷한 클러스터를 측정하는 방법 지정, 이 측정은 항상 두 클러스터 사이에서 이뤄짐


ward : 기본값, 모든 클러스터 내의 분산을 가장 작게 증가시키는 두 클러스터를 합침, 크기가 비교적 비슷한 클러스터가 만들어짐


average : 클러스터 포인트 사이의 평균 거리가 가장 짤븐 두 클러스터를 합침


complete : complete연결(최대 연결)은 클러스터 포인트 사이의 최대 거리가 가장 짧은 두 클러스터를 합침


클러스터에 속한 포인트 수가 많이 다를 때(하나의 클러스터가 다른 것보다 매우 클 때 ) average나 complete가 더 나음


# library import

import mglearn

import matplotlib

import matplotlib.pyplot as plt


# matplotlib 설정

matplotlib.rc('font', family='AppleGothic')

plt.rcParams['axes.unicode_minus'] = False


# 알고리즘 설명 시각화

mglearn.plots.plot_agglomerative_algorithm()

plt.show()

두 인접 클러스터를 반복적으로 합쳐나가는 병합 군집

1. 초기에 각 포인트가 하나의 클러스터

2. 가장 가까운 두 클러스터가 합쳐짐

3. step4까지 이 방식으로 진행

4. step5에서 두 개의 포인트를 가진 클러스중 하나가 3개로 확장

5. step9까지 이 방식으로 진행

6. 3개의 클러스터를 찾는다고 지정하면 알고리즘은 종료




1. make blobs(인위적 데이터)로 병합 군집 분석


알고리즘의 작동 특성상 병합 군집은 새로운 데이터 포인트에 대해서는 예측을 할 수 없기 때문에

predict메소드가 없음(매니폴드 학습과 동일)


# library import

from sklearn.cluster import AgglomerativeClustering

from sklearn.datasets import make_blobs

import numpy as np


# dataset

x, y = make_blobs(random_state=1)


# 모델 생성 및 학습

agg = AgglomerativeClustering(n_clusters=3)

assign = agg.fit_predict(x)


# 배열 x 오른쪽에 열 한개 추가

a = assign.reshape(-1, 1)

x1 = np.hstack([x, a])


# 각 클래스별로 데이터 추출

x_0 = x1[x1[:, 2]==0, :]

x_1 = x1[x1[:, 2]==1, :]

x_2 = x1[x1[:, 2]==2, :]


# 시각화

plt.scatter(x_0[:, 0], x_0[:, 1], cmap=mglearn.cm3)

plt.scatter(x_1[:, 0], x_1[:, 1], cmap=mglearn.cm3)

plt.scatter(x_2[:, 0], x_2[:, 1], cmap=mglearn.cm3)

plt.legend(['cluster 0', 'cluster 1', 'cluster 2'], loc=2)

plt.show()

병합 군집을 사용한 세 개의 클러스터 할당




2. 계층적 군집과 덴드로그램

병합군집은 계층적 군집hierarchical clustering을 만듬. 군집이 반복하여 진행되면 모든 포인트는 하나의 포인트를 가진 클러스터에서 시작하여 마지막 클러스터까지 이동

각 중간단계는 데이터에 대한 (다른 갯수)의 클러스터를 생성


mglearn.plots.plot_agglomerative()

plt.show()

병합 군집으로 생성한 계층적 군집


이 그래프는 계층 군집의 모습을 자세히 나타내지만, 2차원 데이터일 뿐이며 특성이 셋 이상인 데이터셋에는 적용불가

dendrogram은 다차원 데이터셋을 처리가능



3. dendrogram으로 3차원 이상의 데이터 시각화


# library import

from scipy.cluster.hierarchy import dendrogram, ward[각주:1]


# dataset

x, y = make_blobs(random_state=0, n_samples=12)


### 데이터 배열 x에 ward 함수를 적용

### scipy의 ward 함수는 병합 군집을 수행할 때 생성된 거리 정보가 담긴 배열을 반환


linkage_array = ward(x)

dendrogram(linkage_array)

ax = plt.gca() # get current axes

bounds = ax.get_xbound() # x축 데이터(처음과 끝), 즉 최소/최대값을 가진 (1,2)리스트

ax.plot(bounds, [7.25, 7.25], linestyle='--', c='k'# 임의로 라인 생성

ax.plot(bounds, [4, 4], linestyle='--', c='k')

ax.text(bounds[1], 7.25, ' 두 개의 클러스터', va='center', fontdict={'size':15}) # bounds: x축 끝

ax.text(bounds[1], 4, ' 세 개의 클러스터', va='center', fontdict={'size':15})

plt.xlabel('샘플 번호')

plt.ylabel('클러스터 거리')

plt.show()

클러스터의 덴드로그램과 클러스터를 구분하는 점선


덴드로그램에서 데이터 포인트(0 ~ 11)까지는 맨 아래 나타남

이 포인트들을 잎leaf으로 하는 트리가 만들어지며 부모 노드는 두 클러스터가 합쳐질때 추가됨


가지의 길이는 합쳐진 클러스터가 얼마나 멀리 떨어져 있는지를 보여줌

빨강 그룹과 바다색 그룹이 합쳐질때 가지의 길이는 짧아짐 ==> 비교적 짧은 거리의 데이터를 병합

  1. ward : 기본값, 모든 클러스터 내의 분산을 가장 작게 증가시키는 두 클러스터를 합침, 크기가 비교적 비슷한 클러스터가 만들어짐 [본문으로]

#!/usr/bin/env python3


벡터 양자화(vector quantization)로서의 k-평균


0. 살펴보기

PCA : 데이터의 분산이 가장 큰 방향

NMF : 데이터의 극단 또는 일부분에 상응되는 중첩할 수 있는 성분

k-평균 : 클러스터의 중심으로 데이터 포인트를 표현 ==> 각 데이터 포인트가 클러스터 중심, 즉 하나의 성분으로 표현된다고 볼 수 있음

k-평균을 이렇게 각 포인트가 하나의 성분으로 분해되는 관점으로 보는 것을 벡터 양자화vector quantization이라 함


1. PCA, NMF, k-평균에서 추출한 성분을 바탕으로 시각화

# library import

from sklearn.datasets import fetch_lfw_people

from sklearn.model_selection import train_test_split

import matplotlib.pyplot as plt

import matplotlib

import numpy as np

from sklearn.decomposition import NMF

from sklearn.decomposition import PCA

from sklearn.cluster import KMeans


# matplotlib 설정

matplotlib.rc('font', family='AppleGothic')

plt.rcParams['axes.unicode_minus'] = False


# people객체 생성

people = fetch_lfw_people(min_faces_per_person=20, resize=0.7, color=False# 겹치치 않는 최소사람 수, 비율, 흑백

image_shape = people.images[0].shape 


# 특성 50개 추출(True)

idx = np.zeros(people.target.shape, dtype=np.bool) # 3023개의 False생성

for target in np.unique(people.target): 

    idx[np.where(people.target == target)[0][:50]] = 1 


# 데이터 추출

x_people = people.data[idx]

y_people = people.target[idx]


# 데이터 분할

x_train, x_test, y_train, y_test = train_test_split(

    x_people, y_people, # 분할할 데이터

    stratify=y_people, random_state=0) # 그룹, 랜덤상태


# 모델생성 및 학습

nmf = NMF(n_components=100, random_state=0).fit(x_train) 

pca = PCA(n_components=100, random_state=0).fit(x_train)

kmeans = KMeans(n_clusters=100, random_state=0).fit(x_train)


### pca.transform(x_test): 주성분 갯수만큼 차원을 줄인 데이터

### pca.inverse_transform(pca.transform(x_test)): 차원을 줄인 주성분으로 원래 데이터에 매핑

### kmeans.cluster_centers_[kmeans.predict(x_test)] k-평균 중심에서 k-평균으로 구한 라벨을 행 인덱스로 추출하여 재구성

x_reconst_nmf = np.dot(nmf.transform(x_test), nmf.components_) nmf.inverse_transform(nmf.transform(x_test)) 와 동일

x_reconst_pca = pca.inverse_transform(pca.transform(x_test))

x_reconst_kmeans = kmeans.cluster_centers_[kmeans.predict(x_test)]


# visualization

fig, axes = plt.subplots(3, 5,

                         subplot_kw={'xticks':(), 'yticks':()})

for ax, comp_nmf, comp_pca, comp_kmeans in zip(

        axes.T, nmf.components_, pca.components_, kmeans.cluster_centers_): # 행방향 순서로 그림을 채우기 때문에 Transfrom을 

    ax[0].imshow(comp_nmf.reshape(image_shape))

    ax[1].imshow(comp_pca.reshape(image_shape))

    ax[2].imshow(comp_kmeans.reshape(image_shape))


axes[0, 0].set_ylabel('nmf')

axes[1, 0].set_ylabel('pca')

axes[2, 0].set_ylabel('kmeans')

plt.gray()

plt.show()

NMF, PCA, k-평균의 클러스터 중심으로 찾은 성분의 비교

# library import 

fig, axes = plt.subplots(4, 5,

                         subplot_kw={'xticks':(), 'yticks':()}) # subplot 축 없애기

for ax, orig, rec_nmf, rec_pca, rec_kmeans in zip(

        axes.T, x_test, x_reconst_nmf, x_reconst_pca, x_reconst_kmeans):

    ax[0].imshow(orig.reshape(image_shape))

    ax[1].imshow(rec_nmf.reshape(image_shape))

    ax[2].imshow(rec_pca.reshape(image_shape))

    ax[3].imshow(rec_kmeans.reshape(image_shape))


for i, name in zip(range(4), ['원본', 'kmeans', 'pca', 'nmf']):

    axes[i, 0].set_ylabel(name)

plt.gray()

plt.show()

성분(또는 클러스터 중심) 100개를 사용한 NMF, PCA, k-평균의 이미지 재구성


k-평균을 사용한 벡터 양자화는 데이터의 차원보다 더 많은 클러스터를 사용해 데이터를 인코딩할 수 있음



2. 특성의 수보다 많은 클러스터중심을 사용한 k-평균


# library import

from sklearn.datasets import make_moons


# dataset생성

x, y = make_moons(n_samples=200, noise=0.05, random_state=0) # noise: 퍼짐정도


# 모델 생성 및 학습, 예측

kmeans = KMeans(n_clusters=10, random_state=0).fit(x)

y_pred = kmeans.predict(x)


plt.scatter(x[:, 0], x[:, 1], c=y_pred, s=60, cmap='Paired', edgecolors='black', alpha=0.7) # cmap: palette

plt.scatter(kmeans.cluster_centers_[:, 0], kmeans.cluster_centers_[:, 1], c=np.unique(kmeans.labels_), # c: group

            s=100, marker='s', linewidths=2, cmap='Paired', edgecolors='black')

plt.xlabel('특성 0')

plt.ylabel('특성 1')

plt.show()

복잡한 형태의 데이터셋을 다루기 위해 많은 클러스터를 사용한 k-평균


10개의 클러스터를 사용했기 때문에 10개로 구분

해당 클러스터에 해당하는 특성을 1, 해당하지않는 특성을 0으로 구분


큰 규모의 데이터셋을 처리할 수 있는 scikit-learn은 MiniBatchKMenas도 제공


k-평균의 단점은 클러스터의 모양을 가정(원)하고 있어서 활용범위가 비교적 제한

찾으려하는 클러스터의 갯수를 지정해야함

#!/usr/bin/env python3


k-평균 군집 알고리즘


0. 살펴보기

데이터셋을 클러스터cluster라는 그룹으로 나누는 작업

다른 클러스터의 데이터 포인트와는 구분되도록 데이터를 나누는 것이 목표


k-평균k-mean 군집은 가장 간단하고 널리 사용됨

이 알고리즘은 데이터의 어떤 영역을 대표하는 클러스터 중심cluster center를 찾음

1. 데이터 포인트를 가장 가까운 클러스터 영역에 할당

2. 클러스터에 할당된 데이터 포인트의 평균으로 클러스터 중심을 다시 지정

3. 클러스터에 할당되는 데이터 포인트에 변화가 없을 때 알고리즘 종료


# library import

import mglearn

import matplotlib

import matplotlib.pyplot as plt


# matplotlib 설정

matplotlib.rc('font', family='AppleGothic'# 한글 출력

plt.rcParams['axes.unicode_minus'] = False #  축 -


mglearn.plots.plot_kmeans_algorithm()

plt.show()

입력 데이터와 k-평균 군집 알고리즘이 세번 진행되기가지의 과정


# 경계선 그리기

mglearn.plots.plot_kmeans_boundaries()

plt.show() 

k-평균 알고리즘으로 찾은 클러스터 중심과 클러스터 경계




1. make_blobs(인위적 데이터셋)에 k-평균 알고리즘을 적용

# library import

from sklearn.datasets import make_blobs

from sklearn.cluster import KMeans

import numpy as np


# 데이터 만들기

x, y = make_blobs(random_state=1)


# 모델 생성 및 학습

kmean = KMeans(n_clusters=3) # 클러스터 수

kmean.fit(x)


print('클러스터 레이블: \n{}'.format(kmean.labels_)) 

3개의 클러스터를 지정했으므로 각 클러스터는 0 ~ 2까지 번호가 붙음


### predict메소드를 통해 새로운 데이터 클러스터 레이블을 예측할 수 있음

### 예측은 각 포인트에 가장 가까운 클러스터 영역으로 할당하는 것이며 기존 모델을 변경하지 않음

### 훈련 세트에 대해 predict 메소드를 실행하면 labels_와 같은 결과를 얻게 됨


print('예측 레이블: \n{}'.format(kmean.predict(x)))


### 클러스터는 각 데이터 포인트가 레이블을 가진다는 면에서 분류와 비슷함

### 그러나 정답을 모르고 있으며 레이블 자체에 의미가 없음

### 알고리즘이 우리에게 주는 정보는 각 레이블에 속한 데이터는 서로 비슷하다는 것





2. make_blobs 데이터로 그래프 그리기

mglearn.discrete_scatter(x[:, 0], x[:, 1], kmean.labels_, markers= 'o') # x, y, group, marker

mglearn.discrete_scatter(

    kmean.cluster_centers_[:, 0], kmean.cluster_centers_[:, 1], y=np.unique(kmean.labels_),# x, y, group

    markers='^', markeredgewidth=2) # marker, 두께

plt.show()

k-평균 알고리즘으로 찾은 세 개의 클러스터 중심과 클러스터 할당




3. 클러스터수를 변경하여 비교

fig, axes = plt.subplots(2, 2, gridspec_kw={'hspace':0.5}) # gridspec_kw: 그래프 수평간격

n_clusters_set = [2, 4, 5, 6] 

for n, ax in zip(n_clusters_set, axes.ravel()):

    kmeans = KMeans(n_clusters=n) # 클러스터 갯수

    kmeans.fit(x)

    mglearn.discrete_scatter(x[:, 0], x[:, 1], y=kmeans.labels_, ax=ax) # x, y, group, ax = plot객체

    mglearn.discrete_scatter(

        kmeans.cluster_centers_[:, 0], kmeans.cluster_centers_[:, 1], y=np.unique(kmeans.labels_), # x, y, group

        markeredgewidth=3, ax=ax, s=15) # 두께, 그림객체, 사이즈

    ax.set_title('n_cluster: {}'.format(n)) # title

    ax.set_xlabel('feature 0')

    ax.set_ylabel('feature 1')

plt.show()

k-평균 알고리즘으로 클러스터를 여러개 사용했을 때의 중심과 클러스터 할당




4. k-평균 알고리즘이 실패하는 경우

데이터셋의 클러스터 갯수를 알고있더라도 k-평균 알고리즘이 항상 이를 구분해낼 수 있는 것은 아님

각 클러스터를 정의하는 것이 중심 하나뿐이므로 클러스터는 둥근 형태로 나타남

이런 이유로 k-평균 알고리즘은 비교적 간단한 형태를 구분할 수 있음

또한 k-평균은 모든 클러스터의 반경이 똑같다고 가정 ==> 클러스터 중심 사이에 정확히 중간에 경계를 그림


4-1 클러스터의 밀도가 다를 때

x1, y1 = make_blobs(n_samples=200, cluster_std=[1, 2.5, 0.5], random_state=170) # 샘플수, 밀도, 랜덤상태

y_pred = KMeans(n_clusters=3, random_state=0).fit_predict(x1) # 모델 생성 및 훈련 후 예측


mglearn.discrete_scatter(x1[:, 0], x1[:, 1], y=y_pred) # x, y, group

plt.legend(['cluster 0', 'cluster 1', 'cluster 2']) # 범례

plt.xlabel('feature 0')

plt.ylabel('feature 1')

plt.show()

클러스터의 밀도가 다를 때 k-평균으로 찾은 클러스터 할당


4-2 클러스터가 원형이 아닐  때

x2, y2 = make_blobs(n_samples=600, random_state=170) # 샘플 갯수, 램덤상태

rng = np.random.RandomState(74).normal(size=(2,2)) # 랜덤상태, (2,2)size로 normal분포의 숫자 무작위추출

x2_reshape = np.dot(x2, rng) # np.dot 행렬곱


kmeans = KMeans(n_clusters=3).fit(x2_reshape) # 모델 생성

y2_pred = kmeans.predict(x2_reshape) # 예측


mglearn.discrete_scatter(x2_reshape[:, 0], x2_reshape[:, 1], y=kmeans.labels_, alpha=0.7) # x, y , group, 투명도

mglearn.discrete_scatter(

    kmeans.cluster_centers_[:,0], kmeans.cluster_centers_[:,1], y=np.unique(kmeans.labels_), # x, y, group

    markeredgewidth=3, s=15) # 두께, size

plt.xlabel('feature 0')

plt.ylabel('feature 1')

plt.show()

원형이 아닌 클러스터를 구분하지 못하는 k-평균 알고리즘


4-3 클러스터의 구조가 복합형태를 나타낼 때

from sklearn.datasets import make_moons


x3, y3 = make_moons(n_samples=200, noise=0.05, random_state=0) # 갯수, 퍼짐정도, 랜덤상태

kmeans = KMeans(n_clusters=2).fit(x3) # 모델 객체 생성 및 학습

y3_pred = kmeans.predict(x3) # 모델 적용


plt.scatter(x3[:, 0], x3[:, 1], c=y3_pred, s=60, edgecolors='black', cmap=mglearn.cm2) # x, y, group, 점크기 60%, 테두리색 black, palette

plt.scatter(kmeans.cluster_centers_[:, 0], kmeans.cluster_centers_[:, 1], # x, y

            marker='^', s=120, linewidth=2, edgecolors='k', c=[mglearn.cm2(0), mglearn.cm2(1)]) # marker,  점크기 120%, 두께, 테두리색, 색상 

plt.xlabel('feature 0')

plt.ylabel('feature 1')

plt.show()

반달 모양의 클러스터를 구분하지 못하는 k-평균 알고리즘


k-평균 알고리즘이 2개의 반달모양을 구분하지 못하고 원형으로 데이터를 비교






#!/usr/bin/env python3


t-SNE를 이용한 매니폴드 학습


데이터를 산점도로 시각화할 수 있다는 이점 때문에 PCA가 데이터 변환에 가장 먼저 시도해볼 만한 방법이지만,

LFW(Labeled Faces in the Wild) 데이터셋의 산점도에서 본 것처럼 알고리즘의(회전하고 방향을 제거하는) 유용성이 떨어짐

maniford learning매니폴드 학습 알고리즘이라고 하는 시각화 알고리즘들은 훨씬 복잡한 매핑을 만들어 더 나은 시각화를 제공

t-SNE(t-Distributed Stochastic Neighbor Embedding) 알고리즘을 아주 많이 사용함


maniford learnning 알고리즘은 그 모적이 시각화라 3개이상의 특성을 뽑는 경우는 거의 없음

t-SNE를 포함해서 일부 maniford learnning 알고리즘들은 훈련 데이터를 새로운 표현으로 변환시키지만 새로운 데이터에는 변활할 수 없음

즉 테스트세트에는 적용할 수 없고, 단지 훈련데이터에만 변환할 수 있음


따라서 maniford learnning은 탐색적 데이터 분석에 유용하지만 지도학습용으로는 거의 사용하지 않음


t-SNE의 아이디어는 데이터 포인트 사이의 거리를 가장 잘 보존하는 2차원 표현을 찾는 것

먼저 t-SNE는 각 데이터 포인트를 2차원에 무작위로 표현한 후 원본 특성 공간에서 가까운 포인트는 가깝게, 멀리 떨어진 포인트는 멀어지게 만듬

t-SNE는 가까이 있는 포인트에 더 많은 비중을 둠 ==> 이웃 데이터 포인트에 대한 정보를 보존하려함



1. 손글씨 숫자 데이터셋에 t-SNE maniford learnning을 적용

# load library

from sklearn.datasets import load_digits

import matplotlib

import matplotlib.pyplot as plt


# matplotlib 설정

matplotlib.rc('font', family='AppleGothic'# 한글 출력

plt.rcParams['axes.unicode_minus'] = False # 축 - 설정


# data load

digits = load_digits()


# subplot 객체 생성

fig, axes = plt.subplots(2, 5, #  subplot객체(2x5)를 axes에 할당

                         subplot_kw={'xticks':(), 'yticks':()}) # subplot 축 눈금 해제


for ax, img in zip(axes.ravel(), digits.images): # axes.ravel()과 digits.images를 하나씩 할당

    ax.imshow(img)

plt.gray() # 그래프 흑백

plt.show() # 그래프 출력

숫자 데이터셋의 샘플 이미지



2. PCA를 사용하여 데이터를 2차원으로 축소해 시각화

### 처음 두 개의 주성분을 이용해 그래프를 그리고 각 샘플을 해당하는 클래스의 숫자로


from sklearn.decomposition import PCA


# PCA 모델을 생성

pca = PCA(n_components=2) # 주성분 갯수

pca.fit(digits.data) # PCA 적용


# 처음 두 개의 주성분으로 숫자 데이터를 변환

digits_pca = pca.transform(digits.data) # PCA를 데이터에 적용

colors = ['#476A2A', '#7851B8', '#BD3430', '#4A2D4E', '#875525',

               '#A83683', '#4E655E', '#853541', '#3A3120', '#535D8E']


for i in range(len(digits.data)): # digits.data의 길이까지 정수 갯수

    # 숫자 텍스트를 이용해 산점도 그리기

    plt.text(digits_pca[i, 0], digits_pca[i, 1], str(digits.target[i]), # x, y, 그룹; str은 문자로 변환

             color=colors[digits.target[i]], # 산점도 색상

             fontdict={'weight':'bold', 'size':9}) # font 설정

plt.xlim(digits_pca[:, 0].min(), digits_pca[:,1].max()) # 최소, 최대

plt.ylim(digits_pca[:, 1].min(), digits_pca[:,1].max()) # 최소, 최대

plt.xlabel('first principle component'# x 축 이름

plt.ylabel('second principle componet'# y 축 이름

plt.show()

처음 두 개의 주성분을 사용한 숫자 데이터셋의 산점도


각 클래스가 어디 있는지 보기 위해 실제 숫자를 사용해 산점도를 출력

숫자 0, 6, 4는 두 개의 주성분만으로 잘 분리 됨

다른 숫자들은 대부분 많은 부분이 겹쳐 있음


3. 같은 데이터셋에 t-SNE를 적용해 결과를 비교

### TSNE모델에는 transform 메소드가 없고 fit_transform만 있음


# library import

from sklearn.manifold import TSNE


# t-SNE 모델 생성 및 학습

tsne = TSNE(random_state=0)

digits_tsne = tsne.fit_transform(digits.data)


# 시각화

for i in range(len(digits.data)): # 0부터  digits.data까지 정수

    plt.text(digits_tsne[i, 0], digits_tsne[i, 1], str(digits.target[i]), # x, y , 그룹

             color=colors[digits.target[i]], # 색상

             fontdict={'weight': 'bold', 'size':9}) # font

plt.xlim(digits_tsne[:, 0].min(), digits_tsne[:, 0].max()) # 최소, 최대

plt.ylim(digits_tsne[:, 1].min(), digits_tsne[:, 1].max()) # 최소, 최대

plt.xlabel('t-SNE 특성0'# x축 이름

plt.ylabel('t-SNE 특성1'# y축 이름

plt.show() # 그래프 출력

t-SNE로 찾은 두 개의 성분을 사용한 숫자 데이터셋의 산점도


모든 클래스가 확실히 잘 구분 되었음

이 알고리즘은 클래스 레이블 정보를 사용하지 않으므로 완전한 비지도 학습

그럼에도 원본 데이터 공간에서 포인트들이 얼마나 가가이 있는지에 대한 정보로 클래스가 잘 구분되는 2차원 표현을 찾음


t-SNE의 매개변수perplexity, early_exaggeration


perplexity: 값이 크면 더 많은 이웃을 포함하여 작은 그룹은 무시

                       기본값 = 30

                       보통 5~50사이의 값을 가짐


early_exaggeration: 원본 공간의 클러스터들이 얼마나 멀게 2차원에 나타낼지를 정함, 기본값은 4

                                         최소한 1보다 커야하고 값이 클수록 간격이 커짐

#!/usr/bin/env python3


얼굴이미지에 NMF 적용하기


NMF의 핵심 매개변수는 추출할 성분의 갯수

보통 이 값은 특성의 갯수보다 작음(픽셀하나가 두개의 성분으로 나뉘어 표현될 수 있음)


1. NMF를 사용하여 데이터를 재구성하는데 성분의 갯수가 어떤 영향을 주는지 살펴보기

# library import

from sklearn.datasets import fetch_lfw_people

import matplotlib

import matplotlib.pyplot as plt

import numpy as np

import mglearn


# matplotlib 설정

matplotlib.rc('font', family='AppleGothic'# 한글 설정

plt.rcParams['axes.unicode_minus'] = False # 축 -설정


people = fetch_lfw_people(min_faces_per_person=20, resize=0.7, color=False) # 겹치지 않는 최소사람 수, 사이즈비율, 흑백

images_shape = people.images[0].shape # 이미지 한개의 사이즈 저장


counts = np.bincount(people.target) # 갯수 새기

for i, (count, names) in enumerate(zip(counts, people.target_names)): # counts와 people.target_names에서 원소 하나씩 할당하여 인덱스 부여

    print('{0:25} {1:3}'.format(names, count), end='\t'# names에서 25자리, count에서 3자리, 끝에 탭할당

    if i % 3 == 0: # 3의 배수

        print() # 개행


idx = np.zeros(people.target.shape, dtype=np.bool) # False배열 3023개 설정

people_target_unq = np.unique(people.target) # target에서 unique값 할당

for target in people_target_unq: # perploe_target_unq에서 원소 하나씩 target에 할당

    idx[np.where(people.target == target)[0][:50]] = 1 # 전체 리스트 3023개에서 target과 일치하는 것의 수가를 50개 까지 True로 변경

x_people = people.data[idx] # 훈련 데이터

y_people = people.target[idx] # 테스트 데이터


# library import

from sklearn.preprocessing import MinMaxScaler

from sklearn.model_selection import train_test_split


scaler = MinMaxScaler()

x_people_scaled = scaler.fit_transform(x_people) # 훈련 데이터를 적용하여 전처리


x_train, x_test, y_train, y_test = \ 

  train_test_split(x_people_scaled, y_people, # 분할할 데이터

                   stratify=y_people, random_state=0, test_size=0.3) # 그룹화할데이터, 랜덤상태, 테스트 비율


mglearn.plots.plot_nmf_faces(x_train, x_test, images_shape)

plt.gray() # 그림 흑백

plt.show() # 그림 출력

NMF 성분 갯수에 따른 얼굴 이미지의 재구성


변환을 되돌린 결과는 PCA를 사용했을 때와 비슷하지만 품질이 떨어짐

PCA가 재구성 측면에서 최선의 방향을 찾기 때문

NMF는 데이터에 있는 유용한 패턴을 찾는데 활용


2. 비음수 성분 15개로 시각화

# library import , model 생성 , 학습

from sklearn.decomposition import NMF

nmf = NMF(n_components=15, random_state=0)

nmf.fit(x_train)


# 모델 적용

x_train_nmf = nmf.transform(x_train)

x_test_nmf = nmf.transform(x_test)


# 시각화

fig, axes = plt.subplots(3, 5, # axes에 plots객체를 3x5 만큼 할당

                         subplot_kw={'xticks':(), 'yticks':()}) # subplot 축 없애기


for i, (comp, ax) in enumerate(zip(nmf.components_, axes.ravel())): # nmf.components_, axes.ravel()에서 하나씩 comp와 ax에 인덱스부여

    ax.imshow(comp.reshape(images_shape)) # images_shape= (87, 65)

    ax.set_title('성분 {}'.format(i)) # subplot 타이틀

plt.gray() # 그림 흑백

plt.show()

얼굴 데이터셋에서 NMF로 찾은 성분 15개


이 성분들은 모두 양수 값이어서 PCA 성분보다 훨씬 더 얼굴 원형처럼 보임


비음수 성분이 특별히 강하게 나타난 이미지 분석(1)

### argsort : 자료를 정렬한 것의 index


idx = np.argsort(x_train_nmf[:, 3])[::-1] # [start : end : stpe] , step=-1 :내림차순

fig, axes = plt.subplots(2, 5, # subplots객체 (2x5) 를 axes에 할당

                         subplot_kw={'xticks':(), 'yticks':()}) # subplots에 축 없애기


for idx, ax in zip(idx, axes.ravel()): # idx와 aexs.ravel()을 하나씩 idx, ax에 할당

    ax.imshow(x_train[idx].reshape(images_shape))

plt.gray() # 그림 흑백

plt.show() # 그림 출력

성분 3의 계수가 큰 얼굴들 10개


비음수 성분이 특별히 강하게 나타난 이미지 분석(2)

idx = np.argsort(x_train_nmf[:, 10])[::-1] # [start : end : stpe] , step=-1 :내림차순

fig, axes = plt.subplots(2, 5, # subplots객체 (2x5) 를 axes에 할당

                         subplot_kw={'xticks':(), 'yticks':()}) # subplots에 축 없애기


for idx, ax in zip(idx, axes.ravel()): # idx와 aexs.ravel()을 하나씩 idx, ax에 할당

    ax.imshow(x_train[idx].reshape(images_shape)) 

plt.gray() # 그림 흑백

plt.show() # 그림 출력   

성분 10의 계수가 큰 얼굴들 10개


성분 3의 계수가 큰 얼굴은 왼쪽으로 돌아가 있고 성분 10의 계수가 큰 얼굴을 왼쪽으로 돌아가 있음을 확인

이런 패턴으로 데이터를 추출




#!/usr/bin/env python3


NMF(비음수 행렬 분해)


NMF(non-negative matrix factorization)는 유용한 특성을 뽑아내기 위한 또 다른 비지도 학습 알고리즘

NMF에서는 음수가 아닌 성분과 계수 값을 찾음.

즉 주성분과 계수가 모두 0보다 크거나 같아야 함 ==> 이 방식은 음수가 아닌 특성을 가진 데이터에만 적용


음수가 아닌 가중치 합으로 데이터를 분해하는 기능오디오 트랙이나 음악처럼 독립된 소스를 추가하여 만들어진 데이터에 특히 유용. 

이럴 때 NMF는 섞여 있는 데이터에서 원본 성분을 구분할 수 있음

음수로 된 성분이나 계수가 만드는 상쇄 효과를 이해하기 어려운 PCA보다 대체로 NMF의 주성분이 해석하기는 쉬움


인위적 데이터에 NMF 적용하기

NMF로 데이터를 다루려면 주어진 데이터가 양수인지 확인해야함

즉 원점 (0, 0)에서 상대적으로 어디에 놓여 있는지(위치 벡터)가 NMF에서는 중요함

그렇기 대문에 원점 (0, 0)에서 데이터로 가는 방향을 추출한 것으로 음수 미포함 성분을 이해가능


# library import

import mglearn

import matplotlib

import matplotlib.pyplot as plt


# matplotlib 설정

plt.rcParams['axes.unicode_minus'] = False # 축- 설정

matplotlib.rc('font', family='AppleGothic'# 한글출력


# 예제 데이터

mglearn.plots.plot_nmf_illustration()

plt.show() # 그래프 출력

NMF로 찾은 성분이 2개일때(왼쪽)와 1개일 때(오른쪽)


왼쪽은 성분이 둘인 NMF로, 데이터셋의 모든 포인트를 양수로 이뤄진 두 개의 성분으로 표현할 수 있음.

데이터를 완벽하게 재구성할 수 있을 만큼 성분이 아주 많다면(특성의 수가 많다면) 알고리즘은 데이터의 각 특성의 끝에 위치한 포인트를 가리키는 방향(위치벡터)을 선택


하나의 성분만을 사용한다면 NMF는 데이터를 가장 잘 표현할 수 있는 평균으로 향하는 성분을 만듬

PCA와는 반대로 성분 개수를 줄이면 특정 방향이 제거되고 전체성분이 완전 바뀜

NMF에서 성분은 특정 방식으로 정렬되어 있지도 않아서 첫번째, 두번째 같은 것이 없고 모든 성분은 동등하게 취급


NMF는 무작위로 초기화하기 때문에 난수 생성 초기값에 따라 결과가 달라짐 두 개의 성분으로 모든 데이터를 완벽하게 나타낼 수 있는 간단한 데이터에서는 난수가 거의 영향을 주지 않음(성분의 크기나 순서가 바뀔 수는 있음)


인위적인 데이터셋을 사용한 NMF분석

# library import

import mglearn

import matplotlib

import matplotlib.pyplot as plt


# matplotlib 설정

matplotlib.rc('font', family='AppleGothic'# 한글 출력

plt.rcParams['axes.unicode_minus'] = False # 축 - 설정


# data load

S = mglearn.datasets.make_signals() 


# visualization

plt.plot(S) # 라인그래프

plt.xlabel('time'# x 축

plt.ylabel('signal'# y 축

plt.show() # 그래프 출력

원본신호


3개가 섞인 신호만 관찰할 수 있는 상황

합쳐진 신호를 분해해서 원본 신호를 복원


NMF와 PCA를 통한 데이터 복원

# library import

from sklearn.decomposition import NMF 

from sklearn.decomposition import PCA

import numpy as np

A = np.random.RandomState(0).uniform(size=(100, 3)) # 100x3  형태의 균일분포에서 랜덤 데이터 생성

X = np.dot(S, A.T) # S(2000x3)에 A에 전치행렬(3X100)을 행렬 곱


print('X.shape \n측정데이터 형태:{}'.format(X.shape)) # (2000, 3)


nmf = NMF(n_components=3, random_state=0) # 주성분 3개, 랜덤상태

S_ = nmf.fit_transform(X) # nmf적용

print('S_.shape \n복원한 신호 데이터 형태: {}'.format(S_.shape)) # (2000, 3)


pca = PCA(n_components=3) # PCA주성분 3

H = pca.fit_transform(X) # PCA적용


# visualization

models = [X, S, S_, H] 

names = ['측정 신호(처음 3개)', '원본 신호', 'NMF로 복원' ,'PCA로 복원']


fig, axes = plt.subplots(4, # subplot의 객체4개를 axes에 할당

                         gridspec_kw={'hspace':0.5}, # 수평간격 비율

                         subplot_kw={'xticks':(), 'yticks':()}) # subplot의 축 눈금 없애기


for model, name, ax in zip(models, names, axes.ravel()): # models와 names, axes.ravel()에서 하나씩 선택

    ax.set_title(name) # 타이틀 이름

    ax.plot(model[:,:3]) # 모든행, :3 ==> 3개의 열

plt.show() # 그래프 출력

NMF와 PCA를 사용해 복원한 신호


NMF는 원본신호를 잘 복원했지만 PCA는 실패했고 데이터 변동의 대부분을 첫번째 성분을 사용해 나타냄

NMF로 생성한 성분은 순서가 없음을 알 수 있음

NMF 성분의 선수가 원본신호와 같지만 우연의 일치


PCA나 NMF처럼 데이터 포인트를 일정 개수의 성분을 사용해 가중치 합으로 분해하는 알고리즘은 다수 있음

ICA(독립 성분분석), FA(요인 분석), sparse coding(희소 분석)등이 있음 

    










#!/usr/bin/env python3


1. PCA -- eigen face


PCA는 원본 데이터 표현보다 분석하기에 더 적합한 표현을 찾을 수 있으리란 생각에서 출발

이미지는 RGB의 강도가 기록된 픽셀로 구성

PCA를 이용하여 LFW(Labeled Faces in the Wild) 데이터셋의 이미지에서 특성을 추출


# library import

from sklearn.datasets import fetch_lfw_people # fetch_lfw_people library

import matplotlib.pyplot as plt

import matplotlib

import numpy as np


# matplotlib 설정

matplotlib.rc('font', family='AppleGothic'# 한글출력

plt.rcParams['axes.unicode_minus'] = False # 축 - 표시


people = fetch_lfw_people(min_faces_per_person=20, resize=0.7, color=False# 추출된 사진의 서로 다른 얼굴의 수, 사진 비율, 흑백여부

image_shape = people.images[0].shape # people객체 image의 첫번째 원소의 모양

print('people.images의 형태 \n{}'.format(people.images.shape)) # people객체의 image 형태: (3023, 87, 65)

print('class 갯수 \n{}'.format(len(people.target_names))) # people객체의 class 갯수, 62


fig, axes = plt.subplots(2, 5, # figure객체와 2x5의 plot객체를 각각 할당

                         subplot_kw={'xticks': (), 'yticks': ()}) # subplot의 축 설정; x축 눈굼없게, y축 눈금없게


# axes.ravel() : 리스트를 1차원으로

for target, image, ax in zip(people.target, people.images, axes.ravel()): # people.target, people.images, axes.ravel()에서 하나씩 원소 할당

    ax.imshow(image) # imshow로 그림 출력

    ax.set_title(people.target_names[target]) # 각 subplot의 타이틀

plt.gray() # 그림 흑백

plt.show() # 그래프 출력

LFW 데이터셋에 있는 이미지의 샘플



1 -1 people 객체 분석

# 각 target이 나타난 횟수 계산

counts = np.bincount(people.target) # people.target의 빈도 계산


# target별 이름과 횟수 출력

### enumerate() : 각 원소에 인덱스 부여

### print('{0:25}, {1:3}').format(var1, var2):  첫번째 {}에 var1과 25자리까지, 두번째{}에 var2와 3자리를 부여

### print({0!s}.format(var1)): print문에 전달된 형식이 숫자라도 문자로 처리

print('frequency')

for i, (count, name) in enumerate(zip(counts, people.target_names)):

    print('{0:25} {1:3}'.format(name, count), end= '\t'# name에서 25자리, count에서 3자리, 끝은 탭

    if (i + 1) % 3 == 0: # 3의 배수이면, 즉 4번째 원소부터

        print() # 개행처리


# Geoge W Bush와 Colin Powell의 이미지가 많음을 확인


### 데이터셋의 편중을 막기 위해 50개의 이미지만 선택

mask = np.zeros(people.target.shape, dtype=np.bool) # 3023개의 boolean타입 False 생성


people_unique = np.unique(people.target) # 중복된 값 제외

for target in people_unique: # 중복을 제거한 target리스트에서 한개의 원소 선택

# people.target(3023개의 리스트)중 선택된 원소와 같은 것만 출력 ==> [0] 리스트의 원소로 접근 ==> False의 갯수 50개까지 출력

# 이 논리 값을 mask의 인덱스로 사용 후 True로 변환

    mask[np.where(people.target == target)[0][:50]] = 1 

x_people = people.data[mask] # 훈련 데이터 생성

y_people = people.target[mask] # 테스트 데이터 생성


# 전처리 메소드 import

from sklearn.preprocessing import MinMaxScaler


scaler = MinMaxScaler()

x_people_scaled = scaler.fit_transform(x_people) # 전처리 메소드 적용


# 머신 러닝 library import

from sklearn.neighbors import KNeighborsClassifier

from sklearn.model_selection import train_test_split


# 전처리한 데이터를 분할

x_train, x_test, y_train, y_test = \

  train_test_split(x_people_scaled, y_people, # 분할할 데이터

                   stratify=y_people, random_state=0) # 그룹화할 데이터, 랜덤상태


# 머신 러닝 라이브러리 import

knn = KNeighborsClassifier(n_neighbors=1) # 이웃의 수

knn.fit(x_train, y_train) # 모델 학습


print('knn.score(x_test, y_test) \n최근접이웃의 일반화 세트 점수: {:.3f}'.format(

    knn.score(x_test, y_test))) # 0.248

정확도가 24.8%, 무작위로 분류하는 정확도는 1/62 = 1.6%


얼굴의 유사도를 측정하기 위해 원본 픽셀 공간에서 거리를 계산하는 것은 비효율적

각 픽셀의 회색톤 값을 다른 이미지에서 동일한 위치에 있는 픽셀 값과 비교. 이런 방식은 사람이 얼굴 이미지를 인식하는 것과는 많이 다르고, 픽셀을 있는 그대로 비교하는 방식으로는 얼굴의 특징을 잡아내기 어려움


2. PCA whitening 분석


픽셀을 비교할 때 얼굴 위치가 한 픽셀만 오른쪽으로 이동해도 큰 차이를 만들어 완전히 다른얼굴로 인식

주성분으로 변환하여 거리를 계산하여 정확도를 높이는 방법을 사용, PCA whitening 옵션을 사용하여 주성분의 스케일이 같아지도록 조정 ==> whitening 옵션 없이 변환 후 StandardScaler를 적용하는 것과 같음


# library import

import mglearn

mglearn.plots.plot_pca_whitening()

plt.show()

whitening옵션을 사용한 PCA 데이터 변환


데이터가 회전하는 것뿐만 아니라 스케일도 조정되어 그래프가 원 모양으로 바뀜


2-1. PCA  머신 러닝

# library import

from sklearn.decomposition import PCA


# PCA 모델 생성 및 적용

pca = PCA(n_components=100, whiten=True, random_state=0) # 주성분 갯수, whitening option, 랜덤상태

pca.fit(x_train) # PCA 학습

x_train_pca = pca.transform(x_train) # PCA를 데이터에 적용

x_test_pca = pca.transform(x_test)


# PCA를 적용한 데이터 형태

print('x_train_pca.shape \ntrain형태:{}'.format(x_train_pca.shape)) # (1547, 100)

print('x_test_pca.shape \ntest형태:{}'.format(x_test_pca.shape)) # (516, 100)


# 머신 러닝 모델 생성 및 학습

knn = KNeighborsClassifier(n_neighbors=1) # 이웃의 수

knn.fit(x_train_pca, y_train) # 모델 학습

print('테스트 세트 정확도: \n{:.3f}'.format(knn.score(x_test_pca, y_test))) # 0.314


모델의 정확도가 23%에서 31%로 상승


이미지 데이터일 경우엔 계산한 주성분을 쉽게 시각화 가능

주성분이 나타내는 것은 입력 데이터 공간에서의 어떤 방향임을 기억

입력 차원이 87 x 65픽셀의 흑백 이미지이고 따라서 주성분또한 87 x 65


print('pca.components_.shape \n{}'.format(pca.components_.shape)) # (100, 5655)


fig, axes = plt.subplots(3, 5, # subplot 3x5를 axes에 할당

                         subplot_kw={'xticks': (), 'yticks': ()}) # subplot 축 설정


for i, (comp, ax) in enumerate(zip(pca.components_, axes.ravel())): # pca.components_와 axes.ravel()을 하나씩 순서대로 할당한 후 인덱스 부여

    ax.imshow(comp.reshape(image_shape)) # image_shape= (87, 65)

    ax.set_title('pricipal component {}'.format(i+1)) # image title

plt.gray() # 사진 흑백

plt.show() # 사진 출력

얼굴 데이터셋의 pricipal component중 처음 15개


첫 번째 주성분은 얼굴과 배경의 명암차이를 기록한 것으로 추정

두번째 주성분은 오른쪽과 왼쪽 조명의 차이를 담고 있는 것으로 보임

이런 방식이 원본 픽셀 값을 사용하는 것보다 더 의미 있지만, 사람이 얼굴을 인식하는 방식과는 거리가 멈

알고리즘이 데이터를 해석하는 방식은 사람의 방식과는 상당히 다름


PCA 모델을 이해하기 위해 몇개의 주성분을 사용해 원본 데이터를 재구성

몇 개의 주성분으로 데이터를 줄이고 원래 공간으로 되돌릴 수 있음

원래 특성 공간으로 되돌리는 작업은 inverse_transform 메소드를 사용


mglearn.plots.plot_pca_faces(x_train, x_test, image_shape) # 훈련데이터, 테스트데이터, 이미지크기(87x65)

plt.gray() # 그림 흑백

plt.show() # 그림 출력

주성분 갯수에 따른 세 얼굴 이미지의 재구성



'python 머신러닝 -- 비지도학습 > PCA(주성분 분석)' 카테고리의 다른 글

PCA  (0) 2018.03.19

#!/usr/bin/env python3


PCA


PCA주성분 분석은 특성들이 통계적으로 상관관계가 없도록 데이터를 회전시키는 것입니다.

회전한 뒤에 데이터를 설명하는 데 얼마나 중요하냐에 따라 새로운 특성 중 일부만 선택합니다.


다음은 PCA분석의 algorithm입니다.

import mglearn

import matplotlib

import matplotlib.pyplot as plt


matplotlib.rc('font', family='AppleGothic') # 한글출력

plt.rcParams['axes.unicode_minus'] = False # 축 - 표시


mglearn.plots.plot_pca_illustration()

plt.show()

PCA를 이용한 데이터 변환

왼쪽 위 그래프는 원본 데이터 포인트를 색으로 구분해 표시, 이 알고리즘은 먼저 'component1'이라고 쓰여 있는, Variant(분산)이 가장 큰 방향을 찾습니다. 이 방향(벡터)가 가장 많은 정보를 담고 있는 방향이고 ==> 특성들의 상관관계가 가장 큰 방향입니다.


그 다음에 이 algorithm은 첫 번째 방향과 직각인 방향 중에서 가장 많은 정보를 담은 방향을 찾습니다.

2차원에서는 직각 방향은 하나지만 3차원이상부터는 무수히 많은 직각 방향이 존재하고, 이 그래프에서 화살표 머리방향은 의미가 없습니다.


이런 방법으로 찾은 방향이 데이터의 주된 Variant(분산)의 방향이라해서 principal component(주성분)이라합니다.

일반적으로 원본 특성 개수만큼의 주성분 존재합니다.


오른쪽 위 그래프는 주성분1과 2를 각각 x축과 y축에 나란하도록 회전시켰습니다.

회전하기전에 데이터에서 평균을 빼서 중심을 원점에 맞춥니다.


PCA에 의해 회전된 두 축은 독립이므로 변환된 데이터의 correlation matirx(상관관계 행렬)이 대각선 방향(자기자신)을 제외하고는 0이 나옵니다.


PCA는 주성분의 일부만 남기는 차원 축소 용도로 사용할 수 있습니다. 왼쪽 아래 그림은 첫 번재 주성분만 유지하려고하며 2차원 데이터셋이 1차원 데이터셋으로 감소하지만 단순히 원본특성 중 하나만 남기는 것이 아니라, 첫번째 방향의 성분을 유지하도록 데이터를 가공합니다.


마지막으로 데이터에 다시 평균을 더해서 반대로 회전(오른쪽 아래 그림)

이 데이터들은 원래 특성 공간에 놓여 있지만 첫 번째 주성분의 정보만 담고 있음


이 변환은 데이터에서 노이즈를 제거하거나 주성분에서 유지되는 정보를 시각화하는데 사용됩니다.



PCA이 가장 널리 사용되는 분야는 고차원 데이터셋의 시각화영역입니다.

breast cancer와 같은 데이터셋은 특성이 30개나 있어서 30개중 2개를 택하는 경우의 수인 435개의 산점도를 그려야하므로 단순한 시각화가 비효율적입니다.


그러나 'malignant' 'benign' 두 클래스에 대해 각 특성의 히스토그램을 그리면 보다 쉽게 해석 가능

from sklearn.datasets import load_breast_cancer

import numpy as np


cancer = load_breast_cancer()


# 특성이 30개이므로  5X2 3set의 plot 객체 생성합니다.

# set 1

fig, axes = plt.subplots(5, 2)

malignant = cancer.data[cancer.target == 0]

benign = cancer.data[cancer.target == 1]

target_set = np.array([malignant, benign])


for i, ax in zip(np.arange(10), axes.ravel()):

    for t in target_set:

        _, bins = np.histogram(cancer.data[:, i], bins=50) # bins: histogram 간격

        ax.hist(t[:, i], bins=bins, alpha=0.5)

        ax.set_title(cancer.feature_names[i])

        ax.set_yticks(()) 

        ax.set_xlabel('feature size') 

        ax.set_ylabel('frequency')

axes[0, 0].legend(['malignant', 'benign'], loc=(0.85, 1.1), bbox_to_anchor=(0.85, 1.1), fancybox=True, shadow=True)

fig.tight_layout()

plt.show()

특성 1 ~ 10까지의 breast cancer 히스토그램


# set 2

fig, axes = plt.subplots(5, 2) 


for i, ax in zip(np.arange(10, 20), axes.ravel()):

    for t in target_set:

        _, bins = np.histogram(cancer.data[:,i], bins=50) 

        ax.hist(t[:, i], bins=bins, alpha=0.5)

        ax.hist(benign[:, i], bins=bins, alpha=0.5)

        ax.set_title(cancer.feature_names[i])

        ax.set_yticks(())

fig.tight_layout()

plt.show()

특성 11 ~ 20까지의 breast cancer의 히스토그램


# set 3

fig, axes = plt.subplots(5, 2) 


for i, ax in zip(np.arange(20, 30), axes.ravel()):

    for t in target_set:

        _, bins = np.histogram(cancer.data[:,i], bins=50)

        ax.hist(malignant[:, i], bins=bins, alpha=0.5)

        ax.hist(benign[:, i], bins=bins, alpha=0.5)

        ax.set_title(cancer.feature_names[i])

        ax.set_yticks(())

fig.tight_layout()

plt.show()

특성 21 ~ 30까지의 breast cancer의 히스토그램

각 특성에 대해 특정 간격(bin)에 얼마나 많은 데이터 포인트가 나타나는지 횟수를 나타냈습니다.

'smoothness error' 특성은 두 히스토그램이 거의 겹쳐져 불필요한 특성이고

'worst concave point'는 두 히스토그램이 확실히 구분되어 매우 유용한 특성입니다.


그러나 이 그래프는 특성 간의 상호작용이나 상호작용이 클래스와 어떤 관련이 있는지는 알려주지 못합니다.



PCA 주성분분석으로 breast cancer 데이터를 분석해보겠습니다.

PCA를 사용하면 주요 상호작용을 찾아낼 수 있어 데이터 분석에 더 용이합니다.

PCA를 사용하기전 StandardScaler사용(평균=0, 분산=1)하여 전처리를 하겠습니다.

from sklearn.preprocessing import StandardScaler


scaler = StandardScaler()

scaler.fit(cancer.data)

x_scaled = scaler.transform(cancer.data)


# 기본값일 때 PCA는 데이터를 회전과 이동만 시키고 모든 주성분을 유지합니다.

# 데이터의 차원을 줄이려면 PCA객체를 만들 때 얼마나 많은 성분을 유지할지 알려주어야 합니다.

from sklearn.decomposition import PCA


pca = PCA(n_components=2)

pca.fit(x_scaled)

x_pca = pca.transform(x_scaled)


x_scaled_shape =  x_scaled.shape

print('{}'.format(x_scaled_shape))

# (569, 30)


x_pca_shape = x_pca.shape

print('{}'.format(x_pca_shape))

# (569, 2)


targets = np.unique(cancer.target)

markers = ['^', 'o']

for target, marker in zip(targets, markers):

    plt.scatter(x_pca[cancer.target==target][:, 0], x_pca[cancer.target==target][:, 1],

                s=20, alpha=0.7, marker=marker, edgecolors='k')


plt.legend(['malgnent', 'reglgent'], loc=1)

plt.gca().set_aspect('equal') # x축과 y축의 길이를 같게합니다.

plt.xlabel('first component', size=15)

plt.ylabel('second component', size=15)

plt.show()

두 개의 주성분을 사용해 그린 breast cancer 2차원 scatter plot


PCA의 단점은 그래프의 두 축을 해석하기가 쉽지 않다는 것입니다.

주성분은 원본데이터에 있는 어떤 방향에 대응하는 여러 특성이 조합된 형태이며

PCA객체가 학습될 때 components_ 속성에 주성분이 저장됩니다.

주성분의 구성 요소

print('{}'.format(pca.components_.shape))

# (2, 30)

print('{}'.format(pca.components_)) # 주성분 출력

# [[ 0.21890244  0.10372458  0.22753729  0.22099499  0.14258969  0.23928535

#    0.25840048  0.26085376  0.13816696  0.06436335  0.20597878  0.01742803

#    0.21132592  0.20286964  0.01453145  0.17039345  0.15358979  0.1834174

#    0.04249842  0.10256832  0.22799663  0.10446933  0.23663968  0.22487053

#    0.12795256  0.21009588  0.22876753  0.25088597  0.12290456  0.13178394]

#  [-0.23385713 -0.05970609 -0.21518136 -0.23107671  0.18611302  0.15189161

#    0.06016536 -0.0347675   0.19034877  0.36657547 -0.10555215  0.08997968

#   -0.08945723 -0.15229263  0.20443045  0.2327159   0.19720728  0.13032156

#    0.183848    0.28009203 -0.21986638 -0.0454673  -0.19987843 -0.21935186

#    0.17230435  0.14359317  0.09796411 -0.00825724  0.14188335  0.27533947]]

components_의 각 행은 주성분 하나씩을 나타내며 중요도에 따라 정렬되어 있습니다.

열은 원본 데이터의 특성에 대응하는 값입니다.



히트맵으로 그려보면

from mpl_toolkits.axes_grid1 import make_axes_locatable


image = plt.matshow(pca.components_, cmap='viridis')

plt.yticks([0, 1], ['first', 'second'])

plt.xticks(range(len(cancer.feature_names)), cancer.feature_names,

           rotation=90, ha='left')

plt.xlabel('feature', size=15)

plt.ylabel('principal component', size=15)


ax = plt.gca() # GetCurrentAxis

divider = make_axes_locatable(ax)

cax = divider.append_axes('right', size='5%', pad='5%')

plt.colorbar(image, cax=cax)

plt.show()

breast cancer에서 찾은 처음 두개의 주성분 히트맵

첫번째 주성분의 모든 특성은 부호가 같음, 모든 특성 사이에 양의 상관관계가 있습니다.

두번째 주성분은 부호가 섞여있음을 알 수 있습니다.

모든 특성이 섞여 있기 때문에 축이 가지는 의미를 알기가 어려습니다.



참고 자료: 

[1]Introduction to Machine Learning with Python, Sarah Guido

#!/usr/bin/env python3


preprocessing



0. 살펴보기

SVM과 neural_network는 데이터 스케일에 매우 민감

보통 특성마다 스케일을 조정해서 데이터를 변경


# library import

import mglearn

import matplotlib.pyplot as plt


mglearn.plots.plot_scaling() # 그래프 예제 출력

plt.show() # 그래프 출력


데이터셋의 스케일증 조정하거나 전처리하는 여러 방법


첫 번째 그래프는 두 개의 특성을 인위적으로 만든 이진 분류 데이터셋의


오른쪽의 4가지 그래프는 데이터를 기준이 되는 범위로 변환하는 방법

StandardScalar는 각 특성의 평균을 0, 분산을 1로 변경하여 모든 특성이 같은 크기를 가지게 함

    이 방법은 특성의 최솟값과 최댓값 크기를 제한하지 않음

RobustScaler는 특성들이 같은 스케일을 갖게 되지만 평균대신 중앙값을 사용 ==> 극단값에 영향을 받지 않음

MinMaxSclaer는 매우 다른 스케일의 범위를 0과 1사이로 변환

Nomalizer는 uclidian의 길이가 1이 되도록 데이터 포인트를 조정 ==> 각도가 많이 중요할 때 사용



1. 전처리를 통한 breast cancer 데이터 분석(SVM, Support Vector Machine)


# library import

from sklearn.datasets import load_breast_cancer

from sklearn.model_selection import train_test_split


# 데이터 로드

cancer = load_breast_cancer() 


# 데이터 분할

x_train, x_test, y_train, y_test = \ 

  train_test_split( # 데이터 분할을위해

      cancer.data, cancer.target, # 분할할 데이터

      test_size=0.25, random_state=1) # 테스트 비율, 랜덤상태


# 분할한 데이터 속성

print('x_train.shape \n{}'.format(x_train.shape))

print('x_test.shape \n{}'.format(x_test.shape))


# pre-processing import

# library import

from sklearn.preprocessing import MinMaxScaler


# 메소드 호출

scaler = MinMaxScaler() 



### MinMaxScaler의 fit 메소드는 train set에 있는 특성마다 최솟값과 최댓값을 계산 ###

### fit 메소드를 호출할 때 x_train만 넘겨줌 ###

scaler.fit(x_train) # 메소드 적용



###fit 메소드로 실제 훈련 데이터의 스케일을 조정하려면 스케일 객체의 transform 메소드를 사용 ###

### transform: 새로운 데이터 표현을 만들 때 사용하는 메소드 ###

# data 변환

x_train_scaled = scaler.transform(x_train)

x_test_scaled = scaler.transform(x_test)


# 스케일이 조정된 후 데이터셋의 속성

print('size: \n{}'.format(x_train_scaled.shape))

print('스케일 전 최솟값: \n{}'.format(x_train.min(axis=0))) # axis=0 열방향, axis=1 행방향

print('스케일 전 최댓값: \n{}'.format(x_train.max(axis=0)))

print('스케일 후 최솟값: \n{}'.format(x_train_scaled.min(axis=0))) # 최솟값 :0

print('스케일 후 최댓값: \n{}'.format(x_train_scaled.max(axis=0))) # 최댓값: 1


# 스케일이 조정된 후 테스트 데이터의 속성 출력

print('조정 후 특성별 최솟값: \n{}'.format(x_test_scaled.min(axis=0))) # 0이 최솟값이 아님

print('조정 후 특성별 최댓값: \n{}'.format(x_test_scaled.max(axis=0))) # 1이 최댓값이 아님



### 모든 스케일도 train set와 test set에 같은 변환을 적용해야 함 ###

### 스케일 조정된 데이터로 학습

# library import

from sklearn.svm import SVC


# 모델 생성 및 학습

svm = SVC(C=100)  

svm.fit(x_train, y_train)

svm_scaled = SVC(C=100).fit(x_train_scaled, y_train)


# 유효성 검증

print('조정 전 테스트 정확도 \n{:.3f}'.format(svm.score(x_test, y_test))) # 0.615

print('조정 후 테스트 정확도 \n{:.3f}'.format(svm_scaled.score(x_test_scaled, y_test))) # 0.965



### 스케일 조정된 데이터로 학습 ###

### 평균0, 분산 1을 갖도록 StandardScaler 조정 ###


# library import

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()

scaler.fit(x_train)

x_train_scaled = scaler.transform(x_train)

x_test_scaled = scaler.transform(x_test)


# 조정된 데이터로 SVM 학습

svm.fit(x_train_scaled, y_train) 


# 조정된 테스트 세트의 정확도

print('조정된 일반화 정확도 \n{:.3f}'.format(svm.score(x_test_scaled, y_test))) # 0.965


SVM은 스케일 조정에 민감하기때문에 결과값이 매우 다르게 나옴


#!/usr/bin/env python3

Neural Network(Deep Learning)

신경망이라 알려진 algorithm은 최근 deep learning이란 이름으로 불림
간단하게 Classifier와 Regression에 쓸 수 있는 multilayer perceptrons, MLP
multilayer perceptrons은 feed-forward, 또는 신경망이라고도 함

여러개의 가중치 합을 계산하는 것은 수학적으로 보면 하나의 가중치 합을 계산하는 것과 같음
각 은닉 유닛의 가중치 합을 계산한 후 그 결과에 비선형 함수인 rectified linear unit, ReLU나
hyperbolic tangent, tanh를 적용, 그 결과 가중치(𝘸)를 계산하여 ŷ 를 만듬

ReLu : 0이하를 잘라버리고,
tanh: x가 -∞일수록 -1, x가 ∞일수록 +1에 수렴

비선형 함수를 이용해 신경망이 선형 모델에서보다 훨씬 더 복잡한 함수를 학습 가능

1. ReLu함수와 tanh함수 살펴보기
# library import
import numpy as np
import matplotlib.pyplot as plt
import matplotlib

# matplotlib 설정
matplotlib.rc('font', family='AppleGothic'# 한글출력
plt.rcParams['axes.unicode_minus'] = False # 축 -설정

line = np.linspace(-3, 3, num=100) # 시작, 끝, 갯수
plt.plot( #  plot함수 호출
    line, np.tanh(line), linestyle='--',label='tanh' # x, y, style, label
    )
plt.plot(
    line, np.maximum(line, 0), label='relu'
    )
plt.xlabel('x'# x축 이름
plt.ylabel('tanh(x), relu(x)'# y축 이름
plt.legend(loc=2) # 범례위치 왼쪽 위
plt.show() # 그래프 출력

tanh(x), relu(x) 함수


2. Two moons Machine Learning(MLP, Multilayer perceptrons)
library load 
from sklearn.neural_network import MLPClassifier
from sklearn.datasets import make_moons
from sklearn.model_selection import train_test_split
import mglearn

# data load
x, y = make_moons( # moon data set 만들기
    n_samples=100, noise=0.25, random_state=3 # 갯수, 퍼짐정도, 랜덤상태
)

# data 분할
x_train, x_test, y_train, y_test = \
  train_test_split( # 데이터분할을 위해
      x, y, # 분할할 데이터
      stratify=y, random_state=42 # 그룹(binary 데이터로된 경우), 랜덤상태
      )

# model 생성 및 학습
mlp= MLPClassifier( # model 호출
    solver='lbfgs'# algorithm 
    random_state=42, # 랜덤 상태
    activation='relu', # 적용 함수
    hidden_layer_sizes=[100] # 은닉충 수 ex) [10 ,10] 유닛 10개짜리 은닉충 2개
    )
mlp.fit(x_train, y_train) # model 학습

# visualization
mglearn.plots.plot_2d_separator( # 2차원 데이터셋 분할 평면 그리기
    mlp, x_train, fill=True, alpha=0.3 # model 객체, train 데이터, 평면 칠하기, 투명도
    )
mglearn.discrete_scatter( # 2차원 산점도 그리기 위해
    x_train[:, 0], x_train[:, 1], y_train # x, y, 그룹
    )
plt.xlabel('feature 0'# x축 이름
plt.ylabel('feature 1'#  y축 이름
plt.show() # 그래프 출력

은닉 유닛이 100개인 neural로 학습한 two_moons 데이터셋의 결정 경계


neural_network는 매우 비 선형적이지만 비교적 매끄러운 decision boundary

3. Regularization 과 Visualization
Ridge Regression과 Linear Classifier에서 처럼 L2 penalty(계수를 0에 근접)로 alpha로 모델의 복잡도를 제어
기본값은 0.0001

# visualization
fig, axes = plt.subplots(2, 4) # figure객체를 fig에, plots객체를 axes에 2X4만큼 할당
n_hidden_nodes = [10, 100] # 배열생성
alpha_set = [0.0001, 0.01, 0.1, 1] # 배열 생성
for axe, n_node in zip(axes, n_hidden_nodes): # axes(2X4를 1X4, 1X4)와 n_hidden_nodes를 하나씩 axe와 n_node에 할당
    for ax, alpha in zip(axe, alpha_set): # axe(1X4)와 alpha_set을 하나씩 ax와 alpha에 할당
        mlp = MLPClassifier( # model 생성을 위해
            solver='lbfgs'# algorithm
            random_state=42, # 랜덤 상태
            hidden_layer_sizes=[n_node, n_node], # 은닉충
            alpha=alpha # 규제정도(클수록 강화)
            )
        mlp.fit(x_train, y_train) # 학습
        mglearn.plots.plot_2d_separator(# 2차원 평면을 나누기 위해
            mlp, x_train, # model 객체, train데이터
            fill=True, alpha=0.3, ax=ax # 평면칠하기, 투명도, plot객체
            )
        mglearn.discrete_scatter( # 2차원 산점도 그래프를 위해
            x_train[:, 0], x_train[:, 1], y_train, ax=ax # x, y, 그룹, plot객체
            )
        ax.set_title('n_hidden=[{}, {}]\nalpha={}'.format( # 타이틀
            n_node, n_node, alpha
            ))
plt.show()

은닉 유닛과 alpha에 따라 변하는 decision boundary


4. Breast Cancer Dataset Machine Learning(MLPClassifier)
# load library
from sklearn.datasets import load_breast_cancer
from sklearn.neural_network import MLPClassifier
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
import matplotlib

# matplotlib 설정
matplotlib.rc('font', family='AppleGothic'# 한글 설정
plt.rcParams['axes.unicode_minus'] = False # -표시

# data load
cancer = load_breast_cancer()

# data  분할
x_train, x_test, y_train, y_test = \
  train_test_split( # 데이터 분할을 위해
      cancer.data, cancer.target, # 분할할 데이터
      random_state=0, test_size=0.3 # 랜덤상태, 테스트 비율
      )

# feature visualization
plt.boxplot(x_train, manage_xticks=False) # 데이터, 소눈금 표시 안하기
plt.yscale('symlog') # 축 스케일을 log 로
plt.xlabel('feature list'# x축 이름
plt.ylabel('feature'# y축 이름
plt.show() # 그래프 출력

breast cancer 데이터셋의 특성 값 범위(y축은 logscale)


# 전처리, pre-processing
# axis =0 ==> 열, axis =1 ==> 행
train_min = x_train.min(axis=0) # 열방향 최솟값
train_range = (x_train - train_min).max(axis=0) # 최솟값을 제거한 후 최댓값

x_train_scaled = (x_train-train_min)/train_range # train data 조정
x_test_scaled = (x_test-train_min)/train_range # test data 조정(train 데이터 범위로 조정)

x_train_scaled.min(axis=0) # 조정된 범위의 최솟값, 0
x_train_scaled.max(axis=0) # 조정된 범위의 최댓값, 1

print('x_train_scaled min \n{}'.format(x_train_scaled.min(axis=0))) # 0
print('x_train_scaled.max \n{}'.format(x_train_scaled.max(axis=0))) # 1

# model  생성 및 학습
mlp = MLPClassifier( # model 생성
    solver='lbfgs', # algorithm
    random_state=0, # 랜덤상태
    hidden_layer_sizes=[100], # 은닉층 수
    alpha=0.001 # 규제 정도
    )
mlp.fit(x_train_scaled, y_train) # 학습

mlp.score(x_train_scaled, y_train) # train set 정확도
mlp.score(x_test_scaled, y_test) # 일반화 정확도

print('train set scaled accuracy \n{:.3f}'.format(mlp.score(x_train_scaled, y_train))) # 1.000
print('test set sclaed accuracy \n{:.3f}'.format(mlp.score(x_test_scaled, y_test))) # 0.965

5. Breast Cancer 데이터셋으로 학습된 가중치 확인
행 : 30개의 입력특성
열 : 100개의 은닉 유닛
밝은 색은 큰 양수 값

plt.figure(figsize=(20, 5)) # 그림 size
plt.imshow(
    mlp.coefs_[0], interpolation='none', cmap='viridis' # 입력과 은닉층 사이의 가중치, 축, 그림 테마
    )
plt.yticks(range(30), cancer.feature_names) # y축 포인트, label
plt.xlabel('은닉 유닛') # x축 이름
plt.ylabel('입력 특성') # y축 이름
plt.colorbar() # colorbar 생성
plt.show() # 그래프 출력

breast cancer 데이터셋으로 학습시킨 신경망의 첫번째 층의 가중치 히트맵


mlp.coefs_[0]은 입력과 은닉충 사이의 가중치가 저장되어있는 (30, 100) NumPy배열이고
mlp.coefs_[1]은 은닉충과 출력사이의 가중치가 저장되어있는 (100, 1) 크기의 NumPy배열



'python 머신러닝 -- 지도학습 > Classifier' 카테고리의 다른 글

Kernelized Support Vector Machines  (0) 2018.03.15
Gradient Boosting Model  (0) 2018.03.15
Random Forest  (0) 2018.03.15
Decision Tree  (0) 2018.03.14
Multi Linear Classification  (0) 2018.03.14

/usr/bin/env python3


Kernelized Support Vector Machine


데이터가 단순한 초평면으로 구분되지 않는 더 복잡한 model을 만들 수 있도록 확장한 것이

Kernelized Support Vector Machine입니다.


선형적으로 구분되지 않는 클래스를 가진 binary data 생성하여 시각화해보겠습니다.

from mglearn.datasets import make_blobs

from sklearn.svm import LinearSVC

from mglearn.plots import plot_2d_separator

import matplotlib.pyplot as plt

import numpy as np


x, y = make_blobs(centers=4, random_state=8)

y = y % 2 # y를 binary화


linear_svm = LinearSVC().fit(x, y) 


plot_2d_separator(linear_svm, x, fill=False)


marker_set = ['o', '^']

for i, m in zip(np.unique(y), marker_set):

    plt.scatter(x[y==i][:, 0], x[y==i][:, 1], marker=m,

                label='class {}'.format(i), edgecolors='k')

plt.legend(bbox_to_anchor=(0.1, 1.1), fancybox=True)

plt.xlabel('feature 0', size=15)

plt.ylabel('feature 1', size=15)

plt.show()

선형적으로 구분되지 않는 클래스를 가진 binary data

이런 경우에는 Kernel Support Vector Machine를 사용할 수 있습니다.

support vector machine에서 데이터를 고차원 공간에 매핑하는 방법은 크게 2가지가 있으며


1. 원래 특성의 가능한 조합을 모두 계산((특성1)^2, (특성2)^5) 하거나

2. (radial basis function), RBF커널-- 가우시안 커널을 사용하는 것입니다.

RBF 커널의 경우 특성의 중요도는 테일러급수를 이용하기때문에 고차원일수록 줄어듭니다.



1번 방법으로 고차원 공간에 매핑하겠습니다.

# 선 하나로 구분되지 않는 데이터를 분석하기 위해 수학적방법으로 차수를 하나 늘려 비선형 데이터축 추가합니다.


# x[:, 1:]

# x[:, 1].reshape(-1, 1)

# x[:, 1][:,np.newaxis]

# 위의 3개는 모두 같은 결과입니다.

x_new = np.hstack([x, x[:, 1:]**2])


linear_svm_3d = LinearSVC().fit(x_new, y)


xx = np.linspace(x[:, 0].min()-2, x[:, 0].max()+2, num=50)

yy = np.linspace(x[:, 1].min()-2, x[:, 1].max()+2, num=50)


# xx, yy의 좌표를 이용하여 교차 좌표 만듬 ex) x=[1, 2], y=[3, 4]라면 (1, 3), (1, 4), (2, 3), (2, 4) 좌표 생성합니다.

XX, YY = np.meshgrid(xx, yy)

ZZ = YY ** 2 


params = np.c_[XX.ravel(), YY.ravel(), ZZ.ravel()] # np.c_ : XX, YY, ZZ의 원소를 하나씩 출력하여 한개의 행으로 묶기

dec = linear_svm_3d.decision_function(params)

plt.contourf(XX, YY, dec.reshape(XX.shape), # XX.shape = (50, 50) 

             levels=[dec.min(), 0, dec.max()],

                 alpha=0.5)


for i, m in zip(np.unique(y), marker_set):

    plt.scatter(x[y==i][:, 0], x[y==i][:, 1], marker=m, edgecolors='k')


plt.xlabel('feature 0', size=15)

plt.ylabel('feature 1', size= 15)

plt.show() 

고차원 특성을 이용해 구분한 Support vector machine decision boundary



Support Vector는 학습이 진행되는 동안 SVM은 각 train 데이터 포인트가 두 class 사이의 decision boundary를 구분하는 데 얼마나 중요한지를 배우게 됩니다.

일반적으로 train 데이터의 일부만 decision boundary를 만드는데 영향을 줍니다.


class 두 개의 경계에 위치한 데이터 포인트를 support vector라 합니다.


suppor vector의 중요도는 train 과정에서 학습하며 SVC객체의 dual_coef_에 저장되며

radial basis function, RBF커널, gaussian  : 특성의 중요도는 테일러급수를 이용하기때문에 고차원일수록 줄어듭니다.

이번에는 원래 2개 특성의 투영한 decision boundary를 그려보겠습니다.

from sklearn.svm import SVC

from mglearn.tools import make_handcrafted_dataset

from mglearn.plots import plot_2d_separator


x, y = make_handcrafted_dataset()


svm = SVC(kernel='rbf', C=10, gamma=0.1) # kernel, 규제정도, 커널폭의 역수

svm.fit(x, y)


plot_2d_separator(svm, x, eps=0.5)  # epsilon


marker_set = ['o', '^']

color_set = ['skyblue', 'orange']

class_set = np.unique(y)

for i, m, color in zip(class_set, marker_set, color_set):

    plt.scatter(x[y==i][:, 0], x[y==i][:, 1], marker=m, 

label='class {}'.format(i), edgecolors='k', c=color)


sv = svm.support_vectors_

print('{}'.format(sv))

# [[ 8.1062269   4.28695977]

#  [ 9.50169345  1.93824624]

#  [11.563957    1.3389402 ]

#  [10.24028948  2.45544401]

#  [ 7.99815287  4.8525051 ]]


# dual_coef_의 부호에 의해 서포트 벡터의 클래스 레이블이 결정됩니다.

sv_labels = svm.dual_coef_.ravel() > 0 # 중요도가 0보다 높은  것, 클래스 구분

idx_set = [~sv_labels, sv_labels]


for idx,m, color in zip(idx_set, marker_set, color_set):

    plt.scatter(sv[idx][:, 0], sv[idx][:, 1],  s=100, 

marker=m, edgecolors='k', linewidths=2, c=color)


plt.xlabel('feature 0', size=15)

plt.ylabel('feature 1', size=15) 

plt.show() 

RBF Kernel을 사용한 SVM으로 만든 decision boundary와 support vector




SVM에서 model의 complexity를 제어하는 주요 parmeter

gamma: kernel폭의 역수입니다.

gamma가 작을 수록 knernal폭이 넓어져 train 데이터의 영향 범위도 넓어집니다.


다음은 C와 gamma에 따른 decision area를 보여줍니다.

fig, axes = plt.subplots(3, 3) 


C_args = np.array([-1, 0, 3])

C_set = 10**C_args.astype(float)


gamma_args = np.arange(-1, 2)

gamma_set = 10**gamma_args.astype(float)


marker_set = ['o', '^']

for axe, C in zip(axes, C_set):

    for ax, gamma in zip(axe, gamma_set):

        params = {'C':C, 'gamma':gamma}


        svm = SVC(**params).fit(x, y)


        plot_2d_separator(svm, x, eps=0.5, ax=ax)

        ax.set_title('C={}, gamma={}'.format(C, gamma))


        color_set = ['skyblue', 'orange']

        for i, m, color in zip(np.unique(y), marker_set, color_set):

            ax.scatter(x[y==i][:, 0], x[y==i][:, 1], marker=m,

                       label='class {}'.format(i), c=color, edgecolors='k')


        sv = svm.support_vectors_

        idx = svm.dual_coef_.ravel() < 0

        idx_set = np.array([idx, ~idx])


        for i, idx, color, m in zip(np.unique(y), idx_set, color_set, marker_set):

            ax.scatter(sv[idx][:, 0], sv[idx][:, 1], marker=m,

                       c=color, label='class {} support vector'.format(i), s=100,

                       edgecolors='k', linewidths=2)

        

axes[0, 1].legend(loc='upper center', bbox_to_anchor=(0.5, 1.4), fancybox=True, shadow=True, ncol=4)

plt.show()

C와 gamma 에 따른 decision boundary와 support vector

왼쪽에서 오른쪽으로 가면서 gamma 0.1 -> 10 으로 증가합니다.

작은 gamma는 gaussia kernel의 반경을 크게 하여 많은 포인트들이 가까이 있는 것으로 고려됩니다.

따라서 왼쪽 그림의 decision boundary는 부드럽지만 오른쪽 그림의 decision boundary는 데이터 포인트 하나에 민감합니다.

gamma의 값이 클수록 model의 복잡도가 올라가는 것을 확인할 수 있습니다.


위에서 아래로는 C 값을 0.1 -> 1000으로 증가합니다.

linear model과 마찬가지로 작은 C값은 제약이 큰 model을 만듭니다. ==> 일반화 성능이 증가


C값을 증가시키면 제약이 거의 없어져 포인트 하나 값에 영향이 커집니다. ==> 복잡도 증가



SVM으로 Wine Data를 분석해보겠습니다. 

from sklearn.model_selection import train_test_split

from sklearn.datasets import load_wine

from sklearn.svm import SVC


wine = load_wine()


x_train, x_test, y_train, y_test = train_test_split(

      wine.data, wine.target, stratify=wine.target,

      random_state=0, test_size=0.25)


svc = SVC(random_state=0) # default: C=1, kernel='rbf', gamme='auto'

svc.fit(x_train, y_train) 


train_score = svc.score(x_train, y_train)

test_score = svc.score(x_test, y_test)


print('{:.3f}'.format(train_score))

# 1.000


print('{:.3f}'.format(test_score))

# 0.422

gamma의 기본값은 auto로 : 1/wine.data.shape[1]입니다.

train set는 완벽하지만 일반화는 42.2%의 정확도로 overfitting된 것을 알 수가 있습니다.



SVM은 parameter설정과 data scale에 매우 민감합니다. 이를 해결하기 위해 data preprocessing이 존재합니다.

preprocessing을 하기 전에 wine data의 특성을 시각화하여 알아보겠습니다.

plt.boxplot(x_train, manage_xticks=False) # manage_xticks: x축 눈금 작은 눈금 표시

plt.yscale('symlog') # y축 로그 스케일

plt.xlabel('feature list', size =15

plt.ylabel('feature size', size =15)

plt.show()

wine 데이터셋의 특성 값 범위(y축은 logscale)

그래프를 보면 wine 데이터셋의 특성은 자릿 수 자체가 달라서 SVM에서는 영향이 아주 큼을 알 수 있습니다.



이제는 SVM을 위한 데이터 전처리를 해보겠습니다.

전처리는 특성 값의 범위가 비슷해지도록 조정하는 것입니다.


모든 특성의 값을 0과 1사이로 맞추는 방법을 많이 사용합니다.

train_min = x_train.min(axis=0) 

train_range = (x_train - train_min).max(axis=0)

x_train_scaled = (x_train - train_min) / train_range

x_test_scaled = (x_test - train_min) / train_range

    

x_min_ori = x_train_scaled.min(axis=0)

print('{}'.format(x_min_ori))

# [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]


x_max_ori = x_train_scaled.max(axis=0)

print('{}'.format(x_max_ori))

# [1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]



위의 코드는 아래와 같이 전처리 메소드를 로드하여 사용할 수 있으며 앞으로는 이 방법을 사용하겠습니다.

from sklearn.preprocessing import MinMaxScaler


mms_scaler = MinMaxScaler()

mms_scaler.fit(x_train)


x_train_scaled_mms = mms_scaler.transform(x_train)

x_test_scaled_mms = mms_scaler.transform(x_test)


x_min = x_train_scaled.min(axis=0)

print('{}'.format(x_min))

# [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]


x_max = x_train_scaled.max(axis=0)

print('{}'.format(x_max))

# [1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]



이제 전처리한 데이터로 머신러닝을 해보겠습니다.

svc = SVC(random_state=0)

svc.fit(x_train_scaled_mms, y_train)


train_score = svc.score(x_train_scaled_mms, y_train)

test_score = svc.score(x_test_scaled_mms, y_test)


print('{:.3f}'.format(train_score))

# 0.970


print('{:.3f}'.format(test_score))

# 0.978

스케일을 조정하니 성능이 매우 좋아졌습니다.



이번에는 C값을 매우 크게 하여 규제를 완화 해보겠습니다.

svc = SVC(C=1000) 

svc.fit(x_train_scaled, y_train) 


train_score = svc.score(x_train_scaled_mms, y_train) 

test_score = svc.score(x_test_scaled_mms, y_test)


print('{:.3f}'.format(train_score))

# 1.000


print('{:.3f}'.format(test_score))

# 1.000

규제를 매우 완화 하니 train_score와 test_score모두 결과가 1이 나와 과소적합된 형태입니다.



SVM은 데이터의 특성이 몇개 안되도 복잡한 decision boundary를 만들 수 있습니다.

SVM은 parameter와 pre-procession(전처리)에 신경을 많이 써야합니다.


random_forest나 gradient boosting 같은 pre-processing이 거의 없는 모델을 많이 사용합니다.

그러나 예측이 어떻게 결정되었는지 이해하기 어렵고 비전문가에게 설명하기또한 난해합니다.




참고 자료:

[1]Introduction to Machine Learning with Python, Sarah Guido

'python 머신러닝 -- 지도학습 > Classifier' 카테고리의 다른 글

Neural Network(Deep Learning)  (0) 2018.03.17
Gradient Boosting Model  (0) 2018.03.15
Random Forest  (0) 2018.03.15
Decision Tree  (0) 2018.03.14
Multi Linear Classification  (0) 2018.03.14

#!/usr/bin/env python3


Gradient Boosting Model


gradient boosting regression tree는 여러 개의 decision tree를 묶어 강력한 model을 만드는 ensemble기법입니다.

random forest와 달리 gradient boosting model은 이전 tree의 오차를 보완하는 방식으로 tree를 만듭니다.


gradient boosting은 무작위성이 없어 powerful한 pre-pruning이 사용되며

1~5 정도 깊이의 tree를 사용하므로 메모리를 적게 사용하고 예측도 빠릅니다.


gradient boosting은 이런 얕은 트리들을 계속해서 연결해나가는 것입니다.


gradient boosting은 parmeter설정에 random forest보다 조금 더 민감하지만 잘 조정하면 높은 정확도를 제공합니다.


gradinet boosting에서 주요 parameter는 이전 트리의 오차를 얼마나 강하게 보정할 것인가를 제어하는

learning_rate가 있습니다.


learning_rate를 높이면 보정을 강하게 하기 때문에 복잡한 모델을 만듭니다.

n_estimator 값을 키우면 ensemble에 트리가 더 많이 추가되어 모델의 복잡도가 커지고 train 세트를 더 정확하게 fitting합니다.


Breast Cancer datasets으로 GradientBoostingClassifier을 적용해보겠습니다.

from sklearn.ensemble import GradientBoostingClassifier

from sklearn.model_selection import train_test_split

from sklearn.datasets import load_breast_cancer


cancer = load_breast_cancer()


x_train, x_test, y_train, y_test = train_test_split(cancer.data, cancer.target, 

                                                    stratify=cancer.target, random_state=0)


gbc = GradientBoostingClassifier(random_state=0) # 기본값: max_depth=3, learning_rate=0.1

gbc.fit(x_train, y_train)


score_train = gbc.score(x_train, y_train) # train set 정확도

print('{:.3f}'.format(score_train))

# 1.000


score_test = gbc.score(x_test, y_test) # 일반화 정확도

print('{:.3f}'.format(score_test))

# 0.958

훈련세트의 정확도가 100%이므로 overfitting입니다.



overfitting을 막기 위해 트리의 깊이를 줄여 pre-pruning을 강하게 하겠습니다.

gbc = GradientBoostingClassifier(random_state=0, max_depth=1)

gbc.fit(x_train, y_train)


score_train_pre = gbc.score(x_train, y_train) # train set 정확도

print('{:.3f}'.format(score_train_pre))

# 0.995


score_test_pre = gbc.score(x_test, y_test) # 일반화 정확도

print('{:.3f}'.format(score_test_pre))

# 0.965

이전에 비해 train_set는 낮아졌고, test set의 정확도는 높아졌으므로 모델이 향상된 것을 볼 수 있습니다.



이번에는 learning_rate 조절하여 overfitting을 해결하겠습니다.

gbc = GradientBoostingClassifier(random_state=0, max_depth=3, learning_rate=0.01) # 기본값 0.1

gbc.fit(x_train, y_train)


score_train_lr = gbc.score(x_train, y_train)

print('{:.3f}'.format(score_train_lr))

# 0.995


score_test_lr = gbc.score(x_test, y_test) 

print('{:.3f}'.format(score_test_lr))

# 0.944



위의 결과를 시각화하는 코드는 다음과 같습니다.

import matplotlib

import matplotlib.pyplot as plt

import numpy as np


score_set = np.round([score_train, score_test], 3)

max_depth_score_set = np.round([score_train_pre, score_test_pre], 3)

learning_rete_score_set = np.round([score_train_lr, score_test_lr], 3)


index = np.arange(len(score_set))

bar_width = 0.35

bottom = 0.8

list_set = [max_depth_score_set, learning_rete_score_set]

line_set = ['--', ':']

names = ['train', 'test']


for i, line_set, name in zip(index, line_set, names):

    plt.hlines(score_set[i], xmin=0-bar_width,

               xmax=index[-1]+2*bar_width,

               linestyle=line_set, label='default {}'.format(name))

    plt.text(0-bar_width, score_set[i]+0.005, str(score_set[i]))


plt.bar(index, max_depth_score_set, width=bar_width, label='adj max_depth')

plt.bar(index+bar_width, learning_rete_score_set, width=bar_width, label='adj learning rate')

for i, ts, te in zip(index, max_depth_score_set, learning_rete_score_set):

    plt.text(i, (bottom+ts)/2, str(ts), horizontalalignment='center')

    plt.text(i+bar_width, (bottom+te)/2, str(te), horizontalalignment='center')


plt.ylim(bottom, 1.05)

plt.xticks(index+bar_width/2, names)

plt.ylabel('score', size=15)

plt.legend(loc='upper center', bbox_to_anchor=(0.5, 1.1), ncol=4, fancybox=True, shadow=False)

plt.show()

max_depth와 learning rate를 조절한 random forest의 score

위 2가지 방식은 model의 복잡도를 감소시키므로 train 세트의 정확도가 낮아졌습니다.

이 예에서 learning_rate를 낮추는 것은 일반화 정확도의 성능을 조금밖에 개선하지 못했지만, 

트리의 최대 깊이를 낮추는 것은 model 성능 향상에 크게 기여한것을 볼 수 있습니다.



max_depth=1일 때 특성 중요도를 시각화 하는 코드는 다음과 같습니다. 

gbc = GradientBoostingClassifier(max_depth=1, random_state=0)

gbc.fit(x_train, y_train)

n_feature = cancer.data.shape[1]

index=np.arange(n_feature)


plt.barh(index, gbc.feature_importances_, align='center')

plt.yticks(index, cancer.feature_names)

plt.xlabel('feature importances', size=15)

plt.ylabel('feature', size=15)

plt.show() 

breast cancer data로 만든 GradientBoostingClassifier의 feature importances

GradientBoostingClassifier의 특성 중요도와 RandomForestClassifier의 특성 중요도가 비슷합니다.

그러나 GradientBoostingClassifier은 일부 특성을 완전 무시합니다.


보통 더 안정적인 RandomForest를 먼저 적용하고 GradientBoosting을 사용하고

예측 시간이 중요하거나, machine learning model에서 마지막 성능까지 사용할 때 많이 사용



참고 자료: 

[1]Introduction to Machine Learning with Python, Sarah Guido

'python 머신러닝 -- 지도학습 > Classifier' 카테고리의 다른 글

Neural Network(Deep Learning)  (0) 2018.03.17
Kernelized Support Vector Machines  (0) 2018.03.15
Random Forest  (0) 2018.03.15
Decision Tree  (0) 2018.03.14
Multi Linear Classification  (0) 2018.03.14

#!/usr/bin/env python3


Random Forest


ensemble여러 머신러닝 model을 연결하여 강력한 model을 만드는 기법입니다.

ensemble model이 classifier와 regression 문제의 다양한 데이터셋에서 효과적이며

random forestgradient boosting은 둘 다 model을 구성하는 기본 요소로 decision tree를 사용합니다.


random forest는 기본적으로 조금씩 다른 여러 decision tree의 묶음입니다.


random forest의 Idea

각각의 tree는 비교적 예측을 잘 할 수 있지만 데이터의 일부에 overfitting하는 경향을 가진다는 데 기초합니다.

잘 작동하되 서로 다른 방향으로 overfitting된 tree를 많이 만들면 그 결과를 평균냄으로써 overfitting된 양을 줄일 수 있습니다.

이렇게 하면 tree model의 예측 성능이 유지되면서 overfitting이 줄어드는 것이 수학적으로 증명되었습니다.


이런 Idea를 구현하기 위해 decision tree를 많이 만들어야하고

각각의 tree는 타깃 예측을 잘해야 하고 다른 tree와는 구별되어야 합니다.


random forest는 tree들이 달라지도록 tree를 생성할 때 random하게 생성합니다.

random forest에서 tree를 random하게 만드는 방법은 2가지가 있습니다.


1. tree를 만들 때 사용하는 데이터를 무작위로 선택하고

2. 분할 테스트에서 특성을 무작위로 선택합니다.


random forest model에서 주요 parameter

n_estimators : 생성할 tree의 개수와

max_features : 최대 선택할 특성의 수입니다.


random forest는 기본적으로 bootstrap sampling(복원추출)을 사용하며

decision tree 생성으로 algorithm으로 진행합니다.


bootstrap sampling에서 어떤 데이터 포인트는 약 36.6%정도 누락될 확률이 존재하는데

이 것은 다음과정으로 확인할 수 있습니다.

 - 100개의 표본중 표본 하나가 선택되지 않을 확률은 99/100

 - 뽑은 표본을 제외하지 않고 100번 반복할 때 한번도 선택되지 않을 확률: (0.99)^100 = 0.366

 

n_estimators에서

bootstrap sampling은 random forest의 tree가 조금씩 다른 데이터셋으로 만들어지도록합니다.

max_feature에서 각 node는 특성의 일부를 무작위로 추출하여 사용합니다.


max_features를 전체 특성의수로 설정하면 모든 특성을 고려하므로 decision tree에서 무작위성이 들어가지 않습니다.

그러나 bootstrap sampling의 무작위성은 존재합니다.


max_features 값을 크게 하면 

random forest의 tree들은 같은 특성을 고려하므로 tree들이 매우 비슷해지고 가장 두드러진 특성을 이용해 데이터에 잘 맞춰집니다.


max_features를 낮추면 

random forest tree들은 많이 달라지고 각 tree는 데이터에 맞추기 위해 tree의 깊이가 깊어집니다.


인위적 데이터로 random forest를 tree별로 알아보면

from sklearn.ensemble import RandomForestClassifier

from sklearn.datasets import make_moons

from sklearn.model_selection import train_test_split


x, y = make_moons(n_samples=100, noise=0.25, random_state=3) # noise: Standard deviation of Gaussian noise added to the data.


x_train, x_test, y_train, y_test = train_test_split(x, y,

                                                    stratify=y, random_state=42)


forest = RandomForestClassifier(n_estimators=5, n_jobs=-1, random_state=42) # n_estimators: 사용할 tree수

forest.fit(x_train, y_train)


import matplotlib.pyplot as plt

import numpy as np

from mglearn.plots import plot_2d_classification


_, axes = plt.subplots(2, 3)

marker_set = ['o', '^']


for i, (axe, tree) in enumerate(zip(axes.ravel(), forest.estimators_)):

    axe.set_title('tree {}'.format(i))

    plot_2d_classification(tree, x, fill=True, ax=axe, alpha=0.4)


    for i, m in zip(np.unique(y), marker_set):

        axe.scatter(x[y==i][:, 0], x[y==i][:, 1], marker=m,

                    label='class {}'.format(i), edgecolors='k')

        axe.set_xlabel('feature 0')

        axe.set_ylabel('feature 1')


axes[-1, -1].set_title('random forest')

axes[-1, -1].set_xlabel('feature 0')

axes[-1, -1].set_ylabel('feature 1')

plot_2d_classification(forest, x, fill=True, ax=axes[-1, -1], alpha=0.4)


for i, m in zip(np.unique(y), marker_set):

    plt.scatter(x[y==i][:, 0], x[y==i][:, 1], marker=m,

                label='class {}'.format(i), edgecolors='k')

plt.show()

5개의 random dicision tree의 decision boundary와 예측한 확률을 평균내어 만든 random forest

5개의 트리가 만든 decision boundary는 매우 다릅니다.

bootstraping sampling 때문에 한쪽 tree에 나타나는 train point가 다른 tree에는 포함되지 않을 수 있습니다.



random forest를 이용하여 breast cancer datasets을 분석해보겠습니다.

from sklearn.datasets import load_breast_cancer


cancer = load_breast_cancer()


x_train, x_test, y_train, y_test = train_test_split(cancer.data, cancer.target,

                                                    stratify=cancer.target, random_state=0)


n_feature = cancer.data.shape[1]


score_n_tr_est = []

score_n_te_est = []

score_m_tr_mft = []

score_m_te_mft = []


for i in np.arange(1, n_feature+1): # n_estimators와 mat_features는 모두 0보다 큰 정수여야 하므로 1부터 시작합니다.

    params_n = {'n_estimators':i, 'max_features':'auto', 'n_jobs':-1} # **kwargs parameter

    params_m = {'n_estimators':10, 'max_features':i, 'n_jobs':-1}


    forest_n = RandomForestClassifier(**params_n).fit(x_train, y_train)

    forest_m = RandomForestClassifier(**params_m).fit(x_train, y_train)

    

    score_n_tr = forest_n.score(x_train, y_train)

    score_n_te = forest_n.score(x_test, y_test)

    score_m_tr = forest_m.score(x_train, y_train)

    score_m_te = forest_m.score(x_test, y_test)


    score_n_tr_est.append(score_n_tr)

    score_n_te_est.append(score_n_te)

    score_m_tr_mft.append(score_m_tr)

    score_m_te_mft.append(score_m_te)


index = np.arange(len(score_n_tr_est))

plt.plot(index, score_n_tr_est, label='n_estimators train score', color='lightblue', ls='--') # ls: linestyle

plt.plot(index, score_m_tr_mft, label='max_features train score', color='orange', ls='--')

plt.plot(index, score_n_te_est, label='n_estimators test score', color='lightblue')

plt.plot(index, score_m_te_mft, label='max_features test score', color='orange')

plt.legend(loc='upper center', bbox_to_anchor=(0.5, 1.1),

           ncol=2, fancybox=True, shadow=False) # fancybox: 박스모양, shadow: 그림자

plt.xlabel('number of parameter', size=15)

plt.ylabel('score', size=15)

plt.show()

parameter 갯수에 따른 train score와 test score

parameter의 갯수가 적을 때보다 많을때가 성능이 더 좋아 보입니다.



이번에는 사용한 특성의 중요도를 시각화 하겠습니다.

n_feature = cancer.data.shape[1]

index = np.arange(n_feature)


forest = RandomForestClassifier(n_estimators=100, n_jobs=-1)

forest.fit(x_train, y_train)

plt.barh(index, forest.feature_importances_, align='center')

plt.yticks(index, cancer.feature_names)

plt.ylim(-1, n_feature)

plt.xlabel('feature importance', size=15)

plt.ylabel('feature', size=15)

plt.show()

breast cancer로 만든 random forest의 feature importances

decision tree처럼 random foreset도 featre importances를 제공합니다.

일반적으로 random forest의 feature importances가 하나의 tree에서 제공하는 것보다 더 신뢰할 만합니다.


decision tree에서는 'worst radius'특성이 가장 중요하다고 보지만 random forest에서는 'worst perimeter'를 가장 중요한 요소로 판단했습니다.

random forest는 단일 tree보다 더 넓은 시각으로 데이터를 바라볼 수 있습니다. 




참고 자료: 

[1]Introduction to Machine Learning with Python, Sarah Guido

'python 머신러닝 -- 지도학습 > Classifier' 카테고리의 다른 글

Kernelized Support Vector Machines  (0) 2018.03.15
Gradient Boosting Model  (0) 2018.03.15
Decision Tree  (0) 2018.03.14
Multi Linear Classification  (0) 2018.03.14
Logistic Regression  (1) 2018.03.13

+ Recent posts