- 릿지 회귀(Ridge Regression)

  • 릿지 회귀는 선형 회귀를 개선
  • 릿지 회귀는 선형 회귀와 비슷하지만, 가중치의 절대값을 최대한 작게 만든다는 것이 다름
  • 각 특성이 출력값에 주는 영향을 최소한으로 하도록 규제
  • 규제를 사용하면 다중공선성(multicollinearity) 문제를 방지하여 모델의 과대적합 방지
  • 다중공선성 문제는 두 특성이 일치에 가까울 정도로 관련성(상관관계)가 높을 때 발생
  • 릿지 회귀는 다음과 같은 함수를 최소화하는 파라미터 \(w\)를 찾음

$$ RidgeMSE=\frac{1}{N}\sum_{i=1}^{N}(y_{i}-\hat{y_{i}})^{2}+\alpha\sum_{i=1}^{p}w_{i}^2 $$

  • \(\alpha\): 사용자가 지정하는 하이퍼 파라미터
  • \(\alpha\)가 크면 규제 효과가 커지고, \(\alpha\)가 작으면 규제 효과가 작아짐(\(\alpha\)가 작을수록 일반 선형 회귀와 동일한 형태)
# 당뇨병 환자 데이터에 릿지 회귀 적용
from sklearn.linear_model import Ridge
from sklearn.datasets import load_diabetes
from sklearn.model_selection import train_test_split

# return_X_y는 특성 변수와 타겟 변수를 미리 X와 y로 구분하여 불러옴
X, y = load_diabetes(return_X_y = True)
X_train, X_test, y_train, y_test = train_test_split(X, y)

// alpha값을 하이퍼 파라미터로 지정하여 Ridge모델에 데이터 피팅
model = Ridge(alpha = 0.1)
model.fit(X_train, y_train)

print("학습 데이터 점수: {}".format(model.score(X_train, y_train)))
print("평가 데이터 점수: {}".format(model.score(X_test, y_test)))

# 출력 결과(alpah = 0.1)
학습 데이터 점수: 0.5036670060593883
평가 데이터 점수: 0.5151286628940492

# 출력 결과(alpah = 0.2)
학습 데이터 점수: 0.49043581005514436
평가 데이터 점수: 0.5303117309693495

# 모델을 LinearRegression으로 사용하였을 때 점수
학습 데이터 점수: 0.5136038374290528
평가 데이터 점수: 0.5063114437093292
# 시각화
predicted = model.predict(X_test)
expected = y_test

plot_diabetes(expected, predicted)

  • 릿지 회귀는 가중치에 제약을 두어 선형 회귀 모델보다 훈련 데이터 점수가 낮을 수 있음
  • 일반화 성능은 릿지 회귀가 더 높아 평가 데이터 점수는 릿지 회귀가 더 좋음
  • 일반화 성능에 영향을 주는 하이퍼 파라미터인 \(\alpha\)값을 조정해 보면서 릿지 회귀의 성능이 어떻게 변하는지 확인 필요
# 캘리포니아 주택 가격 데이터에 릿지 회귀 적용
from sklearn.datasets import fetch_california_housing

california = fetch_california_housing()

X_train, X_test, y_train, y_test = train_test_split(california.data, california.target, test_size = 0.2)
model = Ridge(alpha = 0.1)
model.fit(X_train, y_train)

print("학습 데이터 점수: {}".format(model.score(X_train, y_train)))
print("평가 데이터 점수: {}".format(model.score(X_test, y_test)))

# 출력 결과(alpha = 0.1)
학습 데이터 점수: 0.604254843729662
평가 데이터 점수: 0.613608636245419

# 출력 결과(alpha = 0.2)
학습 데이터 점수: 0.6019439765084587
평가 데이터 점수: 0.6225652128169401

# 모델을 LinearRegression으로 사용하였을 때 점수
학습 데이터 점수: 0.6067992442029464
평가 데이터 점수: 0.6022552622453919
# 시각화
predicted = model.predict(X_test)
expected = y_test

plot_california(expected, predicted)

 

 

  - 라쏘 회귀(Lasso Regression)

  • 선형 회귀에 규제를 적용한 또 다른 모델
  • 라쏘 회귀는 릿지 회귀와 비슷하게 가중치를 0에 가깝게 만들지만, 조금 다른 방식 사용
  • 라쏘 회귀에서는 다음과 같은 함수를 최소화하는 파라미터 \(w\)를 찾음

$$ LassoMSE=\frac{1}{N}\sum_{i=1}^{N}(y_{i}-\hat{y_{i}})^{2}+\alpha\sum_{i=1}^{p}\left|w_{i}\right| $$

  • 라쏘 회귀도 하이퍼 파라미터 \(\alpha\)값을 통해 규제의 강도 조절
# 당뇨병 환자 데이터에 라쏘 회귀 적용
from sklearn.linear_model import Lasso
from sklearn.datasets import load_diabetes
from sklearn.model_selection import train_test_split

# return_X_y는 특성 변수와 타겟 변수를 미리 X와 y로 구분하여 불러옴
X, y = load_diabetes(return_X_y = True)
X_train, X_test, y_train, y_test = train_test_split(X, y)
model = Lasso(alpha = 0.1)
model.fit(X_train, y_train)

print("학습 데이터 점수: {}".format(model.score(X_train, y_train)))
print("평가 데이터 점수: {}".format(model.score(X_test, y_test)))

# 출력 결과(alpha = 0.1)
학습 데이터 점수: 0.46683101421965556
평가 데이터 점수: 0.5875532568592793

# 출력 결과(alpha = 0.2)
학습 데이터 점수: 0.4923536651989784
평가 데이터 점수: 0.46314670980636674

# 모델을 LinearRegression으로 사용하였을 때 점수
학습 데이터 점수: 0.5136038374290528
평가 데이터 점수: 0.5063114437093292

# 캘리포니아 주택 가격 데이터에 라쏘 회귀 적용
from sklearn.datasets import fetch_california_housing

california = fetch_california_housing()

X_train, X_test, y_train, y_test = train_test_split(california.data, california.target, test_size = 0.2)
model = Lasso(alpha = 0.2)
model.fit(X_train, y_train)

print("학습 데이터 점수: {}".format(model.score(X_train, y_train)))
print("평가 데이터 점수: {}".format(model.score(X_test, y_test)))

# 출력 결과(alpha = 0.1)
학습 데이터 점수: 0.5433203729425291
평가 데이터 점수: 0.5490112288455995

# 출력 결과(alpha = 0.2)
학습 데이터 점수: 0.5059361673257636
평가 데이터 점수: 0.5011330883335348

# 모델을 LinearRegression으로 사용하였을 때 점수
학습 데이터 점수: 0.6067992442029464
평가 데이터 점수: 0.6022552622453919

 

 

  - 신축망(Elastic Net)

  • 신축망은 릿지 회귀와 라쏘 회귀, 두 모델의 규제 모두 사용하는 선형 모델
  • 두 모델의 장점을 모두 가져 좋은 성능
  • 데이터 특성이 많거나 서로 상관관계가 높은 특성 존재 시, 위의 두 모델보다 좋은 성능
  • 신축망은 다음 함수를 최소화하는 파라미터 \(w\)를 찾음

$$ ElasticMSE=\frac{1}{N}\sum_{i=1}^{N}(y_{i}-\hat{y_{i}})+\alpha\rho\sum_{i=1}^{p}\left|w_{i}\right|+\alpha(1-\rho)\sum_{i=1}^{p}w_{i}^{2} $$

  • \(\alpha\): 규제의 강도를 조절하는 하이퍼 파라미터
  • \(\rho\): 라쏘 규제와 릿지 규제 사이의 가중치를 조절하는 하이퍼 파라미터
# 당뇨병 환자 데이터에 신축망 적용
from sklearn.linear_model import ElasticNet
from sklearn.datasets import load_diabetes
from sklearn.model_selection import train_test_split

# return_X_y는 특성 변수와 타겟 변수를 미리 X와 y로 구분하여 불러옴
X, y = load_diabetes(return_X_y = True)
X_train, X_test, y_train, y_test = train_test_split(X, y)
model = ElasticNet(alpha = 0.01, l1_ratio = 0.5)
model.fit(X_train, y_train)

print("학습 데이터 점수: {}".format(model.score(X_train, y_train)))
print("평가 데이터 점수: {}".format(model.score(X_test, y_test)))

# 출력 결과(alpha = 0.01, l1_ratio = 0.5)
학습 데이터 점수: 0.39452567238560965
평가 데이터 점수: 0.34426906645229316

# 모델을 Ridge로 사용하였을 때 점수 
학습 데이터 점수: 0.5036670060593883
평가 데이터 점수: 0.5151286628940492

# 모델을 Lasso로 사용하였을 때 점수 
학습 데이터 점수: 0.46683101421965556
평가 데이터 점수: 0.5875532568592793

# 모델을 LinearRegression으로 사용하였을 때 점수
학습 데이터 점수: 0.5136038374290528
평가 데이터 점수: 0.5063114437093292

# 캘리포니아 주택 가격 데이터에 신축망 적용
from sklearn.datasets import fetch_california_housing

california = fetch_california_housing()

X_train, X_test, y_train, y_test = train_test_split(california.data, california.target, test_size = 0.2)
model = ElasticNet(alpha = 0.2, rho = 0.5)
model.fit(X_train, y_train)

print("학습 데이터 점수: {}".format(model.score(X_train, y_train)))
print("평가 데이터 점수: {}".format(model.score(X_test, y_test)))

# 출력 결과(alpha = 0.01, l1_ratio = 0.5)
학습 데이터 점수: 0.6066539688645749
평가 데이터 점수: 0.5937533298305722

# 모델을 Ridge로 사용하였을 때 점수 
학습 데이터 점수: 0.604254843729662
평가 데이터 점수: 0.613608636245419

# 모델을 Lasso로 사용하였을 때 점수 
학습 데이터 점수: 0.5433203729425291
평가 데이터 점수: 0.5490112288455995

# 모델을 LinearRegression으로 사용하였을 때 점수
학습 데이터 점수: 0.6067992442029464
평가 데이터 점수: 0.6022552622453919

 

 

  - 직교 정합 추구(Orthogonal Matchin Pursuit)

  • 직교 정합 추구 방법은 모델에 존재하는 가중치 벡터에 특별한 제약을 거는 방법
  • 직교 정합 추구 방법은 다음을 만족하는 파라미터 \(w\)를 찾는 것이 목표

$$ \underset{w}{arg\ min}\ \left|\left|y-\hat{y}\right|\right|_{2}^2\ subject\ to\ \left|\left|w\right|\right|_{0}\leq k $$

  • \(\left|\left|w\right|\right|\): 가중치 벡터 \(w\)에서 0이 아닌 값의 개수
  • 직교 정합 추구 방법은 가중치 벡터 \(w\)에서 0이 아닌 값이 \(k\)개 이하가 되도록 훈련됨
  • 이러한 방법은 모델이 필요 없는 데이터 특성을 훈련 과정에서 자동으로 제거하도록 만들 수 있음
# 당뇨병 환자 데이터에 직교 정합 추구 적용
from sklearn.linear_model import OrthogonalMatchingPursuit
from sklearn.datasets import load_diabetes
from sklearn.model_selection import train_test_split

# return_X_y는 특성 변수와 타겟 변수를 미리 X와 y로 구분하여 불러옴
X, y = load_diabetes(return_X_y = True)
X_train, X_test, y_train, y_test = train_test_split(X, y)
model = OrthogonalMatchingPursuit(n_nonzero_coefs = 7)  # 0이 아닌 특성을 7개로 지정
model.fit(X_train, y_train)

print("학습 데이터 점수: {}".format(model.score(X_train, y_train)))
print("평가 데이터 점수: {}".format(model.score(X_test, y_test)))

# 출력 결과(n_nonzero_coefs = 7)
학습 데이터 점수: 0.49747193558480873
평가 데이터 점수: 0.5368821270302075

# 모델을 신축망으로 사용하였을 때 점수 
학습 데이터 점수: 0.39452567238560965
평가 데이터 점수: 0.34426906645229316

# 모델을 Ridge로 사용하였을 때 점수 
학습 데이터 점수: 0.5036670060593883
평가 데이터 점수: 0.5151286628940492

# 모델을 Lasso로 사용하였을 때 점수 
학습 데이터 점수: 0.46683101421965556
평가 데이터 점수: 0.5875532568592793

# 모델을 LinearRegression으로 사용하였을 때 점수
학습 데이터 점수: 0.5136038374290528
평가 데이터 점수: 0.5063114437093292

  • 직교 정합 추구 방법은 위에서 설명한 제약 조건 대신 다음 조건을 만족하도록 변경 가능

$$ \underset{w}{arg\ min}\ \left|\left|w\right|\right|_{0}\ subject\ to\ \left|\left|y-\hat{y}\right|\right|_{2}^2\leq tol $$

  • \(\left|\left|y-\hat{y}\right|\right|_{2}^2\)는 \(\sum_{i=1}^{N}(y-\hat{y})^2\)와 같은 의미
  • 위의 식을 통해 직교 정합 추구 방법을 \(y\)와 \(\hat{y}\) 사이의 "오차제곱합"을 \(tol\) 이하로 하면서 \(\left|\left|w\right|\right|_{0}\)를 최소로 하는 모델로 대체 가능
# 당뇨병 환자 데이터에 직교 정합 추구 방법 중 tol을 제약하는 방법 적용

...

model = OrthogonalMatchingPursuit(tol = 1.)
model.fit(X_train, y_train)

...

print("학습 데이터 점수: {}".format(model.score(X_train, y_train)))
print("평가 데이터 점수: {}".format(model.score(X_test, y_test)))

# 출력 결과(tol = 1)
학습 데이터 점수: 0.6032737807873105
평가 데이터 점수: 0.6175096016107812

# 출력 결과(n_nonzero_coefs = 7)
학습 데이터 점수: 0.49747193558480873
평가 데이터 점수: 0.5368821270302075

# 캘리포니아 주택 가격 데이터에 직교 정합 추구 적용
from sklearn.datasets import fetch_california_housing

california = fetch_california_housing()

X_train, X_test, y_train, y_test = train_test_split(california.data, california.target, test_size = 0.2)
model = OrthogonalMatchingPursuit(n_nonzero_coefs = 5)  # 0이 아닌 특성을 5개로 지정
model.fit(X_train, y_train)

print("학습 데이터 점수: {}".format(model.score(X_train, y_train)))
print("평가 데이터 점수: {}".format(model.score(X_test, y_test)))

# 출력 결과(n_nonzero_coefs = 5)
학습 데이터 점수: 0.5922021375817
평가 데이터 점수: 0.6014385211125419

# 모델을 신축망으로 사용하였을 때 점수 
학습 데이터 점수: 0.6066539688645749
평가 데이터 점수: 0.5937533298305722

# 모델을 Ridge로 사용하였을 때 점수 
학습 데이터 점수: 0.604254843729662
평가 데이터 점수: 0.613608636245419

# 모델을 Lasso로 사용하였을 때 점수 
학습 데이터 점수: 0.5433203729425291
평가 데이터 점수: 0.5490112288455995

# 모델을 LinearRegression으로 사용하였을 때 점수
학습 데이터 점수: 0.6067992442029464
평가 데이터 점수: 0.6022552622453919

# 캘리포니아 주택 가격 데이터에 직교 정합 추구 방법 중 tol을 제약하는 방법 적용
model = OrthogonalMatchingPursuit(tol = 1.)
model.fit(X_train, y_train)

print("학습 데이터 점수: {}".format(model.score(X_train, y_train)))
print("평가 데이터 점수: {}".format(model.score(X_test, y_test)))

# 출력 결과(tol = 1.)
학습 데이터 점수: 0.6046375004558915
평가 데이터 점수: 0.6026936956740174

# 출력 결과(n_nonzero_coefs = 5)
학습 데이터 점수: 0.5922021375817
평가 데이터 점수: 0.6014385211125419

 

 

  - 다항 회귀(Polynomail Regression)

  • 입력 데이터를 비선형 변환 후 사용하는 방법
  • 모델 자체는 선형 모델

$$ \hat{y}=w_{1}x_{1}+w_{2}x_{2}+w_{3}x_{3}+w_{4}x_{1}^2+w_{5}x_{2}^2$$

  • 차수가 높아질수록 더 복잡한 데이터 학습 가능(더 구불구불한 그래프 생성 가능)

# 당뇨병 환자 데이터에 다항 회귀 적용
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_diabetes

X, y = load_diabetes(return_X_y = True)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state = 42)

# make_pipeline으로 ML 과정을 순서대로 배치하여 하나의 파이프라인으로 묶기
model = make_pipeline(
    PolynomialFeatures(degree = 2),
    StandardScaler(),
    LinearRegression()
)
model.fit(X_train, y_train)
print("학습 데이터 점수: {}".format(model.score(X_train, y_train)))
print("평가 데이터 점수: {}".format(model.score(X_test, y_test)))

# 출력 결과
학습 데이터 점수: 0.6048153298370548
평가 데이터 점수: 0.4242419459459561

# 모델을 직교 정합 추구로 사용하였을 때 점수
학습 데이터 점수: 0.49747193558480873
평가 데이터 점수: 0.5368821270302075

# 모델을 신축망으로 사용하였을 때 점수 
학습 데이터 점수: 0.39452567238560965
평가 데이터 점수: 0.34426906645229316

# 모델을 Ridge로 사용하였을 때 점수 
학습 데이터 점수: 0.5036670060593883
평가 데이터 점수: 0.5151286628940492

# 모델을 Lasso로 사용하였을 때 점수 
학습 데이터 점수: 0.46683101421965556
평가 데이터 점수: 0.5875532568592793

# 모델을 LinearRegression으로 사용하였을 때 점수
학습 데이터 점수: 0.5136038374290528
평가 데이터 점수: 0.5063114437093292

# 캘리포니아 주택 가격 데이터에 다항 회귀 적용
from sklearn.datasets import fetch_california_housing

california = fetch_california_housing()
X_train, X_test, y_train, y_test = train_test_split(california.data, california.target, test_size = 0.2)

model = make_pipeline(
    PolynomialFeatures(degree = 2),
    StandardScaler(),
    LinearRegression()
)
model.fit(X_train, y_train)
print("학습 데이터 점수: {}".format(model.score(X_train, y_train)))
print("평가 데이터 점수: {}".format(model.score(X_test, y_test)))

# 출력 결과
학습 데이터 점수: 0.6824013150066678
평가 데이터 점수: 0.6809334820406576

# 모델을 직교 정합 추구로 사용하였을 때 점수
학습 데이터 점수: 0.5922021375817
평가 데이터 점수: 0.6014385211125419

# 모델을 신축망으로 사용하였을 때 점수 
학습 데이터 점수: 0.6066539688645749
평가 데이터 점수: 0.5937533298305722

# 모델을 Ridge로 사용하였을 때 점수 
학습 데이터 점수: 0.604254843729662
평가 데이터 점수: 0.613608636245419

# 모델을 Lasso로 사용하였을 때 점수 
학습 데이터 점수: 0.5433203729425291
평가 데이터 점수: 0.5490112288455995

# 모델을 LinearRegression으로 사용하였을 때 점수
학습 데이터 점수: 0.6067992442029464
평가 데이터 점수: 0.6022552622453919

● 선형 모델(Linear Models)

  • 과거부터 지금까지 널리 사용되고 연구되고 있는 머신러닝 기법
  • 선형 모델은 입력 데이터에 대한 선형 함수를 만들어 예측 수행
  • 회귀 분석을 위한 선형 모델: $$ \hat{y}(w, x)=w_0 + w_{1}x_{1}+\ldots+w_{p}x_{p} $$
  • \(x\): 입력 데이터
  • \(w\): 모델이 학습할 파라미터
  • \(w_{0}\): 편향
  • \(w_{1}~w_{p}\): 가중치

 

  - 선형 회귀(Linear Regression)

  • 선형 회귀 또는 최소제곱법(Ordinary Least Squares)은 가장 간단한 회귀 분석 선형 모델
  • 선형 회귀는 모델의 예측값과 실제값 사이의 평균제곱오차(Mean Squared Error)를 최소화 하는 학습 파라미터 \(w\)를 탐색
  • 평균 제곱 오차: $$ MSE=\frac{1}{N}\sum_{i=1}^{N}(y_{i}-\hat{y_{i}})^2 $$
  • \(y\): 실제값
  • \(\hat{y}\): 예측값
  • 선형 회귀 모델에서 MSE 외에 사용하는 다양한 오류 측정 방법
    • MAE(Mean Absolute Error): 오차의 절댓값의 평균
    • MAPE(Mean Absolute Percentage Error): 오차의 절댓값의 평균을 퍼센트로 나타냄
    • MSE(Mean Squared Error): 오차의 제곱의 평균
    • RMSE(Root Mean Squared Error): 오차의 제곱의 평균에 루트를 씌워, 제곱에 의한 값의 왜곡을 줄임
    • MPE(Mean Percentage Error): 오차의 절댓값이 아닌 원래 값의 평균을 퍼센트로 나타냄
    • \(R^2\)
import numpy as np
import matplotlib.pyplot as plt
plt.style.use(['seaborn-whitegrid'])
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split

# 데이터 생성
# X값(feature 데이터)에 약간의 노이즈 부여
noise = np.random.rand(100, 1)
X = sorted(10 * np.random.rand(100, 1)) + noise
y = sorted(10 * np.random.rand(100))
plt.scatter(X, y)

노이즈가 부여되어 일직선이 아닌 약간 곡선의 형태

# 위에서 생성한 데이터를 통해 X와 y 사이의 최적 회귀식을 계산
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2)
model = LinearRegression()
model.fit(X_train, y_train)

# LinearRegression을 통해 계산된 회귀 가중치(계수)와 회귀식의 편향(절편)
print("선형 회귀 가중치: {}".format(model.coef_))
print("선형 회귀 편향: {}".format(model.intercept_))

# 출력 결과
선형 회귀 가중치: [1.17341194]
선형 회귀 편향: -2.0407947206139223

print("학습 데이터 점수: {}".format(model.score(X_train, y_train)))
print("학습 데이터 점수: {}".format(model.score(X_test, y_test)))

# 출력 결과
학습 데이터 점수: 0.9526520477496929
학습 데이터 점수: 0.9507468780019412

# model에 의해 X_test의 값으로 예측한 값(predict)와 실제 값(y_test)의 비교
predict = model.predict(X_test)
plt.scatter(X_test, y_test)
plt.plot(X_test, predict, '--r')

 

  - 당뇨 가격 데이터

  • 442명의 당뇨병 환자 진행 상황 데이터
from sklearn.datasets import load_diabetes
diabetes = load_diabetes()
print(diabetes.keys())
print(diabetes.DESCR)

# 출력 결과
dict_keys(['data', 'target', 'frame', 'DESCR', 'feature_names', 'data_filename', 'target_filename', 'data_module'])

...

  :Attribute Information:
      - age     age in years
      - sex
      - bmi     body mass index
      - bp      average blood pressure
      - s1      tc, total serum cholesterol
      - s2      ldl, low-density lipoproteins
      - s3      hdl, high-density lipoproteins
      - s4      tch, total cholesterol / HDL
      - s5      ltg, possibly log of serum triglycerides level
      - s6      glu, blood sugar level

...
속성 설명
agr 나이
sex 성별
bmi 체질량 지수
bp 평균 혈압
s1 혈중 총 콜레스테롤(total serum cholesterol, tc)
s2 저밀도 콜레스테롤(low-density lipoproteins, ldl)(나쁜 콜레스테롤)
s3 고밀도 콜레스테롤(high-density lipoproteins, hdl)(좋은 콜레스테롤)
s4 총 콜레스테롤(total cholesterol, tch) / 고밀도 콜레스테롤(hdl)
s5 possibly log of serum triglycerides level, ltg
s6 혈당치(blood sugar level, glu)
target 1년 뒤 병의 진행 상황 측정치
# 더 편리하게 데이터를 조작하며 EDA를 진행하기 위해 pandas의 DataFrame 형태로 변경
import pandas as pd

diabetes_df = pd.DataFrame(diabetes.data, columns = diabetes.feature_names)
diabetes_df['target'] = diabetes.target
diabetes_df.head()

diabetes_df.describe()

# 시각화 1: 당뇨병의 환자 개인별 각 데이터의 분포

for i, col in enumerate(diabetes_df.columns):
    plt.figure(figsize = (8, 4))
    plt.plot(diabetes_df[col])
    plt.title(col)
    plt.xlabel('patient')
    plt.tight_layout()

# 시각화 2: 당뇨병 환자에 대한 각 변수별 target 변수와의 관계

for i, col in enumerate(diabetes_df.columns):
    plt.figure(figsize = (8, 4))
    plt.scatter(diabetes_df[col], diabetes_df['target'])
    plt.title(col)
    plt.xlabel(col, size = 12)
    plt.ylabel('target', size = 12)
    plt.tight_layout()

# 시각화 3: 시각화 2의 변수별 target변수와의 관계를 한 눈에 출력한 seaborn 패키지의 pairplot

import seaborn as sns
sns.pairplot(diabetes_df)

# 선형 회귀 모델을 이용하여 당뇨병 환자의 각 특성 변수로부터 target변수(병의 진행 상황) 예측
model = LinearRegression()

X_train, X_test, y_train, y_test = train_test_split(diabetes.data, diabetes.target, test_size = 0.2)
model.fit(X_train, y_train)
model.score(X_train, y_train)

print("학습 데이터 점수: {}".format(model.score(X_train, y_train)))
print("평가 데이터 점수: {}".format(model.score(X_test, y_test)))

# 출력 결과
학습 데이터 점수: 0.5136038374290528
평가 데이터 점수: 0.5063114437093292

 

  • 데이터를 분리하였기 때문에 훈련에 사용된 양이 작고, 분리가 잘 안된 경우 잘못된 검증이 될 수 있음
  • 이런 경우, 교차 검증 진행
# 교차 검증을 통해 나온 점수
from sklearn.model_selection import cross_val_score

# neg_mean_squared_error은 기존 MSE(Mean Squared Error)에서 부호의 방향이 다른 것
# MSE가 작을수록 좋은 모델인데 부호의 방향을 반대로 하여 점수가 높을수록 좋은 것이라고 더 직관적이므로 표현
scores = cross_val_score(model, diabetes.data, diabetes.target, cv = 10, scoring = 'neg_mean_squared_error')
print("NMSE scores: {}".format(scores))
print("NMSE scores mean: {}".format(scores.mean()))
print("NMSE score std: {}".format(scores.std()))

# 출력 결과
NMSE scores: [-2533.84017856 -2870.77758341 -3512.72914835 -2759.20855951
 -3555.69402408 -2900.34540046 -3696.33102548 -2282.33961544
 -4122.99489276 -1769.64247356]
NMSE scores mean: -3000.390290160842
NMSE score std: 681.7925615943559

 

  • 회귀 모델의 검증을 위한 또 다른 측정 지표인 \(R^2\) 사용
r2_scores = cross_val_score(model, diabetes.data, diabetes.target, cv = 10, scoring = 'r2')

print("R2 scores: {}".format(r2_scores))
print("R2 scores mean: {}".format(r2_scores.mean()))
print("R2 score std: {}".format(r2_scores.std()))

# 출력 결과
R2 scores: [0.5561455  0.23055827 0.35357673 0.62190752 0.2658727  0.61819798
 0.41815142 0.43513747 0.43436229 0.68569253]
R2 scores mean: 0.4619602420450602
R2 score std: 0.1469914507315373

 

  • 생성된 회귀 모델에 대한 평가를 위해 LinearRegression 객체에 포함된 두 개의 속성 값으로 수식 표현
    • intercept_: 추정된 회귀식의 상수항
    • coef_: 추정된 회귀식의 변수별 가중치 벡터
# model에서 자동으로 계산해주는 회귀식의 절편과 각 변수의 회귀계수를 for문으로 출력하여 하나의 회귀식으로 출력
print('y= '+str(model.intercept_) + ' ')
for i, c in enumerate(model.coef_):
    print(str(c) + ' * X' + str(i))

# 출력 결과
y= 149.5089161367518 
33.61273499649665 * X0
-247.676515473911 * X1
563.0736968765942 * X2
301.4945423675652 * X3
-666.4117487275887 * X4
387.9055057888012 * X5
53.535258191636544 * X6
118.17345140681505 * X7
714.1812563836739 * X8
33.07049840631021 * X9

 

  • RMSE score와 \(R^2\) score 비교(train 데이터에 대해)
from sklearn.metrics import mean_squared_error, r2_score

predict = model.predict(X_train)
rmse = (np.sqrt(mean_squared_error(y_train, predict)))
r2 = r2_score(y_train, predict)

print("RMSE score: {}".format(rmse))
print("R2 score: {}".format(r2))

# 출력 결과
RMSE score: 52.94257758575594
R2 score: 0.5136038374290528

 

  • RMSE score와 \(R^2\) score 비교(test 데이터에 대해)
from sklearn.metrics import mean_squared_error, r2_score

y_test_predict = model.predict(X_test)
rmse = (np.sqrt(mean_squared_error(y_test, y_test_predict)))
r2 = r2_score(y_test, y_test_predict)

print("RMSE score: {}".format(rmse))
print("R2 score: {}".format(r2))

# 출력 결과
RMSE score: 56.34236344085632
R2 score: 0.5063114437093292

 

  • 모델이 얼마나 잘 예측하는지 시각화
# 시각화 과정의 함수화
def plot_diabetes(expected, predicted):
    plt.figure(figsize = (8, 4))
    plt.scatter(expected, predicted)
    plt.plot([25, 350], [25, 350], '--r')
    plt.xlabel('True value')
    plt.ylabel('Predicted value')
    plt.tight_layout()

# X_test를 model에 넣어 예측한 값인 predict와 실제 값인 y_test를 비교
# 빨간 선은 가장 잘 예측했을 때 나오는 선으로, 점들이 선에 가까울수록 좋은 모델
predicted = model.predict(X_test)
expected = y_test

plot_diabetes(expected, predicted)

 

  - 캘리포니아 주택 가격 데이터(복습용, 위의 당뇨병 데이터 예측 모델과 같은 과정 반복)

  • 1990년 미국 census 조사의 데이터로, California의 census 블록 그룹당의 데이터
from sklearn.datasets import fetch_california_housing

california = fetch_california_housing()
print(california.keys())
print(california.DESCR)

# 출력 결과
dict_keys(['data', 'target', 'frame', 'target_names', 'feature_names', 'DESCR'])

...

    :Attribute Information:
        - MedInc        median income in block group
        - HouseAge      median house age in block group
        - AveRooms      average number of rooms per household
        - AveBedrms     average number of bedrooms per household
        - Population    block group population
        - AveOccup      average number of household members
        - Latitude      block group latitude
        - Longitude     block group longitude

...
속성 설명
MedInc 블록의 중간 소득
HouseAge 블록의 중간 주택 연도
AveRooms 평균 방 수
AveBedrms 평균 침실 수
Population 블록 내 거주중인 인구 수
AveOccup 평균 주택점유율
Latitude 주택 블록 위도
Longitude 주택 블록 경도
import pandas as pd

california_df = pd.DataFrame(california.data, columns = california.feature_names)
california_df['target'] = california.target
california_df.head()

california_df.describe()

# 시각화 1
import matplotlib.pyplot as plt

for i, col in enumerate(california_df.columns):
    plt.figure(figsize = (8, 4))
    plt.plot(california_df[col])
    plt.title(col)
    plt.tight_layout()

# 시각화 2
import matplotlib.pyplot as plt

for i, col in enumerate(california_df.columns):
    plt.figure(figsize = (8, 4))
    plt.scatter(california_df[col], california_df['target'])
    plt.ylabel('target', size = 12)
    plt.xlabel(col, size = 12)
    plt.title(col)
    plt.tight_layout()

# 시각화 3
import seaborn as sns

sns.pairplot(california_df.sample(1000))

 

  • 위도, 경도 데이터가 있으므로 지도로 시각화 가능(캘리포니아의 모양이 나옴)
california_df.plot(kind = 'scatter', x = 'Longitude', y = 'Latitude', alpha = 0.2, figsize = (12, 10))

 

  • 데이터를 색깔로 추가한 버전
california_df.plot(kind = 'scatter', x = 'Longitude', y = 'Latitude', alpha = 0.2,
                    s = california_df['Population']/100, label = 'Population', figsize = (12, 10),
                    c = 'target', cmap = plt.get_cmap('viridis'), colorbar = True)

    • 원의 크기가 클수록 인구가 많은 곳
    • 색깔이 보라색에 가까울수록 가격이 싼 곳
    • 색깔이 노란색에 가까울수록 가격이 비싼 곳
    • 색깔이 노란색에 가까운 곳은 바닷가로 추정
    • 시각화를 통해 바닷가 주변이 주택 가격이 높다는 인사이트를 얻을 수 있음

 

  • 캘리포니아 주택 가격 선형 회귀
model = LinearRegression()

X_train, X_test, y_train, y_test = train_test_split(california.data, california.target, test_size = 0.2)

model.fit(X_train, y_train)
print('학습 데이터 점수: {}'.format(model.score(X_train, y_train)))
print('평가 데이터 점수: {}'.format(model.score(X_test, y_test)))

# 출력 결과
학습 데이터 점수: 0.6067992442029464
평가 데이터 점수: 0.6022552622453919

scores = cross_val_score(model, california.data, california.target, cv = 10, scoring = 'neg_mean_squared_error')
print('NMSE mean: {}'.format(scores.mean()))
print('NMSE std: {}'.format(scores.std()))

# 출력 결과
NMSE mean: -0.5509524296956628
NMSE std: 0.1928858295386518

r2_scores = cross_val_score(model, california.data, california.target, cv = 10, scoring = 'r2')
print('R2 Score mean: {}'.format(r2_scores.mean()))

# 출력 결과
R2 Score mean: 0.5110068610523781
print('y = ' + str(model.intercept_))
for i, c in enumerate(model.coef_):
    print(str(c) + ' * X' + str(i))

# 출력 결과
y = -36.4797991177529
0.4433911368525524 * X0
0.00936296088142479 * X1
-0.12111648584893303 * X2
0.6725589967168644 * X3
-3.8254599120021365e-06 * X4
-0.0034486963486539766 * X5
-0.416116329408061 * X6
-0.4292610187302255 * X7

 

  • RMSE와 \(R^2\) 평가 점수(train 데이터에 대해)
y_train_predict = model.predict(X_train)
rmse = (np.sqrt(mean_squared_error(y_train, y_train_predict)))
r2 = r2_score(y_train, y_train_predict)

print("RMSE Score: {}".format(rmse))
print("R2 Score: {}".format(r2))

# 출력 결과
RMSE Score: 0.7258609395455958
R2 Score: 0.6067992442029464
  • RMSE와 \(R^2\) 평가 점수(test 데이터에 대해)
y_test_predict = model.predict(X_test)
rmse = (np.sqrt(mean_squared_error(y_test, y_test_predict)))
r2 = r2_score(y_test, y_test_predict)

print("RMSE Score: {}".format(rmse))
print("R2 Score: {}".format(r2))

# 출력 결과
RMSE Score: 0.7184275601386346
R2 Score: 0.6022552622453919

 

  • 얼마나 잘 예측하는지 시각화
def plot_california(expected, predicted):
    plt.figure(figsize = (8, 4))
    plt.scatter(expected, predicted)
    plt.plot([0, 5], [0, 5], '--r')
    plt.xlabel('True Price ($100,000s)')
    plt.ylabel('Predicted Price ($100,000s)')

predicted = model.predict(X_test)
expected = y_test
plot_california(expected, predicted)

    • 빨간색 선이 실제 값을 잘 예측한 것이고, 빨간 선을 중심으로 넓게 퍼져, 분산이 큰 것이 보임
    • 따라서, 예측 점수가 크지 않음

6. model_selection 모듈

  -학습용 데이터와 테스트 데이터로 분리

  -교차 검증 분할 및 평가

  -Estimator의 하이퍼 파라미터 튜닝을 위한 다양한 함수와 클래스 제공

  -train_test_split(): 학습/테스트 세트 분리

from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_diabetes

# diabetes 데이터 세트 불러오기
diabetes = load_diabetes()

# diabetes 데이터 세트를 train데이터와 test 데이터로 쪼개기
# train_test_split의 인자는 X(feature 데이터), y(target 데이터), test_size(테스트 데이터 비율)
# 4개의 값을 반환(feature 데이터 훈련용 / 테스트용, target 데이터 훈련용 / 테스트용)
X_train, x_test, y_train, y_test = train_test_split(diabetes.data, diabetes.target, test_size = 0.3)

# 선형회귀모델 적용해보고 점수 확인하기
model = LinearRegression()
model.fit(X_train, y_train)

print("학습 데이터 점수: {}".format(model.score(X_train, y_train)))
print("평가 데이터 점수: {}".format(model.score(X_test, y_test)))

# 출력결과
학습 데이터 점수: 0.5097761792435763
평가 데이터 점수: 0.5120316690456863
# 모델에 의해 예측된 X_test에 대한 y_test값(y_pred)과 원래의 y_test값 비교 시각화
import matplotlib.pyplot as plt

y_pred = model.predict(X_test)
plt.figure(figsize = (8,4))
plt.scatter(y_test, y_pred)
plt.plot([30, 350], [30, 350], '--r')
plt.tight_layout()

 

    → x축이 실제 정답, y축이 예측된 값

    → 빨간선에 가까울수록 잘 맞춘 것이고, 빨간선에 가까운 점이 적으므로 점수도 0.5정도밖에 나오지 않음

     다른 모델을 사용하던지, 전처리 과정의 조정으로 점수를 점점 개선시켜야 함

 

  -cross_val_score(): 교차 검증

    → 교차검증은 위 그림과 같이 데이터를 여러 개로 쪼개어 한 부분을 검증용으로, 나머지 부분을 테스트용으로 사용하여 각각의 점수를 계산

from sklearn.model_selection import cross_val_score, cross_validate
import numpy as np

# 인자는 LinearRegression를 담은 model, feature 데이터 세트, target 데이터 세트
# 마지막 인자는 교차 검증을 위해 데이터를 몇 개의 세트로 분리할 것인지 지정
# 교차 검증은 분리한 데이터 세트 개수만큼 진행
scores = cross_val_score(model, diabetes.data, diabetes.target, cv = 5)

# 데이터를 5개로 나누어 각 세트에 대해 한번씩 교차검증을 진행하여 총 5번 진행하고 각각의 점수 출력
print("교차 검증 정확도: {}".format(scores))

# 모든 점수 리스트의 평균과 표준편차 출력
print("교차 검증 정확도의 평균: {} +/- {}".format(np.mean(scores), np.std(scores)))

# 출력 결과
교차 검증 정확도: [0.42955615 0.52259939 0.48268054 0.42649776 0.55024834]
교차 검증 정확도의 평균: 0.48231643590864215 +/- 0.04926857751190378

 

  -GridSearchCV: 교차 검증을 반복하며 최적의 하이퍼 파라미터 찾기

from sklearn.model_selection import GridSearchCV
from sklearn.linear_model import Ridge
import pandas as pd

# Ridge 모델에서 최적의 점수를 도출하는 alpha값의 후보
alpha = [0.001, 0.01, 0.1, 1, 10, 100, 1000]

# gridsearch를 통해 찾을 하이퍼파라미터 값과 후보 하이퍼파라미터 값을 딕셔너리 형태로 지정
param_grid = dict(alpha = alpha)

# GridSearch를 통해 최적 하이퍼 파라미터를 모델(Ridge), 하이퍼파라미터 후보, 교차검증 횟수
gs = GridSearchCV(estimator = Ridge(), param_grid = param_grid, cv = 10)

# fit하는 과정은 일반 모델과 동일
result = gs.fit(diabetes.data, diabetes.target)

print("최적 점수: {}".format(result.best_score_))
print("최적 파라미터: {}".format(result.best_params_))
print(gs.best_estimator_)
pd.DataFrame(result.cv_results_)

# 출력 결과
최적 점수: 0.46332219117960366
최적 파라미터: {'alpha': 0.1}
Ridge(alpha=0.1)

     result.cv_results_에 대한 출력결과로, 각 하이퍼파라미터에 대한 10번의 교차 검증의 결과가 전부 표로 정리되어 출력

 

  -multiprocessing을 이용한 GridSearchCV

import multiprocessing
from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression

# 새롭게 diabetes가 아닌 iris 데이터로 연습
iris = load_iris()

# 모델도 새롭게 LinearRegression 모델을 사용하고 그에 맞는 하이퍼파라미터 후보 지정
param_grid = [{'penalty': ['l1', 'l2'],
               'C': [1.5, 2.0, 2.5, 3.0, 3.5]}]

# multiprocessing을 통해 cpu 개수만큼 job을 생성해서 모델을 실행
gs = GridSearchCV(estimator = LogisticRegression(), param_grid = param_grid,
                             scoring = 'accuracy', cv = 10, n_jobs = multiprocessing.cpu_count())

# 이후 동일
result = gs.fit(iris.data, iris.target)

print("최적 점수: {}".format(result.best_score_))
print("최적 파라미터: {}".format(result.best_params_))
print(gs.best_estimator_)
pd.DataFrame(result.cv_results_)

# 출력 결과
최적 점수: 0.9800000000000001
최적 파라미터: {'C': 2.5, 'penalty': 'l2'}
LogisticRegression(C=2.5)

    → 모든 penalty값과 C값의 조합에서 교차 검증이 진행되므로 (C값 후보 5개 × penalty값 후보 2개 = 총 10개)의 경우에 10번씩의 교차검증 과정이 진행

 

 

7. preprocessing 모듈

  -데이터 전처리 모듈

  -데이터의 특징 스케일링(feature scaling): 데이터 값의 범위를 조정, 표준화와 정규화 방법이 있음

  • 표준화 방법(Standaradization): \( x_{i}^{'}=\frac{x_i - mean(x)}{stdev(x)} \)
  • 정규화 방법(Normalization): \( x_{i}^{'}=\frac{x_i - min(x)}{max(x) - min(x)} \)
  • scikit-learn에서는 개별 벡터 크기를 맞추는 형태로 정규화하므로 minmax를 사용한 방법과 차이는 있지만 결국 같은 방법이긴함

 

  -StandardScaler: 표준화 클래스

iris = load_iris()
iris_df = pd.DataFrame(data = iris.data, columns = iris.feature_names)
iris_df.describe()

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()

# fit을 통해 표준화에 필요한 정보를 가져오고, transform을 통해 표준화 동작 실행
# fit_transform의 return값은 넘파이 배열
iris_scaled = scaler.fit_transform(iris_df)
iris_df_scaled = pd.DataFrame(data = iris_scaled, columns = iris.feature_names)
iris_df_scaled.describe()

    → StandardScaler를 실행하기 전과 비교하면 평균은 0에 가까운 값으로, 표준편차는 1에 가까운 값으로 표준화됨

import multiprocessing
from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression

X_train, X_test, y_train, y_test = train_test_split(iris_df_scaled, iris.target, test_size = 0.3)

model = LogisticRegression()
model.fit(X_train, y_train)

print("훈련 데이터 점수: {}".format(model.score(X_train, y_train)))
print("평가 데이터 점수: {}".format(model.score(X_test, y_test)))

# 출력 결과
훈련 데이터 점수: 0.9904761904761905
평가 데이터 점수: 0.9111111111111111

    → 표준화된 데이터에 대해 모델에 적용시키면 평가 데이터 점수에서도 높은 점수를 받을 수 있음

 

  -MinMaxScaler: 정규화 클래스

from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler()
iris_scaled = scaler.fit_transform(iris_df)
iris_df_scaled = pd.DataFrame(data = iris_scaled, columns = iris.feature_names)
iris_df_scaled.describe()

    → MinMaxScaler를 실행하기 전과 비교해서 최소값은 0, 최대값은 1에 맞춰 정규화됨

X_train, X_test, y_train, y_test = train_test_split(iris_df_scaled, iris.target, test_size = 0.3)

model = LogisticRegression()
model.fit(X_train, y_train)

print("훈련 데이터 점수: {}".format(model.score(X_train, y_train)))
print("평가 데이터 점수: {}".format(model.score(X_test, y_test)))

# 출력 결과
훈련 데이터 점수: 0.9333333333333333
평가 데이터 점수: 0.9111111111111111

    → 정규화된 데이터에 대해서도 모델에 적용시키면 높은 점수를 받을 수 있음

 

 

8. 성능 평가 지표

 -정확도(Accuracy)

  • 정확도는 전체 예측 데이터 건수 중(실제 데이터와 비교하여) 예측 결과가 동일한 데이터 건수로 계산
  • scikit-learn에서는 accuracy_score 함수 제공
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

# n_samples: 샘플 개수
# n_features: feature 데이터 개수
# n_informative: 의미있는 변수 개수
# n_redundant: 노이즈
# n_cluster_per_class: 클래스 당 군집 개수
X, y = make_classification(n_samples = 1000, n_features = 2, n_informative = 2, n_redundant = 0, n_clusters_per_class = 1)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3)

model = LogisticRegression()
model.fit(X_train, y_train)

print("훈련 데이터 점수: {}".format(model.score(X_train, y_train)))
print("평가 데이터 점수: {}".format(model.score(X_test, y_test)))

predict = model.predict(X_test)
print('정확도: {}'.format(accuracy_score(y_test, predict)))

# 출력 결과
훈련 데이터 점수: 0.9114285714285715
평가 데이터 점수: 0.9266666666666666
정확도: 0.9266666666666666

    → 정확도는 항상 평가 데이터 점수와 같게 나옴

 

 - 오차 행렬(Confusion Matrix)

  • True Negative(TN): 예측값을 Negative로 예측, 실제 값도 Negative
  • False Positive(FP): 예측값을 Positive로 예측, 실제 값은 Negative
  • False Negative(FN): 예측값을 Negative로 예측, 실제 값은 Positive
  • True Positive(TP): 예측값을 Positive로 예측, 실제 값도 Positive
from sklearn.metrics import confusion_matrix

# 실제값에 y_test를, 예측값에 X_test를 model에 넣어 예측한 predict를 넣고 오차 행렬 출력
confmat = confusion_matrix(y_true = y_test, y_pred = predict)
print(confmat)

# 출력 결과
[[137  11]
 [ 11 141]]
# 오차 행렬 시각화
fig, ax = plt.subplots(figsize = (2.5, 2.5))

# matshow()로 히트맵 그리기
# cmap(color map)은 Blue로 설정, alpha(투명도)는 0.3
ax.matshow(confmat, cmap = plt.cm.Blues, alpha = 0.3)

# 오차 행렬(confmap)의 행(shape[0]), 열(shape[1]) 개수만큼씩 반복하여
# 행렬의 각 칸마다 text 삽입(1행 1열에 오차 행렬의 1행 1열의 값을 문자로 삽입...)
# 수직(vertical alignment), 수평(horizontal alignment) 정렬은 모두 center로 설정
for i in range(confmat.shape[0]):
    for j in range(confmat.shape[1]):
        ax.text(x = j , y = i, s = confmat[i, j], va = 'center', ha = 'center')

# X축 label은 Predicted label(예측한 값이 무엇인지 나타냄)
# Y축 label은 True label(실제 값이 무엇인지 나타냄)
# tight_layout()을 사용하여 figure와 subplot간 여백을 기본값으로 설정
plt.xlabel('Predicted label')
plt.ylabel('True label')
plt.tight_layout()
plt.show()

    → 1사분면: 1로 예측, 실제 0 (FP)

    → 2사분면: 0로 예측, 실제 0 (TN)

    → 3사분면: 0로 예측, 실제 1 (FN)

    → 4사분면: 1로 예측, 실제 1 (TP)

 

 -정밀도와 재현율

  • 정밀도 = TP / (FP + TP), Negative인 것을 Positive로 잘못 판단할 때 큰 문제가 있는 경우 중요
  • 재현율 = TP / (FN + TP), Positive인 것을 Negative로 잘못 판단할 때 큰 문제가 있는 경우 중요
  • 정확도 = (TN + TP) / (TN + FP + FN + TP)
  • 오류율 = (FN + FP) / (TN + FP + FN + TP)
from sklearn.metrics import precision_score, recall_score

# 정밀도
precision = precision_score(y_test, predict)
# 재현율
recall = recall_score(y_test, predict)

print("정밀도: {}".format(precision))
print("재현율: {}".format(recall))

# 출력 결과
정밀도: 0.9276315789473685
재현율: 0.9276315789473685

 

 - F1 Score

  • 정밀도와 재현율을 결합한 지표
  • 정밀도와 재현율이 어느 한쪽으로 치우치지 않을 때 높은 값(재현율과 정밀도는 trade off 관계이며, 둘 다 어느정도 높은 값을 가지도록 하고 싶을 때, F1 Score 사용)

$$ F1=2 \times \frac { precision \times recall} { precision + recall } $$

from sklearn.metrics import f1_score

# f1 score
f1 = f1_score(y_test, predict)
print("F1 score: {}".format(f1))

# 출력 결과
F1 score: 0.9276315789473685

 

 - ROC 곡선과 AUC

  • ROC 곡선은 FPR(False Positive Rate)이 변할 때, TPR(True Positive Rate)이 어떻게 변하는지 나타내는 곡선
    • TPR: TP / (FN + TP), 재현율, 민감도(Sensitivity), 실제값이 양성인 것이 정확히 예측되어야 하는 수준
    • TNR: TN / (FP + TN), 특이도, 실제값이 음성인 것이 정확히 예측되어야 하는 수준
    • FPR: FP / (FP + TN), 1 - TNR

      → FPR을 0부터 1까지 변화시키며 TPR의 변화를 그린 곡선

      → 임계값(threshold)의 활용: 분류 결정 임계값은 양성 예측값을 결정하는 확률의 기준

      → 임계값을 1로 지정하면 양성 예측 기준이 1이 되어, 양성일 확률이 1보다 커야 양성으로 예측하는데, 1보다 클 수 없으므로 무조건 음성으로 예측함, 이때, FPR 공식에서 분자 FP는 0이 되므로 FPR은 0이 됨

      → 반대로, 임계값을 0으로 지정하면, 무조건 양성으로 예측하여 TN이 0이 되고 FPR = FP / FP = 1이 됨

      → 위의 방법으로 FPR을 0부터 1까지 변화시키며 TPR 값의 변화를 관찰

from sklearn.metrics import roc_curve

# model에 X_test값을 넣었을 때, 각 값에 대해 양성으로 예측할 확률
pred_proba_class1 = model.predict_proba(X_test)[:, 1]
# roc_curve의 인자는 실제값(y_test), 양성 예측확률(predict_proba의 두번째 열)
fprs, tprs, thresholds = roc_curve(y_test, pred_proba_class1)

# FPR과 TPR 간의 그래프(ROC 곡선)
plt.plot(fprs, tprs, label = 'ROC')
# (0, 0)에서 (1, 1)을 잇는 직선으로, 완전 랜덤한 추측이 이루어졌을 때(가장 안좋은 상황일 때)를 나타냄
plt.plot([0, 1], [0, 1], '--k', label = 'Random')
# xlim()이 0부터 1이므로, start와 end는 각각 0, 1
start, end = plt.xlim()
# x축값은 0, 1에서부터 0.1씩 커지며 소수점 두번째까지 반올림
plt.xticks(np.round(np.arange(start, end, 0.1), 2))
plt.xlim(0, 1)
plt.ylim(0, 1)
plt.xlabel('FPR(1-TNR)')
plt.ylabel('TPR(Recall)')
plt.legend()

     → 각 임계값에 대해 그 임계값 이상의 양성예측 확률을 가진 것만 양성으로 예측했을 때의 오차행렬을 계산

     → 그 오차행렬로부터 FPR(FP / (FP + TN)), TPR(TP / (FN + TP))을 계산

# model.predict_proba(X_test)의 출력값
# 0열은 음성일 확률, 1열은 양성일 확률
# 1열의 양성일 확률과 임계값을 비교하여 임계값 이상의 것만 양성으로 판단
# 이렇게 판단한 값과 실제값(y_test)를 비교하여 오차행렬을 만들면 그에 따른 FPR, TPR 계산 가능
array([[1.01487800e-02, 9.89851220e-01],
       [4.37551716e-02, 9.56244828e-01],
       [9.97233002e-01, 2.76699767e-03],
       [5.02728650e-03, 9.94972714e-01],
       [1.39106181e-02, 9.86089382e-01],
       [9.85028531e-01, 1.49714687e-02],
       [2.63580374e-02, 9.73641963e-01],
       [9.94944361e-01, 5.05563867e-03],
       [9.72061703e-03, 9.90279383e-01],
       [2.28855348e-03, 9.97711447e-01],
       [9.99983728e-01, 1.62719028e-05],
       [9.92155567e-01, 7.84443295e-03],
       [2.27501886e-02, 9.77249811e-01],
       [9.98637794e-01, 1.36220602e-03],
       [2.54748755e-02, 9.74525124e-01],
       [9.93339766e-01, 6.66023394e-03],
       [3.90060776e-02, 9.60993922e-01],
       [7.02834321e-01, 2.97165679e-01],
       [9.99899997e-01, 1.00002794e-04],
       [3.66162097e-03, 9.96338379e-01],
       [9.94623675e-01, 5.37632451e-03],
       [2.93344676e-03, 9.97066553e-01],
       [2.68465302e-02, 9.73153470e-01],
       [1.49182896e-05, 9.99985082e-01],
       [3.45351748e-02, 9.65464825e-01],
...
       [3.20673218e-02, 9.67932678e-01],
       [9.94737158e-01, 5.26284187e-03],
       [7.22599615e-01, 2.77400385e-01],
       [3.29609357e-03, 9.96703906e-01],
       [1.18750772e-01, 8.81249228e-01]])

     → 완전 랜덤이라면: 임계값이 몇이든 반은 맞고 반은 틀림

     → 잘 예측했다면: 양성으로 예측 확률이 0.5이상인 것에 대해 전부 양성이고 실제도 양성이라면, 임계값이 0.5가 될 때까지 양성을 잘 맞춘 비율인 TPR이 급격히 증가할 것이고, 그 이후에는 0.5미만인 것도 양성으로 예측하여 실제 음성인 것을 양성으로 예측한 비율인 FPR이 증가하기만 하여 아래와 같은 그래프가 나옴

    → 점선에 가까울수록 성능이 안좋음

  • AUC(Area Under Curve): ROC 곡선 밑의 면적(1에 가까울수록 좋은 값이며, 최소 0.5는 랜덤한 데이터라고 보면 됨)
from sklearn.metrics import roc_auc_score

# AUC 점수 간단하게 계산하기
roc_auc = roc_auc_score(y_test, predict)
print("ROC_AUC_Score: {}".format(roc_auc))

# 출력 결과
ROC_AUC_Score: 0.926653627311522

1. scikit-learn 특징

  • 다양한 머신러닝 알고리즘을 구현한 파이썬 라이브러리
  • 심플하고 일관성 있는 API, 유용한 온라인 문서, 풍부한 예제
  • 머신러닝을 위한 쉽고 효율적인 개발 라이브러리 제공
  • 다양한 머신러닝 관련 알고리즘, 개발을 위한 프레임워크와 API 제공
  • 많은 사람이 사용하며 다양한 환경에서 검증된 라이브러리

 

 

2. scikit-learn 주요 모듈

모듈 설명
sklearn.datasets 내장된 예제 데이터 세트
sklearn.preprocessing 다양한 데이터 전처리 기능 제공(변환, 정규화, 스케일링 등)
sklearn.feature_selection feature 선택 기능 제공
sklearn.feature_extraction feature 추출에 사용
sklearn.model_selection 교차 검증을 위해 데이터를 학습용/테스트용으로 분리,
최적 파라미터를 추출하는 API 제공(GridSearch 등)
sklearn.metrics 분류, 회귀, 클러스터링, Pairwise에 대한 다양한 성능 측정 방법 제공(Accuracy, Precision, Recall, ROC-AUC, RMSE 등)
sklearn.pipeline 특징 처리 등의 변환과 ML 알고리즘 학습, 예측 등을 묶어서 실행할 수 있는 유틸리티 제공
sklearn.linear_model 선형 회귀, 릿지, 라쏘, 로지스틱 회귀 등 회귀 관련 알고리즘과 SGD(Stochastic Gradient Descent) 알고리즘 제공
sklearn.svm 서포트 벡터 머신(SVM) 알고리즘 제공
sklearn.neighbors 최근접 이웃(KNN 등) 알고리즘 제공
sklearn.naive_bayes 나이브 베이즈 알고리즘 제공(가우시안 NB, 다항분포 NB 등)
sklearn.tree 의사 결정 나무 알고리즘 제공
sklearn.ensemble 앙상블 알고리즘 제공(RandomForest, AdaBoost, GradientBoost 등)
sklearn.cluster 비지도 클러스터링 알고리즘 제공(K-means, 계층형 클러스터링, DBSCAN 등)

 

 

3. estimator API

  -scikit-learn이 어떤 식으로 이 API를 사용하는지 철학 또는 규칙

  • 일관성을 중요하게 생각: 모든 객체는 일관된 문서를 갖춘 제한된 메서드 집합에서 비롯된 공통 인터페이스를 공유함
  • 검사: 모든 지정된 파라미터 값은 공개 속성으로 노출되게 되어 있음
  • 제한된 객체 계층 구조
    • 알고리즘만 파이썬 클래스에 의해 표현되어 있음
    • 데이터 세트는 표준 형식(Numpy 배열, Pandas DataFrame, Scipy 희소 행렬)으로 표현
    • 매개변수 명은 표준 파이썬 문자열 사용
  • 구성: 많은 머신러닝 작업은 기본 알고리즘의 시퀀스(순서가 있는 나열)로 나타낼 수 있으며, scikit-learn은 가능한 곳이라면 어디서든 이 방식 사용
  • 합리적인 기본값: 모델이 사용자 파라미터를 필요로 할 때 라이브러리가 적절한 기본값을 정의(아무것도 입력하지 않으면 입력하도록 하거나 default 값으로 채워짐)

 

 

4. API 사용 방법

 1) scikit-learn으로부터 적절한 estimator 클래스를 임포트해서 모델의 클래스 선택

# 선형회귀 모델 임포트
from sklearn.linear_model import LinearRegression

 2) 클래스를 원하는 값으로 인스턴스화해서 모델의 하이퍼파라미터 선택

# 선형회귀 모델을 model이라는 변수로 인스턴스화
# 괄호안에 하이퍼파라미터 값 지정
model = LinearRegression()

 3) 데이터를 특징 배열과 대상 벡터로 배치(학습에 사용할 수 있는 feature들로 되어있는 배열(X), 예측 대상이 되는 배열(y))

 4) 모델 인스턴스의 fit() 메서드를 호출해 모델을 데이터에 적합

 5) 모델을 새 데이터에 대해서 적용

   -지도 학습: 대체로 predict() 메서드를 사용해 알려지지 않은 데이터에 대한 레이블 예측

   -비지도 학습: 대체로 transform()이나 predict() 메서드를 사용해 데이터의 속성을 변환하거나 추론

 

 -scikit-learn 사용 예시

import numpy as np
import matplotlib.pyplot as plt

# 데이터셋 생성
x = 10 * np.random.rand(50)
y = 2 * x + np.random.rand(50)
plt.scatter(x, y)

# 적절한 estimator 클래스를 임포트해서 모델의 클래스 선택
from sklearn.linear_model import LinearRegression

# 클래스를 원하는 값으로 인스턴스화해서 모델의 하이퍼파라미터 선택
model = LinearRegression(fit_intercept = True)
model
# 데이터를 특징 배열과 대상 벡터로 배치
# 특징 배열: X, 대상 벡터: y
X = x[:, np.newaxis] # np.newaxis는 배열에 새로운 축을 부여하여 1차원 배열을 2차원 배열로 만들어줌
X

  ※ np.newaxis 적용 후 X의 모양(2차원 배열)

  ※ np.newaxis 적용 전 x의 모양(1차원 배열)

# 모델 인스턴스(model)의 fit() 메서드를 호출해 모델을 데이터에 적합
model.fit(X, y)

# 선형회귀 모델의 회귀 계수
model.coef_  # 결과: array([2.0285485])

# 선형회귀 모델의 절편
model.intercept_  # 결과: 0.36150523836797355
# 모델을 새 데이터에 대해서 적용
# 처음에 생성해서 모델을 학습시킨 데이터를 테스트 해볼 새로운 데이터 생성
xfit = np.linspace(-1, 11)
Xfit = xfit[:, np.newaxis]
yfit = model.predict(Xfit) # 앞서 학습시킨 모델(model)에 새로 생성한 데이터 Xfit을 적용시켜 그에 대한 예측값 yfit 생성

 

  -모델이 잘 예측했는지 원래의 데이터 산점도와 새로 생성하여 예측한 데이터의 선그래프를 비교

plt.scatter(x, y)
plt.plot(xfit, yfit, '--r')

  -비교해본 결과 원래 데이터의 산점도의 경향성을 선그래프가 잘 나타내고 있는 것으로 보임

 

 

5. scikit-learn에서 기본으로 제공해주는 예제 데이터 세트

  -분류/회귀용 데이터 세트

API 설명
datasets.load_linnerud() 체력검사 데이터(다중 출력 회귀용)
datasets.load_breast_cancer() 위스콘신 유방암 특징과 양성/음성 레이블 데이터(분류용)
datasets.diabetes() 당뇨 데이터(회귀용)
datasets.load_digits() 0에서 9까지 숫자 이미지 픽셀 데이터(분류용)
datasets.load_iris() 붓꽃 특징 데이터(분류용)
datasets.load_wine() 와인 데이터(분류용)

 

  -온라인 데이터 세트(내장 데이터 세트와 다르게 크기가 커서 API로 제공)

API 설명
fetch_california_housing 캘리포니아 주택 가격 데이터
fetch_covtype() 회귀 분석용 토지 조사 데이터
fetch_20newsgroups() 뉴스 그룹 텍스트 데이터
fetch_olivetti_faces() 얼굴 이미지 데이터
fetch_lfw_people() 얼굴 이미지 데이터
fetch_lfw_paris() 얼굴 이미지 데이터
fetch_rcv1() 로이터 뉴스 말뭉치 데이터
fetch_lfw_mldata() ML 웹사이트에서 다운로드

 

  -분류와 클러스터링을 위한 표본 데이터 생성

API 설명
datasets.make_classifications() 분류를 위한 데이터 세트 생성, 높은 상관도·불필요한 속성 등 노이즈를 고려한 데이터를 무작위로 생성
datasets.make_blobs() 클러스터링을 위한 데이터 세트 생성, 군집 지정 개수에 따라 여러 클러스터링을 위한 데이터 세트를 무작위로 생성

 

  -예제 데이터 세트 구조

  • 일반적으로 딕셔너리 형태
  • data: 특징 데이터 세트(feature 데이터, X값)
  • target: 분류용은 레이블 값, 회귀용은 숫자 결과값 데이터(y값)
  • target_names: 개별 레이블의 이름(분류용에서만 포함되어 있음)
  • feature_names: 특징의 이름
  • DESCR: 데이터 세트에 대한 설명과 각 특징 설명

 

  -데이터 세트 불러오기 예시

표에 정리한 데이터 외에 다른 토이 데이터도 있음

from sklearn.datasets import load_diabetes
diabetes = load_diabetes()

# 예제 데이터 세트는 딕셔너리 형태로 구성되어 있으므로, key값을 출력하면 데이터 세트에 저장된 정보의 이름이 출력됨
print(diabetes.keys())

# 출력 결과
dict_keys(['data', 'target', 'frame', 'DESCR', 'feature_names', 'data_filename', 'target_filename', 'data_module'])
# 특징 데이터 세트 출력
print(diabetes.data)

# 출력 결과
[[ 0.03807591  0.05068012  0.06169621 ... -0.00259226  0.01990749
  -0.01764613]
 [-0.00188202 -0.04464164 -0.05147406 ... -0.03949338 -0.06833155
  -0.09220405]
 [ 0.08529891  0.05068012  0.04445121 ... -0.00259226  0.00286131
  -0.02593034]
 ...
 [ 0.04170844  0.05068012 -0.01590626 ... -0.01107952 -0.04688253
   0.01549073]
 [-0.04547248 -0.04464164  0.03906215 ...  0.02655962  0.04452873
  -0.02593034]
 [-0.04547248 -0.04464164 -0.0730303  ... -0.03949338 -0.00422151
   0.00306441]]
# 타겟 데이터 세트 출력
diabetes.target

# 출력 결과
[151.  75. 141. 206. 135.  97. 138.  63. 110. 310. 101.  69. 179. 185.
 118. 171. 166. 144.  97. 168.  68.  49.  68. 245. 184. 202. 137.  85.
 131. 283. 129.  59. 341.  87.  65. 102. 265. 276. 252.  90. 100.  55.
  61.  92. 259.  53. 190. 142.  75. 142. 155. 225.  59. 104. 182. 128.
  52.  37. 170. 170.  61. 144.  52. 128.  71. 163. 150.  97. 160. 178.
  48. 270. 202. 111.  85.  42. 170. 200. 252. 113. 143.  51.  52. 210.
  65. 141.  55. 134.  42. 111.  98. 164.  48.  96.  90. 162. 150. 279.
  92.  83. 128. 102. 302. 198.  95.  53. 134. 144. 232.  81. 104.  59.
 246. 297. 258. 229. 275. 281. 179. 200. 200. 173. 180.  84. 121. 161.
  99. 109. 115. 268. 274. 158. 107.  83. 103. 272.  85. 280. 336. 281.
 118. 317. 235.  60. 174. 259. 178. 128.  96. 126. 288.  88. 292.  71.
 197. 186.  25.  84.  96. 195.  53. 217. 172. 131. 214.  59.  70. 220.
 268. 152.  47.  74. 295. 101. 151. 127. 237. 225.  81. 151. 107.  64.
 138. 185. 265. 101. 137. 143. 141.  79. 292. 178.  91. 116.  86. 122.
  72. 129. 142.  90. 158.  39. 196. 222. 277.  99. 196. 202. 155.  77.
 191.  70.  73.  49.  65. 263. 248. 296. 214. 185.  78.  93. 252. 150.
  77. 208.  77. 108. 160.  53. 220. 154. 259.  90. 246. 124.  67.  72.
 257. 262. 275. 177.  71.  47. 187. 125.  78.  51. 258. 215. 303. 243.
  91. 150. 310. 153. 346.  63.  89.  50.  39. 103. 308. 116. 145.  74.
  45. 115. 264.  87. 202. 127. 182. 241.  66.  94. 283.  64. 102. 200.
 265.  94. 230. 181. 156. 233.  60. 219.  80.  68. 332. 248.  84. 200.
  55.  85.  89.  31. 129.  83. 275.  65. 198. 236. 253. 124.  44. 172.
 114. 142. 109. 180. 144. 163. 147.  97. 220. 190. 109. 191. 122. 230.
 242. 248. 249. 192. 131. 237.  78. 135. 244. 199. 270. 164.  72.  96.
 306.  91. 214.  95. 216. 263. 178. 113. 200. 139. 139.  88. 148.  88.
...
 118.  69. 273. 258.  43. 198. 242. 232. 175.  93. 168. 275. 293. 281.
  72. 140. 189. 181. 209. 136. 261. 113. 131. 174. 257.  55.  84.  42.
 146. 212. 233.  91. 111. 152. 120.  67. 310.  94. 183.  66. 173.  72.
  49.  64.  48. 178. 104. 132. 220.  57.]
# 데이터 세트에 대한 정보 출력
diabetes.DESCR

# 출력 결과
.. _diabetes_dataset:

Diabetes dataset
----------------

Ten baseline variables, age, sex, body mass index, average blood
pressure, and six blood serum measurements were obtained for each of n =
442 diabetes patients, as well as the response of interest, a
quantitative measure of disease progression one year after baseline.

**Data Set Characteristics:**

  :Number of Instances: 442 # 인스턴스 수 442개

  :Number of Attributes: First 10 columns are numeric predictive values

  :Target: Column 11 is a quantitative measure of disease progression one year after baseline

  :Attribute Information: # 속성 정보
      - age     age in years
      - sex
      - bmi     body mass index
      - bp      average blood pressure
      - s1      tc, total serum cholesterol
      - s2      ldl, low-density lipoproteins
...
For more information see:
Bradley Efron, Trevor Hastie, Iain Johnstone and Robert Tibshirani (2004) "Least Angle Regression," Annals of Statistics (with discussion), 407-499.
(https://web.stanford.edu/~hastie/Papers/LARS/LeastAngle_2002.pdf)
# feature 데이터의 이름만 출력
print(diabetes.feature_name)

# 출력 결과(DESCR에서 본 것과 다르게 이름의 약자가 출력)
['age', 'sex', 'bmi', 'bp', 's1', 's2', 's3', 's4', 's5', 's6']
# 데이터 세트와 타겟의 파일이름
print(diabetes.data_filename)
print(diabetes.target_filename)

# 출력 결과(압축된 csv 파일 형태)
diabetes_data_raw.csv.gz
diabetes_target.csv.gz

1. 정규방정식

 -선형 회귀 모형에서 비용함수(RMSE 또는 MSE 등)을 최소화하는 회귀 계수 θ를 찾기 위한 해석적인 방법

 -공식 검증

import numpy as np
import matplotlib.pyplot as plt

# 선형관계에 있는 변수 X, y 100개씩 100행 1열의 배열로 생성
X=2*np.random.rand(100,1)
y=4+3*X+np.random.randn(100,1)
plt.plot(X,y,'b.')
plt.axis([0,2,0,15])
plt.show()

X_b=np.c_[np.ones((100,1)),X]   # 모든 샘플에 X0=1을 추가

# X0에 1을 추가한 독립변수 배열에 배열을 전치시킨 것을 곱하여
# 그 역행렬을 구하고
# 역행렬과 독립변수 배열을 전치시킨 배열을 곱한 뒤
# 종속변수 행렬 곱하기
theta_best=np.linalg.inv(X_b.T.dot(X_b)).dot(X_b.T).dot(y)
theta_best

### 결과 ###
array([[4.1140573 ],
          [2.81512351]])

 -엑셀을 이용해 theta_best=np.linalg.inv(X_b.T.dot(X_b)).dot(X_b.T).dot(y) 계산과정 설명

  1) 이해를 위해 X와 y 변수에서 각각 세개씩만 데이터 추출한 뒤 X_b, y 배열 생성

X_b=np.array([[1.09521816,1],
                     [1.40637785,1],
                     [1.06414927,1]])
y=np.array([[6.00397116],
                   [8.4618674],
                   [9.23386642]])

 

 2) 엑셀에서 배열 생성

 

 3) X_b.T  :  전치

X_b배열을 복사하여 행/열 바꿔서 붙여넣기

 

 4) X_b.T.dot(X_b)  :  전치한 행렬과 원래 행렬 곱하기

     -"=MMULT(전치행렬, 원래행렬)" 식 작성 후 Ctrl+Shift+Enter

 

 5) np.linarg.inv(X_b.T.dot(X_b))  :  역행렬

     -"=MINVERSE(행렬)" 식 작성 후 Ctrl+Shift+Enter

 

6) np.linalg.inv(X_b.T.dot(X_b)).dot(X_b.T)  :  5)에서 구한 역행렬과 3)에서 전치시켰던 행렬 곱하기

     -"=MMULT(행렬, 행렬)" 식 작성 후 Ctrl+Shift+Enter

 

 7) np.linalg.inv(X_b.T.dot(X_b)).dot(X_b.T).dot(y)  :  마지막 y 행렬 곱하기

     -"=MMULT(행렬, 행렬)" 식 작성 후 Ctrl+Shift+Enter

 

 8) 결과

 -파이썬과 비교해보면 똑같은 값이 나옴

 

 

 -위와 같은 과정을 거쳐서 나온 theta_best를 이용하여 새로운 값에 대한 y값 예측하기

# 새로운 값을 예측
# 0과 2를 모델에 넣었을 때 각각의 예측값
X_new=np.array([[0],[2]])
X_new_b=np.c_[np.ones((2,1)),X_new]
y_predict=X_new_b.dot(theta_best)
y_predict

### 결과 ###
array([[4.25816196],
          [9.8437471 ]])

 -예측한 0과 2의 값으로 예측된 회귀직선 그려보기

# 그래프로 표시
plt.plot(X_new,y_predict,'r-')
plt.plot(X,y,'b.')
plt.axis([0,2,0,15])
plt.show()

 

 -사이킷런에서 선형회귀

# 사이킷런에서 선형 회귀 모형
from sklearn.linear_model import LinearRegression

# 선형 회귀 모형 객체 생성
lin_reg=LinearRegression()

# X와 y를 모형에 피팅
lin_reg.fit(X,y)

# 선형회귀 모형의 절편과 회귀계수
lin_reg.intercept_, lin_reg.coef_

### 결과 ###
(array([4.25816196]), array([[2.79279257]]))

# 새로운 값(0,2)에 대한 예측
lin_reg.predict(X_new)

### 결과 ###
array([[4.25816196],
          [9.8437471 ]])

 

 -LinearRegression 클래스는 scipy.linalg,lstsq() 함수를 기반으로 함

 -함수를 직접 호출하면 sklearn의 LinearRegressor()에서 구했던 절편과 회귀계수와 같은 값이 출력

# scipy.linalg.lstsq() 함수
theta_best_svd,residuals,rank,s=np.linalg.lstsq(X_b,y,rcond=1e-6)
theta_best_svd

### 결과 ###
array([[4.25816196],
          [2.79279257]])

 -scipy.linalg.lstsq() 함수는 다음 식을 계산함 $$\hat{\theta }=X^+y$$

 -X+는 X의 유사역행렬

 -유사역행렬은 특잇값 분해(SVD)라 부르는 표준 행렬 분해 기법을 사용해 계산

 

 

2. 계산 복잡도

 -정규 방정식은 (n+1)*(n+1) 크기가 되는 $$X^TX$$의 역행렬 계산

 -역행렬을 계산하는 계산 복잡도는 특성 수가 n배로 늘어나면 계산시간이 n^2.4에서 n^3 사이로 늘어남

 -사이킷런의 LinearRegression 클래스가 사용하는 SVD 방법은 약 n^2로 특성 수가 두 배 늘어나면 계산시간은 대략 4배 늘어남

 -정규방정식이나 다른 알고리즘으로 학습된 선형 회귀모델은 예측이 매우 빠름

  (예측 계산 복잡도는 샘플 수와 특성 수에 선형적으로 샘플(또는 특성)이 두배 증가하면 걸리는 시간도 거의 두배 증가)

 

 

1. 사용 데이터

 -사이킷런에서 제공하는 MNIST 데이터셋(손글씨 데이터셋)

# 사이킷런에서 기본적으로 제공하는 MNIST 데이터셋 불러오기
from sklearn.datasets import fetch_openml
mnist=fetch_openml('mnist_784',version=1,as_frame=False)
mnist.keys()

### 결과 ###
dict_keys(['data', 'target', 'frame', 'categories', 'feature_names', 'target_names', 'DESCR', 'details', 'url'])

 

 -MNIST 데이터셋 배열 확인

X,y=mnist['data'],mnist['target']
X.shape     # (70000, 784)
y.shape     # (70000,)

# 이미지가 70000개이고 각 이미지는 784개의 특성을 가짐(28*28 픽셀의 이미지이므로 28*28=784개의 특성을 가짐)
# 각 특성은 0(흰색)~255(검은색)까지의 픽셀 강도

 

2. 다중 분류기 훈련

 -서포트 벡터 머신(SVC) 분류기 사용

from sklearn.svm import SVC
svm_clf=SVC()
svm_clf.fit(X_train, y_train)
svm_clf.predict([some_digit])

### 결과 ###
array([5], dtype=uint8)
# 이진 분류기에서 5인지 아닌지에 따라 True, False로 결과가 나온 것과 달리 0~9까지 숫자 중 5라고 분류해냄

 

 -점수 확인하기

 -0~9까지 각 레이블에 대한 점수를 계산하여 가장 높은 점수를 가진 레이블로 예측함

 -레이블이 5일때 가장 높은 점수일 것으로 예상

some_digit_scores=svm_clf.decision_function([some_digit])
some_digit_scores

### 결과 ###
array([[ 1.72501977,  2.72809088,  7.2510018 ,  8.3076379 , -0.31087254,
         9.3132482 ,  1.70975103,  2.76765202,  6.23049537,  4.84771048]])

 -5일때 9.31점으로 가장 높은 점수가 나와 분류 결과가 5로 출력됨

 

 -분류한 모든 클래스 출력하기

svm_clf.classes_

### 결과 ###
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=uint8)

 

 

 -OvO(OneVsOneClassifier): 0과1 구별, 1과 2 구별..과 같이각 숫자 조합마다 이진 분류를 통해 분류기를 훈련시킴(N*(N-1)/2 개의 분류기 필요)

 -OvR(OneVsRestClassifier): 모든 숫자를 훈련시킨 후, 가장 점수가 높은 것을 선택

 -서포트 벡터 머신 같은 일부 알고리즘에서는 큰 훈련세트에서 몇 개의 분류기를 훈련시키는 것보다는 작은 훈련세트에서 많은 분류기 훈련시키는 것 선호

 -이진 분류 알고리즘에서는 대부분 OvR 선호

 -OvO나 OvR 강제로 사용하기

# OvO나 OvR 사용을 강제하려면 OneVsOneClassifier나 OneVsRestClassifier 사용
from sklearn.multiclass import OneVsRestClassifier
ovr_clf=OneVsRestClassifier(SVC())
ovr_clf.fit(X_train, y_train)
ovr_clf.predict([some_digit])

### 결과 ###
array([5], dtype=uint8)

# SGD 분류기는 직접 샘플을 다중 클래스로 분류할 수 있으므로 별도로 OvO 또는 OvR 적용할 필요 없음
# SGDClassifier 훈련
from sklearn.linear_model import SGDClassifier
sgd_clf=SGDClassifier(random_state=42)
sgd_clf.fit(X_train, y_train)
sgd_clf.predict([some_digit])

### 결과 ###
array([3], dtype=uint8)

sgd_clf.decision_function([some_digit])

### 결과 ###
array([[-31893.03095419, -34419.69069632,  -9530.63950739,
          1823.73154031, -22320.14822878,  -1385.80478895,
        -26188.91070951, -16147.51323997,  -4604.35491274,
        -12050.767298  ]])

 -SGDClassifier에서 5를 3으로 분류해버림

 -클래스마다 부여한 점수확인 결과 3에 부여한 점수가 1823으로 가장 높고 5에 부여한 점수는 그 다음으로 높은 -1385

 

# SGDClassifier의 성능 평가
from sklearn.model_selection import cross_val_score
cross_val_score(sgd_clf, X_train, y_train, cv=3, scoring='accuracy')    # array([0.87365, 0.85835, 0.8689 ])

# 스케일 조정을 하면 정확도를 높일 수 있음
from sklearn.preprocessing import StandardScaler
scaler=StandardScaler()
X_train_scaled=scaler.fit_transform(X_train.astype(np.float64))
cross_val_score(sgd_clf,X_train_scaled,y_train,cv=3,scoring='accuracy') # array([0.8983, 0.891 , 0.9018])

 

 

 

3. 에러 분석

 -모델의 성능을 향상시키기 위해 에러의 종류를 분석하여 확인하는 것

from sklearn.model_selection import cross_val_predict
from sklearn.metrics import confusion_matrix

y_train_pred=cross_val_predict(sgd_clf, X_train_scaled, y_train, cv=3)
conf_mx=confusion_matrix(y_train, y_train_pred)
conf_mx
# 첫 행부터 실제 0일 때 0으로 예측한 개수, 1로 예측한 개수, 2로 예측한 개수...

 -각 숫자를 정확히 예측한 개수가 가장 많지만 숫자 5에 대해 5로 예측한 횟수는 4444로 다른 숫자에 비해 낮음

 -시각화 해보기

import matplotlib.pyplot as plt
plt.matshow(conf_mx, cmap=plt.cm.gray)
plt.show()

 -숫자 5부분만 조금 진한색으로 표시되어 다른 숫자들보다 정확히 예측해낸 횟수가 적음을 의미

 

 -에러의 개수가 아닌 비율을 시각화해보기

 -단순히 5의 전체 개수가 적어서 생긴 현상일 수 있으므로 전체 개수 대비 정확히 예측한 비율을 시각화

# 각 행의 합계 계산
row_sums=conf_mx.sum(axis=1,keepdims=True)
# 각 값을 행의 전체 합계로 나누어 비율 확인
norm_conf_mx=conf_mx / row_sums

# 대각원소는 0으로 채워 무시하고 나머지 값에서 에러 비율의 크기 확인
np.fill_diagonal(norm_conf_mx,0)
plt.matshow(norm_conf_mx, cmap=plt.cm.gray)
plt.show()

 -8열이 밝은 것으로 보아 많은 이미지가 8로 잘못 분류됨

 -3행 5열과 5행 3열이 밝은 것은 3과 5를 서로 잘못 분류한 비율이 높음을 의미

 -3과 5에 대해 3을 3으로, 3을 5로, 5를 3으로, 5를 5로 예측한 데이터들을 한번에 살펴보기

# 그림 그리기 기능 함수
import matplotlib as mpl

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, **options):
    size=28
    images_per_row=min(len(instances),images_per_row)
    n_rows=(len(instances)-1) // images_per_row +1

    # 필요하면 그리드의 끝을 채우기 위해 빈 이미지 추가
    n_empty=n_rows*images_per_row-len(instances)
    padded_instances=np.concatenate([instances, np.zeros((n_empty, size*size))],axis=0)

    # 배열의 크기를 바꿔 28*28 이미지를 담은 그리드로 구성
    image_grid=padded_instances.reshape((n_rows, images_per_row, size, size))

    # 축 0(이미지 그리드의 수직축)과 2(이미지의 수직축)를 합치고 축 1과 3(그리드와 이미지의 수평축)을 합침
    # transpose()를 통해 결합하려는 축을 옆으로 이동한 다음 합침
    big_image=image_grid.transpose(0, 2, 1, 3).reshape(n_rows*size,images_per_row*size)

    # 하나의 큰 이미지 출력
    plt.imshow(big_image, cmap=mpl.cm.binary, **options)
    plt.axis('off')


# 개개의 오류 살펴보며 왜 잘못되었는지 생각해보기(3과 5를 예시로)
cl_a, cl_b=3,5
X_aa=X_train[(y_train==cl_a) & (y_train_pred==cl_a)]    # 실제 3을 3으로 예측
X_ab=X_train[(y_train==cl_a) & (y_train_pred==cl_b)]    # 실제 3을 5로 예측
X_ba=X_train[(y_train==cl_b) & (y_train_pred==cl_a)]    # 실제 5를 3으로 예측
X_bb=X_train[(y_train==cl_b) & (y_train_pred==cl_b)]    # 실제 5를 5로 예측

plt.figure(figsize=(8,8))
plt.subplot(221); plot_digits(X_aa[:25], images_per_row=5)  # X_aa에 해당하는 데이터를 처음 25개만 불러와서 5행으로 정렬
plt.subplot(222); plot_digits(X_ab[:25], images_per_row=5)  # X_ab에 해당하는 데이터를 처음 25개만 불러와서 5행으로 정렬
plt.subplot(223); plot_digits(X_ba[:25], images_per_row=5)  # X_ba에 해당하는 데이터를 처음 25개만 불러와서 5행으로 정렬
plt.subplot(224); plot_digits(X_bb[:25], images_per_row=5)  # X_bb에 해당하는 데이터를 처음 25개만 불러와서 5행으로 정렬
plt.show()

 -5를 3으로 잘못 예측해낸 것(제3사분면) 중 첫 행 2열은 사람이 봐도 3같을 정도로 잘못 분류할 확률이 높아보임

 -위 방식으로 에러를 확인하여 어디서, 왜 오차가 나는지 확인하고 해결방법 고안하기

  ex) 3과 5는 위의 선분과 아래의 원을 잇는 수직선의 위치가 왼쪽, 오른쪽으로 다르다는 점 등을 이용하여 다시 학습시키기

 

 

4. 다중 레이블 분류

 -분류해내야 하는 타겟변수가 여러 개일 때, 여러 개를 한 번에 분류

 -KNeighborsClassifier, DecisionTreeClassifier, RandomForestClassifier, OneVsRestClassifier에서 다중 분류 지원

from sklearn.neighbors import KNeighborsClassifier

y_train_large=(y_train>=7)                      # 분류한 결과가 7보다 큰지
y_train_odd=(y_train%2==1)                      # 분류한 결과가 홀수인지
y_multilabel=np.c_[y_train_large,y_train_odd]   # 위의 두 개의 사항에 대해 예측하는 다중 레이블

knn_clf=KNeighborsClassifier()
knn_clf.fit(X_train,y_multilabel)

knn_clf.predict([some_digit])   # 숫자 5에 대해 예측결과 반환

### 결과 ###
array([[False, True]])

# f1_score를 통해 얼마나 정확한지 확인
from sklearn.metrics import f1_score
y_train_knn_pred=cross_val_predict(knn_clf,X_train,y_multilabel,cv=3)
f1_score(y_multilabel, y_train_knn_pred,average='macro')

### 결과 ###
0.976410265560605

-숫자 5를 7보다 크지 않고,(False), 홀수(True)라고 정확히 분류해냄 

 

 

5. 다중 출력 분류

 -다중 레이블 분류에서 한 레이블이 값을 두 개이상 가질 수 있는 분류

 -MNIST 숫자 이미지 데이터는 한 픽셀당 한 레이블이므로 레이블이 784개인 다중 레이블임

 -각 레이블은 0~255까지의 숫자를 가질 수 있는 다중 출력 분류가 가능한 데이터셋임

# MNIST 이미지의 픽셀 강도에 잡음 추가
noise = np.random.randint(0, 100, (len(X_train), 784))
X_train_mod = X_train + noise       # 독립변수는 잡음이 섞인 데이터
noise = np.random.randint(0, 100, (len(X_test), 784))
X_test_mod = X_test + noise
y_train_mod = X_train               # 예측해야하는 변수는 원래 데이터
y_test_mod = X_test

some_index = 5500
plt.subplot(121); plot_digit(X_test_mod[some_index])
plt.subplot(122); plot_digit(y_test_mod[some_index])
plt.show()

 

 -좌측은 노이즈를 섞은 데이터로 훈련기에 넣으면 노이즈를 제거하여 원래 이미지를 예측해낼 것

 -우측은 원래의 이미지

knn_clf.fit(X_train_mod,y_train_mod)
clean_digit=knn_clf.predict([X_test_mod[some_index]])
plot_digit(clean_digit)

 -노이즈가 있던 왼쪽의 이미지를 분류기에 넣어 분류기가 원래 이미지를 분류해낸 모습

1. 사용 데이터

 -사이킷런에서 제공하는 MNIST 데이터셋(손글씨 데이터셋)

# 사이킷런에서 기본적으로 제공하는 MNIST 데이터셋 불러오기
from sklearn.datasets import fetch_openml
mnist=fetch_openml('mnist_784',version=1,as_frame=False)
mnist.keys()

### 결과 ###
dict_keys(['data', 'target', 'frame', 'categories', 'feature_names', 'target_names', 'DESCR', 'details', 'url'])

 

 -MNIST 데이터셋 배열 확인

X,y=mnist['data'],mnist['target']
X.shape     # (70000, 784)
y.shape     # (70000,)

# 이미지가 70000개이고 각 이미지는 784개의 특성을 가짐(28*28 픽셀의 이미지이므로 28*28=784개의 특성을 가짐)
# 각 특성은 0(흰색)~255(검은색)까지의 픽셀 강도

 

 -가장 첫번째 데이터 출력해보기

 -가로 28개, 세로 28개의 픽셀로 이루어짐

import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np

# 가장 첫번째 데이터 불러와서 28*28의 배열로 재배열
some_digit=X[0]
some_digit_image=some_digit.reshape(28,28)

# imshow()함수를 통해 이미지 확인
plt.imshow(some_digit_image, cmap='binary')
plt.axis('off')
plt.show()

 -이미지 여러개 확인해보기

import matplotlib as mpl

# 그림 그리기 기능 함수
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, **options):
    size=28
    images_per_row=min(len(instances),images_per_row)
    n_rows=(len(instances)-1) // images_per_row +1

    # 필요하면 그리드의 끝을 채우기 위해 빈 이미지 추가
    n_empty=n_rows*images_per_row-len(instances)
    padded_instances=np.concatenate([instances, np.zeros((n_empty, size*size))],axis=0)

    # 배열의 크기를 바꿔 28*28 이미지를 담은 그리드로 구성
    image_grid=padded_instances.reshape((n_rows, images_per_row, size, size))

    # 축 0(이미지 그리드의 수직축)과 2(이미지의 수직축)를 합치고 축 1과 3(그리드와 이미지의 수평축)을 합침
    # transpose()를 통해 결합하려는 축을 옆으로 이동한 다음 합침
    big_image=image_grid.transpose(0, 2, 1, 3).reshape(n_rows*size,images_per_row*size)

    # 하나의 큰 이미지 출력
    plt.imshow(big_image, cmap=mpl.cm.binary, **options)
    plt.axis('off')

# 이미지 샘플 100개 출력
plt.figure(figsize=(9,9))
example_images=X[:100]
plot_digits(example_images,10)
plt.show()

 -레이블 확인

y[0]

### 결과 ###
'5'

 -레이블이 문자형으로 되어있고 머신러닝 알고리즘은 대부분 숫자형의 레이블을 계산하므로 숫자형으로 변환이 필요

y=y.astype(np.uint8)

# train 데이터와 test 데이터 분리(train 6만개, test 1만개)
X_train,X_test,y_train,y_test=X[:60000],X[60000:],y[:60000],y[60000:]

 

 

 

2. 이진 분류기 훈련

 -5인지 5가 아닌지만 분류해보기

# 5인 것만 True, 다른 숫자는 False
y_train_5=(y_train==5)
y_test_5=(y_test==5)

# 확률적 경사하강법(SGD) 사용
from sklearn.linear_model import SGDClassifier
sgd_clf=SGDClassifier(random_state=42)
sgd_clf.fit(X_train,y_train_5)

# 앞에서 살펴봤던 가장 첫번째 데이터를 예측기에 넣어본 결과 True(5)로 예측
sgd_clf.predict([some_digit])

### 결과 ###
array([ True])

 

 

3. 교차 검증을 통한 성능 측정

from sklearn.model_selection import StratifiedKFold
from sklearn.base import clone

# StratifiedKFold는 클래스별 비율이 유지되도록 계층적 샘플링 수행
# 3개의 서브셋으로 나눔
skfolds=StratifiedKFold(n_splits=3,random_state=42,shuffle=True)

# X_train과 y_train_5를 3개의 서브셋으로 나누고 각 서브셋의 인덱스를 받아 해당 인덱스의 데이터만으로 훈련과 검증을 반복(총 세번 반복)
# clone은 훈련되지 않은 새로운 분류기를 생성해줌
for train_index,test_index in skfolds.split(X_train,y_train_5):
     clone_clf=clone(sgd_clf)
     X_train_folds=X_train[train_index]        # X_train 데이터에서 train으로 분류된 데이터
     y_train_folds=y_train_5[train_index]     # y_train 데이터에서 train으로 분류된 데이터
     X_test_fold=X_train[test_index]           # X_train 데이터에서 test로 분류된 데이터
     y_test_fold=y_train_5[test_index]        # y_train 데이터에서 test로 분류된 데이터

     clone_clf.fit(X_train_folds,y_train_folds)     # 훈련되지 않은 새로운 분류기에 train데이터 훈련
     y_pred=clone_clf.predict(X_test_fold)        # test 데이터 예측
     n_correct=sum(y_pred==y_test_fold)       # 정확하게 예측한 데이터 개수
     print(n_correct/len(y_pred))                     # (정확하게 예측한 데이터 개수 / 전체 데이터 개수)로 정확도 계산

### 결과 ###
0.9669
0.91625
0.96785

 -세 번의 교차 검증 결과, 정확하게 예측한 데이터 개수의 비율이 0.9 이상으로 정확도가 90% 이상임

 

4. 오차 행렬을 통한 성능 측정

 -오차 행렬 구하기

# cross_val_predict는 cross_val_score가 평가 점수를 반환하는 것과 다르게 각 테스트셋의 예측값을 반환(훈련에 쓰이지 않은 값들의 예측값)
from sklearn.model_selection import cross_val_predict

# 앞에서 쓴 확률적 경사하강법 모델에 X_train으로 학습시키는 과정을 3번 반복
y_train_pred=cross_val_predict(sgd_clf,X_train,y_train_5,cv=3)

# 실제 y_train_5값과 예측된 y_train_pred값을 비교하여 오차행렬 생성
from sklearn.metrics import confusion_matrix
confusion_matrix(y_train_5,y_train_pred)

### 결과 ###
array([[53892,   867],
          [1891,   3530]])

# True Negative(TN, 실제: 음성, 예측: 음성) : 53892     False Positive(FP, 실제: 음성, 예측: 양성)  : 687
# False Negative(FN, 실제: 양성, 예측: 음성): 1891      True Positive(TP, 실제: 양성, 예측: 양성)   : 3530

 

 -정밀도, 재현율 구하기

from sklearn.metrics import precision_score,recall_score
# 정밀도: TP/(TP+FP)
precision_score(y_train_5,y_train_pred) # 3530 / (3530+687)

# 재현율: TP/(TP+FN)
recall_score(y_train_5, y_train_pred)   # 3530 / (3530+1891)

# F-1 점수: 정밀도와 재현율의 조화 평균, 2 / (1/정밀도)+(1/재현율) = 2*(정밀도*재현율) / (정밀도+재현율)
from sklearn.metrics import f1_score
f1_score(y_train_5,y_train_pred)

 -정밀도와 재현율은 같이 높일 수 없고 한쪽이 높아지면 다른 한 쪽은 낮아지는 트레이드오프 관계이므로 적절한 선에서 타협을 봐야함

 -f1-score는 정밀도와 재현율의 조화 평균으로 f1-score가 클수록 정밀도와 재현율이 비슷함

 -예시 1) 어린이에게 안전한 동영상만 컬러내는 분류기 훈련 가정

  →재현율이 높고 정밀도가 낮음: 실제 양성 중 양성으로 예측한 비율이 높음

                                                      →예측한 것 중에 나쁜 동영상이 있을수 있음

  →재현율이 낮고 정밀도가 높음: 양성으로 예측한 것 중 실제 양성인 비율이  높음

                                                     →예측한 것에 좋은 동영상의 비율이 높으므로 안전한 동영상만 있을 확률이 더 높음

 

 -예시 2) 감시 카메라로 좀도둑을 잡는 분류기 훈련 가정

  →재현율이 높고 정밀도가 낮음: 실제 양성 중 양성으로 예측한 비율이 높음

                                                      →실제 도둑을 도둑으로 예측할 확률이 높아 도둑을 잡을 확률이 높아지지만 경비원이 오인 출동할 확률도 있음

  →재현율이 낮고 정밀도가 높음: 양성으로 예측한 것 중 실제 양성인 비율이  높음

                                                     →도둑이라고 예측했다면 진짜 도둑일 확률이 높이만 애초에 도둑이라고 예측할 비율은 예상할 수 없음

 

 -데이터의 점수에 대한 정밀도와 재현율 그래프 그리기

# 그래프 그리기 전에 먼저 모든 데이터의 점수 받아오기
y_scores=cross_val_predict(sgd_clf,X_train,y_train_5, cv=3,method='decision_function')

# 가능한 모든 임계값에 대한 정밀도와 재현율 계산하는 함수
from sklearn.metrics import precision_recall_curve
precisions,recalls,thresholds=precision_recall_curve(y_train_5,y_scores)

def plot_precision_recall_vs_threshold(precisions,recalls,threshods):
    plt.plot(thresholds,precisions[:-1],'b--',label='정밀도')   # x축에 임계값, y축에 정밀도, 색깔은 blue, 선 종류는 긴 점선
    plt.plot(thresholds,recalls[:-1],'g',label='재현율')        # x축에 임계값, y축에 재현율, 색깔은 green, 선 종류는 실선

plot_precision_recall_vs_threshold(precisions,recalls,thresholds)
plt.show()
# 재현율은 임계값이 올라감에 따라 무조건 줄어들지만 정밀도는 가끔 낮아질 때가 있어 울퉁불퉁한 그래프가 그려짐

# 재현율과 정밀도에 관한 그래프
# 정밀도가 급격하게 줄어드는 지점(이 그래프에서는 재현율이 80%인 지점 쯤) 직전에서 정밀도와 재현율을 결정하는 것이 좋음(이 그래프에서는 재현율이 60%인 지점 쯤)
plt.plot(recalls[:-1],precisions[:-1])

 -정밀도와 재현율 사이에서 임계값 찾기

 -정밀도와 재현율 중 한 점수에 대해 목표를 잡고 그 목표에 대한 임계값 구하기

# 정밀도 90% 달성이 목표라고 했을 때 정밀도가 최소 90%가 되는 가장 낮은 임계값 찾기
# np.argmax()는 최대값의 첫번째 인덱스 반환(여기서는 True의 첫번째 인덱스 반환)
threshold_90_precision=thresholds[np.argmax(precisions>=0.90)]

# 훈련 세트에 대한 예측
y_train_pred_90=(y_scores>=threshold_90_precision)
# 예측에 대한 정밀도와 재현율
precision_score(y_train_5,y_train_pred_90)  # 0.9000345901072293
recall_score(y_train_5,y_train_pred_90)     # 0.4799852425751706
# 재현율이 너무 낮으므로 정밀도가 높아도 유용하지 않은 분류기임

 -정밀도가 90% 이상인 점을 찾아 정밀도는 90%가 넘도록 나왔지만 재현율은 0.48정도로 낮음

 -유용하지 않은 분류기

 

 

5. ROC 곡선과 AUC 점수

 -ROC 곡선은 재현율(진짜 양성 비율)과 FPR(거짓 양성 비율, FP / (FP+TN), 1-특이도)의 곡선

from sklearn.metrics import roc_curve
fpr, tpr, thresholds=roc_curve(y_train_5, y_scores)

def plot_roc_curve(fpr, tpr, label=None):
    plt.plot(fpr, tpr, linewidth=2, label=label)
    plt.plot([0, 1],[0, 1], 'k--')

plot_roc_curve(fpr, tpr)
plt.show()

 -점선은 완전 랜덤 분류기의 성능으로 가장 안좋은 상황

 -좋은 분류기일수록 점선에서 떨어져 왼쪽 위의 모서리로 가까워짐

 

 -AUC 점수는 곡선 아래의 면적(Area Under Curve)으로 분류기의 성능을 비교하기 위해 계산

 -완전 랜덤일 때(점선일 때) 0.5, 가장 좋을 때(왼쪽 위의 모서리가 꼭짓점으로 있을 때) 1

from sklearn.metrics import roc_auc_score
roc_auc_score(y_train_5,y_scores)

### 결과 ###
0.9604938554008616

데이터: 핸즈온 머신러닝(2판)의 예제 데이터(https://github.com/rickiepark/handson-ml2/blob/master/datasets/housing/housing.csv) 사용

 

 

1. 문제 정의

예제 데이터는 캘리포니아의 블록 그룹마다 인구, 중위소득, 중간주택가격 등의 변수를 담은 데이터

※블록 그룹: 미국 인구조사국이 샘플 데이터 발표에 사용하는 최소한의 지리적 단위, 보통 600명~3,000명의 인구

 

● 첫번째 질문: 비즈니스의 목적이 무엇인가?

 -모델을 이용해 어떻게 이익을 얻으려는 건지?

 -문제를 어떻게 구성할지, 어떤 알고리즘을 선택할지, 어떤 성능 지표로 모델을 평가할지, 모델 튜닝을 어느 정도로 빡세게 할지 경정하는데 중요

 

ex 답변) 다른 블록 그룹의 데이터가 주어졌을 때 중간 주택 가격을 예측하여 해당 지역에 투자할 가치가 있는지 평가하는 것이 문제

 

 

● 두번째 질문: 현재 솔루션은 어떻게 구성되어 있는가?

 -문제 해결 방법에 대한 정보 또는 참고 성능으로 활용 가능

 

ex 답변) 한 팀이 블록 그룹의 최신 정보를 모으고 전문가들이 복잡한 규칙을 통해 수동으로 예측 중

→인구조사 데이터는 인구 관련 데이터에 더해 블록 그룹의 중간 주택 가격 데이터를 포함하므로 매우 적합한 데이터셋으로 보임

 

 

● 세번째 질문: 지도/비지도/강화 학습 중 무엇?

                        분류/회귀 중 무엇?

                        배치 학습/온라인 학습 중 무엇?

→중간 주택 가격이 나와있는 훈련 샘플이 있으므로 지도 학습 작업

→중간 주택 가격이라는 값을 예측해야하므로 회귀 문제

→예측에 사용할 특성이 인구, 중간 소득 등 한 개 이상이므로 다중 회귀

→중간 주택 가격 한 개의 값만 예측하므로 단변량 회귀(두 개이상 예측 시 다변량 회귀)

→데이터에 연속적 흐름 없이 고정된 데이터이고 크기도 작으므로 배치 학습이 적정

 

 

 성능 평가 지표 선택

 -회귀 성능 평가 지표: MAE / MSE / RMSE / MSLE / RMSLE / R²

 -분류 성능 평가 지표: 정확도 / 오차행렬 / 정밀도 / 민감도(재현율) / 특이도 / F1 Score / ROC / AUC

 

 

위에서 설정한 가정들 검사해보기

 

 

2. 데이터 가져오기

데이터를 다운로드한 뒤 파이썬 개발환경에 불러오기

import pandas as pd

housing=pd.read_csv('housing.csv',encoding='cp949')

 

데이터 구조 훑어보기

# 불러온 데이터의 처음 다섯 행 확인(괄호안에 숫자 지정하여 더 많은 데이터 확인 가능)
housing.head()

# 불러온 데이터의 마지막 다섯 행 확인(괄호안에 숫자 지정하여 더 많은 데이터 확인 가능
housing.tail()

# 데이터의 간략한 설명, 전체 행 수, 각 변수의 데이터 타입, 결측값(NULL) 개수 확인
housing.info()

# 범주형 변수의 확인
housing['ocean_proximity'].value_count()
# 각 범주별 개수 확인 가능

# 숫자형 변수의 특성(개수, 평균, 표준편차, 최소값, 최대값, 사분위수) 요약
housing.describe()
# 시각적으로 데이터의 분포 알아보기
import matplotlib.pyplot as plt
housing.hist(bins=50,figsize=(20,15))
plt.show()
# 각 변수의 히스토그램을 통해 분포 확인 가능

 

 

데이터 탐색과 시각화(EDA, 탐색적 자료분석)

 -지리적 데이터 시각화: 데이터에 위도(latitude)와 경도(longitude)가 있으므로 이를 이용해 산점도에 위치 데이터 시각화

# plot의 종류는 산점도로, x축에 경도 값, y축에 위도 값 설정, alpha=0.1로 설정하여 투명도 낮추어 밀집된 곳 파악
housing.plot(kind='scatter', x='longitude', y='latitude', alpha=0.1)

 -plot() 함수의 각종 매개변수를 설정해 다양한 값을 한번에 시각화

housing.plot(kind='scatter', x='longitude', y='latitude', alpha=0.4,
                   s=housing['population'],label='population',figsize=(10,7),
                   c='median_house_value', cmap=plt.get_cmap('jet'), colorbar=True,
                   sharex=False)
plt.legend()

 -주택가격은 바다와 밀접한 곳, 인구 밀도 등과 관련이 크다는 점 발견

 

 

 상관관계 조사

 -상관계수를 통해 확인

# housing 데이터의 상관계수를 median_house_value 변수에 대한 상관계수만 내림차순으로 출력
housing.corr()['median_house_value'].sort_values(ascending=False)

 

 -산점도를 통해 확인

# 숫자형 변수 사이의 산점도를 그려주는 판다스 함수
from pandas.plotting import scatter_matrix

# 상관계수 확인 결과 median_house_value와 상관관계가 높아보이는 3개의 변수만 확인
attributes=['median_house_value', 'median_income', 'total_rooms', 'housing_median_age']
scatter_matrix(housing[attributes], figsize=(12,8))

 

 -확인 결과  median_house_value와 median_income이 특히 선형적인 관계가 강한 것을 확인

housing.plot(kind='scatter', x='median_income', y='median_house_value', alpha=0.1)

 -위 그래프에서도 median_house_value가 500000, 450000, 350000일 때 수평선이 확인되며 알고리즘이 이 수평선을 학습하지 않도록 제거해야할 필요가 있음

 

 

-이외에도 특성의 분포에 따라 로그 스케일, Min-Max 스케일 등 변환 과정을 거쳐야 함

 

 특성 조합

 -분포를 정규화하고 상관관계를 확인한 뒤 마지막으로 해볼 수 있는 것은 특성 조합하여 새로운 특성(파생변수) 만들기

 -예시에서 '방 개수'보다는 '가구당 방 개수'가 더 유용할 것이므로 '가구 수'와 '방 개수' 변수를 조합해 '가구당 방 개수' 변수 만들기

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 변수가 전체 방 개수나 침실 개수보다 중간 주택 가격과 더 큰 상관관계를 가짐

 

 -여러 파생변수들을 만들고 결과를 확인하는 반복적인 과정을 통해 좋은 모델 만들 수 있음

 

 

 

3. 데이터 전처리

housing 데이터에서 예측해야하는 median_house_value 변수는 분리하기

housing_labels=housing['median_house_value']

 

수치형 변수 결측값 처리

 -해당 구역을 제거(dropna())

 -전체 특성 제거(drop())

 -어떤 값(0, 평균, 중간값 등)으로 채움(fillna(), SimpleImputer)

 -SimpleImputer 사용예시

from sklearn.impute import SimpleImputer

# SimpleImputer 객체 생성, 중간값을 채우는 것으로 설정
imputer=SimpleImputer(strategy='median')

# 숫치형 변수에 대해 중간값으로 결측값을 채울 것이므로 범주형 변수인 ocean_proximity와 예측할 변수인 median_house_value는 제거
housing_num=housing.drop(['ocean_proximity','median_house_value'],axis=1)

imputer.fit(housing_num)

# statistics_에 각 변수별로 채울 값을 저장(여기서는 중간값)
imputer.statistics_

# housing_num의 숫치형 변수를 피팅한 imputer를 housing_num에 적용
X=imputer.transform(housing_num)

# 결측값이 중간값으로 대체된 데이터에 원래의 변수명과 인덱스를 적용해 원래 데이터 형태로 만들기
housing_tr=pd.DataFrame(X,columns=housing_num.columns,index=housing_num.index)

 

 

 

 범주형 변수 인코딩

 -범주형 변수인 ocean_proximity의 특성 살펴보기

# 범주형 변수인 ocean_proximity만 housing_cat에 저장하고 확인
housing_cat=housing[['ocean_proximity']]
housing_cat

 -OrdinalEncoder, OneHotEncoder, LabelEncoder 등의 인코더를 사용하여 범주형 변수를 수치형 변수로 변환

 -OrdinalEncoder 사용예시

from sklearn.preprocessing import OrdinalEncoder

# ordinalencoder 객체 생성
ordinal_encoder=OrdinalEncoder()

# 범주형 변수인 housing_cat을 ordinalencoder에 피팅하고 변환까지 적용
housing_cat_encoded=ordinal_encoder.fit_transform(housing_cat)
housing_cat_encoded

# 인코딩에 사용된 범주 확인
ordinal_encoder.categories_

 

 

 

 -OneHotEncoder 사용예시

from sklearn.preprocessing import OneHotEncoder

# OneHotEncoder 객체 생성
cat_encoder=OneHotEncoder()

# 범주형 변수인 housing_cat을 인코더에 피팅하고 변환
housing_cat_1hot=cat_encoder.fit_transform(housing_cat)
housing_cat_1hot

# Sparse Row(희소행렬) 형태로 출력되므로 toarray()를 사용하여 넘파이 배열의 형태로 출력할 수 있음
housing_cat_1hot.toarray()

# 인코딩에 사용된 범수 확인
cat_encoder.categories_

 

 

 특성 스케일링

 -모든 특성의 범위를 같게 해줌(0~1 사이 또는 -1~1 사이 등)

 -Min-Max 스케일링, 표준화(StandardScalling) 등

 

 

 변환 파이프라인

 -앞에서 했던 변환을 순서대로 처리할 수 있도록 파이프라인 생성 가능

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
num_pipeline=Pipeline([('imputer',SimpleImputer(strategy='median')),    # SimpleInputer를 사용해 결측값을 중간값으로 설정
                                   ('attribs_adder',CombinedAttributesAdder()),      # 변수 조합 생성
                                   ('atd_scaler', StandardScaler())                         # 정규화를 통해 범위 조정
                                  ])

housing_num_tr=num_pipeline.fit_transform(housing_num)

 

 -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)])       # 범주형 변수열에는 OneHotEncoder 적용하여 수치형 변수로 변환

housing_prepared=full_pipeline.fit_transform(housing)

 

 

 

 

4. 모델 선택과 훈련

 훈련 세트에서 훈련, 평가

 -전처리를 끝낸 데이터를 적절한 알고리즘을 선택해서 훈련시키기

# 선형 회귀 모델
from sklearn.linear_model import LinearRegression
# 선형 회귀 모델 객체 생성
lin_reg=LinearRegression()
# 데이터를 선형 회귀 모델에 훈련시키기
lin_reg.fit(housing_prepared,housing_labels)


# 의사결정나무 모델
from sklearn.tree import DecisionTreeRegressor
# 의사결정나무 모델 객체 생성
tree_reg=DecisionTreeRegressor()
# 데이터를 의사결정나무 모델에 훈련시키기
tree_reg.fit(housing_prepared, housing_labels)

 -훈련된 모델의 성능 평가

from sklearn.metrics import mean_squared_error

# 선형회귀모델의 성능 평가
# 훈련된 모델에 데이터를 넣었을 때 예측값 계산
housing_predictions=lin_reg.predict(housing_prepared)

# 평균제곱오차(mse) 계산
lin_mse=mean_squared_error(housing_labels,housing_predictions)

# mse에 루트를 씌운 값은 rmse 계산
lin_rmse=np.sqrt(lin_mse)

# rmse값
lin_rmse

### 결과 ###
1.6715935001871568e-10
from sklearn.metrics import mean_squared_error

# 의사결정나무 모델의 성능 평가
# 훈련된 모델에 데이터를 넣었을 때 예측값 계산
housing_predictions=tree_reg.predict(housing_prepared)

# 평균제곱오차(mse) 계산
tree_mse=mean_squared_error(housing_labels,housing_predictions)

# mse에 루트를 씌운 값은 rmse 계산
tree_rmse=np.sqrt(tree_mse)

# rmse값
tree_rmse

### 결과 ###
0.0

 -성능 평가 지표의 값이 거의 0에 가깝거나 0이 나온 것은 훈련 데이터에 모델이 과대적합(오버피팅) 되었기 때문

 

 

 교차 검증을 통한 평가

 -훈련 데이터를 정해진 개수(CV)만큼의 서브세트로 무작위로 분할하고 1개의 서브세트를 평가에 활용하고 나머지 서브세트를 훈련에 활용하는 방법

from sklearn.model_selection import cross_val_score

# 의사결정나무 모델에서 교차검증을 통해 mse계산
# 사이킷런의 교차검증 기능은 scoring 매개변수에 낮을수록 좋은 비용함수가 아닌 높을수록 좋은 효용함수를 기대하므로
# neg_mean_squared_error를 계산하여 낮을수록 좋은 점수로 만듦
scores=cross_val_score(tree_reg,housing_prepared,housing_labels,scoring='neg_mean_squared_error',cv=10)
tree_rmse_scores=np.sqrt(-scores)


# 서브세트 개수만큼 계산된 rmse의 평균과 표준편차를 계산
def display_scores(scores):
    print('점수:',scores)
    print('평균:',scores.mean())
    print('표준편차:',scores.std())

display_scores(tree_rmse_scores)

# 선형회귀 모델에도 적용하여 선형회귀 모델을 교차검증하였을 때 점수 계산
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)

 -선형회귀 모델이 의사결정나무 모델보다 과대적합이 덜하여 교차 검증을 했을 때도 더 좋은 점수를 가짐

 

 

 

 

5. 모델 세부 튜닝

 -모델에 사용되는 하이퍼 파라미터 등을 조율하며 가장 좋은 점수를 도출하는 하이퍼 파라미터를 찾아 모델에 적용시키기

 

 그리드 탐색: 탐색하고자 하는 하이퍼 파라미터를 전부 지정하면 모든 하이퍼 파라미터 조합에 대해 교차 검증을 사용해 평가

 -랜덤 포레스트 사용 예시

from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import GridSearchCV

# 그리드 서치에 적용하여 탐색할 파라미터들 지정
param_grid=[{'n_estimators':[3,10,30],'max_features':[2,4,6,8]},
            {'bootstrap':[False],'n_estimators':[3,10],'max_features':[2,3,4]}]
# 첫번째 딕셔너리에서 n_estimators가 각각 3, 10 ,30일 때와 max_features가 각각 2, 4, 6, 8일 때의 조합으로 총 12번 평가
# 두번째 딕셔너리에서는 총 6번 평가하며 bootstrap은 False로 설정
# 총 12+6=18개의 조합을 탐색하고 CV=5로 교차검증을 5번 시도하여 총 훈련횟수는 18*5=90번


# 랜덤포레스트 객체 생성
forest_reg=RandomForestRegressor()

# 랜텀포레스트 모델과 파라미터, 교차검증 횟수, 평가 지표 등을 매개변수로 지정한 그리드 서치 모델 객체 생성
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)

# 최적의 하이퍼 파라미터 출력
gird_search.best_params_

### 결과 ###
{'max_features': 8, 'n_estimators': 30}

 

랜덤 탐색: 지정한 하이퍼 파라미터를 랜덤으로 조합하여 교차 검증을 사용해 평가

 

 

 중요도 탐색: 모델이 정확한 예측을 하기 위한 각 변수의 상대적인 중요도

# 각 변수의 상대적인 중요도 계산
feature_importances=grid_search.best_estimator_.feature_importances_

# 계산된 중요도와 변수의 이름을 짝을 지어 표시
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(feature_importances,attributes),reverse=True)

 

 

 

 

6. 테스트 세트로 최종 모델 평가

pred=forest_reg.predict(test)

 -위에서 train 데이터셋으로 훈련시킨 랜덤 포레스트 모델에 test 데이터셋을 넣으면 test 데이터셋에 대한 예측값이 pred변수에 저장됨

+ Recent posts