[Study][혼공MLDL] Ⅱ. Chapter 2 데이터 다루기
부산대학교 정보컴퓨터공학부 AID 인공지능 동아리, 스터디 “혼자 공부하는 머신러닝+딥러닝”에서 공부한 내용들과 질문들, 추가로 공부하면 좋을 내용들을 기록합니다.
Chapter 2. 데이터 다루기
1장에서 k-NN 알고리즘을 이용해 도미와 빙어를 구분하는 방법을 배웠지만, 학습을 위해 활용한 입력 데이터와 target이 테스트 시에도 그대로 활용되어, 100%라는 조금은 이상한(?) 정확도와 마주하게 되었습니다. (정답을 알고 있는데 문제를 틀리는 것이 이상한 것이죠.)
따라서 이 문제를 해결할 수 있으면서, 머신러닝 알고리즘에 사용될 데이터를 처리하는 여러 가지 방법을 이번 장에서 학습할 예정입니다.
2-1. 훈련 세트와 데이터 세트
지도 학습에서, 위와 같이 입력 데이터와 target이 테스트 시에도 그대로 사용되는 문제를 막기 위해서, 테스트에 사용되는 데이터는 훈련에 사용되는 데이터와 달라야 합니다. 따라서 이 문제는, train-set과 test-set을 분리하는 방법으로 해결할 수 있습니다.
이 때 중요하게 생각해야 할 것이, 샘플링 편향(sampling bias)입니다. test-set과 train-set에 들어있는 데이터에는 각각 구분해야 할 데이터가 골고루 섞여 있어야 합니다. 1장의 예를 들어보자면, 각 세트에는 도미와 빙어가 골고루 섞여있어야 한다는 것이죠. 훈련 세트와 테스트 세트에 샘플이 골고루 섞여 있지 않으면 샘플링이 한쪽으로 치우쳤다는 의미로 샘플링 편향이라고 부릅니다.
책에서는 이후, numpy.random의 shuffle() 함수를 이용해서 훈련 데이터와 테스트 데이터를 섞고, 훈련 세트와 테스트 세트로 나누는 방법에 대해 기술합니다. 그러나 이후에 나오는 scikit-learn의 train_test_split() 사용이 좀 더 편리하다고 생각해, 해당 부분은 다음 장에서 기록하겠습니다.
지도 학습과 비지도 학습

사진출처 : [https://opentutorials.org/course/4548/28942]
- 지도학습 (Supervised Learning) : label이 지정된 데이터 세트(입력과 타깃이 있는 데이터 세트)를 사용하여 모델을 훈련한 다음, 데이터를 분류하거나 결과를 예측하는 데 사용되는 방식.
지도 학습은 1장에서 만난 k-NN 알고리즘처럼, 정답이 있는 학습 방식입니다. 알고리즘을 이용하여 종속 변수와 독립 변수간의 관계를 이해하는 회귀(Regression)와 알고리즘을 이용하여 데이터를 특정 카테고리로 할당하는 분류(Classification)로 나눌 수 있습니다.
- 비지도학습 (Unsupervised Learning) : label이 지정되지 않은 데이터 세트를 분석하고 클러스터링해서, 데이터에서 숨겨진 패턴을 발견하는 방식.
비지도학습은 군집화(Clustering), 연관(Association), 차원 축소(Dimensionality Reduction) 등이 있으며, 6장에서 더 자세히 다룰 예정입니다.
2-2. 데이터 전처리
우선, 넘파이를 이용해서 데이터를 준비해 보도록 하겠습니다. 데이터는 1장에서 사용하였던 도미-빙어 데이터를 예시로 사용하였습니다.
생선의 length와 weight가 list로 주어져 있다고 할 때, 다음과 같은 numpy를 이용한 코드를 이용햐여 data와 target을 만들 수 있습니다.
fish_data = np.column_stack((fish_length, fish_weight))
fish_target = np.concatenate((np.ones(35), np.zeros(14)))
이제, 사이킷런을 이용해서 훈련 세트와 테스트 세트를 나눠봅시다.
from sklearn.model_selection import train_test_split
train_input, test_input, train_target, test_target = train_test_split(
fish_data, fish_target, stratify=fish_target, random_state=42)
- train_test_split() 함수는 훈련 세트와 데이터 세트를 나누어서 4개의 배열로 반환합니다. (랜덤 시드 = 42)
- train_test_split() 함수는 기본적으로 25%를 테스트 세트로 떼어냅니다.
- -> test_size 값을 바꾸면, 테스트 세트의 비율을 조정할 수 있습니다.
- stratify에 타깃 데이터를 전달하면 클래스 비율에 맞게 데이터를 나눕니다.
그럼, 도미 한 마리를 모델에 집어넣고, 결과를 예측해 봅시다.

25cm, 150g 물고기 예측
결과는 당연히 도미(1)일 것이라고 생각하겠지만, predict() 결과는 0으로 나왔습니다.
왜일까요? 이는 기준의 문제입니다. x축의 범위는 좁고(0 ~ 40), y축의 범위는 넓습니다(0 ~ 1000). 그래서, 거리를 결정하는 데에 x축(생선의 길이)보다, y축(생선의 무게)의 차이가 훨씬 더 크게 반영된 것이죠.
생선의 길이와 생선의 무게, 두 특성의 값이 놓인 범위(scale)가 매우 다릅니다.
데이터를 표현하는 기준이 다르면 알고리즙이 올바르게 예측할 수 없습니다. 알고리즘이 거리 기반일 때 특히 그러합니다. 이러한 알고리즘을 제대로 사용하려면 특성값을 일정한 기준으로 맞춰 주어야 합니다. 이런 작업을 데이터 전처리(Data Preprocessing)라고 합니다.
가장 널리 사용하는 전처리 방법 중 하나는 표준점수(Standard Score)입니다. (z 점수라고도 부릅니다.) 표준점수는 각 특성값이 평균에서 표준편차의 몇 배만큼 떨어져 있는지를 나타내므로, 실제 특성값의 크기와 상관없이 동일한 조건으로 비교할 수 있습니다.
넘파이를 이용해서, 평균을 빼고 표준편차를 나누어주는 식은 다음과 같습니다.
mean = np.mean(train_input, axis=0)
std = np.std(train_input, axis=0)
train_scaled = (train_input - mean) / std
특성마다 값의 스케일이 다르므로, 평균과 표준편차는 각 특성별로 계산해야 하고, 따라서 axis=0으로 지정하였고, 이후 train_scaled를 이용하여 각 data의 스케일을 변경한 값을 지정해 주었습니다.
이후 변형된 스케일로 25cm, 150g의 특성을 가지는 물고기를 그래프에 그려내 보면 다음과 같습니다.

25cm, 150g 물고기 예측-스케일 변경
이 그래프는 앞서 표준편차로 변환하기 전의 산점도와 거의 동일하지만, x축과 y축의 범위가 바뀌었다는 점입니다. 훈련 데이터의 두 특성이 비슷한 범위를 가지고 있고, 이 데이터로 다시 모델을 훈련한 후 길이가 25cm이고 무게가 150g인 생선을 예측해 보았을 때, 그 생선을 도미로 예측하였습니다. 성공입니다!
스케일이 다른 두 샘플을, 표준점수를 이용하여 동일한 스케일로 정리하는 데이터 전처리 과정을 거쳤고 성공적으로 모델을 생성할 수 있었습니다.