4.2 선형회귀분석의 기초#

회귀분석은 독립변수 \(x\)에 대응하는 종속변수 \(y\)와 가장 비슷한 값 \(\hat{y}\)를 출력하는 함수 \(f(x)\)를 찾는 과정이다.

\[ \hat{y} = f \left( x \right) \approx y \]

만약 \(f(x)\)가 다음과 같은 선형함수면 이 함수를 **선형회귀모형(linear regression model)**이라고 한다. 선형회귀모형을 사용하는 회귀분석은 선형회귀분석이라고 한다.

\[ \hat{y} = w_0 + w_1 x_1 + w_2 x_2 + \cdots + w_D x_D = w_0 + w^Tx \]

위 식에서 독립변수 \(x=(x_1, x_2, \ldots, x_D)\)\(D\)차원 벡터다. 가중치 벡터 \(w=(w_0, \cdots, w_D)\)는 함수 \(f(x)\)의 계수(coefficient)이자 이 선형회귀모형의 **모수(parameter)**라고 한다.

상수항 결합#

회귀분석모형 수식을 간단하게 만들기 위해 다음과 같이 상수항을 독립변수 데이터에 추가하는 것을 상수항 결합(bias augmentation)작업이라고 한다.

\[\begin{split} x_i = \begin{bmatrix} x_{i1} \\ x_{i2} \\ \vdots \\ x_{iD} \end{bmatrix} \rightarrow x_{i,a} = \begin{bmatrix} 1 \\ x_{i1} \\ x_{i2} \\ \vdots \\ x_{iD} \end{bmatrix} \end{split}\]

상수항 결합을 하게 되면 모든 원소가 1인 벡터가 입력 데이터 행렬에 추가된다.

\[\begin{split} X = \begin{bmatrix} x_{11} & x_{12} & \cdots & x_{1D} \\ x_{21} & x_{22} & \cdots & x_{2D} \\ \vdots & \vdots & \vdots & \vdots \\ x_{N1} & x_{N2} & \cdots & x_{ND} \\ \end{bmatrix} \rightarrow X_a = \begin{bmatrix} 1 & x_{11} & x_{12} & \cdots & x_{1D} \\ 1 & x_{21} & x_{22} & \cdots & x_{2D} \\ \vdots & \vdots & \vdots & \vdots & \vdots \\ 1 & x_{N1} & x_{N2} & \cdots & x_{ND} \\ \end{bmatrix} \end{split}\]

이렇게 되면 전체 수식이 다음과 같이 상수항이 추가된 가중치 벡터 \(w\)와 상수항이 추가된 입력 데이터 벡터 \(x\)의 내적으로 간단히 표시된다.

\[\begin{split} f(x) = w_0 + w_1 x_1 + w_2 x_2 + \cdots + w_D x_D = \begin{bmatrix} 1 & x_1 & x_2 & \cdots & x_D \end{bmatrix} \begin{bmatrix} w_0 \\ w_1 \\ w_2 \\ \vdots \\ w_D \end{bmatrix} = x_a^T w_a = w_a^T x_a \end{split}\]

일반적으로 선형회귀모형은 항상 상수항 결합을 하기 때문에 특별히 벡터 기호를 \(x_a\) 또는 \(w_a\)라고 표시하지 않아도 상수항 결합이 되어있는 것으로 볼 수 있다.

statsmodels 패키지는 상수항 결합을 위한 add_constant 함수를 제공한다.

X0 = np.arange(10).reshape(5, 2)
X0
array([[0, 1],
       [2, 3],
       [4, 5],
       [6, 7],
       [8, 9]])
import statsmodels.api as sm

X = sm.add_constant(X0)
X
array([[1., 0., 1.],
       [1., 2., 3.],
       [1., 4., 5.],
       [1., 6., 7.],
       [1., 8., 9.]])

최소자승법#

최소자승법(OLS: Ordinary Least Squares)는 잔차제곱합(RSS: Residual Sum of Squares)를 최소화하는 가중치 벡터를 구하는 방법이다.

우리가 사용하는 예측 모형은 다음과 같이 상수항이 결합된 선형모형이다.

\[ \hat{y} = Xw \]

이때 잔차 벡터(residual vector) \(e\)

\[ e = {y} - \hat{y} = y - Xw \]

이고 잔차 제곱합(RSS:residual sum of squares)은

\[\begin{split} \begin{aligned} \text{RSS} &= e^Te \\ &= (y - Xw)^T(y - Xw) \\ &= y^Ty - 2y^T X w + w^TX^TXw \end{aligned} \end{split}\]

이다. 잔차의 크기(잔차 제곱합)를 가장 작게 하는 가중치 벡터를 구하기 위해 이 식을 미분하여 잔차 제곱합의 그레디언트(gradient) 벡터를 구하면 다음과 같다.

\[ \dfrac{d \text{RSS}}{d w} = -2 X^T y + 2 X^TX w \]

잔차가 최소가 되는 최적화 조건은 그레디언트 벡터가 0벡터이어야 하므로 다음 식이 성립한다.

\[ \dfrac{d \text{RSS}}{d w} = 0 \]
\[ X^TX w^{\ast} = X^T y \]

만약 \(X^TX\) 행렬의 역행렬이 존재한다면 다음처럼 최적 가중치 벡터 \(w^{\ast}\)를 구할 수 있다.

\[ w^{\ast} = (X^TX)^{-1} X^T y \]

\(X^TX\) 행렬의 역행렬이 존재하고 위에서 구한 값이 최저값이 되려면 잔차 제곱합의 헤시안 행렬인 \(X^TX\)가 양의 정부호(positive definite)이어야 한다.

\[ \frac{d^2 \text{RSS}}{dw^2} = 2X^TX > 0 \]

\(X\)의 각 행렬이 서로 독립(\(X\)가 풀랭크)이 아니면 \(X^TX\)가 양의 정부호가 아니고 역행렬이 존재하지 않으므로 위와 같은 해를 구할 수 없다.

직교 방정식#

여기에서 그레디언트가 0벡터가 되는 관계를 나타내는 다음 식을 **직교 방정식(normal equation)**이라고 한다.

\[ X^T y - X^TX w = 0 \]
\[ X^T (y - X w ) = 0 \]
\[ X^T e = 0 \]

즉, \(c_d\)가 모든 데이터의 \(d\)번째 차원의 원소로 이루어진 데이터 벡터(특징 행렬의 열벡터)라고 할 때 모든 차원 \(d \; (d=0, \ldots, D)\)에 대해 \(c_d\)는 잔차 벡터 \(e\)와 직교다.

\[ c_d^T e = 0 \;\;\; (d=0, \ldots, D) \]

또는

\[ c_d \perp e \;\;\; (d=0, \ldots, D) \]

직교 방정식으로부터 다음과 같은 성질을 알 수 있다.

(1) 모형에 상수항이 있는 경우에 잔차 벡터의 원소의 합은 0이다. 즉, 잔차의 평균은 0이다.

\[ \sum_{i=0}^N e_i = 0 \]

(2) \(x\) 데이터의 평균값 \(\bar{x}\)에 대한 예측값은 \(y\) 데이터의 평균값 \(\bar{y}\)이다.

\[ \bar{y} = w^T \bar{x} \]

1번 성질은 상수항 결합이 되어 있으면 \(X\)의 첫번째 열이 1-벡터라는 것을 이용하여 증명할 수 있다.

\[ c_0^T e = \mathbf{1}^T e = \sum_{i=0}^N e_i = 0 \]

2번 성질은 다음처럼 증명한다.

\[\begin{split} \begin{aligned} \bar{y} &= \dfrac{1}{N}\mathbf{1}^T y \\ &= \dfrac{1}{N}\mathbf{1}^T (Xw + e) \\ &= \dfrac{1}{N}\mathbf{1}^TXw + \dfrac{1}{N}\mathbf{1}^Te \\ &= \dfrac{1}{N}\mathbf{1}^TXw \\ &= \dfrac{1}{N}\mathbf{1}^T \begin{bmatrix}c_1 & \cdots & c_M \end{bmatrix} w \\ &= \begin{bmatrix}\dfrac{1}{N}\mathbf{1}^Tc_1 & \cdots & \dfrac{1}{N}\mathbf{1}^Tc_D \end{bmatrix} w \\ &= \begin{bmatrix}\bar{c}_1 & \cdots & \bar{c}_D \end{bmatrix} w \\ &= \bar{x}^T w \\ \end{aligned} \end{split}\]

NumPy를 이용한 선형 회귀분석#

이제 NumPy의 선형대수 기능을 사용하여 OLS 방법으로 선형 회귀분석을 해보자. 우선 make_regression 명령을 사용하여 다음과 같이 1차원 특징 데이터 x와 이 값에 의존하는 y를 만든다.

from sklearn.datasets import make_regression

bias = 100
X0, y, w = make_regression(
    n_samples=200, n_features=1, bias=bias, noise=10, coef=True, random_state=1
)
X = sm.add_constant(X0)
y = y.reshape(len(y), 1)

우리가 준 바이어스 값은 100이고 make_regression 명령이 생성한 모수 값은 다음과 같다.

w
array(86.44794301)

따라서 x와 y는 다음과 같은 관계를 가진다.

\[ y = 100 + 86.44794301 x + \epsilon \]

위에서 구한 수식을 이용하여 선형회귀 계수를 추정하면 다음과 같다.

# OLS 해를 직접 이용하는 방법
w = np.linalg.inv(X.T @ X) @ X.T @ y
w
array([[99.79150869],
       [86.96171201]])

즉, 최소자승법으로 구한 선형회귀모형은 다음과 같다.

\[ \hat{y} = 99.79150869 + 86.96171201 x \]

이 결과에서 알 수 있는 것은 선형 회귀를 통해 구한 가중치 벡터는 정답과 비슷하지만 똑같지는 않다는 점이다.

이 식에 여러가지 \(x\)값을 대입하여 \(\hat{y}\)을 구해본 결과를 원래 데이터와 비교하면 다음 그림과 같다.

x_new = np.linspace(np.min(X0), np.max(X0), 10)
X_new = sm.add_constant(x_new)  # 상수항 결합
y_new = np.dot(X_new, w)

plt.scatter(X0, y, label="원래 데이터")
plt.plot(x_new, y_new, 'rs-', label="회귀분석 예측")
plt.xlabel("x")
plt.ylabel("y")
plt.title("선형 회귀분석의 예")
plt.legend()
plt.show()
../_images/26bf3d6ebf56f9340024dc4245bd3f320939448413e0ce5663465104e4e2b569.png

scikit-learn 패키지를 사용한 선형 회귀분석#

scikit-learn 패키지를 사용하여 선형 회귀분석을 하는 경우에는 linear_model 서브 패키지의 LinearRegression 클래스를 사용한다. 사용법은 다음과 같다.

(1) LinearRegression 클래스 객체를 생성한다.

model = LinearRegression(fit_intercept=True)

fit_intercept 인수는 모형에 상수항이 있는가 없는가를 결정하는 인수이다. 디폴트 값이 True다. 만약 상수항이 없으면 fit_intercept=False로 설정한다.

(2) fit 메서드로 가중치 값을 추정한다. 상수항 결합을 자동으로 해주므로 사용자가 직접 add_constant 등의 명령를 써서 상수항 결합을 할 필요는 없다.

model = model.fit(X, y)

fit 메서드를 호출하면 모형 객체는 다음과 같은 속성을 가지게 된다. 또한 fit 메서드는 객체 자신을 반환한다.

  • coef_ : 추정된 가중치 벡터

  • intercept_ : 추정된 상수항

(3) predict 메서드로 새로운 입력 데이터에 대한 출력 데이터 예측

y_new = model.predict(x_new)

위 예제를 LinearRegression 클래스로 선형회귀를 하면 다음과 같다.

from sklearn.linear_model import LinearRegression

model = LinearRegression().fit(X0, y)
print(model.intercept_, model.coef_)
[99.79150869] [[86.96171201]]

predict 메서드를 사용하면 새로운 \(x_{new}\) 값에 대응하는 \(y\) 값을 예측할 수 있다. \(x_{new}\) 값으로 2차원 배열을 써야한다는 점을 주의한다.

model.predict([[-2], [-1], [0], [1], [2]])
array([[-74.13191534],
       [ 12.82979668],
       [ 99.79150869],
       [186.7532207 ],
       [273.71493272]])

statsmodels 패키지를 사용한 선형 회귀분석#

statsmodels 패키지에서는 OLS 클래스를 사용하여 선형 회귀분석을 실시한다. OLS 클래스 사용법은 다음과 같다.

  1. 독립변수와 종속변수가 모두 포함된 데이터프레임 생성. 상수항 결함은 하지 않아도 된다.

  2. OLS 클래스 객체 생성. 이 때 from_formula 메서드의 인수로 종속변수와 독립변수를 지정하는 formula 문자열을 넣는다. data 인수로는 독립변수와 종속변수가 모두 포함된 데이터프레임을 넣는다.

    model = OLS.from_formula(formula, data=df)
    

    또는 독립변수만 있는 데이터프레임 dfX와 종속변수만 있는 데이터프레임 dfy를 인수로 넣어서 만들 수도 있다. 이 때는 독립변수만 있는 데이터프레임 dfX가 상수항을 가지고 있어야 한다.

    model = OLS(dfy, dfX)
    
  3. fit 메서드로 모형 추정. scikit-learn 패키지와 달리 추정 결과는 별도의 RegressionResults 클래스 객체로 출력된다.

    result = model.fit()
    
  4. RegressionResults 클래스 객체는 결과 리포트용 summary 메서드와 예측을 위한 prediction 메서드를 제공한다.

    print(result.summary())
    
    y_new = result.predict(x_new)
    

    이 때, 예측을 위한 데이터는 추정시와 동일하게 상수항 결합을 해 주어야 한다.

위 1차원 데이터 예제를 statsmodels의 OLS 명령으로 선형회귀를 하면 다음과 같다. 우선 독립변수와 종속변수가 모두 포함된 데이터프레임 생성. 상수항 결함은 하지 않아도 된다.

df = pd.DataFrame({"x": X0[:, 0], "y": y[:, 0]})
df
x y
0 0.232495 127.879017
1 -0.038696 93.032914
2 0.550537 161.857508
3 0.503185 141.692050
4 2.186980 283.260119
... ... ...
195 -0.172428 87.874277
196 -1.199268 -13.626664
197 1.462108 216.106619
198 1.131629 212.743149
199 0.495211 150.017589

200 rows × 2 columns

다음으로 모델 객체를 만든다. 독립변수만 있는 데이터프레임 dfX와 종속변수만 있는 데이터프레임 dfy를 인수로 넣어서 만들 수도 있다. 이 때는 수동으로 상수항 추가를 해주어야 한다.

dfy = df[["y"]]
dfX = sm.add_constant(df[["x"]])
model = sm.OLS(dfy, dfX)
result = model.fit()

또는 formula 문자열을 사용하여 모형을 만들 수도 있다. formula 문자열을 만드는 방법은 ~ 기호의 왼쪽에 종속변수의 이름을 넣고 ~ 기호의 오른쪽에 독립변수의 이름을 넣는다. 만약 독립변수가 여러개일 경우에는 patsy 패키지의 formula 문자열을 만드는 법을 따른다.

model = sm.OLS.from_formula("y ~ x", data=df)
result = model.fit()

RegressionResults 클래스 객체의 summary 메서드는 복잡한 형태의 보고서를 보여준다. 보고서의 자세한 내용에 대해서는 확률적 회귀모형에서 추후 설명한다. 여기에서는 coef 열의 값이 가중치값이라는 것만 알면 된다.

print(result.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:                      y   R-squared:                       0.985
Model:                            OLS   Adj. R-squared:                  0.985
Method:                 Least Squares   F-statistic:                 1.278e+04
Date:                Mon, 18 Nov 2019   Prob (F-statistic):          8.17e-182
Time:                        21:54:22   Log-Likelihood:                -741.28
No. Observations:                 200   AIC:                             1487.
Df Residuals:                     198   BIC:                             1493.
Df Model:                           1                                         
Covariance Type:            nonrobust                                         
==============================================================================
                 coef    std err          t      P>|t|      [0.025      0.975]
------------------------------------------------------------------------------
Intercept     99.7915      0.705    141.592      0.000      98.402     101.181
x             86.9617      0.769    113.058      0.000      85.445      88.479
==============================================================================
Omnibus:                        1.418   Durbin-Watson:                   1.690
Prob(Omnibus):                  0.492   Jarque-Bera (JB):                1.059
Skew:                           0.121   Prob(JB):                        0.589
Kurtosis:                       3.262   Cond. No.                         1.16
==============================================================================

Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

RegressionResults 클래스 객체의 predict 메서드를 사용하면 새로운 \(x_{new}\) 값에 대응하는 \(y\) 값을 예측할 수 있다.

result.predict({"x": [-2, -1, 0, 1, 2] })
0    -74.131915
1     12.829797
2     99.791509
3    186.753221
4    273.714933
dtype: float64

RegressionResults 클래스는 분석 결과를 다양한 속성에 저장해주므로 추후 사용자가 선택하여 활용할 수 있다. 자주 사용되는 속성으로는 다음과 같은 것들이 있다.

  • params: 가중치 벡터

  • resid: 잔차 벡터

가중치 벡터의 값은 다음처럼 확인한다.

result.params
Intercept    99.791509
x            86.961712
dtype: float64

잔차 벡터의 형태는 다음과 같다.

result.resid.plot(style="o")
plt.title("잔차 벡터")
plt.xlabel("데이터 번호")
plt.ylabel("잔차")
plt.show()
../_images/371025913562745ff813c31d88b3f25153ddfe9c82217b931e87aa31027faa1c.png

직교방정식에서 나온 두 가지 성질이 성립하는지 살펴보자. 우선 잔차의 합을 구하면 0이라는 것을 알 수 있다.

result.resid.sum()
3.7339020764193265e-12

다음으로 x의 평균값을 넣으면 y의 평균값과 같은 값이 나온다는 것도 확인할 수 있다.

result.predict({"x": X0.mean()})
0    109.069351
dtype: float64
y.mean()
109.06935068170775

보스턴 집값 예측#

보스턴 집값 데이터를 statsmodels의 OLS 명령으로 분석한 결과는 다음과 같다.

from sklearn.datasets import load_boston

boston = load_boston()

dfX0 = pd.DataFrame(boston.data, columns=boston.feature_names)
dfX = sm.add_constant(dfX0)
dfy = pd.DataFrame(boston.target, columns=["MEDV"])

model_boston2 = sm.OLS(dfy, dfX)
result_boston2 = model_boston2.fit()
print(result_boston2.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:                   MEDV   R-squared:                       0.741
Model:                            OLS   Adj. R-squared:                  0.734
Method:                 Least Squares   F-statistic:                     108.1
Date:                Mon, 18 Nov 2019   Prob (F-statistic):          6.72e-135
Time:                        21:54:23   Log-Likelihood:                -1498.8
No. Observations:                 506   AIC:                             3026.
Df Residuals:                     492   BIC:                             3085.
Df Model:                          13                                         
Covariance Type:            nonrobust                                         
==============================================================================
                 coef    std err          t      P>|t|      [0.025      0.975]
------------------------------------------------------------------------------
const         36.4595      5.103      7.144      0.000      26.432      46.487
CRIM          -0.1080      0.033     -3.287      0.001      -0.173      -0.043
ZN             0.0464      0.014      3.382      0.001       0.019       0.073
INDUS          0.0206      0.061      0.334      0.738      -0.100       0.141
CHAS           2.6867      0.862      3.118      0.002       0.994       4.380
NOX          -17.7666      3.820     -4.651      0.000     -25.272     -10.262
RM             3.8099      0.418      9.116      0.000       2.989       4.631
AGE            0.0007      0.013      0.052      0.958      -0.025       0.027
DIS           -1.4756      0.199     -7.398      0.000      -1.867      -1.084
RAD            0.3060      0.066      4.613      0.000       0.176       0.436
TAX           -0.0123      0.004     -3.280      0.001      -0.020      -0.005
PTRATIO       -0.9527      0.131     -7.283      0.000      -1.210      -0.696
B              0.0093      0.003      3.467      0.001       0.004       0.015
LSTAT         -0.5248      0.051    -10.347      0.000      -0.624      -0.425
==============================================================================
Omnibus:                      178.041   Durbin-Watson:                   1.078
Prob(Omnibus):                  0.000   Jarque-Bera (JB):              783.126
Skew:                           1.521   Prob(JB):                    8.84e-171
Kurtosis:                       8.281   Cond. No.                     1.51e+04
==============================================================================

Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 1.51e+04. This might indicate that there are
strong multicollinearity or other numerical problems.

따라서 보스턴 집값을 예측하는 식은 다음과 같다.

\[\begin{split} \begin{aligned} y &= 36.4595 - 0.1080\,\text{CRIM} + 0.0464\,\text{ZN} + 0.0206\,\text{INDUS} + 2.6867 \,\text{CHAS} \\ & -17.7666\,\text{NOX} + 3.8099\,\text{RM} + 0.0007\,\text{AGE} -1.4756\,\text{DIS} + 0.3060\,\text{RAD} \\ & -0.0123\,\text{TAX} -0.9527\,\text{PTRATIO} + 0.0093 \,\text{B} -0.5248\,\text{LSTAT} \end{aligned} \end{split}\]