● 선형 모델(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)

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

+ Recent posts