1장. 머신러닝 프로세스와 기본 개념
학습용과 시험용 나누기
시험 공부를 떠올려 보세요. 문제집으로 공부하고, 모의고사로 실력을 점검하고, 본시험으로 성적을 받습니다. 본시험 문제를 미리 봤다면 점수가 높아도 실력이라고 할 수 없습니다. 머신러닝도 데이터를 쓰임에 따라 묶음으로 나눕니다.
왼쪽부터 학습용이 문제집, 검증용이 모의고사, 시험용이 본시험입니다. 학습용으로 배우고, 검증용으로 설정을 고르고, 시험용으로 마지막에 한 번 성적을 매깁니다.
세 묶음
| 묶음 | 쓰임 | 시험 공부로 치면 | 보통의 비율 |
|---|---|---|---|
| 학습용 | 모델이 보고 배운다 | 문제집 | 60~80% |
| 검증용 | 모델의 설정(나무의 깊이 등)을 고른다 | 모의고사 | 10~20% |
| 시험용 | 마지막에 한 번 성적을 매긴다 | 본시험 | 10~20% |
데이터가 적을 때는 검증용을 따로 떼지 않고 학습용 안에서 돌아가며 검증하기도 합니다(교차 검증). 이 장에서는 학습용과 시험용 두 묶음만 씁니다.
train_test_split
scikit-learn 의 train_test_split 이 섞어서 나눠 줍니다. 지연처럼 한쪽이 적은 정답이면 stratify 를 꼭 줍니다. 학습용과 시험용의 지연 비율이 되도록 똑같아지게 나눠 줍니다.
import pandas as pd
from sklearn.model_selection import train_test_split
# 배송 주문 10건(설명용으로 지어낸 표): 지연은 3건
orders = pd.DataFrame({
"주문번호": [101, 102, 103, 104, 105, 106, 107, 108, 109, 110],
"거리km": [5, 180, 12, 150, 8, 90, 200, 20, 15, 60],
"주문시각": [10, 21, 9, 22, 14, 18, 23, 11, 13, 16],
"지연": [0, 1, 0, 1, 0, 0, 1, 0, 0, 0],
})
X = orders[["거리km", "주문시각"]] # 특성
y = orders["지연"] # 레이블
# test_size=0.3 : 30% 를 시험용으로 뗀다(10건 가운데 3건)
# random_state=42: 섞는 순서를 고정해서 다시 실행해도 같게 나눈다
# stratify=y : 지연 비율을 학습용과 시험용에 되도록 똑같이 나눈다
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42, stratify=y
)
# 결과: 각 묶음에 들어간 주문번호와 두 묶음의 지연 비율
print("학습용 주문:", orders.loc[X_train.index, "주문번호"].tolist())
print("시험용 주문:", orders.loc[X_test.index, "주문번호"].tolist())
print("지연 비율 학습용", round(y_train.mean(), 2), "· 시험용", round(y_test.mean(), 2))10건 가운데 7건(104, 101, 105, 108, 109, 103, 107번)이 학습용, 3건(102, 110, 106번)이 시험용으로 나뉩니다. 지연 비율은 학습용 0.29(7건 가운데 2건), 시험용 0.33(3건 가운데 1건)입니다. 건수가 적으면 비율을 딱 맞출 수 없어서, stratify 는 할 수 있는 만큼 가장 가깝게 나눕니다. 실습 4의 배송 데이터 1,194건에서는 학습용 0.206, 시험용 0.207 로 거의 같아집니다. 돌려받는 순서가 X_train, X_test, y_train, y_test 라는 점을 눈여겨보세요. 순서를 바꿔 받으면 엉뚱한 묶음으로 학습하게 됩니다.
코드에 나오는 함수
| 코드 | 쉬운 뜻 |
|---|---|
from sklearn.model_selection import train_test_split | model selection(모델 고르기) 묶음에서 불러온다. train(학습)과 test(시험)로 split(나누기)한다는 이름이다 |
test_size=0.3 | 전체의 30% 를 시험용으로 뗀다 |
random_state=42 | 섞는 순서의 씨앗이다. 같은 숫자를 주면 몇 번을 돌려도 똑같이 나뉜다 |
stratify=y | stratify 는 "층을 나누다"라는 뜻이다. y 의 값(지연, 정시)마다 따로 나눠서 두 묶음의 지연 비율을 맞춘다 |
X_train, X_test, y_train, y_test = ... | 네 묶음을 이 순서로 돌려받는다. 학습용 특성, 시험용 특성, 학습용 정답, 시험용 정답이다 |
X_train.index | 학습용에 들어간 행의 번호표다. 섞여도 원래 표의 번호를 그대로 들고 다닌다 |
orders.loc[번호들, "주문번호"] | loc 는 location(위치)의 줄임말이다. 행 번호표와 열 이름으로 값을 꺼낸다 |
y_train.mean() | 0 과 1 의 평균은 1 의 비율이다. 곧 지연 비율이다 |
이 코드가 뜻하는 것
문제 카드 열 장을 잘 섞어 일곱 장은 문제집으로, 세 장은 본시험으로 봉투에 넣는 일입니다. 어려운 카드(지연)가 한 봉투에 몰리지 않게, 어려운 카드끼리 따로 섞어서 나눠 담습니다. 여기서 카드가 주문 한 건, 어려운 카드끼리 따로 나누는 것이 stratify=y 입니다.
| 줄 | 하는 일 | 쉽게 말하면 |
|---|---|---|
orders = pd.DataFrame({...}) | 주문 10건을 만든다. 지연이 3건이다 | 카드 열 장, 어려운 카드 세 장 |
X = ..., y = ... | 특성과 정답을 나눈다 | 카드 앞면(문제)과 뒷면(답) |
train_test_split(...) | 섞어서 7건과 3건으로 나눈다 | 문제집 봉투와 본시험 봉투에 나눠 담는다 |
orders.loc[X_train.index, "주문번호"] | 각 묶음에 든 주문번호를 본다 | 봉투 안의 카드 번호를 확인한다 |
round(y_train.mean(), 2) | 묶음마다 지연 비율을 본다 | 봉투마다 어려운 카드가 얼마나 들었나 |
학습용 0.29 와 시험용 0.33 은 어려운 카드가 두 봉투에 2장, 1장으로 고르게 갔다는 뜻입니다. 세 장을 7장과 3장 봉투에 나누면 이보다 더 가깝게 맞출 수는 없습니다.
작게 줄여서 눈으로 보기
stratify 를 빼면 어떻게 되는지 봅니다. 같은 10건(지연 3건)을 섞는 씨앗만 바꿔 가며 나누고, 시험용 3건에 지연이 몇 건 들었는지 셉니다. 특성은 설명을 위해 0 부터 9 까지의 번호로 대신했습니다.
from sklearn.model_selection import train_test_split
y = [0, 1, 0, 1, 0, 0, 1, 0, 0, 0] # 지연 3건, 정시 7건
X = [[i] for i in range(10)]
# stratify 없이 섞는 순서(random_state)만 바꿔 가며 시험용의 지연 수를 센다
for seed in range(5):
_, _, _, y_test = train_test_split(X, y, test_size=0.3, random_state=seed)
print("random_state", seed, "→ 시험용 지연", sum(y_test), "건")
# stratify=y 를 주면
_, _, _, y_test = train_test_split(X, y, test_size=0.3, random_state=0, stratify=y)
print("stratify 사용 → 시험용 지연", sum(y_test), "건")random_state 0 → 시험용 지연 0 건
random_state 1 → 시험용 지연 1 건
random_state 2 → 시험용 지연 1 건
random_state 3 → 시험용 지연 1 건
random_state 4 → 시험용 지연 1 건
stratify 사용 → 시험용 지연 1 건| 결과 | 읽는 법 |
|---|---|
random_state 0 → 시험용 지연 0 건 | 운이 나쁘면 시험용에 지연이 하나도 없다. 이러면 "지연을 몇 건 찾았나"를 잴 수가 없다 |
random_state 1 ~ 4 의 1 건 | 대개는 한 건이 들어가지만, 씨앗에 따라 달라진다 |
stratify 사용 → 시험용 지연 1 건 | 같은 씨앗 0 이어도 지연을 나눠 담아 시험용에 한 건이 들어간다 |
_ 는 받기는 하지만 쓰지 않을 값을 담는 이름입니다. 네 묶음 가운데 y_test 만 필요해서 나머지 셋을 _ 로 받았습니다.
주의할 점
- 시험용 데이터는 마지막에 한 번만 봅니다. 시험 점수를 보며 설정을 여러 번 고치면, 시험 문제를 미리 본 것과 같아집니다. 그때는 검증용을 따로 둡니다.
- 정제할 때 쓰는 값(예: 빈칸을 채울 중앙값)도 원래는 학습용에서만 구합니다. 이 장의 실습 2는 흐름을 보이려고 나누기 전에 정제했습니다. 2장부터는 나눈 뒤에 구합니다.
stratify를 빼면 운에 따라 시험용에 지연이 거의 없을 수도 있습니다. 그러면 재현율 같은 지표가 크게 흔들립니다.