2. 데이터 다루기

TechN0·2025년 3월 15일

2-1 훈련 세트와 테스트 세트

지도학습

  • 입력과 타깃으로 이뤄진 훈련 데이터 요구됨
  • 데이터 == 입력
  • 정답 == 타깃
  • 훈련 데이터: 데이터와 타깃

비지도학습

  • 입력 데이터만 사용
  • 정답 사용X, 무언가를 맞힐 수 없음
  • 데이터 파악, 변형에 도움

챕터 1에서는 입력과 타깃이 있었음으로 지도 학습이였음

테스트 세트

  • 평가에 사용되는 데이터

훈련 세트

  • 훈련에 사용되는 데이터

머신 러닝의 정확한 평가 위해 테스트 세트와 훈련 세트는 따로 준비되어야 함

  • 훈련 세트와 테스트 세트를 준비해 보자
fish_length = [25.4, 26.3, 26.5, 29.0, 29.0, 29.7, 29.7, 30.0, 30.0, 30.7, 31.0, 31.0, 
                31.5, 32.0, 32.0, 32.0, 33.0, 33.0, 33.5, 33.5, 34.0, 34.0, 34.5, 35.0, 
                35.0, 35.0, 35.0, 36.0, 36.0, 37.0, 38.5, 38.5, 39.5, 41.0, 41.0, 9.8, 
                10.5, 10.6, 11.0, 11.2, 11.3, 11.8, 11.8, 12.0, 12.2, 12.4, 13.0, 14.3, 15.0]
fish_weight = [242.0, 290.0, 340.0, 363.0, 430.0, 450.0, 500.0, 390.0, 450.0, 500.0, 475.0, 500.0, 
                500.0, 340.0, 600.0, 600.0, 700.0, 700.0, 610.0, 650.0, 575.0, 685.0, 620.0, 680.0, 
                700.0, 725.0, 720.0, 714.0, 850.0, 1000.0, 920.0, 955.0, 925.0, 975.0, 950.0, 6.7, 
                7.5, 7.0, 9.7, 9.8, 8.7, 10.0, 9.9, 9.8, 12.2, 13.4, 12.2, 19.7, 19.9]
  • 각 생선의 길이와 무게를 하나의 리스트로 담은 2차원 리스트
fish_data = [[l, w] for l, w in zip(fish_length, fish_weight)]
fish_target = [1] * 35 + [0] * 14
  • 여기서 하나의 생선 데이터를 샘플 이라고 함!

  • 사이킷런 의 K ~~클래스 임포트 후 모델 객체 생성

from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier()
  • 전체 데이터에서 처음 35개 선택해야 함

  • 리스트 같이 배열 요소 선택할 떄는 인덱스(배열의 위치) 지정함

  • 아래는 다섯번째 샘플을 출력하는 예제

print(fish_data[4])
  • 파이썬 리스트는 슬라이싱도 지원
  • 슬라이싱: 인덱스 범위 지정해 원소 여러개 선택

! 슬라이싱에서 마지막 인덱스 원소는 포함X

주의하삼

  • 다섯 번째 까지 출력하는 코드
print(fish_data[0:5])
  • 첫 인덱스부터 시작이면 아래처럼 생략 가능
print(fish_data[:5])
  • 마지막 원소 까지도 생략 가능( 끝 5개를 출력)
print(fish_data[44:])
  • 다시 본론으로 돌아와서 처음부터 35개의 샘플을 훈련 시키고 나머지를 테스트 세트로 하는 모델을 만들어보자
train_input = fish_data[:35]
train_target = fish_target[:35]

test_input = fish_data[35:]
test_target = fish_target[35:]
kn.fit(train_input, train_target)
kn.score(test_input, test_target)

결과는 0.0 ……

💡

왜 이런 결과가 나오지?

  • 앞에 35개는 도미의 데이터
  • 뒤에 나머지는 빙어의 데이터
  • 도미로만 훈련하고 처음 보는 빙어로 테스트하는데 정확도가 있을리가 없다.
  • 마치 국어 공부만 하고 수학 시험을 보는 것과 같다.
💡

그럼 어떻게 해?

훈련 데이터와 테스트 데이터에 도미랑 방어가 골고루 섞여야함

  • 샘플링 편향: 훈련세트와 테스트 세트에 샘플이 고루 섞이지 않고 한쪽으로 치우친 상태

이러한 데이터 비빔밥 맛집이 있다고 한다.

넘파이

  • py배열 라이브러리
  • 고차원 배열을 쉽게 생성/조작 하는 도구 제공
import numpy as np
  • py리스트를 넘파이 배열로 바꾸기
input_arr = np.array(fish_data)
target_arr = np.array(fish_target)
  • 출력해보자
print(input_arr)
[[  25.4  242. ]
 [  26.3  290. ]
 [  26.5  340. ]
 [  29.   363. ]
 [  29.   430. ]
 [  29.7  450. ]
 [  29.7  500. ]
 [  30.   390. ]
 [  30.   450. ]
 [  30.7  500. ]
 [  31.   475. ]
 [  31.   500. ]
 [  31.5  500. ]
 [  32.   340. ]
 [  32.   600. ]
 [  32.   600. ]
 [  33.   700. ]
 [  33.   700. ]
 [  33.5  610. ]
 [  33.5  650. ]
 [  34.   575. ]
 [  34.   685. ]
 [  34.5  620. ]
 [  35.   680. ]
 [  35.   700. ]
 [  35.   725. ]
 [  35.   720. ]
 [  36.   714. ]
 [  36.   850. ]
 [  37.  1000. ]
 [  38.5  920. ]
 [  38.5  955. ]
 [  39.5  925. ]
 [  41.   975. ]
 [  41.   950. ]
 [   9.8    6.7]
 [  10.5    7.5]
 [  10.6    7. ]
 [  11.     9.7]
 [  11.2    9.8]
 [  11.3    8.7]
 [  11.8   10. ]
 [  11.8    9.9]
 [  12.     9.8]
 [  12.2   12.2]
 [  12.4   13.4]
 [  13.    12.2]
 [  14.3   19.7]
 [  15.    19.9]]

어때요, 참 쉽죠?

Shape

  • 넘파이 배열의 크기를 알려주는 속성
print(input_arr.shape)
(49, 2)

(샘플 수, 특성 수)

의 형태이다.

랜덤 샘플 선택해 훈련 세트와 데스트 세트로 만들기

주의 사항

  • input_arr 랑 targey_arr에서 같은 위치는 함께 선택되어야 함
  • 타깃과 샘플이 함께 움직여야 함
  • 그렇게 하려면 인덱스 값을 다 기억해야 하는데 그럼 효율 나쁨
  • 그냥 인덱스를 섞어 기존의 인풋과 타겟에서 샘플을 선택하게 하자
np.random.seed(42)
index = np.arange(49)
np.random.shuffle(index)
  • seed() 에 따라 다른 결과가 나오는데 통일하기 위해 42로 정함
  • arange()에 파라미터 N 전달 시 0~(N-1)까지 1씩 증가하는 배열 생성
  • shuffle()은 파라미터로 온 배열을 무작위로 섞음
print(index)

# 곃과
[13 45 47 44 17 27 26 25 31 19 12  4 34  8  3  6 40 41 46 15  9 16 24 33
 30  0 43 32  5 29 11 36  1 21  2 37 35 23 39 10 22 18 48 20  7 42 14 28
 38]

이븐하게 잘 비벼졌습니다.

배열 인덱싱

  • 1개의 인덱스가 아닌 여러 개의 인덱스로 한번에 여러 개의 원소를 선택

ex. 두번째와 네번째 샘플 선택

print(input_arr[[1, 3]])

# 결과
[[ 26.3 290. ]
 [ 29.  363. ]]
  • 리스트 대신 넘파이 배열을 인텍스로도 전달 가능
  • input_arr와 target_arr에 랜덤 순서인 index를 인덱스로 사용하게 하여 35개 샘플을 훈련 세트로 만들고
  • 나머지 뒷부분은 테스트 세트로 만들
train_input = input_arr[index[:35]]
train_target = target_arr[index[:35]]

test_input = input_arr[index[35:]]
test_target = target_arr[index[35:]]
  • 이제 산점도 그래프로 ㄱㄱ
 import matplotlib.pyplot as plt
 plt.scatter(train_input[:,0], train_input[:,1])
 plt.scatter(test_input[:,0], test_input[:,1])
 plt.xlabel('length')
 plt.ylabel('weight')
 plt.show()

두 번째 머신러닝 프로그램

  • 이제 K-최근접 이웃 모델을 훈련시키자
  • KNeighborsClassifier 클래스는 fit()매서드 실행할 때 마다 이전 학습 내역을 잃어버림
  • 그대로 두고싶음 새 객체 만들어야함
  • 근데 난 필요없으니 그대로 레츠고
kn.fit(train_input, train_target)
kn.score(test_input, test_target)
1.0

100% 다!

근데 수업에서 100%여도 마냥 좋은건 아니라 하신것 같은데

나중에 나오겠죠 뭐

  • 이제 predict() 메서드에 테스트 타깃을 넣어 예측하게 해봅세다
kn.predict(test_input)

#결과
array([0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0])
test_target

#결과
array([0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0])

오늘의 작은 성공!


2-2 데이터 전처리

하지만 이렇게 만든 모델도 나사가 조금 많이 빠진 녀석이다…

  • 길이 25, 무게 150인 도미를 빙어로 예측하는 문제가 발생

다시 데이터 준비

fish_length = [25.4, 26.3, 26.5, 29.0, 29.0, 29.7, 29.7, 30.0, 30.0, 30.7, 31.0, 31.0, 
                31.5, 32.0, 32.0, 32.0, 33.0, 33.0, 33.5, 33.5, 34.0, 34.0, 34.5, 35.0, 
                35.0, 35.0, 35.0, 36.0, 36.0, 37.0, 38.5, 38.5, 39.5, 41.0, 41.0, 9.8, 
                10.5, 10.6, 11.0, 11.2, 11.3, 11.8, 11.8, 12.0, 12.2, 12.4, 13.0, 14.3, 15.0]
fish_weight = [242.0, 290.0, 340.0, 363.0, 430.0, 450.0, 500.0, 390.0, 450.0, 500.0, 475.0, 500.0, 
                500.0, 340.0, 600.0, 600.0, 700.0, 700.0, 610.0, 650.0, 575.0, 685.0, 620.0, 680.0, 
                700.0, 725.0, 720.0, 714.0, 850.0, 1000.0, 920.0, 955.0, 925.0, 975.0, 950.0, 6.7, 
                7.5, 7.0, 9.7, 9.8, 8.7, 10.0, 9.9, 9.8, 12.2, 13.4, 12.2, 19.7, 19.9]
  • 전 방법은 파이썬 리스트를 순회하여 원소를 하나 씩 꺼내 생선의 길이와 무게를 담는 리스트를 만들었었다.
  • 우린 이제 고급 노예인 넘파이가 있으니 딸깍이 가능함

column_stack()

  • column_stack() 함수는 인자로 받은 리스트를 나란히 연결함
import numpy as np
fish_data = np.column_stack((fish_length, fish_weight))
print(fish_data[:5])
# 결과 무계와 길이가 순서대로 짝지어짐
[[ 25.4 242. ]
 [ 26.3 290. ]
 [ 26.5 340. ]
 [ 29.  363. ]
 [ 29.  430. ]]

함수 내부 구조는 안 뜯어봐서 효율이나 작동 방식은 안 찾아봤는데

시간이 너무 없으니 다음으로 기약 후 넘어가자…

이제 타겟 데이터를 만들어야 한다.

전에는 [1], [0]을 여러번 곱해 만들었는데 이제 함수사용해 날로먹자

np.ones(), np.zeros()

  • 각각 원하는 개수의 1 과 0으로만 이루어진 배열을 만드는 함수

ex)숫자 1로만 이루어진 길이가 5인 배열

print(np.ones(5))

[1. 1. 1. 1. 1.]

np.concatenate()

  • 두 배열을 연결하는 함수
  • column_stack은 나란히 짝지주는 함수인데 요것은 길게 연결한다
  • 도미 빙어 각각 35, 14마리씩 있으
fish_target = np.concatenate((np.ones(35), np.zeros(14)))

print(fish_target)

[1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.
 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0.]

근데 결과가 이진인데 float으로 할 필요가 있나? 메모리 낭비 같은데…

무튼 다음 단계로

train_test_split()

  • 사이킷런에서 제공하는 훈련/테스트 세트 나눠주는 함
from sklearn.model_selection import train_test_split

train_input, test_input, train_target, test_target = train_test_split(fish_data, fish_target, random_state=42)

random_state 는 랜덤시드 지정 매개변수

  • 두개의 배열(fish_data, fish_target)을 전달했으니 2개씩 총 4개의 배열 반환 됨
  • 기본적으로 25%를 테스트 세트로 분할
print(train_input.shape, test_input.shape)
(36, 2) (13, 2)

print(train_target.shape, test_target.shape)
(36,) (13,)
  • 잘 섞였는지도 테스트 해보자
print(test_target)
[1. 0. 0. 0. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
  • 방어가 3마리밖에 안된다…
  • 랜덤이기 때문에 골고루 섞이지 않을 수도 있어 샘플링 편항이 발생한것

stratify

stratify 매개변수에 타깃 게이터를 전달하면 클래스 비율에 맞게 데이터를 나눠줌

train_input, test_input, train_target, test_target = train_test_split(fish_data, fish_target, stratify=fish_target, random_state=42)

print(test_target)

[0. 0. 1. 0. 1. 0. 1. 1. 1. 1. 1. 1. 1.]

이븐하게 비벼진 모습

이제 다시 K-어쩌구 이웃 훈련을 하자

from sklearn.neighbors import KNeighborsClassifier

kn = KNeighborsClassifier()
kn.fit(train_input, train_target)
kn.score(test_input, test_target)

1.0

모든 생선들을 바르게 분류했다 이제 전에 문제가 발생한 도미 데이터를 넣어보자

print(kn.predict([[25, 150]]))

[0.]

왜 빙어라고 하는거야…

이 샘플을 다른 데이터와 함께 산점도로 확인해 보자

import matplotlib.pyplot as plt

plt.scatter(train_input[:,0], train_input[:,1])
plt.scatter(25, 150, marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

ㅏ….

  • K-최근접 이웃은 주변 샘플 중 다수인 클래스로 예측함
  • 그럼 이 문제 샘플의 주변 샘플을 확인해보자

kneighbors()

  • 최근접 이웃을 찾아주는 매서드
  • 이웃까지 거리, 이웃의 인덱스 반환
  • K-네이버 뭐시기의 클래스 이웃 기본 값은 5개 이므로 5개가 반환 될 것임
distances, indexes = kn.kneighbors([[25, 150]])

plt.scatter(train_input[:,0], train_input[:,1])
plt.scatter(25, 150, marker='^')
plt.scatter(train_input[indexes,0], train_input[indexes,1], marker='D')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

print(train_target[indexes])

[[1. 0. 0. 0. 0.]]

가까운 생선 중 4개가 빙어다.

거리 값도 확인하wk

print(distances)

[[ 92.00086956 130.48375378 130.73859415 138.32150953 138.39320793]]

이렇게 떨어져 있는데 40정도밖에 차이가 안 난다고?

  • 여긴 함정이있다

x축과 y축의 범위가 다르다.

  • x축의 범위를 동일하게 맞춰서 출력해보자

xlim()과 ylim()

  • 해당 축 범위를 지정하는 함수
plt.scatter(train_input[:,0], train_input[:,1])
plt.scatter(25, 150, marker='^')
plt.scatter(train_input[indexes,0], train_input[indexes,1], marker='D')
plt.xlim((0, 1000))
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

이렇게 보니 생선의 키는 고만고만이라 별 영향력이 없어보인다.

  • 두 특성의 값이 놓인 범위가 매우 다른데 이를 두 특성의 스케일이 다르다고 한다.

데이터 전처리

  • 특성 값을 일정한 기준으로 맞춰주는 작업

특히 알고리즘이 거리 기반이면 데이터 전처리를 꼭 해주어야 올바른 예측이 가능하다.

표준점수(z점수)

  • 각 특성이 평균에서 표준편차의 몇 배만큼 떨어져 있는지 나타냄
  • 가장 널리 사용하는 전처리 방법
  • 실제 특성값의 크기에 상관없이 동일한 조건으로 비교 가능

방법은 간단함

  • 평균을 배고 표준편차를 나누어줌
  • 이것조차 귀찮다?
  • 넘파이에서 함수를 제공한다…ㅎㅎ

np.mean()

  • 평균 계산 함수

np.std()

표준편차 계산 함수

mean = np.mean(train_input, axis=0)
std = np.std(train_input, axis=0)

print(mean, std)

[ 27.29722222 454.09722222] [  9.98244253 323.29893931]

axis의 의미

  • axis=0: 열(column) 단위 연산 → 각 특성(feature)별 평균, 표준편차 계산
  • axis=1: 행(row) 단위 연산 → 각 샘플(sample)별 평균, 표준편차 계산
  • axis를 지정하지 않으면 전체 배열을 기준으로 연산 수행

특성마다 평균, 표준편차를 구했으니 원본 데이터에서 평균을 빼고 표준 편차로 나누어 표준 점수로 변환하자

train_scaled = (train_input - mean) / std

이 식에서

  • 넘파이는 train_input의 모든 행에서 mean에 있는 두 평균값을 빼줌
  • 그 후 std 에 있는 두 표준 편차를 모든 행에 적용
  • 이런 넘파이 기능을 브로드캐스팅이라 부름

전터리 데이터로 모델 훈련

plt.scatter(train_scaled[:,0], train_scaled[:,1])
plt.scatter(25, 150, marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

  • 왜 혼자 튀려하지?
  • 왜냐하면 (25, 150) 이넘도 전처리 해주어야 함
new = ([25, 150] - mean) / std
plt.scatter(train_scaled[:,0], train_scaled[:,1])
plt.scatter(new[0], new[1], marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

  • 축 범위가 정상화 되어버린것을 알수있다.

이제 다시 K-최근점 이웃 모델을 훈련하자!

kn.fit(train_scaled, train_target)
test_scaled = (test_input - mean) / std
kn.score(test_scaled, test_target)

1.0

이제 문제의 케이스를 예측해 보자

print(kn.predict([new]))

[1.]

성공이다

마지막으로 다시 k-최근점 이웃을 구하고 산점도로 그려보자

distances, indexes = kn.kneighbors([new])

plt.scatter(train_scaled[:,0], train_scaled[:,1])
plt.scatter(new[0], new[1], marker='^')
plt.scatter(train_scaled[indexes,0], train_scaled[indexes,1], marker='D')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

가까운 샘플은 모두 도미이다.

0개의 댓글