#!/usr/bin/env python # coding: utf-8 # # 머신 러닝 교과서 3판 # # 10장 - 회귀 분석으로 연속적 타깃 변수 예측 # **아래 링크를 통해 이 노트북을 주피터 노트북 뷰어(nbviewer.jupyter.org)로 보거나 구글 코랩(colab.research.google.com)에서 실행할 수 있습니다.** # # # # #
# 주피터 노트북 뷰어로 보기 # # 구글 코랩(Colab)에서 실행하기 #
# ### 목차 # - 선형 회귀 # - 단순 선형 회귀 # - 다중 선형 회귀 # - 주택 데이터셋 탐색 # - 데이터프레임으로 주택 데이터셋 읽기 # - 데이터셋의 중요 특징 시각화 # - 최소 제곱 선형 회귀 모델 구현 # - 경사 하강법으로 회귀 모델의 파라미터 구하기 # - 사이킷런으로 회귀 모델의 가중치 추정 # - RANSAC을 사용하여 안정된 회귀 모델 훈련 # - 선형 회귀 모델의 성능 평가 # - 회귀에 규제 적용 # - 선형 회귀 모델을 다항 회귀로 변환 # - 주택 데이터셋을 사용한 비선형 관계 모델링 # - 랜덤 포레스트를 사용하여 비선형 관계 다루기 # - 결정 트리 회귀 # - 랜덤 포레스트 회귀 # - 요약 #
#
# In[1]: from IPython.display import Image # # 선형 회귀 # ## 단순 선형 회귀 # In[2]: Image(url='https://git.io/Jts3N', width=500) # ## 다중 선형 회귀 # In[3]: Image(url='https://git.io/Jts3p', width=500) #
#
# # 주택 데이터셋 탐색 # ## 데이터프레임으로 주택 데이터셋 읽기 # Description, which was previously available at: [https://archive.ics.uci.edu/ml/datasets/Housing](https://archive.ics.uci.edu/ml/datasets/Housing) # # Attributes: # #
# 1. CRIM      per capita crime rate by town
# 2. ZN        proportion of residential land zoned for lots over
#                  25,000 sq.ft.
# 3. INDUS     proportion of non-retail business acres per town
# 4. CHAS      Charles River dummy variable (= 1 if tract bounds
#                  river; 0 otherwise)
# 5. NOX       nitric oxides concentration (parts per 10 million)
# 6. RM        average number of rooms per dwelling
# 7. AGE       proportion of owner-occupied units built prior to 1940
# 8. DIS       weighted distances to five Boston employment centres
# 9. RAD       index of accessibility to radial highways
# 10. TAX      full-value property-tax rate per $10,000
# 11. PTRATIO  pupil-teacher ratio by town
# 12. B        1000(Bk - 0.63)^2 where Bk is the proportion of blacks
#                  by town
# 13. LSTAT    % lower status of the population
# 14. MEDV     Median value of owner-occupied homes in $1000s
# 
# In[4]: import pandas as pd df = pd.read_csv('https://raw.githubusercontent.com/rasbt/' 'python-machine-learning-book-3rd-edition/' 'master/ch10/housing.data.txt', header=None, sep='\s+') df.columns = ['CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV'] df.head() #
# # ### 노트: # # 주택 데이터셋(그리고 책에서 사용하는 다른 모든 데이터셋)은 책의 깃허브에 포함되어 있습니다. 인터넷 # 을 사용하지 않을 때나 깃허브(https://raw.githubusercontent.com/rickiepark/python-machine-learningbook-3rd-edition/master/ch10/housing.data.txt )에 접속되지 않을 때 사용할 수 있습니다. 예를 들어 # 로컬 디렉터리에서 주택 데이터셋을 로드하려면 첫 번째 코드를 두 번째 코드처럼 바꿉니다. # # ``` # df = pd.read_csv('https://raw.githubusercontent.com/rickiepark/' # 'python-machine-learning-book-3rd-edition' # '/master/ch10/housing.data.txt', # sep='\s+') # ``` # # in the following code example by # # ``` # df = pd.read_csv('./housing.data', sep='\s+') # ``` #
#
# ## 데이터셋의 중요 특징 시각화 # `mlxtend`를 설치합니다. # In[5]: get_ipython().system('pip install --upgrade mlxtend') # In[6]: import matplotlib.pyplot as plt from mlxtend.plotting import scatterplotmatrix # In[7]: cols = ['LSTAT', 'INDUS', 'NOX', 'RM', 'MEDV'] scatterplotmatrix(df[cols].values, figsize=(10, 8), names=cols, alpha=0.5) plt.tight_layout() # plt.savefig('images/10_03.png', dpi=300) plt.show() # In[8]: import numpy as np from mlxtend.plotting import heatmap cm = np.corrcoef(df[cols].values.T) hm = heatmap(cm, row_names=cols, column_names=cols) # plt.savefig('images/10_04.png', dpi=300) plt.show() #
#
# # 최소 제곱 선형 회귀 모델 구현 # ... # ## 경사 하강법으로 회귀 모델의 파라미터 구하기 # In[9]: class LinearRegressionGD(object): def __init__(self, eta=0.001, n_iter=20): self.eta = eta self.n_iter = n_iter def fit(self, X, y): self.w_ = np.zeros(1 + X.shape[1]) self.cost_ = [] for i in range(self.n_iter): output = self.net_input(X) errors = (y - output) self.w_[1:] += self.eta * X.T.dot(errors) self.w_[0] += self.eta * errors.sum() cost = (errors**2).sum() / 2.0 self.cost_.append(cost) return self def net_input(self, X): return np.dot(X, self.w_[1:]) + self.w_[0] def predict(self, X): return self.net_input(X) # In[10]: X = df[['RM']].values y = df['MEDV'].values # In[11]: from sklearn.preprocessing import StandardScaler sc_x = StandardScaler() sc_y = StandardScaler() X_std = sc_x.fit_transform(X) y_std = sc_y.fit_transform(y[:, np.newaxis]).flatten() # In[12]: lr = LinearRegressionGD() lr.fit(X_std, y_std) # In[13]: plt.plot(range(1, lr.n_iter+1), lr.cost_) plt.ylabel('SSE') plt.xlabel('Epoch') plt.tight_layout() # plt.savefig('images/10_05.png', dpi=300) plt.show() # In[14]: def lin_regplot(X, y, model): plt.scatter(X, y, c='steelblue', edgecolor='white', s=70) plt.plot(X, model.predict(X), color='black', lw=2) return # In[15]: lin_regplot(X_std, y_std, lr) plt.xlabel('Average number of rooms [RM] (standardized)') plt.ylabel('Price in $1000s [MEDV] (standardized)') # plt.savefig('images/10_06.png', dpi=300) plt.show() # In[16]: print('기울기: %.3f' % lr.w_[1]) print('절편: %.3f' % lr.w_[0]) # In[17]: num_rooms_std = sc_x.transform(np.array([[5.0]])) price_std = lr.predict(num_rooms_std) print("$1,000 단위 가격: %.3f" % sc_y.inverse_transform(price_std.reshape(-1,1))) #
#
# ## 사이킷런으로 회귀 모델의 가중치 추정 # In[18]: from sklearn.linear_model import LinearRegression # In[19]: slr = LinearRegression() slr.fit(X, y) y_pred = slr.predict(X) print('기울기: %.3f' % slr.coef_[0]) print('절편: %.3f' % slr.intercept_) # In[20]: lin_regplot(X, y, slr) plt.xlabel('Average number of rooms [RM]') plt.ylabel('Price in $1000s [MEDV]') # plt.savefig('images/10_07.png', dpi=300) plt.show() # **정규 방정식**을 사용한 방법: # In[21]: # 1로 채워진 열 벡터 추가 Xb = np.hstack((np.ones((X.shape[0], 1)), X)) w = np.zeros(X.shape[1]) z = np.linalg.inv(np.dot(Xb.T, Xb)) w = np.dot(z, np.dot(Xb.T, y)) print('기울기: %.3f' % w[1]) print('절편: %.3f' % w[0]) # QR 분해는 실수 행렬을 직교 행렬(orthogonal matrix) $\boldsymbol{Q}$와 상삼각 행렬(upper triangular matrix) $\boldsymbol{R}$의 곱으로 표현하는 행렬 분해 방법입니다. 직교 행렬은 전치 행렬과 역행렬이 같습니다. 따라서 선형 회귀 공식을 $\boldsymbol{w}$에 정리하면 다음과 같이 쓸 수 있습니다. # # $\boldsymbol{w} = \boldsymbol{X}^{-1}\boldsymbol{y} # = (\boldsymbol{Q}\boldsymbol{R})^{-1}\boldsymbol{y} # = \boldsymbol{R}^{-1}\boldsymbol{Q}^{-1}\boldsymbol{y} # = \boldsymbol{R}^{-1}\boldsymbol{Q}^T\boldsymbol{y} # $ # # `np.linalg.qr()` 함수를 사용하여 QR 분해를 수행한 다음 `np.linalg.inv()` 함수를 사용해 상삼각 행렬의 역행렬을 구하여 계산할 수 있습니다. # In[22]: Q, R = np.linalg.qr(Xb) w = np.dot(np.linalg.inv(R), np.dot(Q.T, y)) print('기울기: %.3f' % w[1]) print('절편: %.3f' % w[0]) # `LinearRegression` 클래스가 사용하는 `scipy.linalg.lstsq` 함수는 $\boldsymbol{X}$의 유사역행렬(pseudo-inverse matrix) $\boldsymbol{X}^+$을 구하여 다음처럼 바로 해를 구합니다. # # $\boldsymbol{w} = \boldsymbol{X}^+\boldsymbol{y}$ # # 유사역행렬은 특잇값 분해(SVD)로 얻은 $\boldsymbol{U}$, $\boldsymbol{\Sigma}$, $\boldsymbol{U}$로 계산합니다. # # $\boldsymbol{X}^+ = \boldsymbol{V}\boldsymbol{\Sigma}^+\boldsymbol{U}^T$ # # 여기에서 $\boldsymbol{\Sigma}^+$는 $\boldsymbol{\Sigma}$ 원소의 역수를 취하고 어떤 임곗값보다 작은 값은 0으로 만들어 얻을 수 있습니다. 예를 들어 $\boldsymbol{\Sigma}$의 행마다 가장 큰 값을 골라 $1 \times 10^{-15}$를 곱한 다음 이보다 작은 원소를 0으로 만듭니다. 넘파이 `np.linalg.pinv()` 함수를 사용하면 이런 작업을 모두 알아서 처리해 주므로 $\boldsymbol{X}^+$를 손쉽게 얻을 수 있습니다. # In[23]: w = np.dot(np.linalg.pinv(Xb), y) print('기울기: %.3f' % w[1]) print('절편: %.3f' % w[0]) #
#
# # RANSAC을 사용하여 안정된 회귀 모델 훈련 # In[24]: from sklearn.linear_model import RANSACRegressor ransac = RANSACRegressor(LinearRegression(), max_trials=100, min_samples=50, loss='absolute_error', residual_threshold=5.0, random_state=0) ransac.fit(X, y) inlier_mask = ransac.inlier_mask_ outlier_mask = np.logical_not(inlier_mask) line_X = np.arange(3, 10, 1) line_y_ransac = ransac.predict(line_X[:, np.newaxis]) plt.scatter(X[inlier_mask], y[inlier_mask], c='steelblue', edgecolor='white', marker='o', label='Inliers') plt.scatter(X[outlier_mask], y[outlier_mask], c='limegreen', edgecolor='white', marker='s', label='Outliers') plt.plot(line_X, line_y_ransac, color='black', lw=2) plt.xlabel('Average number of rooms [RM]') plt.ylabel('Price in $1000s [MEDV]') plt.legend(loc='upper left') # plt.savefig('images/10_08.png', dpi=300) plt.show() # In[25]: print('기울기: %.3f' % ransac.estimator_.coef_[0]) print('절편: %.3f' % ransac.estimator_.intercept_) #
#
# # 선형 회귀 모델의 성능 평가 # In[26]: from sklearn.model_selection import train_test_split X = df.iloc[:, :-1].values y = df['MEDV'].values X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=0) # In[27]: slr = LinearRegression() slr.fit(X_train, y_train) y_train_pred = slr.predict(X_train) y_test_pred = slr.predict(X_test) # In[28]: plt.scatter(y_train_pred, y_train_pred - y_train, c='steelblue', marker='o', edgecolor='white', label='Training data') plt.scatter(y_test_pred, y_test_pred - y_test, c='limegreen', marker='s', edgecolor='white', label='Test data') plt.xlabel('Predicted values') plt.ylabel('Residuals') plt.legend(loc='upper left') plt.hlines(y=0, xmin=-10, xmax=50, color='black', lw=2) plt.xlim([-10, 50]) plt.tight_layout() # plt.savefig('images/10_09.png', dpi=300) plt.show() # In[29]: from sklearn.metrics import r2_score from sklearn.metrics import mean_squared_error print('훈련 MSE: %.3f, 테스트 MSE: %.3f' % ( mean_squared_error(y_train, y_train_pred), mean_squared_error(y_test, y_test_pred))) print('훈련 R^2: %.3f, 테스트 R^2: %.3f' % ( r2_score(y_train, y_train_pred), r2_score(y_test, y_test_pred))) #
#
# # 회귀에 규제 적용 # In[30]: from sklearn.linear_model import Lasso lasso = Lasso(alpha=0.1) lasso.fit(X_train, y_train) y_train_pred = lasso.predict(X_train) y_test_pred = lasso.predict(X_test) print(lasso.coef_) # In[31]: print('훈련 MSE: %.3f, 테스트 MSE: %.3f' % ( mean_squared_error(y_train, y_train_pred), mean_squared_error(y_test, y_test_pred))) print('훈련 R^2: %.3f, 테스트 R^2: %.3f' % ( r2_score(y_train, y_train_pred), r2_score(y_test, y_test_pred))) # 릿지 회귀: # In[32]: from sklearn.linear_model import Ridge ridge = Ridge(alpha=1.0) # 리쏘 회귀: # In[33]: from sklearn.linear_model import Lasso lasso = Lasso(alpha=1.0) # 엘라스틱 넷 회귀: # In[34]: from sklearn.linear_model import ElasticNet elanet = ElasticNet(alpha=1.0, l1_ratio=0.5) #
#
# # 선형 회귀 모델을 다항 회귀로 변환 # In[35]: X = np.array([258.0, 270.0, 294.0, 320.0, 342.0, 368.0, 396.0, 446.0, 480.0, 586.0])\ [:, np.newaxis] y = np.array([236.4, 234.4, 252.8, 298.6, 314.2, 342.2, 360.8, 368.0, 391.2, 390.8]) # In[36]: from sklearn.preprocessing import PolynomialFeatures lr = LinearRegression() pr = LinearRegression() quadratic = PolynomialFeatures(degree=2) X_quad = quadratic.fit_transform(X) # In[37]: # 선형 특성 학습 lr.fit(X, y) X_fit = np.arange(250, 600, 10)[:, np.newaxis] y_lin_fit = lr.predict(X_fit) # 이차항 특성 학습 pr.fit(X_quad, y) y_quad_fit = pr.predict(quadratic.fit_transform(X_fit)) # 결과 그래프 plt.scatter(X, y, label='Training points') plt.plot(X_fit, y_lin_fit, label='Linear fit', linestyle='--') plt.plot(X_fit, y_quad_fit, label='Quadratic fit') plt.xlabel('Explanatory variable') plt.ylabel('Predicted or known target values') plt.legend(loc='upper left') plt.tight_layout() # plt.savefig('images/10_11.png', dpi=300) plt.show() # In[38]: y_lin_pred = lr.predict(X) y_quad_pred = pr.predict(X_quad) # In[39]: print('훈련 MSE 비교 - 선형 모델: %.3f, 다항 모델: %.3f' % ( mean_squared_error(y, y_lin_pred), mean_squared_error(y, y_quad_pred))) print('훈련 R^2 비교 - 선형 모델: %.3f, 다항 모델: %.3f' % ( r2_score(y, y_lin_pred), r2_score(y, y_quad_pred))) #
#
# ## 주택 데이터셋을 사용한 비선형 관계 모델링 # In[40]: X = df[['LSTAT']].values y = df['MEDV'].values regr = LinearRegression() # 이차, 삼차 다항식 특성을 만듭니다 quadratic = PolynomialFeatures(degree=2) cubic = PolynomialFeatures(degree=3) X_quad = quadratic.fit_transform(X) X_cubic = cubic.fit_transform(X) # 학습된 모델을 그리기 위해 특성 범위를 만듭니다 X_fit = np.arange(X.min(), X.max(), 1)[:, np.newaxis] regr = regr.fit(X, y) y_lin_fit = regr.predict(X_fit) linear_r2 = r2_score(y, regr.predict(X)) regr = regr.fit(X_quad, y) y_quad_fit = regr.predict(quadratic.fit_transform(X_fit)) quadratic_r2 = r2_score(y, regr.predict(X_quad)) regr = regr.fit(X_cubic, y) y_cubic_fit = regr.predict(cubic.fit_transform(X_fit)) cubic_r2 = r2_score(y, regr.predict(X_cubic)) # 결과 그래프를 그립니다 plt.scatter(X, y, label='Training points', color='lightgray') plt.plot(X_fit, y_lin_fit, label='Linear (d=1), $R^2=%.2f$' % linear_r2, color='blue', lw=2, linestyle=':') plt.plot(X_fit, y_quad_fit, label='Quadratic (d=2), $R^2=%.2f$' % quadratic_r2, color='red', lw=2, linestyle='-') plt.plot(X_fit, y_cubic_fit, label='Cubic (d=3), $R^2=%.2f$' % cubic_r2, color='green', lw=2, linestyle='--') plt.xlabel('% lower status of the population [LSTAT]') plt.ylabel('Price in $1000s [MEDV]') plt.legend(loc='upper right') # plt.savefig('images/10_12.png', dpi=300) plt.show() # 데이터셋을 변환합니다: # In[41]: X = df[['LSTAT']].values y = df['MEDV'].values # 특성을 변환합니다 X_log = np.log(X) y_sqrt = np.sqrt(y) # 학습된 모델을 그리기 위해 특성 범위를 만듭니다 X_fit = np.arange(X_log.min()-1, X_log.max()+1, 1)[:, np.newaxis] regr = regr.fit(X_log, y_sqrt) y_lin_fit = regr.predict(X_fit) linear_r2 = r2_score(y_sqrt, regr.predict(X_log)) # 결과 그래프를 그립니다 plt.scatter(X_log, y_sqrt, label='Training points', color='lightgray') plt.plot(X_fit, y_lin_fit, label='Linear (d=1), $R^2=%.2f$' % linear_r2, color='blue', lw=2) plt.xlabel('log(% lower status of the population [LSTAT])') plt.ylabel('$\sqrt{Price \; in \; \$1000s \; [MEDV]}$') plt.legend(loc='lower left') plt.tight_layout() # plt.savefig('images/10_13.png', dpi=300) plt.show() #
#
# # 랜덤 포레스트를 사용하여 비선형 관계 다루기 # ... # ## 결정 트리 회귀 # In[42]: from sklearn.tree import DecisionTreeRegressor X = df[['LSTAT']].values y = df['MEDV'].values tree = DecisionTreeRegressor(max_depth=3) tree.fit(X, y) sort_idx = X.flatten().argsort() lin_regplot(X[sort_idx], y[sort_idx], tree) plt.xlabel('% lower status of the population [LSTAT]') plt.ylabel('Price in $1000s [MEDV]') # plt.savefig('images/10_14.png', dpi=300) plt.show() #
#
# ## 랜덤 포레스트 회귀 # In[43]: X = df.iloc[:, :-1].values y = df['MEDV'].values X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.4, random_state=1) # In[44]: from sklearn.ensemble import RandomForestRegressor forest = RandomForestRegressor(n_estimators=1000, criterion='squared_error', random_state=1, n_jobs=-1) forest.fit(X_train, y_train) y_train_pred = forest.predict(X_train) y_test_pred = forest.predict(X_test) print('훈련 MSE: %.3f, 테스트 MSE: %.3f' % ( mean_squared_error(y_train, y_train_pred), mean_squared_error(y_test, y_test_pred))) print('훈련 R^2: %.3f, 테스트 R^2: %.3f' % ( r2_score(y_train, y_train_pred), r2_score(y_test, y_test_pred))) # In[45]: plt.scatter(y_train_pred, y_train_pred - y_train, c='steelblue', edgecolor='white', marker='o', s=35, alpha=0.9, label='Training data') plt.scatter(y_test_pred, y_test_pred - y_test, c='limegreen', edgecolor='white', marker='s', s=35, alpha=0.9, label='Test data') plt.xlabel('Predicted values') plt.ylabel('Residuals') plt.legend(loc='upper left') plt.hlines(y=0, xmin=-10, xmax=50, lw=2, color='black') plt.xlim([-10, 50]) plt.tight_layout() # plt.savefig('images/10_15.png', dpi=300) plt.show() #
#