본 포스팅은 저를 포함한 책을 구입한 분들의 학습 정리를 위해 쓰여졌습니다.
02-1 훈련세트와 테스트 세트
머신러닝은 입력과 타깃값을 주고 미리 훈련한 다음, 기존 훈련에 쓰이지 같은 형태의 데이터로 테스트하는 형태로 평가한다. 즉, 훈련과 테스트는 서로 다른 데이터 값으로 나누어주어야한다. 이렇게 연습문제와 시험문제가 달라야 머신러닝의 알고리즘 평가가 가능하다.
이런 형태를 만드는 방법은 평가를 위한 또 다른 데이터를 준비하거나 이미 준비된 데이터 중에서 일부를 떼어 내 활용하는 것이다. 앞서 본 그림과 같다. 데이터를 분리해 학습용과 테스트용을 분리한다.
데이터를 분리하는 방법은
1. 데이터에 index와 slicing 연산자를 이용
2. numpy라이브러리의 arrange() 함수 이용
2. scikit-learn라이브러리의 train_test_split() 함수를 이용
첫 번째 방법으로 index와 slicing 연산자에 대한 연습을 해보자
일단 데이터를 불러오고
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
하나의 데이터를 샘플sample이라 하는데 bream과 smelt가 각각 35, 14마리씩 있으니, 전체 데이터는 49개의 샘플이 존재한다.
다음으로 KNeighborsClassifier 클래스를 임포트하고 모델 객체를 만든다.
from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier()
배열 요소를 선택할 땐 배열의 위치, 즉 인덱스index를 지정한다. fish_data의 다섯 번째 샘플을 출력한다면
print(fish_data[4])
결과값: [29.0, 430.0]
이렇게 출력 가능하다(인덱스는 0부터 시작한다는 사실을 기억하자)
그리고 슬라이싱slicing 연산자를 활용해 여러 개의 원소를 한번에 선택할 수 있다.
#slicing 슬라이싱은 콜론을 가운데로 두고 인덱스의 범위를 지정하여 여러 개의 원소를 선택함. 단, 마지막 인덱스의 원소는 포함 되지 않음!!!!!
print(fish_data[0:5])
결과값: [[25.4, 242.0], [26.3, 290.0], [26.5, 340.0], [29.0, 363.0], [29.0, 430.0]]
이렇게 5개의 원소가 한꺼번에 선택 되었음을 알 수 있다. 다만 절대 주의할 점은
'마지막 인덱스의 원소는 슬라이싱을 사용할 때 포함되지 않는다'
는 것이다. 사실 많은 파이썬 초보자들이 헷갈리는 부분인데, 저렇게 포함되지 않는다고 외우는 것이 편한다. 그리고 만약 [0:5]처럼 처음부터 시작하는 슬라이싱은 처음을 나타내는 0을 제외하고 쓸 수 있다는 것도 알아두자.
#콜론 앞이 맨 처음 번호인 0이면 생략 가능
print(fish_data[:5])
결과값: [[25.4, 242.0], [26.3, 290.0], [26.5, 340.0], [29.0, 363.0], [29.0, 430.0]]
마지막 원소를 포함하는 경우도 마찬가지로 생략 가능하다
#콜론 뒤가 맨 마지막 번호라면 생략 가능
print(fish_data[44:])
결과값: [[12.2, 12.2], [12.4, 13.4], [13.0, 12.2], [14.3, 19.7], [15.0, 19.9]]
이를 응용해 처음 35개와 15개를 선택하는 것을 간단하게 처리한다,
#훈련세트로 입력 값 중 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:]
인덱스 0~34까지 처음 35개 샘플을 훈련세트로 선택했고, 인덱스 35~48번까지 14개의 샘플을 테스트 세트로 선택했다. 이제 훈련세트로 fit() 메서드를 호출해 모델을 훈련하고 테스트 세트로 score() 메서드를 호출해 평가한다.
kn=kn.fit(train_input, train_target)
kn.score(test_input, test_target)
결과값: 빵점
정확도가 0.0으로 측정된다. 당연하다. 샘플링 편향sampling bias가 일어났기 때문이다. 처음 35개 데이터는 bream이었고 뒤의 14개는 smelt였는데 훈련세트로 bream만 뽑았기 때문이다.
즉, 훈련 세트와 테스트를 나누기 전에 데이터를 섞든지, 처음부터 골고루 샘플을 뽑아서 만들어야 한다. 직접 하긴 번거로우니 이런 과정을 넘파이numpy라이브러리를 이용해 쉽게 처리한다.
먼저 넘파이를 불러오고 리스트를 넘파이 배열로 바꿔준다.
#Numpy: 파이썬의 대표적인 array(배열) 라이브러리
import numpy as np
input_arr = np.array(fish_data)
target_arr = np.array(fish_target)
결과값:
[[ 25.4 242. ]
[ 26.3 290. ]
[ 26.5 340. ]
[ 29. 363. ]
[ 29. 430. ]....
결과값을 보면 49개의 행과 2개의 열로 가지런히 출력되는 것을 확인할 수 있다.
넘파이 배열 객체는 배열의 크기도 알 수 있는데 shape 속성을 이용하면 된다.
#(샘플 수, 특성 수) 출력
print(input_arr.shape)
결과값: (49, 2)
49개의 행과 2개의 열에 대한 정보가 차례로 나타난다.
이제 배열을 준비했으니, 랜덤하게 샘플을 선택해 훈련 세트와 테스트 세트로 만든다.
이 장에서는 무작위로 샘플을 고르는 방법을 사용한다.
여기서 주의점, input_arr와 target_arr에서 같은 위치는 함께 선택되어야 한다는 점이다. 정답과 인풋이 따로 섞여버리면 지도 학습이 불가능하다. 그런데 항상 인덱스값을 기억할 수 없으니 아예 인덱스를 섞은 다음 input_arr와 target_arr에서 샘플을 선택하면 무작위로 훈련세트를 나누는 셈이다. 즉, 인덱스만 섞어버려 정답과 인풋이 잘못 섞여 발생할 수 있는 문제를 방지하는 것!
넘파이 arrange() 함수에 정수 n을 전달하면 0에서부터 n-1까지 1씩 증가하는 배열을 만든다. 이를 사용하면 0에서부터 48까지 1씩 증가하는 인덱스를 간단히 만들 수 있고, 넘파이 random 패키지 아래에 있는 shuffle() 함수는 주어진 배열을 무작위로 섞는데 이를 이용해 인덱스를 랜덤하게 섞어버릴 수 있다.
* 학습 과정의 동일성을 위해 일정한 랜덤 결과를 얻을 수 있도록 랜덤 시드(random seed)를 지정할 수 있다. 여기서는 시드를 정해 학습자와 실습자료 간 동일성을 확보했지만, 일반적으로는 그냥 무작위 결과를 만든다.
#붙어있는 값이 함께 움직여야하기 때문에 같은 위치는 함께 선택해야함
#인덱스를 만들어서 함께 움직이도록
#arange: 넘파이 arange()함수를 사용해서 1씩 증가하는 인덱스 만듦
#무작위 결과를 만드는 메서드, ()안에 시드값을 정해놓을 수 있음
np.random.seed(42)
#인덱스 만들기
index = np.arange(49)
#인덱스 섞기
np.random.shuffle(index)
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]
잘 섞인 것을 확인할 수 있다. 이제 넘파이의 배열 인덱싱array indexing이란 기능을 이용해 1개의 인덱스가 아닌 여러 개의 인덱스로 한 번에 여러 개의 원소를 선택한다.
#넘파이는 배열 인덱싱(array) 기능 제공, 여러 개의 인덱스로 해당하는 여러 개의 원소 선택
#두 번째와 네 번째 샘플 선택 출력
print(input_arr[[1,3]])
결과값:
[[ 26.3 290. ]
[ 29. 363. ]]
리스트 대신 넘파이 배열을 인덱스로 전달할 수도 있다. 앞서 만든 index배열을 input_arr와 target_arr에 전달하여 랜덤하게 35개의 샘플을 훈련 세트로 만든다.
#인덱스 배열의 처음 35개를 input_arr와 target_arr에 전달하여 랜덤하게 35개의 샘플을 훈련세트로 제작
train_input = input_arr[index[:35]]
train_target = target_arr[index[:35]]
#인덱스의 첫 번째 값이 13이니까 train_input의 첫 원소는 input_array의 열 네번째 값에 배당되어 있을걸?
print(input_arr[13], train_input[0])
결과값: [ 32. 340.] [ 32. 340.]
테스트 샘플도 만들어준다.
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()메서드를 활용해 훈련시킨다.
kn = kn.fit(train_input, train_target)
이 모델을 테스트 세트에 적용시켜 보면
kn.score(test_input, test_target)
결과값: 1.0
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])
예측 결과와 정답이 일치했다. 여기 predict() 메서드가 반환하는 값을 보면 넘파이 배열이라는 것을 알 수 있다. 그래서 넘파이와 사이킷런은 찰떡궁합처럼 만들어져있다는 점을 기억하자.
---
02-2 데이터 전처리
이번 장에서는 데이터 사용 전에 '데이터 전처리' 진행하는 과정을 살펴본다. 특히 표준점수로 특성 간 스케일을 변환하는 과정에 집중한다.
원본 데이터를 준비하는 과정과 넘파이를 불러오는 과정은 위 과정에서 진행했으므로 생략하고, 넘파이의 column_stack() 함수를 이용해 전달 받은 리스트를 일렬로 세운 다음 차례대로 나란히 연결해본다.
예시를 보자
#np.column_stack(): 전달받은 리스트를 일렬로 세운 다음 차례대로 나란히 연결
#연결할 리스트는 파이썬 tuple로 전달
#Python Tuple: 리스트와 비슷하다. 리스트처럼 원소에 순서가 있지만, 한번 만들어진 튜플은 수정이 불가능 함, 값이 바뀌지 않는다는 것을 믿을 수 있어 매개변수로 많이 사용
np.column_stack(([1,2,3], [4,5,6]))
결과값:
array([[1, 4],
[2, 5],
[3, 6]])
[1, 2, 3]과 [4, 5, 6] 두 리스트를 일렬로 세운 다음 나란히 옆으로 붙였다. 만들어진 배열은 (3, 2)의 크기라 할 수 있다. 즉, 3개의 행이 있고 2개의 열이 있다는 것.
이와 같은 방법으로 준비한 데이터를 합치고 처음 5개 데이터를 확인해보자
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을 채운 배열을 만들어 준다.
#np.ones(): 값을 1로 만드는 함수
#np.zeros(): 값을 0으로 만드는 함수
print(np.ones(5))
결과값: [1. 1. 1. 1. 1.]
이렇게 1과 0으로 이루어진 배열을 쉽게 만들 수 있다. 이 배열을 그대로 연결하면 되는데. 첫 번째 차원을 따라 배열을 연결하는 np.concatenate() 함수를 사용하여 그대로 연결해본다.
#np.concatenate: 첫 번째 차원을 따라 배열을 연결하는 함수
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.]
이전보다 훨씬 쉽고 간단한 방법으로 타깃 데이터를 만들었다. 넘파이를 활용하면 상당히 효율적인 부분이 많은데, 넘파이는 핵심 부분이 C, C++과 같은 저수준 언어로 개발되어서 빠르고, 데이터 과학 분야에 최적화 된 툴이기 때문에 데이터가 클수록 넘파이를 리스트를 대체해서 쓰는 경우가 많다.
앞서 index, slicing 연산자를 이용하거나 넘파이의 arrange(), shuffle() 함수를 이용해 만들었던 훈련세트와 테스트 세트를 사이킷런을 가지고 만들어 보자. 훈련세트와 테스트 세트를 나누는 방법 중 가장 편리하다!
사이킷런의 model_selction 모듈 아래에 있는 train_test_split()함수를 이용하면 된다. 사용법은 그저 나누고 싶은 리스트나 배열을 원하는만큼 전달하면 된다. 물론 여기서도 seed()함수와 같이 랜덤 시드를 지정할 수 있는 random_state 매개변수가 있으므로 이를 이용해 동일한 출력값을 가질 수 있도록 하자
#train_test_split(): 전달되는 리스트나 배열을 비율에 맞게 훈련세트와 테스트세트로 섞고 나누어줌 model_selection모듈에 존재
from sklearn.model_selection import train_test_split
#나누고 싶은 리스트나 배열을 원하는 만큼 전달
#random_state=N : 랜덤시드 지정
train_input,test_input, train_target, test_target = train_test_split(fish_data, fish_target, random_state=42)
print(train_input.shape, test_input.shape)
결과값: (36, 2) (13, 2)
인풋값(훈련용, 테스트용), 타깃값(훈련용, 테스트용)을 넣어주는 변수를 순서대로 넣고 train_test_split()함수의 첫 번째 매개변수는 인풋값 두 번째는 타깃값을 넣어주면 순서대로 잘린다. 여기서 자르는 비율을 정해주지 않으면 테스트세트의 크기를 자동으로 25% 떼어낸다. 물론 이 비율도 정할 수 있다. 'test_size=' 파라미터를 이용하면 되는데, 여기서는 굳이 쓰지 않는다.
shape을 이용해 크기를 확인해본다.
print(train_input.shape, test_input.shape)
print(train_target.shape, test_target.shape)
결과값:
(36, 2) (13, 2)
(36,) (13,)
결과를 보다시피 인풋은 2개의 열, 타깃은 1개의 열로 이루어져 있으며 값 별로 행이 정확히 나누어져 있다.
그러면 bream과 smelt는 잘 섞였을까?
print(test_target)
결과값: [1. 0. 0. 0. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
1이 bream이고 0이 smelt인데, 0이 조금 적다. 실제 bream-smelt의 비율은 2.5:1인데 여기서는 대략 3.3:1의 비율로 나누어 진것이다. 이런 샘플링 편향을 방지하기 위해 '클래스의 비율에 맞게 데이터를 나누는 방법'이 필요하다. 물론 이 방법도 매우 간단하게 지원한다.
#1과 0의 비율을 보면 원래 도미와 빙어 비율인 2.5:1과 다른 결과값(3.3:1) 도출 -> 샘플링 편향 일어남!
#따라서 train_test_split()함수에서 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.]
바로, "stratify=" 파라미터에 타깃 데이터를 전달하는 것이다. 이렇게 되면 클래스의 비율에 맞게 데이터를 나눌 수 있다. 물론 나누어야하는 데이터의 크기가 일정하지 않아 완전히 동일하게 맞추지는 못하더라도 근사값으로 알아서 조정해준다. (오류가 날 수 있는 환경을 미리 제어해준 제작자님들....멋지다)
데이터 전처리 과정이 끝났으니 K-최근접 이웃을 훈련해본다.
from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier()
kn.fit(train_input, train_target)
kn.score(train_input, train_target)
결과값: 1.0
분류는 역시 쉽게 예측했다. 그렇다면 특정 데이터를 넣어보면 어떨까? bream의 신규 데이터를 하나 넣어본다.
print(kn.predict([[25, 150]]))
결과값: [0.]
예측결과 smelt가 나왔다.... 왜일까? 시각화를 통해 저 데이터를 표시해보면
#엥? 왜 안되지? 산점도를 그려보자
import matplotlib.pyplot as plt
plt.scatter(train_input[:,0], train_input[:,1])
plt.scatter(25, 150, marker='^') #marker 매개변수는 모양을 지정하는 것
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
매개변수 데이터는 삼각형으로 표시했다. 분명 bream과 가까움에도 smelt와 더 가까운 분류라고 측정을 한 이유가 뭘까?
바로 분류 기준인 '근접한 이웃'의 수 때문이다.
K-최근접 이웃은 주변의 샘플 중 다수인 클래스를 예측으로 사용하는 (근묵자흑) 모델이다. 주어진 샘플에서 가장 가까운 5개의 이웃을 기준으로 클래스를 예측하는 것이 이 모델의 기본값이다. KNeighborsClassifier 클래스는 주어진 샘플에서 가장 가까운 이웃을 찾아주는 kneighbors() 메서드를 제공한다. 이걸 가지고 이웃 샘플을 따로 구분해 시각화해보자.
#확인했는데 도미랑 가까운데도 빙어 데이터라고 판단함. why?
distances, indexes = kn.kneighbors([[25,150]])
plt.scatter(train_input[:,0], train_input[:,1])
plt.scatter(25, 150, marker='^') #marker 매개변수는 모양을 지정하는 것
plt.scatter(train_input[indexes, 0], train_input[indexes, 1], marker='D')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
거리가 가장 가까운 이웃 중 4개가 smelt로 나왔다. 데이터를 직접 확인해도
print(train_input[indexes])
#찾아보니까 가장 가까운 5개의 샘플 중 4개가 빙어였음...
print(train_target[indexes])
결과값:
[[[ 25.4 242. ] [ 15. 19.9] [ 14.3 19.7] [ 13. 12.2] [ 12.2 12.2]]]
[[1. 0. 0. 0. 0.]]
마찬가지다. 역시 숫자로 봐야 정확한 것도 존재한다. 거리도 숫자로 계산해보자
#거리도 한번 보자
print(distances)
결과값: [[ 92.00086956 130.48375378 130.73859415 138.32150953 138.39320793]]
결과가 뭔가 이상하다! 그림을 다시보면
시각적으로 세모꼴의 신규 데이터와 1개의 bream 이웃 데이터는 매우 짧은데 92 정도의 길이가 나왔고 4개의 smelt 데이터는 굉장히 멀어보이지만 130정도 밖에 되지 않는다.
이유는 저 위의 x축과 y축의 '스케일scale'에 있다. x축의 범위는 0~40이지만 y축의 범위는 0~1,000으로 매우 넓은 분포를 가지고 있다. 즉, x축과 y축의 범위가 동일하지 않아, 가로 세로의 시각적 길이도 일정하지 않은 것이다. 다시 말해, 가로는 cm로 세로는 m로 계산한것과 마찬가지라는 것! 이를 일정한 기준으로 맞춰 주는 작업을 데이터 전처리data preprocessing이라고 부른다.
축의 범위를 맞춰보자. x축을 1,000으로 두고 데이터를 다시 확인해본다.
plt.scatter(train_input[:,0], train_input[:,1])
plt.scatter(25, 150, marker='^') #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()
xlim() 함수를 이용해 x의 한계값을 0과 1,000으로 두었더니 산점도가 거의 일직선으로 나타났다. 이런 시각화 자료를 토대로 분석을 한다면 x값에 해당하는 특성(생선의 길이)는 가까운 이웃을 구하는 데 큰 영향을 주진 못하는 것으로 판단할 수 있다.
따라서 이웃을 구하는 모델(거리 기반 모델)을 이용할 때는 각각의 특성을 스케일링scaling하여 범위를 맞춰주고 거리에 영향을 미치는 요소를 찾아내는 것이 중요하다.
데이터 전처리 방법 중 가장 널리 사용하는 방법은 표준점수standard score를 사용하는 것이다. z 점수라고도 부르는 표준점수는 각 특성값이 평균에서 표준편차standard deviation의 몇배만큼 떨어져 있는지를 나타낸다. 이를 활용해 실제 특성 값의 크기와 상관없이 동일한 조건으로 비교 가능하다.
참고로 표준편차는 분산variance의 제곱근으로 데이터가 분산된 정도를 나타내고, 표준점수는 각 데이터가 원점에서 몇 표준편차만큼 떨어져 있는지를 나타내는 값이다. 또한 분산은 데이터에서 평균을 뺀값을 모두 제곱한 다음 평균을 내어 구하는 것으로 변수의 흩어진 정도를 계산하는 지표다.
표준편차는 넘파이를 이용해 쉽게 계산 가능하다. np.mean()함수는 평균을 계산하고 np.std()는 표준편차를 계산한다. 특성마다 값의 스케일이 다르므로 평균과 표준편차는 각 특성별로 계산해야 하므로 axis=0으로 행 축을 지정해 행을 따라 각 열의 통계 값을 계산한다.
mean = np.mean(train_input, axis=0) #평균
std = np.std(train_input, axis=0) #표준편차
#axis가 0이면 행을 따라 각 열의 통계 값을 계산함
#axis가 1이면 열을따라 통계값을 계산함
print(mean, std)
결과값: [ 27.29722222 454.09722222] [ 9.98244253 323.29893931]
특성별로 표준편차가 구해졌으니, 각 원본 데이터에서 평균을 빼서 표준편차포 나누어 표준점수로 변환한다.
#표준점수 변환
train_scaled = (train_input - mean) / std
#train_input에 있는 모든 행에서 mean에 있는 두 평균값을 빼준뒤 std에 있는 두 표준편차를 다시 모든 행에 적용
#broadcasting
표준점수 변환 값을 train_scaled에 담았다. 놀랍게도 넘파이는 for나 while 문을 쓸 필요가 없이 train_input의 '모든 행'에서 mean에 있는 평균 값을 빼준 뒤 std에 있는 두 표준편차를 다시 '모든 행'에 적용한다. 이런 넘파이 기능을 브로드캐스팅broadcasting이라고 부른다. 개떡같이 말해도 찰떡같이 알아듣는 수준...
스케일 작업을 한 (표준점수로 변환한) 데이터를 가지고 산점도로 다시 그려본다.
plt.scatter(train_scaled[:,0], train_scaled[:,1])
plt.scatter(25, 150, marker='^') #marker 매개변수는 모양을 지정하는 것
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
이렇게 열심히 작업을 하다보면 잊고 있던 작업 하나가 이상한 결과를 만들어낸다. 우리가 잊은 것은.... 새로운 샘플 데이터를 훈련세트의 mean, std를 이용해서 표준점수로 변환해야한다.
new =([25, 150] - mean) / std
plt.scatter(train_scaled[:,0], train_scaled[:,1])
plt.scatter(new[0], new[1], marker='^') #marker 매개변수는 모양을 지정하는 것
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
이 데이터 셋으로 다시 훈련한다.
kn.fit(train_scaled, train_target)
kn.score(train_scaled, train_target)
결과값: 1.0
이제 테스트 데이터로 평가해야하는데, 당연히 테스트 데이터도 표준 점수화 해야겠지?
#테스트하려면 테스트 세트도 훈련세트의 평균과 표준편차로 변환해야함
test_scaled = (test_input - mean) / std
kn.score(test_scaled, test_target)
결과값: 1.0
테스트 샘플 분류도 완벽하다.
이제 문제의 데이터도 예측해보자
#자 그럼 틀렸던 값 다시 확인
print(kn.predict([new]))
결과값: [1.]
bream이 나왔다! 스케일 작업을 거치니 결과가 달라졌다. 아마 선정한 이웃군이 달라졌을텐데 이를 시각화해서 확인해본다.
distances, indexes = kn.kneighbors([new])
plt.scatter(train_scaled[:,0], train_scaled[:,1])
plt.scatter(new[0], new[1], marker='^') #marker 매개변수는 모양을 지정하는 것
plt.scatter(train_scaled[indexes, 0], train_scaled[indexes, 1], marker='D')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
역시 bream에 가까운 것으로 판단했다.
결론
이 장에서는 데이터 전처리 과정 중 샘플링 편향을 방지하는 훈련세트와 테스트 세트 제작, 서로 다른 수치를 지닌 특성 스케일링을 다루었다. 특히 거리 기반 모델에서는 특성 스케일링을 제대로 해주지 않으면 예측 오류가 발생하기 쉬우므로 주의하자
Key Word
지도 학습(supervised learning): 정답(타깃)이 존재하는 학습 방법으로 타깃을 맞추는 방안을 학습. 새로운 데이터를 예측하는 데 활용한다.
비지도 학습(unsupervised learning): 정답이 없는 학습방법으로 입력 데이터만 있는 경우 학습하는 방법. 따라서 무엇인가를 예측하는 것이 아니라 입력 데이터에서 어떤 특징을 찾는데 주로 활용한다.
강화 학습(reinforcement learning): 알고리즘이 행동한 결과로 얻은 보상을 사용해 학습
입력(input): 지도 학습에서 데이터를 가리키는 말
타깃(target): 지도 학습에서 정답을 가리키는 말
훈련데이터(training data): 입력(데이터)과 타깃(정답)을 합쳐 부르는 말
테스트 세트(test set): 평가에 사용하는 데이터, 전체 데이터에서 20~30%를 테스트 세트로 사용하는 경우가 많다. 데이터가 아주 크면 1%만 덜어내도 충분하다.
훈련 세트(train set): 모델 훈련에 사용하는 데이터. 보통 훈련세트가 클수록 좋다.
샘플(sample): 훈련에 사용하는 데이터 중 하나
샘플링 편향(sampling bias): 샘플이 골고루 섞여있지 않아, 훈련세트와 테스트 샘플의 차이로 인해 편향된 데이터 예측이 일어나는 것
넘파이 라이브러리(numpy library): 파이썬의 대표적인 배열array 라이브러리, 넘파이는 고차원의 배열을 손쉽게 만들고 조작할 수 있는 간편한 도구를 제공한다.
데이터 전처리(data preprocessing): 머신러닝 모델에 훈련 데이터를 주입하기 전에 가공하는 단계.
스케일(Scale): 데이터의 특성이 지닌 범위
표준점수(standard score): 각 특성값이 평균에서 표준편차의 몇배만큼 떨어져 있는지를 나타낸다. 이를 활용해 실제 특성 값의 크기와 상관없이 동일한 조건으로 비교 가능하다. 훈련세트의 스케일을 바꾸는 주요 방법 중 하나. 표준 점수를 얻기 위해선 특성의 평균을 빼고 표준편차로 나눠준다. 반드시 훈련세트의 평균과 표준편차로 테스트 세트를 바꾸는 작업을 해주어야 한다. z점수라고도 불린다. (은근 자주 있는 실수)
표준편차(standard deviation): 자료의 분산정도를 나타내는 수치. 각 측정값과 평균값과의 편차를 제곱하고 그것을 산술평균한 값의 제곱근으로 표준편차가 작은 것은 분산 정도가 작은 것을 말한다.
분산(variance): 주어진 변량이 평균으로부터 떨어져 있는 정도를 나타내는 값의 한 종류로 변수가 흩어진 정도를 계산하는 지표이다. 데이터에서 평균을 뺀 값을 모두 제곱한 다음 평균을 내어 구한다.
즉, 분산 = 편차²의 평균
브로드캐스팅(broadcasting): 크기가 다른 넘파이 배열에서 자동으로 사칙연산을 모든 행이나 열로 확장하여 수행하는 기능
패키지와 함수
[넘파이 라이브러리numpy]
np.random.seed(): 넘파이에서 난수를 생성하기 위한 정수 초깃값을 지정. 초깃값이 같으면 동일한 난수를 뽑을 수 있어 동일한 결과를 재현하고 싶을 때 사용한다.
np.arrange(): 일정한 간격의 정수 또는 실수 배열을 만듦. 매개변수가 하나이면 종료 숫자를 의미한다. 0에서 부터 숫자를 만들고 종료 숫자는 배열에 포함되지 않는다.
np.random.shuffle(): 주어진 배열을 랜덤하게 섞음. 다차원 배열인 경우 첫 번째 축(행)에 대해서만 섞는다.
np.ones(): 1로 구성된 배열을 만드는 함수
np.zeros(): 0으로 구성된 배열을 만드는 함수
np.concatenate(): 첫번째 차원에 따라 서로 다른 배열을 합치는 함수
np.mean(): 평균을 구함. axis 매개변수가 1일 경우 열을 따라 평균을 구하며 0일 경우 행을 따라 평균을 구한다.
np.std(): 표준편차를 구함. axis 매개변수가 1일 경우 열을 따라 평균을 구하며 0일 경우 행을 따라 평균을 구한다.
[사이킷런 라이브러리scikit-learn]
train_test_split(): 훈련 데이터를 훈련 세트와 테스트 세트로 나누는 함수. 여러 개의 배열을 전달할 수 있다. 테스트 세트로 나눌 비율은 test_size 매개변수에서 지정할 수 있으며 기본값은 0.25(25%)이다. shuffle 매개변수로 훈련 세트와 테스트 세트로 나누기 전에 무작위로 섞을지 여부를 결정할 수 있고 기본값은 True이다. stratify 매개변수에 클래스 레이블이 담긴 배열을 전달하면(타깃 데이터) 해당 클래스의 비율에 맞게 훈련세트와 테스트 세트를 나눈다.
kneighbors(): K- 최근접이웃 객체의 메서드로 이 메서드는 입력한 데이터에 가장 가까운 이웃을 찾아 거리와 이웃 샘플의 인덱스를 반환한다. 기본적으로 이웃의 개수는 KNeighborsClassifier 클래스의 객체를 생성할 때 지정한 개수를 사용한다. 물론 n_neighbors 매개변수에서 다르게 지정할 수 있다. return_distance 매개변수를 False로 지정하면 이웃 샘플의 인덱스만 반환하고 거리는 반환하지 않는다.
'데이터 분석 학습' 카테고리의 다른 글
[혼자공부하는머신러닝+딥러닝] Ch.04 다양한 분류 알고리즘 / 로지스틱 회귀, 확률적 경사 하강법, 이진분류, 다중 분류 (0) | 2022.04.20 |
---|---|
[혼자공부하는머신러닝+딥러닝] Ch.03 회귀 알고리즘과 모델 규제 / K-최근접이웃회귀, 선형회귀, 특성 공학과 규제 (0) | 2022.04.19 |
[혼자공부하는머신러닝+딥러닝] Ch.01 나의 첫 머신러닝 리뷰 / K-최근접 이웃 모델 (0) | 2022.04.05 |
[모두의 데이터분석 With 파이썬] Unit.08 리뷰 / 항아리모양 그래프 그리기 (0) | 2022.03.25 |
[모두의 데이터분석 With 파이썬] Unit.07 리뷰 / 데이터에 맞는 시각화 (0) | 2022.03.24 |