Chapter 02. 데이터 다루기

고대빵·2023년 3월 12일
0

혼공머신

목록 보기
2/4
post-thumbnail

@ 지도 학습과 비지도 학습

1. 지도 학습(supervised learning)

입력과 타깃을 전달하여 모델을 훈련한 다음 새로운 데이터를 예측하는 데 활용

  • 지도 학습에서는 데이터를 입력(input), 정답을 타깃(target), 이 둘을 합쳐 훈련 데이터(training data)라고 함.
  • 데이터의 특성을 구분하는 특징을 특성(feature)라고 함.
  • 지도 학습은 정답이 있으니 알고리즘이 정답을 맞히는 것을 학습함.

2. 비지도 학습(unsupervised learning)

  • 비지도 학습 알고리즘은 타깃 없이 입력 데이터만 사용함.
  • 비지도 학습 알고리즘은 정답을 사용하지 않으므로 무언가를 맞힐 수는 없지만, 데이터를 잘 파악하거나 변형하는 데 도움을 줌.

@ 훈련 세트와 테스트 세트

머신러닝 알고리즘의 성능을 제대로 평가하려면 훈련 데이터와 평가에 사용할 데이터가 각각 달라져야 함.

  • 가장 간단한 방법은 평가를 위해 또 다른 데이터를 준비하거나 이미 준비된 데이터 중에서 일부를 떼어 내어 활용하는 것임.
  • 평가에 사용하는 데이터를 테스트 세트(test set), 훈련에 사용되는 데이터를 훈련 세트(train set)라고 함.
  • 하나의 데이터 세트를 샘플(sample)이라고 부름.
# 2차원 리스트 및 모델 객체 생성
fish_data = [[l,w] for l, w in zip(fish_length, fish_weight)]
fish_target = [1]*35 + [0]*14

## 훈련 세트와 테스트 세트로 구분
# 훈련 세트로 입력값 중 0부터 34번째 인덱스까지 사용
train_input = fish_data[:35]
# 훈련 세트로 타깃값 중 0부터 34번째 인덱스까지 사용
train_target = fish_target[:35]
# 테스트 세트로 입력값 중 35번째부터 마지막 인덱스까지 사용
test_input = fish_data[35:]
# 테스트 세트로 타깃값 중 35번째부터 마지막 인덱스까지 사용
test_target = fish_target[35:]

kn = kn.fit(train_input, train_target)
kn.score(test_input, test_target)
### 정답 : 0.0
  • 샘플링 편향으로 인해 머신러닝 모델의 성능이 최악의 성능을 내고 있음.

@ 샘플링 편향

훈련 세트와 테스트 세트에 샘플이 골고루 섞여 있지 않으면 샘플링이 한쪽으로 치우치는 샘플링 편향(sampling bias)이 나타남.

  • 훈련하는 데이터와 테스트하는 데이터에는 도미와 빙어가 골고루 섞여 있어야 함.
  • 따라서 훈련 세트와 테스트를 나누기 전에 데이터를 섞든지 아니면 골고루 샘플을 뽑아서 훈련 세트와 테스트 세트를 만들어야 함.

@ 넘파이(Numpy)

  • 넘파이(Numpy)는 파이썬의 대표적인 배열 라이브러리임.
  • 넘파이는 고차원의 배열을 손쉽게 만들고 조작할 수 있는 간편한 도구를 많이 제공함.
  • 1차원 배열은 선, 2차원 배열은 면, 3차원 배열은 3차원 공간이며, 보통의 xy 좌표계와 달리 시작점이 왼쪽 아래가 아니라 왼쪽 위에서부터 시작함.
  • seed( )는 넘파이에서 난수를 생성하기 위한 정수 초깃값을 지정함. 초깃값이 같으면 동일한 난수를 뽑을 수 있음. 따라서 랜덤 함수의 결과를 동일하게 재현하고 싶을 때 사용함.
  • arange( )는 일정한 간격의 정수 또는 실수 배열을 만듦. 기본 간격은 1이며, 매개변수가 하나면 종료 숫자를 의미함. 종료 숫자는 배열에 포함되지 않음.
  • shuffle( )은 주어진 배열을 랜덤하게 섞음. 다차원 배열일 경우 첫 번째 축(행)에 대해서만 섞음.
# Numpy 임포트
import numpy as np

# 리스트를 넘파이 배열로 바꾸기
input_arr = np.array(fish_data)
target_arr = np.array(fish_target)

# (샘플 수, 특성 수) 출력
print(input_arr.shape)
### (49, 2)

# 일정한 결과 얻기 위해 랜덤 시드(random seed) 지정
np.random.seed(42)
index = np.arange(49)
np.random.shuffle(index)

# 훈련 세트 지정
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()

도미와 빙어를 섞은 머신러닝 프로그램

  • fit( ) 메서드를 실행할 때마다 KNeighborsClassifier 클래스 객체는 이전에 학습한 모든 것을 잃어버림.
  • 이전 모델을 그대로 두고 싶으면 KNeighborsClassifier 클래스 객체를 새로 만들어야 함.
 kn = kn.fit(train_input, train_target)
 kn.score(test_input, test, target)
 ### 정답 1.0

@ 넘파이로 데이터 준비하기

column_stack( ) 함수는 전달받은 리스트를 일렬로 세운 다음 차례대로 나란히 연결함. 연결할 리스트는 파이썬 튜플(tuple)로 전달함.

튜플(tuple)은 리스트처럼 원소에 순서가 있지만, 한 번 만들어진 튜플은 수정할 수 없음. 따라서 매개변수 값으로 많이 사용함.

import numpy as np

np.column_stack(([1,2,3],[4,5,6]))
fish_data = np.column_stack((fish_length, fish_weight))

# np.ones()와 np.zeros()로 각각 원하는 개수의 1과 0을 채운 배열을 만들어 줌.
# 2차원 배열인 np.column_stack()이 아닌 1차원 배열인 np.concatenate() 함수를 사용함.
fish_target = np.concatenate((np.ones(35), np.zeros(14)))
print(fish_target)
  • 데이터가 클수록 파이썬 리스트는 비효율적이므로 넘파이 배열을 사용하는 것이 좋음.

사이킷런으로 훈련 세트와 테스트 나누기

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)
                                                   
# 입력 데이터는 2차원 배열
print(train_input.shape, test_input.shape)

# 타깃 데이터는 1차원 배열
print(train_target.shape, test_target.shape)

# 도미와 빙어의 개수가 35개와 14개이므로 두 생선의 비율은 2.5:1
# 테스트 세트의 도미와 빙어의 비율은 3.3:1 (샘플링 편향)
print(test_target)
### 결과 : [1. 0. 0. 0. 1. 1. 1. 1. 1. 1. 1. 1. 1.]

# stratify 매개변수에 타깃 데이터를 전달하면 클래스 비율에 맞게 데이터를 나눈다.
train_input, test_input, train_target, test_target = train_test_split(fish_data, fish_target, 
                                                                      stratify=fish_target, random_state = 42)
                                                   
# 테스트 세트의 비율이 2.25:1이 되었음.
print(test_target)
### 결과 : [0. 0. 1. 0. 1. 0. 1. 1. 1. 1. 1. 1. 1.]

수상한 도미 한 마리

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()

  • 세모의 샘플은 오른쪽 위로 뻗어 있는 다른 도미 데이터에 더 가까웠음.
  • 하지만 이 모델은 세모의 샘플이 왼쪽 아래에 낮게 깔린 빙어 데이터에 까깝다고 판단함.
from sklearn.neighbors import KNeighborsClassifier

kn = KNeighborsClassifier()
kn.fit(train_input, train_target)
kn.score(test_input, test_target)
print(kn.predict([[25, 150]]))
### 결과 : [0.]
  • indexes 배열을 사용해 훈련 데이터 중에서 이웃 샘플을 따로 구분해 그려보자.
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.]]
print(distances)
### 결과 : [[ 92.00086956 130.48375378 130.73859415 138.32150953 138.39320793]]

기준을 맞춰라

x축은 범위가 좁고 (10~40), y축은 범위가 넓음. (0~1000). 따라서 y축으로 조금만 멀어져도 거리가 아주 큰 값으로 계산됨.

  • x축의 범위를 동일하게 0~1000으로 맞추어보자.
  • matplotlib에서 x축의 범위를 지정하려면 xlim( ) 함수를 사용하면 됨. 비슷하게 y축의 범위를 지정하려면 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()
  • x축과 y축의 범위를 동일하게 맞추었더니 모든 데이터가 수직으로 늘어선 형태가 되었음.
  • 이 데이터에서는 생선의 길이(x축)는 가장 가까운 이웃을 찾는 데 크게 영향을 미치지 못함. 오로지 생선의 무게(y축)만 고려 대상이 됨.

두 특성(길이외 무게)의 값이 스케일(범위)이 매우 다름.

  • 데이터를 표현하는 기준이 다르면 알고리즘이 올바르게 예측할 수 없음.

데이터 전처리(data preprocessing): 알고리즘을 제대로 사용하려면 특성값을 일정한 기준으로 맞춰 주어야 함.

표준 점수(z 점수, standard score): 각 특성값이 평균에서 표준편차의 몇 배만큼 떨어져 있는지를 나타냄.

cf. 표준 편차: 분산의 제곱근으로 데이터가 분산된 정도

# np.mean() 함수는 평균을 계산함.
mean = np.mean(train_input, axis=0)

# np.std() 함수는 표준편차를 계산함.
std = np.std(train_input, axis=0)

# 계산된 평균과 표준편차 출력
print(mean, std)
### 결과 : [ 27.29722222 454.09722222] [  9.98244253 323.29893931]
  • 이제 원본 데이터에서 평균을 빼고 표준편차로 나누어 표준편차로 변환해보자.
# numpy가 train_input의 모든 행에서 mean에 있는 두 평균값을 빼줌. 그다음 std에 있는 두 표준편차를 다시 모든 행에 적용함.
train_scaled = (train_input - mean) / std

브로드캐스팅(broadcasting)은 크기가 다른 넘파이 배열에서 자동으로 사칙 연산을 모든 행이나 열로 확장하여 수행하는 기능임.

전처리 데이터로 모델 훈련하기

plt.scatter(train_scaled[:,0], train_scaled[:,1])
plt.scatter(25, 150, marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
  • 오른쪽 맨 꼭대기에 수상한 샘플 하나만 덩그러니 떨어져 있음.
  • 훈련 세트를 mean(평균)으로 빼고, std(표준편차)로 나누어 주었기 때문에 값의 범위가 크게 달라졌음.
  • 샘플 [25, 150]을 동일한 비율로 변환해야 하는데, 이 때 훈련 세트의 mean, std를 이용해서 변환해야 함.

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()
  • 이 그래프는 표준편차로 변환하기 전의 산점도와 거의 동일하나, 크게 달라진 점은 x축과 y축의 범위가 -1.5~1.5 사이로 바뀌었다는 것임.

훈련 데이터의 두 특성이 비슷한 범위를 차지하고 있는데, 이제 이 데이터셋으로 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.]

# 드디어 도미(1)로 예측함. 
# 확실히 길이가 25cm이고 무게가 150cm인 생선은 도미일 것임.

kneighbors( ) 함수로 이 샘플의 k-최근접 이웃을 구한 다음 산점도로 그려보자.

  • 특성을 표준점수로 바꾸었기 때문에 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개의 댓글