교차 검증
교차 검증(cross validation)은 전체 데이터 샘플 개수가 많지 않을 때 검증 세트를 훈련 세트에서 분리하느라 훈련 세트의 샘플 개수가 줄어들어 모델을 훈련시킬 데이터가 부족해지는 경우에 검증 세트를 분리하지 않기 위해 사용한다.
교차 검증의 원리
교차 검증은 훈련 세트를 작은 덩어리로 나누어 다음과 같이 진행하는데, 이때 훈련 세트를 나눈 작은 덩어리를 '폴드'라고 부른다.
위 그림을 보면 교차 검증을 쉽게 이해할 수 있다. 교차 검증은 전체 데이터 세트를 8:2로 나눈 다음 8에 해당하는 훈련 세트를 다시 5개의 작은 덩어리로 나눈다. 그런 다음 작은 덩어리를 1번씩 검증에 사용하고 나머지 덩어리를 훈련에 사용한다. 교차 검증 과정을 정리하면 다음과 같다.
1. 훈련 세트를 k개의 폴드(fold)로 나눈다.
2. 첫 번째 폴드를 검증 세트로 사용하고 나머지 폴드(k-1개)를 훈련 세트로 사용한다.
3. 모델을 훈련한 다음에 검증 세트로 평가한다.
4. 차례대로 다음 폴드를 검증 세트로 사용하여 반복한다.
5. k개의 검증 세트로 k번 성능을 평가한 후 계산된 성능의 평균을 내어 최종 성능을 계산한다.
이때 교차 검증은 훈련 세트를 k개의 폴드로 나누는 특징이 있으므로 k-폴드 교차 검증이라고 부른다. k-폴드 교차 검증은 모든 훈련 세트가 평가에 1번씩 사용되므로 검증 점수가 안정적이다. 그리고 기존의 훈련 방법보다 더 많은 데이터로 훈련할 수 있다.
k-폴드 교차 검증 구현
k-폴드 교차 검증은 훈련 세트를 동일한 크기의 폴드가 k개가 되도록 나눈다. 그런 다음 각 폴드를 검증 세트로, 나머지 폴드를 훈련 세트로 사용한다. 이 과정을 k번 반복하여 모델을 만들고 평가하면 된다.
k-폴드 교차 검증은 검증 세트가 훈련 세트에 포함된다. 따라서 전체 데이터 세트를 다시 훈련 세트와 테스트 세트로 1번만 나눈 x_train_all과 y_train_all을 훈련과 검증에 사용한다. 또 각 폴드의 검증 점수를 저장하기 위한 validation_scores 리스트를 정의한다. validation_scores 리스트의 값을 평균하여 최종 검증 점수를 계산한다.
이제 k-폴드 교차 검증을 위한 반복문을 구현하겠다. 여기서는 k를 10으로 지정한다. 한 폴드에 들어갈 샘플의 개수는 전체 훈련 세트의 샘플 개수를 k로 나눈 것이므로 그 값을 bins 변수에 저장한다. 그런 다음 bins 변수의 개수만큼 건너뛰며 검증 폴드와 훈련 폴드를 구분하여 모델을 훈련시킨다. 전체 코드는 다음과 같다.
start와 end는 각각 검증 폴드 샘플의 시작과 끝 인덱스이다. 나머지 부분이 바로 훈련 폴드 샘플의 인덱스이다.
train_index에 list() 함수를 이용하여 훈련 폴드의 인덱스를 모아두고 이렇게 만든 훈련 폴드 샘플의 인덱스로 train_fold와 train_target을 만든다.
이 코드에서 특히 신경 써서 봐야 할 부분은 훈련 데이터의 표준화 전처리를 폴드를 나눈 후에 수행한다는 점이다. 만약 폴드를 나누기 전에 전체 훈련 데이터를 전처리한다면 검증 폴드의 정보를 누설하게 되는 셈이다. 반복문을 진행하며 10개의 검증 폴드로 측정한 성능 점수는 validation_scores 리스트에 저장된다. 마지막으로 성능 점수들의 평균을 내기 위해 np.mean() 함수를 사용한다. k-폴드 교차 검증의 구현이 어렵지는 않지만 보통은 사이킷런에 준비되어 있는 함수를 사용하는 것이 편리하다.
사이킷런으로 교차 검증
사이킷런의 model_selection 모듈에는 교차 검증을 위한 cross_validate() 함수가 있다.
cross_validate() 함수를 사용하는 방법은 간단하다. 이 함수의 매개변수 값으로 교차 검증을 하고 싶은 모델의 객체와 훈련 데이터, 타깃 데이터를 전달하고 cv 매개변수에 교차 검증을 수행할 폴드 수를 지정하면 된다.
cross_validate() 함수는 파이썬 딕셔너리를 반환한다. 검증 점수는 scores['test_score']에 저장되어 있다. 결과를 보면 85%로 낮게 나오는데 이는 표준화 전처리를 수행하지 않았기 때문이다.
전처리 담계 포함해 교차 검증 수행
사이킷런은 검증 폴드가 전처리 단계에서 누설되지 않도록 전처리 단계와 모델 클래스를 하나로 연결해 주는 Pipeline 클래스를 제공한다. Pipeline 클래스와 SGDClassifier 클래스의 작동 방식은 먼저 표준화 전처리 단계(평균, 표준 편차를 계산)와 SGDClassifier 클래스 객체를 Pipeline 클래스로 감싸 cross_validate() 함수에 전달한다. 그러면 cross_validate() 함수는 훈련 세트를 훈련 폴드와 검증 폴드로 나누기만 하고 전처리 단계와 SGDClassifier 클래스 객체의 호출은 Pipeline 클래스 객체에서 이뤄진다. 이렇게 하면 검증 폴드가 전처리 단계에서 누설되지 않게 된다. 사이킷런은 Pipeline 클래스 객체를 만들어주는 헬퍼 함수도 제공하므로 이 방법을 사용하여 Pipeline 객체를 만들겠다. make_pipeline() 함수에 전처리 단계와 모델 객체를 전달하면 파이프라인 객체를 만들 수 있다. 사이킷런에서 표준화 전처리를 수행하는 클래스는 preprocessing 모듈 밑에 있는 StandardScaler 클래스이다. StandardScaler 클래스의 객체와 앞에서 만든 SGDClassifier 클래스의 객체(sgd)를 make_pipeline() 함수에 전달하여 파이프라인 객체를 만들어보겠다.
평균 검증 점수가 높아졌다. 표준화 전처리 단계가 훈련 폴드와 검증 폴드에 올바르게 적용된 것이다. 추가로 cross_validate() 함수에 return_train_score 매개변수를 True로 설정하면 훈련 폴드의 점수도 얻을 수 있다.