○ 선형 회귀 모델은 일반적으로 피처와 타겟값 간에 선형의 관계가 있다고 가정하고 이러한 최적의 선형 함수를 찾아내 결과값을 예측함

○ 선형 회귀 모델은 피처값과 타겟값의 분포가 정규분포 형태를 선호

- 피처값과 타겟값의 분포가 왜곡(skew)된 분포일 경우 예측 성능에 부정적인 영향을 미칠 수 있음

→ 선형 회귀 모델을 적용하기 전에 먼저 데이터에 대한 스케일링/정규화 작업을 수행하는 것이 일반적이다. 하지만 이러한 스케일링/정규화 작업을 선행한다고 해서 무조건 예측 성능이 향상되는 것은 아니다. 

<'피처값 변환 - 로그변환' 부가적인 설명>

로그변환은 매우 유용한 변환이며, 원래 값에 log 함수를 적용하면 보다 정규분포에 가까운 형태로 값이 분포된다. 

실제로 스케일링, 다항 특성 변환 방법보다 로그변환이 훨씬 많이 사용되는 변환 방법이다. 왜냐하면 스케일링 방법의 경우 예측 성능 향상을 크게 기대하기 어려운 경우가 많으며, 다항 특성 변환 방법의 경우 피처의 개수가 매우 많을 경우에는 다항 변환으로 생성되는 피처의 개수가 기하급수로 늘어나서 과적합의 이슈가 발생할 수 있기 때문이다.


from sklearn.preprocessing import StandardScaler, MinMaxScaler, PolynomialFeatures
# 보스턴 주택가격 타겟 데이터 세트는 이미 정규분포화 되어 있음
bostonDF['PRICE'].hist()

로그변환은 아래 코드에서 np.log()가 아니라 np.log1p()를 이용한다. 일반적으로 log() 함수를 적용하면 언더 플로우가 발생하기 쉬워서 1+log() 함수를 적용하는데 이를 구현한 것이 np.log1p()이다.

# method는 표준정규분포 변환(Standard), 최대값/최소값 정규화(MinMax), 로그변환(Log) 결정
# p_degree는 다항식 특성을 추가할 때 적용. p_degree는 2 이상 부여하지 않음
def get_scaled_data(method='None', p_degree=None, input_data=None):
    if method == 'Standard':
        scaled_data = StandardScaler().fit_transform(input_data)
    elif method == 'MinMax':
        scaled_data = MinMaxScaler().fit_transform(input_data)
    elif method == 'Log':
        scaled_data = np.log1p(input_data)
    else:
        scaled_data = input_data
        
    
    if p_degree != None:
        scaled_data = PolynomialFeatures(degree=p_degree, includ_bias=False).fit_transform(scaled_data)
        
    return scaled_data

Ridge 클래스의 alpha 값을 변화시키면서 피처 데이터 세트를 여러 가지 방법으로 변환한 데이터 세트를 입력받을 경우에 rmse값이 어떻게 변하는지 살펴보자. (앞에서 생성한 get_linear_reg_eval() 함수 이용)

피처 데이터의 변환 방법은 모두 5가지로 아래와 같다.

  • (None, None) : 아무런 변환을 하지 않은 원본 데이터
  • ('Standard', None) : 표준정규분포
  • ('Standard', 2) : 표준정규분포를 다시 2차 다항식 변환
  • ('MinMax', None) : 최소값/최대값 정규화
  • ('MinMax', 2) : 최소값/최대값 정규화를 다시 2차 다항식 변환
  • ('Log', None) : 로그변환
# Ridge의 alpha값을 다르게 적용하고 다양한 데이터 변환 방법에 따른 rmse 추출
alphas = [0.1, 1, 10, 100]

scale_methods = [(None, None), ('Standard', None), ('Standard', 2),
               ('MinMax', None), ('MinMax', 2), ('Log', None)]
for scale_method in scale_methods:
    X_features_scaled = get_scaled_data(method=scale_method[0], p_degree=scale_method[1], input_data=X_features)
    print('\n## 변환 유형:{0}, Polylnomial Degree:{1}'.format(scale_method[0], scale_method[1]))
    get_linear_reg_eval('Ridge',params=alphas, X_features_n=X_features_scaled, y_target_n=y_target,
                       verbose=False, return_coeff=False)
## 변환 유형:None, Polylnomial Degree:None
alpha 0.1일 때 5 폴드 세트의 평균 rmse:5.788
alpha 1일 때 5 폴드 세트의 평균 rmse:5.653
alpha 10일 때 5 폴드 세트의 평균 rmse:5.518
alpha 100일 때 5 폴드 세트의 평균 rmse:5.330

## 변환 유형:Standard, Polylnomial Degree:None
alpha 0.1일 때 5 폴드 세트의 평균 rmse:5.826
alpha 1일 때 5 폴드 세트의 평균 rmse:5.803
alpha 10일 때 5 폴드 세트의 평균 rmse:5.637
alpha 100일 때 5 폴드 세트의 평균 rmse:5.421

## 변환 유형:Standard, Polylnomial Degree:2
alpha 0.1일 때 5 폴드 세트의 평균 rmse:8.827
alpha 1일 때 5 폴드 세트의 평균 rmse:6.871
alpha 10일 때 5 폴드 세트의 평균 rmse:5.485
alpha 100일 때 5 폴드 세트의 평균 rmse:4.634

## 변환 유형:MinMax, Polylnomial Degree:None
alpha 0.1일 때 5 폴드 세트의 평균 rmse:5.764
alpha 1일 때 5 폴드 세트의 평균 rmse:5.465
alpha 10일 때 5 폴드 세트의 평균 rmse:5.754
alpha 100일 때 5 폴드 세트의 평균 rmse:7.635

## 변환 유형:MinMax, Polylnomial Degree:2
alpha 0.1일 때 5 폴드 세트의 평균 rmse:5.298
alpha 1일 때 5 폴드 세트의 평균 rmse:4.323
alpha 10일 때 5 폴드 세트의 평균 rmse:5.185
alpha 100일 때 5 폴드 세트의 평균 rmse:6.538

## 변환 유형:Log, Polylnomial Degree:None
alpha 0.1일 때 5 폴드 세트의 평균 rmse:4.770
alpha 1일 때 5 폴드 세트의 평균 rmse:4.676
alpha 10일 때 5 폴드 세트의 평균 rmse:4.836
alpha 100일 때 5 폴드 세트의 평균 rmse:6.241
  • 표준정규분포와 최소값/최대값 정규화로 피처 데이터 세트를 변경해도 성능상의 개선이 별로 없음
  • 표준정규분포로 일차 변환 후 2차 다항식 변환을 했을 때 alpha=100에서 4.634로 성능이 개선
  • 최소값/최대값 정규화로 일차 변환 후 2차 다항식 변환을 했을 때 alpha=1에서 4.323으로 성능이 개선
  • 하지만 다항식 변환은 피처의 개수가 많을 경우 적용하기 힘들고 과적합의 이슈가 발생할 수 있음
  • 로그변환으로 피처 데이터 세트를 변경했을 때 alpha가 0.1, 1, 10인 경우에 모두 좋은 성능 향상이 있음

→ 일반적으로 선형 회귀를 적용하려는 데이터 세트에 데이터 값의 분포가 왜곡(skew)되어 있을 경우에 로그변환을 적용하는 것이 좋은 결과를 기대할 수 있고, 다항 특성 변환 방법은 예측에 대한 분산이 높고(alpha값에 따른 다항 특성 변환을 적용한 결과들을 보면 예측에 대한 분산이 높음), 과적합의 우려가 있어 최근에 많이 쓰이지 않음

다항회귀에서 degree1의 경우 지나치게 예측 곡선을 단순화해 과소적합 모델이 만들어졌고, degree15의 경우 지나치게 모든 학습 데이터에 적합한 회귀식을 만들기 위해서 다항식이 복잡해지고 회귀계수가 매우 크게 설정되면서 테스트 데이터 세트에 대해서 형편없는 예측 성능을 보이는 과적합 모델이 만들어졌다.

→ 회귀모델은 적절히 데이터에 적합하면서도 회귀계수가 기하급수적으로 커지는 것을 제어해야 함

 

 

이전까지 선형 모델의 비용함수는 RSS를 최소화하는, 즉 실제값과 예측값의 차이를 최소화 하는 것만 고려해 학습 데이터에 지나치게 맞춰지고 회귀계수가 쉽게 커져 과적합이 발생하는 경향이 있음

→ 위의 문제를 해결하기 위해 비용함수는 RSS 최소화 방법과 과적합을 방지하기 위해 회귀계수 값이 커지지 않도록 하는 방법이 서로 균형을 이루어야 함(학습 데이터의 잔차 오류와 회귀계수의 값이 함께 최소화하는 것을 목표로 함)

alpha는 학습 데이터 적합 정도와 회귀계수 값의 크기를 제어하는 파라미터로, 적절하게 설정해야한다.

○ alpha가 0(또는 매우 작은 값)이라면 Min(RSS(W) + 0)이 되면서 기존과 동일하게 RSS를 최소화하는 것만 고려하는 것과 다를게 없고

○ alpha가 무한대(또는 매우 큰 값)이라면 RSS(W)에 비해 alpha * llWll22 값이 너무 커지게 되므로

즉, alpha를 작게 하면 회귀계수 W의 값이 커져도 어느 정도 상쇄가 가능하므로 학습 데이터 적합을 더 개선할 수 있고, alpha를 크게 하면 회귀계수 W의 값을 작게 해 과적합을 개선할 수 있음

 

이처럼 비용함수에 alpha 값으로 페널티를 부여해 회귀계수 값의 크기를 감소시켜 과적합을 개선하는 방식을 규제(Regularization)라고 함


릿지 회귀(L2 Norm)

W의 제곱에 대해 페널티를 부여하는 방식. 회귀계수의 크기를 감소시키는 규제 모델

비용함수는

이고, 이 식을 최소화하는 W를 찾는 것이 목표이다.

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

from sklearn.datasets import load_boston
%matplotlib inline

boston = load_boston()

bostonDF = pd.DataFrame(boston.data, columns=boston.feature_names)

bostonDF['PRICE'] = boston.target

y_target = bostonDF['PRICE']
X_features = bostonDF.drop(['PRICE'], axis=1, inplace=False)
from sklearn.linear_model import Ridge
from sklearn.model_selection import cross_val_score

# alpha=10으로 설정해 릿지 회귀 수행
ridge = Ridge(alpha=10)
mse_scores = -cross_val_score(ridge, X_features, y_target, scoring='neg_mean_squared_error', cv=5)
rmse_scores = np.sqrt(mse_scores)
avg_rmse = np.mean(rmse_scores)

print('5fold의 개별 mse scores:', np.round(mse_scores, 3))
print('5fold의 개별 rmse scores:', np.round(rmse_scores, 3))
print('5fold의 평균 rmse:{0:.3f}'.format(avg_rmse))

alpha 값에 따른 rmse와 회귀계수를 살펴보자

alphas = [0, 0.1, 1, 10, 100]

for alpha in alphas:
    ridge = Ridge(alpha=alpha)
    
    # cross_val_score를 이용해 5 fold의 평균 rmse 계산
    mse_scores = -cross_val_score(ridge, X_features, y_target, scoring='neg_mean_squared_error', cv=5)
    avg_rmse = np.mean(np.sqrt(mse_scores))
    print('alpha {0} 일 때 5 folds의 평균 rmse : {1:.3f}'.format(alpha, avg_rmse))

# alpha 값에 따른 회귀계수 시각화
fig, axs = plt.subplots(figsize=(18,6), nrows=1, ncols=5)
# alpha 값에 따른 회귀계수를 데이터로 저장하기 위한 DataFrame 생성
coeff_df = pd.DataFrame()

# alphas 리스트 값을 차례로 입력해 회귀계수 시각화 및 데이터 저장. pos는 axis의 위치 지정
for pos, alpha in enumerate(alphas):
    ridge = Ridge(alpha=alpha)
    ridge.fit(X_features, y_target)
    # alpha에 따른 피처별로 회귀계수를 Series로 변환하고 이를 DataFrame의 컬럼으로 추가
    coeff = pd.Series(ridge.coef_, index=X_features.columns)
    colname = 'alpha:'+str(alpha)
    coeff_df[colname] = coeff
    # 막대그래프로 alpha 값에 따른 회귀계수 시각화. 내림차순
    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])
    
plt.show()

coeff_df.sort_values(by='alpha:0', ascending=False)

릿지 회귀는 alpha 값이 커질수록 회귀계수 값을 작게 만든다

 

라쏘 회귀(L1 Norm)

W의 절대값에 대해 페널티를 부여하는 방식. 불필요한 회귀계수를 급격하게 감소시켜 0으로 만드는 규제 모델

→ 이러한 측면에서 적절한 피처만 회귀에 포함시키는 피처 선택의 특성을 가지고 있음

비용함수는

이고, 이 식을 최소화하는 W를 찾는 것이 목표이다.

 

alpha 값에 따른 rmse와 회귀계수를 살펴보자. (alpha 값을 변화시키면서 결과를 출력하는 별도의 함수를 만들것)

from sklearn.linear_model import Lasso, ElasticNet

# alpha 값에 따른 회귀모델의 폴드 평균 rmse를 출력, 회귀계수 값들을 DataFrame으로 반환
def get_linear_reg_eval(model_name, params=None, X_features_n=None, y_target_n=None,
                       verbose=True, return_coeff=True):
    coeff_df = pd.DataFrame() # 빈 데이터프레임 생성
    if verbose : print('#######', model_name, '#######')
    for param in params:
        if model_name == 'Ridge':
            model = Ridge(alpha=param)
        elif model_name == 'Lasso':
            model = Lasso(alpha=param)
        elif model_name == 'ElasticNet':
            model = ElasticNet(alpha=param, l1_ratio=0.7) # l1_ratio는 0.7로 고정시킴
        
        mse_scores = -cross_val_score(model, X_features_n, y_target_n, scoring='neg_mean_squared_error', cv=5)
        avg_rmse = np.mean(np.sqrt(mse_scores))
        print('alpha {0}일 때 5 폴드 세트의 평균 rmse:{1:.3f}'.format(param, avg_rmse))
        
        # cross_val_score는 evaluation metric만 반환하므로 모델을 다시 학습하여 회귀계수 추출
        model.fit(X_features_n, y_target_n)
        if return_coeff:
            # alpha에 따른 피처별 회귀계수를 Series로 변환하고 이를 DataFrame의 컬럼으로 추가
            coeff = pd.Series(model.coef_, index=X_features_n.columns)
            colname = 'alpha:'+str(param)
            coeff_df[colname] = coeff
            
    return coeff_df
# 라쏘에 사용될 alpha 파라미터의 값을 정의하고 get_linear_reg_eval() 함수 호출
lasso_alphas = [0.07, 0.1, 0.5, 1, 3]
coeff_lasso_df = get_linear_reg_eval('Lasso', params=lasso_alphas, X_features_n=X_features, y_target_n=y_target)

# 반환된 coeff_lasso_df를 첫 번째 컬럼순으로 내림차순 정렬해 회귀계수 DataFrame 출력
coeff_lasso_df.sort_values(by='alpha:'+str(lasso_alphas[0]), ascending=False) # by='alpha:0.07'

NOX 피처는 alpha가 0.07일 때부터 회귀계수가 0이다.

라쏘 회귀는 alpha의 크기가 증가함에 따라 일부 피처의 회귀계수는 아예 0으로 바뀌고 있다. 회귀계수가 0인 피처는 회귀식에서 제외되면서 피처 선택의 효과를 얻을 수 있다.

 

엘라스틱넷 회귀(L2 Norm+L1 Norm)

L2 규제와 L1 규제를 결합한 회귀.

○ 엘라스틱넷은 라쏘 회귀가 서로 상관관계가 높은 피처들의 경우에 이들 중에서 중요 피처만을 셀렉션하고 다른 피처들은 모두 회귀계수를 0으로 만드는 성향이 강하다. 특히 이러한 성향으로 인해 alpha값에 따라 회귀계수의 값이 급격히 변동할 수도 있는데, 엘라스틱넷 회귀는 이를 완화하기 위해 L2 규제를 라쏘 회귀에 추가한 것

○ 주요 생성 파라미터는 alpha, l1_ratio

비용함수는

이고, 이 식을 최소화하는 W를 찾는 것이 목표이다.

 

alpha 값에 따른 rmse와 회귀계수를 살펴보자.(코드에 l1_ratio를 0.7로 고정시켰는데, 이는 단순히 alpha값의 변화만 살피기 위해 미리 고정했음)

# 엘라스틱넷에 사용될 alpha 파라미터의 값들을 정의하고 get_linear_reg_eval() 함수 호출
# l1_ratio는 0.7로 고정
elastic_alphas = [0.07, 0.1, 0.5, 1, 3]
coeff_elastic_df = get_linear_reg_eval('ElasticNet', params=elastic_alphas, X_features_n=X_features, y_target_n=y_target)

# 반환된 coeff_elastic_df를 첫 번째 컬럼순으로 내림차순 정렬해 회귀계수 DataFrame 출력
coeff_elastic_df.sort_values(by='alpha:'+str(elastic_alphas[0]), ascending=False) # by='alpha:0.07'

alpha값에 따른 피처들의 회귀계수들 값이 라쏘보다는 상대적으로 0이 되는 값이 적음을 알 수 있다.

 

 

 

Reference)

https://dsbook.tistory.com/206

지금까지 공부한 회귀는 y = w0 + w1*x1 + w2*x2 + ... + wn*xn과 같이 독립변수(feature)와 종속변수(target)의 관계가 일차 방정식 형태로 표현된 회귀였다. 하지만 세상의 모든 관계를 직선으로만 표현할 수 없다.

즉, 다항 회귀는 y = w0 + w1*x1 + w2*x2 + w3*x1*x2 + w4*x1^2 + w5*x2^2과 같이 회귀식이 독립변수의 단항식이 아닌 2차, 3차 방정식과 같은 다항식으로 표현되는 것을 지칭한다.

 

데이터 세트에 대해서 피처 X에 대해 타겟 y값의 관계를 단순 선형회귀 직선형으로 표현한 것보다 다항회귀 곡선형으로 표현한 것이 더 예측 성능이 높을 수 있음

 

 

 

 

 

한 가지 주의할 것은 다항회귀는 선형회귀이다. 회귀에서 선형회귀/비선형회귀를 나누는 기준은 회귀계수가 선형/비선형인지에 따른 것이지 독립변수의 선형/비선형 여부와는 무관하다.

 

사이킷런은 다항회귀를 바로 API로 제공하지 않는다. 

→ PolynomialFeatures 클래스로 원본 단항 피처들을 다항 피처들로 변환한 데이터 세트에 LinearRegression 객체를 적용하여 다항회귀 구현(아래의 과정을 각각 수행하는 것보다, pipeline 클래스를 이용해 두 가지 과정을 결합하여 한 번에 다항회귀를 구현하는 것이 일반적이고 코드가 더 명료함)

단항 피처[x1, x2]를 degree=2, 즉 2차 다항 피처로 변환하면

(x1+x2)^2 의 식 전개에 대응되는 [1, x1, x2, x1x2, x1^2, x2^2]의 다항 피처들로 변환

                                                     ↓

1차 단항 피처들의 값이 [x1, x2] = [0, 1] 일 경우

2차 다항 피처들의 값은 [1, x1=0, x2=1, x1x2=0, x1^2=0, x2^2=1]

형태인 [1, 0, 1, 0, 0, 1] 로 변환

 

 

 

마찬가지로, 단항 피처[x1, x2]를 degree=3, 즉 3차 다항 피처로 변환하면

(x1+x2)^3 의 식 전개에 대응되는 [1, x1, x2, x1x2, x1^2, x2^2, x1^3, x1^2x2, x1x2^2, x2^3]의 다항 피처들로 변환

 

참고로, PolynomialFeatures 클래스는 원본 피처 데이터 세트를 기반으로 degree(차수)에 따른 다항식을 적용하여 새로운 피처들을 생성하는 클래스로 피처 엔지니어링의 기법중의 하나임


PolynomialFeatures를 이용해 다항식 변환을 연습해보자

from sklearn.preprocessing import PolynomialFeatures
import numpy as np

# 단항식 생성, [[0,1],[2,3]]의 2X2 행렬 생성
X = np.arange(4).reshape(2,2)
print('일차 단항식 계수 feature:\n', X)

# degree=2인 2차 다항식으로 변환
poly = PolynomialFeatures(degree=2)
poly.fit(X)
poly_ftr = poly.transform(X)
print('변환된 2차 다항식 계수 feature:\n', poly_ftr)

이번에는 3차 다항회귀 함수를 임의로 설정하고 이의 회귀계수를 예측해보자.

3차 다항식 결정값을 구하는 함수 polynomial_func(X) 생성. y = 1+ 2x_1 + 3x_1^2 + 4x_2^3

def polynomial_func(X):
    y = 1 + 2*X[:,0] + 3*X[:,0]**2 + 4*X[:,1]**3
    return y

X = np.arange(4).reshape(2,2)

print('일차 단항식 계수 feature:\n', X)
y = polynomial_func(X)
print('삼차 다항식 결정값:\n', y)

from sklearn.linear_model import LinearRegression

# 3차 다항식 변환
poly_ftr = PolynomialFeatures(degree=3).fit_transform(X)
print('3차 다항식 계수 feature:\n', poly_ftr)

# LinearRegression에 3차 다항식 계수 feature와 3차 다항식 결정값으로 학습 후 회귀계수 확인
model = LinearRegression()
model.fit(poly_ftr, y)
print('Polynomial 회귀계수\n', np.round(model.coef_, 2))

일차 단항식 계수 피처는 2개였지만, 3차 다항식 변환 후에는 다항식 계수 피처가 10개로 늘어난다.이 피처 데이터 세트에 LinearRegression을 통해 3차 다항회귀 형태의 다항회귀를 적용하면 회귀계수가 10개로 늘어난다. 

 

pipeline을 이용해 한 번에 다항회귀를 구현해보자.

from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline
import numpy as np

def polynomial_func(X):
    y = 1 + 2*X[:,0] + 3*X[:,0]**2 + 4*X[:,1]**3
    return y

# Pipeline 객체로 streamline하게 PolynomialFeature 변환과 LinearRegression 연결
model = Pipeline([('poly', PolynomialFeatures(degree=3)),
                 ('linear', LinearRegression())])
X = np.arange(4).reshape(2,2)
y = polynomial_func(X)

model = model.fit(X, y)
print('Polynomial 회귀계수\n', np.round(model.named_steps['linear'].coef_, 2))


다항회귀를 이용해 보스턴 주택가격을 예측해보자

import numpy as np
import pandas as pd

from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline
from sklearn.metrics import mean_squared_error, r2_score

# boston 데이터셋 로드
boston = load_boston()

# boston 데이타셋 DataFrame 변환 
bostonDF = pd.DataFrame(boston.data, columns=boston.feature_names)

# boston dataset의 target array는 주택 가격임. 이를 PRICE 컬럼으로 DataFrame에 추가함. 
bostonDF['PRICE'] = boston.target
print('보스턴 데이터셋 shape:', bostonDF.shape)

y_target = bostonDF['PRICE']
X_features = bostonDF.drop(['PRICE'], axis=1, inplace=False)

X_train, X_test, y_train, y_test = train_test_split(X_features, y_target, test_size=0.3, random_state=156)

# Pipeline 이용: PolynomialFeatures 변환, LinearRegression 적용을 순차적으로 결합
p_model = Pipeline([('poly', PolynomialFeatures(degree=3, include_bias=False)),
                   ('linear', LinearRegression())])

p_model.fit(X_train, y_train)
y_preds = p_model.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)))


다항회귀를 이용한 underfitting, overfitting 이해

피처 X에 대해 타겟 y값의 관계를 단순 선형회귀 직선형으로 표현한 것보다 다항회귀 곡선형으로 표현한 것이 더 예측 성능이 높을 수 있다. 하지만 다항회귀의 차수(degree)를 높일수록 학습 데이터에만 너무 맞춘 학습이 이뤄져서 정작 테스트 데이터 환경에서는 오히려 예측 정확도가 떨어진다. 즉, 차수가 높아질수록 과적합 문제가 크게 발생한다.

# 데이터 생성
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline
from sklearn.model_selection import cross_val_score
%matplotlib inline

# X는 0이상 1미만의 랜덤값을 30개 생성후 순서대로 샘플링
np.random.seed(0)
n_samples = 30
X = np.sort(np.random.rand(n_samples))

# X값에 대해 Cosine 변환값을 반환하는 함수
def true_fun(X):
    return np.cos(1.5 * np.pi * X)

# y는 X의 Cosine 값에서 약간의 노이즈를 더한 값
y = true_fun(X) + np.random.randn(n_samples) * 0.1
plt.scatter(X, y)

plt.figure(figsize=(14, 5))
degrees = [1, 4, 15]

# 다항회귀 차수를 1, 4, 15로 각각 변화시키면서 비교
for i in range(len(degrees)):
    ax = plt.subplot(1, len(degrees), i+1)
    plt.setp(ax, xticks=(), yticks=())
    
    # 차수별로 Polynomial 변환
    pipeline = Pipeline([('poly', PolynomialFeatures(degree=degrees[i], include_bias=False)),
                        ('linear', LinearRegression())])
    pipeline.fit(X.reshape(-1,1), y)
    
    # 교차검증으로 다항회귀 평가
    scores = -cross_val_score(pipeline, X.reshape(-1,1), y, scoring='neg_mean_squared_error', cv=10)
    # 회귀계수 추출
    print('\ndegree {0} 회귀계수:{1}'.format(degrees[i], np.round(pipeline.named_steps['linear'].coef_, 2)))
    print('degree {0} mse:{1}'.format(degrees[i], np.round(np.mean(scores), 2))) # 교차검증별 mse를 평균냄
    
    # 0부터 1까지 테스트 데이터 세트를 100개로 나눠 예측 수행
    # 테스트 데이터 세트에 회귀 예측을 수행하고 예측 곡선과 실제 곡선을 그려서 비교
    X_test = np.linspace(0,1,100)
    # 예측값 곡선
    plt.plot(X_test, pipeline.predict(X_test[:, np.newaxis]), label='Model')
    # 실제값 곡선
    plt.plot(X_test, true_fun(X_test), '--', label='True function')
    # 학습 데이터
    plt.scatter(X, y, edgecolor='b', s=20, label='Samples')
    
    plt.xlabel('x'); plt.ylabel('y'); plt.xlim((0,1)); plt.ylim((-2,2)); plt.legend(loc='best')
    plt.title('degree {0}\nMSE = {1:.2e}(+/- {2:.2e})'.format(degrees[i], scores.mean(), scores.std()))
    
plt.show()

실선은 예측곡선, 점선은 실제 데이터 세트(테스트 데이터) X(X_test), y(true_func(X_test))의 코사인 곡선이다. 학습데이터는 0부터 1까지 30개의 랜덤의 X값과 그에 따른 코사인 y값에 노이즈를 추가해 구성했으며 MSE 평가는 학습 데이터를 10fold로 나누어 측정해서 평균한 것이다.

  • degree1 예측곡선은 단순한 직선으로 단순 선형회귀와 똑같다. 실제 데이터 세트인 코사인 데이터 세트를 직선으로 예측하기에는 너무 단순해 보인다. 예측 곡선이 학습 데이터의 패턴을 제대로 반영하지 못하고 있는 과소적합 모델이다. MSE 값은 약 0.41이다.
  • degree4 예측곡선은 실제 데이터 세트와 유사하다. 노이즈까지 예측하지는 못했지만, 학습 데이터 세트를 비교적 잘 반영해 코사인 곡선 기반으로 테스트 데이터를 잘 예측한 곡선을 가진 모델이다. MSE 값은 약 0.04로 가장 뛰어난 예측 성능을 나타낸다.
  • degree15 예측곡선은 학습 데이터의 노이즈까지 지나치게 반영한 결과, 예측곡선이 학습 데이터 세트만 정확히 예측하고, 테스트 데이터의 실제 곡선과는 완전히 다른 형태이다. 결과적으로 학습 데이터에 너무 충실하게 맞춘 과적합이 심한 모델이고 MSE 값은 182815433이 될 정도로 말도 안되는 오류값이 발생했다.  
  • degree 15 회귀계수는 degree1, 4와 비교할 수 없을 정도로 매우 큰 값이다. degree 15라는 복잡한 다항식을 만족하기 위해 계산된 회귀계수는 결국 현실과 너무 동떨어진 예측 결과를 보여준다.

→결국 좋은 예측모델은 degree1과 같이 학습 데이터의 패턴을 지나치게 단순화한 과소적합 모델도 아니고, degree15와 같이 모든 학습 데이터의 패턴을 하나하나 감안한 지나치게 복잡한 과적합 모델도 아닌, 학습 데이터의 패턴을 잘 반영하면서 복잡하지 않은 균형 잡힌 모델이다.


편향-분산 트레이드오프(Bias-Variance Trade off)

degree1의 모델: 매우 단순화된 모델로 데이터를 충분히 표현하지 못함 → 높은 편향을 가짐(낮은 분산) → 지나치게 특정 방향으로 치우쳤다는 의미 → 과소적합(underfitting)

degree15의 모델: 학습 데이터 하나하나의 특성을 반영하면서 매우 복잡한 모델로 데이터를 너무 과하게 표현함 → 높은 분산을 가짐(낮은 편향) → 지나치게 높은 변동성을 가졌다는 의미 → 과적합(overfitting)

 

편향과 분산은 한쪽이 높으면 한쪽이 낮아지는 트레이드오프 관계에 있다. 따라서 둘을 적절하게 조절하여 전체 오류가 가장 낮아지는 '골디락스' 지점을 찾는 것이 매우 중요하다. 즉, 편향-분산 트레이드오프는 머신러닝이 극복해야 할 가장 중요한 이슈 중 하나로 머신러닝 모델을 만들때 과소적합, 과적합 모델이 아닌 적절한 모델을 만들어야 한다.

 

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats
from sklearn.datasets import load_boston
import warnings
warnings.filterwarnings('ignore')
%matplotlib inline

# boston 데이터 세트 로드
boston = load_boston()

# boston 데이터 세트 DataFrame 변환
bostonDF = pd.DataFrame(boston.data, columns=boston.feature_names)

# boston 데이터 세트의 target array는 주택 가격. 이를 PRICE 컬럼으로 DataFrame에 추가
bostonDF['PRICE'] = boston.target
bostonDF.head()

변수 설명

  • CRIM: 지역별 범죄 발생률
  • ZN: 25,000평방 피트를 초과하는 거주 지역의 비율
  • INDUS: 비상업 지역 넓이 비율
  • CHAS: 찰스강에 대한 더미변수(강의 경계에 위치한 경우1, 아니면 0)
  • NOX:일산화질소 농도
  • RM: 거주할 수 있는 방 개수
  • AGE: 1940년 이전에 건축된 소유 주택의 비율
  • DIS: 5개 주요 고용센터까지의 가중 거리
  • RAD: 고속도로 접근 용이도
  • TAX: 10,000달러당 재산세율
  • PTRATIO: 지역의 교사와 학생 수 비율
  • B: 지역의 흑인 거주 비율
  • LSTAT: 하위 계층의 비율
bostonDF.info()

bostonDF.isnull().sum()

결측값은 없으며 피처들이 모두 float형이다.

 

다음으로 각 피처와 타겟값 사이의 상관관계를 살펴보자.(참고로 seaborn의 regplot() 함수는 x, y 축 값의 산점도와 함께 선형 회귀 직선을 그려줌)

# 2X4 subplots 이용. axs는 4*2개의 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 # 나머지
    sns.regplot(x=feature, y='PRICE', data=bostonDF, ax=axs[row][col])

타겟값(주택 가격)과 가장 상관관계가 있는 피처는 RM, LSTAT이다.

RM은 양의 상관관계를 보이며, 방의 크기가 클수록 주택 가격이 커짐

LSTAT는 음의 상관관계를 보이며, 하위 계층의 비율이 높아질수록 주택 가격이 낮아짐

 

이제 train, test 데이터 분리하고 학습/예측/평가를 수행하자.

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_features = bostonDF.drop(['PRICE'], axis=1, inplace=False)

X_train, X_test, y_train, y_test = train_test_split(X_features, y_target, test_size=0.3, random_state=156)

# LinearRegression으로 학습/예측/평가 수행
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)))

선형회귀로 학습한 주택가격 모델의 평가 결과를 보면 만족스러운 결과는 아니다.

일단, 선형회귀로 학습한 주태가격 모델의 절편과 회귀계수를 보자.

print('절편:', lr.intercept_)
print('회귀계수:', np.round(lr.coef_, 1))

# 각 피처와 매핑하여 정렬
coeff = pd.Series(data=np.round(lr.coef_, 1), index=X_features.columns)
coeff.sort_values(ascending=False)

NOX 피처의 회귀계수가 음으로 너무 커 보인다. 이는 나중에 규제를 공부한 뒤 최적화를 수행하자.

 

이번에는 교차검증을 통해 MSE와 RMSE를 측정해보자.

from sklearn.model_selection import cross_val_score

y_target = bostonDF['PRICE']
X_features = bostonDF.drop(['PRICE'], axis=1, inplace=False)
lr = LinearRegression()

mse_scores = -cross_val_score(lr, X_features, y_target, scoring='neg_mean_squared_error', cv=5)
rmse_scores = np.sqrt(mse_scores)
avg_rmse = np.mean(rmse_scores)

print('5folds의 개별 mse scores:', np.round(mse_scores, 2))
print('5folds의 개별 rmse scores:', np.round(rmse_scores, 2))
print('5folds의 평균 rmse:{0:.3f}'.format(avg_rmse))

 

 

LinearRegression 클래스

→ 선형 회귀(규제 적용 x)

 

class sklearn.linear_model.LinearRegression(fit_intercept=True, normalize=False, copy_X=True, n_jobs=1)

 

○ LinearRegression 클래스는 실제값과 예측값의 RSS를 최소화해 OLS(Ordinary Least Squares; 최소 제곱법) 추정 방식으로 구현한 클래스

○ 주요 입력 파라미터

- fit_intercept: w0(절편, bias) 값을 계산할 것인지 말지를 지정. 만일 False로 지정하면 절편이 사용되지 않고 0으로 지정됨

- normalize: 회귀를 수행하기 전에 입력 데이터 세트를 정규화할 것인지 결정(사용 x, 전처리 과정에서 스케일링 진행 → 디폴트로 나둠)

○ 주요 속성

- coef_: 회귀계수가 배열 형태로 저장

- intercept_: w0(절편, bias)값

 

선형 회귀의 다중 공선성 문제

선형 회귀와 같은 OLS 기반의 회귀 계수를 계산하는 회귀 모델은 입력 피처의 독립성에 많은 영향을 받는다. 즉, 피처간의 상관관계가 매우 높은 경우 분산이 매우 커져서 오류에 민감해진다. 이를 다중 공선성(multi-collinearity)라고 한다.

일반적으로 상관관계가 높은 피처가 많은 경우 독립적인 중요한 피처만 남기고 제거하거나 규제를 적용한다. 

 

 

회귀 평가 지표

"실제값과 예측값의 차이를 기반"

→ 실제값과 예측값의 차이를 그냥 더하면 +와 -가 섞여서 오류가 상쇄되서 정확한 지표가 될 수 없기 때문에 오류의 절대값 평균이나 제곱의 평균, 또는 제곱한 뒤 루트를 씌운 평균값을 구함

→ 실제값과 예측값의 차이, 다시 말해 오류가 작을수록 좋은 평가 결과이기 때문에 평가 지표가 작을수록 좋은 결과임

R^2은 설명력으로 회귀모델이 데이터를 얼마나 잘 설명하는지를 나타냄

mean_squared_error() 함수는 squared 파라미터가 기본적으로 True이다. 즉 MSE는 사이킷런에서 mean_squared_error(실제값, 예측값, squared=True)이며 RMSE는 mean_squared_error(실제값, 예측값, squared=False)를 이용해 구하지만 헷갈리기 때문에 RMSE를 구할때는 MSE에 np.sqrt()를 이용해 구하자!

 

사이킷런 scoring 함수에 회귀 평가 적용 시 유의 사항

cross_val_score, GridSearchCV와 같은 scoring 함수에 회귀 평가 지표를 적용할 때 유의 사항이 있다.

'neg_'라는 접두어가 붙어있는데 이는 Negative(음수)를 의미하며, 기존의 회귀 평가 지표 값에 음수(-1)를 곱한다는 의미이다. 이렇게 'neg_'가 붙는 이유는 사이킷런의 scoring 함수가 score값이 클수록 좋은 평가 결과로 평가하기 때문이다. 하지만 실제값과 예측값의 차이를 기반으로 하는 회귀 평가 지표의 경우 값이 커지면 오히려 나쁜 모델이라는 의미이므로 음수(-1)를 곱해주는 것이다.

 

 

경사 하강법(Gradient Descent)

비용함수가 최소가 되는 회귀계수를 어떻게 구할 수 있을까?

솔루션이 바로 지금 소개할 경사 하강법(Gradient Descent, GD)이다.

 

경사 하강법은 비용함수 RSS를 최소화하는 방법을 직관적으로 제공하는 뛰어난 방식으로, 점진적인 반복 계산을 통해 회귀계수를 업데이트하면서 오류값이 최소가 되는 회귀계수를 구하는 방법이다.

 

그렇다면 솔루션인 '경사 하강법'을 통해 어떻게 하면 오류가 작아지는 방향으로 회귀계수를 업데이트할 수 있을까?

→ 미분을 통해 비용함수의 최소값을 찾음(미분은 증가 또는 감소의 방향성을 나타냄)

비용함수를 보면 제곱을 했기 때문에 회귀계수에 대한 2차 함수로서 아래 그림과 같은 포물선 형태이다. 2차 함수의 최저점은 2차 함수의 미분값인 1차 함수의 기울기가 가장 최소일 때다.

 

경사 하강법은 비용함수를 최초의 회귀계수(w) 값에서부터 미분을 적용한 뒤, 이 미분 값(기울기)이 계속 감소하는 방향으로 순차적으로 회귀계수를 업데이트한다.마침내 더 이상 미분된 1차 함수의 기울기가 감소하지 않는 지점을 비용함수가 최소인 지점으로 간주하고, 그때의 회귀계수를 반환한다.

 

경사 하강법 수식 정리 및 파이썬 코드 구현

경사 하강법 수식 정리 전 알아야 할 'Chain Rule'

 

 

<경사 하강법 수식 정리>

1. 비용함수 RSS(w0, w1)을 편의상 R(w)로 지칭. R(w)를 미분해 미분 함수의 최소값을 구해야 하는데, R(w)는 두 개의 회귀계수인 w0와 w1을 각각 가지고 있기 때문에 일반적인 미분을 적용할 수 없고, w0, w1 각 변수에 편미분을 적용해야 한다.

2. w1, w0의 편미분 결과값을 반복적으로 보정하면서 w1, w0값을 업데이트하면 비용함수 R(w)가 최소가 되는 w1, w0값을 구할 수 있다.

  • 이때, 편미분 값이 너무 클 수 있기 때문에 보정 계수 η를 곱하는데, 이를 '학습률'이라고 함
  • 업데이트는 이전 회귀계수에서 편미분 결과값을 빼면서 적용함(아래와 같음)

→ 새로운 w1, 새로운 w0를 반복적으로 업데이트 하면서 비용함수가 최소가 되는 값을 찾음

 

경사 하강법의 프로세스를 정리를 하자면

 

 

<경사 하강법 파이썬 코드 구현>

● 실습을 위한 y=4x+6 시뮬레이션 데이터 생성(단순 선형회귀로 예측할 만한 데이터 생성)

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

np.random.seed(0)

noise = np.random.randn(100,1)
X = 2 * np.random.rand(100,1)
y = 6 + 4 * X + noise

plt.scatter(X, y)

● 경사하강법

# 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
# get_weight_updates 함수를 이용해 경사 하강 방식으로 반복적으로 수행하여 w1, w0를 업데이트하는 함수 생성

# 입력 인자 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

● 비용함수(RSS) 값 계산, 회귀계수 계산

# 비용함수 정의
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('cost:{0:.4f}'.format(get_cost(y, y_pred)))

● 앞에서 구한 y_pred에 기반해 회귀선 시각화

plt.scatter(X, y)
plt.plot(X, y_pred)

 

Reference)

 

예측값에 대한 설명 - 인프런 | 질문 & 답변

예측값에 대한 설명 부분에서, 100개의 데이터 X(1, 2, ... 100)이 있다면 예측값(y_pred)은 w0+X(1)w1, w0+X(2)w1, ... w0+X(100)w1 이 되어야하는것 아닌가요? - 질문 & 답변 | 인프런

www.inflearn.com

 

 

경사하강법 질문드립니다 - 인프런 | 질문 & 답변

강사님 안녕하세요 ㅎㅎ 경사하강법 강의를 듣다가 잘 이해가 가지 않아 질문드립니다. 강의 5.4의 get_weight_updates 함수에서 y_pred 에서 X의 개수가 여러개고 W1이 1개인데 왜 w1.T인지 설명해주실수

www.inflearn.com

 

 

get_cost 함수 w1[0,0]가 들어가는 이유 - 인프런 | 질문 & 답변

안녕하세요 강사님 수업 잘 듣고있습니다!파이썬 코드로 경사 하강법 구현하기에서 def get_cost 함수 y_pred = w1[0,0] * X + w0 부분에 질문이 있습니다.현재 코드가 w1와 w0 둘다 shape이 (1,1)이라서 그런

www.inflearn.com


손실함수가 최소가 되는 회귀계수를 찾아가는 경사 하강법에도 단점이 존재한다. 경사 하강법은 모든 학습 데이터에 대해 반복적으로 비용함수 최소화를 위한 값을 업데이트하기 때문에 수행 시간이 매우 오래 걸린다. 

 

때문에 실전에서는 대부분 확률적 경사 하강법(SGD, Stochastic Gradient Descent)을 이용한다. 확률적 경사 하강법은 모든 학습 데이터가 아닌 일부 데이터만 이용해 회귀계수가 업데이트되는 값을 계산하므로 경사 하강법에 비해서 빠른 속도를 보장한다. 

 

→ 일반적으로 말하는 SGD는 실제로 미니 배치 경사 하강법(mini-BGD)이므로, 앞으로 SGD를 떠올릴 때 미니 배치 경사 하강법을 떠올리면 된다. 

 

Reference)

 

[혼공머] 배치와 미니 배치, 확률적 경사하강법

👩‍🔬 이번에는 혼공머 책의 챕터 4-2 파트입니다.📚 혼자공부하는머신러닝+딥러닝, 한빛미디어📄 Gradient Descent - 경사하강법, 편미분, Local Minimum📑 경사하강법(Gradient Descent)🔗 배치와 미니

velog.io

 

 

Optimizer 종류

위에서 언급했듯이, 경사 하강법의 문제점은 다음과 같다.

첫째, 한번 학습할 때마다 모든 데이터셋을 이용함

둘째, 학습률 정하기

셋째, 지역 최소점(Local Minima) 

넷째, 메모리 한계 

 

이런 경사 하강법의 문제들을 개선하기 위해 여러 Optimizer 종류들이 존재한다. 

 

아래의 링크를 보면서 수식에 너무 얽매이지 말고 이런것들이 있구나 정도로만 이해하고, 추후 딥러닝을 공부할 때 집중적으로 다룰 것이다.

 

Reference)

 

[ML] 신경망에서의 Optimizer - 역할과 종류

지난 포스트에서 손실함수란 무엇이고, 어떻게 하면 손실함수의 최솟값이 되는 점을 찾는지 살펴보았다. 우리는 '최적화(optimization)'를 통해 파라미터를 잘 찾아서 문제 해결에 적합한 모델을 결

heeya-stupidbutstudying.tistory.com

 

 

4-3. 경사하강법 (Gradient Descent)

파이썬 머신러닝 완벽 가이드 - Chapter 5. 회귀

velog.io


● 확률적 경사하강법

- 전체 X, y 데이터에서 랜덤하게 batch_size만큼 데이터를 추출해 사용(일부 데이터)

def stochastic_gradient_descent_steps(X, y, batch_size=10, iters=1000):
    w0 = np.zeros((1,1))
    w1 = np.zeros((1,1))
    
    for ind in range(iters):
        np.random.seed(ind)
        
        # 전체 X, y 데이터에서 랜덤하게 batch_size만큼 데이터를 추출해 sample_X, sample_y로 저장
        stochastic_random_index = np.random.permutation(X.shape[0]) # shuffle된 배열 생성
        sample_X = X[stochastic_random_index[0:batch_size]]
        sample_y = y[stochastic_random_index[0:batch_size]]
        
        # 랜덤하게 batch_size만큼 추출된 데이터 기반으로 w1_update, w0_update 계산 후 업데이트
        w1_update, w0_update = get_weight_updates(w1, w0, sample_X, sample_y, learning_rate=0.01)
        w1 = w1 - w1_update
        w0 = w0 - w0_update
        
    return w1, w0
w1, w0 = stochastic_gradient_descent_steps(X, y, iters=1000)
print('w1:', round(w1[0,0], 3), 'w0:', round(w0[0,0], 3))

y_pred = w1[0,0] * X + w0
print('cost:{0:.4f}'.format(get_cost(y, y_pred)))

지금까지는 단순 선형회귀에서 경사 하강법을 적용해 봤다. 피처가 여러개인 경우 어떻게 회귀계수를 도출할 수 있을까?

피처가 1개인 경우와 유사하게 내적을 이용한다.(page 319 ~ 320)

단순 선형 회귀는 독립변수와 종속변수가 각각 1개인 선형 회귀이다.

 

예를 들어, 주택 가격이 주택의 크기로만 결정되는 단순 선형 회귀로 가정하면 집이 넓으면 가격이 높아지는 경향이 있기 때문에 아래 그림과 같이 주택 가격은 주택 크기에 대해 선형(직선 형태)의 관계로 표현할 수 있다.

위에서 볼 수 있듯이 실제 데이터는 우리가 예측한 회귀 모델에 딱 들어맞을 순 없다. 따라서 실제 데이터와 우리가 예측한 회귀 모델의 차이에 따른 오류값(잔차)이 존재할 수밖에 없다.

결국 우리는 현실을 잘 반영하는 예측 회귀 모델을 구하는 것이 관건이다. 즉, 예측 회귀 모델이 현실을 잘 반영하는지 안하는지에 대한 평가는 실제 데이터와 예측 회귀 모델 사이의 잔차가 작으면 현실을 잘 반영하는 좋은 모델이다.


→ 머신러닝에서 최적의 회귀 모델을 만든다는 것은 바로 전체 데이터의 잔차 합이 최소가 되는 모델을 만든다는 의미이고 동시에 잔차 합이 최소가 될 수 있는 최적의 회귀계수를 찾는다는 의미임

 

RSS

예측한 회귀 모델 위아래로 실제 데이터가 분포해 있으므로 잔차는 +나 -가 될 수 있다. 그래서 전체 데이터의 잔차 합을 구하기 위해 단순히 더했다가는 뜻하지 않게 잔차 합이 0이 되거나 크게 줄어들 수 있다.

→ 잔차 합을 계산할 때는

첫째, 잔차에 절대값을 취해서 더하거나(Mean Absolute Error)

둘째, 잔차에 제곱을 취해서 더함(RSS, Residual Sum of Square)

 

일반적으로 미분 등의 계산을 편리하게 하기 위해서 RSS 방식으로 잔차 합을 구함

 

 

RSS = (#1 주택 가격 - (w0+w1*#1 주택 크기))^2

+ (#2 주택 가격 - (w0+w1*#2 주택 크기))^2

+ (#3 주택 가격 - (w0+w1*#3 주택 크기))^2

+ ... (모든 학습 데이터에 대해 RSS 수행)

 

 

 

 

RSS는 학습 데이터의 건수로 나누어서 다음과 같이 정규화된 식으로 표현할 수 있다.

● RSS는 이제 변수가 w0, w1인 식으로 표현할 수 있으며, 이 RSS를 최소로 하는 w0, w1, 즉 회귀계수를 학습을 통해서 찾는 것이 머신러닝 기반 회귀의 핵심 사항임

→ RSS는 회귀식의 독립변수 x, 종속변수 y가 중심 변수가 아니라 w 변수(회귀계수)가 중심 변수임을 인지하는 것이 중요!(학습 데이터로 입력되는 독립변수와 종속변수는 RSS에서 모두 상수로 간주)

● 회귀에서 RSS는 비용(Cost)이며, 회귀계수로 구성되는 RSS를 비용 함수라고 함(비용 함수를 손실 함수(loss function)라고도 부른다.)

 

머신러닝 회귀 알고리즘은 데이터를 계속 학습하면서 이 비용 함수가 반환하는 값을 지속해서 감소시키고, 최종적으로는 더 이상 감소하지 않는 최소의 오류값을 구한다.

이 비용이 최소가 되는 파라미터(여기선 회귀계수)를 찾기 위해 경사 하강법을 사용한다.

지도학습

1. 분류

결정값이 카테고리 값(이산값)

2. 회귀

결정값이 숫자값(연속값)


회귀 개요

회귀분석은 데이터 값이 평균과 같은 일정한 값으로 돌아가려는 경향을 이용한 통계학 기법이다. 통계학 용어를 빌리자면 회귀는 여러 개의 독립변수와 한 개의 종속변수 간의 상관관계를 모델링하는 기법을 통칭한다.

 

예를 들어 아파트의 방 개수, 방 크기, 주변 학군 등 여러 개의 독립변수에 따라 아파트 가격이라는 종속변수가 어떤 관계를 나타내는지 모델링하고 예측하는 것

 

Y: 종속변수

X1, X2, ..., Xn: 독립변수

W1, W2, ..., Wn: 각 독립변수의 값에 영향을 미치는 회귀계수(Regression coefficients)

 

머신러닝 관점에서 독립변수는 피처에 해당되며, 종속변수는 결정값에 해당된다.

따라서 머신러닝 회귀 예측의 핵심은 '주어진 피처와 결정값 데이터 기반에서 학습을 통해 최적의 회귀계수를 찾아내는 것'


회귀의 유형

회귀는 회귀계수의 선형/비선형 여부, 독립변수의 개수, 종속변수의 개수에 따라 여러 가지 유형으로 나눌 수 있다.

1. 선형/비선형

회귀계수가 선형이면 선형 회귀 / 회귀계수가 비선형이면 비선형 회귀

2. 독립변수의 개수

독립변수 개수가 1개이면 단일 회귀 / 독립변수 개수가 여러개이면 다중 회귀

3. 종속변수의 개수

종속변수 개수가 1개이면 단변량 회귀 / 종속변수 개수가 여러개이면 다변량 회귀


선형 회귀의 종류

여러 가지 회귀 중에서 선형 회귀가 가장 많이 사용된다. 선형 회귀는 실제값과 예측값의 차이를 최소화하는 직선형 회귀선을 최적화하는 방식

 

선형 회귀 모델은 규제(Regularization) 방법에 따라 다시 별도의 유형으로 나뉠 수 있다. 여기서 규제는 일반적인 선형 회귀의 과적합 문제를 해결하기 위해서 회귀계수에 패널티 값을 적용하는 것이다.

 

● 일반 선형 회귀

예측값과 실제값의 RSS(Residual Sum of Squares)를 최소화할 수 있도록 회귀계수를 최적화하며, 규제(Regularization)를 적용하지 않은 모델

 

● 릿지(Ridge) 회귀

릿지 회귀는 선형 회귀에 L2 규제를 적용한 회귀 모델

L2 규제는 상대적으로 큰 회귀계수 값의 예측 영향도를 감소시키기 위해서 회귀계수 값을 더 작게 만드는 규제

 

● 라쏘(Lasso) 회귀

라쏘 회귀는 선형 회귀에 L1 규제를 적용한 회귀 모델

L1 규제는 예측 영향력이 작은 피처의 회귀계수를 0으로 만들어 회귀 예측 시 피처가 선택되지 않게 하는 것 → 이러한 특성으로 L1 규제는 피처 선택 기능으로도 불림

 

● 엘라스틱넷(ElasticNet) 회귀

L2, L1 규제를 함께 결합한 모델

주로 피처가 많은 데이터 세트에서 적용되며, L1 규제로 피처의 개수를 줄임과 동시에 L2 규제로 회귀계수 값의 크기를 조정

 

● 로지스틱(Logistic) 회귀

로지스틱 회귀는 회귀라는 이름이 붙어 있지만, 사실은 분류에 사용되는 선형 모델 → 로지스틱 회귀는 매우 강력한 분류 알고리즘

이진 분류뿐만 아니라 희소 영역의 분류(예를 들어 텍스트 분류)에서 뛰어난 예측 성능을 보임 → 이진 분류에 아주 효과적

 

 

 

+ Recent posts