인공지능

 

인공지능이란? 인간처럼 생각하고 행동하는 기기를 만드는 기술

 

인공지능은 레고처럼 조립이 가능하다.

이를 가능하게끔 해주는 조력자는 다음과 같은 것들이 있다.

 

  • Theano
  • Pytorch
  • Keras
  • the Microsoft Cognitive toolkit
  • Caffe2
  • TensorFlow
  • mxnet
  • GLUON
  • Chainer
  • Caffe

인공지능은 도구이다. 도구를 만드는 방법도 중요하지만, 도구를 사용하는 방법도 배워야 한다.

목적에 적당한 도구를 고르는 방법도 알아야 할 것이고, 

고른 도구를 어떻게 사용하는지도 알아야 할 것이다.


기계 학습

 

초창기 : 컴퓨터가 경험을 통해 학습할 수 잇도록 프로그래밍할 수 있다면, 세세하게 프로그래밍 해야 하는 번거로움에서 벗어날 수 있다.

Samuel, 1959
현대 : 어떤 컴퓨터 프로그램이 T라는 작업을 수행할 때, 이 프로그램의 성능을 P라는 척도로 평가하자. 이 때, 경험 E를 통해 성능이 개선되면 이 프로그램은 학습을 했다고 할 수 있다.

Mitchell, 1997

즉, 최적의 프로그램(알고리즘)을 찾는 행위이다.

 

경험 E를 통해 주어진 작업 T에 대한 성능 P의 향상 => E * T = P 

 

기존의 프로그래밍과 기계학습은 무엇이 다를까?

 

기존의 프로그램은 입력값을 넣어주고, 규칙을 주면 그 규칙에 대한 값을 Sequence하게 나열하고 서술한다고 하면,

기계 학습은 입력값과 결과를 주고, 그 안에 점진적으로 좋아지는 규칙을 찾으라는 것이다.

 

초창기 인공지능은 지식기반 방식이었다.

여기서 지식기반이란, 경험적 지식 혹은 사실을 인위적 컴퓨터에 부여하여 학습하였다.

그러나 이에는 한계가 있었다. 학습의 대상이 심한 변화 양상을 갖는 경우, 모든 지식 혹은 사실의 나열이 불가능했다.

 

기계 학습의 문제(task)는 예측(prediction)이다.

이 때, 예측은 회귀(Regression)와 분류(Classfication)로 나뉜다.

회귀는 목표치가 실수이고, 분류는 부류 혹은 종류의 값으로 나타난다.

 

기계 학습의 첫 단계에선 우선 데이터를 관찰한다.

그리고 그 데이터에 대한 모델 즉, 가설(Hypothesis)을 세운다. ex) 데이터 양상이 직선 형태를 보인다 -> 모델을 직선으로 선택한다고 가정한다.

가설인 직선 모델의 수식을 세운다. ex) 2개의 매개변수(parameter)와 w와 b -> y = wx + b

가설을 세운 모델에 대해서 예측을 가장 정확하게 하는 최적의 매개변수를 찾는다.

처음은 임의의 매개변수 값에서 시작하지만, 이를 개선하여 정량적인 최적 성능(Performance)에 도달하도록 한다.

 

훈련을 마치게 되면, 추론(Inference)을 수행한다.

새로운 특징에 대응되는 목표치의 예측에 사용한다. 

 

결국, 기계 학습의 궁극적 목표는 훈련 집합에 없는 새로운 데이터에 대한 오류를 최소화시키고 싶은 것이다.

이러한 테스트 집합(새로운 데이터)에 대한 높은 성능을 일반화(Generalization) 능력이라고 한다.

 

기계 학습의 필수요소

 

  • 학습할 수 있는 데이터
  • 데이터의 규칙이 존재
  • 수학적으로 설명이 불가능해야 한다.
    • 수학적으로 설명이 되면 수학적으로 모델링을 하면 된다.
    • 수학적으로 모델링이 되지 않는(블가능한) 영역을 찾아내는 것이 기계 학습의 역할.

 

특징 공간

 

모든 데이터는 정량적으로 표현되며, 특징 공간 상에 존재하게 된다.

이 특징 공간은 데이터의 차원에따라 1차원에서부터 2차원, ... , 다차원 특징 공간으로 확장될 수 있다.

 

d-차원 데이터에서 

직선 모델을 사용했을 때 매개변수의 수는 d+1이다.

2차 곡선 모델을 사용하면 매개변수 수가 지수적으로 증가하여 d^2+d+1이 될 것이다.

d차 곡선 모델을 사용해도 이는 마찬가지일 것.

 

이러한 모델을 설정할 때 각 특징마다의 거리가 있을 것이다. 이 때, 데이터가 차원의 공간상에 존재하게 되고, 데이터간의 거리가 가까울수록 데이터의 유사도가 높다라고 일반적으로 평가를 내리게 된다. 이 거리라는 개념을 통해서도 규칙을 찾을 수 있게 된다.

  • 유클리드 거리

 

차원의 저주

 

차원이 높아짐에 따라 발생하는 현실적인 문제들을 일컫는 말이다.

차원이 높아질수록 유의미한 표현을 찾기 위해 지수적으로 많은 데이터가 필요하다. 

 

이를 보완하기 위하여 특징 공간을 변환한다. 이 때, 변환할 수 있는 공식을 새롭게 정의하면 다차원에서 제대로 해결할 수 없었던 문제를 해결할 수 있게 된다. 이를 표현 문제(representations matter)라고 한다.

 

표현 학습(representation learning)

 

좋은 특징 공간을 자동으로 찾는 작업이다.

 

심층 학습(Deep Learning)

 

표현학습의 하나로 다수의 은닉층을 가진 신경망을 이용해 최적의 계층적 특징을 학습하는 것이다.

 

인공지능의 단계

 

  • 약인공지능 (현재의 단계)
    • 인간이 지시한 명령의 틀 안에서만 일하기때문에 예측과 관리 용이
  • 강인공지능
    • 인간이 할 수 있는 어떠한 지적 업무도 성공적으로 해낼 수 있는 가상적 기계 지능
  • 초인공지능
    • 인공지능의 발전이 가속화되어 모든 인류의 지성을 합친 것보다 더 뛰어난 인공지능

데이터

 

과학 기술의 정립 과정

 

데이터 수집 -> 모델 정립 (가설 Hypothesis) -> 예측

 

ex) 천동설은 수집한 데이터에 대해 설명하지 못하여 틀린 모델.

그러나 지동설은 제 1, 제 2, 제 3법칙을 완성하여 옳은 모델임을 증명하였음.

 

기계 학습

 

  • 기계학습은 복잡한 문제 혹은 과업을 다룬다.
    • 지능적 범주의 행위들은 규칙의 다양한 변화 양상을 가짐.
  • 단순한 수학 공식으로 표현 불가능함
  • 데이터를 설명할 수 있는 학습 모델을 찾아내는 과정
  • 기계 학습 문제는 데이터 생성 과정을 알 수 없다. 단지 주어진(규칙 x) 훈련집합 X, Y로 가설 모델을 통해 근사 추정만이 가능하다.

 

데이터의 중요성

 

  • 데이터의 양과 질
    • 주어진 과업에 적합한 다양한 데이터를 충분한 양만큼 수집하면 과업 성능이 향상한다.
      • 수학적으로 설명이 안 되는 규칙을 모델(가설)을 통해 설명하고 싶다. 이 때, 데이터가 많으면 많아질수록 그에 관한 훨씬 더 많은 규칙, 정보를 알 수 있게 된다.
  • 공개 데이터
    • 대표적 3가지 데이터 : Iris, MNIST, ImageNet
    • UCI 저장소
  • 적은 양의 데이터베이스로 높은 성능을 달성하는 법
    • 방대한 공간에서 실제 데이터가 발생하는 곳은 매우 작은 부분 공간이다
      • 데이터 희소(Data sparsity) 특성을 가정한다.
      • 매니폴드 가정(manifold assumption (or manifold hypothesis))
        • 고차원의 데이터를 저차원으로 투영시켜 봐서 유사한 규칙을 찾는다. 고차원에서의 규칙과 저차원에 투영했을 때의 규칙은 유지된다.

간단한 기계 학습의 예

 

기계 학습이 할 일을 공식화하면,

 

θ_hat = argmin J(θ)

 

기계 학습은 작은 개선을 반복해 최적의 해를 찾아가는 수치적 방법으로 위 식을 푼다.

이를 알고리즘 형식으로 쓰면

 

1 난수를 생성하여 초기 해 θ_1을 설정

2 t=1

3 while (J(θ_t)가 0.0에 충분히 가깝지 않음) # 수렴 여부 검사

4    J(θ_t)가 작아지는 방향 △θ_t를 구함. # △θ_t는 주로 미분을 사용해 구함

5    θ_(t+1) = θ_t + △θ_t

6    t = t+1

7 θ_hat = θ_t

 

카드 승인 예제

 

요소

  • input
    • x
    • customer application
  • output
    • y
    • approve or deny -> classification 문제
  • target distribution
    • f = P(y|x)
    • ideal credit approval formula
  • data
    • (x_1, y_1), ..., (x_n, y_n)
    • historical records
  • hypothesis
    • g : X -> Y
    • formula to be used

 

x = {x_1, x_2} where x_1 : age and x_2 : annual salary in USD

N=11, d=2, X = R^2, and y = {approve, deny}

 

dataset D

 

n x_1 x_2 (k) y
1 29 56 approve
2 64 89 approve
3 33 17 deny
4 45 94 approve
5 24 26 deny
6 55 24 deny
7 35 52 approve
8 57 65 approve
9 45 32 deny
10 52 75 approve
11 62 31 deny

 

기계학습 설정

 

교사학습의 경우

 

모델(가설) 선택

 

  • 과소적합 (underfitting)
    • 모델의 용량(자유도(파라미터와 비례))이 작아 오차가 클 수 밖에 없는 현상
    • 대안 : 비선형 모델을 사용
      • 선형 모델에 비해 오차가 크게 감소.
  • 과대적합 (overfitting)
    • 모델이 훈련집합에 대해 거의 완벽히 근사.
    • 그러나 새로운 데이터를 예측한다면 큰 문제 발생
    • 이유는 모델의 용량이 크기 때문에 학습 과정에서 잡음까지 수용.

편향(bias)과 분산(변동, variance)

 

훈련집합을 여러 번 수집해 1차 ~ 12차에 반복 적용하는 실험을 한다고 가정하자.

 

  • 2차일 때
    • 매번 큰 오차. -> 편향이 크다.
    • 하지만 비슷한 모델을 얻음 -> 변동이 낮다.
  • 12차일 때
    • 매번 작은 오차. -> 편향이 작다.
    • 하지만 크게 다른 모델을 얻음 -> 변동이 높다.
  • 일반적으로 용량이 작은 모델은 편향이 크고 분산이 작고, 복잡한 모델은 편향이 작고 분산이 큼.
  • 즉, 편향과 분산은 상충(trade-off) 관계이다.

 

기계 학습의 목표는 낮은 편향과 낮은 분산을 가진 예측 모델을 만드는 것

그러나 모델의 편향과 분산은 상충 관계.

따라서 편향을 최소로 유지하며 분산도 최대로 낮추는 전략이 필요하다.


검증집합과 교차검증을 이용한 모델 선택 알고리즘

 

검증집합을 이용한 모델 선택

 

훈련집합과 테스트집합과 다른 별도의 검증집합(validation set)을 가진 상황 (데이터 양이 많을 때)

 

검증집합을 이용한 모델 선택 알고리즘

 

입력 : 모델집합 Ω, 훈련집합, 검증집합, 테스트집합

출력 : 최적 모델과 성능

 

1 for ( Ω에 있는 각각 모델)

2    모델을 훈련집합으로 학습시킨다.

3    검증집합으로 학습된 모델의 성능 측정 # 검증 성능 측정

4 가장 높은 성능을 보인 모델 선택

5 # 테스트집합으로 선택된 모델 성능 측정 # 웬만해선 최후의 수단으로..

 

교차검증을 이용한 모델 선택

 

교차검증(cross validation)

 

비용 문제로 별도 검증집합이 없는 상황에 유용한 모델 선택 기법 (데이터 양이 적을 때)

훈련집합을 등분해 학습과 평가 과정을 여러 번 반복 후 평균 사용

 

교차검증을 이용한 모델 선택 알고리즘

 

입력 : 모델집합 Ω, 훈련집합, 테스트집합, 그룹 개수 k

출력 : 최적 모델과 성능

 

1 훈련집합을 k개 그룹으로 등분

2 for ( Ω에 있는 각각 모델)

3        for ( i=1 to k)

4            i번째 그룹을 제외한 k-1개 그룹으로 모델 학습

5            학습된 모델 성능을 i번째 그룹으로 측정 

6        k개 성능을 평균해 해당 모델 성능으로 취하기

7 가장 높은 성능 보인 모델 선택

8 # 테스트집합으로 선택된 모델 성능 측정

 

부트스트랩(bootstrap)

 

임의의 복원 추출 샘플링(sampling with replacement) 반복

데이터 분포가 불균형일 때 적용한다.

 

부트스트랩 알고리즘

 

입력 : 모델집합 Ω, 훈련집합, 테스트집합, 샘플링 비율 p(0<p≤1), 반복횟수 T

출력 : 최적 모델과 성능

 

1 for ( Ω에 있는 각각 모델)

2        for ( i=1 to T)

3            훈련집합 X에서 pn개 샘플을 뽑아 새로운 훈련집합 X'를 굿헝한다. 이 때 대치 허용.

4            X'로 모델 학습

5            X-X'를 이용해 학습된 모델 성능 측정

6        7개 성능을 평균해 해당 모델 성능으로 취한다.

7 가장 높은 성능을 보인 모델 선택

8 테스트 집합으로 선택된 모델 성능 측정

 

이상탐지, 보안에서 주로 쓰이는 알고리즘이다.


현실에서는 위와 같은 알고리즘을 통해 모델을 선택하는 것보다는

경험으로 큰 틀(가설)을 선택한 후,

모델 선택 알고리즘으로 세부 모델을 선택함

이런 경험적 접근 방법에 대해 『Deep Learning』책에서는 다음과 같이 비유하였다.

어느 정도 우리가 하는 일은 항상 둥근 홈(선택 모델)에 네모 막대기(데이터 생성 과정)를 끼워 넣는 것이라 할 수 있다.

Goodfellow2016(222쪽)

 

현대 기계 학습은

용량이 충분히 큰 모델을 선택 후,

선택한 모델이 정상을 벗어나지 않도록 여러 규제(Regularizaiton, 모델의 용량을 줄여준다.)기법을 적용한다.


규제

 

데이터 확대

 

데이터를 더 많이 수집하면 일반화 능력이 향상된다.

그러나 데이터 수집에는 많은 비용이 든다. -> 실측 자료(ground truth)를 사람이 일일이 표식(Labeling)해야 하기 때문에..

그래서 인위적으로 데이터를 확대(Data Augmentation)한다.

 

  • 훈련집합에 있는 샘플을 변형(transform)
  • ex) 약간 회전(rotation) 또는 왜곡(warping) 단, 원 데이터의 부류 소속 등의 고유 특성이 변하지 않게 주의한다.

 

가중치 감쇠

 

개선된 목적함수를 이용해 가중치를 작게 조절하는 규제 기법이다.

 

 

λ는 주어진 가중치의 감쇠 선호 정도를 제어한다.


기계 학습 지도 방식에 따른 유형

 

  • 지도 학습(Supervised learning)
    • 특징 벡터 X와 목표치 Y가 모두 주어진 상황
    • 회귀(Regression)와 분류(Classification) 문제로 구분
  • 비지도 학습(Unsupervised learning)
    • 특징 벡터 X는 주어지나, 목표치 Y가 주어지지 않는 상황
    • 군집화(Clustering) 과업 
      • ex) 고객 성향 맞춤 홍보
    • 밀도 추정(density estimation), 특징 공간 변환 과업 (ex) PCA)
  • 강화 학습(Reinforcement learning)
    • 상대적 목표치가 주어지는데, 지도 학습과 다른 형태. -> 보상(Reward)
    • ex) 바둑
      • 수를 두는 행위가 샘플. 게임이 끝나면 목표치 하나가 부여.
        • 이기면 1, 패하면 -1 부여
      • 게임을 구성한 샘플들 각각 목표치를 나눠줘야함.
  • 준지도 학습(Semi-supervised learning)
    • 일부는 X와 Y를 모두 가지지만, 나머지는 X만 가진 상황
    • 최근 대부분 데이터가 X 수집은 쉽지만, Y는 수작업이 필요해 최근 중요성 부각

 

다양한 기준에 따른 유형

 

  • 오프라인 학습(offline learning)과 온라인 학습(online learning)
    • 보통은 오프라인 학습을 다룸
    • 온라인 학습은 IOT등에 추가로 발생하는 데이터 샘플을 갖고 점증적 학습 수행
  • 결정론적 학습(Deterministic learning)과 확률적 학습(stochastic learning)
    • 결정론적에서는 같은 데이터를 갖고 다시 학습하면 같은 예측 모델이 만들어진다.
    • 확률적 학습은 학습 과정에서 확률 분포를 사용해 같은 데이터로 다시 학습하면 다른 예측 모델이 만들어짐
  • 분별 모델(Discriminative models)과 생성 모델(Generative models)
    • 분별 모델은 부류 예측에만 관심. 즉, P(y|x)의 추정에 관심
    • 생성 모델은 P(x) 또는 P(x|y)를 추정. 즉, 새로운 샘플 생성 가능

'AI > KDT 인공지능' 카테고리의 다른 글

[06/22] 라이브 세션  (0) 2021.06.22
[06/21] 신경망의 기초  (0) 2021.06.21
[06/15] 분류문제 실습  (0) 2021.06.15
오토인코더, t-SNE  (0) 2021.06.14
[06/02] End to End Machine Learning Project  (0) 2021.06.02

Mnist 데이터 분류

 

# Python ≥3.5 is required
import sys
assert sys.version_info >= (3, 5)

# Scikit-Learn ≥0.20 is required
import sklearn
assert sklearn.__version__ >= "0.20"

# Common imports
import numpy as np
import os

# to make this notebook's output stable across runs
np.random.seed(42)

# To plot pretty figures
%matplotlib inline
import matplotlib as mpl
import matplotlib.pyplot as plt
mpl.rc('axes', labelsize=14)
mpl.rc('xtick', labelsize=12)
mpl.rc('ytick', labelsize=12)

# Where to save the figures
PROJECT_ROOT_DIR = "."
CHAPTER_ID = "classification"
IMAGES_PATH = os.path.join(PROJECT_ROOT_DIR, "images", CHAPTER_ID)
os.makedirs(IMAGES_PATH, exist_ok=True)

def save_fig(fig_id, tight_layout=True, fig_extension="png", resolution=300):
    path = os.path.join(IMAGES_PATH, fig_id + "." + fig_extension)
    print("Saving figure", fig_id)
    if tight_layout:
        plt.tight_layout()
    plt.savefig(path, format=fig_extension, dpi=resolution)

MNIST 데이터를 불러오고, 확인해보자.

from sklearn.datasets import fetch_openml
mnist = fetch_openml('mnist_784', version=1, cache=True)
mnist.keys()
X, y = mnist["data"], mnist["target"]
print(X.shape) # (70000, 784)
%matplotlib inline
import matplotlib as mpl
import matplotlib.pyplot as plt

some_digit = X[2]
some_digit_image = some_digit.reshape(28, 28)
plt.imshow(some_digit_image, cmap=mpl.cm.binary)
plt.axis("off")

save_fig("some_digit_plot")
plt.show()

some_digit에는 검은 부분을 제외하고 전부 0인 배열이 나타나게 된다.

y = y.astype(np.uint8)
def plot_digit(data):
    image = data.reshape(28, 28)
    plt.imshow(image, cmap = mpl.cm.binary,
               interpolation="nearest")
    plt.axis("off")
    
def plot_digits(instances, images_per_row=10, **options):
    size = 28
    images_per_row = min(len(instances), images_per_row)
    images = [instance.reshape(size,size) for instance in instances]
    n_rows = (len(instances) - 1) // images_per_row + 1
    row_images = []
    n_empty = n_rows * images_per_row - len(instances)
    images.append(np.zeros((size, size * n_empty)))
    for row in range(n_rows):
        rimages = images[row * images_per_row : (row + 1) * images_per_row]
        row_images.append(np.concatenate(rimages, axis=1))
    image = np.concatenate(row_images, axis=0)
    plt.imshow(image, cmap = mpl.cm.binary, **options)
    plt.axis("off")
plt.figure(figsize=(9,9))
example_images = X[:100]
plot_digits(example_images, images_per_row=10)
save_fig("more_digits_plot")
plt.show()

학습 데이터와 테스트 데이터를 분류하자.

X_train, X_test, y_train, y_test = X[:60000], X[60000:], y[:60000], y[60000:]

이진분류기 (Binary classifier)

숫자 5만 식별해보자.

y_train_5 = (y_train == 5)
y_test_5 = (y_test == 5)

# 로지스틱 회귀 모델 사용

from sklearn.linear_model import LogisticRegression
log_clf = LogisticRegression(random_state=0).fit(X_train, y_train_5)

log_clf.predict([X[0],X[1],X[2]]) # array([ True, False, False]) #y[0]=5, y[1]=0, y[2]=4이기 때문에 제대로 정답이 나왔다.

# 교차 검증을 사용해 평가

from sklearn.model_selection import cross_val_score
cross_val_score(log_clf, X_train, y_train_5, cv=3, scoring="accuracy") # array([0.97525, 0.97325, 0.9732 ])

모든 교차 검증 폴드에 대해 정확도가 97% 이상이다. 모델이 좋은 것일까?
또 다른 classify를 만들어 보도록 하자. 지금 만들 분류기는 무조건 5가 아니라고 판별하는 함수이다.

from sklearn.base import BaseEstimator
class Never5Classifier(BaseEstimator):
    def fit(self, X, y=None):
        pass
    def predict(self, X):
        return np.zeros(len(X), dtype=bool)
        
never_5_clf = Never5Classifier()
cross_val_score(never_5_clf, X_train, y_train_5, cv=3, scoring="accuracy") # array([0.91125, 0.90855, 0.90915])

never_5_clf.predict(X) # array([False, False, False, ..., False, False, False])

다 5가 아니라고 판별하는 데에도 불구하고 정확도가 90이 넘게 나온다. 어떻게 이런 결과가 나오는 것일까?

 

숫자 5는 학습 데이터 상에서 10퍼센트 정도의 분포를 차지한다. 그래서 무조건 이것은 5가 아니라고 하면 맞을 확률이 자연스럽게 90퍼센트가 넘게 되는 것이다.

 

이와 비슷한 경우로 1%의 사람에게서만 발병되는 아주 희귀한 병에 대해서 진단을 할 때 관련 모델을 만들면 그 모델이 100%에 대해 그 병이 아니다라고 진단을 내리게 되면 그 진단의 Accuracy는 99%가 되나, 그 모델은 좋은 모델이라고 할 수 없는 것이다. 왜냐하면 그 병을 지닌 사람에 대해서 정확한 진단을 내릴 수 없게 되기 때문이다.

 

즉, 목표값이 불균형인 경우에 정확도는 좋은 지표가 아니다.

 

오차 행렬(Confusion matrix)

 

from sklearn.model_selection import cross_val_predict

y_train_pred = cross_val_predict(log_clf, X_train, y_train_5, cv=3)

y_train_pred.shape # (60000, )

from sklearn.metrics import confusion_matrix

confusion_matrix(y_train_5, y_train_pred)
'''
array([[54039,   540],
       [ 1026,  4395]], dtype=int64)
'''

TN|FP

FN|TP

로 구성되어 있는 오차 행렬이다.

 

 

이 때 정밀도는

Precision = TP/TP+FP

 

재현율은

Recall = TP/TP+FN

이다.

 

즉, 위와 같은 경우에서의 정밀도는

4395/(4395+540)

 

재현율은

4395/(4395+1026)

 

scikit-learn에서 주어진 함수를 통해 정밀도, 재현율을 구해보자.

from sklearn.metrics import precision_score, recall_score

precision_score(y_train_5, y_train_pred) # 0.8905775075987842

4395/(4395+541) # 0.8903970826580226

recall_score(y_train_5, y_train_pred) # 0.8107360265633647

4395/(4395+1026) # 0.8107360265633647

 

그렇다면 모든 답을 5가 아니라고 판별한 모델에 대한 오차 행렬을 구해보자.

confusion_matrix(y_train_5, never_5_clf.predict(X)[:60000])

'''
array([[54579,     0],
       [ 5421,     0]], dtype=int64)
'''

precision_score(y_train_5, never_5_clf.predict(X)[:60000]) # 0

recall_score(y_train_5, never_5_clf.predict(X)[:60000]) # 0

positive 쪽의 답이 전부 0이 나왔다. 그에 따라 자연스럽게 정밀도와 재현율 또한 0이 나오게 된다. 정확도는 90퍼센트로 높게 나왔지만 결과적으론 상당히 좋지 않은 모델이 된 것이다. 

 

그렇다면 어떤 경우에 정밀도가 중요하고, 어떤 경우에 재현율이 중요할까?

병에 대한 진단 모델같은 큰 Risk가 있는 모델의 경우 정밀도보다는 재현율이 높아야 한다.

 

반대의 경우로, 동영상을 분류할 때 어린이들에게 안전한 동영상인지 아닌지 판별하는 알고리즘을 만든다고 할 땐 정밀도가 중요하다고 할 수 있다.

 

Precision / Recall Trade-off

 

 

threshold를 어디에 잡는지에 따라 Precision과 Recall의 값이 완전히 달라질 것이다.

 

y_train_pred[48], y_train_5[48] # (True, False)

some_digit = X_train[48]

y_scores = log_clf.decision_function([some_digit]) # 예측값이 얼마나 정답으로부터 떨어져 있는지 수치로 보여줌.
y_scores # array([0.22419046])
some_digit_image = some_digit.reshape(28, 28)
plt.imshow(some_digit_image, cmap=mpl.cm.binary)
plt.axis("off")

save_fig("some_digit_plot")
plt.show()

threshold = 0
y_some_digit_pred = (y_scores > threshold)
y_some_digit_pred # array([ True])

threshold = 0.5
y_some_digit_pred = (y_scores > threshold)
y_some_digit_pred # array([False]) 

한 경우에 대해 threshold에 따라서 recall을 줄일 수도 (threshold가 클수록) 있다. 이런 식으로 recall과 precision의 비율을 조절할 수 있는 것이다.

y_scores = cross_val_predict(log_clf, X_train, y_train_5, cv=3,
                             method="decision_function")
                             
y_scores.shape # (60000,)

from sklearn.metrics import precision_recall_curve

precisions, recalls, thresholds = precision_recall_curve(y_train_5, y_scores)

precisions.shape # (59897,) # 각 threshold에 따른 precision의 값들

thresholds.shape # (59896,) # 가능한 모든 경우의 threshold의 값들

 

precision과 recall을 그래프로 나타낼 수 있다.

def plot_precision_vs_recall(precisions, recalls):
    plt.plot(recalls, precisions, "b-", linewidth=2)
    plt.xlabel("Recall", fontsize=16)
    plt.ylabel("Precision", fontsize=16)
    plt.axis([0, 1, 0, 1])
    plt.grid(True)

plt.figure(figsize=(8, 6))
plot_precision_vs_recall(precisions, recalls)
save_fig("precision_vs_recall_plot")
plt.show()


다중 분류 (Multicalss Classification)

 

지금까지 MNIST 문제를 이진 분류해보았다. 이번엔 원래의 MNIST 분류 문제의 목적에 맞게 다중 분류를 해보도록 하자.

 

from sklearn.linear_model import LogisticRegression
softmax_reg = LogisticRegression(multi_class="multinomial",solver="lbfgs", C=10) # multinomial로 설정하면 multiclass에 대한 logisticregression이 가능
softmax_reg.fit(X_train, y_train)

softmax_reg.predict(X_train)[:10] # array([5, 0, 4, 1, 9, 2, 1, 3, 1, 4], dtype=uint8) # 정확히 예측하고 있다.

from sklearn.metrics import accuracy_score
y_pred = softmax_reg.predict(X_test)
accuracy_score(y_test, y_pred) # 0.9243

 

위의 모델을 조금 더 향상시키도록 하자.

 

Data Augmentation

 

가지고 있는 학습데이터에 레이블을 유지한 채 약간의 변형을 가해 데이터를 더하여 모델을 새로 학습했을 때 조금 더 안정적인 모델을 만들어낼 수 있을 것이다.

 

from scipy.ndimage.interpolation import shift

# 오른쪽이나 아래로 이미지를 조금씩 shift시키는 함수
def shift_image(image, dx, dy):
    image = image.reshape((28, 28))
    shifted_image = shift(image, [dy, dx], cval=0, mode="constant")
    return shifted_image.reshape([-1])
    
image = X_train[1000]
shifted_image_down = shift_image(image, 0, 5) # 아래쪽으로 이동
shifted_image_left = shift_image(image, -5, 0) # 왼쪽으로 이동

plt.figure(figsize=(12,3))
plt.subplot(131)
plt.title("Original", fontsize=14)
plt.imshow(image.reshape(28, 28), interpolation="nearest", cmap="Greys")
plt.subplot(132)
plt.title("Shifted down", fontsize=14)
plt.imshow(shifted_image_down.reshape(28, 28), interpolation="nearest", cmap="Greys")
plt.subplot(133)
plt.title("Shifted left", fontsize=14)
plt.imshow(shifted_image_left.reshape(28, 28), interpolation="nearest", cmap="Greys")
plt.show()

X_train_augmented = [image for image in X_train]
y_train_augmented = [label for label in y_train]

# 레이블을 유지한 채 shift한 데이터를 추가하는 작업
for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)):
    for image, label in zip(X_train, y_train):
        X_train_augmented.append(shift_image(image, dx, dy)) 
        y_train_augmented.append(label)

X_train_augmented = np.array(X_train_augmented)
y_train_augmented = np.array(y_train_augmented)

X_train_augmented.shape # (300000, 784) # 원래의 데이터보다 5배만큼 늘어났다.

# 같은 레이블의 데이터가 연속으로 있으면 모델을 학습하는 데 있어서 좋지 않은 영향을 끼칠 것이다. 그래서 데이터를 섞어주는 작업을 하자.
shuffle_idx = np.random.permutation(len(X_train_augmented))
X_train_augmented = X_train_augmented[shuffle_idx]
y_train_augmented = y_train_augmented[shuffle_idx]

# 모델 학습
softmax_reg_augmented = LogisticRegression(multi_class="multinomial",solver="lbfgs", C=10)
softmax_reg_augmented.fit(X_train_augmented, y_train_augmented)

y_pred = softmax_reg_augmented.predict(X_test)
accuracy_score(y_test, y_pred) # 0.9279 # data augmentation을 진행하기 전보다 0.002정도 오른 것을 확인할 수 있다.
# 0.002면 작은 수치일 수도 있으나, 이미 잘 나온 모델에서 조금이라도 정확도를 올렸다는 것은 굉장히 유의미한 일이다.

Titanic 데이터셋에 대해 분류하기

 

import numpy as np
import pandas as pd

train_data = pd.read_csv("titanic.csv")

 

속성들

  • Survived: that's the target, 0 means the passenger did not survive, while 1 means he/she survived.
  • Pclass: passenger class.
  • Name, Sex, Age: self-explanatory
  • SibSp: how many siblings & spouses of the passenger aboard the Titanic.
  • Parch: how many children & parents of the passenger aboard the Titanic.
  • Ticket: ticket id
  • Fare: price paid (in pounds)
  • Cabin: passenger's cabin number
  • Embarked: where the passenger embarked the Titanic
train_data.info()
'''
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
 #   Column       Non-Null Count  Dtype  
---  ------       --------------  -----  
 0   PassengerId  891 non-null    int64  
 1   Survived     891 non-null    int64  
 2   Pclass       891 non-null    int64  
 3   Name         891 non-null    object 
 4   Sex          891 non-null    object 
 5   Age          714 non-null    float64
 6   SibSp        891 non-null    int64  
 7   Parch        891 non-null    int64  
 8   Ticket       891 non-null    object 
 9   Fare         891 non-null    float64
 10  Cabin        204 non-null    object 
 11  Embarked     889 non-null    object 
dtypes: float64(2), int64(5), object(5)
memory usage: 83.7+ KB
'''

Age, Cabin, Embarked 속성들이 missing value를 갖고 있다.

Cabin, Name, Ticket 속성은 무시하도록 하자.

이 중 Ticket같은 경우는 고유한 번호가 될 가능성이 높은데 고유한 식별자를 feature data로 쓰면 학습을 할 때 고유한 정보를 외우는 수준까지 갈 수도 있기 때문에(이 고유한 식별자에 집중하게 될 수도 있기 때문에) 예측값이 굉장히 안 좋아질 수도 있다. 그렇기 때문에 사용하지 않는 것이 더 좋다고 볼 수 있다.

train_data["Survived"].value_counts()
'''
0    549
1    342
Name: Survived, dtype: int64
'''
train_data["Pclass"].value_counts()
'''
3    491
1    216
2    184
Name: Pclass, dtype: int64
'''
train_data["Sex"].value_counts()
'''
male      577
female    314
Name: Sex, dtype: int64
'''
train_data["Embarked"].value_counts()
'''
S    644
C    168
Q     77
Name: Embarked, dtype: int64
'''
from sklearn.base import BaseEstimator, TransformerMixin

# 속성을 골라 사용할 수 있도록 해줌
class DataFrameSelector(BaseEstimator, TransformerMixin):
    def __init__(self, attribute_names):
        self.attribute_names = attribute_names
    def fit(self, X, y=None):
        return self
    def transform(self, X):
        return X[self.attribute_names]
      
# Numerical 속성 처리 Pipeline      
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer

num_pipeline = Pipeline([
        ("select_numeric", DataFrameSelector(["Age", "SibSp", "Parch", "Fare"])),
        ("imputer", SimpleImputer(strategy="median")),
    ])
    

# missing value를 가장 많이 나오는 값으로 채워넣어준다.
class MostFrequentImputer(BaseEstimator, TransformerMixin):
    def fit(self, X, y=None):
        self.most_frequent_ = pd.Series([X[c].value_counts().index[0] for c in X],
                                        index=X.columns)
        return self
    def transform(self, X, y=None):
        return X.fillna(self.most_frequent_)

# Categorical 속성 처리 Pipeline
from sklearn.preprocessing import OneHotEncoder
cat_pipeline = Pipeline([
        ("select_cat", DataFrameSelector(["Pclass", "Sex", "Embarked"])),
        ("imputer", MostFrequentImputer()),
        ("cat_encoder", OneHotEncoder(sparse=False)),
    ])

# Categorical, Numerical 속성들을 통합하자.
from sklearn.pipeline import FeatureUnion
preprocess_pipeline = FeatureUnion(transformer_list=[
        ("num_pipeline", num_pipeline),
        ("cat_pipeline", cat_pipeline),
    ])

# 최종적으로 만들어진 pipeline을 train_data에 적용시켜보자.
X_train = preprocess_pipeline.fit_transform(train_data)
X_train.shape # (891, 12)

# 목표값 벡터
y_train = train_data["Survived"]

log_clf = LogisticRegression(random_state=0).fit(X_train, y_train)

# 모델에서 어떤 통찰을 얻을 수 있는지 살펴보기
a = np.c_[log_clf.decision_function(X_train), y_train, X_train] # np.c_는 1차원 배열을 칼럼으로 붙여 2차원 배열로 만드는 것이다.
df = pd.DataFrame(data=a, columns=["Score", "Survived", "Age", "SibSp", "Parch", "Fare", "Pclass_1", "Pclass_2", "Pclass_3", "Female", "Male", "Embarked_C", "Embarked_Q", "Embarked_S"])
df.sort_values(by=['Score'], ascending=False)[:20]

df.sort_values(by=['Score'])[:20]

 

'AI > KDT 인공지능' 카테고리의 다른 글

[06/21] 신경망의 기초  (0) 2021.06.21
[06/16] 인공지능과 기계학습  (0) 2021.06.16
오토인코더, t-SNE  (0) 2021.06.14
[06/02] End to End Machine Learning Project  (0) 2021.06.02
[05/18] Django로 동적 웹페이지 만들기  (0) 2021.05.18

오토인코더를 이용한 준지도학습

 

준지도학습이란? 

 

레이블이 있는 데이터셋에 대해 모델을 학습하는 과정 (지도학습)

+

목표하는 변수를 알 수 없는 데이터셋에 대해 모델을 학습하는 과정 (비지도학습)

 

AutoEncoder

 

가장 대표적 비지도학습 방법.

입력을 출력으로 변환하기 위해 표현하는 중간 상태 학습

 

그림 출처 : https://jaehyeongan.github.io/2020/02/28/Autoencoder-LSTMautoencoder/

 

입력값을 답으로 주기 때문에 스스로 학습을 한다고 해서 자기지도학습이라고도 얘기한다.

차원이 축소되는 부분을 Bottleneck이라고 한다.

AutoEncoder는 이 z 부분을 효과적으로 학습하는 것이 목적이다.

 

대표적인 사용 예로, 입력 데이터에 노이즈를 더해 입력하고, 출력 데이터에는 원본을 출력하도록 학습하여 노이즈를 제거하는 모델을 만들 수 있다.

 

t-SNE visualizaition (t-Distributed Stochastic Neighbor Embedding)

 

데이터의 차원을 줄이고, 최대 정보를 가진 상위 n개의 component만 생성하는 데이터셋 decomposition 기술

 

그림 출처 : 프로그래머스 스쿨

https://www.kaggle.com/mlg-ulb/creditcardfraud 데이텃세 활용

 

부동산 회사에 막 고용된 데이터 과학자라 가정 후 예제 프로젝트를 처음부터 끝까지 (End-to-End) 진행하자. 주요 단계는 다음과 같다.

  1. 큰 그림을 본다. (look at the big picture)
  2. 데이터를 구한다. (get the data)
  3. 데이터로부터 통찰을 얻기 위해 탐색하고 시각화한다. (discover and visualize the data to gain insights)
  4. 머신러닝 알고리즘을 위해 데이터를 준비한다. (prepare the data for Machine Learning algorithms)
  5. 모델을 선택하고 훈련시킨다. (select a model and train it)
  6. 모델을 상세히 조정한다. (fine-tune your model)
  7. 솔루션을 제시한다. (present your solution)
  8. 시스템을 론칭하고 모니터링하고 유지 보수한다.(launch, monitor, and maintain your system)

큰 그림 보기

풀어야 할 문제 : 캘리포니아 인구조사 데이터를 사용해 캘리포니아의 주택 가격 모델을 만든다.

이 모델이 전체 시스템 안에서 어떻게 사용될 지 이해하는 것이 중요하다.

중요한 질문 : 현재 솔루션은? 전문가가 수동으로? 복잡한 규칙? 머신러닝?

문제 정의

  • 지도학습(supervised learning), 비지도 학습(unsupervised learning), 강화학습(reinforcement learning) 중 어떤 경우에 해당하는가? - 지도학습이다.
  • 분류문제(classification)인가 아니면 회귀문제(regression)인가? - 회귀문제이다.
  • 배치학습(batch learning), 온라인학습(online learning) 중 어떤 것을 사용해야 하는가? - 배치학습을 사용해야 한다.

성능측정지표(performance measure) 선택

평균제곱근 오차(root mean square error (RMSE))

  • m : 데이터셋에 있는 샘플 수
  • x^(i) : i번째 샘플의 전체 특성값의 벡터(vector)
  • y^(i) : i번째 샘플의 label(해당 샘플 기대 출력값)

  • X : 데이터셋 모든 샘플의 모든 특성값(features)을 포함하는 행렬(matrix)
  • h : 예측함수(prediction function). 하나의 샘플 x^(i)에 대해 예측값 yhat^(i)=h(x^(i))를 출력함.
  • RMSE(X, h) : 모델 h가 얼마나 좋은지 평가하는 지표, 또는 비용함수(cost function)

데이터 가져오기 (Get the Data)

 

데이터 구조 훑어보기

 

housing = load_housing_data()
housing.head()

 

각각의 행은 한 구역(지역)을 나타낸다.

 

  • longtitude / latitude : 위치 정보(경도와 위도)
  • housing_median_age : 해당 지역의 집들을 기준으로 연도를 조사한 중간값
  • total_rooms : 지역 전체 방의 수
  • etc

 

housing.info()

 

총 20640개의 행들로 구성되어 있음을 알 수 있다. 이 중 total_bedrooms 열에 대해서는 167개의 missing value(null data)가 존재함을 알 수 있다.

마지막 ocean_proximity는 유일하게 Dtype이 'object'인데 이는 csv 파일에서 읽었기 때문에 text 데이터(범주형 필드)라는 것을 유추할 수 있다.

 

이 데이터들을 히스토그램으로 분석해보자.

 

%matplotlib inline
import matplotlib.pyplot as plt
housing.hist(bins=50, figsize=(20,15))
save_fig("attribute_histogram_plots")
plt.show()

 

 

median income의 x축 값같은 경우 0, 2, 4...로 굉장히 작은 값들로 이뤄져있음을 알 수 있다. 살짝 이상함을 느낄 수 있을 것이다. 이에 대한 정확한 단위를 데이터를 제작한 사람에게 문의하고, 확인하여야 한다.

또, 가끔 이상하게 뒷 쪽에 값이 몰려있는 부분이 있다. 이렇듯 이상할 수 있는 것은 특정값 상위값에서 제한을 둔 것이라고 생각할 수 있을 것이다. 이런 부분도 데이터를 만든 사람에게 문의하고, 확인할 수 있도록 하여야 한다.


데이터로부터 통찰을 얻기 위해 탐색하고 시각화한다.

 

테스트 데이터셋 만들기

 

하나의 데이터셋을 훈련 / 테스트용으로 분리하기

 

import numpy as np
np.random.seed(42)

def split_train_test(data, test_ratio):
    shuffled_indices = np.random.permutation(len(data))
    test_set_size = int(len(data) * test_ratio)
    test_indices = shuffled_indices[:test_set_size]
    train_indices = shuffled_indices[test_set_size:]
    return data.iloc[train_indices], data.iloc[test_indices]
    
train_set, test_set = split_train_test(housing, 0.2)
len(train_set), len(test_set)

 

 

총 20640개의 데이터에서 16512개의 데이터가 훈련용, 4128개의 데이터가 테스트용으로 임의로 뒤섞여 분리되었다.

 

그러나 위 방법에는 문제점이 하나 있다. 바로, 테스트 데이터를 뽑아내는 작업을 한 번만 하게 되는 것이 아니라 여러 번 하게 될 가능성이 높다는 것이다. 예를 들어 새로운 데이터가 들어왔을 때 다시 한 번 훈련 데이터와 테스트 데이터로 나누고 싶은 경우가 생길 수도 있는데 이 때, 또 위의 함수를 사용한다면 이전 훈련 데이터에 속하던 데이터들이 테스트 데이터로 속하게 될 수도 있고 반대의 경우가 생기게 될 수도 있을 것이다. 그러나 우리가 원하는 것은 한 번 테스트 데이터였던 데이터들은 계속 테스트 데이터로 남는 것이다.

 

이를 해결하기 위해선 각각 샘플마다 식별자(identifier)를 사용해 분할한다!

 

from zlib import crc32

def test_set_check(identifier, test_ratio):
    return crc32(np.int64(identifier)) & 0xffffffff < test_ratio * 2**32

def split_train_test_by_id(data, test_ratio, id_column):
    ids = data[id_column]
    in_test_set = ids.apply(lambda id_: test_set_check(id_, test_ratio))
    return data.loc[~in_test_set], data.loc[in_test_set]
    

 

test_set_check에서 식별자가 들어왔을 때, crc32 함수를 해싱 함수로 사용하고, & 기호는 bitwise and function이다. 위의 0xffffffff 숫자는 (2^32)-1이다. 저렇게 하게 되면 2^32으로 나눈 나머지 값이 되는 것이다. 그래서 식별자가 들어오면 이 식별자를 가진 데이터가 테스트 데이터에 속하는지 속하지 않는지 True or False를 반환하게 된다.

 

이제 식별자가 될 column을 추가해주어 보도록 하자.

 

# 인덱스를 id로 추가

housing_with_id = housing.reset_index()   # adds an `index` column
train_set, test_set = split_train_test_by_id(housing_with_id, 0.2, "index")

 

위의 방법에는 문제점이 있다. 왜냐하면 id가 되려면 id는 늘 일정히 유지를 해야 하는데 행 번호를 id로 쓰게 되면 나중에 데이터베이스가 update 되어 몇몇 데이터들은 사라질 수도 있고, 중간에 삽입이 될 수도 있고(이런 문제를 방지하기 위해선 늘 새로운 데이터는 뒤에 넣어주어야 한다는 제약이 생기게 되기도 한다.), ... 그렇게 되면 행 번호가 바뀌게 될 수도 있기 때문이다. 

 

그래서 id를 만드는 데 안전한 feature들을 사용하도록 해야 한다.

 

# 경도와 위도를 사용한 식별자 생성

housing_with_id["id"] = housing["longitude"] * 1000 + housing["latitude"]
train_set, test_set = split_train_test_by_id(housing_with_id, 0.2, "id")
train_set.head()

 

위 데이터에서 가장 unique한 데이터인 지리적 속성을 사용해서 식별자를 생성할 수 있게 된다. id는 다음과 같은 새로운 column의 형태로 생성된다.

 

 

우리의 scikit-learn에서 친절하게도 기본적으로 제공되는 데이터분할 함수가 있다.

 

from sklearn.model_selection import train_test_split

train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42)

 

위 우리가 생성한 함수에서처럼 20퍼센트의 데이터를 test 데이터로 사용할 것이고, 훈련 데이터는 train_set에, 테스트 데이터는 test_set에 저장되게끔 할 것이다. 결과는 직접 생성한 함수와 같다. 그러나 이렇게 하게 되면 우리가 식별자를 잘 나눠주었음에도 불구하고 문제가 생길 수 있다. 예를 들어 얘기하자면, 투표가 있으면 남성과 여성의 투표 전체적 비율이 훈련 데이터와 테스트 데이터에도 잘 보존이 될 수 있도록 해야 공평한 테스트가 이뤄진다고 볼 수 있을 것이다. 그래서 이러한 경우를 위해서 우리는 계층적 샘플링(stratified sampling)을 한다.

 

위 데이터의 경우에는 가장 중요한 특성인 'median income'의 분포가 훈련 데이터, 테스트 데이터에 잘 나타날 수 있도록 해보겠다.

housing["median_income"].hist()

 

위 데이터들을 그룹으로 나눠 연속적인 데이터에 범주형(categorical) 데이터로 변환해보도록 하자. 

 

housing["income_cat"] = pd.cut(housing["median_income"],
                               bins=[0., 1.5, 3.0, 4.5, 6., np.inf],
                               labels=[1, 2, 3, 4, 5])

 

housing["median_income"].hist()

 

 

이렇게 중요한 데이터를 범주형 데이터로 변환한 후에 이를 이용해 계층적 샘플링을 하도록 하자. 이를 직접 구현할 수 도 있겠지만... 아까 말했다시피 친절한 scikit-learn에서는 이 계층적 샘플링에 대해서도 이미 구현이 되어 있다!!!

 

from sklearn.model_selection import StratifiedShuffleSplit

split = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=42)
for train_index, test_index in split.split(housing, housing["income_cat"]):
    strat_train_set = housing.loc[train_index]
    strat_test_set = housing.loc[test_index]

 

housing["income_cat"].value_counts() / len(housing)

 

 

strat_train_set["income_cat"].value_counts() / len(strat_train_set)

 

 

strat_test_set["income_cat"].value_counts() / len(strat_test_set)

 

 

완전히 똑같진 않지만 원래의 데이터와 훈련 데이터, 테스트 데이터가 계층(우리가 median_income을 범주형 데이터로 변환한 새로운 데이터인 income_cat)에 따라 똑같진 않지만 매우 비슷한 비율로 잘 분포되어 나눠졌음을 확인할 수 있다!

 

즉, 계층적 샘플링을 하게 되면 우리가 중요하다고 생각하는 특성에 관해서 그 분포를 그대로 가져갈 수 있게 된다.


데이터 이해를 위한 탐색과 시각화

 

지리적 데이터 시각화

 

housing.plot(kind="scatter", x="longitude", y="latitude")
save_fig("bad_visualization_plot")

 

 

밀집된 영역 표시

 

housing.plot(kind="scatter", x="longitude", y="latitude", alpha=0.1)
save_fig("better_visualization_plot")

 

 

더 다양한 조건을 표시해보자.

 

housing.plot(kind="scatter", x="longitude", y="latitude", alpha=0.4,
    s=housing["population"]/100, label="population", figsize=(10,7), # 원의 반지름 => 인구
    c="median_house_value", cmap=plt.get_cmap("jet"), colorbar=True, # 색상 => 가격
    sharex=False)
plt.legend()
save_fig("housing_prices_scatterplot")

 

 

지도 위에도 직접 나타내보자!!!

 

그림 출처 : https://ko.wikipedia.org/wiki/%EC%BA%98%EB%A6%AC%ED%8F%AC%EB%8B%88%EC%95%84%EC%A3%BC#/media/%ED%8C%8C%EC%9D%BC:USA_California_location_map.svg

# Download the California image
images_path = os.path.join(PROJECT_ROOT_DIR, "images", "end_to_end_project")
os.makedirs(images_path, exist_ok=True)
#DOWNLOAD_ROOT = "https://raw.githubusercontent.com/ageron/handson-ml2/master/"
DOWNLOAD_ROOT = "https://ko.wikipedia.org/wiki/%EC%BA%98%EB%A6%AC%ED%8F%AC%EB%8B%88%EC%95%84%EC%A3%BC#/media/%ED%8C%8C%EC%9D%BC:USA_California_location_map.svg"
filename = "california.png"

#나 같은 경우는 주석친 교수님이 주신 사이트에 캘리포니아 지도 이미지가 나타나지 않아 아래의 이미지를 따로 다운로드 받아 사용하였다...

import matplotlib.image as mpimg
california_img=mpimg.imread(os.path.join(images_path, filename))
ax = housing.plot(kind="scatter", x="longitude", y="latitude", figsize=(10,7),
                       s=housing['population']/100, label="Population",
                       c="median_house_value", cmap=plt.get_cmap("jet"),
                       colorbar=False, alpha=0.4,
                      )
plt.imshow(california_img, extent=[-124.55, -113.80, 32.45, 42.05], alpha=0.5,
           cmap=plt.get_cmap("jet"))
plt.ylabel("Latitude", fontsize=14)
plt.xlabel("Longitude", fontsize=14)

prices = housing["median_house_value"]
tick_values = np.linspace(prices.min(), prices.max(), 11)
cbar = plt.colorbar()
cbar.ax.set_yticklabels(["$%dk"%(round(v/1000)) for v in tick_values], fontsize=14)
cbar.set_label('Median House Value', fontsize=16)

plt.legend(fontsize=16)
save_fig("california_housing_prices_plot")
plt.show()

 


상관관계(Correlations) 관찰하기

 

corr_matrix = housing.corr()
corr_matrix["median_house_value"].sort_values(ascending=False)

# from pandas.tools.plotting import scatter_matrix # For older versions of Pandas
from pandas.plotting import scatter_matrix

# 특성 몇 개만 살펴봄 
attributes = ["median_house_value", "median_income", "total_rooms",
              "housing_median_age"]
scatter_matrix(housing[attributes], figsize=(12, 8))
save_fig("scatter_matrix_plot")

 

 

여기서 median_house_value와 median_income 사이에서의 상관관계를 살펴보면

 

housing.plot(kind="scatter", x="median_income", y="median_house_value",
             alpha=0.1)
plt.axis([0, 16, 0, 550000])
save_fig("income_vs_house_value_scatterplot")

 

 

50만불에서의 데이터가 많이 모여있다는 사실을 알 수 있다. 우리는 이를 통해 집 값이 50만 불 이상으로 넘어가는 값들에 대해서는 딱 50만 불로 정제를 했을 것이라는 사실을 유추할 수 있다. 이런식으로 비정상적으로 보이는 데이터 같은 경우는 가능하면 traininig data에서 제거시켜주는 것이 모델 학습에 더 좋은 결과를 나타내어 줄 것이다.

 

우리는 다양한 특성들을 조합해서 실험을 해볼 수 있다.

 

가령, 가구당 방 개수가 평균적으로 몇 개일지,

침대방의 비율은 얼마나 될지,

가구당 인원은 얼마나 되는지 등에 대해서 있는 특성을 조합해 새로운 특성으로써 만들어 사용할 수 있다.

 

housing["rooms_per_household"] = housing["total_rooms"]/housing["households"]
housing["bedrooms_per_room"] = housing["total_bedrooms"]/housing["total_rooms"]
housing["population_per_household"]=housing["population"]/housing["households"]

 

이렇게 새롭게 만들어 낸 특성으로 다음과 같은 새로운 사실을 유추해 낼 수 있을 것이다.

 

bedrooms per room같은 경우에는 하나의 집에서 침실이 차지하는 비율인데 집이 당연히 크면 클수록 침실로 쓸 방들은 제한이 되고, 다른 방들의 비율이 더 늘어날 것이다.

 

이 외에도 또다른 새로운 사실들을 많이 유추해 낼 수 있을 것이다.

 

이제는 실제로 모델에 데이터를 학습시키기 위하여 조금 더 데이터를 정제하는 작업을 거쳐보도록 하겠다.

 

데이터는 수동으로 변환하는 것보다 자동으로 변환하는 것에 대한 장점은 다음과 같다.

 

  • 새로운 데이터에 대한 변환을 손쉽게 재생산(reproduce)할 수 있다.
  • 향후에 재사용(reuse)할 수 있는 라이브러리를 구축할 수 있다.
  • 실제 시스템에 가공되지 않은 데이터(raw data)를 알고리즘에 쉽게 입력으로 사용할 수 있도록 해준다.
  • 여러 데이터 변환 방법을 쉽게 시도해 볼 수 있다.

 

데이터를 정제하기 이전, 데이터를 가공할 필요가 없는 median_house_value 데이터는 따로 빼둔다.

 

housing = strat_train_set.drop("median_house_value", axis=1) # drop labels for training set
housing_labels = strat_train_set["median_house_value"].copy()

 

이제 본격적으로 데이터를 정제해보도록 하자.


머신러닝 알고리즘을 위해 데이터를 준비한다.

 

데이터 정제(Data Cleaning)

 

누락된 특성(missing values, null values) 다루는 방법들 - 우리가 사용하는 데이터에선 'total_bedrooms' 특성만이 missing values가 존재했다!

 

  • 해당 구역 제거 (행 제거)
    • sample_incomplete_rows.dropna(subset=["total_bedrooms"])
  • 해당 특성 제거 (열 제거)
    • sample_incomplete_rows.drop("total_bedrooms", axis=1)       
  • 임의의 값으로 채움 (0, 평균, 중간값 등)
    • median = housing["total_bedrooms"].median()
      sample_incomplete_rows["total_bedrooms"].fillna(median, inplace=True)

 

위의 작업같은 경우에도 또 한 번 친절한(!!) Scikit-learn에서는 SimpleImputer라는 클래스를 통해 기능을 제공하고 있다.

SimpleImputer를 사용해보자.

 

from sklearn.impute import SimpleImputer
imputer = SimpleImputer(strategy="median")

 

위 같은 경우는 strategy를 median으로 설정해 null 값인 부분에 중위값으로 대신해서 채워넣는 작업을 한다.

그런데 SimpleImputer는 수치형 특성에만 작동이 되기 때문에 텍스트 특성은 제외하고 작업을 진행하여야 한다.

 

housing_num = housing.drop("ocean_proximity", axis=1)
imputer.fit(housing_num)
X = imputer.transform(housing_num)

# 이 때 X는 numpy array를 리턴하기 때문에 다시 pandas DataFrame으로 만들어 주어야 한다.

housing_tr = pd.DataFrame(X, columns=housing_num.columns,
                          index=housing.index)

 

이제 null 값을 채운 데이터를 보면

 

housing_tr.loc[sample_incomplete_rows.index.values]

 

 

N/A로 표시되던 값들이 모두 중위값인 433으로 채워넣어진 모습을 확인할 수 있다.


Estimator, Transformer, Predictor

 

  • 추정기(estimator)
    • 데이터셋을 기반으로 모델 파라미터들을 추정하는 객체를 추정기라고 합니다(예를 들자면 imputer). 추정자체는 fit() method에 의해서 수행되고 하나의 데이터셋을 매개변수로 전달받습니다(지도학습의 경우 label을 담고 있는 데이터셋을 추가적인 매개변수로 전달)
  • 변환기(transformer)
    • (imputer같이) 데이터셋을 변환하는 추정기를 변환기라고 합니다. 변환은 transform() method가 수행합니다. 그리고 변환된 데이터셋을 반환합니다.
  • 예측기(predictor)
    • 일부 추정기는 주어진 새로운 데이터셋에 대해 예측값을 생성할 수 있습니다. 앞에서 사용했던 LinearRegression도 예측기입니다. 예측기의 predict() method새로운 데이터셋을 받아 예측값을 반환합니다. 그리고 score() method는 예측값에 대한 평가지표를 반환합니다.

 

텍스트와 범주형 특성 다루기

 

위 데이터 중 범주형 특성이 하나 있었다. 바로 'ocean_proximity'이다. 이 데이터는 어떻게 처리를 해야 모델에서 쉽게 처리할 수 있을까?

 

각 범주를 Numerical한 형태로 바꿔주면 Machine Learning Algorithm에 활용할 수 있을 것이다.

 

첫 번째 방법은 OrdinalEncoder를 사용하는 것이다.

 

from sklearn.preprocessing import OrdinalEncoder

ordinal_encoder = OrdinalEncoder()
housing_cat_encoded = ordinal_encoder.fit_transform(housing_cat)
housing_cat_encoded[:10]

 

OrdinalEncoder는 카테고리들을에 대한 리스트를 생성했고, 그 리스트의 순서에 따라 값을 변환하는 즉, 0번째 자리에 있는 범주값은 0으로 변경하고 1번째 자리에 있는 범주값은 1로 변경하는... 굉장히 단순한 인코더인 것이다.

 

이 OrdianlEncoder에는 약간의 문제가 있다.

머신러닝 모델같은 경우에는 특성의 값이 비슷할수록 두 개의 샘플이 비슷하다는 것이 성립할 때 학습이 쉬워지게 된다. 그러나 위와 같은 경우에는 집이 바다에 얼마나 가까운지에 대한 속성인데 이 속성에 특징에 대해 전혀 분간을 즉, 어떤 집이 바다와 가까운지, 어떤 집이 바다와 가깝지 않은지에 대해 전혀 분간할 수 없게 된다. 이러한 문제점을 보완하기 위한 방법이 바로 One-hot encoding이다.

 

from sklearn.preprocessing import OneHotEncoder

cat_encoder = OneHotEncoder()
housing_cat_1hot = cat_encoder.fit_transform(housing_cat)

#One-hot encoding은 sparse matrix(하나의 값만 1이고 나머진 전부 0이기 때문에 공간 사용을 optimize하기 위해 저장한 특별한 형태)이기
#때문에 이를 보기 위해 다음과 같이 선언해준다.
housing_cat_1hot.toarray()

'''
#애초에 sparse option을 False로 주어 일반적인 Array로 생성해 보아도 된다.
cat_encoder = OneHotEncoder(sparse=False)
housing_cat_1hot = cat_encoder.fit_transform(housing_cat)
housing_cat_1hot
'''

 


나만의 변환기(Custom Transformers) 만들기

 

Scikit-Learn이 물론 유용한 변환기를 많이 제공하지만, 경우에 따라 특수한 데이터 처리 작업을 해야 할 경우가 많다. 이 때 나만의 변환기를 만들 수 있다.

 

반드시 구현해야 할 method들은 다음과 같다.

 

  • fit()
  • transform()

 

앞에서 우리는 feature들에 대한 조합으로 새로운 특성(rooms_per_household, population_per_household)을 만들어 보았다. 이 두 개의 새로운 특성을 데이터셋에 추가하는 작업을 해보도록 하자.

+) add_bedrooms_per_room(하이퍼 파라미터) = True로 주어지면 bedrooms_per_room  특성을 추가하자.

 

from sklearn.base import BaseEstimator, TransformerMixin

# column index
rooms_ix, bedrooms_ix, population_ix, households_ix = 3, 4, 5, 6

class CombinedAttributesAdder(BaseEstimator, TransformerMixin):
    def __init__(self, add_bedrooms_per_room = True): # no *args or **kargs
        self.add_bedrooms_per_room = add_bedrooms_per_room
    def fit(self, X, y=None):
        return self  # nothing else to do
    def transform(self, X):
        rooms_per_household = X[:, rooms_ix] / X[:, households_ix]
        population_per_household = X[:, population_ix] / X[:, households_ix]
        if self.add_bedrooms_per_room:
            bedrooms_per_room = X[:, bedrooms_ix] / X[:, rooms_ix]
            return np.c_[X, rooms_per_household, population_per_household,
                         bedrooms_per_room]
        else:
            return np.c_[X, rooms_per_household, population_per_household]

attr_adder = CombinedAttributesAdder(add_bedrooms_per_room=False)
#housing.values라는 값을 사용해 Numpy Array를 넘겼다고 보면 된다. 즉, 위에서의 X는 Numpy Array 데이터라는 것이다.
housing_extra_attribs = attr_adder.transform(housing.values)

#위를 실행시킬 때 numpy array로 변환시켜 넘겼기 때문에 return된 값도 numpy array이다.
#이를 DataFrame으로 변환하기 위하여 다음의 코드를 실행시켜야 한다.

housing_extra_attribs = pd.DataFrame(
    housing_extra_attribs,
    columns=list(housing.columns)+["rooms_per_household", "population_per_household"],
    index=housing.index)
housing_extra_attribs.head()

 


특성 스케일링(Feature Scaling)

 

범위가 너무 넓은 특성을 특정 범위 내에 있도록 스케일링하는 작업이다. 특성 스케일링은 다음과 같은 것들이 있다.

 

  • Min-max scaling : 0과 1사이 값이 되도록 조정
  • 표준화(standardlization) : 평균 0, 분산 1이 되도록 만들어 준다.(Scikit-Learn의 StandardScaler)

변환 파이프라인(Transformation Pipelines)

 

여러 변환이 순차적으로 이뤄져야 할 경우 Pipeline class를 사용한다.

 

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

num_pipeline = Pipeline([
        ('imputer', SimpleImputer(strategy="median")),
        ('attribs_adder', CombinedAttributesAdder()),
        ('std_scaler', StandardScaler()),
    ])

housing_num_tr = num_pipeline.fit_transform(housing_num)

 

아까 위에서 null 값을 중위값으로 채워주는 작업,

새롭게 만든 변환기에서의 작업,

표준화작업 세 가지를 Pipeline이 한꺼번에 수행할 수 있도록 도와준다.

 

마지막 단계를 제외하고는 전부 fit_transform() method를 가지고 있어야 하기 때문에 꼭 변환기여야 한다. 마지막은 fit() method만 가지고 있어도 된다.

 

각 열마다 다른 파이프라인을 적용할 수도 있다.

가령, 수치형 특성과 범주형 특성들에 대해 별도의 변환이 필요하다면 ColumnTransformer를 사용하면 된다.

 

from sklearn.compose import ColumnTransformer

num_attribs = list(housing_num)
cat_attribs = ["ocean_proximity"]

full_pipeline = ColumnTransformer([
        ("num", num_pipeline, num_attribs),
        ("cat", OneHotEncoder(), cat_attribs),
    ])

housing_prepared = full_pipeline.fit_transform(housing)

 

ColumnTransformer는 위의 Pipeline과는 달리 이름, 파이프라인, 파이프라인을 사용할 특성들의 리스트 총 세 가지를 전달해야 한다.


모델을 선택하고 훈련시킨다.

 

모델 훈련(Train a Model)

 

이제 본격적으로 모델을 훈련시켜 보도록 하자. 사용한 모델은 선형회귀모델이다.

 

from sklearn.linear_model import LinearRegression

lin_reg = LinearRegression()
lin_reg.fit(housing_prepared, housing_labels)

 

모델 훈련은 단 3줄이면 가능하다. 이제 몇 개 샘플에 모델을 적용해 예측값을 확인하고, 실제값과 비교해보자.

 

extra_attribs = ["rooms_per_hhold", "pop_per_hhold", "bedrooms_per_room"]
cat_encoder = full_pipeline.named_transformers_["cat"]
cat_one_hot_attribs = list(cat_encoder.categories_[0])
attributes = num_attribs + extra_attribs + cat_one_hot_attribs
sorted(zip(lin_reg.coef_, attributes), reverse=True)

 

왼쪽의 값은 학습한 계수의 값들이고, 오른쪽이 feature의 이름이다.

이 값이 클수록 집 값이 높은데에 더 높은 영향을 미친다는 것이다. 

 

비교적 계수값이 작은 경우에는 반드시 영향이 없는 것일까? 반드시 그런 것은 아니다. 다른 특성들 가운데 겹치는 부분, 관련성이 많은 부분이 있을 수도 있다. 이러한 경우에는 그 특성들 모두가 중요하다 해서 모든 값들이 큰 값을 가지거나, 작은 값을 가지거나 그러지 않고 하나만 큰 값을 가지고 나머진 작은 값을 가진다는 등 나타날 수 있어 계수들의 크기만 갖고 중요하다 아니다라고 판단하는 것은 위험한 판단일 수 있다.

 

몇 개의 샘플에 대해 데이터 변환 및 예측을 해보도록 하자.

 

some_data = housing.iloc[:5] #raw data
some_labels = housing_labels.iloc[:5]
some_data_prepared = full_pipeline.transform(some_data)

print("Predictions:", lin_reg.predict(some_data_prepared).round(decimals=1))
print("Labels:", list(some_labels))

 

첫 번째 값같은 경우는 실제값은 28만 예측값은 21만, 7만불정도 차이가 나는 경우를 볼 수 있다.

어떤 경우는 비슷하고, 어떤 경우는 빗나간 결과를 냈다는 것을 확인할 수 있다.


모델을 상세히 조정한다. (fine-tune your model)

 

전체 훈련 데이터셋에 대한 RMSE를 측정해보도록 하자.

 

from sklearn.metrics import mean_squared_error

housing_predictions = lin_reg.predict(housing_prepared)
lin_mse = mean_squared_error(housing_labels, housing_predictions)
lin_rmse = np.sqrt(lin_mse)
lin_rmse

 

7만불(대략 8천만원)정도가 평균적으로 에러가 나고 있다. 작은 에러라고 치부하고 넘어갈 수는 없을 것 같다. 게다가 테스트 데이터셋도 아니고 훈련 데이터셋에서 나는 에러라서... 우리가 적용한 모델에서 과소적합이 일어났음을 예상해볼 수 있을 것 같다... 왜 과소적합이 일어났을까?

 

우리는 몇 가지 근거를 생각해볼 수 있다.

 

  • 특성들(features)이 충분한 정보를 제공하지 못했다.
  • 모델이 충분히 강력하지 못했다.

 

우리는 두 번째 근거에 초점을 맞춰보도록 하겠다. 이번엔 강력한 비선형모델인 DecisionTreeRegressor를 사용해 보도록 하자.

 

잠깐 DecisionTreeRegressor가 뭔지 간단히 알아보도록 하자.

제일 위에 노드가 있다. 그 옆에 노드들이 있다. 이런 식으로 노드들 밑에 자식 노드들이 있는 우리들이 흔히 잘 아는 트리 구조이다.

그 노드 각각에는 Feature 이름이 주어진다. a라는 feature가 가장 위의 노드가 있다고 하면 들어온 데이터의 a와 일치하는 특성이 a에 정해진 특성 값보다 작으면 왼쪽 노드로 이동하게 된다.

다른 특성에도 마찬가지로 작으면 왼쪽, 크면 오른쪽 노드로 이동을 하다가 최종적으로 예측값을 return해내는 형태이다.

이러한 Tree를 만들어내는 과정이 모델의 학습 과정이다.

 

from sklearn.tree import DecisionTreeRegressor

tree_reg = DecisionTreeRegressor(random_state=42)
tree_reg.fit(housing_prepared, housing_labels)

housing_predictions = tree_reg.predict(housing_prepared)
tree_mse = mean_squared_error(housing_labels, housing_predictions)
tree_rmse = np.sqrt(tree_mse)
tree_rmse

 

에러값이 0이 나왔다.

그렇다면 DecisionTreeRegressor 모델은 선형모델보다 나을까? 과대적합(Overfitting)이진 않을까?

나은지 낫지 않은지 어떻게 알 수 있을까?

 

  • 테스트 데이터셋을 이용해 검증한다.
    • (비추천) 이 방법은 테스트 데이터셋을 계속해서 들여다보게 되고, 계속 모델이 학습하는데 영향을 미치게 된다. 그렇게 되면 나중에 또 다른 테스트 데이터가 들어갈 때 그 데이터에 대해선 좋지 못한 가능성을 낼 확률이 높아지게 된다. 최대한 모델을 런칭하기 전까지 미루는 것이 낫다.
  • 훈련 데이터셋의 일부를 검증데이터(validation data)셋으로 분리해 검증한다.
  • k-겹 교차 검증(k-fold cross-validation)

교차 검증(Cross-Validation)을 사용한 평가

 

교차 검증이란 무엇일까? 예시를 통해 알아보도록 하자.

 

ex) 학습데이터를 5개로 쪼개고, 첫 번째 데이터를 제외하고 4개의 데이터로 훈련을 시킨다. 제외한 첫 번째 데이터에 대한 RMSE를 구하여 모델의 Performance를 낸다.

다음으론 두 번째 데이터를 제외하고 나머지 4개의 데이터로 훈련을 시킨다. 제외한 두 번째 데이터에 대한 RMSE를 구하여 모델의 Performance를 낸다.

이것을 마지막 다섯 번째 데이터까지 똑같이 반복한다. 

최종적인 Performance의 값의 평균을 낸 값이 교차 검증을 통해 구할 수 있는 Error 값이다.

 

이제 교차 검증을 이용해서 모델에 대해 평가해보도록 하자.

 

from sklearn.model_selection import cross_val_score

scores = cross_val_score(tree_reg, housing_prepared, housing_labels,
                         scoring="neg_mean_squared_error", cv=10)
tree_rmse_scores = np.sqrt(-scores)

def display_scores(scores):
    print("Scores:", scores)
    print("Mean:", scores.mean())
    print("Standard deviation:", scores.std())

display_scores(tree_rmse_scores)

 

10개로 데이터를 나눴기 때문에 10개의 점수가 나왔다. 이 때 평균적인 에러값을 보니 71407...의 값이 나왔다. 아까의 0.0이 나왔던 모습과는 완전히 다른 결과가 나온 것이다. 즉, 위의 모델도 사용하지 않은 모델에 대해서는 좋지 않은 결과가 나온다는 것이다.

 

선형 모델의 평가는 다음과 같다.

 

lin_scores = cross_val_score(lin_reg, housing_prepared, housing_labels,
                             scoring="neg_mean_squared_error", cv=10)
lin_rmse_scores = np.sqrt(-lin_scores)
display_scores(lin_rmse_scores)

 

DecisionTreeRegressor 모델보다 Mean 값이 즉 에러의 평균값이 작게 나온 것을 확인할 수 있다. 오히려 선형 모델이 새로운 데이터에 대해선 더 좋은 퍼포먼스를 낼 수 있을 것이라고 예측할 수 있는 것이다.

 

그렇다고 해서 선형 모델의 에러값이 작은 값은 아니기 때문에 모델에 대한 개선이 필요할 것 같다.

이번엔 RandomForestRegressor 모델을 이용해보도록 하자.

 

RandomForestRegressor도 아까 DecisionTreeRegressor처럼 트리 구조이다. 그러나 이 모델의 다른 점은 여러 개의 트리를 사용한다는 것이다. 각 트리의 개수만큼 prediction값들이 있을 것이다. 이 prediction의 값들을 모아 평균을 내 최종적인 prediction을 내는 모델이 바로 RandomForestRegressor이다.

 

from sklearn.ensemble import RandomForestRegressor

forest_reg = RandomForestRegressor(n_estimators=100, random_state=42)
forest_reg.fit(housing_prepared, housing_labels)

housing_predictions = forest_reg.predict(housing_prepared)
forest_mse = mean_squared_error(housing_labels, housing_predictions)
forest_rmse = np.sqrt(forest_mse)
forest_rmse

from sklearn.model_selection import cross_val_score

forest_scores = cross_val_score(forest_reg, housing_prepared, housing_labels,
                                scoring="neg_mean_squared_error", cv=10)
forest_rmse_scores = np.sqrt(-forest_scores)
display_scores(forest_rmse_scores)

 

에러들의 평균값이 5만 불 정도로 위의 두 모델보다 더 좋은 예측을 수행하고 있다는 사실을 예상할 수 있다.


모델 세부 튜닝(Fine-Tune Your Model)

 

우리는 이제 RandomForest 모델을 사용할 것으로 선택하였다.

그러나 선택 후에 모델을 세부적으로 튜닝하는 과정이 필요하다.

바로, 모델 학습을 위한 최적의 하이퍼파라미터를 찾는 과정이라고 할 수 있다.

 

그리드 탐색(Grid Search)

 

수동으로 하이퍼파라미터 조합을 시도하는 대신 GridSearchCV를 사용해보도록 하자.

 

from sklearn.model_selection import GridSearchCV

param_grid = [
    # try 12 (3×4) combinations of hyperparameters
    {'n_estimators': [3, 10, 30], 'max_features': [2, 4, 6, 8]},
    # then try 6 (2×3) combinations with bootstrap set as False
    #bootstrap은 하나하나 일일이 해보는 것을 말한다.
    {'bootstrap': [False], 'n_estimators': [3, 10], 'max_features': [2, 3, 4]},
  ]

forest_reg = RandomForestRegressor(random_state=42)
# train across 5 folds, that's a total of (12+6)*5=90 rounds of training 
grid_search = GridSearchCV(forest_reg, param_grid, cv=5,
                           scoring='neg_mean_squared_error',
                           return_train_score=True)
grid_search.fit(housing_prepared, housing_labels)

grid_search.best_params_

 

GridSearchCV의 결과로 8개의 특성을 사용하고, 30개의 트리의 개수가 가장 최적의 하이퍼파라미터라는 결과가 나왔다.

 

위에서 다양한 조합을 사용했을 때 학습한 결과도 확인할 수 있다.

 

cvres = grid_search.cv_results_
for mean_score, params in zip(cvres["mean_test_score"], cvres["params"]):
    print(np.sqrt(-mean_score), params)

 

 

정말 좋은 방법이지만 우리가 일일이 하이퍼 파라미터의 조합을 정해주는 게 여간 귀찮은 일이 아니다. 이것을 또 개선하기 위하여 있는 좋은 방법이 바로 랜덤 탐색이다.


랜덤 탐색(Randomized Search)

 

하이퍼파라미터들이 가져야 하는 분포를 설정하고, 몇 가지 조합을 시도할 것인지 지정해주면 알아서 하이퍼 파라미터 조합을 만들어 내준다.

 

from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint

param_distribs = {
        'n_estimators': randint(low=1, high=200), #1에서 200 사이의 값을 uniform하게 샘플링
        'max_features': randint(low=1, high=8),
    }

forest_reg = RandomForestRegressor(random_state=42)
rnd_search = RandomizedSearchCV(forest_reg, param_distributions=param_distribs,
                                n_iter=10, cv=5, scoring='neg_mean_squared_error', random_state=42)
rnd_search.fit(housing_prepared, housing_labels)

cvres = rnd_search.cv_results_
for mean_score, params in zip(cvres["mean_test_score"], cvres["params"]):
    print(np.sqrt(-mean_score), params)

10번을 지정해 10가지 조합이 시도된 것을 확인할 수 있다.

위와 같은 경우에는 max_features가 7이고 n_estimators가 180일 때 가장 좋은 결과가 나타난다는 것을 확인할 수 있다.


특성 중요도, 에러 분석

 

extra_attribs = ["rooms_per_hhold", "pop_per_hhold", "bedrooms_per_room"]
#cat_encoder = cat_pipeline.named_steps["cat_encoder"] # old solution
cat_encoder = full_pipeline.named_transformers_["cat"]
cat_one_hot_attribs = list(cat_encoder.categories_[0])
attributes = num_attribs + extra_attribs + cat_one_hot_attribs
sorted(zip(feature_importances, attributes), reverse=True)

 

RandomForest 모델에 따면 가장 중요한 특성은 median income이고, 다음은 범주형 특성을 one-hot encoding으로 변환해준 데이터가 중요하다는 것을 알 수 있다.


테스트 데이터셋으로 최종 평가하기

 

final_model = grid_search.best_estimator_

X_test = strat_test_set.drop("median_house_value", axis=1)
y_test = strat_test_set["median_house_value"].copy()

X_test_prepared = full_pipeline.transform(X_test)
final_predictions = final_model.predict(X_test_prepared)

final_mse = mean_squared_error(y_test, final_predictions)
final_rmse = np.sqrt(final_mse)

final_rmse

마지막으로 얻어진 에러값은 47730이 나오는 것을 확인할 수 있다. 결과가 마음에 들면 론칭할 수 있고, 마음에 들지 않으면 개선(데이터를 더 구한다 / 데이터를 정제한다 / 다른 모델을 사용한다 / etc)을 해야할 것이다...


론칭, 모니터링, 시스템 유지 보수

 

상용환경에 배포하기 위해서는 데이터 전처리와 모델의 예측이 포함된 파이프라인을 만들어 저장하는 것이 좋다.

 

full_pipeline_with_predictor = Pipeline([
        ("preparation", full_pipeline),
        ("linear", LinearRegression())
    ])

my_model = full_pipeline_with_predictor

import joblib
joblib.dump(my_model, "my_model.pkl")
#...
my_model_loaded = joblib.load("my_model.pkl")

my_model_loaded.predict(some_data)

 

.pkl 파일로 모델을 저장해 배포할 수 있다. 저 모델 파일만 있으면 다른 환경에서 모델을 읽어들일 때, load함수만 사용하여 읽어들일 수 있다. 그 후 변환되지 않은 데이터가 주어졌을 때 한 번의 실행으로 데이터 변환부터 예측까지 모두 수행해 줄 수 있다.


론칭후 시스템 모니터링

  • 시간이 지나면 모델이 낙후되면서 성능이 저하될 수 있기 때문에 모니터링 프로그램을 미리 구축해 놓는 것이 좋다.
  • 자동모니터링
    • ex) 추천시스템의 경우, 추천된 상품의 판매량이 줄어드는지?
  • 수동모니터링
    • ex) 이미지 분류의 경우, 분류된 이미지들 중 일부를 전문가에게 검토시킴
  • 결과가 나빠진 경우
    • 데이터 입력의 품질이 나빠졌는지, 센서가 고장났는지, 트렌드가 변화했는지, 계절적 요인이 있는지 등 문제점에 대해 파악해야 한다.

유지보수

  • 정기적으로 새로운 데이터(레이블)를 수집해야 한다.
  • 새로운 데이터를 테스트 데이터로, 현재의 테스트 데이터는 학습데이터로 편입한다.
  • 다시 학습후, 새로운 테스트 데이터에 기반해 현재 모델과 새 모델을 평가하고 비교한다.

 

모든 프로세스에 골고루 시간을 배분해야 좋은 모델이 만들어 질 수 있다.

'AI > KDT 인공지능' 카테고리의 다른 글

[06/15] 분류문제 실습  (0) 2021.06.15
오토인코더, t-SNE  (0) 2021.06.14
[05/18] Django로 동적 웹페이지 만들기  (0) 2021.05.18
[05/17] Web Application with Django  (0) 2021.05.17
[05/12] EDA mini project  (0) 2021.05.12

+ Recent posts