본문으로 건너뛰기

040 회귀에서의 특성 스케일링

키워드: 스케일링, 정규화, StandardScaler

개요

특성 스케일링은 서로 다른 범위의 특성을 비슷한 스케일로 맞추는 전처리 기법입니다. 일부 회귀 알고리즘은 스케일링 없이도 잘 작동하지만, 다른 알고리즘은 스케일링이 필수입니다.

실습 환경

  • Python 버전: 3.11 권장
  • 필요 패키지: flaml[automl], scikit-learn, pandas
pip install flaml[automl] scikit-learn pandas matplotlib

스케일링이 필요한 이유

특성 범위 차이 문제

import pandas as pd
import numpy as np

# 040 범위가 다른 특성들
data = pd.DataFrame({
'income': [50000, 80000, 35000, 120000, 65000], # 단위: 원
'age': [25, 35, 28, 45, 32], # 단위: 세
'rooms': [3, 5, 2, 6, 4], # 단위: 개
'distance_km': [2.5, 8.0, 1.2, 15.0, 5.5] # 단위: km
})

print("특성별 범위:")
print(data.describe().loc[['min', 'max', 'mean', 'std']].round(2))

print("\n문제점:")
print(" income 범위: 35,000 ~ 120,000")
print(" age 범위: 25 ~ 45")
print(" → 거리 기반 알고리즘에서 income이 지배적")

알고리즘별 스케일링 필요성

scaling_requirements = {
'알고리즘': ['Linear Regression', 'Ridge/Lasso', 'SVR', 'KNN', 'Decision Tree', 'Random Forest', 'LightGBM/XGBoost'],
'스케일링 필요': ['권장', '필수', '필수', '필수', '불필요', '불필요', '불필요'],
'이유': [
'계수 해석 용이',
'규제 항에 영향',
'거리 기반',
'거리 기반',
'분할 기준에 무관',
'트리 앙상블',
'트리 앙상블'
]
}

print("\n알고리즘별 스케일링 필요성:")
print(pd.DataFrame(scaling_requirements).to_string(index=False))

스케일링 방법

1. StandardScaler (Z-score 정규화)

from sklearn.preprocessing import StandardScaler

# 040 원본 데이터
X = data.values

# 040 StandardScaler
scaler_std = StandardScaler()
X_std = scaler_std.fit_transform(X)

print("StandardScaler (평균=0, 표준편차=1):")
print(f" 변환 전: mean={X.mean(axis=0).round(2)}")
print(f" 변환 후: mean={X_std.mean(axis=0).round(2)}")
print(f" 변환 전: std={X.std(axis=0).round(2)}")
print(f" 변환 후: std={X_std.std(axis=0).round(2)}")

2. MinMaxScaler (0-1 정규화)

from sklearn.preprocessing import MinMaxScaler

# 040 MinMaxScaler
scaler_mm = MinMaxScaler()
X_mm = scaler_mm.fit_transform(X)

print("\nMinMaxScaler (0~1 범위):")
print(f" 변환 전: min={X.min(axis=0)}, max={X.max(axis=0)}")
print(f" 변환 후: min={X_mm.min(axis=0).round(2)}, max={X_mm.max(axis=0).round(2)}")

3. RobustScaler (이상치에 강건)

from sklearn.preprocessing import RobustScaler

# 040 이상치가 있는 데이터
X_outlier = X.copy()
X_outlier[0, 0] = 1000000 # 이상치 추가

# 040 RobustScaler
scaler_robust = RobustScaler()
X_robust = scaler_robust.fit_transform(X_outlier)

# 040 StandardScaler와 비교
X_std_outlier = StandardScaler().fit_transform(X_outlier)

print("\n이상치 있을 때 비교 (첫 번째 특성):")
print(f" StandardScaler 범위: {X_std_outlier[:, 0].min():.2f} ~ {X_std_outlier[:, 0].max():.2f}")
print(f" RobustScaler 범위: {X_robust[:, 0].min():.2f} ~ {X_robust[:, 0].max():.2f}")
print(" → RobustScaler가 이상치 영향 덜 받음")

스케일링 비교 시각화

import matplotlib.pyplot as plt

fig, axes = plt.subplots(2, 2, figsize=(12, 10))

# 040 원본
axes[0, 0].boxplot(X)
axes[0, 0].set_xticklabels(data.columns)
axes[0, 0].set_title('Original')

# 040 StandardScaler
axes[0, 1].boxplot(X_std)
axes[0, 1].set_xticklabels(data.columns)
axes[0, 1].set_title('StandardScaler')

# 040 MinMaxScaler
axes[1, 0].boxplot(X_mm)
axes[1, 0].set_xticklabels(data.columns)
axes[1, 0].set_title('MinMaxScaler')

# 040 RobustScaler
axes[1, 1].boxplot(scaler_robust.fit_transform(X)) # 이상치 없는 버전
axes[1, 1].set_xticklabels(data.columns)
axes[1, 1].set_title('RobustScaler')

plt.tight_layout()
plt.show()

FLAML에서 스케일링 효과

트리 기반 모델 (스케일링 불필요)

from flaml import AutoML
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.metrics import r2_score

# 040 데이터 준비
data = fetch_california_housing()
X_train, X_test, y_train, y_test = train_test_split(
data.data, data.target, test_size=0.2, random_state=42
)

# 040 스케일링 없이 (트리 모델)
automl_tree = AutoML()
automl_tree.fit(
X_train, y_train,
task="regression",
time_budget=60,
estimator_list=["lgbm", "rf", "xgboost"],
verbose=0
)

y_pred_tree = automl_tree.predict(X_test)
print(f"트리 모델 (스케일링 없음): R² = {r2_score(y_test, y_pred_tree):.4f}")
print(f" 최적 모델: {automl_tree.best_estimator}")

선형 모델 (스케일링 권장)

# 040 스케일링 적용
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# 040 선형 모델 비교
from sklearn.linear_model import Ridge

# 040 스케일링 없이
ridge_no_scale = Ridge()
ridge_no_scale.fit(X_train, y_train)
r2_no_scale = r2_score(y_test, ridge_no_scale.predict(X_test))

# 040 스케일링 후
ridge_scaled = Ridge()
ridge_scaled.fit(X_train_scaled, y_train)
r2_scaled = r2_score(y_test_scaled := scaler.transform(X_test), ridge_scaled.predict(X_test_scaled))

print(f"\nRidge Regression:")
print(f" 스케일링 없음: R² = {r2_no_scale:.4f}")
print(f" 스케일링 적용: R² = {r2_scaled:.4f}")

전체 estimator 비교

# 040 스케일링 없이 - 전체 모델
automl_no_scale = AutoML()
automl_no_scale.fit(
X_train, y_train,
task="regression",
time_budget=60,
verbose=0
)

# 040 스케일링 후 - 전체 모델
automl_scaled = AutoML()
automl_scaled.fit(
X_train_scaled, y_train,
task="regression",
time_budget=60,
verbose=0
)

print("\n전체 모델 비교:")
print(f" 스케일링 없음: R² = {r2_score(y_test, automl_no_scale.predict(X_test)):.4f}, 모델={automl_no_scale.best_estimator}")
print(f" 스케일링 적용: R² = {r2_score(y_test, automl_scaled.predict(X_test_scaled)):.4f}, 모델={automl_scaled.best_estimator}")

스케일링 파이프라인

from sklearn.pipeline import Pipeline

# 040 스케일링 + FLAML 파이프라인
class ScaledAutoML:
def __init__(self, scaler=None, **automl_kwargs):
self.scaler = scaler or StandardScaler()
self.automl_kwargs = automl_kwargs
self.automl = None

def fit(self, X, y):
X_scaled = self.scaler.fit_transform(X)
self.automl = AutoML()
self.automl.fit(X_scaled, y, **self.automl_kwargs)
return self

def predict(self, X):
X_scaled = self.scaler.transform(X)
return self.automl.predict(X_scaled)

def score(self, X, y):
X_scaled = self.scaler.transform(X)
return self.automl.score(X_scaled, y)

# 040 사용
scaled_automl = ScaledAutoML(
scaler=StandardScaler(),
task="regression",
time_budget=30,
verbose=0
)
scaled_automl.fit(X_train, y_train)
print(f"\n파이프라인 R²: {scaled_automl.score(X_test, y_test):.4f}")

타겟 변수 스케일링

from sklearn.preprocessing import StandardScaler
import numpy as np

# 040 타겟 스케일링
y_scaler = StandardScaler()
y_train_scaled = y_scaler.fit_transform(y_train.reshape(-1, 1)).flatten()

# 040 학습
automl_y_scaled = AutoML()
automl_y_scaled.fit(
X_train, y_train_scaled,
task="regression",
time_budget=30,
verbose=0
)

# 040 예측 후 역변환
y_pred_scaled = automl_y_scaled.predict(X_test)
y_pred_original = y_scaler.inverse_transform(y_pred_scaled.reshape(-1, 1)).flatten()

print(f"타겟 스케일링 후 R²: {r2_score(y_test, y_pred_original):.4f}")

스케일링 선택 가이드

guide = {
'상황': ['일반적인 경우', '이상치 많음', '특정 범위 필요', '신경망 사용'],
'권장 스케일러': ['StandardScaler', 'RobustScaler', 'MinMaxScaler', 'StandardScaler 또는 MinMaxScaler'],
'특징': ['평균0, 표준편차1', '중앙값 기반', '0~1 범위', '안정적인 학습']
}

print("\n스케일링 선택 가이드:")
print(pd.DataFrame(guide).to_string(index=False))

정리

  • 트리 기반 모델(LightGBM, XGBoost, RF)은 스케일링이 불필요합니다.
  • 선형 모델, SVM, KNN은 스케일링이 필수입니다.
  • StandardScaler: 가장 일반적, 평균=0, 표준편차=1
  • MinMaxScaler: 0~1 범위, 특정 범위가 필요할 때
  • RobustScaler: 이상치에 강건, 중앙값 기반
  • FLAML은 주로 트리 모델을 선택하므로 스케일링 효과가 제한적입니다.

다음 글 예고

다음 글에서는 **타겟 변수 변환 (로그 변환)**에 대해 알아보겠습니다. 비대칭 분포의 타겟을 정규화하는 방법을 다룹니다.


FLAML AutoML 마스터 시리즈 #040