[머신러닝] 다중 선형 회귀(Multiple Linear Regression)란?

복습

회귀 모델에서 베이스라인 모델의 정의와 이 과정이 중요한 이유

  • 적절한 배치 크기 탐색은 모델 훈련에서 매우 중요한 과업 중 하나이다.
  • 기준이 되는 최소화 기능의 값을 탐색한다.

회귀 분석

  • 일반화를 통해 미래 예측을 추정하고자하는 것이다.

학습/테스트 데이터

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import pandas as pd
df = pd.read_csv('https://ds-lecture-data.s3.ap-northeast-2.amazonaws.com/house-prices/house_prices_train.csv')

df.columns

'''
Index(['Id', 'MSSubClass', 'MSZoning', 'LotFrontage', 'LotArea', 'Street',
       'Alley', 'LotShape', 'LandContour', 'Utilities', 'LotConfig',
       'LandSlope', 'Neighborhood', 'Condition1', 'Condition2', 'BldgType',
       'HouseStyle', 'OverallQual', 'OverallCond', 'YearBuilt', 'YearRemodAdd',
       'RoofStyle', 'RoofMatl', 'Exterior1st', 'Exterior2nd', 'MasVnrType',
       'MasVnrArea', 'ExterQual', 'ExterCond', 'Foundation', 'BsmtQual',
       'BsmtCond', 'BsmtExposure', 'BsmtFinType1', 'BsmtFinSF1',
       'BsmtFinType2', 'BsmtFinSF2', 'BsmtUnfSF', 'TotalBsmtSF', 'Heating',
       'HeatingQC', 'CentralAir', 'Electrical', '1stFlrSF', '2ndFlrSF',
       'LowQualFinSF', 'GrLivArea', 'BsmtFullBath', 'BsmtHalfBath', 'FullBath',
       'HalfBath', 'BedroomAbvGr', 'KitchenAbvGr', 'KitchenQual',
       'TotRmsAbvGrd', 'Functional', 'Fireplaces', 'FireplaceQu', 'GarageType',
       'GarageYrBlt', 'GarageFinish', 'GarageCars', 'GarageArea', 'GarageQual',
       'GarageCond', 'PavedDrive', 'WoodDeckSF', 'OpenPorchSF',
       'EnclosedPorch', '3SsnPorch', 'ScreenPorch', 'PoolArea', 'PoolQC',
       'Fence', 'MiscFeature', 'MiscVal', 'MoSold', 'YrSold', 'SaleType',
       'SaleCondition', 'SalePrice'],
      dtype='object')
'''
  • 모델의 성능을 평가하기 위해 훈련/테스트 데이터로 나눌 것이다.
  • 모델 학습에 사용한 훈련(train) 데이터를 잘 맞추는 모델이 아니라, 학습에 사용하지 않은 테스트(test) 데이터를 얼마나 잘 맞추는지가 중요하다.
  • 데이터를 훈련/테스트 데이터로 나누어야 우리가 만든 모델의 예측 성능을 제대로 평가할 수 있다.
  • 학습에 사용하는 데이터와 모델을 평가하는데 사용하는 데이터가 달라야 한다.
  • 데이터를 무작위로 선택해 나누는 방법이 일반적이지만, 시계열 데이터를 가지고 과거에서 미래를 예측하려고 하는 경우 무작위로 데이터를 섞으면 안된다.
    • 이때는 훈련 데이터 보다 테스트 데이터가 미래의 것이어야 한다.

데이터 스플릿(Split)

  • 데이터프레임의 시간/날짜에 관한 정보를 포함한 특성들이 있다.
  • 하지만 시간 변화에 상관없는 집 값 예측이 목표이기 때문에 무작위로 훈련/테스트 데이터셋으로 나눈다.
1
2
3
4
5
# train/test 데이터를 sample 메소드를 사용해 스플릿
train = df.sample(frac=0.75,random_state=1) # 75%
test = df.drop(train.index)

train.head()

스크린샷 2021-08-10 10 17 35

1
2
3
4
# train, test 길이 비교
len(train), len(test)

# (1095, 365)

다중 선형 회귀 모델

베이스라인 모델

  • SalePrice의 평균을 베이스라인 모델로 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 레이블 정의
target = 'SalePrice'
y_train = train[target] # 학습
y_test = test[target] # 테스트


# 베이스라인 모델 설정: 평균값
pred = y_train.mean()


pred
'''
180327.24200913243
'''


# 베이스라인 모델로 훈련 에러(MAE) 계산
from sklearn.metrics import mean_absolute_error
y_pred = [pred] * len(y_train)
mae = mean_absolute_error(y_train, y_pred)


print(f'훈련 에러: {mae:.2f}')
'''
훈련 에러: 57775.57
'''


# 테스트 에러(MAE)
y_pred = [pred] * len(y_test)
mae = mean_absolute_error(y_test, y_pred)
print(f'테스트 에러: {mae:.2f}')
테스트 에러: 55862.90

단순 선형 회귀 모델

1
2
import seaborn as sns
sns.regplot(x=train['GrLivArea'], y=train['SalePrice']).set_title('Housing Prices');

스크린샷 2021-08-10 10 27 25

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from sklearn.linear_model import LinearRegression

model = LinearRegression()


features = ['GrLivArea']
X_train = train[features]
X_test = test[features]


# 모델 fit
model.fit(X_train, y_train)
y_pred = model.predict(X_train)
mae = mean_absolute_error(y_train, y_pred)
print(f'훈련 에러: {mae:.2f}')
'''
훈련 에러: 38327.78
'''


# 테스트 데이터에 적용
y_pred = model.predict(X_test)
mae = mean_absolute_error(y_test, y_pred)
print(f'테스트 에러: {mae:.2f}')
'''
테스트 에러: 35476.63
'''
  • 베이스라인 모델과 비교해 보면 에러가 줄어든 것을 확인할 수 있다.

다중 선형 회귀 모델 학습(특성 2개 이상)

1
2
3
4
5
6
7
8
9
10
11
12
import numpy as np
import plotly.express as px
import plotly.graph_objs as go
import itertools

px.scatter_3d(
    train,
    x='GrLivArea', 
    y='OverallQual', 
    z='SalePrice',  
    title='House Prices'
)

스크린샷 2021-08-10 10 34 14

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 다중모델 학습을 위한 특성
features = ['GrLivArea', 
            'OverallQual']
X_train = train[features]
X_test = test[features]


# 모델 fit
model.fit(X_train, y_train)
y_pred = model.predict(X_train)
mae = mean_absolute_error(y_train, y_pred)
print(f'훈련 에러: {mae:.2f}')
'''
훈련 에러: 29129.58
'''


# 테스트 데이터에 적용
y_pred = model.predict(X_test)
mae = mean_absolute_error(y_test, y_pred)
print(f'테스트 에러: {mae:.2f}')
'''
테스트 에러: 27598.31
'''
  • 하나의 특성을 사용한 단순 선형 회귀 모델보다 테스트 오류가 더 줄어들었다.

단순 vs 다중 선형 회귀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import numpy as np
import plotly.express as px
import plotly.graph_objs as go
import itertools

def surface_3d(df, f1, f2, target, length=20, **kwargs):
    """
    2특성 1타겟 선형 모델 평면 시각화
    
    df: 데이터프레임
    f1: 특성 1 열 이름
    f2: 특성 2 열 이름
    target: 타겟 열 이름
    length: 각 특성의 관측치 갯수
    
    """
    
    # scatter plot(https://plotly.com/python-api-reference/generated/plotly.express.scatter_3d)
    plot = px.scatter_3d(df, x=f1, y=f2, z=target, opacity=0.5, **kwargs)
    

    # 다중 선형 회귀방정식 학습
    model = LinearRegression()
    model.fit(df[[f1, f2]], df[target])    


    # 좌표축 설정
    x_axis = np.linspace(df[f1].min(), df[f1].max(), length)
    y_axis = np.linspace(df[f2].min(), df[f2].max(), length)
    coords = list(itertools.product(x_axis, y_axis))


    # 예측
    pred = model.predict(coords)
    z_axis = pred.reshape(length, length).T


    # plot 예측평면
    plot.add_trace(go.Surface(x=x_axis, y=y_axis, z=z_axis, colorscale='Viridis'))
    
    return plot


surface_3d(
    train,
    f1='GrLivArea', 
    f2='OverallQual', 
    target='SalePrice',  
    title='House Prices'
)    

스크린샷 2021-08-10 21 20 00

회귀계수 해석/모델 평가

  • 단순 선형 회귀식: $y = \beta_0 + \beta_1 x $
  • 2특성의 다중 선형 회귀식 $y = \beta_0 + \beta_1x_1 + \beta_2x_2$
  • 계수들(coefficients or parameters) $\beta_0$~$\beta_2$를 구하는 방법은 아래와 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 절편(intercept)과 계수들(coefficients)
model.intercept_, model.coef_
'''
(-102743.02342270731, array([   54.40145532, 33059.44199506]))
'''


# 회귀식
b0 = model.intercept_
b1, b2 = model.coef_

print(f'y = {b0:.0f} + {b1:.0f}x\u2081 + {b2:.0f}x\u2082')
'''
y = -102743 + 54x₁ + 33059x₂
'''
  • $\beta_1$과 $\beta_2$ 모두 양수이다.
    • 이것은 $x_1$, $x_2$이 증가할 때마다 $y$ 도 증가한다는 뜻이다.
  • 만약 음수인 경우에는 $y$ 가 감소한다는 뜻이다.
  • 가상의 관측 데이터를 넣어 모델이 어떻게 예측하는지 관찰한다.
1
2
3
4
5
6
7
8
9
10
model.predict([[2000, 10]])
'''
array([336654.30716253])
'''


model.predict([[2000, 3]])
'''
array([105238.21319714])
'''
  • 선형회귀는 다른 머신러닝 모델에 비해 상대적으로 학습이 빠르고 설명력이 강하다.
  • 하지만 선형 모델이므로 과소적합(underfitting)이 잘 일어난다는 단점이 있다.

회귀모델을 평가하는 평가지표들(evaluation metrics)

  • MSE (Mean Squared Error) = $\frac{1}{n}\sum_{i=1}^{n}(y_{i} - \hat{y_{i}})^{2}$
  • MAE (Mean absolute error) = $\frac{1}{n}\sum_{i=1}^{n}\left \vert y_{i} - \hat{y_{i}} \right \vert$
  • RMSE (Root Mean Squared Error) = $\sqrt{MSE}$
  • R-squared (Coefficient of determination) = $1 - \frac{\sum_{i=1}^{n}(y_{i} - \hat{y_{i}})^{2}}{\sum_{i=1}^{n}(y_{i} - \bar{y_{i}})^{2}} = 1 - \frac{SSE}{SST} = \frac {SSR}{SST}$
  • 참고
    • SSE(Sum of Squares Error, 관측치와 예측치 차이): $\sum_{i=1}^{n}(y_{i} - \hat{y_{i}})^{2}$
    • SSR(Sum of Squares due to Regression, 예측치와 평균 차이): $\sum_{i=1}^{n}(\hat{y_{i}} - \bar{y_{i}})^{2}$
    • SST(Sum of Squares Total, 관측치와 평균 차이): $\sum_{i=1}^{n}(y_{i} - \bar{y_{i}})^{2}$ , SSE + SSR
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 훈련
model.fit(X_train, y_train)
y_pred = model.predict(X_train)
hMAE = mean_absolute_error(y_train, y_pred)
hMSE = mean_squared_error(y_train, y_pred)
hRMSE = np.sqrt(hMSE)
hR2 = r2_score(y_train, y_pred)
print(f'훈련 MAE: {hMAE}\n훈련 MSE: {hMSE}\n훈련 RMSE: {hRMSE}\n훈련 R2: {hR2}')


# 테스트
y_pred = model.predict(X_test)
mae = mean_absolute_error(y_test, y_pred)
tMAE =  mean_absolute_error(y_test, y_pred)
tMSE = mean_squared_error(y_test, y_pred)
tRMSE = np.sqrt(tMSE)
tR2 = r2_score(y_test, y_pred)
print(f'테스트 MAE: {tMAE}\n테스트 MSE: {tMSE}\n테스트 RMSE: {tRMSE}\n테스트 R2: {tR2}')
'''
훈련 MAE: 170777.34212565765
훈련 MSE: 67099053309.953606
훈련 RMSE: 259034.84960513248
훈련 R2: 0.5076085988757708

테스트 MAE: 179252.52593261775
테스트 MSE: 71083994178.75656
테스트 RMSE: 266615.81757044455
테스트 R2: 0.45999300199894533
'''

과적합(Overfitting)과 과소적합(Underfitting)

  • 테스트 데이터에서 만들어내는 오차를 일반화 오차라고 부른다.
  • 훈련 데이터에서와 같이 테스트 데이터에서도 좋은 성능을 내는 모델은 일반화가 잘 된 모델이라고 부른다.
  • 모델이 너무 훈련 데이터에 과하게 학습(과적합)을 하지 않도록 하는 많은 일반화 방법들이 있다.
  • 당연히 예측 모델이 훈련 데이터에서보다 테스트 데이터에서 오차가 적게 나오기를 기대하지만 현실적으로 모든 데이터를 얻을 수 없기 때문에 훈련 데이터로부터 일반화가 잘 되는 모델을 학습시켜야 한다.
    • 과적합: 훈련 데이터에만 특수한 성질을 과하게 학습해 일반화를 못해 결국 테스트 데이터에서 오차가 커지는 현상이다.
    • 과소적합: 훈련 데이터에 과적합도 못하고 일반화 성질도 학습하지 못해, 훈련/테스트 데이터 모두에서 오차가 크게 나오는 경우이다.
  • 머신러닝 과정 중에서 과적합은 피할 수 없는 문제이고 완전히 극복할 수도 없다.
  • 그래서 대부분 학습 알고리즘은 이런 과적합을 완화시킬 수 있는 방법을 제공한다.
  • 다중 선형 회귀에서는 $R^2$값으로 과적합 여부를 판단할 수 있다.

분산/편향 트레이드오프

  • 과/소적합은 오차의 편향(Bias)과 분산(Variance)개념과 관계가 있다.
    • 분산이 높은경우는, 모델이 학습 데이터의 노이즈에 민감하게 적합하여 테스트 데이터에서 일반화를 잘 못하는 경우 즉 과적합 상태이다.
    • 편향이 높은경우는, 모델이 학습 데이터에서, 특성과 타겟 변수의 관계를 잘 파악하지 못해 과소적합 상태이다.
\[{\displaystyle \operatorname {E} _{D}{\Big [}{\big (}y-{\hat {f}}(x;D){\big )}^{2}{\Big ]}={\Big (}\operatorname {Bias} _{D}{\big [}{\hat {f}}(x;D){\big ]}{\Big )}^{2}+\operatorname {Var} _{D}{\big [}{\hat {f}}(x;D){\big ]}+\sigma ^{2}}\] \[{\displaystyle \operatorname {Bias} _{D}{\big [}{\hat {f}}(x;D){\big ]}=\operatorname {E} _{D}{\big [}{\hat {f}}(x;D){\big ]}-f(x)}\] \[{\displaystyle \operatorname {Var} _{D}{\big [}{\hat {f}}(x;D){\big ]}=\operatorname {E} _{D}[{\big (}\operatorname {E} _{D}[{\hat {f}}(x;D)]-{\hat {f}}(x;D){\big )}^{2}]}\]

예) 독립 변수와 종속 변수가 비선형 관계인 모델로 학습을 해야하는 데이터

  • 단순 선형 모델로 학습하는 경우와 데이터 포인터를 모두 지나가도록 곡선 피팅이 가능한 다항모델로 학습을 진행한다고 가정한다.
    • 선형 모델 예측은 학습 데이터에서 타겟값과 오차가 크다. 이를 “편향이 높다”고 한다.(과소적합)
    • 하지만 훈련/테스트 두 데이터에서 그 오차가 비슷하다. 이를 “분산이 낮다”고 한다.(오차는 여전히 많지만)

스크린샷 2021-08-10 14 03 25

  • 곡선을 피팅한 모델에서는, 학습 데이터에서 오차가 0에 가까우나(“낮은 편향”), 테스트 데이터에서 오차가 많아진다.
  • 이렇게 한 데이터 세트에서는 오차가 적은데 다른 데이터 세트에서는 오차가 많이 늘어나는 현상(데이터 세트의 종류에 따라 예측값 분산이 높을 때)을 과적합이라고 하며 “분산이 높다”라고 한다.

스크린샷 2021-08-10 14 15 55

스크린샷 2021-08-10 14 16 13

  • 만들기 어렵지만, 편향도 적고 분산도 적은 모델이 좋은 모델이다.
  • 어떤 모델을 학습시키든 훈련/테스트 데이터에서의 모델의 성능과 그 차이를 보고 과적합과 과소적합을 적절하게 구분해 낼 수 있는 것이 중요하다.

스크린샷 2021-08-10 14 17 31

스크린샷 2021-08-10 14 17 23

  • 모델의 복잡성에 따라 성능 그래프를 그려보면, 모델이 복잡해질수록 훈련 데이터 성능은 계속 증가하는데 검증 데이터 성능은 어느정도 증가하다가 증가세가 멈추고 오히려 낮아지는 지점을 찾을 수 있다.
  • 우리는 보통 이 시점을 과적합이 일어나는 시점으로 파악하고 더 복잡한 모델은 불필요함을 알게 된다.
  • 앞으로 더 많은 피쳐를 사용하고 여러 모델들을 배우고 사용하게 될 텐데 과적합/과소적합에 대한 내용은 계속 숙지해야한다.

다항 회귀 모델 이용 과적합 테스트

  • 독립 변수와 타겟 변수 사이에 비선형 관계를 학습할 수 있는 다항 회귀 모델(polynomial regression)의 차수(degrees)를 조정해 회귀곡선을 만들어보는 테스트를 진행한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 실험에 사용할 랜덤 데이터를 생성(30, 2)
rng = np.random.RandomState(1)
data = np.dot(rng.rand(2, 2), rng.randn(2, 30)).T
X = pd.DataFrame([i[0] for i in data])
y = pd.DataFrame([i[1] for i in data])


from sklearn.model_selection import train_test_split

## X_train, X_test, y_train, y_test
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=1)


from sklearn.preprocessing import PolynomialFeatures
X1 = np.arange(6).reshape(3, 2)
print(X1)


poly = PolynomialFeatures(2)
X_poly = poly.fit_transform(X1)
# poly = PolynomialFeatures(interaction_only=True)
# poly.fit_transforim(X)
'''
[[0 1]
 [2 3]
 [4 5]]
'''
  • 사이킷런(Sklearn)의 PolynomialFeatures는 다항 회귀 모델을 쉽게 구현하도록 도와준다.
  • 이름에서 알 수 있듯이 다항 특성(polynomial features)을 방정식에 추가하는 것이다.
  • 다항 특성은 특성들의 상호 작용을 보여줄 수 있기 때문에 상호 작용 특성(interaction features)이라고도 부른다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
## X_poly: [1, a, b, a^2, ab, b^2]
X_poly
'''
array([[ 1.,  0.,  1.,  0.,  0.,  1.],
       [ 1.,  2.,  3.,  4.,  6.,  9.],
       [ 1.,  4.,  5., 16., 20., 25.]])
'''

from IPython.display import display, HTML
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import PolynomialFeatures
import matplotlib.pyplot as plt


plt.rcParams["figure.figsize"] = (5,5)


# 다항 회귀 모델도 결국 다중 선형 회귀 모델로 변형하여 모델을 만들 수 있는 선형모델이다.
def PolynomialRegression(degree=2, **kwargs):
    return make_pipeline(PolynomialFeatures(degree), 
                         LinearRegression(**kwargs))


polynomial_degrees = [1, 3, 4, 6, 10, 20]
train_r2s = []
test_r2s = []


for degree in polynomial_degrees:
    model = PolynomialRegression(degree)
    print(f'Degree={degree}')
    
    model.fit(X_train, y_train)
    train_r2 = model.score(X_train, y_train)
    test_r2 = model.score(X_test, y_test)
    display(HTML(f'<b style="color: blue">train R2 {train_r2:.2f}</b>'))
    display(HTML(f'<b style="color: red">test R2 {test_r2:.2f}</b>'))

    plt.scatter(X_train, y_train, color='blue', alpha=0.5)
    plt.scatter(X_test, y_test, color='red', alpha=0.5)
    

    x_domain = np.linspace(X.min(), X.max())
    curve = model.predict(x_domain)
    plt.plot(x_domain, curve, color='blue')
    plt.axis([-2., 2.0, -0.5, 0.5])
    plt.show()
    display(HTML('<hr/>'))
    
    train_r2s.append(train_r2)
    test_r2s.append(test_r2)

스크린샷 2021-08-10 14 23 29

스크린샷 2021-08-10 14 24 06

스크린샷 2021-08-10 14 24 23

  • 모델 복잡도가(차수) 올라갈 수록 과적합 되어 훈련 $R^2$ 값이 좋아지지만 테스트 $R^2$ 값은 줄어드는 것을 확인 할 수 있다.

참조

plotly

훈련/테스트 세트 분리

Bias-Variance Tradeoff

Regression

0%