AICE (에이스)

AICE (에이스) 시험 노트개념 정리15 MIN

X·y 분리와 Train/Test 분할

AICE Associate 전처리 과목의 마지막 걸음입니다. 입력 X와 정답 y를 나누고, train_test_split의 test_size·random_state·stratify를 쓰며, 결측 대치·인코딩·스케일링을 분할 앞뒤 어디에 두는지를 연습 문제 7개로 짚습니다.

전처리의 마지막 걸음은 표를 두 번 자르는 일입니다. 한 번은 세로로 잘라 모델이 볼 열과 맞힐 열을 가르고, 한 번은 가로로 잘라 학습에 쓸 행과 평가에 남길 행을 가릅니다. 사이킷런 모델의 fit은 이 둘이 갈라진 상태를 전제로 짜여 있어서, 이 걸음을 건너뛰면 모델링 과목의 첫 줄부터 막힙니다.

AICE Associate에서 이 자리는 거의 매번 나옵니다. 「타깃을 Churn으로 하여 X와 y를 나누시오」, 「학습 데이터와 평가 데이터를 8:2로 나누되 random_state=42로 고정하시오」처럼 인자까지 지정해 주는 문항이 많고, 지정한 인자를 빠뜨리면 이후 점수가 기준값과 달라집니다.

X와 y

피처와 타깃

모델이 보고 판단하는 입력 열들이 피처이고, 그 판단으로 맞히려는 한 열이 타깃입니다. 사이킷런은 관례대로 피처 표를 대문자 X, 타깃을 소문자 y로 부릅니다. 대문자는 여러 열로 된 2차원 표, 소문자는 한 줄짜리 1차원 값이라는 표시입니다.

고객 이탈 표라면 나이·요금·사용량이 피처이고 이탈 여부 Churn이 타깃입니다. 앞 노트들에서 식별자와 타깃이 새는 열을 지운 것은 이 X에 들어가면 안 되는 것을 미리 걸러 낸 일이었습니다.

분리 코드

X = df.drop(columns=['Churn'])
y = df['Churn']

X는 타깃만 뺀 나머지 전부이고, y는 대괄호 한 겹으로 꺼낸 시리즈입니다. drop은 원래 표를 바꾸지 않고 새 표를 돌려주므로 df에는 Churn이 그대로 남아 있습니다.

y를 df[['Churn']]처럼 두 겹으로 꺼내면 열 하나짜리 데이터프레임이 됩니다. 대부분의 모델이 받기는 하지만 「column-vector y was passed」 경고를 내고, 신경망 쪽에서는 모양이 어긋나 오류가 나기도 합니다. 타깃은 한 겹으로 꺼냅니다.

피처를 몇 개만 고르라는 문항이면 빼는 대신 넣을 열을 적습니다.

X = df[['Age', 'Charge', 'Usage']]

train_test_split

반환값

학습용과 평가용을 나누는 함수가 train_test_split입니다.

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

받는 쪽의 차례가 가장 자주 틀리는 자리입니다. 넣은 것이 X, y 순이면 돌려주는 것은 X의 학습·평가, y의 학습·평가 순입니다. 「X_train, y_train, X_test, y_test」로 받으면 오류 없이 돌아가지만 y_train 자리에 평가용 피처가 들어가서, 다음 줄 fit에서 행 수가 안 맞는다는 오류가 나거나 엉뚱한 결과가 나옵니다.

학습용은 모델이 fit으로 규칙을 배우는 데 쓰고, 평가용은 다 배운 모델에 처음 보여 줘 점수를 매기는 데만 씁니다. 평가용을 학습에 한 번이라도 쓰면 그 점수는 더 이상 처음 보는 데이터의 점수가 아닙니다.

test_size

test_size는 평가용의 크기입니다. 0과 1 사이의 소수를 주면 비율로, 정수를 주면 행 수로 읽습니다.

인자 1,000행일 때 뜻
생략 학습 750 · 평가 250 기본값 0.25
test_size=0.2 학습 800 · 평가 200 비율
test_size=150 학습 850 · 평가 150 행 수
train_size=0.7 학습 700 · 평가 300 학습 쪽 비율

비율이 행 수로 딱 안 떨어지면 평가용 쪽을 올림합니다. 1,001행에 0.2를 주면 200.2가 올림되어 평가 201, 학습 800입니다. 「8:2로 나누시오」는 test_size=0.2, 「7:3」은 0.3입니다.

shuffle

train_test_split은 기본으로 행을 섞은 뒤 자릅니다(shuffle=True). 파일이 날짜순이나 클래스순으로 정렬되어 있으면 섞지 않고 자를 때 평가용에 뒤쪽 한 종류만 몰리기 때문입니다. 시계열처럼 앞날로 뒷날을 맞혀야 하는 데이터만 shuffle=False로 앞뒤를 그대로 자릅니다.

random_state와 stratify

random_state

섞는 일은 난수로 하므로 돌릴 때마다 다른 행이 평가용에 들어갑니다. 그러면 점수도 돌릴 때마다 조금씩 바뀝니다. random_state에 정수를 주면 난수의 출발점이 고정되어 몇 번을 돌려도 같은 행이 같은 쪽으로 갑니다.

어떤 정수를 쓰는지는 결과의 좋고 나쁨과 관계가 없습니다. 42가 흔한 것은 관례일 뿐입니다. 다만 시험이 값을 지정하면 그 값을 그대로 써야 채점 기준과 같은 분할이 나옵니다. 모델 쪽에도 random_state가 따로 있으므로 — 랜덤포레스트가 나무마다 행을 뽑을 때 쓴다 — 문항이 둘 다 지정하면 둘 다 적습니다.

stratify

분류 문제에서 클래스가 고르지 않으면 섞는 운에 따라 평가용의 비율이 흔들립니다. 1,000행 중 이탈이 100행(10%)인 표를 0.2로 나누면 평가용 200행에 이탈이 20행쯤 들어가야 하는데, random_state를 0에서 4까지 바꿔 보면 15·20·15·26·19행으로 흔들립니다.

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

stratify=y를 주면 y의 클래스 비율을 양쪽에 그대로 나눠 담습니다. 위 표라면 학습용 800행에 이탈 80행, 평가용 200행에 이탈 20행이 정확히 들어갑니다. 이렇게 층마다 비율을 맞춰 뽑는 것을 층화 추출이라 부릅니다.

넘기는 값은 True가 아니라 비율을 맞출 기준 열 자체입니다. 그리고 회귀처럼 타깃이 연속값이면 쓰지 않습니다 — 값마다 한두 행뿐이라 나눠 담을 층이 안 서고, 어느 클래스가 한 행뿐이어도 「least populated class」 오류가 납니다.

전처리와 분할의 순서

배우는 값

전처리 중 어떤 것은 분할 전에 해도 되고 어떤 것은 뒤에 해야 합니다. 가르는 질문은 하나입니다 — 그 처리가 여러 행을 보고 값을 계산해 배우는가.

처리 배우는 값 자리
열 지우기, 표기 통일, 자료형 바꾸기 없음 분할 전후 상관없음
원핫 인코딩 범주 이름 목록 분할 전에 한 번
결측치 평균·중앙값 대치 평균, 중앙값 분할 뒤, 학습용에서
이상치 IQR 경계 Q1Q_1, Q3Q_3 분할 뒤, 학습용에서
스케일링 평균·표준편차·최댓값 분할 뒤, 학습용에서

평균·사분위수·최댓값은 계산할 때 들어간 행에 따라 달라집니다. 분할 전 전체에서 구하면 평가용 행이 그 값에 섞이므로 앞 노트의 데이터 누수가 됩니다. 범주 이름 목록은 값의 크기를 담지 않아 섞여도 새는 것이 없습니다.

전체 흐름

한 문항이 처음부터 끝까지 이어질 때의 차례는 이렇습니다.

  1. 열 정리·표기 통일·자료형 되돌리기
  2. 범주형 열 원핫 인코딩, 타깃 라벨 인코딩
  3. X·y 분리
  4. train_test_split
  5. 결측 대치·스케일링을 학습용에 fit_transform, 평가용에 transform
  6. 모델 fit

시험의 개별 문항은 흔히 이 순서를 지키지 않습니다. 「Income의 결측치를 중앙값으로 채우시오」가 분할보다 먼저 나오는 식입니다. 그럴 때는 문항이 시키는 대로 답하고, 순서 자체를 묻는 문항에서만 위 차례를 기준으로 고릅니다.

검증용 분할

하이퍼파라미터를 고르거나 신경망의 학습을 지켜볼 때는 학습용에서 다시 한 조각을 떼어 검증용으로 씁니다. 평가용은 끝까지 손대지 않고 마지막에 한 번만 봅니다.

X_tr, X_val, y_tr, y_val = train_test_split(
    X_train, y_train, test_size=0.25, random_state=42
)

학습용 80% 중 25%를 떼면 전체의 20%이므로 학습·검증·평가가 60·20·20이 됩니다. 케라스는 fit(validation_split=0.2)로 같은 일을 대신해 주므로 모델링 과목에서 다시 나옵니다.

출제 형태는 셋입니다. 반환값을 받는 차례를 채우게 하거나, test_size와 행 수를 주고 각 쪽의 크기를 묻거나, 불균형 타깃에 붙여야 할 인자를 고르게 합니다.

연습 문제

  1. 표 df에서 타깃 Price를 y로, 나머지 열 전부를 X로 나누는 코드를 쓰시오.
    타깃은 대괄호 한 겹으로 꺼냅니다.
    X = df.drop(columns=['Price'])
    y = df['Price']
  2. 다음 중 train_test_split(X, y)의 반환값을 받는 차례로 옳은 것은?
    ① X_train, y_train, X_test, y_test
    ② X_train, X_test, y_train, y_test
    ③ X_test, X_train, y_test, y_train
    ④ y_train, y_test, X_train, X_test
    ②. 넣은 배열마다 학습·평가 차례로 둘씩 돌려줍니다. X가 먼저 들어갔으므로 X의 둘, 그다음 y의 둘입니다.
  3. 2,500행인 표를 test_size=0.3으로 나누면 학습용과 평가용은 각각 몇 행인가? test_size를 생략하면 어떻게 되는가?
    평가용은 2500×0.3=7502500 \times 0.3 = 750행, 학습용은 1,750행입니다. 생략하면 기본값 0.25가 쓰여 평가용 625행, 학습용 1,875행입니다.
  4. 3,000행 중 사기 거래가 150행(5%)인 표를 test_size=0.2, stratify=y로 나눴다. 평가용에 사기 거래는 몇 행 들어가는가?
    평가용은 3000×0.2=6003000 \times 0.2 = 600행이고 그 5%인 30행이 사기 거래입니다. 학습용 2,400행에는 120행이 들어갑니다. stratify=y가 원래 비율을 양쪽에 그대로 나눠 담습니다.
  5. 같은 코드를 두 번 돌렸는데 정확도가 0.84와 0.87로 달랐다. 데이터와 모델은 그대로다. 원인과 고치는 방법을 쓰시오.
    random_state를 주지 않아 돌릴 때마다 다른 행이 평가용에 들어갔습니다. train_test_split(..., random_state=42)처럼 정수를 주면 분할이 고정됩니다. 랜덤포레스트처럼 모델 안에도 난수가 있으면 모델에도 random_state를 줍니다.
  6. 다음 중 train_test_split보다 먼저 전체 데이터에 해도 데이터 누수가 생기지 않는 것은?
    ① 결측치를 평균으로 채우기
    ② StandardScaler로 표준화하기
    ③ 고객 번호 열 지우기
    ④ IQR 경계로 이상치 자르기
    ③. 열을 지우는 것은 다른 행의 값을 보고 배우는 것이 없습니다. 나머지 셋은 평균·표준편차·사분위수를 계산하므로 분할 전에 하면 평가용 행이 그 값에 섞입니다.
  7. 전체 데이터를 학습·검증·평가 70·15·15로 나누려 한다. 먼저 평가용을 test_size=0.15로 떼었다면, 남은 학습용에서 검증용을 뗄 때 test_size는 얼마로 줘야 하는가? 소수 넷째 자리에서 반올림하시오.
    남은 학습용은 전체의 85%이고 그중 전체의 15%를 떼야 하므로 0.15/0.85≈0.1760.15 / 0.85 \approx 0.176입니다. 1,000행이면 평가 150행을 뗀 뒤 남은 850행에 0.176을 줘 검증 150행(149.6을 올림), 학습 700행이 됩니다.

여기서 손이 굳어야 하는 것은 한 줄입니다 — X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)입니다. 데이터가 모델 앞에 놓였으면 남은 것은 어떤 모델을 꺼낼지 정하는 일입니다.

AICE (에이스) 시험 노트 전체 보기