스케일링과 정규화
피처의 스케일(범위, 단위)이 다르면 일부 ML 모델은 스케일이 큰 피처에 편향됩니다. 스케일링(Scaling)은 피처들을 비슷한 범위로 변환하여 모델이 모든 피처를 공정하게 학습할 수 있게 합니다.
학습 목표
- StandardScaler, MinMaxScaler, RobustScaler의 차이를 이해한다
- 데이터 특성에 맞는 스케일러를 선택할 수 있다
- 로그 변환과 Box-Cox 변환으로 분포를 정규화할 수 있다
- 스케일링이 필요한 모델과 불필요한 모델을 구분할 수 있다
왜 중요한가
경력(120년)과 연봉(200010000만원)을 동시에 사용하면, 연봉의 수치가 훨씬 크므로 거리 기반 모델(KNN, SVM)은 연봉에 지배됩니다. 스케일링으로 이 문제를 해결합니다.
StandardScaler — 표준화
평균을 0, 표준편차를 1로 변환합니다.
import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler
import matplotlib.pyplot as plt
np.random.seed(42)
df = pd.DataFrame({
'salary': np.random.normal(4500, 800, 200),
'experience': np.random.uniform(1, 20, 200),
'performance': np.random.normal(75, 10, 200)
})
# StandardScaler
scaler = StandardScaler()
df_standard = pd.DataFrame(
scaler.fit_transform(df),
columns=df.columns
)
print("StandardScaler 결과:")
print(df_standard.describe().round(2))
# mean ≈ 0, std ≈ 1
MinMaxScaler — 최소-최대 정규화
모든 값을 0~1 범위로 변환합니다.
# MinMaxScaler
minmax = MinMaxScaler()
df_minmax = pd.DataFrame(
minmax.fit_transform(df),
columns=df.columns
)
print("MinMaxScaler 결과:")
print(df_minmax.describe().round(2))
# min = 0, max = 1
# 특정 범위로 스케일링
minmax_custom = MinMaxScaler(feature_range=(-1, 1))
df_custom = pd.DataFrame(
minmax_custom.fit_transform(df),
columns=df.columns
)
RobustScaler — 이상치에 강건한 스케일링
중앙값과 IQR을 사용하여 이상치의 영향을 줄입니다.
# 이상치가 포함된 데이터
df_outlier = df.copy()
df_outlier.loc[0, 'salary'] = 50000 # 극단적 이상치
# 세 스케일러 비교
scalers = {
'Standard': StandardScaler(),
'MinMax': MinMaxScaler(),
'Robust': RobustScaler()
}
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
for ax, (name, scaler) in zip(axes, scalers.items()):
scaled = scaler.fit_transform(df_outlier[['salary']])
ax.hist(scaled, bins=30, color='#4a9eca', alpha=0.7, edgecolor='white')
ax.set_title(f'{name}Scaler')
ax.set_xlabel('스케일링된 값')
plt.suptitle('이상치가 있을 때 스케일러 비교')
plt.tight_layout()
plt.show()
스케일러 비교
| 스케일러 | 수식 | 이상치 영향 | 적합한 상황 |
|---|
| Standard | (x - mean) / std | 높음 | 정규분포, 이상치 없음 |
| MinMax | (x - min) / (max - min) | 매우 높음 | 범위가 정해진 데이터 |
| Robust | (x - median) / IQR | 낮음 | 이상치가 있는 데이터 |
분포 변환 — 편향 보정
from scipy import stats
from sklearn.preprocessing import PowerTransformer
# 편향된 데이터
np.random.seed(42)
skewed_data = np.random.exponential(5, 500)
fig, axes = plt.subplots(1, 4, figsize=(16, 4))
# 원본
axes[0].hist(skewed_data, bins=30, color='#e6a23c', alpha=0.7)
axes[0].set_title(f'원본 (왜도: {stats.skew(skewed_data):.2f})')
# 로그 변환
log_data = np.log1p(skewed_data) # log(1+x): 0 포함 가능
axes[1].hist(log_data, bins=30, color='#4a9eca', alpha=0.7)
axes[1].set_title(f'로그 변환 (왜도: {stats.skew(log_data):.2f})')
# 제곱근 변환
sqrt_data = np.sqrt(skewed_data)
axes[2].hist(sqrt_data, bins=30, color='#4a9e4a', alpha=0.7)
axes[2].set_title(f'제곱근 변환 (왜도: {stats.skew(sqrt_data):.2f})')
# Box-Cox 변환 (양수 데이터만)
pt = PowerTransformer(method='yeo-johnson')
boxcox_data = pt.fit_transform(skewed_data.reshape(-1, 1)).flatten()
axes[3].hist(boxcox_data, bins=30, color='#9b59b6', alpha=0.7)
axes[3].set_title(f'Yeo-Johnson (왜도: {stats.skew(boxcox_data):.2f})')
plt.tight_layout()
plt.show()
| 변환 | 적합한 상황 | 제약 |
|---|
| 로그 (log1p) | 오른쪽 치우침, 양수 데이터 | 0 이하 값 불가 (log1p로 해결) |
| 제곱근 (sqrt) | 약한 오른쪽 치우침 | 음수 값 불가 |
| Box-Cox | 양수 데이터 | 양수만 가능 |
| Yeo-Johnson | 모든 데이터 | 제약 없음 (Box-Cox의 일반화) |
스케일링이 필요한 모델
| 모델 | 스케일링 필요 | 이유 |
|---|
| 선형회귀, 로지스틱회귀 | 필수 | 계수 크기가 스케일에 의존 |
| SVM | 필수 | 거리 기반 최적화 |
| KNN | 필수 | 거리 계산에 스케일 영향 |
| PCA | 필수 | 분산 기반 차원 축소 |
| 결정 트리, Random Forest | 불필요 | 분할 기준이 스케일 무관 |
| XGBoost, LightGBM | 불필요 | 트리 기반 모델 |
| 신경망 | 필수 | 경사하강법 수렴 속도 |
확실하지 않으면 StandardScaler를 기본으로 사용하세요. 이상치가 있으면 RobustScaler, 신경망에서는 MinMaxScaler(0~1)가 자주 사용됩니다.
AI/ML에서의 활용
- 모델 성능: 스케일링만으로 KNN, SVM의 성능이 크게 향상됩니다
- 수렴 속도: 신경망의 경사하강법 수렴이 빨라집니다
- 해석 가능성: StandardScaler 후 회귀 계수를 비교하면 피처의 상대적 중요도를 파악할 수 있습니다
- 파이프라인 통합: 반드시 Pipeline 내에서 스케일링하여 데이터 누수를 방지합니다
fit_transform과 transform의 차이는?
fit_transform()은 학습 데이터에서 통계값(평균, 표준편차 등)을 계산하고 변환합니다. transform()은 이미 계산된 통계값으로 새 데이터를 변환합니다. 테스트 데이터에는 반드시 transform()만 사용해야 합니다.
체크리스트
다음 문서