올바른 결과 도출을 위해서 데이터를 사용하기 전에 데이터 전처리 과정을 거치므로 전처리 과정을 거친 데이터로 훈련했을 때의 차이를 알고 표준점수로 특성의 스케일을 변환하는 방법을 배우자
넘파이로 데이터 준비하기
나는 누구인가?
길이가 25cm이고 무게가 150g이면 도미인데 지금까지의 모델은 빙어라고 예측을 한다 무엇이 잘못된 걸까?
넘파이로 데이터 준비하기
도미와 빙어 데이터를 준비하는데 전에는 파이썬 리스트를 순회하면서 원소를 하나씩 꺼내 생선 하나의 길이와 무게를 리스트 안의 리스트로 직접 구성했음 하지만 이제 넘파이를 배웠으므로 훨씬 간편하게 생선 데이터를 준비 넘파이의 column_stack() 함수는 전달받은 리스트를 일렬로 세운 다음 차례대로 나란히 연결하므로 fish_length와 fish_weight를 전달하면 두 리스트가 일렬로 세워진 후 나란히 연결되어 세로로 쌓아지게 됨
+) 세로(열)로 쌓아주는 column_stack() 대신 row_stack()은 가로(행)로 쌓아줌
동일한 방법으로 타깃 데이터를 만들어주는데 전에는 원소가 하나인 리스트 [1], [0]을 여러 번 곱해서 타깃 데이터를 만들었음 하지만 넘파이의 np.ones()와 np.zeros() 함수를 이용하여 각각 원하는 개수의 1과 0을 채운 배열을 쉽게 만들 수 있음 그 다음 만들어진 두 배열을 np.concatenate() 함수를 이용해 연결해주면 됨
앞에서는 넘파이 배열의 인덱스를 직접 섞어서 훈련 세트와 테스트 세트로 나누었으나 이번에는 train_test_split() 함수를 이용 이 함수는 전달되는 리스트나 배열을 비율에 맞게 훈련 세트와 테스트 세트로 나누어주므로 나누고 싶은 리스트나 배열을 전달 fish_data와 fish_target 2개의 배열을 전달했으므로 2개씩 나뉘어 총 4개의 배열이 반환됨 fish_data → train_input, test_input / fish_target → train_target, test_target
또한 무작위로 데이터를 나누었을 때 샘플이 골고루 섞이지 않을 수 있으며 특히 일부 클래스의 개수가 적으면 이런 일이 발생 그러므로 stratify 매개 변수에 타깃 데이터를 전달하면 타깃 비율을 보고 클래스 비율에 맞게 골고루 섞이도록 데이터를 나눠줌 이는 훈련 데이터가 작거나 특정 클래스의 샘플 개수가 적을 때 특히 유용
수상한 도미 한 마리
수상한 도미
앞에서 준비한 데이터로 k-최근접 이웃을 훈련해본 후 모델을 평가하면 테스트 세트의 도미와 빙어를 모두 올바르게 분류함 하지만 25cm, 150g의 도미 데이터를 넣고 결과를 확인해보면 빙어로 예측되게 됨
이 샘플을 다른 데이터와 함께 산점도로 그려볼 경우 분명히 오른쪽 위로 뻗어 있는 다른 도미 데이터에 더 가깝지만 왼쪽 아래에 낮게 깔린 빙어 데이터에 가깝다고 판단되게 됨
그러므로 이 샘플의 주변 샘플을 알아보기 위해 KNeighborClassifier 클래스에서 주어진 샘플에서 가장 가까운 이웃을 찾아주는 kneighbors() 메소드를 이용해 이웃까지의 거리와 이웃 샘플의 인덱스를 반환 받아 훈련 데이터 중에서 이웃 샘플을 따로 구분해 그려보기로 함
그려보면 마찬가지로 가장 가까운 이웃에 도미가 하나 밖에 포함되지 않고 나머지 4개의 샘플은 모두 빙어 산점도를 보면 직관적으로 도미와 더 가깝게 보이는데 왜 가장 가까운 이웃을 빙어라고 생각한걸까?
기준을 맞춰라
기준을 맞춰라
가장 가까운 첫 번째 샘플까지의 거리는 92이고, 그외 가장 가까운 샘플들은 모두 130, 138인데 거리가 92와 130이라고 했을 때 그래프에 나타난 거리 비율이 이상한 것을 알 수 있음 이는 x축(10~40)은 범위가 좁고, y축(0~1000)은 범위가 넓기 때문에 스케일이 맞지 않아 y축에서 조금만 멀어져도 거리가 아주 큰 값으로 계산되게 되며 이 때문에 오른쪽 위의 도미 샘플이 이웃으로 선택되지 못했던 것
이를 눈으로 명확히 확인하기 위해 x축의 범위를 동일하게 0~1000으로 맞추기 위해 xlim() 함수를 이용하여 x축과 y축의 범위를 동일하게 맞추었더니 모든 데이터가 수직으로 늘어선 형태가 되었음 이런 데이터라면 생선의 길이(x축)는 가장 가까운 이웃을 찾는데 크게 영향을 끼치지 않고 오로지 생선의 무게(y축)만 고려 대상 즉, 무게가 생선을 구분하는데 큰 영향력이 있고, 길이는 거의 미미하므로 두 특성의 값이 놓인 범위가 매우 달라 두 특성의 스케일링 달라 발생하는 문제라고 할 수 있음
데이터를 표현하는 기준이 다르면 스케일이 큰 특성에 절대적으로 영향을 받으므로 올바르게 예측할 수 없음 그러므로 여기에서는 길이가 역할을 하지 못하고, 무게로만 주변 샘플을 찾게 되는 문제 발생 특히 거리 기반일 때 더욱 그러며 k-최근접 이웃도 포함됨 이런 알고리즘들은 샘플 간의 거리에 영향을 많이 받으므로 제대로 사용하려면 특성값을 일정한 기준으로 맞춰주는 데이터 전처리 작업이 필요
표준 점수로 바꾸기
가장 널리 사용하는 전처리 방법 중 하나는 표준점수 표준점수는 각 특성값이 평균에서 표준편차의 몇 배만큼 떨어져 있는지를 나타내므로 평균을 빼고 표준편차로 나누면 됨 np.mean() 함수는 평균을 계산하고, np.std() 함수는 표준편차를 계산함 특성마다 값의 스케일이 다르므로 평균과 표준편차는 각 특성별(길이, 무게)로 계산해야 하므로 axis=0으로 지정하여 행을 따라 각 열의 통계 값을 계산함 이후 train_input의 모든 행에서 mean에 있는 두 평균값을 빼주고 std에 있는 두 표준편차를 다시 모든 행에 적용함 이러한 넘파이의 기능을 브로드캐스팅이라고 부르며, 이는 넘파이 배열끼리의 산술 연산 시에 자동으로 발생
train_input, mean, std가 모두 넘파이이므로 브로드캐스팅 자동 발생
전처리 데이터로 모델 훈련하기
수상한 도미 다시 표시하기
표준점수로 변환한 train_scaled와 25cm, 150g의 도미를 다시 산점도로 그리면 수상한 샘플 하나만 덩그라니 떨어져 있게 됨
훈련 세트를 평균으로 빼고 표준편차로 나누어 주었기 때문에 값의 범위가 크게 달라졌으므로 샘플도 동일한 비율로 변환해야 함 이를 위해 훈련 세트의 mean, std를 이용해서 동일한 기준으로 샘플을 변환하고 다시 산점도를 그릴 경우 앞서 표준편차로 변환하기 전의 산점도와 거의 동일하나 x축과 y축의 범위가 -1.5~1.5 사이로 바뀌게 되어 훈련 데이터의 두 특성이 비슷한 범위를 차지하고 있게 됨
전처리 데이터에서 모델 훈련
이 데이터셋으로 k-최근접 이웃 모델을 다시 훈련하기 위해서는 테스트 세트도 훈련 세트의 평균과 표준편차로 변환해야 함 그렇지 않다면 데이터의 스케일이 같아지지 않으므로 훈련한 모델이 쓸모없게 됨 즉, 훈련 후 테스트 세트를 평가할 때는 훈련 세트의 기준으로 테스트 세트를 변환해야 같은 스케일로 산점도를 그릴 수 있음
그러므로 변환 후에 모델을 평가하면 모든 테스트 세트의 샘플을 완벽하게 분류하는 것을 볼 수 있으며 25cm 150g의 샘플도 도미로 예측해내게 될 수 있음
마지막으로 kneighbors() 함수로 이 샘플의 k-최근접 이웃을 구한 다음 산점도를 그려볼 경우 샘플에서 가장 가까운 샘플은 모두 도미가 되어 이 수상한 샘플을 도미로 예측하게 되는 것을 알 수 있음
이를 통해 특성값의 스케일에 민감하지 않고 안정적인 예측을 할 수 있는 모델을 만들게 되었음