선형 회귀
회귀는 연속적인 타깃을 예측하는 알고리즘입니다. 그 중에 선형 회귀Linear Regression가 가장 기본입니다. 선형 회귀는 훈련 데이터에 가장 잘 들어 맞는 선형 방정식
를 찾는 문제입니다. 여기에서 은 훈련 데이터에 있는 특성의 수입니다.
편의상 를
으로 바꾸어 하나의 벡터
로 나타내겠습니다.
에 포함된
에 대응하기 위해 훈련 데이터에
을 추가하여 벡터
를 정의합니다. 이제 이 선형 방정식은
와 같이 간단히 쓸 수 있습니다. 훈련 샘플이 하나가 아니라 여러개이므로 벡터 를 다음과 같이 행렬로 확장할 수 있습니다. 벡터는 굵은 소문자, 행렬은 굵은 대문자를 사용합니다. 여기에서
은 훈련 샘플의 수입니다.
얼마나 잘 들어 맞는지를 측정 방법으로는 평균 제곱 오차Mean Square Error, MSE를 사용합니다.
이런 측정 함수를 비용 함수cost function이라고 부릅니다. 선형 회귀의 비용 함수인 평균 제곱 오차를 최소화하는 선형 방정식의 를 찾아야 합니다. 해석적인 방법으로 해를 구할 수 있습니다. 비용 함수를 미분하여 도함수가 0이 되는 점을 찾습니다. 먼저 MSE 비용 함수를 간단한 식으로 표현하겠습니다.
은 미분 결과에 영향을 미치지 않으므로 제외하고
에 대해 미분합니다.
이 도함수가 0이 되는 는 다음과 같습니다. 이 식을 정규 방정식Normal Equation이라고 합니다.
샘플 데이터
사이킷런에 포함된 샘플 데이터 중 캘리포니아 주택 가격 데이터셋을 사용하겠습니다.
import sklearn
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.datasets import fetch_california_housing
fetch_california_housing()
함수를 호출하여 사이킷런의 Bunch
클래스 객체를 얻습니다. 캘리포니아 주택가격 데이터셋은 전체 샘플 개수가 20,640개이고 8개의 특성을 가집니다.
housing = fetch_california_housing()
print(housing.data.shape, housing.target.shape)
(20640, 8) (20640,)
train_test_split()
함수를 사용해서 75%는 훈련 세트로 25%는 테스트 세트로 분리합니다. 편의상 그래프로 나타내기 편하도록 하나의 특성만 사용하겠습니다. 사이킷런의 모델은 훈련 데이터가 2차원 배열일 것으로 예상합니다. 따라서 housing.data
에서 하나의 특성만 선택하더라도 2차원 배열이 되도록 넘파이 슬라이싱을 사용했습니다. 여기서는 첫 번째 특성만 사용합니다.
X_train, X_test, y_train, y_test = train_test_split(housing.data[:, 0:1],
housing.target, random_state=42)
print(X_train.shape, X_test.shape, y_train.shape, y_test.shape)
(15480, 1) (5160, 1) (15480,) (5160,)
정규 방정식
앞서 구한 정규 방정식을 이용하여 평균 제곱 오차가 최소가 되는 모델 파라미터 를 구해 보겠습니다.
먼저 훈련 데이터 X_train
에 절편에 해당하는 을 추가합니다.
np.ones()
와 np.hstack()
함수를 사용하면 손쉽게 X_train
의 첫 번째 열에 1로 채워진 벡터를 추가할 수 있습니다.
x0 = np.ones((X_train.shape[0],1))
X = np.hstack((x0, X_train))
print(X.shape)
(15480, 2)
정규 방정식에 나오는 전치 행렬은 넘파이 배열에서 바로 변환이 가능합니다. 역행렬은 넘파이에 있는 선형 대수 모듈 아래 inv()
함수를 사용하여 구할 수 있습니다.
w = np.linalg.inv(X.T.dot(X)).dot(X.T).dot(y_train)
print(w)
[0.44967564 0.41788087]
w
배열의 첫 번째 원소가 절편이고 두 번째 원소가 기울기가 됩니다. 1차원 데이터셋이므로 그래프에 출력하여 학습된 방정식을 그려볼 수 있습니다. 먼저 X_train
과 y_train
으로 산점도를 그리고 학습된 방정식을 나타내기 위해 (0, w[0])
에서 (10, 10*w[1]+w[0])
를 지나는 직선 그래프를 그려 보겠습니다.
plt.scatter(X_train, y_train)
plt.plot([0, 10], [w[0], 10*w[1]+w[0]], 'r')
plt.xlabel('x')
plt.ylabel('y')
plt.show()

LinearRegression
사이킷런의 LinearRegression
클래스는 사이파이SciPy의 lstsq()
함수를 사용하여 선형 회귀 문제를 풉니다. 이 함수는 특잇값 분해(SVD) 방식을 사용하여 유사 역행렬을 계산합니다. 사이킷런의 모델 클래스를 사용할 때는 값을 고려하지 않아도 됩니다. 다음 코드는 훈련 데이터
X_train
과 y_train
을 사용하여 선형 회귀를 수행한 결과 입니다.
lr = LinearRegression()
lr.fit(X_train, y_train)
lr.score(X_test, y_test)
0.47083837938023365
사이킷런의 회귀 모델 클래스들은 RegressorMixin
클래스를 상속합니다. 이 클래스는 결정 계수 점수를 계산하는
score()
메서드를 제공합니다. 결정 계수Coefficient of determination는 -1~1 사이의 값을 가지며 공식은 다음과 같습니다. 여기에서 는 타깃 데이터의 평균입니다.
이 공식을 직접 계산해서 score()
메서드의 결과를 재현해 보죠.
y_pred = lr.predict(X_test)
r2 = 1 - ((y_test - y_pred)**2).sum() / ((y_test - y_test.mean())**2).sum()
print(r2)
0.47083837938023365
LinearRegression
클래스가 구한 모델 파라미터는 가중치와 절편이 coef_
와 intercept_
인스턴스 변수에 따로 저장되어 있습니다. 하나의 특성만 사용했기 때문에 구해진 가중치 배열의 원소가 하나입니다.
print(lr.coef_, lr.intercept_)
[0.41788087] 0.44967564199686194
이 값은 앞서 정규 방정식으로 계산한 값과 거의 동일합니다. 여기에서도 앞에서와 같이 그래프로 모델의 방정식을 그려보죠.
plt.scatter(X_train, y_train)
plt.plot([0, 10], [lr.intercept_, 10 * lr.coef_ + lr.intercept_], 'r')
plt.xlabel('x')
plt.ylabel('y')
plt.show()

입력 데이터의 최소, 최대값 사이가 0을 포함하지 않을 수 있기 때문에 LinearRegression
클래스는 의미있는 절편을 구하기 위해서 훈련 데이터의 평균을 원점에 맞춥니다. fit()
메서드에 입력된 훈련 데이터는 참조에 의한 전달이 됩니다. 원본 데이터를 변경하지 않으려면 훈련 데이터를 복사해서 사용해야 합니다. LinearRegression
클래스의 copy_X
매개변수에서 이를 조정할 수 있으며 기본값은 True
로 훈련 데이터를 복사하여 사용합니다.
fit_intercept
매개변수를 기본값 True
에서 False
로 바꾸면 절편을 계산하지 않습니다. 데이터셋이 원점에 맞추어져 있을 때 사용할 수 있습니다.
lr_no_intercept = LinearRegression(fit_intercept=False)
lr_no_intercept.fit(X_train, y_train)
print(lr_no_intercept.coef_, lr_no_intercept.intercept_)
[0.51131441] 0.0
절편이 0입니다. 앞에서와 마찬가지로 이 직선을 훈련 데이터의 산점도 위에 그려보죠.
plt.scatter(X_train, y_train)
plt.plot([0, 10],
[lr_no_intercept.intercept_, 10 * lr_no_intercept.coef_ +
lr_no_intercept.intercept_], 'r')
plt.xlabel('x')
plt.ylabel('y')
plt.show()

normalize
매개변수를 기본값 False
에서 True
로 바꾸면 각 특성에서 평균을 빼고 L2 노름으로 나누어 정규화합니다. 그 결과 각 특성은 L2 노름이 1이 됩니다. 현재 사이킷런의 구현은 fit_intercept
가 True
일 때만 이 매개변수가 작동됩니다. 이는 버그이며 사이킷런 0.21 버전에서 수정될 것으로 예상됩니다.
복수개의 타깃을 가진 데이터셋을 훈련할 때는 n_jobs
매개변수를 1 이상으로 지정하여 시스템의 CPU 코어를 최대한 활용할 수 있습니다. n_jobs
매개변수의 기본값은 한 개의 코어만을 사용하며 -1로 지정하면 가용한 모든 코어를 사용합니다.
- https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearRegression.html
- https://scikit-learn.org/stable/modules/generated/sklearn.datasets.fetch_california_housing.html
- https://docs.scipy.org/doc/scipy/reference/generated/scipy.linalg.lstsq.html
- https://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.inv.html
- https://www.theanalysisfactor.com/center-on-the-mean/
- https://eli.thegreenplace.net/2014/derivation-of-the-normal-equation-for-linear-regression
이 포스트에 사용한 코드는 http://nbviewer.jupyter.org/github/rickiepark/sklearn-tutorial/blob/master/linear_model/1.LinearRegression.ipynb 에서 볼 수 있습니다.
핑백: [텐서 플로우 블로그 (Tensor ≈ Blog)] [사이킷런 정주행] 1. LinearRegression - DEVBLOG - 개발자 메타블로그
궁금한게 있어서 글을 남깁니다.
housing.data.shape 와 housing.target.shape에서 data와 target이 어떤식으로
쓰이고 어느 라이브러리에서 가져오는지 궁금합니다.
실제 파이썬에 위 코드를 작성해보면, 동작하지를 않네요.
좋아요좋아요
안녕하세요. housing 데이터는 사이킷런에 내장되어 있습니다. fetch_california_housing() 함수가 이 데이터를 읽어 들입니다. 깃허브의 코드를 참고하세요(https://github.com/rickiepark/sklearn-tutorial/blob/master/linear_model/1.LinearRegression.ipynb). 감사합니다.
좋아요좋아요
답변감사합니다.
좋아요Liked by 1명
plt.plot([0, 10],
[lr_no_intercept.intercept_, 10 * lr_no_intercept.coef_ +
lr_no_intercept.intercept_], ‘r’)
여기서 기울기에 10을 왜 곱하는건가요?? 어디서 나온 수치인가용??
좋아요좋아요
10은 임의로 선택한 값입니다. 0~10 사이에서 직선의 그래프를 그린 것입니다.
좋아요좋아요