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