● 퍼셉트론

1. 초기 머신러닝의 역사

 - Warren McCulloch과 Walter Pits가 뇌의 뉴런 개념을 발표

   (MCP 뉴런 - 신경 세포를 이진 출력을 내는 간단한 논리 회로로 표현)

 - Frank Rosenblatt는 MCP 뉴런 모델을 기반으로 퍼셉트론 학습 개념을 발표

 - 퍼셉트론 규칙에서 자동으로 최적의 가중치를 학습하는 알고리즘 제안

 - 가중치는 뉴런의 출력 신호를 낼지 말지 결정하기 위해 입력 특성에 곱하는 계수

 - 지도학습분류 문제에서 퍼셉트론 알고리즘을 사용하여 새로운 데이터가 한 클래스에 속하는지 아닌지 예측 가능

 

2. 인공 뉴런의 수학적 정의

 - 두 클래스를 1과 -1로 나타냄

 - 입력값 \(x\)와 가중치 벡터 \(w\)의 선형 조합으로 결정함수 \(\phi(z)\) 정의

$$z = w_{1}x_{1} + w_{2}x_{2} + \cdots + w_{m}x_{m}$$

$$w = \begin{bmatrix} w_{1} \\ \vdots  \\ w_{m} \end{bmatrix}, x= \begin{bmatrix} x_{1} \\ \vdots  \\ x_{m} \end{bmatrix}$$

 - 특정 샘플 \(x\)의 최종 입력이 사전 정의된 임계값 \(\theta\)보다 크면 클래스 1, 아니면 클래스 -1로 예측

$$\phi(z) = \begin{cases} 1 & z \geq \theta \\ -1 & 그외 \end{cases}$$

 - \(z \geq \theta\) 식에서 \(\theta\)를 왼쪽으로 넘겨 \(w_{0}=-\theta\)이고, \(x_{0}=1\)인 0번째 가중치를 정의하여 식 간소화

$$z = w_{0}x_{0} + w_{1}x_{1} + \cdots + w_{m}x_{m} = w^{T}x$$

 - 결정함수는 다음과 같이 변경

$$\phi(z) = \begin{cases} 1 & z \geq 0 \\ -1 & 그외 \end{cases}$$

 - \(w_{0} = -\theta\)를 절편이라고 함

퍼셉트론의 결정함수와 결정 경계

 

3. 퍼셉트론 학습 규칙

 - 뇌의 뉴런 하나가 작동하는 방식을 흉내내는 환원주의 접근 방식 사용

  1. 가중치를 0 또는 랜덤한 작은 값으로 초기화
  2. 각 훈련 샘플 \(x^{(i)}\)에서 다음 작업 수행
    1. 출력 값 \(\hat{y}\) 계산
      → 출력값은 계단 함수로 예측한 클래스 레이블
    2. 가중치 업데이트
      \(w_{j}:=w_{j}+\Delta w_{j}\)
      가중치 업데이트 값인 \(\Delta w_{j}=\eta (y^{(i)}-\hat{y}^{(i)})x_{j}^{(i)}\)
      \(\eta\)는 학습률로 0.0~0.1 사이의 값
      \(y^{(i)}\)와 \(\hat{y}^{(i)}\)는 각각 \(i\)번째 훈련 샘플의 정답 레이블,
       예측 레이블

 - 가중치 벡터의 모든 가중치는 동시에 업데이트

 - 모든 가중치가 각자의 업데이트 값 \(\Delta w_{j}\)에 의해 업데이트되기 전에 예측 레이블 \(\hat{y}^{(i)}\)를 다시 계산하지 않음
      → \(\Delta w_{0}=\eta(y^{(i)}-output^{(i)})\)
      → \(\Delta w_{1}=\eta(y^{(i)}-output^{(i)})x_{1}^{(i)}\)
      → \(\Delta w_{2}=\eta(y^{(i)}-output^{(i)})x_{2}^{(i)}\)

 - 퍼셉트론이 클래스 레이블을 정확히 예측하면 가중치 유지(업데이트 값은 0)
      → \(y^{(i)}=-1, \hat{y}^{(i)}=-1, \Delta w_{j}=\eta(-1-(-1))x_{j}^{(i)}=0\)
      \(y^{(i)}=1, \hat{y}^{(i)}=1,\Delta w_{j}=\eta(1-1)x_{j}^{(i)}=0\)
 - 퍼셉트론이 클래스 레이블을 잘못 예측하면 가중치를 양성 또는 음성 타겟 클래스 방향으로 이동시킴
      \(y^{(i)}=1, \hat{y}^{(i)}=-1, \Delta w_{j}=\eta(1-(-1))x_{j}^{(i)}=\eta(2)x_{j}^{(i)}\)
      \(y^{(i)}=-1, \hat{y}^{(i)}=1, \Delta w_{j}=\eta(-1-1)x_{j}^{(i)}=\eta(-2)x_{j}^{(i)}\)

 - 가중치 업데이트는 \(x_{j}^{(i)}\)값에 비례하여, 이 값을 조절해 결정경계를 더 크게 움직이거나 더 작게 움직일 수 있음

 - 퍼셉트론은 두 클래스가 선형적으로 구분되고 학습률이 충분히 작을 때만 수렴이 보장됨

 - 선형 결정 경계로 나눌 수 없다면 훈련 데이터셋을 반복할 최대 횟수(epoch)를 지정해 분류 허용 오차 지정, 그렇지 않으면 퍼셉트론은 가중치 업데이트를 멈추지 않음

퍼셉트론 알고리즘

 

4. 파이썬으로 퍼셉트론  학습 알고리즘 구현

 - 퍼셉트론 클래스 정의

import numpy as np

class Perceptron(object):
    def __init__(self, eta = 0.01, n_iter = 50, random_state = 1):
        self.eta = eta
        self.n_iter = n_iter
        self.random_state = random_state

    def fit(self, X, y):
        rgen = np.random.RandomState(self.random_state) # 난수 생성기 객체 생성(격리된 시드 부여 가능, 이전과 동일한 랜덤 시드 재현 가능)
        self.w_ = rgen.normal(loc = 0.0, scale = 0.01,
                              size = 1 + X.shape[1])    # 평균 0, 표준편차 0.01의 정규분포에서 훈련 데이터 개수(X.shape[1]) + 절편(1)만큼의 난수 생성
        self.errors_ = []                               # 훈련 중 error의 감소 확인을 위한 객체

        for _ in range(self.n_iter):
            errors = 0
            for xi, target in zip(X, y):
                update = self.eta * (target - self.predict(xi)) # 가중치 w_j의 변화량
                self.w_[1:] += update * xi                      # 각 훈련 데이터에 가중치값 곱하기
                self.w_[0] += update                            # 가중치값 업데이트
                errors += int(update != 0.0)                    # 업데이트 된 값이 0이 아니면(실제값과 예측값이 다르면) errors에 1 더함
            self.errors_.append(errors)                         # 전체 훈련 데이터에서 예측값이 실제값과 다르게 나온 횟수 추가
        return self
    
    # 최종입력인 Z 계산
    def net_input(self, X):
        return np.dot(X, self.w_[1:]) + self.w_[0]
    
    # 결정함수를 임계값인 0과 비교, 크거나 같으면 1, 작으면 -1로 반환
    def predict(self, X):
        return np.where(self.net_input(X) >= 0, 1, -1)

 - fit 메서드 내의 w_객체에 가중치를 초기화하여 선언
 - 가중치가 0이 아니어야 학습률(eta)가 분류 결과에 영향을 줄 수 있어 0이 아닌 랜덤 값으로 초기화
 - 가중치가 0이면 eta는 가중치 벡터의 방향이 아니라 크기에만 영향을 미침

 - 붓꽃 데이터셋에서 퍼셉트론 훈련
 - Perceptron은 이진분류이므로 setosa와 versicolor에 대해서만 분류 진행
 - 또한, 특성은 첫번째 특성(꽃받침 길이)와 세번째 특성(꽃잎 길이)을 사용

 

import pandas as pd
import matplotlib.pyplot as plt

s = 'https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data'
df = pd.read_csv(s, header = None, encoding = 'utf-8')

# 처음 100개의 데이터(setosa 50개와 versicolor 50개)의 다섯번째 열인 예측 대상을 y로 선언
y = df.iloc[0:100, 4].values
# setosa이면 -1, versicolor이면 1로 변경
y = np.where(y == 'Iris-setosa', -1, 1)

# 처음 100개의 데이터에서 예측에 사용하고자 하는 특성인 첫번재 열과 세번째 열만 추출
X = df.iloc[0:100, [0, 2]].values

# 산점도로 시각화
plt.scatter(X[:50, 0], X[:50, 1], color = 'red', marker = 'o', label = 'setose')
plt.scatter(X[50:100, 0], X[50:100, 1], color = 'blue', marker = 'x', label = 'versicolor')
plt.xlabel('sepal length [cm]')
plt.ylabel('petal length [cm]')
plt.legend(loc = 'upper left')
plt.show()

# Perceptron 알고리즘 훈련
ppn = Perceptron(eta = 0.1, n_iter = 10)
ppn.fit(X, y)

# 훈련 과정에서 각 반복에서 나온 오차의 개수 시각화
plt.plot(range(1, len(ppn.errors_) + 1), ppn.errors_, marker = 'o')
plt.xlabel('Epochs')
plt.ylabel('Number of updates')
plt.show()

from matplotlib.colors import ListedColormap

# 결정경계 시각화 함수
def plot_decision_regions(X, y, classifier, resolution = 0.02):
    markers = ('s', 'x', 'o', '^', 'v')
    colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan')
    cmap = ListedColormap(colors[:len(np.unique(y))])

    # 두 특성의 최소값과 최대값에 각각 -1, +1을 한 값을 정의
    x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    # 위에서 구한 최소값과 최대값을 시작으로 하는 meshgrid 생성(최소값과 최대값 사이에 0.02의 간격으로 데이터 생성)
    xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution),
                           np.arange(x2_min, x2_max, resolution))
    
    # ravel 함수로 meshgrid를 1차원으로 평평하게 펴고, xx1, xx2를 각각 X, y로 Perceptron에 넣어 예측
    Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
    # 예측된 값을 다시 meshgrid 형태로 변경
    Z = Z.reshape(xx1.shape)

    # contourf 함수로 등고선 그래프 그리기(등고선이 두 집단의 경계로 그려짐)
    # 분류된 값이 -1인 것과 1인 것을 각각 빨간색과 파란색으로 구분
    plt.contourf(xx1, xx2, Z, alpha = 0.3, cmap = cmap)
    plt.xlim(xx1.min(), xx1.max())
    plt.ylim(xx2.min(), xx2.max())
    # 훈련 데이터를 빨간색 네모 모양와 파란색 x모양으로 구분
    for idx, cl in enumerate(np.unique(y)):
        plt.scatter(x = X[y == cl, 0], y = X[y == cl, 1],
                    alpha = 0.8, c = colors[idx], marker = markers[idx], label = cl, edgecolor = 'black')
    
plot_decision_regions(X, y, classifier = ppn)
plt.xlabel('sepal length [cm]')
plt.ylabel('petal length [cm]')
plt.legend(loc = 'upper left')
plt.show()

 - OvR(One-versus-Resr)기법을 사용하면 이진 분류기로 다중 클래스 문제 해결 가능
       클래스마다 하나의 분류기를 훈련(각 클래스를 양성으로 분류하고 나머지는 음성으로 분류)
       레이블이 없는 새로운 데이터 샘플 분류 시에는 클래스 레이블의 개수와 같은 n개의 분류기 사용
       신뢰도가 가장 높은 클래스 레이블(최종 입력의 절댓값이 가장 큰 클래스)을 분류하려는 샘플의 레이블로 선택

 - 퍼셉트론의 가장 큰 문제는 수렴
       구 클래스가 선형적인 초평면으로 구분될 수 있을 때 수렴하지만, 선형 결정 경계로 완벽하게 구분되지 않으면 최대 epoch를 지정하지 않는한 가중치 업데이트가 멈추지 않음

● 책 '파이썬 머신러닝 완벽 가이드' 참고

● 예시로 '월간 데이콘 쇼츠 - 초전도체 임계온도 예측 AI 헤커톤'의 데이터 사용(train, test, submission 파일 3개 존재)

 

1. 모듈 임포트

import os
# Seed 고정
def seed_everything(seed):
  random.seed(seed)
  os.environ['PYTHONHASHSEED'] = str(seed)
  np.random.seed(seed)
seed_everything(42)
# warning 무시
import warnings
warnings.filterwarnings("ignore")

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

from sklearn.preprocessing import LabelEncoder, OneHotEncoder
from sklearn.preprocessing import StandardScaler, MinMaxScaler

from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor
from sklearn.tree import DecisionTreeClassifier, DicisionTreeRegressor
from sklearn.linear_model import LogisticRegression, LinearRegression

from sklearn.model_selection import KFold
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import GridSearchCV

from sklearn.metrics import accuracy_score, mean_squared_error, mean_absolute_error, r2_score

 

 

2. 데이터 불러오기

train = pd.read_csv('train.csv')
test = pd.read_csv('test.csv')
submission = pd.read_csv('sample_submission.csv')

 

 

3. 데이터 탐색

train.head()  # 처음 5개의 데이터 (전체적인 데이터 구조 확인)
train.info()  # 데이터에 대한 개략적인 정보 (행과 열 개수, non-null값 개수, 데이터 타입)
train.describe()  # 숫자형 데이터 요약 (개수, 평균, 표준편차, 최소값, 25%, 50%, 75%, 최대값)
train.isna().sum()  # 각 데이터의 결측값 개수
# 이외에도 이해가 가지 않는 행이나 열만 따로 뽑아서 탐색, 데이터 도메인 지식 등을 찾아보며 데이터에 대한 이해 필요
train = train.drop('ID', axis = 1)  # train 데이터 탐색 결과 단순 식별자 변수인 ID는 필요 없는 것으로 판단, 삭제
feature = train.copy()  # 밑에서 target변수를 제거하고 feature 변수만 남긴 데이터
target = feature.pop('critical_temp') # target 데이터 추출, train에는 feature 데이터만 남음

  - train.head(): 처음 5개의 데이터 (전체적인 데이터 구조 확인)

  - train.info(): 데이터에 대한 개략적인 정보 (행과 열 개수, non-null값 개수, 데이터 타입)

  - train.describe(): 숫자형 데이터 요약 (개수, 평균, 표준편차, 최소값, 25%, 50%, 75%, 최대값)

  - train.isna().sum(): 각 데이터의 결측값 개수

 

 

4. 데이터 시각화

  - 박스플롯으로 이상값 등 데이터 분포 확인

scaler = StandardScaler() # 박스플롯 출력 전 하나의 박스플롯에서 데이터를 한눈에 보기 위해 같은 범위로 임시 스케일링
plt.figure(figsize = (15, 10))
sns.boxplot(scaler.fit_transform(feature))

 

  - 각 feature 데이터의 바이오린 플롯과 히스토그램 출력하여 데이터 분포 확인

for col in train.columns:
  fig, axs = plt.subplots(nrows = 1, ncols = 2, figsize = (10, 4))
  sns.violinplot(x = col, data = train, kde = True, ax = axs[0])
  sns.histplot(x = col, data = train, kde = True, ax = axs[1])

 

  - 히트맵으로 각 feature간 상관관계 파악

plt.figure(figsize = (15, 15))
sns.heatmap(train.corr(), cmap = 'vlag')

 

  - 히트맵으로 결측값 분포 확인(결측값이 있다면 그 부분이 살구색으로 표현됨)

plt.figure(figsize = (15, 15))
sns.heatmap(train.isnull())

 

 

5. 데이터 전처리(결측값 처리)

# 방법 1. 결측값이 있는 행 제거
feature = feature.dropna(axis = 0, how = 'any') # 각 행에서 하나의 값이라도 NaN이면 해당 행 제거
feature = feature.dropna(axis = 0, how = 'all') # 각 행의 모든 값이 NaN이면 해당 행 제거

# 방법 2. 결측값 채우기(특정 값으로 채우기)
feature = feature.fillna(0) # 0으로 채우기
feature = feature.fillna(method = 'pad')  # 바로 앞의 값으로 채우기
feature = feature.fillna(method = 'bfill')  # 바로 뒤의 값으로 채우기
feature = feature.fillna(feature.mean())  # 해당 열의 평균값으로 채우기

# 방법 3. 결측값 채우기(보간법)
feature = feature.interpolate(method = 'time') # 인덱스가 날짜/시간일 때 날짜나 시간에 따라 보간
feature = feature.interpolate(method = 'nearest', limit_direction = 'forward') # 가장 가까운 값으로 보간
feature = feature.interpolate(method = 'linear', limit_direction = 'forward') # 양 끝값에 따라 선형식에 적용하여 보간
feature = feature.interpolate(method = 'polynomial', order = 2) # 양 끝값에 따라 다항식에 적용하여 보간 (order은 다항식 차수)

# 방법 4. 결측값이 있는 열을 해당 열의 결측값이 아닌 행과 다른 feature 변수들로 예측하여 채우기
feature_target = feature.pop('결측값 열') # 다른 행들로부터 결측값을 예측할 목표열 분리
target_index = feature_target[feature_target['결측값 열'] == np.nan].index  # 목표열 중 결측값인 행의 인덱스 추출
feature_train = feature.iloc[~target_index] # 목표열이 아닌 다른 열에서 결측값이 있는 행의 인덱스가 아닌 행 추축(train 용)
feature_test = feature.iloc[target_index] # 목표열이 아닌 다른 열에서 결측값이 있는 행의 인덱스인 행 추출(test 용)
feature_target_train = feature_target[~target_index]  # 목표열 중 결측값이 아닌 행 추출(train 용)
rf = RandomForestRegressor()  # 랜덤포레스트로 예측
rf.fit(feature_train, feature_target_train) # 랜덤포레스트에 목표열 중 결측값이 아닌 행을 target 변수, 다른 열의 해당 행들을 feature 변수로 모델 피팅
pred = rf.predict(feature_test) # 피팅된 모델로 목표열에서 결측값인 행 예측
feature.iloc[target_index]['결측값 열'] = pred  # 원래 데이터프레임에서 목표열의 결측값인 값을 위에서 예측한 값으로 대체

 

 

6. 데이터 전처리(이상값 처리)

# IQR으로 이상값 판하여 절단
def outlier(df, col):
  tar = df[col]
  quantile_25 = np.percentile(tar.values, 25)
  quantile_75 = np.percentile(tar.values, 75)
  iqr = quantile_75 - quantile_25
  low = quantile_25 - iqr * 1.5
  high = quantile_75 + iqr * 1.5
  outlier_index = tar[(tar < low) | (tar > high)].index
  del_outlier = df.drop(outlier_index, axis = 0)
  return del_outlier

 

 

7. 데이터 전처리(표준화 및 정규화 = 피처 스케일링)

# 방법 1. StandardScaler
scaler = StandardScaler()
feature[[feature.select_dtypes([float, int]).columns]] = scaler.fit_transform(feature.select_dtypes([float, int]))
test[[test.select_dtypes([float, int]).columns]] = scaler.transform(test.select_dtypes([float, int]))

  -  평균 0, 분산 1

  - SVM, 선형회귀, 로지스틱 회귀는 데이터가 가우시안 정규 분포를 가지고 있다고 가정하고 구현되어 있기 때문에 StandardScaler가 예측 성능 향상에 도움

 

# 방법 2. MinMaxScaler
scaler = MinMaxScaler()
feature[[feature.select_dtypes([float, int]).columns]] = scaler.fit_transform(feature.select_dtypes([float, int]))
test[[test.select_dtypes([float, int]).columns]] = scaler.transform(test.select_dtypes([float, int]))

  - 최소값 0, 최대값 1

  - 음수값이 있다면 최소값 -1, 최대값 1

  - 데이터가 가우시안 정규 분포가 아닐 때 적용해볼 수 있음

 

 

8. 데이터 전처리(인코딩)

# 방법 1. 문자형 변수 라벨 인코딩
encoder = LabelEncoder()
feature[[feature.select_dtypes(object).columns]] = encoder.fit_transform(feature.select_dtypes(object))
test[[test.select_dtypes(object).columns]] = encoder.transform(test.select_dtypes(object))

 

# 방법 2. 문자형 변수 원핫 인코딩
encoder = OneHotEncoder()
feature[[feature.select_dtypes(object).columns]] = encoder.fit_transform(feature.select_dtypes(object).toarray())
test[[test.select_dtypes(object).columns]] = encoder.transform(test.select_dtypes(object).toarray())

  - 라벨 인코딩은 문자형 변수 고윳값의 개수에 따라 0부터 n-1까지의 숫자로 변환되어 ML에서 왜곡된 결과를 불러올 수 있음

  - 원핫 인코딩은 이런 문제를 해결하기 위해 고윳값의 개수만큼 새로운 feature 변수를 생성하여 각 행에서 고윳값에 해당하는 feature만 1, 나머지는 0으로 표시

 

 

9. 모델 피팅 및 예측

# feature 변수와 target 변수 분리
X = feature
y = target

# 학습용 데이터(X_train, y_train)와 검증용 데이터(X_test, y_test)로 분리
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2)

# 모델 선언(사용할 모델로 선정, 예시로는 RandomForestRegressor 선정)
model = RandomForestRegressor()

# 모델 학습
model.fit(X_train, y_train)

# 학습된 모델로 검증용 데이터 예측
y_pred = rf.predict(X_test)

# 실제 검증용 데이터와 예측한 검증용 데이터 비교하여 점수 출력
print(f'mse: {mean_squared_error(y_test, y_pred)}\nmae: {mean_absolute_error(y_test. y_pred)}\naccuracy: {accuracy_score(y_test, y_pred)}')

 

 

10. 교차 검증

# 방법 1. KFold
def exec_kfold(model, folds = 5):
  kfold = KFold(n_splits = folds)
  scores = []

  for iter, (train_index, test_index) in enumerate(kfold.split(X)):
    X_train, X_test = X.values[train_index], X.values[test_index]
    y_train, y_test = y.values[train_index], y.values[test_index]
    model.fit(X_train, y_train)
    y_pred = model.predict(X_test)
    accuracy = accuracy_score(y_test, y_pred)
    scores.append(accuracy)
    print(f'교차 검증 {iter} 정확도: {accuracy:.4f}')

  print(f'평균 정확도: {np.mean(scores):.4f}')

exec_kfold(model, folds = 5)
# 방법 2. cross_val_score
scores = cross_val_score(model, X, y, cv = 5)
for iter, accuracy in enumerate(scores):
  print(f'교차 검증 {iter} 정확도: {accuracy:.4f}')
print(f'평균 정확도: {np.mean(scores):.4f}')

  - KFold와 cross_val_score에서 각각 평균 정확도가 다른 이유는 cross_val_score가 StratifiedKFold를 이용해 폴드 세트를 분할하기 때문

 

# 방법 3. GridSearchCV
params = {'max_depth': [2, 3, 5, 10], 'min_samples_split': [2, 3, 5], 'min_samples_leaf': [1, 5, 8]}  # 사용할 모델에 맞는 하이퍼 파라미터 중 탐색하고 싶은 범위 지정
grid_model = GridSearchCV(model, param_grid = params, scoring = 'accuracy', cv = 5, verbose = 3)  # GridSearchCV에 model, 하이퍼 파라미터, 점수, 교차검증 횟수 등을 파라미터로 지정
grid_model.fit(X_train, y_train)  # GridSearchCV 모델에 train 데이터 피팅
print(f'GridSearchCV 최적 하이퍼 파라미터: {grid_model.best_params_}')  # 최적 하이퍼 파라미터
print(f'GridSearchCV 최고 정확도: {grid_model.best_score_:.4f}')  # 최적 하이퍼 파라미터일 때 나온 최고 점수
best_model = grid_model.best_estimator_ # 최적 하이퍼 파라미터로 최고 점수가 나온 모델을 best_model로 저장

y_pred = best_model.predict(X_test) # best_model로 test 데이터 예측
accuracy = accuracy_score(y_test, y_pred) # 점수 계산
print(f'테스트 세트에서의 model 정확도: {accuracy:.4f}')

 

 

11. 기본 분석 이후

  - pycaret, autogluon 등 auto ML을 사용해 최적의 모델 선택

  - 데이터에 대한 깊은 이해를 통해 더 최적의 변수 선택 및 전처리, 차원 축소 및 군집화 등으로 파생변수 생성

● 추천 시스템(Recommender Systems)

  • 추천 시스템은 크게 두 가지로 구분 가능
    • 컨텐츠 기반 필터링(content-based filtering): 지금까지 사용자의 이전 행동과 명시적 피드백을 통해 사용자가 좋아하는 것과 유사한 항목을 추천
    • 협업 필터링(collaborative filtering): 사용자와 항목간의 유사성을 동시에 사용해 추천
  • 두 가지를 조합한 hybrid 방식도 가능

 

1. Surprise

  • 추천 시스템 개발을 위한 라이브러리
  • 다양한 모델과 데이터 제공
  • scikit-learn과 유사한 사용방법
# %pip install scikit-surprise
from surprise import SVD
from surprise import Dataset
from surprise.model_selection import cross_validate

data = Dataset.load_builtin('ml-100k', prompt = False)
data.raw_ratings[:10]

# 출력 결과
# (사용자 id, 영화 id, 평점, 시간)
[('196', '242', 3.0, '881250949'),
 ('186', '302', 3.0, '891717742'),
 ('22', '377', 1.0, '878887116'),
 ('244', '51', 2.0, '880606923'),
 ('166', '346', 1.0, '886397596'),
 ('298', '474', 4.0, '884182806'),
 ('115', '265', 2.0, '881171488'),
 ('253', '465', 5.0, '891628467'),
 ('305', '451', 3.0, '886324817'),
 ('6', '86', 3.0, '883603013')]
# SVD 알고리즘을 통해 각각의 measure를 측정
model = SVD()
cross_validate(model, data, measures = ['rmse', 'mae'], cv = 5, verbose = True)

# 출력 결과
Evaluating RMSE, MAE of algorithm SVD on 5 split(s).

                         Fold 1    Fold 2    Fold 3    Fold 4    Fold 5    Mean      Std     
RMSE (testset)    0.9273  0.9320  0.9400  0.9451  0.9362  0.9361  0.0062  
MAE (testset)      0.7333  0.7357  0.7387  0.7444  0.7372  0.7379  0.0037  
Fit time               2.58     3.23      2.98      1.96      2.03      2.56      0.50    
Test time            0.38      0.45      0.31      0.41      0.25      0.36      0.07    
{'test_rmse': array([0.92730437, 0.93195753, 0.93997153, 0.94506308, 0.93619742]),
 'test_mae': array([0.73328536, 0.73566774, 0.73869817, 0.74442193, 0.73723008]),
 'fit_time': (2.5789830684661865,
  3.2331647872924805,
  2.975646495819092,
  1.9638335704803467,
  2.027461051940918),
 'test_time': (0.3779747486114502,
  0.4478921890258789,
  0.3068206310272217,
  0.40511560440063477,
  0.24795103073120117)}

 

 

2. 컨텐츠 기반 필터링(Content-based Filtering)

  • 컨텐츠 기반 필터링은 이전의 행동과 명시적 피드백을 통해 좋아하는 것과 유사한 항목을 추천
    • ex) 내가 지금까지 시청한 영화 목록과 다른 사용자의 시청 목록을 비교해 나와 비슷한 취향의 사용자가 시청한 영화를 추천
  • 유사도를 기반으로 추천
  • 컨텐츠 기반 필터링의 장단점
    • 장점
      • 많은 수의 사용자를 대상으로 쉽게 확장 가능
      • 사용자가 관심을 갖지 않던 상품 추천 가능
    • 단점
      • 입력 틍성을 직접 설계해야 하기 때문에 많은 도메인 지식이 필요
      • 사용자의 기존 관심사항을 기반으로만 추천 가능
import numpy as np
from surprise import Dataset

data = Dataset.load_builtin('ml-100k', prompt = False)
raw_data = np.array(data.raw_ratings, dtype = int)

# raw_data가 0부터 시작하도록 조절
raw_data[:, 0] -= 1
raw_data[:, 1] -= 1

n_users = np.max(raw_data[:, 0])
n_movies = np.max(raw_data[:, 1])
# 행: 총 유저수, 열: 총 영화수
shape = (n_users + 1, n_movies + 1)
shape

# 출력 결과
(943, 1682)
adj_matrix = np.ndarray(shape, dtype = int)
for user_id, movie_id, rating, time in raw_data:
    # 1이 있는 위치가 현재 데이터가 있는 위치
    adj_matrix[user_id][movie_id] = 1.
adj_matrix

# 출력 결과
array([[1, 1, 1, ..., 0, 0, 0],
       [1, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       ...,
       [1, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       [0, 1, 0, ..., 0, 0, 0]])
my_id, my_vector = 0, adj_matrix[0]
best_match, best_match_id, best_match_vector = -1, -1, []
for user_id, user_vector in enumerate(adj_matrix):
    # user_id와 my_id가 다르면 유사도(similarity) 계산
    if my_id != user_id:
        similarity = np.dot(my_vector, user_vector)
        if similarity > best_match:
            best_match = similarity
            best_match_id = user_id
            best_match_vector = user_vector

print('Best Match: {}, Best Match ID: {}'.format(best_match, best_match_id))

# 출력 결과
Best Match: 183, Best Match ID: 275
recommend_list = []
for i, log in enumerate(zip(my_vector, best_match_vector)):
    log1, log2 = log
    # 내가 보지 않았지만(log1(my_vector)가 1보가 작음) 유사하게 나온 영화(log2(best_match_vector)가 0보다 큼)를 추천
    if log1 < 1. and log2 > 0.:
        recommend_list.append(i)
print(recommend_list)

# 출력 결과
[272, 273, 275, ..., 1480, 1481, 1482]

 

  - 유클리드 거리를 사용해 추천

  • 거리가 가까울수록(값이 작을수록) 나와 유사한 사용자

$$ euclidian = \sqrt{\sum_{d=1}^{D}(A_{i}-B_{i})^2} $$

my_id, my_vector = 0, adj_matrix[0]
best_match, best_match_id, best_match_vector = 9999, -1, []
for user_id, user_vector in enumerate(adj_matrix):
    # user_id와 my_id가 다르면 유클리드 거리 계산
    if my_id != user_id:
        # 유클리드 거리 계산식
        euclidian_dist = np.sqrt(np.sum(np.square(my_vector - user_vector)))
        if euclidian_dist < best_match:
            best_match = euclidian_dist
            best_match_id = user_id
            best_match_vector = user_vector

print('Best Match: {}, Best Match ID: {}'.format(best_match, best_match_id))

# 출력 결과
Best Match: 14.832396974191326, Best Match ID: 737
recommend_list = []
for i, log in enumerate(zip(my_vector, best_match_vector)):
    log1, log2 = log
        recommend_list.append(i)
print(recommend_list)

# 출력 결과
[297, 312, 317, ..., 968, 1015, 1046]

 

 

  - 코사인 유사도를 사용해 추천

  • 두 벡터가 이루고 있는 각을 계산

$$ cos\theta =\frac{A\cdot B}{\left\| A\right\|\times \left\| B\right\|} $$

# 코사인 유사도 계산식
def compute_cos_similarity(v1, v2):
    norm1 = np.sqrt(np.sum(np.square(v1)))
    norm2 = np.sqrt(np.sum(np.square(v2)))
    dot = np.dot(v1, v2)
    return dot / (norm1 * norm2)

my_id, my_vector = 0, adj_matrix[0]
best_match, best_match_id, best_match_vector = -1, -1, []
for user_id, user_vector in enumerate(adj_matrix):
    # user_id와 my_id가 다르면 코사인 유사도 계산
    if my_id != user_id:
        cos_similarity = compute_cos_similarity(my_vector, user_vector)
        if cos_similarity > best_match:
            best_match = cos_similarity
            best_match_id = user_id
            best_match_vector = user_vector

print('Best Match: {}, Best Match ID: {}'.format(best_match, best_match_id))

# 출력 결과
Best Match: 0.5278586163659506, Best Match ID: 915
recommend_list = []
for i, log in enumerate(zip(my_vector, best_match_vector)):
    log1, log2 = log
    # 내가 보지 않았지만(log1(my_vector)가 1보가 작음) 유사하게 나온 영화(log2(best_match_vector)가 0보다 큼)를 추천
    if log1 < 1. and log2 > 0.:
        recommend_list.append(i)
print(recommend_list)

# 출력 결과
[272, 275, 279, ..., 1427, 1596, 1681]

 

 

  - 기존 방법에 명시적 피드백(사용자가 평가한 영화 점수)을 추가해 실험

adj_matrix = np.ndarray(shape, dtype = int)
for user_id, movie_id, rating, time in raw_data:
    # 단순히 데이터가 존재하는지 안하는지에 따라 0, 1이 아니라 존재하는 곳에 rating값을 넣음
    adj_matrix[user_id][movie_id] = rating
adj_matrix

# 출력 결과
array([[5, 3, 4, ..., 0, 0, 0],
       [4, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       ...,
       [5, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       [0, 5, 0, ..., 0, 0, 0]])
# 유클리드 거리로 계산
my_id, my_vector = 0, adj_matrix[0]
best_match, best_match_id, best_match_vector = 9999, -1, []
for user_id, user_vector in enumerate(adj_matrix):
    # user_id와 my_id가 다르면 유클리드 거리 계산
    if my_id != user_id:
        # 유클리드 거리 계산식
        euclidian_dist = np.sqrt(np.sum(np.square(my_vector - user_vector)))
        if euclidian_dist < best_match:
            best_match = euclidian_dist
            best_match_id = user_id
            best_match_vector = user_vector

print('Best Match: {}, Best Match ID: {}'.format(best_match, best_match_id))

# 출력 결과
Best Match: 55.06359959174482, Best Match ID: 737
# 코사인 거리로 계산
my_id, my_vector = 0, adj_matrix[0]
best_match, best_match_id, best_match_vector = -1, -1, []
for user_id, user_vector in enumerate(adj_matrix):
    # user_id와 my_id가 다르면 코사인 유사도 계산
    if my_id != user_id:
        cos_similarity = compute_cos_similarity(my_vector, user_vector)
        if cos_similarity > best_match:
            best_match = cos_similarity
            best_match_id = user_id
            best_match_vector = user_vector

print('Best Match: {}, Best Match ID: {}'.format(best_match, best_match_id))

# 출력 결과
Best Match: 0.569065731527988, Best Match ID: 915

 

 

3. 협업 필터링(Collaborative Filtering)

  • 사용자와 항목의 유사성을 동시에 고려해 추천
  • 기존에 내 관심사가 아닌 항목이라도 추천 가능
  • 자동으로 임베딩 학습 가능
  • 협업 필터링의 장단점
    • 장점
      • 자동으로 임베딩을 학습하기 때문에 도메인 지식이 필요없음
      • 기존의 관심사가 아니더라도 추천 가능
    • 단점
      • 학습 과정에서 나오지 않은 항목은 임베딩을 만들 수 없음
      • 추가 특성을 사용하기 어려움
from surprise import KNNBasic, SVD, SVDpp, NMF
from surprise import Dataset
from surprise.model_selection import cross_validate

data = Dataset.load_builtin('ml-100k', prompt = False)

 

  - KNN을 사용한 협업 필터링

model = KNNBasic()
cross_validate(model, data, measures = ['rmse', 'mae'], cv = 5, n_jobs = 4, verbose = True)

# 출력 결과
Evaluating RMSE, MAE of algorithm KNNBasic on 5 split(s).

                  Fold 1  Fold 2  Fold 3  Fold 4  Fold 5  Mean    Std     
RMSE (testset)    0.9743  0.9842  0.9849  0.9823  0.9689  0.9789  0.0063  
MAE (testset)     0.7689  0.7785  0.7786  0.7752  0.7643  0.7731  0.0056  
Fit time          2.72    2.70    2.85    2.98    1.69    2.59    0.46    
Test time         16.55   16.59   16.43   15.93   7.88    14.68   3.40    
{'test_rmse': array([0.9742902 , 0.98423331, 0.98486835, 0.98230369, 0.96892197]),
 'test_mae': array([0.76887025, 0.77850316, 0.77859337, 0.77524521, 0.76433142]),
 'fit_time': (2.724426746368408,
  2.6965529918670654,
  2.8537588119506836,
  2.977182388305664,
  1.6942033767700195),
 'test_time': (16.55093264579773,
  16.586602687835693,
  16.429330825805664,
  15.92642879486084,
  7.883346796035767)}

 

  - SVD를 사용한 협업 필터링

model = SVD()
cross_validate(model, data, measures = ['rmse', 'mae'], cv = 5, n_jobs = 4, verbose = True)

# 출력 결과
Evaluating RMSE, MAE of algorithm SVD on 5 split(s).

                  Fold 1  Fold 2  Fold 3  Fold 4  Fold 5  Mean    Std     
RMSE (testset)    0.9364  0.9366  0.9345  0.9349  0.9355  0.9356  0.0008  
MAE (testset)     0.7378  0.7368  0.7356  0.7365  0.7381  0.7370  0.0009  
Fit time          3.69    3.54    4.03    4.02    4.02    3.86    0.20    
Test time         1.18    1.36    1.51    1.55    0.45    1.21    0.40    
{'test_rmse': array([0.93641775, 0.93660876, 0.934486  , 0.93493886, 0.93553188]),
 'test_mae': array([0.73780807, 0.73676556, 0.73564193, 0.73649871, 0.73805738]),
 'fit_time': (3.690873146057129,
  3.541585922241211,
  4.030731439590454,
  4.015835523605347,
  4.018853425979614),
 'test_time': (1.1828830242156982,
  1.3635668754577637,
  1.5132882595062256,
  1.5461750030517578,
  0.4530982971191406)}

 

  - NMF를 사용한 협업 필터링

model = NMF()
cross_validate(model, data, measures = ['rmse', 'mae'], cv = 5, n_jobs = 4, verbose = True)

# 출력 결과
Evaluating RMSE, MAE of algorithm NMF on 5 split(s).

                  Fold 1  Fold 2  Fold 3  Fold 4  Fold 5  Mean    Std     
RMSE (testset)    0.9603  0.9552  0.9631  0.9654  0.9632  0.9614  0.0035  
MAE (testset)     0.7569  0.7511  0.7570  0.7578  0.7573  0.7560  0.0025  
Fit time          4.99    5.05    4.98    4.89    2.28    4.44    1.08    
Test time         0.62    0.64    0.53    0.46    0.22    0.49    0.15    
{'test_rmse': array([0.96025745, 0.95521602, 0.96314659, 0.96535182, 0.96319395]),
 'test_mae': array([0.75686291, 0.75106343, 0.75704307, 0.75776259, 0.75726996]),
 'fit_time': (4.98656702041626,
  5.046948671340942,
  4.978093385696411,
  4.887223243713379,
  2.2761926651000977),
 'test_time': (0.6195485591888428,
  0.6439330577850342,
  0.5302431583404541,
  0.4606480598449707,
  0.2169051170349121)}

 

  - SVD++를 사용한 협업 필터링

model = SVDpp()
cross_validate(model, data, measures = ['rmse', 'mae'], cv = 5, n_jobs = 4, verbose = True)

# 출력 결과
Evaluating RMSE, MAE of algorithm SVDpp on 5 split(s).

                  Fold 1  Fold 2  Fold 3  Fold 4  Fold 5  Mean    Std     
RMSE (testset)    0.9180  0.9196  0.9126  0.9336  0.9101  0.9188  0.0082  
MAE (testset)     0.7193  0.7196  0.7155  0.7324  0.7149  0.7204  0.0063  
Fit time          64.45   65.24   65.04   65.49   25.32   57.11   15.90   
Test time         9.41    9.28    8.90    9.10    4.87    8.31    1.73    
{'test_rmse': array([0.91803553, 0.9196422 , 0.91259638, 0.9335687 , 0.91007489]),
 'test_mae': array([0.71934988, 0.719645  , 0.71549358, 0.73242896, 0.71492586]),
 'fit_time': (64.4547209739685,
  65.24023914337158,
  65.03504347801208,
  65.49227690696716,
  25.323737144470215),
 'test_time': (9.414633989334106,
  9.28382134437561,
  8.901108026504517,
  9.10077452659607,
  4.8735032081604)}

 

 

4. 하이브리드(Hybrid)

  • 컨텐츠 기반 필터링과 협업 필터링을 조합한 방식
  • 많은 하이브리드 방식이 존재
  • 실습에서는 협업 필터링으로 임베딩을 학습하고 컨텐츠 기반 필터링으로 유사도 기반 추천을 수행하는 추천 엔진 개발
import numpy as np
from sklearn.decomposition import randomized_svd, non_negative_factorization
from surprise import Dataset

data = Dataset.load_builtin('ml-100k', prompt = False)
raw_data = np.array(data.raw_ratings, dtype = int)
raw_data[:, 0] -= 1
raw_data[:, 1] -= 1

n_users = np.max(raw_data[:, 0])
n_movies = np.max(raw_data[:, 1])
shape = (n_users + 1, n_movies + 1)
shape

# 출력 결과
(943, 1682)


adj_matrix = np.ndarray(shape, dtype = int)
for user_id, movie_id, ratting, time in raw_data:
    adj_matrix[user_id][movie_id] = rating
adj_matrix

# 출력 결과
array([[3, 3, 3, ..., 0, 0, 0],
       [3, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       ...,
       [3, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       [0, 3, 0, ..., 0, 0, 0]])
U, S, V = randomized_svd(adj_matrix, n_components = 2)
S = np.diag(S)

print(U.shape)
print(S.shape)
print(V.shape)

# 출력 결과
(943, 2)
(2, 2)
(2, 1682)


# 분해 후 재조합
# (U * S) * V
np.matmul(np.matmul(U, S), V)

# 출력 결과
array([[ 2.75413997e+00,  1.40492906e+00,  7.42675835e-01, ...,
        -8.18516364e-04,  1.56868753e-02,  1.47148277e-02],
       [ 1.37986536e+00,  9.01276577e-02,  4.55027384e-01, ...,
         1.09681871e-02,  4.52128919e-04, -1.05261346e-03],
       [ 9.93032597e-01,  7.39687516e-03,  3.35229530e-01, ...,
         8.95866035e-03, -3.68132502e-04, -1.54632039e-03],
       ...,
       [ 6.28183642e-01,  6.62904209e-02,  2.03737923e-01, ...,
         4.52498630e-03,  5.10681060e-04, -1.32467414e-04],
       [ 9.59658161e-01,  4.03024146e-01,  2.70469585e-01, ...,
         1.31860581e-03,  4.42188957e-03,  3.93973413e-03],
       [ 1.80307089e+00,  1.01280345e+00,  4.73641731e-01, ...,
        -2.26049256e-03,  1.13925590e-02,  1.09104420e-02]])

 

 

  - 사용자 기반 추천

  • 나와 비슷한 취향을 가진 다른 사용자의 행동을 추천
  • 사용자 특징 벡터의 유사도 사용
my_id, my_vector = 0, U[0]
best_match, best_match_id, best_match_vector = -1, -1, []
for user_id, user_vector in enumerate(U):
    # user_id와 my_id가 다르면 코사인 유사도 계산
    if my_id != user_id:
        cos_similarity = compute_cos_similarity(my_vector, user_vector)
        if cos_similarity > best_match:
            best_match = cos_similarity
            best_match_id = user_id
            best_match_vector = user_vector

print('Best Match: {}, Best Match ID: {}'.format(best_match, best_match_id))

# 출력 결과
# 무려 99% 유사
Best Match: 0.9999996213542565, Best Match ID: 639
recommend_list = []
for i, log in enumerate(zip(adj_matrix[my_id], adj_matrix[best_match_id])):
    log1, log2 = log
    # 내가 보지 않았지만(log1(my_vector)가 1보가 작음) 유사하게 나온 영화(log2(best_match_vector)가 0보다 큼)를 추천
    if log1 < 1. and log2 > 0.:
        recommend_list.append(i)
print(recommend_list)

# 출력 결과
[300, 301, 303, ..., 1227, 1243, 1257]

 

  - 항목 기반 추천

  • 내가 본 항목과 비슷한 항목을 추천
  • 항목 특징 벡터의 유사도 사용
my_id, my_vector = 0, V.T[0]
best_match, best_match_id, best_match_vector = -1, -1, []
for user_id, user_vector in enumerate(V.T):
    # user_id와 my_id가 다르면 코사인 유사도 계산
    if my_id != user_id:
        cos_similarity = compute_cos_similarity(my_vector, user_vector)
        if cos_similarity > best_match:
            best_match = cos_similarity
            best_match_id = user_id
            best_match_vector = user_vector

print('Best Match: {}, Best Match ID: {}'.format(best_match, best_match_id))

# 출력 결과
# 더 높은 유사도
Best Match: 0.9999999998965627, Best Match ID: 1425
recommend_list = []
for i, user_vector in enumerate(adj_matrix):
    if adj_matrix[i][my_id] > 0.9:
        recommend_list.append(i)
print(recommend_list)

# 출력 결과
[0, 1, 4, ..., 935, 937, 940]

 

  - 비음수 행렬 분해를 사용한 하이브리드 추천

A, B, iter = non_negative_factorization(adj_matrix, n_components = 2)

np.matmul(A, B)

# 출력 결과
array([[2.65054793e+00, 1.43305723e+00, 7.12899311e-01, ...,
        4.99364929e-03, 1.61183931e-02, 1.52342501e-02],
       [1.46896452e+00, 3.82199135e-02, 4.63060557e-01, ...,
        7.41842083e-03, 0.00000000e+00, 0.00000000e+00],
       [1.05504090e+00, 2.74503376e-02, 3.32579733e-01, ...,
        5.32806429e-03, 0.00000000e+00, 0.00000000e+00],
       ...,
       [6.45969509e-01, 2.42500330e-02, 2.02959350e-01, ...,
        3.21642261e-03, 8.79481547e-05, 8.31239301e-05],
       [9.88950712e-01, 3.86580826e-01, 2.79306078e-01, ...,
        2.77435827e-03, 4.26387068e-03, 4.02998437e-03],
       [1.66732038e+00, 1.06422315e+00, 4.33815308e-01, ...,
        2.13993190e-03, 1.20624618e-02, 1.14007989e-02]])

 

  • 사용자 기반 추천
my_id, my_vector = 0, U[0]
best_match, best_match_id, best_match_vector = -1, -1, []
for user_id, user_vector in enumerate(U):
    # user_id와 my_id가 다르면 코사인 유사도 계산
    if my_id != user_id:
        cos_similarity = compute_cos_similarity(my_vector, user_vector)
        if cos_similarity > best_match:
            best_match = cos_similarity
            best_match_id = user_id
            best_match_vector = user_vector

print('Best Match: {}, Best Match ID: {}'.format(best_match, best_match_id))

# 출력 결과
Best Match: 0.9999996213542565, Best Match ID: 639
recommend_list = []
for i, log in enumerate(zip(adj_matrix[my_id], adj_matrix[best_match_id])):
    log1, log2 = log
    # 내가 보지 않았지만(log1(my_vector)가 1보가 작음) 유사하게 나온 영화(log2(best_match_vector)가 0보다 큼)를 추천
    if log1 < 1. and log2 > 0.:
        recommend_list.append(i)
print(recommend_list)

# 출력 결과
[300, 301, 303, ..., 1227, 1243, 1257]

 

  • 항목 기반 추천
my_id, my_vector = 0, V.T[0]
best_match, best_match_id, best_match_vector = -1, -1, []
for user_id, user_vector in enumerate(U):
    # user_id와 my_id가 다르면 코사인 유사도 계산
    if my_id != user_id:
        cos_similarity = compute_cos_similarity(my_vector, user_vector)
        if cos_similarity > best_match:
            best_match = cos_similarity
            best_match_id = user_id
            best_match_vector = user_vector

print('Best Match: {}, Best Match ID: {}'.format(best_match, best_match_id))

# 출력 결과
Best Match: 0.9999999998965627, Best Match ID: 1425
recommend_list = []
for i, user_vector in enumerate(adj_matrix):
    if adj_matrix[i][my_id] > 0.9:
        recommend_list.append(i)
print(recommend_list)

# 출력 결과
[0, 1, 4, ..., 935, 937, 940]

● 분해(Decomposition)

  • 큰 하나의 행렬을 여러 개의 작은 행렬로 분해
  • 분해 과정에서 중요한 정보만 남게됨

 

  • 라이브러리, 데이터 불러오기
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris, fetch_olivetti_faces
from sklearn.decomposition import PCA, IncrementalPCA, KernelPCA, SparsePCA
from sklearn.decomposition import TruncatedSVD, DictionaryLearning, FactorAnalysis
from sklearn.decomposition import FastICA, NMF, LatentDirichletAllocation
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis

iris, labels = load_iris(return_X_y = True)
faces, _ = fetch_olivetti_faces(return_X_y = True, shuffle = True)
  • iris 그래프 그리기 함수
def plot_iris(iris, labels):
    plt.figure()
    colors = ['navy', 'purple', 'red']
    for xy, label in zip(iris, labels):
        plt.scatter(xy[0], xy[1], color = colors[label])
  • faces 그리기 함수
def show_faces(faces):
    plt.figure()
    # 2*3의 배열로 표현
    num_rows, num_cols = 2, 3
    # 총 6개씩 뜨게 됨
    for i in range(num_rows * num_cols):
        plt.subplot(num_rows, num_cols, i+1)
        plt.imshow(np.reshape(faces[i], (64, 64)), cmap = plt.cm.gray)
  • 위의 함수 사용해서 그래프 그리기
plot_iris(iris[:, :2], labels)

show_faces(faces)

 

 

1. 주성분 분석(Principal Component Analysis, PCA)

  • PCA를 사용해 iris 데이터 변환
  • 150×4 크기의 데이터를 150×2 크기의 행렬로 압축
# 기존 iris 데이터의 shape
iris.shape

# 출력 결과
(150, 4)


# PCA 변환 후 iris 데이터의 shape
model = PCA(n_components = 2, random_state = 0)
model.fit(iris)
transformed_iris = model.transform(iris)
transformed_iris.shape

# 출력 결과
(150, 2)
# PCA로 변환한 iris 데이터 시각화
plot_iris(transformed_iris, labels)

더 명확하게 분류됨

 

  • PCA를 통해 학습된 각 컴포넌트(6개)
  • 각 컴포넌트는 얼굴의 주요 특징을 나타냄
# 기존 faces 데이터의 shape
faces.shape

# 출력 결과
(400, 4096)


# PCA 변환 후 faces 데이터의 shape
model = PCA(n_components = 6, random_state = 0)
model.fit(faces)
faces_components = model.components_
faces_components.shape

# 출력 결과
(6, 4096)
# PCA로 변환한 faces 데이터 시각화
show_faces(faces_components)

기존 얼굴에서 주요 특징만 남음

 

2. Incremental PCA

  • PCA는 SVD 알고리즘 실행을 위해 전체 학습용 데이터 셋을 메모리에 올려야 함
  • Incremental PCA는 학습 데이터를 미니 배치 단위로 나누어 사용
  • 학습 데이터가 크거나 온라인으로 PCA 적용이 필요할 때 유용
model = IncrementalPCA(n_components = 2)
model.fit(iris)
transformed_iris = model.transform(iris)
transformed_iris.shape

# 출력 결과
(150, 2)
# 시각화
plot_iris(transformed_iris, labels)

일반 PCA로 변환한 것과 큰 차이는 없음

model = IncrementalPCA(n_components = 6)
model.fit(faces)
faces_components = model.components_
faces_components.shape

# 출력 결과
(6, 4096)
# 시각화
show_faces(faces_components)

 

 

3. Kernel PCA

  • 비선형적인 형태가 Kernel로 표현될 수 있음
  • 차원 축소를 위한 복잡한 비선형 투형
model = KernelPCA(n_components = 2, kernel = 'rbf', random_state = 0)
model.fit(iris)
transformed_iris = model.transform(iris)
transformed_iris.shape

# 출력 결과
(150, 2)
# 시각화
plot_iris(transformed_iris, labels)

model = KernelPCA(n_components = 6)
model.fit(faces)

# Kernel PCA는 components_를 출력할 수 없어 오류가 발생함
faces_components = model.components_

 

 

4. Sparse PCA

  • PCA의 주요 단점 중 하나는 주성분들이 보통 모든 입력 변수들의 선형 결합으로 나타난다는 점
  • 희소 주성분 분석은 몇 개의 변수들만의 선형결합으로 주성분을 나타냄으로써 이러한 단점을 극복
model = SparsePCA(n_components = 2, random_state = 0)
model.fit(iris)
transformed_iris = model.transform(iris)
transformed_iris.shape

# 출력 결과
(150, 2)
# 시각화
plot_iris(transformed_iris, labels)

model = SparsePCA(n_components = 6)
model.fit(faces)
faces_components = model.components_
faces_components.shape

# 출력 결과
(6, 4096)
# 시각화
show_faces(faces_components)

아예 특성이 없어져 흰색이나 검은색으로 나타난 얼굴도 있는 모습

 

 

5. Truncated Singular Value Decomposition(Truncated SVD)

  • PCA는 정방 행렬에 대해서만 행렬 분해 가능
  • SVDs는 정방 행렬 뿐만 아니라 행과 열이 다른 행렬도 분해 가능
  • PCA는 밀집 행렬(Dense Matrix)에 대한 변환만 가능하지만, SVD는 희소 행렬(Sparse Matrix)에 대한 변환도 가능
  • 전체 행렬 크기에 대해 Full SVD를 사용하는 경우는 적음
  • 특이값이 0인 부분을 모두 제거하고 차원을 줄인 Truncated SVD를 주로 사용
model = TruncatedSVD(n_components = 2, random_state = 0)
model.fit(iris)
transformed_iris = model.transform(iris)
transformed_iris.shape

# 출력 결과
(150, 2)
# 시각화
plot_iris(transformed_iris, labels)

model = TruncatedSVD(n_components = 6)
model.fit(faces)
faces_components = model.components_
faces_components.shape

# 출력 결과
(6, 4096)
# 시각화
show_faces(faces_components)

 

 

6. Dictionary Learning

  • Sparse code를 사용하여 데이터를 가장 잘 나타내는 사전 찾기
  • Sparse coding은 overcomplete 기저 벡터(basis vector)(기저보다 많은 수의 함수로 프레임 표현하는 )를 기반으로 데이터를 효율적으로 표현하기 위한 개발
  • 기저 벡터는 벡터 공간에 속하는 벡터의 집합이 선형 독립이고, 다른 모든 벡터 공간의 벡터들이 그 벡터 집합의 선형 조합으로 나타남
  • 이웃한 픽셀들의 가능한 모든 조합으로 이미지를 재정의
  • 픽셀 정보를 넘어서 더 풍부한 표현력으로 이미지를 설명, 인식의 정확성 향상
model = DictionaryLearning(n_components = 2, random_state = 0)
model.fit(iris)
transformed_iris = model.transform(iris)
transformed_iris.shape

# 출력 결과
(150, 2)
# 시각화
plot_iris(transformed_iris, labels)

기저 벡터만 남음

model = DictionaryLearning(n_components = 6)
model.fit(faces)
faces_components = model.components_
faces_components.shape

# 출력 결과
(6, 4096)
# 시각화
show_faces(faces_components)

 

 

7. Factor Analysis

  • 요인 분석은 변수들 간의 상관관계를 고려하여 저변에 내재된 개념인 요인들을 추출해내는 분석방법
  • 요인 분석은 변수들 간의 상관관계를 고려하여 서로 유사한 변수들끼지 묶어주는 방법
  • PCA에선느 오차(error)를 고려하지 않고, 요인 분석에서는 오차(error)를 고려
model = FactorAnalysis(n_components = 2, random_state = 0)
model.fit(iris)
transformed_iris = model.transform(iris)
transformed_iris.shape

# 출력 결과
(150, 2)
# 시각화
plot_iris(transformed_iris, labels)

model = FactorAnalysis(n_components = 6)
model.fit(faces)
faces_components = model.components_
faces_components.shape

# 출력 결과
(6, 4096)
# 시각화
show_faces(faces_components)

 

 

8. Independent Component Analysis(ICA)

  • 독립 성분 분석은 다변량의 신호를 통계적으로 독립적인 하부 성분으로 분리하는 계산 방법
  • ICA는 주성분을 이용하는 점은 PCA와 유사하지만, 데이터를 가장 잘 설명하는 축을 찾는 PCA와 달리 가장 독립적인 축, 독립성이 최대가 되는 벡터를 찾음
model = FastICA(n_components = 2, random_state = 0)
model.fit(iris)
transformed_iris = model.transform(iris)
transformed_iris.shape

# 출력 결과
(150, 2)
# 시각화
plot_iris(transformed_iris, labels)

model = FastICA(n_components = 6)
model.fit(faces)
faces_components = model.components_
faces_components.shape

# 출력 결과
(6, 4096)
# 시각화
show_faces(faces_components)

 

 

9. Non-negative Matrix Factorization

  • 음수 미포함 행렬 분해는 음수를 포함하지 않은 행렬 V를 음수를 포함하지 않은 행렬 W와 H의 곱으로 분해하는 알고리즘
  • 숫자 5를 분해하면 2+3, 1+4 등으로 분리 가능 -> 이것과 마찬가지로 행렬을 어떤 두 개의 행렬의 곱으로 분해
model = NMF(n_components = 2, random_state = 0)
model.fit(iris)
transformed_iris = model.transform(iris)
transformed_iris.shape

# 출력 결과
(150, 2)
# 시각화
plot_iris(transformed_iris, labels)

model = NMF(n_components = 6)
model.fit(faces)
faces_components = model.components_
faces_components.shape

# 출력 결과
(6, 4096)
# 시각화
show_faces(faces_components)

어느정도 형체가 살아있음

 

 

10. Latent Dirichlet Allocation(LDA)

  • 잠재 디리클레 할당은 이산 자료들에 대한 확률적 생성 모형
  • 디리클레 분포에 따라 잠재적인 의미 구조를 파악
    • 디리클레 분포(Dirichlet distribution)는 연속 확률분포의 하나로, k차원의 실수 벡터 중 벡터의 요소가 양수이며 모든 요소를 더한 값이 1인 경우 (이를 k−1차원 단체라고 한다)에 대해 확률값이 정의되는 분포이다.
    • 디리클레 분포는 베이즈 통계학에서 다항 분포에 대한 사전 켤레확률이다. 이 성질을 이용하기 위해, 디리클레 분포는 베이즈 통계학에서의 사전 확률로 자주 사용된다.
model = LatentDirichletAllocation(n_components = 2, random_state = 0)
model.fit(iris)
transformed_iris = model.transform(iris)
transformed_iris.shape

# 출력 결과
(150, 2)
# 시각화
plot_iris(transformed_iris, labels)

디리클레 분포에 따라 일자로 나열됨

model = LatentDirichletAllocation(n_components = 6)
model.fit(faces)
faces_components = model.components_
faces_components.shape

# 출력 결과
(6, 4096)
# 시각화
show_faces(faces_components)

노이즈가 낀 형태,

 

 

11. Linear Discriminant Analysis(LDA)

  • LDA는 PCA와 유사하게 입력 데이터 세트를 저차원 공간에 통해 차원을 축소
  • LDA는 지도 학습 분류에서 사용하기 쉽도록 개별 클래스를 분별할 수 있는 기준을 최대한 유지하면서 차원 축소
  • 정답이 있는 지도학습에서 사용하기 때문에 faces 데이터에 대해서는 사용 불가능
model = LinearDiscriminantAnalysis(n_components = 2)
# 정답인 labels를 같이 모델에 넣어 학습시켜 줘야함
model.fit(iris, labels)
transformed_iris = model.transform(iris)
transformed_iris.shape

# 출력 결과
(150, 2)
# 시각화
plot_iris(transformed_iris, labels)

 

 

12. 압축된 표현을 사용한 학습

  • 원래의 digits 데이터와 분해(decomposition)된 digits 데이터의 cross val score 비교

  - digits 데이터 및 학습 모델 라이브러리 불러오기

from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_digits
from sklearn.model_selection import cross_val_score

# min_max 스케일링 하는 함수
def min_max_scale(x):
    min_value, max_value = np.min(x, 0), np.max(x, 0)
    x = (x - min_value) / (max_value - min_value)
    return x
    
# digits 데이터 그래프 그리는 함수
def plot_digits(digits, labels):
    digits = min_max_scale(digits)
    ax = plt.subplot(111, projection = '3d')
    for i in range(digits.shape[0]):
        ax.text(digits[i, 0], digits[i, 1], digits[i, 2],
                str(labels[i]), color = plt.cm.Set1(labels[i] / 10.),
                fontdict = {'weight': 'bold', 'size': 9})
    ax.view_init(4, -72)

# digits 데이터 불러온 뒤 NMF로 분해
# 기존 데이터와 분해된 데이터 shape 비교
digits = load_digits()
nmf = NMF(n_components = 3)
nmf.fit(digits.data)
decomposed_digits = nmf.transform(digits.data)
print(digits.data.shape)
print(decomposed_digits.shape)
print(decomposed_digits)

# 출력 결과
(1797, 64)
(1797, 3)
[[0.48392621 0.         1.24523912]
 [0.5829615  1.4676756  0.07150889]
 [0.61515882 1.10963207 0.387782  ]
 ...
 [0.55272665 1.26056519 0.72094739]
 [0.7872562  0.2789873  1.04952028]
 [0.78507412 0.67250884 0.92677982]]
# 시각화
plt.figure(figsize = (20, 10))
plot_digits(decomposed_digits, digits.target)

 

  - KNN

# 분해 전
knn = KNeighborsClassifier()
score = cross_val_score(
    estimator = knn,
    X = digits.data, y = digits.target,
    cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))

# 출력 결과
[0.94722222 0.95555556 0.96657382 0.98050139 0.9637883 ]
mean cross val score: 0.9627282575054161 (+/- 0.011168537355954218)


# 분해 후
knn = KNeighborsClassifier()
score = cross_val_score(
    estimator = knn,
    X = decomposed_digits, y = digits.target,
    cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))

# 출력 결과
[0.54722222 0.58055556 0.64066852 0.59610028 0.56267409]
mean cross val score: 0.5854441349427422 (+/- 0.03214521445075084)

 

  - SVC

# 분해 전
svm = SVC()
score = cross_val_score(
    estimator = svm,
    X = digits.data, y = digits.target,
    cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))

# 출력 결과
[0.96111111 0.94444444 0.98328691 0.98885794 0.93871866]
mean cross val score: 0.9632838130609718 (+/- 0.02008605863225686)

# 분해 후
svm = SVC()
score = cross_val_score(
    estimator = svm,
    X = decomposed_digits, y = digits.target,
    cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))

# 출력 결과
[0.61388889 0.62222222 0.66016713 0.60167131 0.59888579]
mean cross val score: 0.6193670690188796 (+/- 0.022070024720937543)

 

  - Decision Tree

# 분해 전
decision_tree = DecisionTreeClassifier()
score = cross_val_score(
    estimator = decision_tree,
    X = digits.data, y = digits.target,
    cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))

# 출력 결과
[0.78333333 0.69722222 0.78830084 0.83286908 0.78830084]
mean cross val score: 0.7780052615289385 (+/- 0.04421837659784472)


# 분해 후
decision_tree = DecisionTreeClassifier()
score = cross_val_score(
    estimator = decision_tree,
    X = decomposed_digits, y = digits.target,
    cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))

# 출력 결과
[0.57222222 0.50833333 0.57938719 0.5821727  0.52924791]
mean cross val score: 0.5542726709996905 (+/- 0.0298931375955385)

 

  - Random Forest

# 분해 전
random_forest = RandomForestClassifier()
score = cross_val_score(
    estimator = random_forest,
    X = digits.data, y = digits.target,
    cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))

# 출력 결과
[0.93888889 0.90555556 0.97214485 0.96657382 0.91086351]
mean cross val score: 0.9388053234292789 (+/- 0.02745509790638632)


# 분해 후
random_forest = RandomForestClassifier()
score = cross_val_score(
    estimator = random_forest,
    X = decomposed_digits, y = digits.target,
    cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))

# 출력 결과
[0.58888889 0.60277778 0.64066852 0.59052925 0.54874652]
mean cross val score: 0.594322191272052 (+/- 0.029463634023029848)

 

  - 전반적으로 분해한 데이터에서 성능이 떨어진 모습

 

 

13. 복원된 표현을 사용한 학습

  • 분해 후 복원된 행렬을 사용해 학습

  - 데이터 행렬 복원

# 분해된 행렬에 곱하기 연산을 통해 원래의 행렬로 복원
components = nmf.components_
reconstructed_digits = decomposed_digits @ components
print(digits.data.shape)
print(decomposed_digits.shape)
print(reconstructed_digits.shape)

# 출력 결과
(1797, 64)
(1797, 3)
(1797, 64)
# reconstructed digits 시각화
plt.figure(figsize = (16, 8))
plt.suptitle('Re-Constructed digits')
for i in range(10):
    plt.subplot(2, 5, i+1)
    plt.xticks([])
    plt.yticks([])
    plt.imshow(reconstructed_digits[i].reshape(8, 8))

 

  - KNN

knn = KNeighborsClassifier()
score = cross_val_score(
    estimator = knn,
    X = reconstructed_digits, y = digits.target,
    cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))

# 출력 결과
[0.54166667 0.59444444 0.66295265 0.57660167 0.57381616]
mean cross val score: 0.5898963169297431 (+/- 0.04029722337499952)

 

  - SVM

svm = SVC()
score = cross_val_score(
    estimator = svm,
    X = reconstructed_digits, y = digits.target,
    cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))

# 출력 결과
[0.62777778 0.60555556 0.66016713 0.61002786 0.5821727 ]
mean cross val score: 0.6171402042711235 (+/- 0.025969174809053776)

 

  - Decision Tree

decision_tree = DecisionTreeClassifier()
score = cross_val_score(
    estimator = decision_tree,
    X = reconstructed_digits, y = digits.target,
    cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))

# 출력 결과
[0.57777778 0.51666667 0.53481894 0.56824513 0.55153203]
mean cross val score: 0.5498081089445992 (+/- 0.0221279380012718)

 

  - Random Forest

random_forest = RandomForestClassifier()
score = cross_val_score(
    estimator = random_forest,
    X = reconstructed_digits, y = digits.target,
    cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))

# 출력 결과
[0.58055556 0.55833333 0.65181058 0.59610028 0.57660167]
mean cross val score: 0.592680284741566 (+/- 0.031916555892366645)

 

  - 새로 복원한 데이터에서도 큰 성능 향상점은 없음

 

 

14. 이미지 복원

# faces 데이터를 train와 test 데이터로 분리
from sklearn.model_selection import train_test_split

train_faces, test_faces = train_test_split(faces, test_size = 0.1)
show_faces(train_faces)

show_faces(test_faces)

# 테스트 데이터에는 랜덤한 점에 0값을 주어 검은색의 점으로 노이즈를 생성
damaged_faces = []
for face in test_faces:
    idx = np.random.choice(range(64 * 64), size = 1024)
    damaged_face = face.copy()
    damaged_face[idx] = 0.
    damaged_faces.append(damaged_face)
show_faces(damaged_faces)

 

# train 데이터로 NMF 분해 학습
nmf = NMF(n_components = 10)
nmf.fit(train_faces)
# 노이즈를 준 test 데이터를 NMF 모델에 넣어 분해하고 다시 복원하는 과정으로 노이즈 제거
# damaged_faces의 형식을 float32로 변환하는 과정 필요
damaged_faces = np.asarray(damaged_faces, dtype = np.float32)
matrix1 = nmf.transform(damaged_faces)
matrix2 = nmf.components_
show_faces(matrix1 @ matrix2)

# 분해 components를 조절하여 정교함 조절 가능, 더 높은 수로 분해할수록 더 정교하게 복원할 수 있음
nmf = NMF(n_components = 100)
nmf.fit(train_faces)

matrix1 = nmf.transform(damaged_faces)
matrix2 = nmf.components_
show_faces(matrix1 @ matrix2)

n_components = 10일때 보다 더 정교하게 복원됨

nmf = NMF(n_components = 300)
nmf.fit(train_faces)

matrix1 = nmf.transform(damaged_faces)
matrix2 = nmf.components_
show_faces(matrix1 @ matrix2)

● 다양체 학습

  • 높은 차원의 데이터를 저차원으로 축소하는 방법

https://scikit-learn.org/0.23/auto_examples/manifold/plot_compare_methods.html

  • 고차원 데이터를 2차원 또는 3차원으로 축소해 시각화에 활용할 수 있음
  • 차원 축소 과정에서 중요하지 않은 정보는 버려지고 중요한 정보만 남기 대문에 데이터 정제에 활용 가능

 

  • 데이터 생성 및 시각화 함수
# 필요 라이브러리
import numpy as np
import matplotlib.pyplot as plt
from sklearn import manifold
from sklearn import random_projection
from sklearn import datasets

# s_curve 데이터셋 생성
s_curve, color = datasets.make_s_curve(1000, random_state = 0)

# 손글씨 데이터셋 생성
digits, labels = datasets.load_digits(return_X_y = True)
# projection으로 만들기
rand_proj = random_projection.SparseRandomProjection(n_components = 3, random_state = 0)
projected_digits =rand_proj.fit_transform(digits)

# min_max_scale 하는 함수
def min_max_scale(x):
    min_value, max_value = np.min(x, 0), np.max(x, 0)
    x = (x - min_value) / (max_value - min_value)
    return x

# s_curve 시각화 함수
def plot_s_curve(s_curve, color, position, projection):
    s_curve = min_max_scale(s_curve)
    if projection == '3d':
        ax = plt.subplot(position, projection = projection)
        ax.scatter(s_curve[:, 0], s_curve[:, 1], s_curve[:, 2], c = color, cmap = plt.cm.Spectral)
        ax.view_init(4, -72)
    elif projection == '2d':
        ax = plt.subplot(position)
        ax.scatter(s_curve[:, 0], s_curve[:, 1], c = color, cmap = plt.cm.Spectral)

# 손글씨 시각화 함수
def plot_digits(digits, labels, position, projection):
    digits = min_max_scale(digits)
    if projection == '3d':
        ax = plt.subplot(position, projection = projection)
        for i in range(digits.shape[0]):
            ax.text(digits[i, 0], digits[i, 1], digits[i, 2], str(labels[i]),
                    color = plt.cm.Set1(labels[i] / 10.), fontdict = {'weight': 'bold', 'size': 9})
        ax.view_init(4, -72)
    elif projection == '2d':
        ax = plt.subplot(position)
        for i in range(digits.shape[0]):
            ax.text(digits[i, 0], digits[i, 1], str(labels[i]),
                    color = plt.cm.Set1(labels[i] / 10.), fontdict = {'weight': 'bold', 'size': 9})

 

  - 데이터 시각화

# 작성한 함수를 사용하여 s_curve와 손글씨 데이터를 각각 시각화(3차원으로)
fig = plt.figure(figsize = (20, 10))
plot_s_curve(s_curve, color, 121, '3d')
plot_digits(projected_digits, labels, 122, '3d')

# 작성한 함수를 사용하여 s_curve와 손글씨 데이터를 각각 시각화(2차원으로)
fig = plt.figure(figsize = (20, 10))
plot_s_curve(s_curve, color, 121, '2d')
plot_digits(projected_digits, labels, 122, '2d')

 

 

1. Locally Linear Embedding(LLE)

  • 국소 이웃 거리를 보존하는 저차원 임베딩을 찾음
# s_curve는 2차원으로
s_curve_lle = manifold.LocallyLinearEmbedding(n_neighbors = 30, n_components = 2,
                                              method = 'standard', random_state = 0).fit_transform(s_curve)

# 손글씨는 3차원으로
digits_lle = manifold.LocallyLinearEmbedding(n_neighbors = 30, n_components = 3,
                                              method = 'standard', random_state = 0).fit_transform(s_curve)
# 시각화
fig = plt.figure(figsize = (20, 10))
plot_s_curve(s_curve_lle, color, 121, '2d')
plot_digits(digits_lle, labels, 122, '3d')

 

 

2. Local Tangent Space Alignment(LTSA)

  • 탄젠트 공간을 통해 각 이웃의 국소 성질을 특성화
  • 국소 탄젠트 공간을 정렬
# LLE에서 method만 'ltsa'로 바꾸면 ltsa 사용 가능

s_curve_ltsa = manifold.LocallyLinearEmbedding(n_neighbors = 30, n_components = 2,
                                               method = 'ltsa', random_state = 0).fit_transform(s_curve)

digits_ltsa = manifold.LocallyLinearEmbedding(n_neighbors = 30, n_components = 3,
                                              method = 'ltsa', random_state = 0).fit_transform(digits)

 

# 시각화
fig = plt.figure(figsize = (20, 10))
plot_s_curve(s_curve_ltsa, color, 121, '2d')
plot_digits(digits_ltsa, labels, 122, '3d')

 

 

3. Hessian Eigenmapping

  • LLE의 문제를 해결한 다른 방법
    • LLE는 단순히 이웃간의 거리를 기반으로만 유지를 한다는 제한점이 있음
  • 국소 선형 구조를 복원하기 위해 각 이웃에서 hessian 기반의 이차 형태를 중심으로 회전
# LLE에서 method만 'hessian'으로 바꾸면 hessian 사용 가능

s_curve_hlle = manifold.LocallyLinearEmbedding(n_neighbors = 30, n_components = 2,
                                               method = 'hessian', random_state = 0).fit_transform(s_curve)

digits_hlle = manifold.LocallyLinearEmbedding(n_neighbors = 30, n_components = 3,
                                              method = 'hessian', random_state = 0).fit_transform(digits)
# 시각화
fig = plt.figure(figsize = (20, 10))
plot_s_curve(s_curve_hlle, color, 121, '2d')
plot_digits(digits_hlle, labels, 122, '3d')

s_curve데이터는 ltsa와 색깔이 반대로 나옴, 손글씨 데이터는 별 차이 없음

 

4. Modfied Locally Linear Embedding

  • 각 이웃에 여러 가중치 벡터를 사용
  • n_neighbors > n_components를 만족해야 함
# LLE에서 method만 'modified'로 바꾸면 modified 사용 가능

s_curve_mlle = manifold.LocallyLinearEmbedding(n_neighbors = 30, n_components = 2,
                                               method = 'modified', random_state = 0).fit_transform(s_curve)

digits_mlle = manifold.LocallyLinearEmbedding(n_neighbors = 30, n_components = 3,
                                              method = 'modified', random_state = 0).fit_transform(digits)
# 시각화
fig = plt.figure(figsize = (20, 10))
plot_s_curve(s_curve_mlle, color, 121, '2d')
plot_digits(digits_mlle, labels, 122, '3d')

 

5. Isomap

  • 초기의 다양체 학습 알고리즘
  • MDS와 커널 PCA의 확장으로 볼 수 있음
  • 모든 점들 사이의 측지 거리를 유지하는 저차원 임베딩을 찾음
s_curve_isomap = manifold.Isomap(n_neighbors = 30, n_components = 2).fit_transform(s_curve)
digits_isomap = manifold.Isomap(n_neighbors = 30, n_components = 3).fit_transform(digits)
# 시각화
fig = plt.figure(figsize = (20, 10))
plot_s_curve(s_curve_isomap, color, 121, '2d')
plot_digits(digits_isomap, labels, 122, '3d')

손글씨 데이터에서 위의 다른 분석에서는 한쪽에 몰려있던 것에 비해 측지거리를 유지한 모습을 확인할 수 있음

 

6. Multi Dimensional Scaling(MDS)

  • 고차원 공간에서의 거리를 고려하는 저차원 공간을 찾음
  • neighbors 개념이 없고 차원(components)만 지정
  • 거리를 각각 고려하며 동작하여 분석 속도가 느림
s_curve_mds = manifold.MDS(n_components = 2, random_state = 0).fit_transform(s_curve)
digits_mds = manifold.MDS(n_components = 3, random_state = 0).fit_transform(digits)
fig = plt.figure(figsize = (20, 10))
plot_s_curve(s_curve_mds, color, 121, '2d')
plot_digits(digits_mds, labels, 122, '3d')

s_curve 데이터에서는 s모양 유지, 손글씨 데이터에서는 거리를 유지하면서 골고루 퍼진 형태로 embedding

 

7. Spectral Embedding(SE)

  • 스펙트럼 분해를 통해 데이터의 저차원 표현을 찾음
  • 데이터의 점이 저차원 공간에서도 서로 가깝게 유지되도록 함
s_curve_se = manifold.SpectralEmbedding(n_components = 2, random_state = 0).fit_transform(s_curve)
digits_se = manifold.SpectralEmbedding(n_components = 3, random_state = 0).fit_transform(digits)
# 시각화
fig = plt.figure(figsize = (20, 10))
plot_s_curve(s_curve_se, color, 121, '2d')
plot_digits(digits_se, labels, 122, '3d')

모양이 펼쳐져 있지만 서로 가까이 유지되는 형태가 유지됨

 

 

8. t-distributed Stochastic Neighbor Embedding(t-SNE)

  • 데이터 포인트의 유사성을 확률로 변환
  • 국소(local) 구조에 민감
  • 국소 구조를 기반으로 샘플 그룹을 추출하는데 강함
  • 항상 KL발산의 국소 최소값에서 끝남
    • 쿨백-라이블러 발산(Kullback–Leibler divergence, KLD)은 두 확률분포의 차이를 계산하는 데에 사용하는 함수로, 어떤 이상적인 분포에 대해, 그 분포를 근사하는 다른 분포를 사용해 샘플링을 한다면 발생할 수 있는 정보 엔트로피 차이를 계산, 상대 엔트로피(relative entropy), 정보 획득량(information gain), 인포메이션 다이버전스(information divergence)라고도 한다.
  • 계산 비용이 많이 듦(시간이 오래 걸림)
  • 전역 구조를 보존하지 않음
s_curve_tsne = manifold.TSNE(n_components = 2, random_state = 0).fit_transform(s_curve)
digits_tsne = manifold.TSNE(n_components = 3, random_state = 0).fit_transform(digits)
# 시각화
fig = plt.figure(figsize = (20, 10))
plot_s_curve(s_curve_tsne, color, 121, '2d')
plot_digits(digits_tsne, labels, 122, '3d')

  • 단순히 s모양으로 된 데이터의 국소 구조를 유지한 채 임베딩이 된 모습
  • 포인트들 간의 유사성을 확률로 변환하는 알고리즘 동작 때문에 나온 분포
  • 손글끼는 데이터 포인트의 유사성을 확률로 변환하였기 때문에 잘 정리되어 임베딩된 모습

 

 

9. 정제된 표현을 이용한 학습

  • 다양체 학습의 결과를 정제된 데이터로 생각할 수 있음
  • 저차원 변환을 한 데이터를 다른 모델의 학습을 위한 데이터로 사용 가능
  • 정제된 표현이기 때문에 분석 비교적 용이함
  • 기계학습 모델의 입력으로 사용했을때 성능향상을 기대할 수 있음

 

  - 다른 모델에 활용 예시

# 다른 모델 라이브러리 불러오기
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score

knn = KNeighborsClassifier()
svm = SVC()
decision_tree = DecisionTreeClassifier()
random_forest = RandomForestClassifier()


# 손글씨 데이터 새로 불러오기
raw_digits, target = datasets.load_digits(return_X_y = True)

 

  - 원본 데이터 사용 시

# KNN
score = cross_val_score(
    estimator = knn,
    X = raw_digits, y = target,
    cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))

# 출력 결과
[0.94722222 0.95555556 0.96657382 0.98050139 0.9637883 ]
mean cross val score: 0.9627282575054161 (+/- 0.011168537355954218)


# SVM
score = cross_val_score(
    estimator = svm,
    X = raw_digits, y = target,
    cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))

# 출력 결과
[0.96111111 0.94444444 0.98328691 0.98885794 0.93871866]
mean cross val score: 0.9632838130609718 (+/- 0.02008605863225686)


# Decision Tree
score = cross_val_score(
    estimator = decision_tree,
    X = raw_digits, y = target,
    cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))

# 출력 결과
[0.77222222 0.73333333 0.79387187 0.82451253 0.78272981]
mean cross val score: 0.7813339523367379 (+/- 0.029700574831777498)


# Random Forest
score = cross_val_score(
    estimator = random_forest,
    X = raw_digits, y = target,
    cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))

# 출력 결과
[0.94166667 0.90833333 0.96100279 0.96657382 0.92479109]
mean cross val score: 0.9404735376044568 (+/- 0.021831308367203855)

 

  - 정데된 데이터 사용 시

# KNN
score = cross_val_score(
    estimator = knn,
    X = digits_tsne, y = target,
    cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))

# 출력 결과
[0.96388889 0.96388889 0.98328691 0.99164345 0.96935933]
mean cross val score: 0.9744134942742185 (+/- 0.011159643225931395)


# SVM
score = cross_val_score(
    estimator = svm,
    X = digits_tsne, y = target,
    cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))

# 출력 결과
[0.95       0.93333333 0.98328691 0.98885794 0.96100279]
mean cross val score: 0.9632961931290621 (+/- 0.02065358544710109)


# Decision Tree
score = cross_val_score(
    estimator = decision_tree,
    X = digits_tsne, y = target,
    cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))

# 출력 결과
[0.96944444 0.91388889 0.98050139 0.9637883  0.93314763]
mean cross val score: 0.9521541318477251 (+/- 0.024752179064191634)


# Random Forest
score = cross_val_score(
    estimator = random_forest,
    X = digits_tsne, y = target,
    cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))

# 출력 결과
[0.95833333 0.91944444 0.99164345 0.99164345 0.97214485]
mean cross val score: 0.966641906530486 (+/- 0.02674722386771949)

 

  - 원본 데이터를 썼을 때보다 tsne로 정제된 데이터를 썼을 때 score가 0.1 내외의 차이로 상승되어 나옴

  - 특히 Decision Tree 알고리즘에서 tsne로 정제된 데이터를 썼을 때 큰 향상이 이루어짐

● 군집화

  • 대표적인 비지도학습 알고리즘
  • 레이블(정답)이 없는 데이터를 그룹화하는 알고리즘

https://scikit-learn.org/stable/auto_examples/cluster/plot_cluster_comparison.html#sphx-glr-auto-examples-cluster-plot-cluster-comparison-py

 

  • 필요 라이브러리
import numpy as np
import matplotlib.pyplot as plt
from sklearn import cluster
from sklearn import mixture
from sklearn import datasets
from sklearn.preprocessing import StandardScaler
  • 예시 데이터 생성
# 그래프 그리는 함수 작성
def plot_data(datasets, position, title):
    X, y = datasets
    plt.subplot(position)
    plt.title(title)
    plt.scatter(X[:, 0], X[:, 1])
    
# 랜덤한 예시 데이터 생성을 위한 파라미터
np.random.seed(0)
n_samples = 1500
random_state = 0
noise = 0.05

# 여러 구조의 예시 데이터 작성
circles = datasets.make_circles(n_samples = n_samples, factor = 0.5, noise = noise, random_state = random_state)
moons = datasets.make_moons(n_samples = n_samples, noise = noise, random_state = random_state)
blobs = datasets.make_blobs(n_samples = n_samples, random_state = random_state)
no_structures = np.random.rand(n_samples, 2), None

# 그래프 그리는 함수로 예시 데이터 시각화
plt.figure(figsize = (12, 12))
plot_data(circles, 221, 'Circles')
plot_data(moons, 222, 'Moons')
plot_data(blobs, 223, 'Blobs')
plot_data(no_structures, 224, 'No structures')

 

  • 군집화를 위해 학습, 예측, 시각화하는 과정을 하나의 함수로 작성
# 학습하고, 예측하고, 시각화하는 함수
def fit_predict_plot(model, dataset, position, title):
    X, y = dataset
    model.fit(X)
    if hasattr(model, 'labels_'):
        labels = model.labels_.astype(np.int)
    else:
        labels = model.predict(X)
    
    colors = np.array(['#30A9DE', '#E53A40', '#090707', '#A593E0', '#F6B352', '##519D9E', '#D81159', '#8CD790', '#353866'])
    ax = plt.subplot(position)
    ax.set_title(title)
    ax.scatter(X[:, 0], X[:, 1], color = colors[labels])

 

 

1. K-Means

  • n개의 등분산 그룹으로 군집화
  • 제곱합 함수를 최소화
  • 군집화 개수를 지정해야 함
  • 각 군집 \(C\)의 평균 \(\mu_{j}\)을 중심점이라고 함
  • 다음을 만족하는 중심점을 찾는 것이 목표

$$ \sum_{i=0}^{n}\underset{\mu _{j}\in C}{min}(\left\| x_{i}-\mu_{j}\right\|^2) $$

  • 거리를 기반으로 계산

 

  - 만들어둔 예시 데이터와 함수를 기반으로 군집화 및 시각화

fig = plt.figure(figsize = (12, 12))
fig.suptitle('K-Means')
fit_predict_plot(cluster.KMeans(n_clusters = 2, random_state = random_state), circles, 221, 'Circles')
fit_predict_plot(cluster.KMeans(n_clusters = 2, random_state = random_state), moons, 222, 'Moons')
fit_predict_plot(cluster.KMeans(n_clusters = 2, random_state = random_state), blobs, 223, 'Blobs')
fit_predict_plot(cluster.KMeans(n_clusters = 2, random_state = random_state), no_structures, 224, 'No structures')

 

  - 군집 개수를 3개로

fig = plt.figure(figsize = (12, 12))
fig.suptitle('K-Means')
fit_predict_plot(cluster.KMeans(n_clusters = 3, random_state = random_state), circles, 221, 'Circles')
fit_predict_plot(cluster.KMeans(n_clusters = 3, random_state = random_state), moons, 222, 'Moons')
fit_predict_plot(cluster.KMeans(n_clusters = 3, random_state = random_state), blobs, 223, 'Blobs')
fit_predict_plot(cluster.KMeans(n_clusters = 3, random_state = random_state), no_structures, 224, 'No structures')

 

  - 군집 개수를 4개로

fig = plt.figure(figsize = (12, 12))
fig.suptitle('K-Means')
fit_predict_plot(cluster.KMeans(n_clusters = 4, random_state = random_state), circles, 221, 'Circles')
fit_predict_plot(cluster.KMeans(n_clusters = 4, random_state = random_state), moons, 222, 'Moons')
fit_predict_plot(cluster.KMeans(n_clusters = 4, random_state = random_state), blobs, 223, 'Blobs')
fit_predict_plot(cluster.KMeans(n_clusters = 4, random_state = random_state), no_structures, 224, 'No structures')

 

 

  - 붓꽃 데이터 군집화

from sklearn.datasets import load_iris
iris = load_iris()

model = cluster.KMeans(n_clusters = 3)
model.fit(iris.data)
predict = model.predict(iris.data)

# 예측결과가 0인 것에 인덱스 부여
idx = np.where(predict == 0)
iris.target[idx]

# 예측결과가 1인 것에 인덱스 부여
idx = np.where(predict == 1)
iris.target[idx]

# 예측결과가 2인 것에 인덱스 부여
idx = np.where(predict == 2)
iris.target[idx]

 

2. 미니 배치 K-Means

  • 배치 처리를 통해 계산 시간을 줄인 K-평균
  • K-평균과 다른 결과가 나올 수 있음

 

  - 위의 예제 데이터로 실습

  - 군집 2개로 군집화

fig = plt.figure(figsize = (12, 12))
fig.suptitle('MiniBatch K-Means')
fit_predict_plot(cluster.MiniBatchKMeans(n_clusters = 2, random_state = random_state), circles, 221, 'Circles')
fit_predict_plot(cluster.MiniBatchKMeans(n_clusters = 2, random_state = random_state), moons, 222, 'Moons')
fit_predict_plot(cluster.MiniBatchKMeans(n_clusters = 2, random_state = random_state), blobs, 223, 'Blobs')
fit_predict_plot(cluster.MiniBatchKMeans(n_clusters = 2, random_state = random_state), no_structures, 224, 'No structures')

  - 군집 3개로 군집화

fig = plt.figure(figsize = (12, 12))
fig.suptitle('MiniBatch K-Means')
fit_predict_plot(cluster.MiniBatchKMeans(n_clusters = 3, random_state = random_state), circles, 221, 'Circles')
fit_predict_plot(cluster.MiniBatchKMeans(n_clusters = 3, random_state = random_state), moons, 222, 'Moons')
fit_predict_plot(cluster.MiniBatchKMeans(n_clusters = 3, random_state = random_state), blobs, 223, 'Blobs')
fit_predict_plot(cluster.MiniBatchKMeans(n_clusters = 3, random_state = random_state), no_structures, 224, 'No structures')

  - 군집 4개로 군집화

fig = plt.figure(figsize = (12, 12))
fig.suptitle('MiniBatch K-Means')
fit_predict_plot(cluster.MiniBatchKMeans(n_clusters = 4, random_state = random_state), circles, 221, 'Circles')
fit_predict_plot(cluster.MiniBatchKMeans(n_clusters = 4, random_state = random_state), moons, 222, 'Moons')
fit_predict_plot(cluster.MiniBatchKMeans(n_clusters = 4, random_state = random_state), blobs, 223, 'Blobs')
fit_predict_plot(cluster.MiniBatchKMeans(n_clusters = 4, random_state = random_state), no_structures, 224, 'No structures')

 

 

3. Affinity Propagation

  • 샘플 쌍끼리 메시지를 보내 군집을 생성
  • 샘플을 대표하는 적절한 예를 찾을 때까지 반복
  • 군집의 개수를 자동으로 정함

https://datascienceschool.net/03%20machine%20learning/16.05%20Affinity%20Propagation.html

 

  - 위의 예제 데이터로 실습

fig = plt.figure(figsize = (12, 12))
fig.suptitle('Affinity Propagation')

# 군집의 개수를 정할 필요 없고 대신, damping과 preference를 파라미터로 작성
# damping: 알고리즘의 매 반복마다 Responsiblity Matrix와 Availability Matrix를 업데이트할 때 Exponential weighted average를 적용
# preference: 각 data point들이 얼마다 exemplar로 선택될 가능성이 높은지를 지정
fit_predict_plot(cluster.AffinityPropagation(damping = .9, preference = -200), circles, 221, 'Circles')
fit_predict_plot(cluster.AffinityPropagation(damping = .9, preference = -200), moons, 222, 'Moons')
fit_predict_plot(cluster.AffinityPropagation(damping = .9, preference = -200), blobs, 223, 'Blobs')
fit_predict_plot(cluster.AffinityPropagation(damping = .9, preference = -200), no_structures, 224, 'No structures')

각 구조마다 적절한 군집 개수를 스스로 결정항 군집화한 모습

 

 

4. Mean Shift

  •  중심점 후보를 정해진 구역 내 평균으로 업데이트
fig = plt.figure(figsize = (12, 12))
fig.suptitle('Mean Shift')

# 파라미터는 따로 지정 안함
fit_predict_plot(cluster.MeanShift(), circles, 221, 'Circles')
fit_predict_plot(cluster.MeanShift(), moons, 222, 'Moons')
fit_predict_plot(cluster.MeanShift(), blobs, 223, 'Blobs')
fit_predict_plot(cluster.MeanShift(), no_structures, 224, 'No structures')

 

 

5. 스펙트럼 군집화

fig = plt.figure(figsize = (12, 12))
fig.suptitle('Spectral Clustering')
fit_predict_plot(cluster.SpectralClustering(n_clusters = 2, random_state = random_state), circles, 221, 'Circles')
fit_predict_plot(cluster.SpectralClustering(n_clusters = 2, random_state = random_state), moons, 222, 'Moons')
fit_predict_plot(cluster.SpectralClustering(n_clusters = 2, random_state = random_state), blobs, 223, 'Blobs')
fit_predict_plot(cluster.SpectralClustering(n_clusters = 2, random_state = random_state), no_structures, 224, 'No structures')

  - 군집 3개

fig = plt.figure(figsize = (12, 12))
fig.suptitle('Spectral Clustering')
fit_predict_plot(cluster.SpectralClustering(n_clusters = 3, random_state = random_state), circles, 221, 'Circles')
fit_predict_plot(cluster.SpectralClustering(n_clusters = 3, random_state = random_state), moons, 222, 'Moons')
fit_predict_plot(cluster.SpectralClustering(n_clusters = 3, random_state = random_state), blobs, 223, 'Blobs')
fit_predict_plot(cluster.SpectralClustering(n_clusters = 3, random_state = random_state), no_structures, 224, 'No structures')

  - 군집 4개

fig = plt.figure(figsize = (12, 12))
fig.suptitle('Spectral Clustering')
fit_predict_plot(cluster.SpectralClustering(n_clusters = 4, random_state = random_state), circles, 221, 'Circles')
fit_predict_plot(cluster.SpectralClustering(n_clusters = 4, random_state = random_state), moons, 222, 'Moons')
fit_predict_plot(cluster.SpectralClustering(n_clusters = 4, random_state = random_state), blobs, 223, 'Blobs')
fit_predict_plot(cluster.SpectralClustering(n_clusters = 4, random_state = random_state), no_structures, 224, 'No structures')

 

 

  - 유방암 데이터 군집화

from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()

model = cluster.SpectralClustering(n_clusters = 2, eigen_solver = 'arpack', affinity = 'nearest_neighbors')
model.fit(cancer.data)
predict = model.labels_
# 0으로 predict 한 target 표시
idx = np.where(predict == 0)
cancer.target[idx]

# 출력 결과
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1,
       1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1,
       0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
       1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1,
       1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0,
       0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1,
       1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1,
       1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0,
       1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0,
       0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0])
       
       

# 1로 predict 한 target 표시
idx = np.where(predict == 1)
cancer.target[idx]

# 출력 결과
array([0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1])

 

 

6. 계층 군집화

fig = plt.figure(figsize = (12, 12))
fig.suptitle('Hierarchical Clustering')
fit_predict_plot(cluster.AgglomerativeClustering(n_clusters = 2, linkage = 'ward'), circles, 221, 'Circles')
fit_predict_plot(cluster.AgglomerativeClustering(n_clusters = 2, linkage = 'ward'), moons, 222, 'Moons')
fit_predict_plot(cluster.AgglomerativeClustering(n_clusters = 2, linkage = 'ward'), blobs, 223, 'Blobs')
fit_predict_plot(cluster.AgglomerativeClustering(n_clusters = 2, linkage = 'ward'), no_structures, 224, 'No structures')

  - 군집 3개

fig = plt.figure(figsize = (12, 12))
fig.suptitle('Hierarchical Clustering')
fit_predict_plot(cluster.AgglomerativeClustering(n_clusters = 3, linkage = 'ward'), circles, 221, 'Circles')
fit_predict_plot(cluster.AgglomerativeClustering(n_clusters = 3, linkage = 'ward'), moons, 222, 'Moons')
fit_predict_plot(cluster.AgglomerativeClustering(n_clusters = 3, linkage = 'ward'), blobs, 223, 'Blobs')
fit_predict_plot(cluster.AgglomerativeClustering(n_clusters = 3, linkage = 'ward'), no_structures, 224, 'No structures')

  - 군집 4개

fig = plt.figure(figsize = (12, 12))
fig.suptitle('Hierarchical Clustering')
fit_predict_plot(cluster.AgglomerativeClustering(n_clusters = 4, linkage = 'ward'), circles, 221, 'Circles')
fit_predict_plot(cluster.AgglomerativeClustering(n_clusters = 4, linkage = 'ward'), moons, 222, 'Moons')
fit_predict_plot(cluster.AgglomerativeClustering(n_clusters = 4, linkage = 'ward'), blobs, 223, 'Blobs')
fit_predict_plot(cluster.AgglomerativeClustering(n_clusters = 4, linkage = 'ward'), no_structures, 224, 'No structures')

 

  - 와인 데이터 군집화

from sklearn.datasets import load_wine
wine = load_wine()

model = cluster.AgglomerativeClustering(n_clusters = 3)
model.fit(wine.data)
predict = model.labels_

idx = np.where(predict == 0)
wine.target[idx]

# 출력 결과
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 1, 1])


idx = np.where(predict == 1)
wine.target[idx]

# 출력 결과
array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
       2, 2, 2, 2, 2, 2])


idx = np.where(predict == 2)
wine.target[idx]

# 출력 결과
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
       2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2])

 

 

7. DBSCAN(Density-Based Spatial Clustering of Applications with Noise)

fig = plt.figure(figsize = (12, 12))
fig.suptitle('DBSCAN')
fit_predict_plot(cluster.DBSCAN(eps = .3), circles, 221, 'Circles')
fit_predict_plot(cluster.DBSCAN(eps = .3), moons, 222, 'Moons')
fit_predict_plot(cluster.DBSCAN(eps = .3), blobs, 223, 'Blobs')
fit_predict_plot(cluster.DBSCAN(eps = .3), no_structures, 224, 'No structures')

 

 

8. OPTICS(Ordering Points To Identify the Clustering Structure)

fig = plt.figure(figsize = (12, 12))
fig.suptitle('DOPTICS')
fit_predict_plot(cluster.OPTICS(min_samples = 20, xi = 0.05, min_cluster_size = 0.1), circles, 221, 'Circles')
fit_predict_plot(cluster.OPTICS(min_samples = 20, xi = 0.05, min_cluster_size = 0.1), moons, 222, 'Moons')
fit_predict_plot(cluster.OPTICS(min_samples = 20, xi = 0.05, min_cluster_size = 0.1), blobs, 223, 'Blobs')
fit_predict_plot(cluster.OPTICS(min_samples = 20, xi = 0.05, min_cluster_size = 0.1), no_structures, 224, 'No structures')

 

 

9. Birch(Balanced iterative redcing and clustering using hierarchies)

fig = plt.figure(figsize = (12, 12))
fig.suptitle('Birch')
fit_predict_plot(cluster.Birch(n_clusters = 2, threshold = .3), circles, 221, 'Circles')
fit_predict_plot(cluster.Birch(n_clusters = 2, threshold = .3), moons, 222, 'Moons')
fit_predict_plot(cluster.Birch(n_clusters = 2, threshold = .3), blobs, 223, 'Blobs')
fit_predict_plot(cluster.Birch(n_clusters = 2, threshold = .3), no_structures, 224, 'No structures')

  - 군집 3개

fig = plt.figure(figsize = (12, 12))
fig.suptitle('Birch')
fit_predict_plot(cluster.Birch(n_clusters = 3, threshold = .3), circles, 221, 'Circles')
fit_predict_plot(cluster.Birch(n_clusters = 3, threshold = .3), moons, 222, 'Moons')
fit_predict_plot(cluster.Birch(n_clusters = 3, threshold = .3), blobs, 223, 'Blobs')
fit_predict_plot(cluster.Birch(n_clusters = 3, threshold = .3), no_structures, 224, 'No structures')

 

 

10. 손글씨 데이터 군집화

  - 데이터 확인

 
from sklearn.datasets import load_digits

digits = load_digits()

X = digits.data.reshape(-1, 8, 8)
y= digits.target

plt.figure(figsize = (16, 8))
for i in range(10):
    plt.subplot(2, 5, i+1)
    plt.imshow(X[i])

 

  - K-Means

kmeans = cluster.KMeans(n_clusters = 10)
kmeans.fit(digits.data)
predict = kmeans.predict(digits.data)

# 텍스트로 예측 결과 확인
for i in range(10):
    idx = np.where(predict == i)
    real_class = digits.target[idx]
    print('Cluster {}: {}'.format(i+1, real_class))
    
# 이미지로 예측 결과 확인
for i in range(10):
    idx = np.where(predict == i)[0]
    choice_idx = np.random.choice(idx, size = 5)
    choice_image = X[choice_idx]

    k = 1

    print('Cluster: {}'.format(i+1))
    for image in choice_image:
        plt.subplot(1, 5, k)
        plt.xticks([])
        plt.yticks([])
        plt.imshow(image)
        k += 1
    
    plt.show()

 

  - 스펙트럼 군집화

spectral = cluster.SpectralClustering(n_clusters = 10, eigen_solver = 'arpack', affinity = 'nearest_neighbors')
spectral.fit(digits.data)
predict = spectral.labels_

# 텍스트로 예측 결과 확인
for i in range(10):
    idx = np.where(predict == i)
    real_class = digits.target[idx]
    print('Cluster {}: {}'.format(i+1, real_class))

# 이미지로 예측 결과 확인
for i in range(10):
    idx = np.where(predict == i)[0]
    choice_idx = np.random.choice(idx, size = 5)
    choice_image = X[choice_idx]

    k = 1

    print('Cluster: {}'.format(i+1))
    for image in choice_image:
        plt.subplot(1, 5, k)
        plt.xticks([])
        plt.yticks([])
        plt.imshow(image)
        k += 1
    
    plt.show()

 

  - 계층 군집화

hierarchical = cluster.AgglomerativeClustering(n_clusters = 10, linkage = 'ward')
hierarchical.fit(digits.data)
predict = hierarchical.labels_

# 텍스트로 예측 결과 확인
for i in range(10):
    idx = np.where(predict == i)
    real_class = digits.target[idx]
    print('Cluster {}: {}'.format(i+1, real_class))

# 이미지로 예측 결과 확인
for i in range(10):
    idx = np.where(predict == i)[0]
    choice_idx = np.random.choice(idx, size = 5)
    choice_image = X[choice_idx]

    k = 1

    print('Cluster: {}'.format(i+1))
    for image in choice_image:
        plt.subplot(1, 5, k)
        plt.xticks([])
        plt.yticks([])
        plt.imshow(image)
        k += 1
    
    plt.show()

 

  - Birch

birch = cluster.Birch(n_clusters = 10, threshold = .3)
birch.fit(digits.data)
predict = birch.labels_

# 텍스트로 예측 결과 확인
for i in range(10):
    idx = np.where(predict == i)
    real_class = digits.target[idx]
    print('Cluster {}: {}'.format(i+1, real_class))

# 이미지로 예측 결과 확인
for i in range(10):
    idx = np.where(predict == i)[0]
    choice_idx = np.random.choice(idx, size = 5)
    choice_image = X[choice_idx]

    k = 1

    print('Cluster: {}'.format(i+1))
    for image in choice_image:
        plt.subplot(1, 5, k)
        plt.xticks([])
        plt.yticks([])
        plt.imshow(image)
        k += 1
    
    plt.show()

● XGBoost

  • 트리 기반의 앙상블 기법
  • 분류에 있어서 다른 알고리즘보다 좋은 예측 성능을 보여줌
  • XGBoost는 GBM 기반이지만, GBM의 단점인 느린 수행 시간과 과적합 규제 부재 등의 문제를 해결
  • 병렬 CPU 환경에서 빠르게 학습 가능

 

  • 필요 라이브러리
from sklearn.datasets import load_iris, load_breast_cancer, load_wine, load_diabetes
from sklearn.model_selection import train_test_split, cross_validate
from sklearn.metrics import accuracy_score, precision_score, recall_score

import xgboost as xgb
from xgboost import XGBClassifier, XGBRegressor
from xgboost import plot_importance, plot_tree

import graphviz
import matplotlib.pyplot as plt

 

1. 파이썬 기반 XGBoost

  - 유방암 데이터로 연습

cancer = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, test_size = 0.2, random_state = 42)

# XGBoost는 XGBoost만의 데이터 형식인 DMatrix 형식을 사용하므로 다음과 같이 train과 test 데이터를 변환
dtrain = xgb.DMatrix(data = X_train, label = y_train)
dtest = xgb.DMatrix(data = X_test, label = y_test)

# 파라미터
params = {
    'max_depth': 3,
    'eta': 0.1,
    'objective': 'binary:logistic',
    'eval_metric': 'logloss',
    'early_stopping': 100
}
num_rounds = 400

# 모델 생성
evals = [(dtrain, 'train'), (dtest, 'eval')]
xgb_model = xgb.train(params = params, dtrain = dtrain, num_boost_round = num_rounds, early_stopping_rounds = 100, evals = evals)

 

  - xgb_model 생성 결과, logloss가 더 이상 감소되지 않을 때까지 모델 최적화

  - 계속 감소한다면 사전에 정해둔 round 횟수(num_rounds = 400)만큼만 진행한 뒤 모델 생성 종료

  - 가장 마지막 모델로 생성

 

  - train 데이터로 생성한 모델에 test 데이터를 넣어 예측값 출력(상위 10개만)

import numpy as np

predicts = xgb_model.predict(dtest)
print(np.round(predicts[:10], 3))

 

  - 정확도, 정밀도, 재현율 출력

# 데이터 생성 결과를 이원화(0 또는 1로)하여 2*2 테이블 상에서 정확도, 정밀도, 재현율을 계산할 수 있도록 변경
preds = [1 if x > 0.5 else 0 for x in predicts]
print(preds[:10])

print("정확도: {}".format(accuracy_score(y_test, preds)))
print("정밀도: {}".format(precision_score(y_test, preds)))
print("재현율: {}".format(recall_score(y_test, preds)))

# 출력 결과
정확도: 0.9736842105263158
정밀도: 0.9722222222222222
재현율: 0.9859154929577465

 

  - 변수들의 중요도를 알아보기 위한 그래프 출력

fig, ax = plt.subplots(figsize = (10, 12))
plot_importance(xgb_model, ax = ax)

 

  - xgb 모델을 트리 형식으로 표현한 그래프 출력

dot_data = xgb.to_graphviz(xgb_model)
dot_data

 

2. XGBClassifier

  - 붓꽃 데이터

iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size = 0.2, random_state = 42)

  - XGBClassifier 모델 생성

xgbc = XGBClassifier(n_estimators = 400, learning_rate = 0.1, max_depth = 3)
xgbc.fit(X_train, y_train)
preds = xgbc.predict(X_test)
preds_proba = xgbc.predict_proba(X_test)[:, 1]

cross_val = cross_validate(
    estimator = xgbc,
    X = iris.data, y = iris.target,
    cv = 5
)
print('avg fit time: {} (+/- {})'.format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print('avg score time: {} (+/- {})'.format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print('avg test score: {} (+/- {})'.format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 0.38040671348571775 (+/- 0.12548339326469174)
avg score time: 0.0038000106811523437 (+/- 0.001165665213187403)
avg test score: 0.96 (+/- 0.024944382578492935)

  - 모델에서 데이터의 각 변수 중요도 출력

fig, ax = plt.subplots(figsize = (10, 12))
plot_importance(xgbc, ax = ax)

  - xgb 모델을 트리 형식으로 출력

dot_data = xgb.to_graphviz(xgbc)
dot_data

 

 

  - 와인 데이터

wine = load_wine()
X_train, X_test, y_train, y_test = train_test_split(wine.data, wine.target, test_size = 0.2, random_state = 42)

  - XGBClassifier 모델 생성

  - 파라미터의 종류

  • n_estimators : 학습 모델의 수, 많아질수록 성능 향상의 가능성이 있으나, 속도가 느려짐
  • learning_rate : 학습률, 너무 크면 gradient 발산의 가능성이 있으며, 너무 작으면 학습이 느림
  • max_depth : 최대 탐색 깊이, 너무 크면 과적합의 가능성, 너무 작으면 학습 성능 저하
  • min_samples_split : 분할 종료 최소 샘플 수, 큰 수면 과적합을 막지만 학습 성능 저하 가능성
  • min_samples_leaf : leaf node가 되기 위한 최소 샘플 수, min_samples_split과 비슷한 용도
xgbc = XGBClassifier(n_estimators = 400, learning_rate = 0.1, max_depth = 3)
xgbc.fit(X_train, y_train)
preds = xgbc.predict(X_test)
preds_proba = xgbc.predict_proba(X_test)[:, 1]

cross_val = cross_validate(
    estimator = xgbc,
    X = iris.data, y = iris.target,
    cv = 5
)
print('avg fit time: {} (+/- {})'.format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print('avg score time: {} (+/- {})'.format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print('avg test score: {} (+/- {})'.format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 0.8099905490875244 (+/- 0.2795799385941016)
avg score time: 0.004575538635253906 (+/- 0.0016525575848722433)
avg test score: 0.96 (+/- 0.024944382578492935)

  - 모델에서 데이터의 각 변수 중요도 출력

fig, ax = plt.subplots(figsize = (10, 12))
plot_importance(xgbc, ax = ax)

  - xgb 모델을 트리 형식으로 출력

dot_data = xgb.to_graphviz(xgbc)
dot_data

 

 

3. XGBRegressor

  - XGB는 Classifier에 더 특화되어 있지만 Regressor도 할 수는 있음

  - 당뇨병 데이터

diabetes = load_diabetes()
X_train, X_test, y_train, y_test = train_test_split(diabetes.data, diabetes.target, test_size = 0.2, random_state = 42)

  - XGBRegressor 모델 생성

  - 파라미터의 종류

  • n_estimators : 학습 모델의 수, 많아질수록 성능 향상의 가능성이 있으나, 속도가 느려짐
  • learning_rate : 학습률, 너무 크면 gradient 발산의 가능성이 있으며, 너무 작으면 학습이 느림
  • max_depth : 최대 탐색 깊이, 너무 크면 과적합의 가능성, 너무 작으면 학습 성능 저하
  • min_samples_split : 분할 종료 최소 샘플 수, 큰 수면 과적합을 막지만 학습 성능 저하 가능성
  • min_samples_leaf : leaf node가 되기 위한 최소 샘플 수, min_samples_split과 비슷한 용도
  • objective : 목적함수, reg:linear(linear-regression), binary:logistic(binary-logistic-classification), count:poisson(count data poison regression) 등 다양

  - XGBClassifier보다 test score가 낮게 나

xgbr = XGBRegressor(n_estimators = 400, learning_rate = 0.1, max_depth = 3, objective = 'reg:squarederror')
xgbr.fit(X_train, y_train)
preds = xgbr.predict(X_test)

cross_val = cross_validate(
    estimator = xgbr,
    X = diabetes.data, y = diabetes.target,
    cv = 5
)
print('avg fit time: {} (+/- {})'.format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print('avg score time: {} (+/- {})'.format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print('avg test score: {} (+/- {})'.format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 0.3746964454650879 (+/- 0.027832593554179098)
avg score time: 0.005658674240112305 (+/- 0.0017620359721342335)
avg test score: 0.30005291115066424 (+/- 0.07589309667544569)

  - 모델에서 데이터의 각 변수 중요도 출력

fig, ax = plt.subplots(figsize = (10, 12))
plot_importance(xgbr, ax = ax)

  - xgb 모델을 트리 형식으로 출력

dot_data = xgb.to_graphviz(xgbr)
dot_data

 

 

● LightGBM

  • XGBoost보다 2년쯤 뒤에 나와 더 개선됨
  • 빠른 학습과 예측 시간
  • 더 적은 메모리 사용
  • 범주형 특징의 자동 변환과 최적 분할

 

  • 필요 라이브러리
from lightgbm import LGBMClassifier, LGBMRegressor
from lightgbm import plot_importance, plot_metric, plot_tree

 

1. LGBMClassifier

  - 붓꽃 데이터

iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size = 0.2, random_state = 42)

  - LGBMClassifier 모델 생성

lgbmc = LGBMClassifier(n_estimators = 400)
evals = [(X_test, y_test)]
lgbmc.fit(X_train, y_train, early_stopping_rounds = 100, eval_metric = 'logloss', eval_set = evals, verbose = True)
preds = lgbmc.predict(X_test)

cross_val = cross_validate(
    estimator = lgbmc,
    X = iris.data, y = iris.target,
    cv = 5
)
print('avg fit time: {} (+/- {})'.format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print('avg score time: {} (+/- {})'.format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print('avg test score: {} (+/- {})'.format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 0.19687752723693847 (+/- 0.08293570240505971)
avg score time: 0.001802968978881836 (+/- 0.0004004494069017428)
avg test score: 0.9600000000000002 (+/- 0.04898979485566355)

  - lgbm 모델이 실행되며 logloss의 변화를 관찰할 수 있는 그래프 출력

  - 성능이 어떻게 향상되고 있는지 볼 수 있음

plot_metric(lgbmc)

150번째 반복쯤에서 거의 최저값 나옴

  - 변수 중요도

  - LGBM 전용 plot_importance 사용

plot_importance(lgbmc, figsize = (10, 12))

  - 트리 형태로 출력

plot_tree(lgbmc, figsize = (28, 24))

 

  - 와인 데이터

wine = load_wine()
X_train, X_test, y_train, y_test = train_test_split(wine.data, wine.target, test_size = 0.2, random_state = 42)

  - LGBMClassifier 모델 생성

lgbmc = LGBMClassifier(n_estimators = 400)
evals = [(X_test, y_test)]
lgbmc.fit(X_train, y_train, early_stopping_rounds = 100, eval_metric = 'logloss', eval_set = evals, verbose = True)
preds = lgbmc.predict(X_test)

cross_val = cross_validate(
    estimator = lgbmc,
    X = wine.data, y = wine.target,
    cv = 5
)
print('avg fit time: {} (+/- {})'.format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print('avg score time: {} (+/- {})'.format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print('avg test score: {} (+/- {})'.format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 0.6419896125793457 (+/- 0.21000223475863156)
avg score time: 0.003860330581665039 (+/- 0.002245084936532545)
avg test score: 0.9776190476190475 (+/- 0.01119469694127331)

  - lgbm 모델이 실행되며 logloss의 변화를 관찰할 수 있는 그래프 출력

plot_metric(lgbmc)

  - 변수 중요도

plot_importance(lgbmc, figsize = (10, 12))

  - 트리 형태로 출력

plot_tree(lgbmc, figsize = (28, 24))
 

 

  - 유방암 데이터

cancer = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, test_size = 0.2, random_state = 42)

  - LGBMClassifier 모델 생성

lgbmc = LGBMClassifier(n_estimators = 400)
evals = [(X_test, y_test)]
lgbmc.fit(X_train, y_train, early_stopping_rounds = 100, eval_metric = 'logloss', eval_set = evals, verbose = True)
preds = lgbmc.predict(X_test)

cross_val = cross_validate(
    estimator = lgbmc,
    X = cancer.data, y = cancer.target,
    cv = 5
)
print('avg fit time: {} (+/- {})'.format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print('avg score time: {} (+/- {})'.format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print('avg test score: {} (+/- {})'.format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 1.5169551372528076 (+/- 0.7215118218596442)
avg score time: 0.0062160491943359375 (+/- 0.002043592719798801)
avg test score: 0.9736531594472908 (+/- 0.015674460437800138)

  - lgbm 모델이 실행되며 logloss의 변화를 관찰할 수 있는 그래프 출력

plot_metric(lgbmc)

  - 변수 중요도

plot_importance(lgbmc, figsize = (10, 12))

  - 트리 형태로 출력

plot_tree(lgbmc, figsize = (28, 24))

 

 

2. LGBMRegressor

  - 당뇨병 데이터

diabetes = load_diabetes()
X_train, X_test, y_train, y_test = train_test_split(diabetes.data, diabetes.target, test_size = 0.2, random_state = 42)

  - LGBMRegressor 모델 생성

lgbmr = LGBMRegressor(n_estimators = 400)
evals = [(X_test, y_test)]
lgbmr.fit(X_train, y_train, early_stopping_rounds = 100, eval_metric = 'logloss', eval_set = evals, verbose = True)
preds = lgbmr.predict(X_test)

cross_val = cross_validate(
    estimator = lgbmr,
    X = diabetes.data, y = diabetes.target,
    cv = 5
)
print('avg fit time: {} (+/- {})'.format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print('avg score time: {} (+/- {})'.format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print('avg test score: {} (+/- {})'.format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 1.210813045501709 (+/- 0.6173186047501074)
avg score time: 0.010993432998657227 (+/- 0.009632501449539222)
avg test score: 0.30867643947179507 (+/- 0.07010708786960605)

  - lgbm 모델이 실행되며 logloss의 변화를 관찰할 수 있는 그래프 출력

plot_metric(lgbmr)

  - 변수 중요도

plot_importance(lgbmr, figsize = (10, 12))

  - 트리 형태로 출력

plot_tree(lgbmr, figsize = (28, 24))

● 앙상블(Ensamble)

  • 일반화와 강건성(Robustness)을 향상시키기 위해 여러 모델의 예측 값을 결합하는 방법
  • 앙상블의 종류
    • 평균 방법
      • 여러 추정값을 독립적으로 구한 뒤 평균을 취함
      • 결합 추정값은 분산이 줄어들어 단일 추정값보다 좋은 성능
    • 부스팅 방법
      • 순차적으로 모델 생성
      • 결합된 모델의 편향을 감소시키기 위해 노력
      • 부스팅 방법의 목표는 여러 개의 약한 모델들을 결합해 하나의 강력한 앙상블 모델을 구축하는 것

 

1. Bagging meta-estimator

  • bagging은 bootstrap arregatng의 줄임말
  • 원래 훈련 데이터셋의 일부를 사용해 여러 모델을 훈련
  • 각각의 결과를 결합해 최종 결과를 생성
  • 분산을 줄이고 과적합을 막음
  • 강력하고 복잡한 모델에서 잘 동작

 

  - 사용 라이브러리 및 연습용 데이터 불러오기

from sklearn.datasets import load_iris, load_wine, load_breast_cancer, load_diabetes
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import cross_validate

from sklearn.ensemble import BaggingClassifier, BaggingRegressor
from sklearn.neighbors import KNeighborsClassifier, KNeighborsRegressor
from sklearn.svm import SVC, SVR
from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor

iris = load_iris()
wine = load_wine()
cancer = load_breast_cancer()
diabetes = load_diabetes()

 

  - KNeighborsClassifier에서 일반 모델과 bagging 모델 비교

# 기본 모델 파이프라인 생성
base_model = make_pipeline(
    StandardScaler(),
    KNeighborsClassifier()
)
# bagging 모델 분류기 생성
bagging_model = BaggingClassifier(base_model, n_estimators = 10, max_samples = 0.5, max_features = 0.5)

# 일반 모델 파이프라인을 사용하여 분류한 결과 정확도 출력
cross_val = cross_validate(
    estimator = base_model,
    X = wine.data, y = wine.target,
    cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 0.0015646457672119141 (+/- 0.00046651220506644384)
avg score time: 0.0024621009826660155 (+/- 0.0007703127175682573)
avg test score: 0.9493650793650794 (+/- 0.037910929811115976)


# bagging 모델 분류기를 사용하여 분류한 결과 정확도 출력
cross_val = cross_validate(
    estimator = bagging_model,
    X = wine.data, y = wine.target,
    cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 0.01585836410522461 (+/- 0.001614824165009782)
avg score time: 0.004801464080810547 (+/- 0.0011524532393675253)
avg test score: 0.9496825396825397 (+/- 0.02695890511274157)

  - 일반 모델 파이프라인과 bagging 모델 분류기 간의 avg test score의 차이가 없음

 

  - SVC에서 일반 모델과 bagging 모델 비교

# 기본 모델 파이프라인 생성
base_model = make_pipeline(
    StandardScaler(),
    SVC()
)
# bagging 모델 분류기 생성
bagging_model = BaggingClassifier(base_model, n_estimators = 10, max_samples = 0.5, max_features = 0.5)

# 일반 모델 파이프라인을 사용하여 분류한 결과 정확도 출력
cross_val = cross_validate(
    estimator = base_model,
    X = wine.data, y = wine.target,
    cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 0.0019928932189941405 (+/- 0.0006299217280194511)
avg score time: 0.0007997512817382813 (+/- 0.0003998954564704184)
avg test score: 0.9833333333333334 (+/- 0.022222222222222233)


# bagging 모델 분류기를 사용하여 분류한 결과 정확도 출력
cross_val = cross_validate(
    estimator = bagging_model,
    X = wine.data, y = wine.target,
    cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 0.03082098960876465 (+/- 0.010328177445939042)
avg score time: 0.003788471221923828 (+/- 0.0007431931234207308)
avg test score: 0.9495238095238095 (+/- 0.03680897280161461)

  - bagging 모델 분류기에서 avg test score가 일반 모델보다 더 감소함

 

  - DecisionTreeClassifier에서 일반 모델과 bagging 모델 비교

# 기본 모델 파이프라인 생성
base_model = make_pipeline(
    StandardScaler(),
    DecisionTreeClassifier()
)
# bagging 모델 분류기 생성
bagging_model = BaggingClassifier(base_model, n_estimators = 10, max_samples = 0.5, max_features = 0.5)

# 일반 모델 파이프라인을 사용하여 분류한 결과 정확도 출력
cross_val = cross_validate(
    estimator = base_model,
    X = wine.data, y = wine.target,
    cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 0.0016411781311035157 (+/- 0.0005283725076633593)
avg score time: 0.0005584716796875 (+/- 0.00046257629871031806)
avg test score: 0.8709523809523809 (+/- 0.04130828490281938)


# bagging 모델 분류기를 사용하여 분류한 결과 정확도 출력
cross_val = cross_validate(
    estimator = bagging_model,
    X = wine.data, y = wine.target,
    cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 0.025203371047973634 (+/- 0.006259195949988035)
avg score time: 0.0019852161407470704 (+/- 0.0006092217277319406)
avg test score: 0.9665079365079364 (+/- 0.032368500562618134)

  - bagging 모델 분류기에서 avg test score가 일반 모델보다 더 증가함

 

  - KNeighborsRegressor에서 일반 모델과 bagging 모델 비교

# 기본 모델 파이프라인 생성
base_model = make_pipeline(
    StandardScaler(),
    KNeighborsRegressor()
)
# bagging 모델 분류기 생성
bagging_model = BaggingClassifier(base_model, n_estimators = 10, max_samples = 0.5, max_features = 0.5)

# 일반 모델 파이프라인을 사용하여 분류한 결과 정확도 출력
cross_val = cross_validate(
    estimator = base_model,
    X = diabetes.data, y = diabetes.target,
    cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))# 출력 결과

# 출력 결과
avg fit time: 0.0011986732482910157 (+/- 0.0003976469573187139)
avg score time: 0.0011948585510253907 (+/- 0.0004032221568088893)
avg test score: 0.3689720650295623 (+/- 0.044659049060165365)


# bagging 모델 분류기를 사용하여 분류한 결과 정확도 출력
cross_val = cross_validate(
    estimator = bagging_model,
    X = diabetes.data, y = diabetes.target,
    cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))# 출력 결과

# 출력 결과
avg fit time: 0.01416783332824707 (+/- 0.0021502472754025685)
avg score time: 0.005353689193725586 (+/- 0.0005163540544834088)
avg test score: 0.4116223973880059 (+/- 0.039771045284647706)

  - bagging 모델 분류기에서 avg test score가 일반 모델보다 더 증가함

 

  - SVR에서 일반 모델과 bagging 모델 비교

# 기본 모델 파이프라인 생성
base_model = make_pipeline(
    StandardScaler(),
    SVR()
)
# bagging 모델 분류기 생성
bagging_model = BaggingClassifier(base_model, n_estimators = 10, max_samples = 0.5, max_features = 0.5)

# 일반 모델 파이프라인을 사용하여 분류한 결과 정확도 출력
cross_val = cross_validate(
    estimator = base_model,
    X = diabetes.data, y = diabetes.target,
    cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 0.005189418792724609 (+/- 0.00039828050210851215)
avg score time: 0.0029881954193115234 (+/- 4.301525777064362e-05)
avg test score: 0.14659868748701582 (+/- 0.021908831719954277)


# bagging 모델 분류기를 사용하여 분류한 결과 정확도 출력
cross_val = cross_validate(
    estimator = bagging_model,
    X = diabetes.data, y = diabetes.target,
    cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))# 출력 결과

# 출력 결과
avg fit time: 0.030098438262939453 (+/- 0.004244103777765697)
avg score time: 0.014351558685302735 (+/- 0.002644062031057098)
avg test score: 0.06636804266664734 (+/- 0.026375606251683278)

  - bagging 모델 분류기에서 avg test score가 일반 모델보다 더 감소함

 

  - DecisionTreeRegressor에서 일반 모델과 bagging 모델 비교

# 기본 모델 파이프라인 생성
base_model = make_pipeline(
    StandardScaler(),
    DecisionTreeRegressor()
)
# bagging 모델 분류기 생성
bagging_model = BaggingClassifier(base_model, n_estimators = 10, max_samples = 0.5, max_features = 0.5)

# 일반 모델 파이프라인을 사용하여 분류한 결과 정확도 출력
cross_val = cross_validate(
    estimator = base_model,
    X = diabetes.data, y = diabetes.target,
    cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 0.0030806541442871095 (+/- 0.0005084520543164849)
avg score time: 0.0005962371826171875 (+/- 0.0004868346584328474)
avg test score: -0.10402518188546787 (+/- 0.09614122612623877)


# bagging 모델 분류기를 사용하여 분류한 결과 정확도 출력
cross_val = cross_validate(
    estimator = bagging_model,
    X = diabetes.data, y = diabetes.target,
    cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 0.028301239013671875 (+/- 0.009722365434744872)
avg score time: 0.001835775375366211 (+/- 0.0003816480339082081)
avg test score: 0.3206163477240517 (+/- 0.06240931175651393)

  - bagging 모델 분류기에서 avg test score가 일반 모델보다 더 증가함

 

  - 데이터에 따라 다르지만 bagging 모델이 일반 모델보다 좋은 성능을 보임

 

 

2. Forests of randomized trees

  • sklean.ensemble 모듈에는 무작위 결정 트리를 기반으로 하는 두 개의 평균화 알고리즘이 존재
    • Random Forest
    • Extra-Trees
  • 모델 구성에 임의성을 추가해 다양한 모델 집합 생성
  • 앙상블 모델의 예측은 각 모델의 평균
from sklearn.ensemble import RandomForestClassifier, ExtraTreesClassifier

 

  - Random Forests 분류

# 모델 파이프라인 생성
model = make_pipeline(
    StandardScaler(),
    RandomForestClassifier()
)


# 붓꽃 데이터
cross_val = cross_validate(
    estimator = model,
    X = iris.data, y = iris.target,
    cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 0.18105216026306153 (+/- 0.01685682915874488)
avg score time: 0.013797760009765625 (+/- 0.0025621248659456705)
avg test score: 0.96 (+/- 0.024944382578492935)


# 와인 데이터
cross_val = cross_validate(
    estimator = model,
    X = wine.data, y = wine.target,
    cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 0.22976922988891602 (+/- 0.028256732778271565)
avg score time: 0.01587204933166504 (+/- 0.0023274044664123553)
avg test score: 0.9665079365079364 (+/- 0.032368500562618134)


# 유방암 데이터
cross_val = cross_validate(
    estimator = model,
    X = cancer.data, y = cancer.target,
    cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 0.2917177200317383 (+/- 0.045315794078103835)
avg score time: 0.017597723007202148 (+/- 0.006803860063290489)
avg test score: 0.9578326346840551 (+/- 0.02028739541529243)

 

  - Random Forests 회귀

# 모델 파이프라인 생성
model = make_pipeline(
    StandardScaler(),
    RandomForestRegressor()
)


# 당뇨병 데이터
cross_val = cross_validate(
    estimator = model,
    X = diabetes.data, y = diabetes.target,
    cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 0.38582072257995603 (+/- 0.05492288277511406)
avg score time: 0.01399979591369629 (+/- 0.0017906758222056265)
avg test score: 0.41577582207684943 (+/- 0.04029500816412448)

 

  - Extremely Randomized Trees 분류

# 모델 파이프라인 생성
model = make_pipeline(
    StandardScaler(),
    ExtraTreesClassifier()
)

# 붓꽃 데이터
cross_val = cross_validate(
    estimator = model,
    X = iris.data, y = iris.target,
    cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 0.12993454933166504 (+/- 0.013365132836320116)
avg score time: 0.015802621841430664 (+/- 0.0026369354952103644)
avg test score: 0.9533333333333334 (+/- 0.03399346342395189)


# 와인 데이터
cross_val = cross_validate(
    estimator = model,
    X = cancer.data, y = cancer.target,
    cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 0.14599318504333497 (+/- 0.027877641431797804)
avg score time: 0.013452291488647461 (+/- 0.0019263323264865461)
avg test score: 0.9776190476190475 (+/- 0.020831783767013237)


# 유방암 데이터
cross_val = cross_validate(
    estimator = model,
    X = cancer.data, y = cancer.target,
    cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 0.15427541732788086 (+/- 0.01803722986834076)
avg score time: 0.014791202545166016 (+/- 0.0007367600639162756)
avg test score: 0.9683744760130415 (+/- 0.010503414750935476)

 

  - Extremely Randomized Trees 회귀

# 모델 파이프라인 생성
model = make_pipeline(
    StandardScaler(),
    ExtraTreesRegressor()
)


# 당뇨병 데이터
cross_val = cross_validate(
    estimator = model,
    X = diabetes.data, y = diabetes.target,
    cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 0.21371040344238282 (+/- 0.029887789885253244)
avg score time: 0.011214303970336913 (+/- 0.0022113466437151)
avg test score: 0.4334859913753323 (+/- 0.037205109491594564)

 

 

  - Random Forest, Extra Tree 시각화

  • 결정 트리, Random Forest, Extra Tree의 결정 경계와 회귀식 시각화
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from sklearn.tree import DecisionTreeClassifier

# 기본 파라미터
n_classes = 3
n_estimators = 30
cmap = plt.cm.RdYlBu
plot_step = 0.02
plot_step_coarser = 0.5
RANDOM_SEED = 13

iris = load_iris()
plot_idx = 1

# 세 가지 모델을 for문에 넣어서 각 모델에 대한 예측 결과를 출력
models = [DecisionTreeClassifier(max_depth = None),
          RandomForestClassifier(n_estimators = n_estimators),
          ExtraTreesClassifier(n_estimators = n_estimators)]

plt.figure(figsize = (16, 8))
for pair in ([0, 1], [0, 2], [2, 3]):
    # 위에서 지정한 세가지 모델에 대해 각각 예측
    for model in models:
        X = iris.data[:, pair]
        y = iris.target
        
        # 독립변수의 개수만큼 인덱스 배열 생성
        idx = np.arange(X.shape[0])
        # 랜덤시드 설정하고 랜덤으로 인덱스 셔플링(필요 x)
        np.random.seed(RANDOM_SEED)
        np.random.shuffle(idx)
        X = X[idx]
        y = y[idx]

        # 평균과 표준편차를 계산하여 데이터 정규화
        mean = X.mean(axis = 0)
        std = X.std(axis = 0)
        X = (X-mean) / std

        # 위에서 정규화한 X와 y값을 모델에 피팅
        model.fit(X, y)

        # 모델 제목은 모델의 타입을 "."으로 분리하여
        # ["<class 'sklearn", 'tree', '_classes', "DecisionTreeClassifier'>"]
        # [-1]은 리스트의 가장 마지막 부분 추출("DecisionTreeClassifier'>")
        # [:-2]는 위의 문자열의 끝에서 두번째 문자전까지만 추출("DecisionTreeClassifier")
        # 마지막으로 해당 문자열에서 Classifier을 제외하기 위해 Classifier의 문자열 길이 전까지만 추출
        model_title = str(type(model)).split(".")[-1][:-2][:-len("Classifier")]

        # 3 * 3의 그래프 배열에서 plot_idx만큼(plot_idx는 모델 한 번 돌때마다 변경됨)
        plt.subplot(3, 3, plot_idx)
        if plot_idx <= len(models):
            plt.title(model_title, fontsize = 9)
        
        # meshgrid 생성
        x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
        y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
        xx, yy = np.meshgrid(np.arange(x_min, x_max, plot_step),
                 np.meshgrid(np.arange(y_min, y_max, plot_step)))

        # DecisionTreeClassifier 모델일 때와 나머지 모델일 때 구분
        if isinstance(model, DecisionTreeClassifier):
            Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
            Z = Z.reshape(xx.shape)
            cs = plt.contourf(xx, yy, Z, cmap = cmap)
        
        # 나머지 모델일 때는 alpha값을 주어 투명도 추가
        else:
            estimator_alpha = 1.0 / len(model.estimators_)
            for tree in model.estimators_:
                Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
                Z = Z.reshape(xx.shape)
                cs = plt.contourf(xx, yy, Z, alpha = estimator_alpha, cmap = cmap)
        
        xx_coarser, yy_coarser = np.meshgrid(np.arange(x_min, x_max, plot_step_coarser),
                                             np.arange(y_min, y_max, plot_step_coarser))
        Z_points_coarser = model.predict(np.c_[xx_coarser.ravel(),
                                                yy_coarser.ravel()]).reshape(xx_coarser.shape)
        cs_points = plt.scatter(xx_coarser, yy_coarser, s = 15, c = Z_points_coarser, cmap = cmap, edgecolor = 'none')

        plt.scatter(X[:, 0], X[:, 1], c = y, cmap = ListedColormap(['r','y','b']), edgecolor = 'k', s = 20)
        plot_idx += 1

# 그래프 최상단 제목 설정
plt.suptitle("Classifiers", fontsize = 12)
plt.axis('tight')
plt.tight_layout(h_pad = 0.2, w_pad = 0.2, pad = 2.5)
plt.show()

 

plot_idx = 1
models = [DecisionTreeRegressor(max_depth = None),
          RandomForestRegressor(n_estimators = n_estimators),
          ExtraTreesRegressor(n_estimators = n_estimators)]

plt.figure(figsize = (16, 8))
for pair in (0, 1, 2):
    for model in models:
        X = diabetes.data[:, pair]
        y = diabetes.target

        idx = np.arange(X.shape[0])
        np.random.seed(RANDOM_SEED)
        np.random.shuffle(idx)
        X = X[idx]
        y = y[idx]

        mean = X.mean(axis = 0)
        std = X.std(axis = 0)
        X = (X-mean) / std

        model.fit(X.reshape(-1, 1), y)

        model_title = str(type(model)).split(".")[-1][:-2][:-len('Regreffor')]

        plt.subplot(3, 3, plot_idx)
        if plot_idx <= len(models):
            plt.title(model_title, fontsize = 9)
        
        x_min, x_max = X.min() -1, X.max() + 1
        y_min, y_max = y.min() -1, y.max() + 1
        xx, yy = np.arange(x_min - 1, x_max + 1, plot_step), np.arange(y_min - 1, y_max + 1, plot_step)

        if isinstance(model, DecisionTreeRegressor):
            Z = model.predict(xx.reshape(-1, 1))
            cs = plt.plot(xx, Z)
        else:
            estimator_alpha = 1.0 / len(model.estimators_)
            for tree in model.estimators_:
                Z = tree.predict(xx.reshape(-1, 1))
                cs = plt.plot(xx, Z, alpha = estimator_alpha)

        plt.scatter(X, y, edgecolors = 'k', s = 20)
        plot_idx += 1
        
plt.suptitle("Regressor", fontsize = 12)
plt.axis('tight')
plt.tight_layout(h_pad = 0.2, w_pad = 0.2, pad = 2.5)
plt.show()

 

3. AdaBoost

  • 대표적인 부스팅 알고리즘
  • 일련의 약한 모델들을 학습
  • 수정된 버전의 데이터를 (가중치가 적용된)반복 학습
  • 가중치 투표(또는 합)을 통해 각 모델의 예측값을 결합
  • 첫 단계에서는 원본 데이터를 학습하고 연속적인 반복마다 개별 샘플에 대한 가중치가 수정되고 다시 모델이 학습
    • 잘못 예측된 샘플은 가중치가 증가, 올바르게 예측된 샘플은 가중치 감소
    • 각각의 약한 모델들은 예측하기 어려운 샘플에 집중하게 됨

 

  - AdaBoostClassifier

from sklearn.ensemble import AdaBoostClassifier, AdaBoostRegressor

# 기본 모델 파이프라인 생성
model = make_pipeline(
    StandardScaler(),
    AdaBoostClassifier()
)

# 붓꽃 데이터
cross_val = cross_validate(
    estimator = model,
    X = iris.data, y = iris.target,
    cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 0.10685276985168457 (+/- 0.0251679732383264)
avg score time: 0.012082815170288086 (+/- 0.0029404438972186714)
avg test score: 0.9466666666666667 (+/- 0.03399346342395189)


# 와인 데이터
cross_val = cross_validate(
    estimator = model,
    X = wine.data, y = wine.target,
    cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 0.12471566200256348 (+/- 0.022184160168356563)
avg score time: 0.010206842422485351 (+/- 0.0014569535430837709)
avg test score: 0.8085714285714285 (+/- 0.16822356718459935)


# 유방암 데이터
cross_val = cross_validate(
    estimator = model,
    X = cancer.data, y = cancer.target,
    cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 0.21773457527160645 (+/- 0.036282505551639664)
avg score time: 0.01280508041381836 (+/- 0.006666702833190784)
avg test score: 0.9701133364384411 (+/- 0.019709915473893072)

 

  - AdaBoostRegressor

from sklearn.ensemble import AdaBoostClassifier, AdaBoostRegressor

# 기본 모델 파이프라인 생성
model = make_pipeline(
    StandardScaler(),
    AdaBoostRegressor()
)

# 당뇨병 데이터
cross_val = cross_validate(
    estimator = model,
    X = diabetes.data, y = diabetes.target,
    cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 0.0806793212890625 (+/- 0.019655713051304806)
avg score time: 0.004206609725952148 (+/- 0.0017171964676314028)
avg test score: 0.4325980577698525 (+/- 0.046753912177138576)

 

 

4. Gradient Tree Boosting

  • 임의의 차별화 가능한 손실함수로 일반화한 부스팅 알고리즘
  • 웹 검색, 분류 및 회귀 등 다양한 분야에서 모두 사용 가능

 

  - GradientBoostingClassifier

from sklearn.ensemble import GradientBoostingClassifier, GradientBoostingRegressor

# 기본 모델 파이프라인 생성
model = make_pipeline(
    StandardScaler(),
    GradientBoostingClassifier()
)

# 붓꽃 데이터
cross_val = cross_validate(
    estimator = model,
    X = iris.data, y = iris.target,
    cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 0.28792128562927244 (+/- 0.08891755551147741)
avg score time: 0.0009984493255615235 (+/- 1.4898700510940572e-05)
avg test score: 0.9666666666666668 (+/- 0.02108185106778919)


# 와인 데이터
cross_val = cross_validate(
    estimator = model,
    X = wine.data, y = wine.target,
    cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 0.557232141494751 (+/- 0.11776815518280624)
avg score time: 0.001400327682495117 (+/- 0.0004905852751874076)
avg test score: 0.9385714285714286 (+/- 0.032068206474093704)


# 유방암 데이터
avg fit time: 0.5082685947418213 (+/- 0.05271622631546759)
avg score time: 0.0009903907775878906 (+/- 9.857966772333804e-06)
avg test score: 0.9596180717279925 (+/- 0.02453263202329889)

 

  - GradientBoostingRegressor

from sklearn.ensemble import GradientBoostingClassifier, GradientBoostingRegressor

# 기본 모델 파이프라인 생성
model = make_pipeline(
    StandardScaler(),
    GradientBoostingRegressor()
)

# 당뇨병 데이터
cross_val = cross_validate(
    estimator = model,
    X = diabetes.data, y = diabetes.target,
    cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 0.12577242851257325 (+/- 0.01002747346482568)
avg score time: 0.0013751983642578125 (+/- 0.00048391264033975266)
avg test score: 0.40743256738384626 (+/- 0.06957393690515927)

 

 

5. 투표 기반 분류(Voting Classifier)

  • 서로 다른 모델들의 결과를 투표를 통해 결합
  • 두가지 방법으로 투표 가능
    • 가장 많이 예측된 클래스를 정답으로 채택(hard voting)
    • 예측된 확률의 가중치 평균(soft voting)

 

  - hard 보팅

from sklearn.svm import SVC
from sklearn.naive_bayes import GaussianNB
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import VotingClassifier
from sklearn.model_selection import cross_val_score

model1 = SVC()
model2 = GaussianNB()
model3 = RandomForestClassifier()
vote_model = VotingClassifier(
    estimators = [('svc', model1), ('naive', model2), ('forest', model3)],
    voting = 'hard'
)

for model in (model1, model2, model3, vote_model):
    model_name = str(type(model)).split(".")[-1][:-2]
    scores = cross_val_score(model, iris.data, iris.target, cv = 5)
    print('accurancy: %0.2f (+/- %0.2f) [%s]' % (scores.mean(), scores.std(), model_name))

# 출력 결과
accurancy: 0.97 (+/- 0.02) [SVC]
accurancy: 0.95 (+/- 0.03) [GaussianNB]
accurancy: 0.96 (+/- 0.02) [RandomForestClassifier]
accurancy: 0.96 (+/- 0.02) [VotingClassifier]

 

  - soft 보팅

from sklearn.svm import SVC
from sklearn.naive_bayes import GaussianNB
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import VotingClassifier
from sklearn.model_selection import cross_val_score

model1 = SVC(probability = True)
model2 = GaussianNB()
model3 = RandomForestClassifier()
vote_model = VotingClassifier(
    estimators = [('svc', model1), ('naive', model2), ('forest', model3)],
    voting = 'soft',
    weights = [2, 1, 2]
)

for model in (model1, model2, model3, vote_model):
    model_name = str(type(model)).split(".")[-1][:-2]
    scores = cross_val_score(model, iris.data, iris.target, cv = 5)
    print('accurancy: %0.2f (+/- %0.2f) [%s]' % (scores.mean(), scores.std(), model_name))

# 출력 결과
accurancy: 0.97 (+/- 0.02) [SVC]
accurancy: 0.95 (+/- 0.03) [GaussianNB]
accurancy: 0.97 (+/- 0.02) [RandomForestClassifier]
accurancy: 0.96 (+/- 0.02) [VotingClassifier]

 

  - 결정경계 시각화

from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.ensemble import VotingClassifier
from itertools import product

# 총 4개의 feature 중 2개만 사용
X = iris.data[:, [0, 2]]
y = iris.target

model1 = DecisionTreeClassifier(max_depth = 4)
model2 = KNeighborsClassifier(n_neighbors = 7)
model3 = SVC(gamma = .1, kernel = 'rbf', probability = True)
vote_model = VotingClassifier(estimators = [('dt', model1), ('knn', model2), ('svc', model3)], voting = 'soft', weights = [2, 1, 2])

model1 = model1.fit(X, y)
model2 = model2.fit(X, y)
model3 = model3.fit(X, y)
vote_model = vote_model.fit(X, y)

x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.1),
                     np.arange(y_min, y_max, 0.1))

f, axarr = plt.subplots(2, 2, sharex = 'col', sharey = 'row', figsize = (12, 8))
for idx, model, tt in zip(product([0, 1], [0,1]), [model1, model2, model3, vote_model],
                          ['Decision Tree (depth=4)', 'KNN (k=7)', 'Kernel SVM', 'Soft Voting']):
    Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    axarr[idx[0], idx[1]].contourf(xx, yy, Z, alpha = 0.4)
    axarr[idx[0], idx[1]].scatter(X[:, 0], X[:, 1], c = y, s = 20, edgecolor = 'k')
    axarr[idx[0], idx[1]].set_title(tt)
    
plt.show()

 

 

6. 투표 기반 회귀

  • 서로 다른 모델의 예측값의 평균을 사용
from sklearn.linear_model import LinearRegression
from sklearn.ensemble import GradientBoostingRegressor, RandomForestRegressor, VotingRegressor

model1 = LinearRegression()
model2 = GradientBoostingRegressor()
model3 = RandomForestRegressor()
vote_model = VotingRegressor(
    estimators = [('Linear', model1), ('gbr', model2), ('rfr', model3)],
    weights = [1, 1, 1]
)

for model in (model1, model2, model3, vote_model):
    model_name = str(type(model)).split(".")[-1][:-2]
    scores = cross_val_score(model, diabetes.data, diabetes.target, cv = 5)
    print('R2: %0.2f (+/- %0.2f) [%s]' % (scores.mean(), scores.std(), model_name))

# 출력 결과
R2: 0.48 (+/- 0.05) [LinearRegression]
R2: 0.41 (+/- 0.07) [GradientBoostingRegressor]
R2: 0.42 (+/- 0.05) [RandomForestRegressor]
R2: 0.46 (+/- 0.05) [VotingRegressor]

 

  - 시각화

X = diabetes.data[:, 0].reshape(-1, 1)
y = diabetes.target

model1 = LinearRegression()
model2 = GradientBoostingRegressor()
model3 = RandomForestRegressor()
vote_model = VotingRegressor(
    estimators = [('Linear', model1), ('gbr', model2), ('rfr', model3)],
    weights = [1, 1, 1]
)

model1 = model1.fit(X, y)
model2 = model2.fit(X, y)
model3 = model3.fit(X, y)
vote_model = vote_model.fit(X, y)

x_min, x_max = X.min() - 1, X.max() + 1
xx = np.arange(x_min - 1, x_max + 1, 0.1)

f, axarr = plt.subplots(2, 2, sharex = 'col', sharey = 'row', figsize = (12,  8))

for idx, model, tt in zip(product([0, 1], [0, 1]),
                          [model1, model2, model3, vote_model],
                          ['Linear Regressor', 'Gradient Boosting', 'Random Forest', 'Voting']):
    Z = model.predict(xx.reshape(-1, 1))
    axarr[idx[0], idx[1]].plot(xx, Z, c = 'r')
    axarr[idx[0], idx[1]].scatter(X, y, s = 20, edgecolor = 'k')
    axarr[idx[0], idx[1]].set_title(tt)

plt.show()

 

 

7. 스택 일반화(Stacked Generalization)

  • 각 모델의 예측값을 최종 모델의 입력으로 사용(모델의 예측값을 최종 모델에 스택으로 쌓음)
  • 모델의 편향을 줄이는데 효과적

 

  - 스택 회귀

from sklearn.linear_model import Ridge, Lasso
from sklearn.svm import SVR
from sklearn.ensemble import GradientBoostingRegressor, StackingRegressor

estimators = [('ridge', Ridge()),
              ('lasso', Lasso()),
              ('svr', SVR())]

reg = make_pipeline(
    StandardScaler(),
    StackingRegressor(estimators = estimators,
                      final_estimator = GradientBoostingRegressor())
)

cross_val = cross_validate(
    estimator = reg,
    X = diabetes.data, y = diabetes.target,
    cv = 5
)
print('avg fit time: {} (+/- {})'.format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print('avg score time: {} (+/- {})'.format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print('avg test score: {} (+/- {})'.format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 0.34942941665649413 (+/- 0.10878244133017631)
avg score time: 0.010065841674804687 (+/- 0.00266810792836556)
avg test score: 0.37135451119632856 (+/- 0.0688832914154432)

 

  - 회귀식 시각화

X = diabetes.data[:, 0].reshape(-1, 1)
y = diabetes.target

model1 = Ridge()
model2 = Lasso()
model3 = SVR()
reg = StackingRegressor(estimators = estimators,
                        final_estimator = GradientBoostingRegressor())


model1 = model1.fit(X, y)
model2 = model2.fit(X, y)
model3 = model3.fit(X, y)
reg = reg.fit(X, y)

x_min, x_max = X.min() - 1, X.max() + 1
xx = np.arange(x_min - 1, x_max + 1, 0.1)

f, axarr = plt.subplots(2, 2, sharex = 'col', sharey = 'row', figsize = (12,  8))

for idx, model, tt in zip(product([0, 1], [0, 1]),
                          [model1, model2, model3, vote_model],
                          ['Ridge', 'Lasso', 'SVR', 'Stack']):
    Z = model.predict(xx.reshape(-1, 1))
    axarr[idx[0], idx[1]].plot(xx, Z, c = 'r')
    axarr[idx[0], idx[1]].scatter(X, y, s = 20, edgecolor = 'k')
    axarr[idx[0], idx[1]].set_title(tt)

plt.show()

 

  - 스택 분류

from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.naive_bayes import GaussianNB
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import StackingClassifier

estimators = [('logistic', LogisticRegression(max_iter = 10000)),
              ('svc', SVC()),
              ('naive', GaussianNB())]

clf = StackingClassifier(
    estimators = estimators,
    final_estimator = RandomForestClassifier()
)

cross_val = cross_validate(
    estimator = clf,
    X = iris.data, y = iris.target,
    cv = 5
)
print('avg fit time: {} (+/- {})'.format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print('avg score time: {} (+/- {})'.format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print('avg test score: {} (+/- {})'.format(cross_val['test_score'].mean(), cross_val['test_score'].std()))

# 출력 결과
avg fit time: 1.7309207439422607 (+/- 0.5153520045243725)
avg score time: 0.08048839569091797 (+/- 0.06365540285519627)
avg test score: 0.9733333333333334 (+/- 0.02494438257849294)

 

  - 결정 경계 시각화

# 총 4개의 feature 중 2개만 사용
X = iris.data[:, [0, 2]]
y = iris.target

model1 = LogisticRegression(max_iter = 10000)
model2 = SVC()
model3 = GaussianNB()
stack = StackingClassifier(estimators = estimators, final_estimator = RandomForestClassifier())

model1 = model1.fit(X, y)
model2 = model2.fit(X, y)
model3 = model3.fit(X, y)
stack = stack.fit(X, y)

x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.1),
                     np.arange(y_min, y_max, 0.1))

f, axarr = plt.subplots(2, 2, sharex = 'col', sharey = 'row', figsize = (12, 8))
for idx, model, tt in zip(product([0, 1], [0,1]), [model1, model2, model3, stack],
                          ['Logistic Regression', 'SVC', 'GaussianNB', 'Stack']):
    Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    axarr[idx[0], idx[1]].contourf(xx, yy, Z, alpha = 0.4)
    axarr[idx[0], idx[1]].scatter(X[:, 0], X[:, 1], c = y, s = 20, edgecolor = 'k')
    axarr[idx[0], idx[1]].set_title(tt)
    
plt.show()

+ Recent posts