특성 공학과 데이터 전처리: 스케일링 · 인코딩 · 결측값 · 파이프라인
표준화 · 원-핫 · 결측 대치 · 데이터 누수 · Pipeline을 실행 코드로 익히는 전처리 수업
먼저 알아야 할 것
다루는 내용
숫자 특성의 표준화 · 최소-최대 · 로그 변환을 작은 숫자로 계산하고, 어떤 모델에 스케일링이 필요한지 정리합니다. 원-핫과 순서 인코딩, 처음 보는 범주 처리, 평균 · 중앙값 결측 대치와 그 함정을 다룹니다. scikit-learn 문서의 데이터 누수 규칙을 소개하고 ColumnTransformer · Pipeline · 교차 검증을 묶은 코드를 실제로 실행한 결과를 보여 줍니다.
해설
특성 공학과 데이터 전처리: 스케일링 · 인코딩 · 결측값 · 파이프라인
실무에서 모델 성능을 가장 크게 좌우하는 것은 알고리즘보다 데이터를 모델이 먹을 수 있는 숫자로 바꾸는 방식인 경우가 많습니다. 이 수업은 숫자 특성의 크기 맞추기(표준화 · 최소-최대 · 로그 변환), 범주 특성을 숫자로 바꾸기(원-핫 · 순서 인코딩), 빈칸 채우기(결측값 대치), 그리고 이 모든 단계를 데이터 누수 없이 묶는 scikit-learn
Pipeline·ColumnTransformer를 직접 실행한 코드로 익힙니다.
| 항목 | 내용 |
|---|---|
| 참고 자료 | scikit-learn 사용자 안내서 '7.3 Preprocessing data' · scikit-learn.org/stable/modules/preprocessing.html, '11. Common pitfalls and recommended practices' |
| 트랙 · 수준 | 머신러닝 · 기초 |
| 이 수업의 범위 | 표준화 · 최소-최대 스케일링 · 로그 변환, 원-핫 · 순서 인코딩, 처음 보는 범주 처리, 결측값 대치, 특성 만들기, 데이터 누수, Pipeline · ColumnTransformer · 교차 검증 |
| 먼저 알면 좋은 것 | 평균 · 표준편차, 로지스틱 회귀, 학습 · 검증 · 테스트 나누기 |
들어가기 전에
- 모델 평가와 과적합 — 데이터 나누기와 교차 검증을 알아야 "전처리도 학습 데이터로만 맞춘다"는 규칙이 이해됩니다.
- 로지스틱 회귀와 소프트맥스 — 이 수업의 예제 모델입니다.
한눈에 보기
- 왜 특성 크기를 맞춰야 하는지, 어떤 모델은 필요 없고 어떤 모델은 꼭 필요한지 압니다.
- 표준화 · 최소-최대 · 로그 변환을 작은 숫자로 직접 계산합니다.
- 범주 특성에 원-핫과 순서 인코딩 중 무엇을 쓸지 고르고, 처음 보는 범주를 처리합니다.
- 결측값을 평균 · 중앙값으로 채우는 법과 그 함정을 압니다.
- 데이터 누수가 무엇인지 알고,
Pipeline으로 막는 코드를 실행해 봅니다.
1. 숫자 특성의 크기 맞추기
직관(해설용 비유): 나이(20~65)와 월소득(만 원 단위, 수십~수천)을 그대로 넣으면, 거리를 재거나 경사 하강법으로 학습하는 모델은 숫자가 큰 소득에만 끌려다닙니다. 서로 다른 단위의 자를 하나의 눈금으로 맞추는 작업이 스케일링입니다.
표준화(standardization)
| 기호 | 뜻 |
|---|---|
| 학습 데이터에서 잰 특성의 평균 | |
| 학습 데이터에서 잰 표준편차 | |
| 평균 0, 표준편차 1로 옮긴 값 |
숫자로 해 보기(해설용): 이면 , 입니다. . scikit-learn StandardScaler도 같은 값을 냅니다(표준편차를 으로 나눈 값을 씀 — 실행해 scale_ = 11.18을 확인).
최소-최대 스케일링(min-max scaling)
같은 는 이 됩니다. 범위를 [0, 1]로 딱 맞추고 싶을 때(이미지 픽셀 등) 씁니다. 대신 이상치 하나가 를 끌어올리면 나머지가 모두 0 근처로 눌립니다.
로그 변환(log transform)
소득 · 조회수 · 가격처럼 오른쪽으로 길게 늘어진 분포는 로그를 취하면 고르게 펴집니다. 0이 있을 수 있으면 를 씁니다. 는 를 로 바꿉니다 — 1000배 차이가 7 정도의 차이로 줄어듭니다.
어떤 모델에 필요한가:
| 모델 | 스케일링 필요? | 이유 |
|---|---|---|
| kNN, k-평균, SVM(RBF) | 꼭 필요 | 거리 계산이 큰 단위 특성에 좌우됨 |
| 선형 · 로지스틱 회귀 + 정규화 | 필요 | 벌점이 가중치 크기에 걸려 단위에 따라 불공평 |
| 신경망 | 필요 | 경사 하강법이 길쭉한 손실 면에서 느려짐 |
| 결정 트리 · 랜덤 포레스트 · 부스팅 | 거의 불필요 | 한 특성의 임곗값 비교라 단조 변환에 영향 없음 |
scikit-learn SVM 문서도 SVM은 크기에 불변이 아니므로 데이터 스케일링을 강하게 권하고, 테스트 데이터에도 같은 스케일링을 적용해야 한다고 적고 있습니다.
2. 범주 특성을 숫자로
원-핫 인코딩(one-hot encoding)
직관: "지역 = 서울/부산/대구"를 1, 2, 3으로 바꾸면 모델이 "대구 = 서울 × 3" 같은 엉뚱한 크기 관계를 배웁니다. 범주마다 0/1 칸을 하나씩 만들어 해당 칸만 1로 둡니다.
숫자로 해 보기: 범주가 알파벳순으로 Busan, Daegu, Seoul이면 Busan → , Seoul → 입니다. OneHotEncoder(handle_unknown='ignore')로 학습하면, 학습 때 없던 Gwangju는 이 됩니다(실행 확인). 기본값 handle_unknown='error'이면 오류를 냅니다 — 서비스 중 새 범주가 들어올 수 있다면 꼭 정해 두어야 합니다.
함정: 범주가 수천 개(상품 ID, 우편번호)면 칸이 수천 개로 늘어납니다. 드문 범주를 '기타'로 묶거나(min_frequency, max_categories 옵션), 빈도 · 목표값 평균으로 바꾸는 방법, 신경망이라면 임베딩을 씁니다.
순서 인코딩(ordinal encoding)
옷 크기 S < M < L처럼 순서가 진짜 있는 범주는 0, 1, 2로 바꿔도 됩니다. OrdinalEncoder(categories=[['S','M','L']])처럼 순서를 직접 지정합니다. 지정하지 않으면 알파벳순(L, M, S)이 되어 순서가 뒤틀립니다. 트리 모델은 순서가 없는 범주도 순서 인코딩으로 넣어 쓰는 경우가 많습니다.
3. 결측값 채우기
직관: 설문의 빈칸을 그대로 두면 대부분의 모델은 계산을 못 합니다. 가장 흔한 방법은 학습 데이터의 평균이나 중앙값으로 채우는 것입니다.
숫자로 해 보기(해설용): 값
- 평균 대치: 빈칸 →
- 중앙값 대치: 빈칸 →
이상치(8)가 있으면 중앙값이 덜 흔들립니다. scikit-learn SimpleImputer의 기본 전략은 mean입니다.
함정과 요령
- 빠진 것 자체가 정보일 때: "소득 미기입"이 특정 고객층에 몰려 있다면 빈칸 여부가 예측에 도움이 됩니다.
add_indicator=True로 "빠졌음" 표시 칸을 함께 만듭니다. - 많이 빠진 특성: 대부분이 빈칸이면 채운 값이 오히려 잡음입니다. 특성을 빼는 것도 선택지입니다.
- 일부 모델(예: scikit-learn의 히스토그램 기반 부스팅)은 결측값을 직접 다룹니다.
4. 특성 만들기: 도메인 지식을 숫자로
특성 공학(feature engineering)은 원래 칸을 조합해 모델이 쓰기 쉬운 새 칸을 만드는 일입니다.
| 원래 데이터 | 만든 특성 예 | 왜 도움이 되나 |
|---|---|---|
| 가입일, 기준일 | 가입 기간(일) | 날짜 문자열은 그대로 못 씀 |
| 주문 시각 | 요일, 시간대, 주말 여부 | 주기 패턴을 드러냄 |
| 키, 몸무게 | 체질량지수 | 두 칸의 관계를 한 칸에 |
| 총구매액, 구매 횟수 | 1회 평균 구매액 | 비율이 더 안정적 |
| 위도, 경도 | 매장까지 거리 | 선형 모델이 못 만드는 비선형 관계 |
선형 모델에서는 특성 공학의 효과가 특히 큽니다. 트리 · 신경망은 조합을 어느 정도 스스로 찾지만, 좋은 특성은 데이터를 덜 쓰고도 같은 성능을 내게 합니다.
5. 데이터 누수: 전처리도 학습 데이터로만 맞춘다
직관(해설용 비유): 시험 전에 시험지의 평균 점수를 몰래 보고 공부 계획을 세우는 것과 같습니다. 테스트 데이터의 정보(평균, 표준편차, 범주 목록, 선택된 특성)가 학습 과정에 조금이라도 들어가면 점수가 실제보다 좋게 나옵니다.
scikit-learn '흔한 함정' 문서의 규칙은 간단합니다. 테스트 데이터에는 절대 fit을 부르지 않습니다. 먼저 나누고, 학습 데이터로 fit한 변환기를 테스트에는 transform만 합니다. 같은 문서의 예에서는, 무작위 특성 1만 개 · 표본 200개 · 무작위 라벨로 만든 데이터에서 전체 데이터로 특성을 고른 뒤 나누면 정확도가 약 0.76으로 나왔지만, 먼저 나누고 학습 데이터로만 고르면 우연 수준인 약 0.5가 나왔습니다. 라벨이 무작위인데 0.76이 나왔다는 것 자체가 누수의 증거입니다.
누수가 흔히 생기는 곳
- 전체 데이터로 스케일러 · 대치값 · 특성 선택을 맞춘 뒤 나누기
- 시간 순서가 있는 데이터에서 미래 정보를 쓰는 특성(예: "다음 달 해지 여부"를 예측하면서 "해지 처리일"을 특성으로 넣기)
- 같은 고객 · 같은 문서가 학습과 테스트에 나뉘어 들어감
6. Pipeline과 ColumnTransformer: 한 번에 묶기
직관: 전처리 단계와 모델을 한 줄로 묶어 두면 fit은 학습 데이터에만, predict는 같은 변환을 거쳐 실행됩니다. 교차 검증을 돌려도 각 겹(fold)마다 전처리가 그 겹의 학습 부분으로만 다시 맞춰집니다.
flowchart LR
A[원본 표 데이터] --> B[학습 · 테스트로 먼저 나누기]
B --> C[ColumnTransformer]
C --> D[숫자 열: 중앙값 대치 → 표준화]
C --> E[범주 열: 원-핫 인코딩]
C --> F[순서 열: 순서 인코딩]
D --> G[합친 특성 행렬]
E --> G
F --> G
G --> H[모델 학습 fit]
H --> I[테스트: 같은 변환 transform → 예측]import numpy as np, pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder, OrdinalEncoder
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split, cross_val_score
# 설명용 가짜 데이터 400행: 나이 · 월소득(일부 빈칸) · 도시 · 등급 → 구매 여부
rng = np.random.default_rng(0); n = 400
df = pd.DataFrame({
"age": rng.integers(20, 65, n).astype(float),
"income": np.round(rng.lognormal(5.5, 0.6, n)),
"city": rng.choice(["Seoul", "Busan", "Daegu"], n),
"grade": rng.choice(["S", "M", "L"], n),
})
logit = 0.05*(df["age"]-40) + 0.004*(df["income"]-300) + (df["city"]=="Seoul")*0.8 - 0.4
y = (rng.random(n) < 1/(1+np.exp(-logit))).astype(int)
df.loc[rng.choice(n, 30, replace=False), "income"] = np.nan # 빈칸 30개
pre = ColumnTransformer([
("num", Pipeline([("imp", SimpleImputer(strategy="median")),
("sc", StandardScaler())]), ["age", "income"]),
("cat", OneHotEncoder(handle_unknown="ignore"), ["city"]),
("ord", OrdinalEncoder(categories=[["S", "M", "L"]]), ["grade"]),
])
model = Pipeline([("pre", pre), ("clf", LogisticRegression())])
X_tr, X_te, y_tr, y_te = train_test_split(df, y, test_size=0.25, random_state=0, stratify=y)
print(cross_val_score(model, X_tr, y_tr, cv=5).round(3)) # 학습 데이터 안에서 5겹 교차 검증
model.fit(X_tr, y_tr)
print(round(model.score(X_te, y_te), 3)) # 테스트 정확도
print(model[:-1].get_feature_names_out()) # 변환 후 특성 이름실제 실행 결과(Python 3.14 · scikit-learn 1.9.0 · pandas 3.0.3, 시드 0):
[0.75 0.683 0.717 0.65 0.7 ]
0.58
['num__age' 'num__income' 'cat__city_Busan' 'cat__city_Daegu'
'cat__city_Seoul' 'ord__grade']결과 읽기: 교차 검증 평균은 0.70인데 테스트 100행에서는 0.58이 나왔습니다. 일부러 잡음을 많이 넣은 작은 가짜 데이터라 평가 데이터를 어떻게 떼느냐에 따라 점수가 크게 흔들린다는 것을 보여 줍니다. 그래서 작은 데이터에서는 한 번의 테스트 점수보다 교차 검증의 평균과 흔들림을 함께 봐야 합니다. 특성 이름을 보면 원-핫이 도시 3칸을 만들었고 숫자 2칸, 순서 1칸으로 총 6칸이 된 것을 확인할 수 있습니다.
연습 문제
1. 학습 데이터 키(cm)가 일 때 표준화 계수는? 테스트의 키 190은 어떻게 바뀌나요?
풀이: , . 테스트 190은 학습 기준으로 입니다. 테스트 데이터로 평균을 다시 재지 않습니다.
2. 혈액형(A, B, O, AB)에는 어떤 인코딩을 쓰나요?
풀이: 순서가 없으므로 원-핫(4칸)입니다. 순서 인코딩은 "AB > O" 같은 가짜 순서를 만듭니다(트리 모델은 예외적으로 쓰기도 함).
3. 값 의 평균 대치값과 중앙값 대치값은?
풀이: 평균 , 중앙값 . 이상치 100 때문에 평균 대치가 크게 왜곡됩니다.
4. 다음 코드의 문제는? X = StandardScaler().fit_transform(X_all); X_tr, X_te = train_test_split(X)
풀이: 테스트 데이터까지 포함한 평균 · 표준편차로 스케일러를 맞춰 정보가 샙니다. 먼저 나누고, 스케일러를 학습 데이터로만 fit하거나 Pipeline으로 묶어야 합니다.
스스로 점검
- 랜덤 포레스트에는 왜 표준화가 거의 필요 없나요?
- 답: 트리는 한 특성을 임곗값과 비교해 나눌 뿐이라, 크기를 바꾸는 단조 변환이 어느 쪽으로 갈지를 바꾸지 않기 때문입니다.
handle_unknown='ignore'는 언제 꼭 필요하나요?- 답: 서비스 중 학습 때 없던 범주가 들어올 수 있을 때입니다. 기본값('error')이면 예측이 오류로 멈춥니다.
Pipeline을 쓰면 교차 검증에서 무엇이 좋아지나요?- 답: 겹마다 전처리를 그 겹의 학습 부분으로만 다시 맞추므로, 검증 부분의 정보가 새지 않습니다.
용어 정리
| 용어 | 쉬운 뜻 |
|---|---|
| 특성 공학(feature engineering) | 원래 데이터로 모델이 쓰기 좋은 새 특성을 만드는 일 |
| 표준화(standardization) | 평균 0, 표준편차 1로 옮기기 |
| 최소-최대 스케일링(min-max scaling) | [0, 1] 범위로 맞추기 |
| 로그 변환(log transform) | 길게 늘어진 분포를 로그로 펴기 |
| 원-핫 인코딩(one-hot encoding) | 범주마다 0/1 칸을 하나씩 |
| 순서 인코딩(ordinal encoding) | 순서 있는 범주를 0, 1, 2…로 |
| 결측값 대치(imputation) | 빈칸을 평균 · 중앙값 등으로 채우기 |
| 데이터 누수(data leakage) | 평가 데이터 정보가 학습에 새어 들어감 |
| 파이프라인(Pipeline) | 전처리와 모델을 한 줄로 묶은 객체 |
| ColumnTransformer | 열마다 다른 전처리를 적용하는 도구 |
더 깊이 보려면
scikit-learn '7.3 Preprocessing data'에는 강건 스케일러(이상치에 강함), 분위수 · 거듭제곱 변환, 이산화, 다항 특성 생성까지 정리되어 있습니다. '11. Common pitfalls and recommended practices'는 이 수업의 5절 내용 — 일관되지 않은 전처리, 데이터 누수, 난수 제어 — 을 코드로 보여 줍니다.
다음에 볼 것
- k-최근접 이웃과 나이브 베이즈 — 스케일링이 왜 중요한지 거리 기반 모델에서 바로 확인합니다.
- 결정 트리: 지니 불순도 · 정보 이득 · 가지치기 — 스케일링이 필요 없는 모델의 원리를 봅니다.
라이선스
이 글은 원문 번역이 아니라 KAITABASE가 직접 쓴 해설이며, 숫자 예와 예제 데이터는 설명용으로 만든 것입니다(코드는 직접 실행해 결과를 붙임). 참고 자료 scikit-learn 문서는 BSD-3-Clause 라이선스입니다.
출처
- 참고: scikit-learn.org/stable/modules/preproces…
- 참고: scikit-learn.org/stable/common_pitfalls.h… (데이터 누수 규칙과 특성 선택 누수 예의 수치)
- 참고: scikit-learn.org/stable/modules/svm.html (SVM 스케일링 권고)
AI 원리부터 이해하기 · 머신러닝 모듈
머신러닝 모듈 학습
이 글은 머신러닝 모듈의 학습 자료입니다. 전체 17개 자료 중 4번째입니다.
출처·원문
원문 보기요약만