다시 살펴보는 머신러닝 주요 개념 2편 – 데이터 인코딩, 피처 스케일링, 교차 검증

경진대회를 푸는 데 필요한 주요 머신러닝 개념들을 요약·정리해뒀습니다. 머신러닝 이론을 기초부터 차근히 설명하려는 목적이 ‘아니므로’ 정독하실 필요는 없습니다. 문제를 풀다가 언뜻 떠오르지 않는 개념이 있을 때 참고해주세요. 총 4편으로 준비했습니다. 2편에서는 데이터 인코딩과 피처 스케일링, 교차 검증에 대해서 알아봅시다.

 

1. 데이터 인코딩

머신러닝 모델은 문자 데이터를 인식하지 못합니다. 그렇기 때문에 문자로 구성된 범주형 데이터는 숫자로 바꿔야 합니다. 경우에 따라서 이미 숫자로 구성된 범주형 데이터도 모델 성능을 향상하기 위해 다른 숫자 데이터로 바꾸기도 합니다. 이렇듯 범주형 데이터를 숫자 형태로 바꾸는 작업을 데이터 인코딩이라고 합니다. 대표적인 데이터 인코딩 방식으로는 레이블 인코딩과 원-핫 인코딩이 있습니다.

 

1.1 레이블 인코딩

레이블 인코딩(label encoding)은 범주형 데이터를 숫자로 일대일 매핑해주는 인코딩 방식입니다. 범주형 데이터를 숫자로 치환하는 겁니다. 사이킷런의 LabelEncoder로 구현할 수 있습니다. 다음은 레이블 인코딩을 적용해 과일 문자열 데이터를 숫자형으로 변환하는 코드입니다.

from sklearn.preprocessing import LabelEncoder # 레이블 인코더
fruits = ['사과', '블루베리', '바나나', '귤', '블루베리', '바나나', '바나나', '사
과']

# 레이블 인코더 생성
label_encoder = LabelEncoder()
# 레이블 인코딩 적용
fruits_label_encoded = label_encoder.fit_transform(fruits)

print('레이블 인코딩 적용 후 데이터:', fruits_label_encoded)
레이블 인코딩 적용 후 데이터: [3 2 1 0 2 1 1 3]

 

귤은 0, 바나나는 1, 블루베리는 2, 사과는 3으로 바뀌었습니다. 레이블 인코딩을 적용하면 원본 데이터의 값에 사전순으로 번호를 매깁니다.

▼ 레이블 인코딩 적용

???? warning: 1차원 데이터에 적용하는 LabelEncoder 외에 2 차원 데이터에 적용하는 OrdinalEncoder도 있습니다. 앞의 예에서는 1차원 데이터를 다뤘으므로 LabelEncoder를 사용 했습니다. 경진대회에서 여러 피처를 인코딩하려면 OrdinalEncoder를 사용하는 게 좋습니다. 물론 for문을 순 회하며 LabelEncoder를 적용해도 OrdinalEncoder와 같 은 효과가 있습니다.

 

레이블 인코딩은 간단하지만 단점이 있습니다. 명목형 데이터를 레이블 인코딩하면 모델 성능이 떨어질 수 있다는 점입니다. 머신러닝 모델이 서로 가까운 숫자를 비슷한 데이터라고 판단하기 때문이죠. 머신러닝 모델은 1(바나나)과 3(사과)보다 1(바나나)과 2(블루베리)를 더 비슷한 데이터라고 인식합니다. 실제로는 그렇지 않죠? 바나나, 사과, 블루베리는 별개 데이터입니다. 단지 1, 2, 3으로 독립적으로 치환했을 뿐이죠. 이 문제는 원-핫 인코딩으로 해결할 수 있습니다.

 

 

1.2 원-핫 인코딩

원-핫 인코딩one-hot encoding은 여러 값 중 하나one만 활성화hot하는 인코딩입니다. 실행 절차는 다음과 같습니다.

  1. 인코딩하려는 피처의 고윳값 개수를 구합니다.
  2. 피처의 고윳값 개수만큼 열을 추가합니다.
  3. 각 고윳값에 해당하는 열에 1을 표시하고 나머지 열에는 0을 표시합니다.

▼ 원-핫 인코딩 적용

원-핫 인코딩은 레이블 인코딩의 문제(서로 가까운 숫자를 비슷한 데이터로 판단하는 문제)를 해결합니다. 그렇지만 원-핫 인코딩도 열 개수가 지나치게 많아진다는 단점이 있습니다. 피처의 고윳값이 많으면 그만큼 열 개수와 메모리 사용량이 늘어나기 때문에 모델 훈련 속도가 느려질 우려가 있습니다.

 

????TIP: 그러면 명목형 피처에 고윳값이 상당히 많을 땐 어떻게 해결해야 할까요?

  1. 비슷한 고윳값끼리 그룹화 : 그룹화하면 해당 명목형 피처의 고윳값 개수가 줄어드는 효과가 있습니다.
  2. 빈도가 낮은 고윳값을 ‘기타(etc)’로 처리하기 : 비슷한 고윳값끼리 그룹화하는 방법과 비슷합니다. 빈도가 낮은 고윳값들을 묶어 ‘기타 고윳값’으로 일괄 처리하는 방법입니다.
  3. 다른 인코딩 적용하기: 타깃 인코딩, 프리퀀시 인코딩 등 그 외 인코딩 기법이있습니다. 다른 인코딩 기법도 각자 단점이 있긴 합니다.

 

다음은 문자열 데이터를 원-핫 인코딩하는 코드입니다. 문자열 데이터에 바로 원-핫 인코딩을 적용할 순 없으니, 먼저 숫자형 데이터로 변환해야 합니다. 레이블 인코딩을 활용하면 됩니다.

from sklearn.preprocessing import LabelEncoder, OneHotEncoder

fruits = ['사과', '블루베리', '바나나', '귤', '블루베리', '바나나', '바나나', '사과']

# 레이블 인코더, 원-핫 인코더 생성
label_encoder = LabelEncoder()
onehot_encoder = OneHotEncoder()

# 레이블 인코딩 적용(문자 데이터 -> 숫자 데이터)
fruits_label_encoded = label_encoder.fit_transform(fruits)
# 원-핫 인코딩 적용
fruits_onehot_encoded = onehot_encoder.fit_transform(fruits_label_encoded.reshape(-1, 1))

print('원-핫 인코딩 적용 후 데이터:\n', fruits_onehot_encoded.toarray())
원-핫 인코딩 적용 후 데이터:
[[0. 0. 0. 1.]
 [0. 0. 1. 0.]
 [0. 1. 0. 0.]
 [1. 0. 0. 0.]
 [0. 0. 1. 0.]
 [0. 1. 0. 0.]
 [0. 1. 0. 0.]
 [0. 0. 0. 1.]]

 

인코딩 결과가 앞의 ‘원-핫 인코딩 적용’ 그림과 똑같습니다. 1 레이블 인코딩한 데이터( 즉, 문자를 숫자로 바꾼 데이터)를 활용해 2 원-핫 인코딩을 적용했습니다. 이때 레이블 인코딩된 데이터는 1차원이라서 중간에 reshape(-1, 1) 메서드를 이용해 2차원으로 바꿨습니다.

 

reshape( ) 메서드 용법

reshape( ) 메서드는 배열 형상을 바꿀 때 사용합니다. 다음과 같이 1차원 배열인 fruits_label_encoded를 예로 reshape( ) 용법을 알아보겠습니다.

fruits_label_encoded
array([3, 2, 1, 0, 2, 1, 1, 3])

이를 (4, 2) 형상의 행렬로 바꾸려면 다음과 같이 reshape(4, 2)를 호출하면 됩니다.

fruits_label_encoded.reshape(4, 2)
array([[3, 2],
       [1, 0],
       [2, 1],
       [1, 3]])

reshape( )에 넘기는 값 중 하나를 -1로 지정할 수도 있습니다. 그러면 원본 데이터와 나머지 형상을 참고해서 최종 형상을 적절히 바꿔줍니다. 다음 예를 보시죠.

fruits_label_encoded.reshape(-1, 1)
array([[3],
       [2],
       [1],
       [0],
       [2],
       [1],
       [1],
       [3]])

reshape(-1, 1)을 호출해서 (8, 1) 형상으로 만들었습니다. 만약 reshape(-1, 4)로 호출하면 (2, 4) 형상이 되며, reshape(4, -1)을 호출하면 (4, 2) 형상이 됩니다. 그럼 reshape(3, -1)을 호출하면 어떻게 될까요? 원본 데이터가 총 8개라서 3으로 딱 나누어 떨어지지 않기 때문에 오류가 납니다.

한편, 원-핫 인코딩은 대부분 값이 0인 희소행렬을 만들어냅니다. 희소행렬은 메모리 낭비가 심하기 때문에 OneHotEncoder는 변환 결과를 압축된 형태인 CSR(Compressed Sparse Row) 행렬로 돌려줍니다. 3 의 마지막에 호출한 .toarray( )는 CSR 형태의 행렬을 일반 배열로 바꿔주는 역할을 합니다.

판다스의 get_dummies( ) 함수3를 사용하면 다음과 같이 더 간단하게 구현할 수도 있습니다. 문자열 데이터를 숫자형으로 바꾸지 않아도 되어 훨씬 편리합니다.

import pandas as pd

pd.get_dummies(fruits)

▼ 실행결과

OneHotEncoder로 변환한 인코딩 결과와 같죠?

 

 

2. 피처 스케일링

피처 스케일링feature scaling이란 서로 다른 피처 값의 범위(최댓값 – 최솟값)가 일치하도록 조정하는 작업을 말합니다. 값의 범위가 데이터마다 다르면 모델 훈련이 제대로 안 될 수도 있습니다.

 

???? Note: 단, 트리 기반 모델(랜덤 포레스트, XGBoost, LightGBM 등)은 피처 스케일링이 필요 없습니다. 트리 기반 모델은 데이터의 크기보다는 대소 관계에 영향을 받기 때문입니다. 피처 스케일링을 하더라도 데이터의 대소 관계에는 변함이 없습니다.

 

예를 들어보겠습니다. 광일, 혜성, 덕수의 키와 몸무게는 다음 표와 같습니다. 광일이의 옷 사이즈는 L이고 혜성이의 옷 사이즈는 S일 때, 덕수의 옷 사이즈는 L일까요 S일까요?

▼ 키,몸무게에 따른 옷 사이즈

직관적으로 봤을 때 L일 겁니다. 광일이보다 몸무게는 적지만 키가 크기 때문이죠. 머신러닝 모델은 덕수의 옷 사이즈를 어떻게 예측할까요? 간단하게 키와 몸무게를 더하는 방법이 있습니다. 더한 값이 광일이와 가까우면 L, 혜성이와 가까우면 S로 예측할 겁니다. 하지만 이 간단한 알고리즘에 따르면 계산 결과가 다음과 같습니다.

  • 광일:1.7+75=76.7
  • 혜성:1.5+55=56.5
  • 덕수:1.8+60=61.8

즉, 덕수의 키와 몸무게의 합(61.8)은 광일(76.7)보다 혜성(56.5)에 가깝기 때문에 이 머신러닝 모델은 덕수의 옷 사이즈를 S로 예측할 것입니다. 잘못 예측했습니다. 왜 이런 결과가 나왔을까요? 키와 몸무게 범위가 서로 다르기 때문입니다. 키의 최댓값(1.8)과 최솟값(1.5) 차이는 0.3인 한편, 몸무게의 최댓값(75)과 최솟값(55) 차이는 20입니다. 67배 차이가 나네요. 오류를 개선하려면 키와 몸무게 값의 범위를 같은 수준으로 맞춰야 합니다. 이때 필요한 기법이 피처 스케일링입니다.

피처 스케일링에는 다양한 방법이 있으며, 여기서는 그중 가장 많이 쓰이는 min-max 정규화와 표준화를 살펴보겠습니다.

 

 

2.1 min-max 정규화

min-max 정규화(min-max normalization)는 피처 값의 범위를 0~1로 조정하는 기법입니다. 조정 후 최솟값은 0, 최댓값은 1이 됩니다. 방법은 간단합니다. 피처 x에서 최솟값을 뺀 뒤, 그 값을 최댓값과 최솟값의 차이로 나누면 됩니다. 수식으로는 다음과 같습니다.

min-max 정규화를 적용해 덕수의 옷 사이즈를 다시 예측해보겠습니다.

 

???? TIP: min-max 정규화는 모든 값의 범위를 0~1 사이로 맞출 때 주로 사용합니다.

 

▼ min-max 정규화를 적용한 키와 몸무게

덕수의 키와 몸무게 합은 1.25입니다. 혜성(0)보다 광일(1.67)에 가까워졌네요. 따라서 덕수의 옷 사이즈는 L이라고 예측할 수 있겠네요. 피처 스케일링을 적용하니 올바르게 예측했습니다.

 

???? warning: 이상치(대부분의 값과 동떨어진 값)가 너무 크거나 작을 때는 min-max 정규화가 좋지 않은 결과를 낼 수 있습니다. min-max 정규화 후에 이상치는 0 또는 1이 되겠지만 나머지 값들은 아주 미세한 차이로 좁은 구간에 몰려 있게 됩니다. 따라서 이상치가 너무 크거나 작을 땐 다음 절에서 배울 표준화가 더 바람직합니다.

 

min-max 정규화는 사이킷런의 MinMaxScaler로 구현할 수 있습니다. 앞서 다룬 예시 데이터로 min-max 정규화를 구현해볼까요? 먼저 원본 데이터를 만듭니다.

import pandas as pd

height_weight_dict = {'키': [1.7, 1.5, 1.8], '몸무게': [75, 55, 60]}
df = pd.DataFrame(height_weight_dict, index=['광일', '혜성', '덕수'])

print(df)
    키 몸무게
광일 1.7 75
혜성 1.5 55
덕수 1.8 60

MinMaxScaler가 어떻게 동작하는지 확인해보겠습니다. MinMaxScaler 객체를 생성한 뒤 fit( )과 transform( ) 함수를 호출하면 피처 스케일링이 적용됩니다.

from sklearn.preprocessing import MinMaxScaler

# min-max 정규화 객체 생성
scaler = MinMaxScaler()

# min-max 정규화 적용
scaler.fit(df)
df_scaled = scaler.transform(df)

print(df_scaled)
[[0.66666667 1.        ]
 [0.         0.        ]
 [1.         0.25      ]]

보다시피 앞의 표에서 직접 계산한 값과 같습니다.

 

fit( )과 transform( ) vs. fit_transform( )

참고로 1 의 fit( )과 transform( )은 fit_transform( )으로 한 번에 실행할 수 있습니다.

# min-max 정규화 객체 생성
scaler = MinMaxScaler()

# min-max 정규화 적용
df_scaled = scaler.fit_transform(df)

print(df_scaled)
[[0.66666667 1.        ]
 [0.         0.        ]
 [1.         0.25      ]]

복잡하게 두 함수를 나눠놓은 이유는 다음 예를 보면 이해될 겁니다.

scaler = MinMaxScaler()

scaler.fit(df)
df_scaled = scaler.transform(df)
df_scaled2 = scaler.transform(df2)
df_scaled3 = scaler.transform(df3)

데이터 하나에만 min-max 정규화를 적용하려면 fit_transform( )을 쓰는 게 당연히 더 편리합니다. 하지만 이 코드처럼 한 데이터에 맞춰 놓은 스케일링 범위를 다른 데이터에도 적용하려면 fit( )과 transform( )을 따로 써야 합니다.

 

 

2.2 표준화

표준화(standardization)는 평균이 0, 분산이 1이 되도록 피처 값을 조정하는 기법입니다. min-max 정규화와 다르게 표준화는 상한과 하한이 없습니다. min-max 정규화를 적용하면 상한은 1이고, 하한은 0이었죠. 상한, 하한을 따로 정해야 하는 경우가 아니라면 표준화를 적용할 수 있습니다.

 

???? TIP: 정규분포를 따르는 데이터는 표준화 스케일링을 적용하는 것이 좋습니다.

 

표준화 수식은 다음과 같습니다. xr 는 평균, v 는 표준편차를 의미합니다.

 

사이킷런의 StandardScaler로 표준화를 구현해보겠습니다. 이번엔 fit_transform( ) 함수를 사용해보시죠.

from sklearn.preprocessing import StandardScaler

# StandardScaler 객체 생성
scaler = StandardScaler()

# 표준화 적용
df_scaled = scaler.fit_transform(df)

print(df_scaled)
[[ 0.26726124  1.37281295]
 [-1.33630621 -0.98058068]
 [ 1.06904497 -0.39223227]]

 

 

3. 교차 검증

일반적으로 훈련 데이터로 모델을 훈련하고, 테스트 데이터로 예측해 모델 성능을 측정합니다. 예측한 결과를 제출하면 성능 평가점수가 얼마인지 알게 되죠. 모델을 훈련만 하고, 성능을 검증해보지 않으면 두 가지 문제가 발생할 수 있습니다.

첫째, 모델이 과대적합될 가능성이 있습니다. 경진대회에서 제공하는 훈련 데이터와 테스트 데이터는 고정되어 있습니다. 고정된 훈련 데이터만을 활용해 반복해서 훈련한다면 모델이 훈련 데이터에만 과대적합될 가능성이 있습니다. 과대적합되면 퍼블릭 리더보드에서 높은 점수를 기록해도 프라이빗 리더보드에서 점수가 떨어질 우려가 있습니다.

둘째, 제출 전까지 모델 성능을 확인하기 어렵습니다. 대부분의 경진대회는 일일 제출 횟수가 제한되어 있습니다. 훈련 데이터로 모델 훈련만 하고 무작정 제출하기 힘든 환경입니다. 제출하기 전에 대략적인 성능을 알 수 있으면 좋겠군요. 물론 전체 데이터를 훈련 데이터와 검증 데이터로 나눈 뒤, 검증 데이터로 성능을 가늠해볼 수 있습니다. 하지만 그럴 경우 검증 데이터만큼은 훈련에 사용하지 못해 손실입니다. 실무에서도 마찬가지입니다. 아직 주어지지 않은 미래 데이터로 미리 테스트해볼 수는 없습니다. 즉, 실제 서비스에 적용하기 전에는 모델 성능을 가늠해볼 수 없죠.

이상의 두 문제를 개선하기 위한 방법이 교차 검증입니다. 교차 검증은 훈련 데이터를 여러 그룹으로 나누어 일부는 훈련 시 사용하고, 일부는 검증 시 사용해서 모델 성능을 측정하는 기법입니다. 가장 일반적인 교차 검증 기법은 K 폴드 교차 검증입니다.

 

3.1 K 폴드 교차 검증

K 폴드 교차 검증(K-Fold Cross Validation) 절차는 다음과 같습니다.

  1. 전체 훈련 데이터를 K개 그룹으로 나눕니다.
  2. 그룹 하나는 검증 데이터로, 나머지 K-1개는 훈련 데이터로 지정합니다.
  3. 훈련 데이터로 모델을 훈련하고, 검증 데이터로 평가합니다.
  4. 평가점수를 기록합니다.
  5. 검증 데이터를 다른 그룹으로 바꿔가며 2~4 절차를 K번 반복합니다.
  6. K개 검증 평가점수의 평균을 구합니다.

K개 검증 평가점수의 평균이 최종 평가점수이며, 제출하지 않고도 이 점수를 통해 모델 성능을 가늠해볼 수 있습니다.

▼ K폴드교차검증(K=5일때)

 

K 폴드 교차 검증 시 데이터가 어떻게 나뉘는지 파이썬 코드로 확인해보겠습니다.

import numpy as np
from sklearn.model_selection import KFold

data = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

folds = KFold(n_splits=5, shuffle=False)

for train_idx, valid_idx in folds.split(data):
    print(f'훈련 데이터: {data[train_idx]}, 검증 데이터: {data[valid_idx]}')
훈련 데이터: [2 3 4 5 6 7 8 9], 검증 데이터: [0 1]
훈련 데이터: [0 1 4 5 6 7 8 9], 검증 데이터: [2 3]
훈련 데이터: [0 1 2 3 6 7 8 9], 검증 데이터: [4 5]
훈련 데이터: [0 1 2 3 4 5 8 9], 검증 데이터: [6 7]
훈련 데이터: [0 1 2 3 4 5 6 7], 검증 데이터: [8 9]

KFold( )는 데이터를 K 폴드로 나누는 함수입니다. n_splits 파라미터에 전달하는 값이 K값입니다. 여기서는 5로 설정하여 데이터가 총 5개로 나뉩니다. 폴드가 5개이므로 검증 데이터는 [0, 1], [2, 3], [4, 5], [6, 7], [8, 9]가 됩니다. 순서대로 모든 데이터를 검증에 사용하는 거죠.

데이터가 편향되게 분포되어 있을 수도 있어서 폴드로 나누기 전에 데이터를 섞어주는 게 좋습니다. 다음과 같이 shuffle 파라미터에 True를 전달하면 됩니다.

folds = KFold(n_splits=5, shuffle=True)

for train_idx, valid_idx in folds.split(data):
    print(f'훈련 데이터: {data[train_idx]}, 검증 데이터: {data[valid_idx]}')
훈련 데이터: [0 1 2 3 4 5 6 9], 검증 데이터: [7 8]
훈련 데이터: [1 2 3 5 6 7 8 9], 검증 데이터: [0 4]
훈련 데이터: [0 3 4 5 6 7 8 9], 검증 데이터: [1 2]
훈련 데이터: [0 1 2 3 4 5 7 8], 검증 데이터: [6 9]
훈련 데이터: [0 1 2 4 6 7 8 9], 검증 데이터: [3 5]

데이터가 골고루 섞여 폴드가 구성되었습니다.

 

 

3.2 층화 K 폴드 교차 검증

층화 K 폴드 교차 검증(Stratified K-Fold Cross Validation)은 타깃값이 골고루 분포되게 폴드를 나누는 K 폴드 교차 검증 방법입니다. 타깃값이 불균형하게 분포되어 있는 경우 층화 K 폴드를 사용하는 게 좋습니다.

 

???? TIP: stratify는 사회학에서 ‘수평적 지위 집단으로 나눈다’라는 의미입니다.

 

일반 메일과 스팸 메일을 분류하는 문제를 생각해볼까요? 받은 메일이 1,000개인데, 그중 스팸은 단 10개라고 가정해봅시다. 스팸이 10개밖에 안되므로 K 폴드 교차 검증을 해도 특정 폴드에는 스팸이 아예 없을 수 있습니다. 스팸 데이터 없이 모델을 훈련하면 스팸 예측을 제대로 못하겠죠.

이처럼 특정 타깃값이 다른 타깃값보다 굉장히 적은 경우에 주로 층화 K 폴드 교차 검증을 사용합니다. 이 방식에서는 스팸 데이터를 모든 폴드에 균일하게 나눠줍니다. 즉, 폴드가 5개면 각 폴드에 스팸 데이터를 2개씩 골고루 분배해서 교차 검증을 수행합니다.

참고로 층화 K 폴드 교차 검증은 분류 문제에만 쓰입니다. 회귀 문제의 타깃값은 연속된 값이라서 폴드마다 균등한 비율로 나누는 게 불가능합니다. 균등한 비율로 나누기 위해서는 타깃값이 유한해야 하기 때문입니다.

▼ 층화K폴드교차검증(K=5일때)

코드로도 한번 살펴볼까요? 먼저 데이터 50개를 만듭니다. 이중 일반 메일은 45개, 스팸 메일은 5개입니다. 이 데이터를 우선 K 폴드로 나눠보겠습니다.

y = np.array(['스팸']*5 + ['일반']*45)

folds = KFold(n_splits=5, shuffle=True) # K 폴드 교차 검증

for idx, (train_idx, valid_idx) in enumerate(folds.split(y)):
    print(f'Fold {idx+1} 검증 데이터 타깃 값:')
    print(y[valid_idx], '\n')
Fold 1 검증 데이터 타깃 값:
['스팸' '스팸' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반'] 

Fold 2 검증 데이터 타깃 값:
['스팸' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반'] 

Fold 3 검증 데이터 타깃 값:
['스팸' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반'] 

Fold 4 검증 데이터 타깃 값:
['스팸' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반'] 

Fold 5 검증 데이터 타깃 값:
['일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반']

결과를 보시죠. 세 번째, 네 번째, 다섯 번째 폴드에서는 10개 중 1개만 스팸입니다. 하지만 첫 번째 폴드에는 스팸이 2개고, 두 번째 폴드에선 스팸이 하나도 없습니다. 폴드마다 스팸 분포가 다릅니다. 특히 두 번째 폴드에선 스팸 메일에 대한 훈련이 아예 안 되겠죠? 모든 폴드에 스팸 데이터가 고루 있었으면 좋겠네요. 이럴 때 층화 K 폴드를 쓰면 됩니다.

from sklearn.model_selection import StratifiedKFold

X = np.array(range(50))
y = np.array(['스팸']*5 + ['일반']*45)

folds = StratifiedKFold(n_splits=5) # '층화' K 폴드 교차 검증

for idx, (train_idx, valid_idx) in enumerate(folds.split(X, y)):
    print(f'Fold {idx+1} 검증 데이터 타깃 값:')
    print(y[valid_idx], '\n')
Fold 1 검증 데이터 타깃 값:
['스팸' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반'] 

Fold 2 검증 데이터 타깃 값:
['스팸' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반'] 

Fold 3 검증 데이터 타깃 값:
['스팸' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반'] 

Fold 4 검증 데이터 타깃 값:
['스팸' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반'] 

Fold 5 검증 데이터 타깃 값:
['스팸' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반' '일반']

모든 폴드에 스팸이 1개씩 포함되었습니다.

 

???? warning: KFold의 split()에는 데이터 하나만 전달해도 됩니다. 데이터 불균형 여부와 상관없이 임의로 K개로 분할하기 때문이죠. 반면 StratifiedKFold의 split() 함수에는 피처와 타깃값 모두를 전달해야 합니다. 인수를 하나만 전달하면 다음과 같은 오류가 발생합니다.

TypeError: split() missing 1 required positional argument: 'y'

 

3편에서는 7가지 주요 머신러닝 모델에 대해서 알아봅시다.

신백균
KAIST 산업및시스템공학과 졸업 후 한국생산성본부에서 직무교육 기획 및 운영을 담당하는 전문위원입니다. 세계 랭킹 0.18%의 캐글 노트북 엑스퍼트(Expert)이며, 월 평균 6만여 명이 방문하는 데이터 분석/머신러닝 관련 기술 블로그를 운영하고 있습니다. 참여자 1,200명 이상인 머신러닝 관련 오픈 채팅방의 운영진이기도 합니다.

1 Comment

Leave a Reply

©2020 GoldenRabbit. All rights reserved.
상호명 : 골든래빗 주식회사
(04051) 서울특별시 마포구 양화로 186, 5층 512호, 514호 (동교동, LC타워)
TEL : 0505-398-0505 / FAX : 0505-537-0505
대표이사 : 최현우
사업자등록번호 : 475-87-01581
통신판매업신고 : 2023-서울마포-2391호
master@goldenrabbit.co.kr
개인정보처리방침
배송/반품/환불/교환 안내