01. 회귀 소개
회귀(Regression)는 여러 개의 독립변수와 한 개의 종속 변수 간의 상관관계를 모델링하는 기법.
Y = W1X1 + W2X2 + ... + WnXn 이라는 선형 회귀식에서, Y는 종속변수, X는 독립변수, W는 독립변수의 값에 영향을 미치는 회귀 계수(Regresstion coefficients)이다.
머신러닝 관점에서 독립변수는 피처에 해당되며 종속변수는 결정 값이다.
즉, 머신러닝 회귀 예측의 핵심은 주어진 피처와 결정 값 데이터 기반에서 학습을 통해 최적의 회귀 계수(W)를 찾아내는 것
분류
- 회귀 계수의 선형 여부 : 선형 회귀, 비선형 회귀
- 독립 변수의 개수 : 단일 회귀, 다중 회귀
이전에 배운 분류는 예측값이 카테고리와 같은 이산형 클래스 값이고, 회귀는 연속형 숫자 값이라는 차이가 있다.
규제에 따른 선형 회귀 분류
- 일반 선형 회귀 : 예측값과 실제 값의 RSS를 최소화할 수 있도록 회귀 계수를 최적화하며, 규제(Regularzation)를 적용하지 않은 모델.
- 릿지(Ridge) : 선형 회귀에 L2 규제를 추가한 회귀 모델.
- 라쏘(Lasso) : 선형 회귀에 L1 규제를 추가한 회귀 모델.
- 엘라스틱넷(ElasticNet) : L2, L1 규제를 함께 결합한 모델. 주로 피처가 많은 데이터 세트에서 적용된다.
- 로지스틱 회귀(Logistic Regression) : 회귀이지만 분류에 사용되는 선형 모델.
02. 단순 선형 회귀를 통한 회귀 이해
단순 선형 회귀란 독립변수와 종속변수가 하나인 선형 회귀를 말한다.
특정 기룰기와 절편을 가진 1차 함수식( Y = w0 + w1X)로 나타낼 수 있다.
기울기 W1과 절편 W0을 회귀 계수로 지칭한다.
실제 값과 회귀 모델의 차이에 따른 오류 값을 남은 오류, 잔차라고 부른다.
즉, 최적의 회귀모델은 잔차의 합이 최소가 되는 모델이다.
오류 값은 +나 -가 될 수 있으므로 오류 합을 계산할 때는 절댓값, 오류 값의 제곱을 합하는 방식(RSS) 등을 채택한다.
회귀에서 이 RSS는 비용(Cost)이며 w 변수(회귀 계수)로 구성되는 RSS를 비용 함수라고 한다.
머신러닝 회귀 알고리즘은 데이터를 계속 학습하면서 이 비용 함수가 반환하는 값을 지속해서 감소시키고 최종적으로 더 이상 감소하지 않는 최소의 오류 값을 구한다.
비용 함수를 손실 함수(loss function)이라고도 한다.
03. 비용 최소화하기 - 경사 하강법(Gradient Descent) 소개
경사 하강법은 고차원 방정식에 대한 문제를 해결해 주면서 비용 함수 RSS를 최소화 하는 방법을 직관적으로 제공한다.
점진적으로 반복적인 계산을 통해 w 파라미터 값을 업데이트하면서 오류 값이 최소가 되는 w 파라미터를 구한다.
비용 함수의 반환 값이 작아지는 방향성을 가지고 w 파라미터를 보정해 나가면서, 오류 값이 더 이상 작아지지 않으면 그 오류 값을 최소 비용으로 판단하고 w 값을 반환한다.
R(w)를 RSS(w0,w1)이라 할 때, 각 파라미터에 편미분한 결과는 다음과 같다.
저 편미분 결과값을 반복적으로 보정한다.
업제이트는 새로운 w1을 이전 w1에서 평미분 결과값을 마이너스하면서 적용한다. 위 편미붑 값이 너무 클 수 있으므로 보정 계수 n을 곱하는데, 이를 학습률이라 한다.
실제 gradient descent를 구현하면 다음과 같다.
# w1 과 w0 를 업데이트 할 w1_update, w0_update를 반환.
def get_weight_updates(w1, w0, X, y, learning_rate=0.01):
N = len(y)
# 먼저 w1_update, w0_update를 각각 w1, w0의 shape와 동일한 크기를 가진 0 값으로 초기화
w1_update = np.zeros_like(w1)
w0_update = np.zeros_like(w0)
# 예측 배열 계산하고 예측과 실제 값의 차이 계산
y_pred = np.dot(X, w1.T) + w0
diff = y-y_pred
# w0_update를 dot 행렬 연산으로 구하기 위해 모두 1값을 가진 행렬 생성
w0_factors = np.ones((N,1))
# w1과 w0을 업데이트할 w1_update와 w0_update 계산
w1_update = -(2/N)*learning_rate*(np.dot(X.T, diff))
w0_update = -(2/N)*learning_rate*(np.dot(w0_factors.T, diff))
return w1_update, w0_update
# 입력 인자 iters로 주어진 횟수만큼 반복적으로 w1과 w0를 업데이트 적용함.
def gradient_descent_steps(X, y, iters=10000):
# w0와 w1을 모두 0으로 초기화.
w0 = np.zeros((1,1))
w1 = np.zeros((1,1))
# 인자로 주어진 iters 만큼 반복적으로 get_weight_updates() 호출하여 w1, w0 업데이트 수행.
for ind in range(iters):
w1_update, w0_update = get_weight_updates(w1, w0, X, y, learning_rate=0.01)
w1 = w1 - w1_update
w0 = w0 - w0_update
return w1, w0
get_weight_updates()을 경사 하강 방식으로 반복적으로 수행하여 w1과 w0을 업데이트 하는 함수인 gradient_descent_steps() 함수를 생성했다.
def get_cost(y, y_pred):
N = len(y)
cost = np.sum(np.square(y - y_pred))/N
return cost
w1, w0 = gradient_descent_steps(X, y, iters=1000)
print("w1:{0:.3f} w0:{1:.3f}".format(w1[0,0], w0[0,0]))
y_pred = w1[0,0] * X + w0
print('Gradient Descent Total Cost:{0:.4f}'.format(get_cost(y, y_pred)))
그 후 예측 값과 실제 값의 RSS 차이를 계산하는 get_cost() 함수를 생성하고 경사 하강법의 예측 오류도 계산한다.
결과는 실제 값과 유사하게 나왔다.
일반적으로 경사 하강법은 수행 시간이 오래 걸리므로 실전에서는 확률적 경사 하강법을 이용한다.
확률적 경사 하강법은 일부 데이터만 이용해 w가 업데이트되는 값을 계산하므로 빠른 속도를 보장한다.
만약 피처(X)가 여러개인 경우, 회귀 계수도 여러개 로 도출된다. 피처가 M개이면 회귀 계수는 M+1(절편 포함)개로 도출된다.
이러한 경우 회귀 예측값은 아래와 같이 도출할 수 있다.
04. 사이킷런 LinearRegression을 이용한 보스턴 주택 가격 예측
규제가 적용되지 않은 성형 회귀를 구현한 클래스인 LinearRegression을 이용해 보스턴 주택가격 회귀를 구현한다.
LinearRegression 클래스 - Ordinary Least Squares
LinearRegression 클래스는 예측값과 실제 값의 RSS를 최소화해 OLS 추정 방식으로 구현한다.
fit() 메서드로 X, y 배열을 받으면 회귀 계수(Coefficients)인 w를 coef_속성에 저장한다.
입력 파라미터
- fit_intercept : Boolean값으로, defualt = True. 절편 값을 계산할지 말지를 지정한다.
- normalize : Boolean값으로, default = False. fit_intercept가 False인 경우 무시 된다. True이면 회귀를 수행하기 전 입력 데이터 세트를 정규화한다.
속성
- coef_ : fit() 메서드를 수행했을 때 회귀 계수가 배열로 저장하는 속성. Shape(Rarget 값 개수, 피처 개수)
- intercept_ : intercept값
OLS 기반의 회귀 계수 계산은 입력 피처의 독립성에 많은 역향을 받는다. 피처 간 상관관계가 매우 높은 경우 분산이 커져서 오류에 민감해진다.
회귀 평가 지표
여기에 MSE나 RMSE에 로그를 적용한 MSLE도 사용한다.
사이킷런은 RMSE를 제공하지 않으므로 MSE에 제곱근을 씌워서 계산하는 함수를 직접 만들어야 한다.
(0.22 부터는 MSE를 위한 metrics.mean_sqaured_error() 함수에 squared 파라미터를 False를 지정하여 RMSE를 구할 수 있다!)
다음으로 각 평가 방법에 대한 사이킷런의 API 밒 scoring 파라미터의 적용 값이다.
scoring 함수는 score 값이 클수록 좋은 평가 결과로 자동 평가하는데, neg_라는 접두어를 가진 값들은 음수 값을 가진다.
회귀 평가 지표는 값이 커질수록 오히려 나쁜 모델이라는 의미이므로, scoring함수에 반영하기 위해서는 -1을 곱해서 보정해야 한다.
즉, neg_mean_absolute_error는 -1*metrics.mean_absolute_error()이다.
LinearRegression을 이용해 보스턴 주택 가격 회귀 구현
사이킷런에 데이터셋이 내장되어 있었으나...윤리적인 문제로 없어져서 캐글에서 직접 가져와서 사용해야 한다.
따라서 Target값이 따로 없으므로 실습과 같게 하기 위해 MEDV 컬럼을 PRICE로 변경하는 등 전처리 과정을 약간 수정함.
각 칼럼이 회귀 결과에 미치는 영향을 시간화 한다.
# 2개의 행과 4개의 열을 가진 subplots를 이용. axs는 4x2개의 ax를 가짐.
fig, axs = plt.subplots(figsize=(16,8) , ncols=4 , nrows=2)
lm_features = ['RM','ZN','INDUS','NOX','AGE','PTRATIO','LSTAT','RAD']
for i , feature in enumerate(lm_features):
row = int(i/4)
col = i%4
# 시본의 regplot을 이용해 산점도와 선형 회귀 직선을 함께 표현
sns.regplot(x=feature , y='PRICE',data=bostonDF , ax=axs[row][col])
fig1 = plt.gcf()
fig1.savefig('p322_boston.tif', format='tif', dpi=300, bbox_inches='tight')
결과를 보면 RM과 LSTAT의 PRICE 영향도가 가장 두드러지게 나타난다.
RM(방 개수)은 양 방향의 선형성, LSTAT(하위 계층의 비율)는 음 방향의 선형성이 가장 크다.
이제 LinearRegression 클래스를 이용해 보스턴 주택 가격의 회귀 모델을 만들고, metrics 모듈의 mean_squared_error()와 r2_score() API를 이용해 MSE와 R2 Score를 측정한다.
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score
y_target = bostonDF['PRICE']
X_data = bostonDF.drop(['PRICE'],axis=1,inplace=False)
X_train , X_test , y_train , y_test = train_test_split(X_data , y_target ,test_size=0.3, random_state=156)
# Linear Regression OLS로 학습/예측/평가 수행.
lr = LinearRegression()
lr.fit(X_train ,y_train )
y_preds = lr.predict(X_test)
mse = mean_squared_error(y_test, y_preds)
rmse = np.sqrt(mse)
print('MSE : {0:.3f} , RMSE : {1:.3F}'.format(mse , rmse))
print('Variance score : {0:.3f}'.format(r2_score(y_test, y_preds)))
MSE : 17.297 , RMSE : 4.159
Variance score : 0.757
다음으로 주택가격 모델의 intercept(정편)과 coefficient(회귀 계수) 값을 본다.
print('절편 값:',lr.intercept_)
print('회귀 계수값:', np.round(lr.coef_, 1))
절편 값: 40.99559517216467
회귀 계수값: [ -0.1 0.1 0. 3. -19.8 3.4 0. -1.7 0.4 -0. -0.9 0.
-0.6]
coef_ 속성은 회귀 계수값만 가지므로 이를 피처별 회귀 계수 값으로 매핑하고 높은 값 순으로 출력한다.
pandas Series의 sort_values() 함수 이용
# 회귀 계수를 큰 값 순으로 정렬하기 위해 Series로 생성. index가 칼럼명에 유의
coeff = pd.Series(data=np.round(lr.coef_, 1), index=X_data.columns )
coeff.sort_values(ascending=False)
RM 3.4
CHAS 3.0
RAD 0.4
ZN 0.1
INDUS 0.0
AGE 0.0
TAX -0.0
B 0.0
CRIM -0.1
LSTAT -0.6
PTRATIO -0.9
DIS -1.7
NOX -19.8
dtype: float64
RM의 회귀 계수가 가장 크며 , NOX 피처의 회귀 계수 -값이 너무 크므로 최적화를 수행하며 피처의 변화를 살펴 본다.
5개의 폴드 세트에서 cross_val_score()를 이용해 교차 검증으로 MSE롸 RMSE를 측정한다.
위에서 설명했듯이 사이킷런의 Scoring 함수를 호출하면 모델에서 계산된 MSE 값에 -1을 곱해서 반환한다.
따라서 cross_val_score()에서 반환된 값에 다시 1을 곱해야 양의 값이 되고, 이렇게 변환된 값에 sqrt() 함수를 적용해 RMSE를 구할 수 있다.
from sklearn.model_selection import cross_val_score
y_target = bostonDF['PRICE']
X_data = bostonDF.drop(['PRICE'],axis=1,inplace=False)
lr = LinearRegression()
# cross_val_score( )로 5 Fold 셋으로 MSE 를 구한 뒤 이를 기반으로 다시 RMSE 구함.
neg_mse_scores = cross_val_score(lr, X_data, y_target, scoring="neg_mean_squared_error", cv = 5)
rmse_scores = np.sqrt(-1 * neg_mse_scores)
avg_rmse = np.mean(rmse_scores)
# cross_val_score(scoring="neg_mean_squared_error")로 반환된 값은 모두 음수
print(' 5 folds 의 개별 Negative MSE scores: ', np.round(neg_mse_scores, 2))
print(' 5 folds 의 개별 RMSE scores : ', np.round(rmse_scores, 2))
print(' 5 folds 의 평균 RMSE : {0:.3f} '.format(avg_rmse))
5 folds 의 개별 Negative MSE scores: [-12.46 -26.05 -33.07 -80.76 -33.31]
5 folds 의 개별 RMSE scores : [3.53 5.1 5.75 8.99 5.77]
5 folds 의 평균 RMSE : 5.829
평균 RMSE가 5.5829가 나왔다.
드디어 회귀가 나왔다.
수업 시간에 이해 안된 부분 다시 공부하는 중.
'Euron > 정리' 카테고리의 다른 글
[Week8] 05. 회귀 - 캐글 실습 (1) | 2023.10.19 |
---|---|
[Week5] 05. 회귀(2) (1) | 2023.10.08 |
[Week4] 04. 분류 - 캐글 실습 (0) | 2023.09.29 |
[Week3] 04. 분류(2) (0) | 2023.09.21 |
[Week2] 04. 분류(1) (0) | 2023.09.17 |