검증 세트를 나누고 전처리 과정 배우기
이전에 위스콘신 유방암 데이터 세트를 두 덩어리로 나눈 '훈련 세트'와 '테스트 세트'를 준비했다. 훈련 세트는 fit() 메서드에 전달되어 모델을 훈련하는 데 사용하였고, 테스트 세트는 score() 메서드에 전달해 모델의 성능을 평가했다. 이번에는 '테스트 세트'의 사용 방법에 대해 조금 더 깊이 알아보겠다. 목표는 '어느 데이터 세트에만 치우친 모델을 만들지 않는 것'이다.
테스트 세트로 모델을 튜닝
이전에 했던 것처럼 cancer 데이터 세트를 읽어 들여 훈련 세트와 테스트 세트로 나눈다.
그런 다음 SGDClassifier 클래스를 이용하여 로지스틱 회귀 모델을 훈련해 보겠다. fit() 메서드에 x_train_all, y_train_all을 전달하여 모델을 훈련한다. 그리고 score() 메서드에 x_test, y_test를 전달하여 성능을 평가해 보겠다.
테스트 세트에서 정확도는 약 83%이다. 이 성능이 만족스럽지 않다면 다른 손실 함수를 사용해도 된다. 그런데 loss와 같은 매개변수의 값은 사용자가 직접 선택해야 한다. 이런 값을 특별히 하이퍼파라미터(hyperparameter)라고 부른다. loss의 값을 바꾸면 성능이 정말 좋아지는지 확인해 보겠다.
SGDClassifier 클래스의 loss 매개변수를 log에서 hinge로 바꾸면 선형 서포트 벡터 머신(Support Vector Machine; SVM)문제를 푸는 모델이 만들어진다. 여기서 SVM이란 '훈련 데이터의 클래스를 구분하는 경계선을 찾는 작업' 정도로 이해하면 된다.
성능 평가 결과를 보니 93%로 로지스틱 회귀로 만든 모델의 성능보다 좋아졌다. 성능이 만족스럽지 않을 경우에 이렇게 loss 매개변수에 다른 값을 적용했듯이 SGDClassifier 클래스의 다른 매개변수들을 바꿔보면 된다. 그리고 이런 작업을 '모델을 튜닝한다'고 한다. 그런데 이 모델은 실전에서 좋은 성능을 내지 못할 확률이 높다.
테스트 세트로 모델을 튜닝하면 실전에서 좋은 성능을 기대하기 어렵다
테스트 세트는 실전에 투입된 모델의 성능을 측정하기 위해 사용한다. 그런데 테스트 세트로 모델을 튜닝하면 '테스트 세트에 대해서만 좋은 성능을 보여주는 모델'이 만들어진다. 테스트 세트에 대해서만 좋은 성능을 낼 수 있도록 모델을 튜닝하면 실전에서 같은 성능을 기대하기 어렵다. 이런 현상을 '테스트 세트의 정보가 모델에 새어 나갔다'라고 말한다. 정리하면 테스트 세트로 모델을 튜닝하면 테스트 세트의 정보가 모델에 새어 나가므로 모델의 일반화 성능(generalization performance)이 왜곡 된다. 머신러닝 분야에서는 이를 낙관적으로 성능을 추정한다고도 말한다.
검증 세트를 준비
테스트 세트의 정보가 모델에 새어 나가지 않게하기 위해서는 모델을 튜닝할 때 테스트 세트를 사용하지 않으면 된다. 테스트 세트는 모델 튜닝을 모두 마치고 실전에 투입하기 전에 딱 한 번만 사용하는 것이 좋다. 즉, 모델 튜닝을 위한 세트는 따로 준비해야 한다. 모델을 튜닝하는 용도의 세트는 검증 세트(validation set)라고 하며 훈련 세트를 조금 떼어 만든다. 검증 세트는 개발 세트(dev set)라고도 부른다.
전체 데이터 세트를 훈련, 검증, 테스트 세트로 나누고 SGDClassifier 클래스로 만든 모델을 훈련해 보겠다. 이번에도 위스콘신 유방암 데이터를 사용한다.
전체 데이터 세트를 8:2로 나누어 훈련 세트와 테스트 세트를 만들고 다시 훈련 세트를 8:2로 나누어 훈련 세트와 검증 세트를 만들었다. 455개의 훈련 세트가 8:2 비율로 나누어져 훈련 세트(x_train)는 364개, 검증 세트(x_val)는 91개가 되었다.
검증 세트로 모델을 평가해 보겠다.
훈련 세트의 크기가 줄어들었기 때문에 앞에서 얻은 평가 점수보다 조금 낮아졌다. 사실 위스콘신 유방암 데이터 세트의 샘플 개수는 적은 편이다. 데이터 양이 적으니 검증 세트의 비율이나 random_state 매개변수의 값을 조금만 조절해도 성능 평가 점수가 크게 변하는 것이다. 데이터 양이 너무 적은 경우에는 검증 세트를 나누지 않고 교차 검증(cross validation)이라는 방법을 사용하기도 한다. 하지만 요즘은 대량의 훈련 데이터를 손쉽게 모을 수 있다. 일반적으로 10만 개 정도의 데이터가 있다면 8:1:1 정도로 분할하고 딥러닝은 이보다 더 많은 데이터를 사용하는 경우도 많다. 100만 개 이상의 데이터는 98:1:1 정도의 비율로 샘플을 나눈다. 일반적으로 검증과 테스트 세트의 샘플 수를 1만 개 이상 확보할 수 있다면 훈련 세트에 많은 샘플을 할당하는 것이 좋다.
데이터 전처리와 특성의 스케일
사이킷런과 같은 머신러닝 패키지에 준비되어 있는 데이터는 대부분 실습을 위한 것이므로 잘 가공되어 있다. 하지만 실전에서 수집된 데이터는 그렇지 않다. 누락된 값이 있을 수도 있고 데이터의 형태가 균일하지 않을 수도 있다. 이런 경우 데이터를 적절히 가공하는 '데이터 전처리(data preprocessing)' 과정이 필요하다.
제대로 가공되어 있는 데이터는 전처리가 필요없는데 다만 특성의 스케일(scale)이 다른 경우에는 전처리를 해줘야한다. 특성의 스케일이란 어떤 특성이 가지고 있는 값의 범위를 말한다. 어떤 알고리즘들은 스케일에 민감하여 모델의 성능에 영향을 주는데, 경사 하강법은 스케일에 민감한 알고리즘이므로 특성의 스케일을 맞추는 등의 전처리를 해야 한다. 이때 특성의 스케일을 전처리하는 것을 '스케일을 조정한다'라고 표현한다.
스케일을 조정하지 않고 모델을 훈련했을 때의 단점
특성의 스케일이 서로 다른 데이터를 이용하여 모델을 훈련하면 가중치가 어떻게 변하는지 살펴보겠다.
이 실습에서는 위스콘신 유방암 데이터와 이전에 만든 단일층 신경망 모델을 사용한다. 특성의 스케일을 비교해야 하므로 유방암 데이터의 mean perimeter와 mean area 특성을 가져왔다. 그런 다음 박스 플롯을 그려서 두 특성의 스케일을 확인해 보겠다.
그래프로 보니 두 특성의 스케일은 차이가 크다.
SingleLayer 클래스에 인스턴스 변수를 추가하여 에포크마다 가중치의 값을 저장하여 가중치의 변화를 관찰할 때 사용하겠다. 또 학습률이라는 개념도 도입하겠다. 먼저 init() 메서드에서 인스턴스 변수 w_history를 만들고 학습률 파라미터 learning_rate를 추가한다.
learning_rate는 하이퍼파라미터이며 변수 이름 그대로 '학습률'을 의미하는데, 이 값으로 가중치의 업데이트 양을 조절할 것이다.
위 그림을 보면 높은 학습률을 적용한 모델은 가중치가 큰 폭으로 업데이트되어 전역 최솟값을 그냥 지나쳐버렸음을 알 수 있다. 반대로 적절한 학습률을 적용한 모델은 가중치가 적절한 폭으로 업데이트되므로 천천히 전역 최솟값을 찾는다. 이때 위 그림을 보고 '손실 함수의 표면을 천천히 이동하며 전역 최솟값을 찾는다'라고 말하기도 한다. 주어진 문제마다 적절한 학습률은 다르지만 보통 0.001, 0.01 등의 로그 스케일로 학습률을 지정하여 테스트한다.
위 코드는 fit() 메서드에서 가중치가 바뀔 때마다 w_history 리스트에 가중치를 기록하고 학습률을 이용하여 가중치를 업데이트 하도록(self.lr * w_grad) 수정한 것이다. 넘파이 배열을 리스트에 추가하면 실제 값이 복사되는 것이 아니라 배열을 참조하기 때문에 가중치 변수 self.w의 값이 바뀔 때마다 그 값을 복사하여 w_history 리스트에 추가해야한다.
성능 점수를 확인해 보니 약 91%의 정확도를 보이고 있다.
이제 스케일을 조정하지 않은 훈련 세트를 사용한 모델의 가중치의 변화를 그래프로 그려보겠다. 또 최종으로 결정된 가중치는 점으로 표시하겠다.
그래프를 보면 가중치의 최적값에 도달하는 동안 w3 값이 크게 요동치므로 모델이 불안정하게 수렴한다는 것을 알 수 있다. 이런 현상을 줄이려면 스케일을 조정하면 된다.
스케일을 조정해 모델을 훈련한다
스케일을 조정하는 방법은 많지만 신경망에서 자주 사용하는 스케일 조정 방법 중 하나는 표준화(standardization)이다. 표준화는 특성값에서 평균을 빼고 표준 편차로 나누면 된다. 사이킷런에는 표준화를 위한 StandardScaler 클래스가 준비되어 있지만 여기서는 학습을 위해 직접 표준화를 구현한다.
넘파이의 mean(), std() 함수를 사용하여 평균과 표준 편차를 계산하면 표준화를 쉽게 구현할 수 있다. 표준화를 구현한 다음에는 특성별로 스케일을 조정한다. mean() 과 std() 함수의 axis 매개변수를 0으로 지정하면 2차원 배열의 열을 기준으로 통계치를 계산하여 하나의 행 벡터로 반환해 준다. 그런 다음 훈련 세트 x_train에서 평균을 빼고 표준 편차로 나누면 된다.
이제 스케일을 조정한 데이터 세트로 단일층 신경망을 다시 훈련시키고 가중치를 그래프로 그려보겠다.
이전 그래프와는 확실히 다른 결과를 보여준다. 이처럼 경사 하강법에서는 서로 다른 특성의 스케일을 맞추는 것이 아주 중요하다. 이제 검증 세트를 통해 이 모델을 평가해 보겠다.
검증 세트의 스케일을 조정하지 않아서 성능이 매우 좋지 않게 나온다. 검증 세트도 표준화 전처리를 적용하겠다.
검증 세트에 대한 정확도가 약 96%로 나왔다. 하지만 여기에는 교묘한 함정이 숨어 있다.
스케일을 조정한 다음에 실수하기 쉬운 함정
앞에서 언급한 함정이란 '훈련 세트와 검증 세트가 다른 비율로 스케일이 조정된 경우'를 말한다. 이해를 돕기 위해 원본 훈련 세트와 검증 세트, 전처리된 훈련 세트와 검증 세트에서 데이터를 50개씩 뽑아 산점도를 그린 다음 산점도를 비교해 보며 이 함정에 대해 설명하겠다.
위의 두 산점도를 비교해 보면 조금 미세하지만 훈련 세트와 검증 세트가 각각 다른 비율로 변환되었음을 알 수 있다. 즉, 원본 훈련 세트와 검증 세트의 점과 점 사이의 거리가 변환된 이후에 그대로 유지되지 않았다. 데이터를 제대로 전처리했다면(스케일을 조정했다면) 훈련 세트와 검증 세트의 거리가 그대로 유지되어야 한다. 점과 점 사이의 거리가 달라진 이유는 훈련 세트와 검증 세트를 가각 다른 비율로 전처리했기 때문입니다.
검증 세트의 스케일이 훈련 세트의 스케일과 다른 비율로 조정되면 모델에 적용된 알고리즘들이 검증 세트의 샘플 데이터를 잘못 인식한다. 따라서 검증 세트를 훈련 세트와 같은 비율로 전처리해야 한다. 테스트 세트와 모델을 실전에 투입하여 새로운 샘플을 처리할 때도 마찬가지이다. 실전에는 샘플 하나에 대한 예측값을 만들기 때문에 전처리를 위한 평균이나 표준 편차를 계산할 수도 없다.
훈련 세트의 평균, 표준 편차를 사용하여 검증 세트를 변환하여 훈련 세트와 같은 비율로 검증 세트를 변환 하면 위 문제를 해결할 수 있다.
이제 원본 데이터의 산점도와 스케일 조정 이후의 산점도가 같아졌다. 즉, 검증 세트와 훈련 세트가 동일한 비율로 변환되었다.
이 검증 세트로 모델의 성능을 평가해 보겠다. 이 예제에서 사용하는 위스콘신 유방암 데이터 세트는 크지 않기 때문에 검증 세트를 전처리하기 전과 후의 성능이 동일하다. 즉, 동일한 개수의 샘플을 올바르게 예측했다. 만약 검증 세트가 클 경우에 차이가 나타날 수 있다.
지금까지 검증 세트와 전처리의 필요성에 대해 알아보았다. 실전에 투입될 새로운 데이터에도 같은 전처리를 적용해야 된다는 것도 알았다.