기계학습에서 모델을 학습하는데 사용하는 train set, 적합된 모델의 성능을 평가하는데 사용하는 test set 으로 나누어놓고 시작합니다. 


이번 포스팅에서는 2차원 행렬 형태의 데이터셋을 무작위로 샘플링하여 Train set, Test set 으로 분할하는 방법을 소개하겠습니다. 


(1) scikit-learn 라이브러리 model_selection 클래스의 train_test_split 함수를 이용하여 train, test set 분할하기

(2) numpy random 클래스의 permutation() 함수를 이용하여 train, test set 분할하기

(3) numpy random 클래스의 choice() 함수를 이용하여 train, test set 분할하기

(4) numpy random 클래스의 shuffle() 함수를 이용하여 train, test set 분할하기




  (1) scikit-learn.model_selection의 train_test_split 함수로 train, test set 분할하기

     (split train and test set using sklearn.model_selection train_test_split())


제일 편리하고 그래서 (아마도) 제일 많이 사용되는 방법이 scikit-learn 라이브러리 model_selection 클래스의 train_test_split() 함수를 사용하는 것일 것입니다. 무작위 샘플링을 할지 선택하는 shuffle 옵션, 층화 추출법을 할 수 있는 stratify 옵션도 제공하여 간단한 옵션 설정으로 깔끔하게 끝낼 수 있으니 사용하지 않을 이유가 없습니다. 


예제로 사용할 간단한 2차원 numpy array의 X와 1차원 numpy array의 y를 만들어보겠습니다. 



import numpy as np


X = np.arange(20).reshape(10, 2)

X

[Out]:
array([[ 0,  1],
       [ 2,  3],
       [ 4,  5],
       [ 6,  7],
       [ 8,  9],
       [10, 11],
       [12, 13],
       [14, 15],
       [16, 17],
       [18, 19]])

y = np.arange(10)

y

[Out]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])




(1-1) 순차적으로 train, test set 분할


이제 sklearn.model_selection 의 train_test_split() 함수를 사용해서 train set 60%, test set 40%의 비율로 무작위로 섞는 것 없이 순차적으로(shuffle=False) 분할을 해보겠습니다. 시계열 데이터와 같이 순서를 유지하는 것이 필요한 경우에 이 방법을 사용합니다. suffle 옵션의 디폴트 설정은 True 이므로 만약 무작위 추출이 아닌 순차적 추출을 원하면 반드시 shuffle=False 를 명시적으로 설정해주어야 합니다. 



from sklearn.model_selection import train_test_split


# shuffle = False

X_train, X_test, y_train, y_test = train_test_split(X, 

                                                    y, 

                                                    test_size=0.4, 

                                                    shuffle=False

                                                    random_state=1004)



print('X_train shape:', X_train.shape)

print('X_test shape:', X_test.shape)

print('y_train shape:', y_train.shape)

print('y_test shape:', y_test.shape)

[Out]:
X_train shape: (6, 2)
X_test shape: (4, 2)
y_train shape: (6,)
y_test shape: (4,)



X_train

[Out]: 
array([[ 0,  1],
       [ 2,  3],
       [ 4,  5],
       [ 6,  7],
       [ 8,  9],
       [10, 11]])


y_train

[Out]: array([0, 1, 2, 3, 4, 5])





(1-2) 무작위 추출로 train, test set 분할


이번에는 train set 60%, test set 40%의 비율로 무작위 추출(random sampling, shuffle=True)하여 분할을 해보겠습니다. random_state 는 재현가능(for reproducibility)하도록 난수의 초기값을 설정해주는 것이며, 아무 숫자나 넣어주면 됩니다. shuffle=True 가 디폴트 설정이므로 생략 가능합니다. 



# shuffle = True

X_train, X_test, y_train, y_test = train_test_split(X, 

                                                    y, 

                                                    test_size=0.4, 

                                                    shuffle=True

                                                    random_state=1004)


X_train

array([[ 2,  3],
       [ 8,  9],
       [ 6,  7],
       [14, 15],
       [10, 11],
       [ 4,  5]])

 


y_train

array([1, 4, 3, 7, 5, 2])





  (2) numpy random 클래스의 permutation 함수를 이용하여 train, test set 분할하기


이번에는 numpy 라이브러리를 이용해서 train, test set을 분할하는 사용자 정의 함수(user defined function)를 직접 만들어보겠습니다. 방법은 간단합니다. 먼저 np.random.permutation()으로 X의 관측치 개수(X.shape[0])의 정수를 무작위로 섞은 후에, --> train_num만큼의 train set을 슬라이싱하고, test_num 만큼의 test set을 슬라이싱 합니다. 

np.random.seed(seed_number) 는 재현가능성을 위해서 난수 초기값을 설정해줍니다. 



# UDF of split train, test set using np.random.permutation()

# X: 2D array, y:1D array

def permutation_train_test_split(X, y, test_size=0.2, shuffle=True, random_state=1004):

    import numpy as np

    

    test_num = int(X.shape[0] * test_size)

    train_num = X.shape[0] - test_num

    

    if shuffle:

        np.random.seed(random_state)

        shuffled = np.random.permutation(X.shape[0])

        X = X[shuffled,:]

        y = y[shuffled]

        X_train = X[:train_num]

        X_test = X[train_num:]

        y_train = y[:train_num]

        y_test = y[train_num:]

    else:

        X_train = X[:train_num]

        X_test = X[train_num:]

        y_train = y[:train_num]

        y_test = y[train_num:]

        

    return X_train, X_test, y_train, y_test



# create 2D X and 1D y array

X = np.arange(20).reshape(10, 2)

y = np.arange(10)



# split train and test set using by random sampling

X_train, X_test, y_train, y_test = permutation_train_test_split(X, 

                                                                y, 

                                                                test_size=0.4, 

                                                                shuffle=True,

                                                                random_state=1004)


X_train

[Out]:
array([[ 0,  1],
       [12, 13],
       [16, 17],
       [18, 19],
       [ 2,  3],
       [ 8,  9]])

y_train

[Out]: array([0, 6, 8, 9, 1, 4])






  (3) numpy random 클래스의 choice 함수를 이용하여 train, test set 분할하기


(3-1) 다음으로 numpy.random.choice(int_A, int_B, replace=False) 함수를 사용하면 비복원추출(replace=False)로 A개의 정수 중에서 B개의 정수를 무작위로 추출하여 이를 train set의 index로 사용하고, np.setdiff1d() 함수로 train set의 index를 제외한 나머지 index를 test set index로 사용하여 indexing하는 방식입니다. 



def choice_train_test_split(X, y, test_size=0.2, shuffle=True, random_state=1004):

    

    test_num = int(X.shape[0] * test_size)

    train_num = X.shape[0] - test_num

    

    if shuffle:

        np.random.seed(random_state)

        train_idx = np.random.choice(X.shape[0], train_num, replace=False)

        

        #-- way 1: using np.setdiff1d()

        test_idx = np.setdiff1d(range(X.shape[0]), train_idx)

        

        X_train = X[train_idx, :]

        X_test = X[train_idx, :]

        y_train = y[test_idx]

        y_test = y[test_idx]

        

    else:

        X_train = X[:train_num]

        X_test = X[train_num:]

        y_train = y[:train_num]

        y_test = y[train_num:]

        

    return X_train, X_test, y_train, y_test

 



(3-2) 아래는 위의 (3-1)과 np.random.choice()를 사용하여 train set 추출을 위한 index 번호 무작위 추출은 동일하며, test set 추출을 위한 index를 for loop 과 if not in 조건문을 사용하여 list comprehension 으로 생성한 부분이 (3-1)과 다릅니다. 



def choice_train_test_split(X, y, test_size=0.2, shuffle=True, random_state=1004):

    

    test_num = int(X.shape[0] * test_size)

    train_num = X.shape[0] - test_num

    

    if shuffle:

        np.random.seed(random_state)

        train_idx = np.random.choice(X.shape[0], train_num, replace=False)

        

        #-- way 2: using list comprehension with for loop

        test_idx = [idx for idx in range(X.shape[0]) if idx not in train_idx]

        

        X_train = X[train_idx, :]

        X_test = X[train_idx, :]

        y_train = y[test_idx]

        y_test = y[test_idx]

        

    else:

        X_train = X[:train_num]

        X_test = X[train_num:]

        y_train = y[:train_num]

        y_test = y[train_num:]

        

    return X_train, X_test, y_train, y_test

 




  (4) numpy random shuffle() 함수를 이용하여 train, test set 분할하기

    (split train, test set using np.random.shuffle() function)


np.random.shuffle() 함수는 배열을 통째로 무작위로 섞은 배열을 반환합니다. 따라서 무작위로 섞었을 때 X와 y가 동일한 순서로 무작위로 섞인 결과를 얻기 위해서 (4-1) X와 y를 먼저 np.column_stack((X, y)) 를 사용해서 옆으로 나란히 붙인 후에(concatenate), --> (4-2) np.random.shuffle(Xy)로 X와 y 배열을 나란히 붙힌 Xy 배열을 무작위로 섞고 (inplace 로 작동함), --> (4-3) train set 개수 (train_num row) 만큼 위에서 부터 행을 슬라이싱을 하고, X 배열의 열(column) 만큼 슬라이싱해서 X_train set을 만듭니다. (4-4) 무작위로 섞인 Xy 배열로부터 train set 개수(train_num row) 만큼 위에서 부터 행을 슬라이싱하고, y 배열이 제일 오른쪽에 붙여(concatenated) 있으므로 Xy[train_num:, -1] 로 제일 오른쪽의 행을 indexing 해오면 y_train set을 만들 수 있습니다. 



# UDF of split train, test set using np.random.shuffle()

# X: 2D array, y:1D array

def shuffle_train_test_split(X, y, test_size=0.2, shuffle=True, random_state=1004):

    import numpy as np

    

    test_num = int(X.shape[0] * test_size)

    train_num = X.shape[0] - test_num

    

    if shuffle:

        np.random.seed(random_state) # for reproducibility

        Xy = np.column_stack((X, y)) # concatenate first

        np.random.shuffle(Xy)           # random shuffling second

        X_train = Xy[:train_num, :-1]  # slicing from 1 to train_num row, X column

        X_test = Xy[train_num:, :-1]   # slicing from 1 to train_num row, y column

        y_train = Xy[:train_num, -1]

        y_test = Xy[train_num:, -1]

    else:

        X_train = X[:train_num]

        X_test = X[train_num:]

        y_train = y[:train_num]

        y_test = y[train_num:]

        

    return X_train, X_test, y_train, y_test


# shuffle = True

X = np.arange(20).reshape(10, 2)

y = np.arange(10)


X_train, X_test, y_train, y_test = shuffle_train_test_split(X, 

                                                            y, 

                                                            test_size=0.4, 

                                                            shuffle=True)


X_train

[Out]:
array([[ 0,  1],
       [12, 13],
       [16, 17],
       [18, 19],
       [ 2,  3],
       [ 8,  9]])


y_train

[Out]: array([0, 6, 8, 9, 1, 4])




무작위 층화 추출법을 이용한 train set, test set 분할 방법(train and test set split by stratified random sampling in python)은 https://rfriend.tistory.com/520 를 참고하시기 바랍니다. 


np.random.choice() 메소드를 활용한 1-D 배열로 부터 임의확률표본추출하는 방법(Generate a random sample frm a given 1-D array)은 https://rfriend.tistory.com/548 를 참고하시기 바랍니다. 


pandas DataFrame에 대한 무작위 표본 추출 방법은 https://rfriend.tistory.com/602 를 참고하세요.


많은 도움이 되었기를 바랍니다. 

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. :-)



Posted by R Friend Rfriend

댓글을 달아 주세요

  1. 이준기 2020.04.20 14:57  댓글주소  수정/삭제  댓글쓰기

    (1)에 (1-1) scikit learn 설명 부분에 혹시 오타가 있는건 아닌지요? 코드는 제대로 되어 있는데 글에 오타가 있는 것 같습니다. 순차적 샘플링 시에는 shffle=False가 맞지 않은지요?