漸入佳境

선형회귀와 스케일링

DevLog

업무 외 지시는 부당지시 아닌가여? 근데 흥미로워

바빠 죽겠던 어느날, 상사가 코드를 좀 봐줄 수 있냐고 부탁을해서 당연히 업무와 관련된 건줄 알고 그러겠다 했다. 근데 내용을 받아보니, 업무가 아닌 본인이 수강하고 있는 강의의 과제였던 것이다…
상사의 궁금사항은 이러했다.

선형회귀모델을 만들었는데, 로우데이터, 정규화 데이터, 표준화 데이터의 MSE 값이 모두 동일하게(완벽히 동일은 아니고 소수점 7자리 까지 동일함) 나온다는 것이었다. 데이터 스케일링을 하면 이상치 값들이 정제되면서 모델의 성능이 개선되어야 하는것이 아니냐? 라는게 상사의 질문.

보통 그렇지..? 모델 학습 전에 데이터들의 크기를 맞춰주기 위해 스케일링을 하긴 하지..? 근데 MSE 값이 왜 동일하게 나왔지? 순간 나도 궁금해졌다..

과제의 마지막 문제가
“일반 선형회귀모델(로우데이터), 정규화를 거친 선형회귀모델, 표준화를 거친 선형회귀모델의 결과를 비교분석하여 선형회귀모델의 특성을 찾아내라” 였다.

만약 스케일링이 이 과제의 중요한 개념이었다면, 스케일링을 통한 모델 성능 향상을 보여라,, 뭐 이런식으로 문제가 진행되었을 텐데. 선형회귀모델의 특성을 찾아라?

“선형회귀모델의 성능과 데이터 스케일링의 관계” 를 생각해볼 수 있는 좋은 기회였다.

결론을 미리 말하자면, 이 문제는 OLS 기반의 선형회귀 모델의 MSE 측정 이라는 개념이 중요했다. (근데 사실 나 아직 정답모른다.. 상사가 과제 결과를 말 안해줌…)

데이터 스케일링은 왜 하는걸까?

지금까지 진행했던 프로젝트들에서 모델 개발할 때 데이터의 왜곡을 해결하기위해 아묻따 스케일링 해버렸던 과거의 내가 떠오른다.

다양한 피처들(원인 변수)이 결과에 주는 영향을 제대로 파악하려면, 특정 피처가 자체 데이터의 특성으로 인해 모델에 과도하게 영향을 주는 것을 방지해야 한다.
소득 변수는 범위가 몇백에서 몇천씩 벌어지지만, 나이 변수는 범위를 나타내는 단위가 아무리 많아봐야 10의 자릿수이다.
이 두 변수를 아무런 정제없이 그냥 학습시켜버리면 학습 과정에 왜곡이 발생할 수 있기 때문에 우리는 데이터의 크기(단위)를 맞춰주기 위해 스케일링을 한다.

특히 몇 알고리즘은 스케일링이 필수이다. ML 모델 중 거리기반으로 데이터를 학습하는 친구들이 있다. KNN, K-Means, SVM, PCA, 등
데이터들간의 거리를 기반으로 내적 연산을 진행하는데, 이때 스케일링을 하지 않으면 연산값이 데이터 단위의 크기에 의존해버리게 된다.
그렇기 때문에 ‘거리’ 뿐 아니라 변수 간 분산과 공분산을 기준으로 학습하는 차원축소 모델 PCA, LDA에서도 스케일링은 중요하다.

또한 경사하강법기반 모델에서도 스케일링은 매우 중요하다. 로우데이터들의 스케일 차이가 크면 최적의 가중치로 수렴하는데 시간이 오래걸리 수 밖에 없다. 이러한 문제를 ‘경사가 비대칭적으로 계산되어 학습이 매우 느려진다’ 라고 표현하더라.

즉, 스케일링을 하는 이유는 입력 변수의 크기 차이를 조정함으로써 데이터 왜곡, 성능 저하, 학습 속도 저하 의 문제를 해결하기 위함이다.

근데 스케일링 별로 안중요할 때도 있음

스케일링이 중요하지 않다는 것은 모델의 성능이 데이터의 스케일에 덜 민감하다는 뜻이다.
바로 트리 기반 모델 (DT, RF, XGB….)
이 친구들은 특정 임계점을 기준으로 0, 1 로 구분되어지다보니 데이터의 크기보다는 데이터의 상대적인 크기 순서가 중요하다.
즉, 스케일링을 하면 데이터의 단위는 조정돼더라도, 그 안에서 데이터들간의 크기 순서는 일정하기 때문에 스케일링이 중요하지 않다는 것이다.

그렇다고 해서 트리기반에서 스케일링이 필요없다는 것은 아니다. 경사하강법을 쓰는 XGB나 LightGBM 에서는 잔차 줄이는 속도를 개선하기 위해 스케일링을 해주면 좋다.

그럼 선형회귀는 왜?

선형회귀 모델 중 경사하강법 기반의 모델은 제외(이미 언급했듯이 이 모델들은 스케일링 해야함)

과제에서 사용한 선형회귀 모델은 sklearn 라이브러리의 LinearRegression 클래스이다. 이 알고리즘은 기본적으로 OLS(Ordinary Least Squares , 최소제곱법)을 사용한다.
독립변수와 종속변수 간의 오차(잔차) 제곱의 합을 최소화하는 방식으로 회귀 계수를 찾는 방식이다.

이 클래스의 소스코드를 보면 scipy.linalg.lstsq 를 사용하여 계수를 계산하는데 저 함수가 행렬 연산을 통해 최적의 가중치를 구하는 방식이라고 한다. (경사하강법x)

LinearModel 소스코드 (해당 소스코드 682 Line 에 나와있음)

$$ x = (A^T A)^{-1} A^T b $$

  • $A$: $(m \times n)$ 크기의 입력 행렬(디자인 행렬).
  • $b$: $(m \times 1)$ 크기의 출력 벡터(목표값).
  • $x$ : $(n \times 1)$ 크기의 최적의 가중치 벡터(회귀 계수).
  • $A^T$: $A$ 의 전치 행렬.
  • $(A^T A)^{-1}$: $(A^T A)$ 의 역행렬.

이 공식은 $Ax = b$ 를 근사적으로 만족하면서 , 잔차의 제곱합을 최소화하는 $x$를 구하는 식이다.
나도 저 계산과정은 머리아파서 들여다보기 싫당. 파이썬 예제로 보자!

# 원본 데이터
# 원본 데이터 (약간의 오차를 포함한 종속 변수)
A_original = np.array([[1, 10],
                       [2, 16],
                       [3, 20],
                       [4, 24],
                       [5, 30]])  # 독립 변수(상관관계가 높게 설정)
b = np.array([14.8, 29.5, 45.2, 59.7, 75.1])  # 종속 변수 

# 스케일링 전 회귀 계수 계산
A_transpose_original = A_original.T
x_original = np.linalg.inv(A_transpose_original @ A_original) @ A_transpose_original @ b
y_pred_original = A_original @ x_original
mse_original = mean_squared_error(b, y_pred_original)

# 스케일링 (표준화)
scaler = StandardScaler()
A_scaled = scaler.fit_transform(A_original)

# 스케일링 후 회귀 계수 계산
A_transpose_scaled = A_scaled.T
x_scaled = np.linalg.inv(A_transpose_scaled @ A_scaled) @ A_transpose_scaled @ b
y_pred_scaled = A_scaled @ x_scaled
mse_scaled = mean_squared_error(b, y_pred_scaled)

# 스케일링된 계수를 원래 스케일로 변환
std = scaler.scale_  # 각 피처의 표준편차
mean = scaler.mean_  # 각 피처의 평균

# 계수 변환
x_rescaled = x_scaled / std
# 절편(intercept) 계산
intercept_rescaled = -np.sum((x_scaled * mean) / std) + np.mean(b)

# 변환된 계수를 사용해 원본 데이터로 예측
y_pred_rescaled = A_original @ x_rescaled + intercept_rescaled
mse_rescaled = mean_squared_error(b, y_pred_rescaled)

행렬 계산 과정을 코드로 구현한 것인데, 출력 결과를 보자.

=== 스케일링 전 ===
계수 (Coefficients): [15.39130435 -0.06557971]
예측 값 (Predictions): [14.73550725 29.73333333 44.86231884 59.99130435 74.98913043]
MSE (Mean Squared Error): 0.05395652173913026

=== 스케일링 후 (원본 스케일로 변환됨) ===
계수 (Coefficients): [15.2   -0.025]
절편 (Intercept): -0.24000000000032884
예측 값 (Predictions): [14.71 29.76 44.86 59.96 75.01]
MSE (Mean Squared Error): 0.05340000000000017

데이터를 스케일링한다는 것은 데이터의 표현방식을 바꾸는 것이기에 결과 해석에 쓰이는 계수값은 당연히 변한다. 하지만 예측 값은 유사하게 나온다.
왜냐하면 데이터의 표현이 바뀌었을 뿐 데이터들간의 상대적인 관계는 유지되었기 때문이다.

따라서 예측값과 실제값의 오차의 제곱을 계산하는 MSE 값도 두 결과과 매우 유사하다. 스케일링 결과가 조금 더 성능이 높게 나오지만, 학습 데이터의 수가 많아질 수록 저 차이가 줄어든다.

주목할 점은 독립변수간의 상관관계를 높게 설정해두었는데, 만약 상관관계가 약하다면 결과는 또 달라진다.(스케일링이 결과에 유의미한 영향을 준다.)

후두다닥 마무리

그래서 결론은 ~
상사의 과제에서 스케일 유무에 관계없이 MSE 값이 유사했던 이유는~
우선 OLS 기반의 선형회귀 모델을 사용했기 때문에, 스케일링으로 데이터의 크기는 조정했을지라도 데이터의 상대적 차이에는 큰 영향을 주지 못했기 때문이라는 게 내가 도출한 결론이다. (이라기 보다는 지인 도움과 구글 서칭, GPT의 합작)

OLS 기반 선형회귀 모델에서 독립변수에 스케일링을 하더라도, 그 데이터들 간의 상대적 차이는 유사하게 유지되기 때문에 모델은 동일한 데이터 관계를 학습하게 되므로 예측 결과가 동일할 수 있다.

그렇다고 선형회귀를 쓸 때 스케일링을 할 필요가 없다라는건 절대 아니고!! 데이터의 분포에 따라 스케일링이 유의미한 성능 개선을 보일 수도 있으니까…

아래는 ML 직무로 근무 중인 지인한테 받은 답변.

Your Alt Text