Week 2. 데이터 핸들링
모델보다 데이터가 먼저입니다. NumPy와 pandas로 데이터를 만지고, 표준화하고, 학습/테스트로 나누는 기본기를 닦습니다.
이번 주에 배우는 것
- 왜 데이터를 먼저 봐야 하는가
- NumPy 배열의 사고방식
- pandas DataFrame 핵심 연산
- 표준화와 정규화 — 스케일이 왜 중요한가
- 학습/검증/테스트 분할과 데이터 누수
0. 이번 주의 문제의식
딥러닝 강의에서 "모델" 이야기만 기대했다가 2주차에 판다스와 표준화를 만나면 살짝 김이 샙니다. 하지만 현업 데이터 과학자들이 입을 모아 말하는 것이 하나 있습니다. "나는 하루 근무 시간의 70% 이상을 데이터를 씻고 정렬하는 데 쓴다." 실제로 MPC나 강화학습 같은 화려한 기법을 다룰 때도, 정작 성능을 좌우하는 건 입력으로 들어가는 상태 변수를 어떻게 스케일링했는가 같은 "지루한" 결정들입니다.
그래서 이번 주의 목표는 화려하지 않습니다. ① 데이터를 머릿속에 "배열의 모양"으로 그리는 법, ② 스케일을 맞추는 법과 왜 맞춰야 하는지, ③ 학습/검증/테스트를 나누면서 정보 누수를 막는 법. 이 세 가지만 제대로 익혀도 이후 모든 주차에서 디버깅 시간이 반으로 줄어듭니다.
1. 데이터를 먼저 봐야 하는 이유
실무 머신러닝의 80%는 데이터 정리이고 20%만이 모델링이라는 말이 있습니다. 다소 과장이지만 본질은 정확합니다 — 아무리 좋은 모델도 더러운 데이터 위에서는 더러운 결과를 냅니다 (Garbage In, Garbage Out).
"본다"는 건 막연한 말이 아닙니다. 구체적으로 다음의 네 가지 점검 항목을 습관화하세요.
그래서 코드를 짜기 전에 우리는 이렇게 묻습니다.
- 각 컬럼의 단위와 범위는?
- 결측치(NaN)는 얼마나 있는가?
- 이상치(outlier)가 분포를 망가뜨리는가?
- 클래스 불균형은 없는가? (예: 사기 거래 0.1%)
df.describe(), df.isnull().sum(), 각 컬럼의 히스토그램을 그려 보는 게 훨씬 빠릅니다.1.1 "이상치"는 항상 제거해야 하는가
그렇지 않습니다. 이상치는 세 종류로 나눌 수 있습니다. ① 센서 고장이나 입력 실수로 생긴 진짜 오류는 제거 또는 수정, ② 꼬리가 긴 분포에서 자연스럽게 나타나는 극단값은 보존(오히려 중요 정보), ③ 희귀하지만 의미 있는 사건(사기, 결함)은 그 자체가 예측 대상이니 절대 버리면 안 됩니다. "3σ 밖의 점을 자동으로 지운다" 같은 규칙을 맹목적으로 적용하지 마세요.
2. NumPy — 벡터화 사고
NumPy는 파이썬 리스트를 더 빠르게 만든 게 아니라 완전히 다른 사고방식입니다. 루프를 도는 대신 배열 전체에 한 번에 연산을 적용합니다.
import numpy as np
x = np.array([1, 2, 3, 4, 5])
y = x ** 2 + 1 # [2, 5, 10, 17, 26]
print(x.mean(), x.std()) # 3.0 1.414
다차원 배열의 모양(shape)을 머릿속에서 그릴 수 있어야 합니다. 예를 들어 이미지 100장이면 $(100, 28, 28)$ 모양이고, 컬러면 $(100, 28, 28, 3)$입니다.
2.1 벡터화는 왜 그렇게 빠른가
파이썬 루프가 $10^6$번 도는 데 몇 초가 걸리지만, 같은 크기의 NumPy 연산은 수십 밀리초 안에 끝납니다. 이유는 두 가지입니다. 첫째, NumPy의 연산은 내부적으로 C/Fortran으로 구현돼 있어서 인터프리터 오버헤드가 없습니다. 둘째, SIMD(한 명령으로 여러 데이터 처리) CPU 명령어를 활용해 한 사이클에 여러 곱셈을 동시에 수행합니다. 이는 CNN의 합성곱과 MLP의 행렬곱이 GPU에서 수백 배 더 빨라지는 원리의 축소판이기도 합니다.
2.2 브로드캐스팅(broadcasting)
NumPy에서 가장 헷갈리면서도 가장 강력한 기능입니다. 모양이 다른 두 배열을 연산할 때 작은 쪽이 "자동으로 늘어나서" 큰 쪽에 맞춰집니다. 예:
X = np.array([[1,2,3],[4,5,6]]) # (2, 3)
mu = np.array([1, 1, 1]) # (3,)
X - mu # (2, 3) — mu가 각 행에 빼짐
표준화를 한 줄로 쓸 수 있는 것도 브로드캐스팅 덕분입니다: (X - X.mean(0)) / X.std(0). 초보자가 자주 겪는 버그는 $(N,)$ 과 $(N,1)$을 혼동하는 것입니다. 전자는 1차원 벡터, 후자는 2차원 열벡터로 브로드캐스팅 결과가 완전히 달라집니다. 헷갈릴 때는 reshape(-1,1)로 명시해 주세요.
3. pandas — 표 데이터의 엑셀
pandas의 DataFrame은 컬럼마다 이름이 붙은 표입니다. SQL과 엑셀의 사고방식을 모두 흉내 낼 수 있습니다.
import pandas as pd
df = pd.read_csv('fish.csv')
df.head() # 위 5줄
df.describe() # 통계 요약
df[df['weight'] > 500] # 필터
df.groupby('species').mean() # 집계
4. 표준화와 정규화 — 스케일의 힘
"길이는 30cm, 무게는 600g". 두 컬럼의 숫자 크기가 너무 다르면 거리 기반 모델(KNN, K-means)은 큰 숫자에만 영향을 받습니다. 그래서 모든 컬럼을 비슷한 스케일로 맞춰주는 게 필수입니다.
4.1 수치적으로 무슨 일이 벌어지는가
길이와 무게의 예를 수치적으로 끝까지 따라가 봅시다. 두 점 A=(30cm, 600g), B=(31cm, 650g) 사이의 유클리드 거리를 계산하면
$$ d = \sqrt{(31-30)^2 + (650-600)^2} = \sqrt{1 + 2500} \approx 50.01 $$보다시피 거리의 99.98%가 "무게 차이"에서 옵니다. 길이 차이(1cm)는 사실상 무시됩니다. 이것이 바로 KNN이나 K-means가 스케일에 극도로 민감한 이유입니다. 같은 두 점을 표준화(예: 길이 σ=5cm, 무게 σ=100g 가정)한 뒤에는
$$ d = \sqrt{(1/5)^2 + (50/100)^2} = \sqrt{0.04 + 0.25} \approx 0.54 $$이제 길이와 무게가 거리에 비슷한 비중으로 기여합니다. KNN, K-means/PCA 같은 모델은 표준화가 사실상 필수이고, 경사하강법으로 학습하는 선형 회귀·로지스틱 회귀·신경망도 표준화를 안 하면 학습률 튜닝이 악몽이 됩니다.
4.2 왜 경사하강법은 스케일에 민감한가
한 변수의 스케일이 훨씬 크면 손실 함수의 등고선이 길쭉한 타원이 됩니다. 경사하강법은 등고선에 수직으로 움직이므로, 길쭉한 골짜기에서 좌우로 지그재그 진동하며 천천히 내려갑니다. 모든 변수를 같은 스케일로 맞춰서 등고선을 "동그란 원"에 가깝게 만들면 한 방향으로 곧장 내려갈 수 있습니다. 이는 W8의 배치 정규화가 층 내부에서도 같은 일을 하는 이유와 정확히 같습니다.
표준화(standardization) — 평균 0, 표준편차 1로:
$$ z = \frac{x - \mu}{\sigma} $$정규화(min-max) — 0과 1 사이로:
$$ x' = \frac{x - x_{\min}}{x_{\max} - x_{\min}} $$🎮 인터랙티브: 원본 vs 표준화
왼쪽은 원본 데이터, 오른쪽은 표준화한 데이터입니다. 두 컬럼의 스케일이 다를 때 표준화가 어떻게 데이터를 "둥글게" 만드는지 확인하세요.
5. 학습/검증/테스트 분할
모델이 새 데이터에 잘 작동하는지 알려면, 학습에 쓰지 않은 데이터로 평가해야 합니다. 보통 다음과 같이 나눕니다.
- 학습(train) — 60~80%. 모델 파라미터를 맞추는 데 씁니다.
- 검증(validation) — 10~20%. 하이퍼파라미터를 고르고 과적합을 감시합니다.
- 테스트(test) — 10~20%. 최종 성능 보고용. 딱 한 번만 봅니다.
🎮 인터랙티브: 학습/테스트 분할 시각화
슬라이더로 테스트 비율을 바꾸고 셔플을 누르면, 데이터가 어떻게 두 그룹으로 나뉘는지 보입니다.
분류 문제에서 클래스 비율이 유지되도록 나누는 것을 층화 추출(stratified split)이라고 합니다.
6. 히스토그램 — 분포를 한눈에
한 컬럼의 값들이 어떻게 퍼져 있는지 보려면 히스토그램이 가장 빠릅니다.
🎮 인터랙티브: 히스토그램 빈 수
같은 데이터도 빈(bin)을 몇 개로 나누느냐에 따라 모양이 달라집니다. 너무 적으면 거칠고, 너무 많으면 잡음이 보입니다.
7. 코드 예제
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
df = pd.read_csv('fish.csv')
X = df[['length','weight']].values
y = df['species'].values
X_tr, X_te, y_tr, y_te = train_test_split(
X, y, test_size=0.2, stratify=y, random_state=42)
scaler = StandardScaler()
X_tr_s = scaler.fit_transform(X_tr) # fit은 학습 데이터로만
X_te_s = scaler.transform(X_te) # 테스트는 transform만
📖 더 깊이 공부하기
- Python for Data Analysis — Wes McKinney (pandas의 창시자). 데이터프레임의 모든 것.
- NumPy 공식 문서 — numpy.org/doc. 브로드캐스팅 규칙 부분은 필독.
- scikit-learn preprocessing — 공식 가이드. StandardScaler, MinMaxScaler, RobustScaler 비교.
- Hands-On Machine Learning — Aurélien Géron, 2장. 실제 데이터셋으로 끝까지 가는 사례.