#!/usr/bin/env python3


Simple Grid Search


머신러닝의 성능을 높이는 방법에는 여러가지가 있지만

여기서는 매개변수를 튜닝하여 일반화 성능을 높이는 것목표입니다.

가장 널리 사용하는 방법은 grid search그리드 서치 이며

관심있는 매개변수들을 대상으로 가능한 모든 조합을 시도해보는 것입니다.


SVM의 경우 

커널의 폭에 해당하는 gamma와 규제 매개변수 C가 중요합니다.

SVM에 더 자세한 정보는 이전 포스팅을 참조하세요

[1]Kernelized Support Vector Machines

매개변수 C와 gamma에 [0.001, 0.01, 0.1, 1, 10, 100] 값을 적용해보면

조합의 수는 총 6x6=36개가 나오며


모든 조합을 살펴보기 위해 SVM 매개변수 설정 테이블을 다음처럼 만들 수 있습니다.

매개 변수 설정 테이블


간단한 grid search의 경우는 아래처럼 코드를 짤 수 있습니다.

# library import

from sklearn.svm import SVC

from sklearn.model_selection import train_test_split

from sklearn.datasets import load_iris


# data load

iris = load_iris()


# data partition

x_train, x_test, y_train, y_test = \

  train_test_split(iris.data, iris.target,

                   test_size=0.3, random_state=0)


print('train set size:{}, test set size:{}'.format(x_train.shape, x_test.shape))

# train set size:(105, 4), test set size:(45, 4)


# grid search

best_score = 0

values = [0.001, 0.01, 0.1, 1, 10, 100]

for g in values:

    for c in values:

        svc = SVC(gamma=g, C=c).fit(x_train, y_train)

        score = svc.score(x_test, y_test)

        if score > best_score:

            best_score = score

            best_param = {'C':c, 'gamma':g}


print('best score ==> {:.3f}'.format(best_score))

print('best parameter ==> {}'.format(best_parameter))

# best score ==> 0.978

# best parameter ==> {'C': 100, 'gamma': 0.001}

>> grid search를 통해 C=100, gamma: 0.001일 때 가장 좋은 결과가 나온 것을 확인할 수 있습니다.

이 결과를 보면 이 데이터셋에서 모델 정확도가 97.8%라고 보고할 수 있습니다.


하지만 이런 주장은 다음과 같은 이유로 잘못될 수도 있습니다.

1. 이 정확도는 새로운 데이터까지 이어지지 않을 수 있습니다.

매개변수를 조정하기 위해 테스트 세트를 이미 사용했기 때문에 테스트세트는 모델이 얼마나 좋은지 평가하는 데 사용할 수 없습니다.

즉 평가를 위해서는 모델을 만들 때 사용하지 않은 독립된 데이터셋이 필요하고 

다음 장에서 독립된 데이터셋을 다루는 방법을 살펴보겠습니다.



참고 자료: 

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

'모델 평가와 성능 향상 > 그리드 서치' 카테고리의 다른 글

nested cross-validation  (1) 2018.04.05
asymmetric parameter with grid search  (0) 2018.04.05
cross-validation result analysis  (2) 2018.04.05
parameter overfitting  (0) 2018.04.04

#!/usr/bin/env python3


다양한 교차 검증 방법


1. LOOCVLeave-one-out cross-validation

LOOCV 교차 검증폴드 하나샘플 하나만 들어 있는 k-겹 교차 검증

각 반복에서 하나의 데이터 포인트를 선택해 테스트 세트로 사용

특히 데이터셋이 클 때는 시간이 매우 오래 걸리지만, 작은 데이터셋에서는 좋은 결과를 만들어냄


# library load

from sklearn.datasets import load_iris

from sklearn.linear_model import LogisticRegression

from sklearn.model_selection import LeaveOneOut, cross_val_score, KFold


# dataset

iris = load_iris()


# create object

logreg = LogisticRegression() # model

loo = LeaveOneOut() # LeaveOneOut model


# test validation

# LOOCV

scores_loo = cross_val_score(logreg, iris.data, iris.target, cv=loo) 


# K-fold(5)

kfold = KFold(n_splits=5, random_state=0, shuffle=True)

scores_fold = cross_val_score(logreg, iris.data, iris.target, cv=kfold) # model, train, target, cross validation


# cv result

print('iris.data.shape \n{}'.format(iris.data.shape))

print('cv number of partition \n{}'.format(len(scores_loo)))

print('mean score_loocv \n{:.3f}'.format(scores_loo.mean()))

print('mean score_kfold \n{:.3f}'.format(scores_fold.mean()))


cv result


>> LOOCV가 K-fold보다 성능이 더 좋음을 알 수가 있음



2. shuffle-split cross-validation임의 분할 교차 검증

shuffle-split cross-validation에서는 

train_size만큼의 포인트로 train set를 만들고, test_size만큼(train set와 중첩되지 않는) 포인트로 test set를 만들도록 분할함

이 분할은 n_splits 횟수만큼 반복됨


# load library

import mglearn

import matplotlib.pyplot as plt

import matplotlib


# matplotlib 설정

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

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


mglearn.plots.plot_shuffle_split()

plt.show()


10개의 데이터 포인트에 train_size=5, test_size=2와 n_splits=4를 적용한 ShuffleSplit


>> train_size와 test_size에 정수를 입력하면 데이터포인트의 절대 갯수를

실수를 입력하면 전체 데이터에서의 비율을 의미


# iris데이터를 이용하여 shuffle-split cross-validation

# load library

from sklearn.model_selection import ShuffleSplit


# create object

shuffle_split = ShuffleSplit(test_size=0.5, train_size=0.5, n_splits=10) # ShuffleSplit 객체 생성


# cv test

scores = cross_val_score(logreg, iris.data, iris.target, cv=shuffle_split)


# cv test result

print('cv test score \n{}'.format(scores))

cv test score

>> 임의 분할 교차 검증은 반복 횟수를 훈련 세트나 테스트 세트의 크기와 독립적으로 조절해야 할때 유용함

또한 train_size와 test_size의 합을 전체와 다르게 함으로써 전체 데이터의 일부만 사용할 수 있음

이렇게 데이터를 subsampling부분 샘플링방식은 대규모 데이터셋으로 작업할 때 유용함



3. 그룹별 교차 검증

# load library

mglearn.plots.plot_group_kfold()

plt.show()

레이블에 기반한 GroupKFold분할


>> 이 그림에서 볼 수 있듯이 각 분할에서 한 그룹 전체가 훈련 세트 아니면 테스트 세트에 있음

그룹별 교차 검증의 자세한 정보는 아래 링크 참조

http://scikit-learn.org/stable/modules/cross_validation.html


데이터 안에 매우 연관된 그룹이 있을 때도 교차 검증을 널리 사용함

<<얼굴 사진에서 표정을 인식하는 시스템을 만들기 위해 100명의 사진을 모았다고 가정>>

한 사람을 찍은 여러 장의 사진이 각기 다른 표정을 담고 있음 ==>

이 데이터셋에 없는 사람의 표정을 정확히 구분할 수 있는 분류기를 만드는 것이 목표


같은 사람의 사진훈련 세트와 테스트 세트에 모두 나타날 수 있으므로 그룹별 교차겸증을 하는것이 타당


새 얼굴에 대한 일반화 성능을 더 정확하게 평가하려면

훈련 세트와 테스트 세트에 서로 다른 사람의 사진이 들어가도록 해야함


이를 위해 사진의 사람이 누구인지 기록한 배열을 

groups 매개변수로 전달 받을 수 있는 GroupKFold를 사용할 수 있음

groups 배열은 훈련 세트와 테스트 세트를 만들 때 분리되지 않아야 할 그룹을 지정하는 것이라 클래스 레이블과는 다름


데이터에 그룹에 있는 예로는 의료분야가 일반적

여기서는 같은 환자로부터 얻은 여러 샘플을 가지고 새로운 환자에게 일반화하는 것이 목적


비슷하게 음성 인식에서도 데이터셋에 같은 사람의 목소리가 여러 개 녹음되어 있을 수 있으며, 

관심사항은 새로운 사람의 대화를 인식하는 것


# load library

from sklearn.model_selection import GroupKFold

from sklearn.datasets import make_blobs


# datasets

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

plt.scatter(x[:, 0], x[:, 1], c=y)

plt.xlabel('feature 0')

plt.ylabel('feature 1')

plt.show()


make_blobs로 만든 인위적 데이터

>> 인위적으로 만든 데이터셋에 group 배열로 그룹을 지정하는 방법

데이터셋은 12개의 포인트로 이뤄져 있고 groups는 각 데이터 포인트에 대해 각 포인트가 어떤그룹(ex. 환자)에

속하는지를 나타냄

4개의 그룹을 나타내고 있으며 처음 3개의 샘플을 첫번째 그룹, 다음 4개는 두번째 그룹을 나타내는 식

샘플 데이터를 그룹으로 정렬할 필요는 없음


# create object

gkfold = GroupKFold(n_splits=3) # GroupKFold

groups = [0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 3, 3] # group class


# cv test

scores = cross_val_score(logreg, x, y, groups, cv=gkfold)


# cv test result

print('cv test score \n{}'.format(scores))

cv test score


#!/usr/bin/env python3


scikit-learn에서의 cross-validation(교차검증)


scikit-learn에서 교차 검증은 model_selection 모듈cross_val_score 함수로 구현되어 있음


1. iris 데이터셋에 cross-validation을 간단하게 적용하기

iris 데이터셋에 LogisticRegression을 평가해보면

LogisticRegression 이전 포스팅 참조하기: [1]Logistic Regression[1], [2]Logistic Regression[2]


# library import

from sklearn.model_selection import cross_val_score

from sklearn.datasets import load_iris

from sklearn.linear_model import LogisticRegression


# dataset

iris = load_iris()

logreg = LogisticRegression()


scores = cross_val_score(logreg, iris.data, iris.target, cv=5) # model, train, target, cross validation


print('cross-val-score \n{}'.format(scores))

print('cross-val-score.mean \n{:.3f}'.format(scores.mean()))


5겹 교차검증 결과와 평균


>> 교차 검증 평균값으로 우리는 이 모델의 정확도가 대략 96%일 것으로 기대할 수 있음

5겹 교차 검증이 만든 5개의 값을 모두 보면 100%에서 90%까지 폴드에 따라 비교적 차이가 큼

이는 모델이 훈련에 사용한 폴드에 매우 의존적이거나 데이터셋이 작기 때문일 수 있음



교차 검증의 장점은 train_test_split와 비교해보면

train_test_split는 데이터를 무작위로 나눔. 데이터를 무작위로 나눌 때

훈련 세트 ==>  분류하기 어려운 샘플

테스트세트 ==> 분류하기 쉬운 샘플

테스트 세트의 정확도는 비현실적으로 높게 나올 것입니다. 

반대의 경우라면 테스트 세트의 정확도는 비현실적으로 낮게 나옴


그러나 교차 검증을 사용하면 테스트 세트에 각 샘플이 정확하게 한번씩 들어가고

각 샘플은 폴드 중 하나에 속하며 각 폴드는 한번씩 테스트 세트가 됨 

그렇기 때문에 교차 검증의 점수를 높이기 위해서는 데이터 셋에 있는 모든 샘플에 대해 모델이 잘 일반화 되어야 함


데이터를 여러개로 나누면 모델이 훈련 데이터에 얼마나 민감한지 알 수가 있음 

iris데이터셋에서 90%~ 100%의 정확도를 얻음. 최악의 경우와 최선의 경우를 짐작할 수 있음


교차 검증의 주요 단점연산 비용이 늘어남

모델을 k개 만들어야 하므로 데이터를 한 번 나눴을때보다 대략 k배 더 느림



2. 계층별 k-겹 교차 검증과 그 외 전략들


# load library

import matplotlib.pyplot as plt

import matplotlib

from sklearn.datasets import load_iris

from sklearn.linear_model import LogisticRegression

from sklearn.model_selection import cross_val_score

import numpy as np


# data load

iris = load_iris()


print('iris label \n{}'.format(iris.target))

print('''iris label's bin \n{}'''.format(np.bincount(iris.target)))

iris label과 bin


>>첫번째 1/3은 클래스 0, 두번째 1/3은 클래스1 마지막 1/3은 클래스2 임을 확인할 수 있음

첫번째 폴드는 클래스 0만 가지고 있으므로, 첫 번째 반복에서 테스트세트는 클래스 0만을, 훈련세트는 클래스 1과 2만을 가지게 됨 

세번의 반복 모두 훈련 세트와 테스트 세트의 클래스가 다르므로 데이터셋에서 3-겹 교차 검증의 정확도는 0이 됨



단순한 k-겹 교차검증에는 문제가 있으니 stratified k-fold cross-validation계층별 k-겹 교차 검증을 사용함


# library import

import mglearn


# matplotlob 설정

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

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


mglearn.plots.plot_stratified_cross_validation()

plt.show()

클래스 레이블 순서대로 정렬된 데이터에서 기본 교차 검증과 계층별 교차 검증 비교


계층별 교차 검증은 폴드 안의 클래스 비율이 데이터셋의 클래스 비율과 같도록 데이터를 나눔

예를 들어 샘플의 90%가 클래스 A이고 10%가 클래스 B에 속한다면, 계층별 교차 검증은 각 폴드에 클래스 A샘플이 90%, 클래스 B 샘플이 10%가 되도록 만듬



2. 교차 검증 상세 옵션


그러나 scikit-learn에서는 cv 매개변수에 교차 검증 분할기cross-vailidation splitter를 전달함으로써 데이터를 분할할 때 더 세밀하게 제어할 수 있음

대부분의 경우 회귀에서는 k-겹 교차 검증, 분류에서는 계층별 k-겹 교차검증의 기본값이 잘 작동함


하지만 조금 다른 전략이 필요할 때가 있음. 예를 들어 다른사람의 결과를 재현하기 위해 분류 데이터셋에 기본 k-겹 교차 검증을 사용해야 할때

이렇게 하려면 먼저 model_selection에서 KFold 분할기를 임포트하고 원하는 폴드 수를 넣어 객체를 생성해야 함


# library import

from sklearn.model_selection import KFold


kfold = KFold(n_splits=5) # KFold 객체 생성

logreg = LogisticRegression() # 모델 객체 생성


for n in [3, 5]:

    kfold = KFold(n_splits=n)

    scores = cross_val_score(logreg, iris.data, iris.target, cv=kfold)

    print('n_splits={}, cross validation score: {}'.format(n, scores))

n_splits에 따른 교차 검증 테스트 점수

>> n_splits=3일 때 각 폴드는 iris 데이터셋의 클래스 중 하나에 대응하므로 아무것도 학습할 수가 없음

계층별 폴드를 만드는 대신 이문제를 해결하는 방법은 

데이터를 섞어서 샘플의 순서를 랜덤하게 만드는것(KFold의 shuffle 매개변수True)


데이터를 섞을 때 random_state를 고정해서 똑같은 작업을 재현할 수있음

그렇지 않으면 cross_val_score를 실행할 때마다 폴드가 바뀌어 매번 결과가 달라짐


for n in [3, 5]:

    kfold = KFold(n_splits=n, shuffle=True, random_state=0)

    scores = cross_val_score(logreg, iris.data, iris.target, cv=kfold)

    print('n_splits={}, cross validation score: {}'.format(n, scores))


shuffle과 random_stat매개변수를 조정한 교차 검증 테스트 점수

>> n_splits에 상관없이 결과가 유의미하다는 것을 확인할 수가 있음



'모델 평가와 성능 향상 > 교차 검증' 카테고리의 다른 글

다양한 교차 검증 방법  (0) 2018.04.03
cross-validation  (0) 2018.04.02

#!/usr/bin/env python3


cross-validation


0. 들어가기 전에

# library import

from sklearn.datasets import make_blobs

from sklearn.linear_model import LogisticRegression

from sklearn.model_selection import train_test_split


x, y = make_blobs(random_state=0)


x_train, x_test, y_train, y_test = \

  train_test_split(x, y,

                   random_state=0, test_size=0.3)


logreg = LogisticRegression().fit(x_train, y_train)

print('test score \n{:.3f}'.format(logreg.score(x_test, y_test))) # 0.900


데이터를 훈련 세트와 테스트 세트로 나누는 이유는  새로운 데이터에 모델이 얼마나 잘 일반화되는지 측정하기 위해서

모델이 훈련 세트에 잘 맞는 것보다 학습 과정에 없던 데이터에 대해 예측을 얼마나 잘하느냐가 중요


cross-validation교차검증은 데이터를 여러 번 반복해서 나누고 여러 모델을 학습

보통 5 or 10을 사용


5-겹 교차 검증을 하려면 먼저 fold폴드라고 하는 비슷한 크기의 'subset'다섯 개로 나누고, 모델들을 만듬

첫 번째 모델은 첫 번째 fold를 테스트 세트로 사용하고 나머지 (2에서 5까지) fold를 훈련 세트로 사용하여 학습

즉 이 모델은 fold 2~5까지의 데이터를 사용해 만들어지고 fold1을 사용해 정확도를 평가함


그 다음 두 번째 모델은 fold2를 테스트 세트로 사용하고 fold1, 3 ,4, 5의 데이터를 훈련 데이터로 사용

이런 방법으로 3, 4, 5를 테스트 세트로 사용해 반복

이렇게 데이터를 훈련 세트와 테스트 세트로 나누는 다섯 번의 분할마다 정확도를 측정하여, 결국 다섯 개의 정확도 값을 얻게 됨


# library import

import mglearn

import matplotlib.pyplot as plt


mglearn.plots.plot_cross_validation()

plt.show()

5-겹 교차 검증에서의 데이터 분할


#!/usr/bin/env python3


Use of expert knowledge


여행사를 위해 항공료를 예측해야 한다고 가정하면 휴가 성수기나 공휴일 근처에서는 항공료 가격이 올라감.

크리스마스같은 날짜가 고정되어 있는 기념일은 학습이 가능하지만 음력 날짜 같은 경우에는 그레고리안 달력으로 표현하기 힘듬. 그러나 이런 공휴일의 비행스케쥴이 기록된 특성을 추가하는 일은 어렵지 않음


자전거 대여소의 예제 데이터를 이용하여 집 앞에 있는 자전거를 사람들이 얼마나 대여할 것인지를 에측하기


뉴욕에서 시티바이크는 회원 가입 시스템과 함께 자전거 대여소를 운영,

자전거 대여 데이터는 익명으로 공개되어 있고 여러방식으로 분석되어 옴


우리가 풀려는 문제는 특정 날짜와 시간에 집 앞에 있는 자전거를 사람들이 얼마나 대여할 것인지를 예측하여

빌릴 자전거가 남아 있는지 알려고 함




1. citibike 데이터의 형태와 시각화

### library import

from mglearn.datasets import load_citibike

import matplotlib

import matplotlib.pyplot as plt

import pandas as pd

import numpy as np


### matplotlib 설정

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

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


### datasets

citibike = load_citibike()

print('citibike.head() \n{}'.format(citibike.head()))

citibike.head()

>> indexdatetime형태로 이루어져 있으며 형식은 %Y-%m-%d 00:00:00이며

Freq: 3H ==> 3시간 주기로 데이터가 기록되었으며

data의 열이름은 'starttime', 'one' 이며 one에는 대여된 자전거의 횟수가 기록됨을 알 수 있음



2. 시각화를 위한 전처리

print('citibike.index.min() \n{}'.format(citibike.index.min()))

print('citibike.index.max() \n{}'.format(citibike.index.max()))

xticks = pd.date_range(start=citibike.index.min(), end=citibike.index.max(), freq='D')

print('xticks \n{}'.format(xticks))

# 그래프의 축을 만들기 위해 freq='D'로 설정하여 일별데이터로 수정


citibike의 index전처리



# strftime('%w'): datatime에서 일요일 ~ 토요일을 인덱스 0~6까지 부여

# strftime(' %m-%d'): datatime에서 '월', '일' 뽑아내기

print('''xticks.strftime(%w) \n{}'''.format(xticks.strftime('%w'))) 

print('''xticks.strftime( '%m-%d') \n{}'''.format(xticks.strftime(' %m-%d'))) 

x축 데이터를 위한 전처리


week = ['sun', 'mon', 'tue', 'wed', 'thr', 'fri', 'sat']

# xticks.strftime('%w')의 결과는 str형태이기 때문에 int()를 이용하여 숫자로 변환

xticks_name = [week[int(w)] + d for w, d in zip(xticks.strftime('%w'), xticks.strftime(' %m-%d'))] # list 축약

print('xticks_name \n{}'.format(xticks_name))

x축 데이터를 위한 xticks_name




3. citibike의 시각화

plt.xticks(xticks, xticks_name,rotation=90, ha='left'# ha: horizon align; 수평정렬 

plt.plot(citibike, lw=1, c='darkblue'# lw = linewidth, c = colors

plt.xlabel('date')

plt.ylabel('count')

plt.show()

한 시티바이크 대여소에서 한달간 자전거가 대여된 횟수


>> 데이터를 보면 24시간 간격으로 낮과 밤을 확실히 구분 가능, 주중과 주말의 패턴도 구분이 가능

시계열 데이터를 이용한 예측 작업은 과거 데이터에서 학습하여 미래를 예측하는 방식을 사용

즉 데이터를 train set와 test set로 나눌 때 어떤 날짜까지의 모든 데이터를 train data로 하고, 날짜 이후의 모든 데이터를 test set로 사용

23일과 나머지 8일을 train set 와 data set로 사용



4. citibike 데이터의 기계학습과 시각화


pandas의 DatetimeIndex는 나노초이기 때문에 10**9으로 나눠줘야함

이 작업에서 우리가 사용할 특성은 일정 횟수의 대여가 일어난 날짜와 시간뿐

즉 입력 특성은 '2015-08-01 00:00:00'같은 날짜이고 출력은 대여횟수


y = citibike.values # target

x = citibike.index.astype('int64').values.reshape(-1, 1) // 10**9 # datetime ==> int64 ==> 값만 가지고오기 ==> 1열의 데이터로 ==> scale조정


### library import

from sklearn.ensemble import RandomForestRegressor


### train 갯수

n_train = 8 * 23


### data 분할

x_train, x_test, y_train, y_test = \

  x[:n_train], x[n_train:], y[:n_train], y[n_train:]


### 모델 적용  

rfr = RandomForestRegressor(n_estimators=100, random_state=0)

rfr.fit(x_train, y_train)

y_pred = rfr.predict(x_test)

y_pred_train = rfr.predict(x_train)


### 유효성 평가

print('R-Square \n{:.3f}'.format(rfr.score(x_test, y_test))) # -0.035


### visualization

plt.xticks(range(0, len(x), 8), xticks_name, rotation=90, ha='left'# ha = holizon align 

plt.plot(range(n_train), y_train, label='train')

plt.plot(range(n_train, len(y_test) + n_train), y_test, ls=':', label='test'# plt.plot(x, y, linestyles, label)

plt.plot(range(n_train), y_pred_train, ls='--', label='train test') # ls = linestyles

plt.plot(range(n_train, len(y_test) + n_train), y_pred, ls='--', label='test pred'

plt.legend(loc=2)

plt.xlabel('date')

plt.ylabel('count')

plt.show()


POSIX 시간만 사용하여 만든 랜덤 포레스트의 예측

>> 테스트 세트에 대해선 한 가지 값으로만 예측

R-Square 값은 -0.035로 아무것도 학습되지 않음

테스트 세트에 있는 POSIX시간 특성의 값은 훈련 세트에 있는 모든 데이터보다 뒤의 시간

트리 모델인 랜덤포레스트는 훈련 세트에 있는 특성의 범위 밖으로 extrapolation외삽할 수 있는 능력이 없음


데이터 그래프를 보면 시간요일이라는 두 요소가 중요한 것으로 판단 <== 데이터 분석가의 판단

POSIX 시간으로 아무것도 학습되지 않으므로 이 특성은 제외


이 로직을 계속 사용할 것이기 때문에 함수로 만듬

RandomForest는 이전 포스팅 참조: [1]Random Forest -- intro, [2]Random Forest


# n_train = 8 * 23

# model = RandomForestRegressor(n_estimators=100, random_state=0)

def eval_feature(features, target, model):

    # data 분할

    x_train, x_test, y_train, y_test = \

      features[:n_train], features[n_train:], target[:n_train], target[n_train:]


    # model 적용

    model.fit(x_train, y_train)

    y_pred = model.predict(x_test)

    y_pred_train = model.predict(x_train)

    

    # 유효성 평가

    print('R-Square \n{:.3f}'.format(model.score(x_test, y_test)))


    # visualization

    plt.xticks(range(0, len(x), 8), xticks_name, rotation=90, ha='left')

    plt.plot(range(n_train), y_train, label='train')

    plt.plot(range(n_train, len(y_test) + n_train), y_test, ls=':', label='test')

    plt.plot(range(n_train), y_pred_train, ls='--', label='train test')

    plt.plot(range(n_train, len(y_test) + n_train), y_pred, ls='--', label='test pred')

    plt.legend(loc=2)

    plt.xlabel('date')

    plt.ylabel('count')

    plt.show()


5. 데이터의 x축 중 시간 요소만 출력하여 분석

# x = citibike.index.astype('int64').values.reshape(-1, 1) // 10**9

x_hours = citibike.index.hour.values.reshape(-1, 1) # 시간만 출력

eval_feature(x_hours, y, model) # 사용자 정의 함수


시간만 사용하여 만든 랜덤 포레스트의 예측// R-square = 0.600

>> R-square가 나아졋지만 일별 패턴은 예측하지 못함



6. 시간요소에 요일정보를 더해서 분석

# print(citibike.index.dayofweek) : 일요일부터 토요일까지를 0~6까지로 인덱싱 

### 시간데이터에 요일 정보를 추가

x_hours_week = np.hstack([citibike.index.dayofweek.values.reshape(-1, 1),

                          citibike.index.hour.values.reshape(-1 ,1)])

eval_feature(x_hours_week, y, model)


시간과 요일 특성을 사용해 만든 랜덤 포레스트의 예측//R-square = 0.842


>> 이제 모델은 하루의 시간과 요일에 따른 주기적인 패턴을 따르고 있음. R-square는 0.842로 좋은 예측을 나타냄

이 모델이 학습한 것은 8월 23일까지 요일별, 시간별 평균 대여 횟수

랜덤 포레스트 회귀로 만든 예측은 여러 트리가 예측한 값들의 평균이므로

그래서 8월 24일 월요일의 예측값과 8월 31일의 예측값은 동일

이런 작업에서 랜덤 포레스트 같이 복잡한 모델이 필요한 것은 아니므로 더 간단한 모델인 LinearRegression을 적용해보면



7. 시간요소에 요일정보를 더해서 LinearRegression을 적용

LinearRegression은 이전 포스팅 참조: [1]Linear Regression -- intro, [2]LinearRegression ,[3]Ridge


# library import

from sklearn.linear_model import LinearRegression, Ridge

eval_feature(x_hours_week, y, LinearRegression())

시간과 요일 특성을 사용하여 만든 선형 회귀의 예측, R-square=0.132


>> LinearRegression성능은 훨씬 나쁘고 주기적인 패턴도 다르게 나타남

그 이유는 요일시간정수로 인코딩 되어있어서 연속형 변수로 해석되기 때문임. 선형모델은 시간을 선형 함수로 함수로만 학습할 수 있어서, 시간이 흐를수록 대여 수가 늘어나게 학습됨


이 패턴을 잡아내기 위해 연속형 변수를 범주화하기 위해 OneHotEncoder를 사용


8. OneHotEncoder를 적용한 Ridge분석


# library import

from sklearn.preprocessing import OneHotEncoder


# encoder load

encoder = OneHotEncoder()

encoder.fit(x_hours_week)

x_hours_week_encoder = encoder.transform(x_hours_week).toarray() # ndarray로 전환


eval_feature(x_hours_week_encoder, y, Ridge())


시간과 요일의 원-핫 인코딩을 사용해 만든 선형 회귀의 예측, R-Square = 0.619


>> 연속형 특성일 때보다 훨씬 좋아짐

이 선형 모델은 요일에 대해 하나의 계수를 학습하고 시간에 대해서도 하나의 계수를 학습

이 말은 시간 패턴이 모든 모든 날에 걸쳐 공유된다는 뜻임


9. 상호작용 특성을 사용하여 Ridge분석

상호작용 특성을 사용하면 시간과 요일의 조합별 계수를 학습할 수 있음


상호작용 특성은 이전 포스트 참조: [1]polynomial analysis with boston house price


# library import

from sklearn.preprocessing import PolynomialFeatures


poly_transformer = PolynomialFeatures(degree=2, interaction_only=True, include_bias=False# include_bias=왼쪽에 절편항 추가

poly_transformer.fit(x_hours_week_encoder) # 인코더 학습

x_hours_week_encoder_poly = poly_transformer.transform(x_hours_week_encoder) #  인코더를 데이터에 적용


ridge = Ridge()

eval_feature(x_hours_week_encoder_poly, y, ridge)


시간과 요일의 곱을 특성으로 사용해서 만든 선형 회귀의 예측R-Square = 0.845


>> 이런 특성변환을 통해 모델의 성능이 랜덤 포레스트와 거의 비슷해짐

이 모델의 가장  큰 장점은 무엇이 학습되었는지가 명확하다는 것

각 날짜와 시간에 대해 하나의 계수를 학습

랜덤 포레스트와는 달리 선형 모델은 모델이 학습한 계수를 그래프로 나타낼 수 있음


hour = ['{:02d}:00'.format(i) for i in range(0, 24, 3)]

day = ['mon', 'tue', 'wed', 'thr', 'fri','sat', 'sun']

features = day + hour


### get_featrues_names 메소드를 통해 PolynomialFeatures로 추출한 상호작용 특성에 이름을 달아주고 계수가 0이 아닌 특성만 선형


features_poly = poly_transformer.get_feature_names(features)

features_nonzero = np.array(features_poly)[ridge.coef_ != 0]

coef_nonzero = ridge.coef_[ridge.coef_ != 0]


x_axis_data = np.arange(len(coef_nonzero))

plt.scatter(x_axis_data, coef_nonzero)

plt.xticks(x_axis_data, features_nonzero, rotation=90)

plt.xlabel('feature name')

plt.ylabel('coefficient size')

plt.show()


시간과 요일의 곱을 사용한 선형 모델의 계수

선형 모델은 구간 분할이나 다항식과 상호작용 특성을 새로 추가해 큰 이득을 볼 수 있음

반면에 랜덤 포레스트나 SVM같은 비선형 모델은 특성을 늘리지 않고서도 복잡한 문제를 학습가능

#!/usr/bin/env python3


feature selection based model(모델 기반 특성 선택)


0. 살펴보기

feature selection based model모델 기반 특성 선택지도 학습 machine learning 모델을 사용하여

1. 특성의 중요도를 평가

2. 가장 중요한 특성들만 선택함


특성 선택에 사용하는 지도 학습 모델은 최종적으로 사용할 지도 학습 모델과 같을 필요는 없음

특성 선택을 위한 모델은 각 특성의 중요도를 측정하여 순서를 매길 수 있어야


decision tree는 각 특성의 중요도가 담겨있는 feature_importances_를 제공

decision tree : [1]Decision Tree


선형 모델 계수의 절댓값도 특성의 중요도를 평가하는 데 사용할 수 있음

L1규제를 사용한 선형 모델은 일부 특성의 계수만 학습(Lasso)

이는 그 모델 자체를 위해 특성이 선택된다고 생각할 수 있지만,

다른 모델의 특성 선택을 위해 전처리 단계로 사용할 수도 있음


Lasso : Lasso



단변량 분석과는 반대로 feature selection based model

한 번에 모든 특성을 고려하므로(사용된 모델이 상호작용을 잡아낼 수 있다면)

상호작용 부분을 반영할 수 있음


SelectFromModel은 (지도 학습 모델로 계산된) 중요도가 지정한 임계치보다 큰 모든 특성을 선택

이는 매우 복잡한 모델이고 단변량분석보다 훨씬 강력한 방법



1. breast cancer 데이터를 통한 단변량 분석과 모델 기반 특성 선택의 비교

### library import

from sklearn.datasets import load_breast_cancer

from sklearn.feature_selection import SelectFromModel, SelectPercentile

from sklearn.ensemble import RandomForestClassifier

from sklearn.linear_model import LogisticRegression

from sklearn.model_selection import train_test_split

import matplotlib.pyplot as plt

import matplotlib

import numpy as np


### matplotlib 한글, 축 - 설정

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

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


### datasets

cancer = load_breast_cancer()

# print(cancer.data.shape)


### noise

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

noise = rnd.normal(size=(cancer.data.shape[0], 70))


### np.hstack

cancer_data_noise = np.hstack([cancer.data, noise])


### data 분할

x_train, x_test, y_train, y_test =\

  train_test_split(cancer_data_noise, cancer.target,

                   random_state=0, test_size=0.3)


### 특성을 고를 model 적용

randforest = RandomForestClassifier(n_estimators=100, random_state=0)

select_model = SelectFromModel(randforest, threshold='median').fit(x_train, y_train)


select_uni = SelectPercentile(percentile=50).fit(x_train, y_train)



### visualization

models = [select_uni, select_model]

_, axes = plt.subplots(3, 1)


ones = np.ones(x_train.shape[1], dtype=np.bool)

axes[0].matshow(ones.reshape(1, -1), cmap='gray')

axes[0].set_yticks([])

axes[0].set_title('full feature')


for model, ax in zip(models, [axes[1], axes[2]]):

    x_train_model = model.transform(x_train)

    x_test_model = model.transform(x_test)


    idx = model.get_support()

    ax.matshow(idx.reshape(1, -1), cmap='gray')

    ax.set_yticks([])

    ax.set_title('{}'.format(model.__class__.__name__))

plt.show()


특성의 전체 선택, 단변량 선택, 모델기반선택으로 선택한 특성



### 일반화

logreg = LogisticRegression().fit(x_train, y_train)

score = logreg.score(x_test, y_test)

print('total 특성의 정확도: {:.3f}'.format(score))

models = [select_uni, select_model]

names = ['univariate', 'model_based']

for model, name in zip(models, names):

    x_train_model = model.transform(x_train)

    x_test_model = model.transform(x_test)

    logreg = LogisticRegression().fit(x_train_model, y_train)

    score = logreg.score(x_test_model, y_test)

    print('{}의 정확도: {:.3f}'.format(name, score))

각 모델별 정확도


#!/usr/bin/env python3


univariate analysis(단변량 분석)


0.살펴보기

univariate analysis단변량 통계에서는

1. 개개의 특성target 사이에 중요한 통계적 관계가 있는지를 계산

2. 깊게 관련되어 있다고 판단되는 특성을 선택


분산분석 : 데이터를 클래스별로 나누어 평균을 비교하는 방법

분산분석으로 계산한 어떤 특성의 F-통계량이 높으면 그 특성은 클래스별 평균이 서로 다르다는 뜻

분류에서는 ANOVA, analysis of variance분산분석


사용하는 분포는 F-distribution

0. 기본적인 형태는 group(1) / group(2), 큰 값을 분자로 사용하여 최솟값을 1이 되게함

1. H0 = 클래스들의 평균과 분산이 같다

2. H0상태에서의 F-distribution 그래프를 그림

3. F-통계량이 1(좌측)에 가까운 수가 나올수록 두 집단은 비슷한 평균과 분산을 갖음

4. F-통계량이 1에 가까울수록 p-value의 값은 커짐 ==> target에 미치는 영향이 적다고 판단

5. p-value는 pvalues_에 저장됨

6. scikit-learn의 SelectBest, SelectPercentile에서 특성을 선택하는 기준은 F-통계값

7. F-통계값은 scores_에 저장됨


이 방법의 핵심 요소는 univariate, 즉 각 특성이 독립적으로 평가됨

==> 따라서 다른 특성과 깊게 연관된 특성은 선택되지 않을 것으로 예상 가능


이 방법의 특징은

1. 계산이 매우 빠르고 평가를 위해 모델을 만들 필요가 없음

2. 특성을 선택한 후 적용하려는 모델에 상관없이 사용 가능


scikit-learn에서 univariate로 특성을 선택하려면 

1. 분류에는 f_classif(default)를, 회귀에서는 f_regression을  선택하여 테스트

2. 계산한 p-value에 기초하여 특성을 제외하는 방식을 선택. 


이런 방식들은 매우 높은 p-value를 가진(target값과 연관성이 적은) 특성을 제외할 수 있도록 critical value를 조정하는 parameter를 사용

critical value를 계산하는 방법중 하나인 SelectKBest는 고정된 k개의 특성을 선택하고 SelectPercentile은 지정된 비율만큼 특성을 선택



1. cancer data에서 univariate analysis(단변량 분석)하기

### library import

from sklearn.datasets import load_breast_cancer

from sklearn.feature_selection import SelectPercentile

from sklearn.model_selection import train_test_split

import numpy as np

import matplotlib

import matplotlib.pyplot as plt


### matplotlib 한글출력, 축- 설정

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

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


### dataload

cancer = load_breast_cancer()


### preprocessing

rng = np.random.RandomState(seed=0) # random seed

noise = rng.normal(size=(cancer.data.shape[0], 50)) # normal분포에서 cancer.data.shape[0]x50의 데이터추출

cancer_data_noise = np.hstack([cancer.data, noise]) # data 병합

print('cancer_data_noise.shape \n{}'.format(cancer_data_noise.shape)) # (569, 80)


### data 분할

x_train, x_test, y_train, y_test = \

  train_test_split(cancer_data_noise, cancer.target,

                   test_size=0.5, random_state=0)


### preprocessing 메소드 호출 및 적용

select = SelectPercentile(percentile=50).fit(x_train, y_train) # percentile: 50%만 선택

x_train_select = select.transform(x_train) # 적용

x_test_selected = select.transform(x_test)


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

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

합친 데이터와 선택된 데이터의 형태

>>  특성의 갯수가 80에서 40개로 줄음



2. 어떤 특성이 선택되었는지 확인


idx = select.get_support()[각주:1]

print('idx \n{}'.format(idx))

논리값으로 만들어진 idx


plt.matshow(idx.reshape(1, -1), cmap='gray_r')

plt.yticks([]) # 축눈금없애기

plt.xlabel('feature number')

plt.show()


SelectPercentile이 선택한 특성


>> 마스킹된 그래프에서 볼 수 있듯이 선택된 특성은 대부분 원본특성이며, 노이즈 특성은 거의 제거됨을 볼 수 있음




3. 전체 특성을 이용했을 때와 선택된 특성만 사용했을 때 로지스틱 회귀 성능 비교


Logistic Regression 링크 참조

[1]Logistic Regression[1], [2]Logistic Regression[2]


### library import

from sklearn.linear_model import LogisticRegression


### model 호출

lr = LogisticRegression()


### 전체특성을 사용했을 때 정확도

lr.fit(x_train, y_train)

print('전체 특성을 사용한 점수: \n{:.3f}'.format(lr.score(x_test, y_test))) # 0.937


### 일부특성을 사용했을 때 정확도

lr.fit(x_train_select, y_train)

print('선택된 특성을 사용한 점수 \n{:.3f}'.format(lr.score(x_test_selected, y_test))) # 0.951


>> 이 경우에는 일부 원본 특성이 없더라도 노이즈 특성을 제거한 쪽의 성능이 더 높음

이 예는 인위적으로 간단하게 만들었고 실제 데이터에서는 엇갈리는 경우도 많음

너무 많은 특성 때문에 모델을 만들기가 현실적으로 어려울 때 univariate를 사용하거나,

많은 특성들이 확실히 도움이 안된다고 생각할 때 사용

  1. get_support(): 선택된 특성을 논리값으로 표시 [본문으로]

#!/usr/bin/env python3


feature auto-selection --intro


새로운 특성을 만드는 방법이 많으므로 데이터의 차원이 원복 특성의 수 이상으로 증가

따라서 모델은 더 복잡해지고 overfitting될 가능성도 높아짐

새로운 특성을 추가하거나, 고차원 데이터셋을 사용할 때 가장 유용한 특성만 선택하여 차원을 줄이는 것이 좋음

이렇게 하면 모델이 간단해지고 일반화 성능이 올라감


어떤 특성이 좋은지 아는 방법에는 크게 3가지가 있음

1. univariate statics일변량통계

2. model-based selection모델기반선택

3. iterative selection반복적 선택


이 방법들은 모두 지도학습이므로 target값이 필요하며

train set에만 특성 선택에 사용해야함

#!/usr/bin/env python3


수학함수를 이용한 비선형 변환


제곱항이나 세제곱 항을 추가하면 linear regression model의 성능을 향상시킬 수 있음

한편 log, sin, exp 같은 함수를 적용하는 것도 특성 변환에 유용


tree 모델은 max_feature(트리의 분기에서 사용될 후보 특성의 갯수)가 주요 parameter


각 모델의 특징은 이전 포스팅 참조


random forest의 기본값은 'auto'로 특성 갯수의 제곱근을 나타내기때문에 나열 순서가 결과의 영향을 줄 수 있지만

decision tree와 gradient boosting tree의 기본값은 'None'으로 전체 특성을 모두 사용하기 때문에 순서에 상관없음

그러나 max_feature='auto'로 설정하면 random forest 처럼 특성의 나열 순서에 영향을 받을 수 있음


linear modelneural network는 각 특성의 스케일과 분포에 밀접하게 연관

특성과 target값 사이에 비선형이 있다면 특히 linear regression에서는 모델을 만들기가 어려움

log와 exp함수는 데이터 스케일을 변경하여 linear model과 neural network의 성능을 올릴 수가 있음


sin, cos함수주기적인 패턴이 들어 있는 데이터를 다룰 때 사용


대부분의 모델은 gaussian distribution을 기본으로 하여 만들어졌기 때문에 각 특성이 gaussian distribution과 비슷할 때 최고의 성능을 나타냄


logexp같은 함수는 gaussian dist.모양을 만드는데 쉽고 효과적임

이런 변환은 정수형 데이터를 다룰 때 유효

카운트 데이터(음이 아닌 정수)는 특별한 통계 pattern을 따르는 경우가 많음



1. 인위적 카운트 데이터를 생성하여 비선형 변환 적용


# library import

import numpy as np

import matplotlib.pyplot as plt

import matplotlib


# matplotlib 한글/ 축- 설정

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

plt.rcParams['axes.unicode_minus']


# datasets

rnd = np.random.RandomState(seed=0) # random seed

x_org = rnd.normal(size=(1000,3)) # 정규분포

w = rnd.normal(size=3) # 정규분포


x = rnd.poisson(lam=np.exp(x_org)) # poisson distribution, lam = lambda

y = np.dot(x_org, w) # x_org(1000,3) , w(3,)이므로 (1000,)형태의 데이터가 만들어짐

print('x[:10, 0] \n{}'.format(x[:10, 0]))

print('특성 종류 \n{}'.format(np.unique(x[:, 0])))

print('특성 빈도 \n{}'.format(np.bincount(x[:,0])))


x의 개략적인 구조 확인


# visualization

_, axes = plt.subplots(1, 3)

for i, ax in enumerate(axes.ravel()):

    bins = np.bincount(x[:, i])

    ax.bar(range(len(bins)), bins, color='grey')

    ax.hlines(y=263, xmin=-1, xmax=len(bins)+1, alpha=0.4, linestyles=':', colors='red')

    ax.hlines(y=129, xmin=-1, xmax=len(bins)+1, alpha=0.4, linestyles=':', colors='red')

    ax.set_xlabel('x의 {}번째 특성'.format(i+1))

    ax.set_ylabel('frequency')

plt.show()


x의 특성별 histogram


>> bincount는 0부터 시작하고 큰 값의 수는 빠르게 줄어들음



2. poisson distribution에 여러가지 모델 적용

이런 종류의 분포(작은 수치는 많고, 큰값은 적은)는 많이 나타나지만 선형 모델은 이런 데이터를 잘 처리하지 못함


# library import: Ridge는 위의 링크 참조

from sklearn.linear_model import Ridge

from sklearn.model_selection import train_test_split


# data 분할

x_train, x_test, y_train, y_test =\

  train_test_split(x, y,

                   random_state=0, test_size=0.3)


# model 유효성 평가

for alpha in [0.001, 1, 1000]:

    ridge = Ridge(alpha=alpha).fit(x_train, y_train)

    print('\nalpha={}, {}의 정확도 {:.3f}'.format(alpha, ridge.__class__.__name__,ridge.score(x_test, y_test)))


alpha값에 따란 Ridge의 정확도

### 정확도가 51.3%로 좋지 못하지만, 규제를 풀수록(alpha가 커질수록) 정확도가 낮아지는 것을 확인


### log scaling

x_train_log = np.log(x_train + 1) # 0이 있으면 Inf가 나오기 때문에 1을 더함

x_test_log = np.log(x_test + 1)


###  visualization

_, bins = np.histogram(x_train_log, bins=10)

_, axes = plt.subplots(1, 3)

for i, ax in enumerate(axes.ravel()):

    ax.hist(x_train_log[:, i], bins=bins, color='darkblue')

    ax.hlines(y=200, xmin=-1, xmax=len(bins)+1, alpha=0.4, linestyles=':', colors='red')

    ax.hlines(y=100, xmin=-1, xmax=len(bins)+1, alpha=0.4, linestyles=':', colors= 'red')

    ax.set_xlabel('logx의 {}번째 특성'.format(i+1))

    ax.set_ylabel('frequency')

plt.show()


로그 변환 후 x 특성 값의 histogram


>> 데이터셋과 모델의 조합에 최적인 변환 방법을 찾는것은 예술에 가까운 일

이 예에서는 모든 특성이 같은 속성을 가지고 있으나 실제로 이런 경우는 드물며,

실제로는 일부 특성만 변환하거나 특성마다 모두 다르게 변환

이렇게 특성 변환은 선형 모델에서는 필수


구간 분할, 다항식, 상호작용(특성곱)은 데이터가 주어진 상황에서 모델의 성능에 큰 영향을 줄 수 있음

tree모델 스스로 중요한 상호작용을 찾아낼 수 있고, 대부분의 경우 데이터를 변환하지 않아도 됨

SVM, K-NN, neural network모델은 선형 모델보다는 영향이 뚜렷하지 않음

#!/usr/bin/env python3


polynomial analysis with boston house price


1. 실제 데이터로 polynomial 분석



Ridge 참조


Random Forest 참조


### load library

from sklearn.datasets import load_boston

from sklearn.model_selection import train_test_split

from sklearn.preprocessing import MinMaxScaler, OneHotEncoder, PolynomialFeatures

from sklearn.linear_model import Ridge

from sklearn.ensemble import RandomForestRegressor


### data load

boston = load_boston() 

# print(boston.keys())

x_train, x_test, y_train, y_test = \

  train_test_split(boston.data, boston.target,

                   test_size=0.3, random_state=0)


### scale 조정

scaler_mms = MinMaxScaler().fit(x_train) # scaler 호출

x_train_scaled = scaler_mms.transform(x_train) # scaler를 x_train에 적용

x_test_scaled = scaler_mms.transform(x_test) # scaler를 x_test를 적용


### model

randfor = RandomForestRegressor(n_estimators=100, random_state=0, n_jobs=-1) # n_jobs = 코어의 수(-1 : all)

ridge = Ridge()

models = [randfor, ridge]


### model 적용 및 성능 테스트

for degree in range(1, 6):

    poly = PolynomialFeatures(degree=degree, include_bias=False).fit(x_train_scaled)# include_bias = True: x_train_scaled 맨 왼쪽에 상수항 추가. 

    x_train_poly = poly.transform(x_train_scaled)

    x_test_poly = poly.transform(x_test_scaled)

    print('degree={}'.format(degree))

    print('x_train.shape={}'.format(x_train.shape))

    print('x_train_poly.shape={}\n'.format(x_train_poly.shape))

    print('원래 특성과 polynomial 한 특성의 수 \n==> origin:{}, poly:{}\n'.format(

        boston.data.shape[1], len(poly.get_feature_names())))


    for model in models:

        conf = model.fit(x_train_scaled, y_train)

        print('교차항이 없을 때 {} 점수: {:.3f}'.format(

            model.__class__.__name__, model.score(x_test_scaled, y_test))) # __class__.__name__ : class이름 호출

        conf = model.fit(x_train_poly, y_train)

        print('degree={}, 교차항이 있을 때 {} 점수{:.3f}\n'.format(

            degree, model.__class__.__name__, model.score(x_test_poly, y_test)))

    ptrint('----------------------------------------------')


모델 성능 테스트 평가의 일부분

교차항이 있을 때 Ridge모델은 성능이 상승하지만

복잡한 모델인 RandomForestRegressor경우에는 정확도가 큰차이가 나지 않음

degree=3의 경우

중복조합을 이용

원래 특성 13개 + x0 ~ x12 중 중복을 허락하여 2개를 조합(91개) + x0 ~ x12 중 중복을 허락하여 3개를 조합(455개)를 다합친 559개가 특성으로 사용됨


'데이터 표현과 특성 > interaction과 polynomial' 카테고리의 다른 글

interaction과 polynomial  (0) 2018.03.28

#!/usr/bin/env python3


interaction과 polynomial


특별히 특성을 다양하게 나타내는 방법은 원본 데이터interactionpolynomial을 추가하는 것

이런 방법은 머신러닝에도 많이 적용


구간별 선형 모델은 인위적인 wave 데이터셋의 각 구간에 대해 상수값을 학습하지만 linear 모델은 절편뿐만 아니라 기울기도 학습 가능


선형 모델에 기울기를 추가하는 방법은 구간으로 분할된 데이터에 원래특성을 다시 추가

wave 데이터셋의 경우 10개의 구간을 나누면 원래 특성 + 10 = 11차원 데이터셋이 만들어짐



1. wave data(인위적 데이터셋)에 특성 추가하기

### import library

import matplotlib.pyplot as plt

import matplotlib

from sklearn.preprocessing import OneHotEncoder

from mglearn.datasets import make_wave

import numpy as np


### matplotlib 설정

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

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


### datasets

x , y = make_wave(n_samples=100)

# print(x)

# print(y)


### binding

bins = np.linspace(-3, 3, num=11)

# print(bins)


which_bins = np.digitize[각주:1](x, bins=bins) # bins: 구간

# print(x[:5])

# print(which_bins)


encoder = OneHotEncoder(sparse=False).fit(which_bins) # OneHotEncoder 메소드 호출 후 적용

x_binned = encoder.transform(which_bins)

print('x_binned[:5] \n{}'.format(x_binned[:5]))

wave데이터의 x에서 one-hot-encoding을 적용한 데이터의 일부


### import library

from sklearn.linear_model import LinearRegression


### model 호출 후 OHE(One Hot Encoding)된 데이터 적용

lreg = LinearRegression().fit(x_binned, y)


### 축 생성

line = np.linspace(-3, 3, num=1000, endpoint=False).reshape(-1, 1)


### np.digitize(line, bins=bins) : line의 각 데이터 포인트가 bins의 어디 구간인지 표현 ==> 1~10 까지 숫자

### encoder.transform(np.digitize(line, bins=bins)) : 1 ~ 10까지를 OHE로 표현 ex) 1 => [1, 0, 0, 0, 0, 0, 0, 0, 0, 0]  // 3 => [0, 0, 1, 0, 0, 0, 0, 0 ,0 ,0]

line_binned = encoder.transform(np.digitize(line, bins=bins))


### predict

lreg_pred = lreg.predict(line_binned) 

### 각 구간에서는 같은 특성(ex)[0, 1, 0, 0, 0 ,0 ,0 ,0 ,0 ,0])을 가지기 때문에 각 구간에서는 일정한 값으로 예측됨


### 구간에서 기울기 값 추가

x_combined = np.hstack([x, x_binned]) # 원래 특성x와 구간별로 OHE된 리스트를 결합

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


line_combined = np.hstack([line, line_binned])


### model 호출 후 결합한 데이터를 적용

lreg_comb = LinearRegression().fit(x_combined, y)


### predict

pred_lreg_comb = lreg_comb.predict(line_combined)


### visualization

plt.scatter(x[:, 0], y, marker='o', c='k'# c = color

plt.plot(line, lreg_pred, label='binding linear Reg', ls='--', lw=3) # ls = linestyles,  lw= linewidths

plt.plot(line, pred_lreg_comb, label='plus original feature')

plt.vlines(bins, ymin=-3, ymax=3, lw=1, alpha=0.2, linestyles=':'# lw = linewidths

plt.ylabel('output regression')

plt.xlabel('input feature')

plt.legend(loc=2) # loc = 1, 2, 3, 4 : 오른쪽 위, 왼쪽 위, 왼쪽 아래, 오른쪽 아래

plt.show()

구간으로 분할된 특성과 하나의 기울기를 사용한 linear regression


이 모델은 각 구간의 절편과 기울기를 학습

학습된 기울기는 음수이고 모든 구간에 걸쳐 동일

x축 특성이 하나이므로 기울기도 하나이며 모든 구간에서 동일




2. wave data(인위적 데이터셋)에 기울기 특성 추가하기(1)


각 구간에서 다른 기울기를 가지게 하기 위해 데이터 포인트가 있는 구간과

x축 사이의 상호작용 특성을 추가 가능(구간 특성과 원본 특성의 곱)하여 기울기를 표현


### data 축 추가

x_product = np.hstack([x_binned, x * x_binned])

line_proudct = np.hstack([line_binned, line*line_binned])

print(x_product.shape) # (100, 20)

### x_binned는 데이터 포인트가 속한 구간의 one-hot-encoding이므로 x를 곱하면 해당 구간이외에는 모두 0


### 모델 호출 후 결합한 데이터 적용

linear_reg = LinearRegression().fit(x_product, y)

pred_linear_reg = linear_reg.predict(line_proudct)


### visualization

plt.plot(line, pred_linear_reg, label='original feature를 곱한 linear regression')


for bin in bins:

    plt.plot([bin, bin], [-3, 3], ls=':', c='k', lw=1, alpha=0.4)


plt.scatter(x[:, 0], y, marker='o', c='k')

plt.ylabel('output regression')

plt.xlabel('input feature')

plt.legend(loc=2)

plt.show()

구간별 기울기가 다른 linear regression




3. wave data(인위적 데이터셋)에 기울기 특성 추가하기(2)

### 구간 나누기는 연속형 특성을 확장하는 방법 중 하나

### 특성 x가 주어지면 x**2, x**3, x**4 ... 등을 시도 가능하여 여러차원으로 까지 늘릴 수 있음

### preprocessing의 PolynomialFeatures에 구현되어 있음


from sklearn.preprocessing import PolynomialFeatures


### degree: x**10까지, include_bias=True: 절편을 그리기 위해 값이 1인 특성을 왼쪽에 추가

poly = PolynomialFeatures(degree=10, include_bias=False) 

poly.fit(x)


x_poly = poly.transform(x)


# 10차원을 사용했으므로 10개의 특성이 만들어짐

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

print('x[:5] \n{}'.format(x[:5]))

print('x_poly[:5] \n{}'.format(x_poly[:5]))

print('poly.get_feature_names() \n{}'.format(poly.get_feature_names()))

만들어진 특성의 모양


### 다항식 특성을 선형 모델과 함께 사용하면 전형적인 polynomial regression다항 회귀모델이 됨


SVR 은 아래 링크 참조

Kernelized Support Vector Machines


### viusalization

### 다항식을 결합한 특성을 적용

poly_reg = LinearRegression().fit(x_poly, y)

line_poly = poly.transform(line)

pred_poly_reg = poly_reg.predict(line_poly)


plt.plot(line, pred_poly_reg, label='polynomial linear regression')

plt.scatter(x[:, 0], y, marker='o', c='k')

plt.xlabel('input feature')

plt.ylabel('output regression')

plt.vlines(bins, ymin=-3, ymax=3, linestyles=':', colors='grey', alpha=0.4) # linestyles = ':'  ==> dash

from sklearn.svm import SVR

for gamma in [1, 10]:

    svr = SVR(gamma=gamma).fit(x, y)

    plt.plot(line, svr.predict(line), label='SVR gamma={}'.format(gamma))

plt.legend(loc=(-0.001, 1.01))

plt.show()

RBF 커널 SVM의 gamma parameter 변화와 10차 다항식을 이용한 LinearRegression



  1. np.digitize: 각 데이터 포인트가 어느 구간에 속하는지 계산 후 인덱스로 리턴 [본문으로]

#!/usr/bin/env python3


One-Hot-Encoding으로 wave 데이터셋 분석


데이터를 가장 잘 표현하는 방법은 데이터가 가진 의미뿐 아니라 어떤 모델을 사용하는지에 따라 다름

아주 폭넓게 사용하는 두 알고리즘인

linear 모델과 tree 기반 모델(decision tree, gradient boosting tree, random forest 등)

특성의 표현 방식으로 인해 미치는 영향이 매우 다름



1. wave 데이터셋(인위적 데이터)으로 확인하기

이 데이터에는 입력 특성이 하나뿐

이 데이터셋을 이용해 linear regression 과 decision tree regression tree를 비교


# library import

from sklearn.linear_model import LinearRegression

from sklearn.tree import DecisionTreeRegressor

from mglearn.datasets import make_wave

import numpy as np

import matplotlib.pyplot as plt

import matplotlib


# matplotlib 설정

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

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


# dataset

x, y = make_wave(n_samples=100)

line = np.linspace(-3, 3, num=1000, endpoint=False).reshape(-1, 1)


# model fit

# decision tree

decision_tree_reg = DecisionTreeRegressor(min_samples_split=3).fit(x, y) # min_samples_split[각주:1]

plt.plot(line, decision_tree_reg.predict(line), label='decision tree')


# linear regression

linear_reg = LinearRegression().fit(x, y)

plt.plot(line, linear_reg.predict(line), ls='--', label='linear regression'# ls = linestyle, 


plt.scatter(x[:, 0], y, marker='o', c='k'# c=color

plt.ylabel('output regression')

plt.xlabel('input feature')

plt.legend(loc=2)

plt.show()

wave 데이터셋에 적용한 LinearRegression과 DecisionTreeRegressor




2. binding(구간분할)

linear 모델은 선형으로만 modeling 하므로 특성이 하나일 땐 직선으로 나타남

decision tree는 훨씬 복잡한 modeling을 함

연속형 데이터에 아주 강력한 linear 모델을 만드는 방법중 하나는

한 특성을 여러 특성으로 나누는 구간분할binding(이산화)


wave 데이터셋이 특성의 입력값 범위(-3, 3)가 나뉘어 여러 구간으로

10개로 되었다고 가정하면 각 데이터 포인트가 어떤 구간에 속하는지로 나타낼 수 있음


### step1. 구간을 먼저 정하고 ==> 같은 간격으로 10개의 구간을 정의

bins = np.linspace(-3, 3, num=11) # num: 쪼갤 갯수

print('구간: \n{}'.format(bins))

print('구간의 수 \n{}'.format(len(bins)-1))

-3, 3 사이의 구간과 구간의 수


### step2. 각 데이터 포인트가 어느 구간에 속하는지 기록

which_bin = np.digitize(x, bins=bins) # 데이터, 구간

print('데이터 포인트 처음 5개 \n{}'.format(x[:5]))

print('데이터 포인트의 소속구간 처음 5개 \n{}'.format(which_bin[:5]))

각 구간에 맵핑한 x 데이터의 일부


이렇게 함으로써 wave 데이터셋에 있는 연속형 특성을 각 데이터 포인트가 어느 구간에 속하는지 인코딩한 범주형 특성으로 변환이 가능

==> 이 데이터에 모델을 적용하기 위해 preprocessing 모듈의 OneHotEncoder로 이산적인 이 특성을 one-hot-encoding으로 변환

OneHotEncoder는 pandas.get_dummies와 같지만 현재는 숫자로된 범주형 변수에만 적용가능




3. OneHotEncoding된 데이터로 Linear Regressor와 Decision Tree Regressor의 비교

# library import

from sklearn.preprocessing import OneHotEncoder


encoder = OneHotEncoder(sparse=Falsesparse matrix: 희소행렬, True면 1이 있는 값의 위치와 값을 나타냄

encoder.fit(which_bin)

x_binned = encoder.transform(which_bin)

print('x_binned[:5] \n{}'.format(x_binned[:5]))

one-hot-encoder된 구간에 맵핑된 x 데이터의 일부


### 구간을 10개로 정의했기 때문에 변환된 데이터셋 x_binned는 10개의 특성으로 구간

print('x_binned.shape \n{}'.format(x_binned.shape)) # (100, 10)


### one-hot-encoding된 데이터로 선형 회귀 모델과 결정트리의 모델을 새로 만들면

line_binned = encoder.transform(np.digitize(line, bins=bins))

plt.scatter(x[:, 0], y, marker='o', c='k'# c = color

lr_reg = LinearRegression().fit(x_binned, y)

plt.plot(line, lr_reg.predict(line_binned), label='binding linear regression', lw=5) # lw = linewidth


tree_reg = DecisionTreeRegressor(min_samples_split=3).fit(x_binned, y)

plt.plot(line, tree_reg.predict(line_binned), ls='--', label='binding decision tree', c='red'# ls = linestyle, c = color


plt.vlines(bins, ymin=-3, ymax=3, lw=1, alpha=0.2) # lw = linewidth

plt.legend(loc=2)

plt.ylabel('output regression')

plt.xlabel('input feature')

plt.show()

구간으로 나뉜 특성에 적용한 Linear Regrssion과 Decision Tree Regressor의 비교


LinearRegression과 DecisionTreeRegressor가 같은 예측을 만들어내서 완전히 겹침

구간별로 이 모델이 예측한 것은 상수값, 각 구간안에서는 특성의 값이 상수이므로 어떤 모델이든 그 구간의 포인트에 대해서는 같은 값을 예측함


구간으로 나누기전과 비교하면 각 구간에서 다른 값을 가지고 있으므로 linear모델이 훨씬 유연해짐, 반면 decision tree는 덜 유연해짐

tree 모델은 데이터를 자유롭게 나눠 학습할 수 있으므로 특성의 값을 구간으로 나누는 것이 아무런 득이 되지 않음

즉  decision tree는 데이터셋에서 예측을 위한 가장 좋은 구간을 학습함

구간나누기는 특성마다 따로 해야 하지만 decision tree는 한 번에 여러 특성을 살필 수 있음

하지만 linear 모델은 이런 변환으로 큰 이득을 얻음


일부 특성과 출력이 비선형 관계이지만, 용량이 매우 크고 고차원 데이터셋이라 선형모델을 사용해야 한다면

binding구간분할이 모델 성능을 높이는데 아주 좋은 방법이 될 수 있음

  1. min_samples_split: int, float, optional (default=2) The minimum number of samples required to split an internal node: If int, then consider min_samples_split as the minimum number. If float, then min_samples_split is a percentage and ceil(min_samples_split * n_samples) are the minimum number of samples for each split. Changed in version 0.18: Added float values for percentages. [본문으로]

'데이터 표현과 특성 > One-Hot-Encoding(' 카테고리의 다른 글

One-Hot-Encoding(가변수)  (0) 2018.03.27

#!/usr/bin/env python3


One-Hot-Encoding(가변수)


0.살펴보기

factor범주 변수를 표현하는데 가장 널리 쓰이는 방법인 One-Hot-Encoding원-핫-인코딩

dummy variable가변수을 만들어 factor형 변수를 0 또는 1 의 값을 가진 하나 이상의 새로운 특성으로 바꿈

따라서 특성당 0, 1의 값이 들어있기 때문에 binary classifier algorithm이진분류알고리즘 적용가능


pandas의 get_dummies[각주:1] 함수는 숫자 특성은 모두 연속형이라 생각해서 가변수를 만들지 않음 

대신 어떤 열이 연속형인지 범주형인지를 저장할 수 있는 scikit-learn의 OneHotEncoder를 사용할수 있고

DataFrame에 있는 숫자로 된 열을 문자열로 바꿀 수도 있음


# library import

import pandas as pd


# DataFrame 생성

df = pd.DataFrame({'숫자 특성': [0, 1, 2, 1],

                        'factor형 특성': ['핸드폰', '밀스', '핸드폰', '상자']})


# DataFrame 출력

print('df \n{}'.format(df))


# one-hot-encoding 적용

df_dummy = pd.get_dummies(df)

print('df_dummy \n{}'.format(df_dummy))


### get_dummies를 사용하면 문자열 특성만 인코딩되며 숫자 특성은 바뀌지 않음


### series를 str형태로 변형후 get_dummies 적용

### get_dummies를 적용할 때 columns를 직접 입력

### 방법 1

df1 = df.copy() #  DataFrame 복사

df1['숫자 특성'] = df1['숫자 특성'].astype(str) # 타입변형

df1_dummies = pd.get_dummies(df1)

print('df1_dummies \n{}'.format(df1_dummies))


### 방법 2

df2_dummies = pd.get_dummies(df, columns=['숫자 특성', 'factor형 특성']) # get_dummies 적용시 columns 지정

print('df2_dummies \n{}'.format(df2_dummies))



1. adult data loading

# library import

import pandas as pd


# data load

adult.csv

adult = pd.read_csv('adult.csv', index_col=False


# 원하는 열만 추출

adult = adult[['age', 'workclass', 'education', 'gender', 'hours-per-week', 'occupation', 'income']]

print('adult.head() \n{}'.format(adult.head()))

adult데이터의 처음 5번째 행


2. 범주형 데이터 문자열 확인

pandas ==> value_counts 메소드로 유일한 값이 각각 몇 번 나타나는지 확인


print('columns names \n{}'.format(adult.columns)) # 열 출력


print('age의 갯수 \n{}'.format(adult.age.value_counts())) # dataframe.열.메소드

print('workclass의 갯수 \n{}'.format(adult.workclass.value_counts())) # ? 값 있음을 확인

print('education의 갯수 \n{}'.format(adult.education.value_counts())) 

print('gender의 갯수 \n{}'.format(adult.gender.value_counts()))

print('hours-per-week의 갯수 \n{}'.format(adult['hours-per-week'].value_counts())) # 열이름이 특수문자나 띄어쓰기 있으면 ['열이름']으로 접근

print('occupation의 갯수 \n{}'.format(adult.occupation.value_counts())) # ? 값 있음을 확인

print('income의 갯수 \n{}'.format(adult.income.value_counts()))


value_counts로 확인한 특성의 수



3. pandas의 get_dummies로 데이터 reshaping 


adult_dummies = pd.get_dummies(adult)

print('원본 특성:\n', list(adult.columns), '\n')

print('get_dummies 후의 특성 :\n', list(adult_dummies.columns))

원본 특성과 get_dummies후의 특성, 'age', 'hours-per-week'를 제외하고 전부 나뉘어진 것을 볼 수 있음


### 숫자형인 age, hours-per-week는 그대로지만 factor형 특성은 값마다 새로운 특성이 만들어짐

### pandas의 values속성을 이용해 numpy 배열로 바꿀수 있음

### target을 income으로 잡을 것이므로 income을 데이터에서 분리해야함


### numpy에서 np.arange(11)[0:10]은 인덱스가 10인 항목을 포함하지 않음

### 그러나 pandas에서 범위 끝은 그 열을 포함함

features = adult_dummies.loc[:, 'age':'occupation_ Transport-moving']


import numpy as np

x = features.values # feature를 numpy배열로 변환

y = adult_dummies['income_ >50K'].values # 타겟데이터를 numpy 배열로 변환


print('x.shape: {}. y.shape: {}'.format(x.shape, y.shape)) # x.shape: (31143, 44), y.shape:(31143,)



4. one-hot-coding한 데이터를 바탕으로 machine learning

from sklearn.linear_model import LogisticRegression

from sklearn.model_selection import train_test_split


x_train, x_test, y_train, y_test = \

  train_test_split(x, y,

                   test_size=0.3, random_state=0)


logreg = LogisticRegression().fit(x_train, y_train)

print('테스트 점수 \n{:.3f}'.format(logreg.score(x_test, y_test))) # 0.813


### adult 데이터셋에서는 factor형 변수가 문자열로 인코딩 되어있음

### 철자 오류가 날 수 있지만 다른 한 편으로는 변수가 factor형이란 것을 확실하게 알 수가 있음


### 하지만 저장 공간을 절약하거나 데이터 취합 방식에 따라 범주형 변수가 숫자로 인코딩 된 경우가 많음

### 예를들어 adult 데이터셋에 있는 인구조사 데이터가 설문지를 이용해 모은 것이라고 가정하면 workclass에 대합 대답은 0, 1 ,2 등이 됨 ==> 이 열은 0~8까지의 숫자로 채워지

### 게 되고 누군가 이 데이터셋을 보면 이 변수를 연속형으로 다뤄야할지 factor형으로 다뤄야할지 단번에 알아채기가 어려움. 그러나 숫자가 workclass를 나타낸다고 알게  되면

### 이  값은 이산적이므로 연속형 변수로 다루면 안된다는 것이 명확해짐



  1. 객체 타입(문자열 같은)이나 범주형을 가진 열을 자동으로 변환해줌 [본문으로]

#!/usr/bin/env python3


Face Datasets Analysis with DBSCAN


# load library

from sklearn.datasets import fetch_lfw_people

import numpy as np

import matplotlib

import matplotlib.pyplot as plt

from sklearn.decomposition import PCA, NMF

from sklearn.preprocessing import MinMaxScaler

from sklearn.cluster import DBSCAN


# 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# min_faces_per_person = 겹치지 않는 최소 얼굴 수, 

image_size = people.images[0].shape


# outlier 있는지 확인 

_, bins = np.histogram(people.target, bins=100)

plt.hist(people.target, bins=bins)

plt.xticks(range(len(people.target_names)), people.target_names, rotation=90, ha='center')

plt.hlines(50, xmin=0, xmax=bins[-1], linestyles='--')# 수평선

plt.show()

각 특성별 히스토그램 

# 특성이 50개

idx = np.zeros(people.target.shape, dtype=np.bool)

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

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


# 데이터 분할

x_people = people.data[idx]

y_people = people.target[idx]


# 전처리

scaler_mms = MinMaxScaler()

x_people_scaled = scaler_mms.fit_transform(x_people)

# pca는 whiten 옵션을 True를 주면 전처리효과가 발생


# 훈련데이터를 PCA주성분 100개로 변환

pca = PCA(n_components=100, random_state=0, whiten=True).fit(x_people)

x_pca = pca.transform(x_people)


# 훈련데이터를 NMF 성분 100개로 변환

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

x_nmf = nmf.transform(x_people_scaled)


# DBSCAN에서 알맞은 min_sample값과 eps구하기

for min_sample in [3, 5, 7, 9]:

    for eps in [0.2, 0.4, 0.9, 1, 3, 5, 7, 9, 11, 13, 15, 17]:

        dbscan = DBSCAN(n_jobs=-1, min_samples=min_sample, eps=eps)

        labels_pca = dbscan.fit_predict(x_pca) # pca를 이용한 성분을 이용한 dbscan을 예측

        labels_nmf = dbscan.fit_predict(x_nmf) # nmf를 이용한 성분을 이용한 dbscan으로 예측

        print('︎=== min_sample:{}, eps:{} ==='.format(min_sample, eps))

        print('')

        print('pca ==> cluster 수: {}'.format(len(np.unique(labels_pca))))

        print('pca ==> cluster 크기: {}\n'.format(np.bincount(labels_pca+1)))


        print('nmf ==> cluster 수: {}'.format(len(np.unique(labels_nmf))))

        print('nmf ==> cluster 크기: {}'.format(np.bincount(labels_nmf+1)))

        print('---------------------------------------')

알맞은 min_sample과 eps를 찾는 과정

### pca : min_sample=3, eps=7일 때 가장 많은 클러스터가 생김

### nmf : min_sample=3, eps=0.9일 때 가장 많은 클러스터가 생김


# 가장 많은 클러스터가 생긴 eps와 n_sample 츨력

dbscan = DBSCAN(min_samples=3, eps=7, n_jobs=-1) # n_jobs = 코어의 갯수

labels_pca = dbscan.fit_predict(x_pca)


dbscan = DBSCAN(min_samples=3, eps=0.9, n_jobs=-1)

labels_nmf = dbscan.fit_predict(x_nmf)


# pca 주성분, DBSCAN 을 이용한 시각화

for cluster in range(labels_pca.max() + 1):

    idx = labels_pca == cluster 

    n_images = np.sum(idx) # idx중 true인 것의 갯수

    fig, axes = plt.subplots(1,n_images, figsize=(n_images*1.5, 4), # figsize=(a,b) axb 그림사이즈

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

    

    for image, label, ax in zip(x_people[idx], y_people[idx], axes.ravel()):

        ax.imshow(image.reshape(image_size))

        ax.set_title(people.target_names[label].split()[-1])


plt.gray()

plt.show()

0123456789101112

PCA 주성분을 이용한 DBSCAN 





#!/usr/bin/env python3


face dataset으로 Agglomerative Algorithm 비교

1. face data load

# library import

from sklearn.decomposition import PCA, NMF

from sklearn.preprocessing import StandardScaler, MinMaxScaler

from sklearn.datasets import fetch_lfw_people

from sklearn.model_selection import train_test_split

from sklearn.cluster import DBSCAN, KMeans, AgglomerativeClustering

import numpy as np

import matplotlib.pyplot as plt

import matplotlib


# matplotlib 설정

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

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


# dataset

people = fetch_lfw_people(min_faces_per_person=20, resize=0.7, color=False) 

people_image = people.images[0].shape 


# 빈도수 확인

counts = np.bincount(people.target)


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

    print('{0:12} {1:3}'.format(count, name), end='\t')

    if i%3 == 0:

        print()


# 특성의수를 50개까지

idx = np.zeros(people.target.shape, dtype=np.bool)

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]


# preprocessing

scaler = MinMaxScaler()

x_people_scaled = scaler.fit_transform(x_people) # 전처리 메소드로 원래데이터 적용




2 . PCA와 NMF를 이용하여 DBSCAN


# PCA 주성분 분석

pca = PCA(n_components=100, whiten=True, random_state=0) 

pca.fit_transform(x_people_scaled) # 모델 fitting

x_pca = pca.transform(x_people_scaled) # 적용한 모델을 원래 데이터에 적용


# NMF 분석

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

nmf.fit_transform(x_people_scaled) # 모델 fitting

x_nmf = nmf.transform(x_people_scaled) # 적용한 모델을 원래 데이터에 적용


# DBSCAN Agglomerative Algorithm

dbscan = DBSCAN() 

labels_pca = dbscan.fit_predict(x_pca) # pca를 군집화

labels_nmf = dbscan.fit_predict(x_nmf) # nmf를 군집화


print('\n레이블 종류: \npca: {}\nnmf: {}'.format(np.unique(labels_pca), np.unique(labels_nmf))) # [-1], 

### PCA, NMF 모두 레이블이 -1뿐임 ==> DBSCAN에 의해 noise point로 처리

### solution:

### 1. eps값을 크게 ==> 데이터 point의 이웃의수 증가

### 2. min_samples 값을 작게 ==> cluster에 모을 point 수를 감소



3. 알맞은 DBSCAN값 찾기

### 알맞은 eps값 확인 (-1, 0)

for eps in [0.5, 0.7, 0.9, 1, 5, 9, 15, 17]: # eps 목록

    dbscan = DBSCAN(min_samples=3, eps=eps)

    labels_pca = dbscan.fit_predict(x_pca)

    labels_nmf = dbscan.fit_predict(x_nmf)

    print('pca ==> n_samples: {}, eps: {:.3f} - {}'.format(3, eps, np.unique(labels_pca)))

    print('nmf ==> n_samples: {}, eps: {:.3f} - {}\n'.format(3, eps, np.unique(labels_nmf)))


# DBSCAN -- pca

dbscan = DBSCAN(min_samples=3, eps=15)

labels_pca = dbscan.fit_predict(x_pca)


# DBSCAN -- NMF

dbscan = DBSCAN(min_samples=3, eps=1)

labels_nmf = dbscan.fit_predict(x_nmf)


print('레이블 종류: \npca: {}\nnmf: {}'.format(np.unique(labels_pca), np.unique(labels_nmf))) # (-1, 0)

pca_count = np.bincount(labels_pca + 1) # bincount는 음수는 표현할 수없으니 1을 더함

nmf_count = np.bincount(labels_nmf + 1)


print('클러스터별 포인트 수:\npca: {}\nnmf: {}'.format(pca_count, nmf_count))


### visualization

noise_pca = x_people[labels_pca == -1] # noise 값만 추출

noise_nmf = x_people[labels_nmf == -1]


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

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

                        gridspec_kw={'hspace':0.5})


for image, ax in zip(noise_pca, axes.ravel()):

    ax.imshow(image.reshape(people_image))

plt.gray()

plt.show()

face dataset에서 DBSCAN이 noise point로 레이블한 sample(PCA)


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

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

                        gridspec_kw={'hspace':0.5})


for image, ax in zip(noise_nmf, axes.ravel()):

    ax.imshow(image.reshape(people_image))

plt.gray()

plt.show()

face dataset에서 DBSCAN이 noise point로 레이블한 sample(NMF)


face image에서 무작위로 선택한 이미지와 비교해보면 대부분 얼굴이 일부 가려져 있거나 각도가 이상, 혹은 너무 가까이거나 너무 멀리 있는 경우임


+ Recent posts