※ 주요 학습 내용
✓ 데이터 전처리 ▶️정교한 결과 도출을 위한 데이터 전처리 알아보기
__ 넘파이로 데이터 준비하기
__ 사이킷런으로 훈련 세트와 테스트 세트 나누기
____ 전처리 데이터로 모델 훈련하기
이전 과정에선 파이썬 리스트를 순회하며 원소를 하나씩 꺼내 데이터를 생성.
하나의 길이와 무게를 리스트 안의 리스트로 직접 구성했음.
이젠 넘파이를 통해 훨씬 간편하게 생성 가능.
np.column_stack()
전달받은 리스트를 일렬로 세운 다음 차례대로 나란히 연결.
파이썬 튜플(tuple)
튜플은 리스트와 비슷. 한 번 생성된 튜플은 수정 불가.
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. ]]
넘파이 배열을 출력하면 행과 열을 맞춰 정리된 모습으로 출력.
5개의 행과 2개의 열로 구성된 것을 쉽게 알 수 있다.
타깃 데이터 생성
이전 과정에서는 원소가 하나인 리스트 [1], [0]을 여러 번 곱해서 타깃 데이터를 생성했음.
하지만 넘파이 함수인 np.ones()와 np.zeros()를 이용하여
원하는 개수의 1과 0을 채운 배열을 생성할 수 있음.
#1이 35개인 배열과 0이 14개인 배열 생성
np.ones(35)
'''
array([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.])
'''
np.zeros(14)
'''
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
np.concatenate()
두 배열을 연결할 때, 차원을 따라 연결하는 함수 이용.
fish_target = np.concatenate((np.ones(35), np.zeros(14)))
사이킷런
머신러닝 모델을 위한 알고리즘 뿐만 아니라 다양한 유틸리티 도구도 제공.
train_test_split()사이킷런의 함수로 전달되는 리스트나 배열을 비율에 맞게 훈련 세트와 테스트 세트로 나누어줌.
나누기 전 섞어도 줌.
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=42
np.random.seed()와 같이 무작위로 섞기 전에 랜덤 시드를 지정하는 매개변수
train_test_split()은 기본적으로 25%를 테스트 세트로 구분.
print(train_input.shape, test_input.shape)
print(train_target.shape, test_target.shape)
'''
# 출력 결과
(36, 2) (13, 2)
(36,) (13,)
'''
훈련 데이터 36개
테스트 데이터 13개
입력데이터(훈련 데이터)는 2개의 열이 있는 2차원 배열
타깃 데이터(테스트 데이터)는 1차원 배열
print(test_target)
# 출력 결과 [1. 0. 0. 0. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
k-최근접 이웃으로 훈련.
훈련 데이터로 모델 훈련하고
테스트 데이터로 모델 평가
rom sklern.neighbors import KneighborsClassifier
kn = KneighborsClassifier()
kn.fit(train_input, train_target)
kn.score(test_input, test_target)
print(kn.predict([[25, 150]])) # 출력 결과 1.0 → 빙어로 잘못 예측함.
#[25, 150] 샘플 데이터를 다른 데이터와 함께 산점도 그려보기.
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()

[25, 150] 샘플 데이터가 도미 데이터에 가깝다.
왜 이 모델은 빙어 데이터에 가깝다고 판단한 걸까?
kneighbors()
KneighborsClassifier 클래스의 메서드로, 주어진 샘플에서 가장 가까운 이웃을 찾아줌. 이 메서드는 이웃까지의 거리와 이웃 샘플의 인덱스를 반환. KneighborsClassifier 클래스의 이웃 개수인 n_neighbors의 기본값은 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()

가장 가까운 이웃에 도미가 1마리 뿐.
print(train_input[indexes])
print(train_target[indexes])
'''
# 출력 결과
[[[ 25.4 242. ]
[ 15.19.9]
[ 14.319.7]
[ 13.12.2]
[ 12.212.2]]]
'''
산점도를 보았을 때 가장 가까운 이웃은 직관적으로는 도미와 가깝게 보이는데 왜 빙어로 예측했을까?
이 문제의 해결 실마리를 찾기 위해 kneighbors() 메서드에서 반환한 distances 배열을 출력해 보자! 이 배열에는 이웃 샘플까지의 거리가 담겨있다.
print(distances)

이 값을 보고 무언가 이상한 점을 눈치 채야 한다.
산점도를 다시 천천히 살펴보면 삼각형 샘플에 가장 가까운 첫 번째 샘플까지의 거리는 92 이고 , 그 외 가장 가까운 샘플들은 모두 130,138
그런데 거리가 92와 130 이라고 했을 때 그래프에 나타난 거리 비율이 이상하다.
가장 가까운 데이터와의 거리가 92 인데 도미 데이터와의 거리가 130보다는 훨씬 더 멀어보이는데 말이다!
잘 살펴보니 x축과 y축의 범위가 맞지 않는다.
x축은 (10 ~ 40) 이고 y축은 (0~1000) 이다. 따라서 y축으로 조금만 멀어져도 거리가 아주 큰 값으로 계산되는 것 이다.
x축의 범위를 y축과 동일하게 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축만 고려의 대상이 될테니까!
이는 두 특성의 값이 놓인 범위가 매우 다르다고 볼 수 있다. 이를 두 특성의 [스케일] 이 다르다고 말하는데 이런 경우는 매우 흔하다. 어떤 사람이 방의 길이를 재는데 세로는 cm , 가로는 inch로 쟀다면 정사각형인 방도 직사각형처럼 보일 것 이다. 데이터를 표현하는 기준이 다르면 알고리즘이 올바르게 예측할 수 없다. 여기에는 k-최근접 이웃도 포함된다. 이런 알고리즘들은 샘플 간의 거리에 영향을 많이 받으므로 제대로 사용하려면 특성값을 일정한 기준으로 맞춰 주어야 한다. 이런 작업이 바로 데이터 전처리 이다.
가장 널리 사용하는 전처리 방법 중 하나는 '표준점수' 이다.
효준 점수는 각 특성값이 평균에서 표준편차의 몇 배 만큼 떨어져 있는지를 나타낸다. 이를 통해 실제 특성값의 크기와 상관없이 동일한 조건으로 비교할 수 있다.
mean = np.mean(train_input,axis=0) #평균
std = np.std(train_input, axis=0) #표준편차
train_input 은 (36,2) 크기의 배열이다. 특성마다 값의 스케일이 다르므로 평균과 표준편차는 각 특성별(컬럼)로 계산해야 한다. 이를 위해 axis=0 으로 지정했다. 이렇게 하면 행을 따라 각 열의 통계값을 계산한다.
print(mean, std)
train_scaled = (train_input - mean) / std
mean 과 std 에는 컬럼별 평균값과 , 표준편차가 들어있고 그것들을 한번에
train_scaled 변수에 넣어줬다.
앞에서 표준점수로 변환한 train_scaled 로 다시 산점도를 그려보자.
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] 샘플을 동일한 비율로 변환해야 함.
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 - meadn) / std
이제 모델을 평가해보자
kn.score(test_scaled, test_target) #결과 1.0
역시 완벽하게 분류해냈다.
앞서 [25,150] 샘플을 사용해 모델의 예측을 출력해보자
kn.predict([new]) # [1.]
이제야 도미(1)로 예측해냈다. 확실히 길이가 25cm이고 무게가 150g인 생선은 도미일 것 이다. 마지막으로 kneighbors() 함수로 이 샘플의 K-최근접 이웃을 구한 다음 산점도를 그려보자. 특성을 표준점수로 바꾸었기 때문에 올바르게 거리를 측정했을 것 이다.
# kneighbors() 함수로 [25, 150] 샘플의 k-최근접 이웃을 구한 산점도
distances, indexes = kn.kneighbors([new])
plt.scatter(train_scaled[:, 0], train_scaled[:, 1])
plt.scatter(new[0], new[1], marker=’^’)
plt.scatter(train_input[indexes, 0], train_input[indexes, 1], marker=’D’)
plt.xlabel(‘length’)
plt.ylabel(‘weight’)
plt.show()

가장 가까운 샘플을 도미로 잘 찾아낸다. 특성값의 스케일에 민감하지 않고,
안정적인 예측을 할 수 있는 모델을 만들었다.
훈련세트와 테스트세트를 잘 분류했다 하더라도 예측을 이상하게 할 수 있다.
샘플의 특성들이 스케일이 다를 경우 그럴 수 있다.
특성을 표준점수로 변화하였고, 스케일을 조정하는 방법은 표준점수 말고도 많다. 그렇지만 표준점수를 가장 널리 사용한다.
주의한 점은 훈련세트를 변환시켰다면 반드시 테스트세트도 변환해주어야 한다.
변화시키지 않을 경우 훈련의 의미도, 테스트의 의미도 없어지기 때문이다.