데이터로그😎

1. 선형 회귀 (Linear Regression) 본문

머신러닝/지도 학습

1. 선형 회귀 (Linear Regression)

지연v'_'v 2023. 9. 4. 19:01
  • 선형  - 비선형 회귀의 구분
    • 회귀계수가 선형인지 비선형인지에 따름
      • 선형: 선형회귀
      • 비선형: 딥러닝, 트리...
    • 독립변수의 선형, 비선형 여부와는 무관
규제 적용X 규제 적용O
  • 일반 선형회귀: RSS(잔차) 최소화하는 회귀계수 + 규제 적용X
  • 단순 선형회귀 (하나 독립변수-하나 종속변수: 선형관계)
  • 다항 회귀(여러 독립변수-하나 종속변수: 비선형 관계)
  • 릿지(Ridge): 선형회귀+L2 규제(회귀계수 값 작게 만드는 규제모델)
  • 라쏘(Lasso): 선형회귀+L1 규제(예측 영향력이 작은 피처의 회귀계수를 0으로 만들어 회귀 예측 시 피처가 선택되지 않게 함
  • 엘라스틱 넷(Elastic Net) = 릿지+라쏘. 피처가 많은 데이터 세트에 적용. L1규제로 피처 개수를 줄임과 동시에 L2규제로 계수 값 크기 조정
파라미터 속성
  • fit_intercept
    • 디폴트 True.
    • 절편값을 계산할건지 말건지 지정
      False = y절편이 0/ True = y절편 지정
  • normalize
    • 디폴트 Flase
    • 입력 데이터를 정규화할지 여부를 결정합니다.
    • True로 설정하면 입력 데이터의 평균과 표준편차를 사용하여 정규화합니다.
    • False로 설정하면 정규화하지 않습니다. 
  • coef_
    • 입력 변수의 회귀계수(coefficient)입니다.
    • 이 값은 학습한 모델의 회귀식에서 각 입력 변수의 가중치를 나타냅니다.
  • intercept_
    • 절편(intercept)입니다.
    • 이 값은 학습한 모델의 회귀식에서 y 절편을 나타냅니다.

✅<규제 X 선형회귀>

1. 단순 선형회귀

  • 독립변수 1, 종속변수 1
  • y = w0 + w1*x + 오류값(잔차)
  • 독립변수 = feature
  • 종속변수(결과) = target, label
  • 목표: RSS 최소화 (잔차값 최소화).
    • 최적 회귀 모델을 만든다는 것 = 잔차의 합이 최소가 되는 회귀 계수를 찾는 것.
    • 회귀계수(coefficient, 가중치) = 각 featrue(특성)이 결과값에 미치는 영향력의 크기

1-1. 데이터 불러오기

  • 보스턴 주택가격 예측
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')  # 사이킷런 1.2 부터는 보스턴 주택가격 데이터가 없어진다는 warning 메시지 출력 제거

data_url = "http://lib.stat.cmu.edu/datasets/boston"
raw_df = pd.read_csv(data_url, sep="\s+", skiprows=22, header=None)
data = np.hstack([raw_df.values[::2, :], raw_df.values[1::2, :2]])
target = raw_df.values[1::2, 2]

data_url = "http://lib.stat.cmu.edu/datasets/boston"
raw_df = pd.read_csv(data_url, sep="\s+", skiprows=22, header=None)
data = np.hstack([raw_df.values[::2, :], raw_df.values[1::2, :2]])
target = raw_df.values[1::2, 2]

boston_df = pd.DataFrame(
    data=data,
    columns=['CRIM','ZN','INDUS','CHAS','NOX','RM','AGE','DIS','RAD','TAX','PTRATIO','B','LSTAT']
)

boston_df['PRICE']=target
boston_df

 

1-2. 데이터 상관관계 확인

  • heatmap
boston_df.corr()['PRICE'].sort_values()

#히트맵으로 확인
# Feature끼리의 상관관계 확인 
# 너무 많은 상관성을 가진 데이터를 확인할 수 있다. -> 다중공선성

plt.figure(figsize=(10,10))

corr_df = boston_df.corr()
sns.heatmap(
    data = corr_df,
    annot = True)

plt.show()
  • regplot: 산점도+선형회귀 직선 (독립-종속변수 간의 관계를 모든 컬럼마다 그려줌)
fig, axs=plt.subplots(figsize=(20,10),ncols=4,nrows=2)
features = ['RM','ZN','INDUS','NOX','AGE','PTRATIO','LSTAT','RAD']

for i, feature in enumerate(features):
    row = int(i/4)
    col = i%4
    sns.regplot(x=feature, y='PRICE',data=boston_df,ax=axs[row][col])

regplot

regplot을 해석해보면 RM, LSTAT의 PRICE 영향도가 큼.

RM = 양방향의 선형성, LSTAT = 음의 방향의 선형성

 

1-3. 데이터 분할 & 모델 훈련

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression

X_train, X_test, y_train, y_test = train_test_split(
        boston_df.drop('PRICE', axis =1), # 13개 features
        boston_df['PRICE'],
        test_size = 0.2,
        random_state = 42
)


lr_reg = LinearRegression()
lr_reg.fit(X_train, y_train)

 

1-4. 모델 학습결과 확인: 가중치(.coef_), 편향(.intercept_)

# 가중치, 편향 (모델 파라미터) 확인 
# 모델 파라미터: 학습하면서 데이터가 스스로 배우는 것
# 하이퍼 파라미터: 개발자가 직접 넣어야하는 것

print('가중치 \:{}'.format(lr_reg.coef_),end='\n\n') #.coef_ : 기울기
print('편향 b: {}'.format(lr_reg.intercept_)) #.interceipt_ : 절편

>>> 가중치: [-0.1  0.1  0.  3....]
>>> 편향: 40,9955....

 

  • 추가로 coef_만 따로 뽑아내서 확인할 수도 있다.
print(f'회귀계수: {np.round(lr_reg.coef_,1)}')
print()
print(f'절편: {lr_reg.intercept_}')

>>>회귀계수: [ -0.1   0.1   0.    3.  -19.8   3.4   0.   -1.7   0.4  -0.   -0.9   0.
  -0.6]

절편: 40.995595172164336
coeff=pd.Series(np.round(lr.coef_,1),index= boston_df.drop('PRICE',axis=1).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

 

1-5. 평가

from sklearn.metrics import mean_squared_error, r2_score

mse = mean_squared_error(y_test, y_pred)
rmse = np.sqrt(mse) # mse에 루트씌우면  mse
r2 = r2_score(y_test, y_pred) # accuracy_score와 같은 기능. 즉 모델의 점수 개념! model.score로 구할 수 있다

print('MSE: {:.3f}'.format(mse))
print('RMSE: {:.3f}'.format(rmse))

print()
print('R2: {:.3f}'.format(r2))
print('Score: {:.3f}'.format(lr_reg.score(X_test, y_test)))
#r2와 score값 동일!!


------------------------------------------
MSE: 24.291
RMSE: 4.929

R2: 0.669  (accuracy_score와 비슷)
Score: 0.669

 

2. 다항회귀 (Polynomial)

  • 다항회귀는 선형회귀
  • 1차식이 아닌 2,3차 방정식으로 표현되는 회귀식
  • 사이킷런에서는 다항회귀를 위한 클래스를 명시적으로 제공하진 x. 따라서  다항피처로 변환 후 선형회귀 클래스 사용.
    • 다항회귀는 PolynomiaFeatures 클래스를 통해 X_train데이터를 다항식 피처로 변환후 훈련(fit)한다.
    • 그 후 X_test데이터도 Poly로 변환해줌
    • 순서: X_train데이터 다항식피처로 변환 →모델학습(fit)→ X_test데이터도 변환 → 변환한 X_test데이터로 predict → mse,rmse,r2 산출
  • 파라미터
    • degree : 다항식의 차수를 나타내는 파라미터입니다. 예를 들어, degree=2로 설정하면 2차 다항식 특성이 추가됩니다.
    • include_bias : 상수항을 추가할지 여부를 결정하는 파라미터입니다. 기본값은 True로, 상수항이 추가됩니다.

 

2-1. X_train 데이터 -> 3차식 변환

from sklearn.preprocessing import PolynomialFeatures

boston_poly = PolynomialFeatures(degree=3, include_bias = False)
#3차식으로 변형된 X_train =X_poly_3
X_poly_3 = boston_poly.fit_transform(X_train)
X_train.shape, X_poly_3.shape 


#원래 13개 피쳐를 갖고 있던 X_train이 poly변환을 통해 559피처로 증가
>>>((404, 13), (404, 559))

 

2-2. 학습 (using X_poly_3)

lr_reg_poly = LinearRegression().fit(X_poly_3, y_train)
# 먼저 X_train -> 3차 다항식으로 X_poly_3로 변환한뒤 fit해서 학습

 

2-3. X_test ->3차식 변형

X_poly_test3 = boston_poly.fit_transform(X_test)

 

2-4. 예측

y_test_pred = lr_reg_poly.predict(X_poly_test3)

test_mse = mean_squared_error(y_test, y_test_pred)
test_rmse = np.sqrt(test_mse)
test_r2 = r2_score(y_test, y_test_pred)


print('Test MSE: {:.3f}'.format(test_mse))
print('Test RMSE: {:.3f}'.format(test_rmse))
print('Test R2: {:.3f}'.format(test_r2))

# 3차 다항식으로 구하니 말도 안되는...오류 수치들이 나옴
# 엄청난 과대적함...

>>Test MSE: 129848.080
Test RMSE: 360.344
Test R2: -1769.645

 

2-5. 2차 다항회귀

from sklearn.preprocessing import PolynomialFeature
from sklearn.linear_model import LinearRegression

# 2차 다항식으로 변경
boston_poly = PolynomialFeature(degree=2, include_bias = False)
X_poly_2 = boston_poly.fit_transform(X_train)

# 학습
lr_reg_poly = LinearRegression().fit(X_poly_2, y_train)

# X_test 데이터 변형
X_poly_test2 = boston_poly.fit_transform(X_test)

# predict
y_test_pred2 = lr_reg_poly.predict(X_poly_test2)

# 평가
test_mse = mean_squared_error(y_test, y_test_pred2)
test_rmse = np.sqrt(test_mse)
test_r2 = r2_score(y_test, y_test_pred2)

print('Test MSE: {:.3f}'.format(test_mse))
print('Test RMSE: {:.3f}'.format(test_rmse))
print('Test R2: {:.3f}'.format(test_r2))

>>>결과:
Test MSE: 14.257
Test RMSE: 3.776
Test R2: 0.806 

# 일반 선형회귀 사용 때보다 다항회귀를 사용하니 오류 줄고 R2 상승.

⭐⭐다항식의 차수가 높아질 수록 과적합의 문제 발생!!!⭐

degree=1 → 너무 단순화된 곡선. 과소적합

degree=15 → 과적합. 회귀계수가 너무 커져서 형편없는 예측성능


❓일반 선형회귀의 문제점?

  • 위의 2-4에서 볼 수 있듯이, 다항회귀에서는 차수가 낮으면 지나치게 곡선이 단순화되어 과소적합, 차수가 높으면 지나치게 모든 데이터에 적합해져 다항식이 복잡 & 회귀계수 매우 크게 설정되어 예측성능↓
  • 따라서 회귀 모델은 데이터에 적합하며 회귀 계수가 커지는 것을 제어할 수 있어야 함.
    • 회귀계수가 커진다? → 해당 feature가 결과값에 큰 영향을 미치게 되어 그 feature값을 조정하면 결과값이 크게 바뀔 수 있다는 것을 의미. 즉, 해당 feature가 예측에 매우 중요하다는 의미.
    • 그러나 모델이 학습 데이터에 지나치게 맞추게 되면 회귀계수가 커져서 모델이 지나치게 학습 데이터에만 특화→따라서 회귀모델은 회귀계수가 기하급수적으로 커지는 것을 제어할 수 있어야 함.
  • 일반 선형 모델에서는 RSS를 최소화하는 것만 고려. -> 지나치게 학습 데이터에 맞추게 되면 잔차가 작아지는 방향으로  회귀계수가 커짐 = overfitting -> 새로운 데이터에 대한 예측 성능 저하.
    • 이를 막기 위해, 비용함수는 RSS 최소화 + 회귀 계수 값 제어가 균형을 이루어 모델이 학습 데이터에만 과적합 되는 것을 방지 필요가 있음. 👉 규제 선형모델

 

✅<규제 선형모델>

  • 비용함수에서 alpha값으로 페널티를 부여하여 회귀계수 값의 크기를 감소시켜 과적합을 개선하는 방식
  • 라쏘 (L1 규제) : 회귀계수 0으로 만들 수 있음 (변수 선택한다는 관점)

Lasso regression

  • 릿지 (L2 규제): 회귀계수 크기 제한

ridge regression

  • alpha는 회귀 계수값의 크기를 제어하는 튜닝 파라미터
  • alpha=0 → 기존의 비용함수와 동일해짐
  • alpha 값을 크게 → 비용함수는 회귀계수 W의 값을 작게함→ 과대적합 개선
  • alpha 값 작게 → W의 값이 커져도 어느정도 상쇄 가능 → 데이터 적합을 개선할 수 음

 

 

1. 릿지회귀(L2): 회귀계수 크기 제한

보스턴 주택가격 분석

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns

import warnings
warnings.filterwarnings('ignore')  # 사이킷런 1.2 부터는 보스턴 주택가격 데이터가 없어진다는 warning 메시지 출력 제거

data_url = "http://lib.stat.cmu.edu/datasets/boston"
raw_df = pd.read_csv(data_url, sep="\s+", skiprows=22, header=None)
data = np.hstack([raw_df.values[::2, :], raw_df.values[1::2, :2]])
target = raw_df.values[1::2, 2]

boston_df = pd.DataFrame(
    data=data,
    columns=['CRIM','ZN','INDUS','CHAS','NOX','RM','AGE','DIS','RAD','TAX','PTRATIO','B','LSTAT']
)

boston_df['PRICE']=target
boston_df

 

Poly (X) + alpha = 10일때

from sklearn.linear_model import Ridge
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

X = boston_df.drop('PRICE',axis=1)
y = boston_df['PRICE']

X_train, X_test, y_train, y_test = train_test_split(
                X, y, test_size = 0.2,
                random_state = 42)


# 릿지 생성

lr_ridge = Ridge(alpha=10) 
# alpha는 log scale로 지정하는게 보통(0.01,0.1,10,100...10^n)

lr_ridge.fit(X_train, y_train)


# 예측, 평가
ridge_pred = lr_ridge.predict(X_test)

mse = mean_squared_error(y_test, ridge_pred)
rmse = np.sqrt(mse)
r2 = lr_ridge.score(X_test, y_test)


print('MSE: {:.3f}'.format(mse))
print('RMSE: {:.3f}'.format(rmse))
print('R2: {:.3f}'.format(r2))

# 12. 선형회귀의 '평가'부분의 결과보다 MSE, RMSE 늘어났음 = 모델이 단순해진 것.
# = 성능 떨어짐 = 규제 풀어주는 게 좋을 것 (alpha를 0에 가깝게 만드는 것)

>>>MSE: 24.648
RMSE: 4.965
R2: 0.664

2차 다항 선형회귀보다 성능이 안좋아짐(오류 늘어남) -> 과대적합 되었다는 말? -> 회귀계수가 커졌다 -> alpha를 줄여서 과대적합 줄여보자!

 

Poly(X) + alpha = 0일때

lr_ridge = Ridge(alpha=0) 
# alpha는 log scale로 지정하는게 보통(0.01,0.1,10,100...10^n)

lr_ridge.fit(X_train, y_train)

ridge_pred = lr_ridge.predict(X_test)

mse = mean_squared_error(y_test, ridge_pred)
rmse = np.sqrt(mse)
r2 = lr_ridge.score(X_test, y_test)


print('MSE: {:.3f}'.format(mse))
print('RMSE: {:.3f}'.format(rmse))
print('R2: {:.3f}'.format(r2))

>>>MSE: 24.291
RMSE: 4.929
R2: 0.669

alpha가 0이면 그냥 Linear Regression이랑 똑같은 것임

  • 즉, 내가 alpha를 아무리 건드려서 줄여봐도 Linear Regression의 성능을 넘을 순 없음
  • 따라서 이제 데이터 공학이 필요한 때 -> degree=3으로 조정

 

Poly 3차원(O) + 릿지 규제

from sklearn.preprocessing import PolynomialFeatures
from sklearn.metrics import r2_score

boston_poly = PolynomialFeatures(degree=3, include_bias =False)

X_train_3 = boston_poly.fit_transform(X_train)
X_test_3 = boston_poly.transform(X_test)


# 훈련 데이터에 대한 예측
lr_train_pred = lr.predict(X_train_3)
ridge_train_pred = lr_ridge.predict(X_train_3)

# 평가
## 일반 선형회귀
lr_train_mse = mean_squared_error(y_train, lr_train_pred)
lr_train_rmse = np.sqrt(lr_train_mse)
lr_train_r2 = r2_score(y_train, lr_train_pred)

## 릿지 규제
ridge_train_mse = mean_squared_error(y_train, ridge_train_pred)
ridge_train_rmse = np.sqrt(ridge_train_mse)
ridge_train_r2 = r2_score(y_train, ridge_train_pred)

print("LinearRegression MSE : {:.3f} / RMSE : {:.3f} / R2 : {:.3f}".format(lr_train_mse, lr_train_rmse, lr_train_r2))
print("Ridge MSE : {:.3f} / RMSE : {:.3f} / R2 : {:.3f}".format(ridge_train_mse, ridge_train_rmse, ridge_train_r2))


>>>LinearRegression MSE : 0.000 / RMSE : 0.000 / R2 : 1.000
Ridge MSE : 19.070 / RMSE : 4.367 / R2 : 0.780
# 테스트 세트 확인
lr_test_pred = lr.predict(X_test_3)
ridge_test_pred = lr_ridge.predict(X_test_3)

lr_test_mse = mean_squared_error(y_test, lr_test_pred)
lr_test_rmse = np.sqrt(lr_test_mse)
lr_test_r2 = r2_score(y_test, lr_test_pred)

ridge_test_mse = mean_squared_error(y_test, ridge_test_pred)
ridge_test_rmse = np.sqrt(ridge_test_mse)
ridge_test_r2 = r2_score(y_test, ridge_test_pred)

print("LinearRegression MSE : {:.3f} / RMSE : {:.3f} / R2 : {:.3f}".format(lr_test_mse, lr_test_rmse, lr_test_r2))
print("Ridge MSE : {:.3f} / RMSE : {:.3f} / R2 : {:.3f}".format(ridge_test_mse, ridge_test_rmse, ridge_test_r2))


>>>LinearRegression MSE : 129848.080 / RMSE : 360.344 / R2 : -1769.645
Ridge MSE : 160.367 / RMSE : 12.664 / R2 : -1.187

 

alpha값 변화에 따른 ridge 회귀계수의 변화

fig, axs = plt.subplots(figsize=(18,6),nrows=1, ncols=5)
coeff_df = pd.DataFrame()

alphas = [0, 0.1, 1, 10, 100]
for pos, alpha in enumerate(alphas):
    ridge = Ridge(alpha = alpha)
    ridge.fit(X_data, y_target)
    coeff = pd.Series(data = ridge.coef_, index = X_data.columns)
    colname = 'alpha:'+str(alpha)
    coeff_df[colname] = coeff
    
    coeff = coeff.sort_values(ascending=False)
    axs[pos].set_title(colname)
    axs[pos].set_xlim(-3,6)
    sns.barplot(x=coeff.values, y=coeff.index, ax=axs[pos])

alpha값이 0->100으로 커질 수록 NOX 값은 줄어든다.

 

2. 라쏘회귀(L1): 회귀계수 0으로 만들 수 있음

Poly(X) + alpha=10

from sklearn.linear_model import Lasso

lr_lasso = Lasso(alpha=10).fit(X_train, y_train)
lasso_pred = lr_lasso.predict(X_test)

mse = mean_squared_error(y_test, lasso_pred)
rmse = np.sqrt(mse)
r2 = r2_score(y_test, lasso_pred)

print("MSE : {:.3f} / RMSE : {:.3f} / R2 : {:.3f}".format(mse, rmse, r2))

>>>MSE : 34.686 / RMSE : 5.889 / R2 : 0.527


Poly(O) + alpha=10

lr_lasso = Lasso(alpha=10).fit(X_train_3, y_train)

lasso_pred = lr_lasso.predict(X_test_3)

mse = mean_squared_error(y_test, lasso_pred)
rmse = np.sqrt(mse)
r2 = r2_score(y_test, lasso_pred)

print("MSE : {:.3f} / RMSE : {:.3f} / R2 : {:.3f}".format(mse, rmse, r2))

>>>MSE : 13.241 / RMSE : 3.639 / R2 : 0.819

alpha값 변화에 따른 lasso 회귀계수의 변화

from sklearn.linear_model import Lasso
fig, axs = plt.subplots(figsize=(18,6),nrows=1, ncols=5)
coeff_df = pd.DataFrame()

alphas = [0.07, 0.1, 0.5, 1, 3]
for pos, alpha in enumerate(alphas):
    lasso = Lasso(alpha = alpha)
    lasso.fit(X_data, y_target)
    coeff = pd.Series(data = lasso.coef_, index = X_data.columns)
    colname = 'alpha:'+str(alpha)
    coeff_df[colname] = coeff
    
    coeff = coeff.sort_values(ascending=False)
    axs[pos].set_title(colname)
    axs[pos].set_xlim(-3,6)
    sns.barplot(x=coeff.values, y=coeff.index, ax=axs[pos])

Lasso

 

라쏘는 위의 데이터프레임과 같이 회귀계수 0인 피처들이 있다. 이러한 피처들은 회귀 식에서 제외되면서 피처 선택의 효과를 얻을 수 있다.

 

  • Ridge보다 Lasso가 여기서는 성능이 더 좋다
  • 피처들이 3차식일 때 (복잡해진 경우) Lasso가 더 나을 수 있다.
  • 그러나 3차식으로 하는 경우는 특이케이스고, 일반케이스는 Ridge가 더 성능이 좋긴하다.

'머신러닝 > 지도 학습' 카테고리의 다른 글

0. 회귀 (Regression)  (0) 2023.09.04
4-3. Boosting (부스팅)  (0) 2023.09.04
4-2. Bagging (배깅)  (0) 2023.09.04
4-1. Voting  (0) 2023.09.04
4. Ensemble (앙상블)  (0) 2023.09.04