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]
▶ 넘파이 라이브러리 가져오기
import numpy as np
▶ np.column_stack() 함수
: 전달받은 리스트를 일렬로 세운 다음 차례대로 나란히 연결하는 함수
: 2개의 리스트를 붙힘, 연결할 리스트는 튜플로 전달
np.column_stack(([1,2,3], [4,5,6]))
* fish_data 만들기
: .column_stack() 함수로 fish_length와 fish_weight를 합침
fish_data = np.column_stack((fish_length, fish_weight))
print(fish_data[:5])
▶ np.ones() / np.zeros() 함수
: 리스트[1], [0]을 곱해 타깃 데이터 만들기
fish_target = np.concatenate((np.ones(35), np.zeros(14)))
print(fish_target)
2. 사이킷런으로 훈련 세트와 테스트 세트 나누기
▶ 사이킷런
: 머신러닝 모델을 위한 알고리즘뿐만 아니라 유틸리티 도구도 제공
: 대표적인 도구가 train_test_split() 함수
▶ train_test_split() 함수
: 전달받은 리스트나 배열을 비율에 맞게 훈련 세트와 테스트 세트 나눠줌
: 사이킷런의 model_selection 모듈 아래에 있음
▶ train_test_split() 함수의 매개변수 random_state
: 매개변수 random_state에 섞을 데이터 수 적기 (np.random.seed()함수로도 동일한 실습 가능)
: 기본적으로 25%를 테스트 세트로 생성
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)
* 훈련/테스트 세트의 데이터 shape 살펴보기
print(train_input.shape, test_input.shape)
* 타깃 데이터의 shape 살펴보기
print(train_target.shape, test_target.shape)
▶ 테스트 데이터 출력해보기
: 도미(1)와 빙어(0)가 잘 섞였는지 확인
print(test_target)
: 잘 섞인 것 같지만 빙어의 비율이 조금 모자름
→ 전체 데이터의 [도미 : 빙어] 비율은 [35: 14]로 [2.5 : 1] 정도인데 테스트 세트 데이터의 비율은 [3.3 : 1]임
→ 샘플링 편향이 발생
▶ train_test_split() 함수의 매개변수 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)
: 빙어가 1개 추가되면서 테스트 세트의 비율이 [2.25 : 1] 로 됨
: 데이터가 작아 전체 훈련 데이터의 비율과 동일하게 맞추긴 어렵지만 꽤 비슷한 비율
3. 수상한 도미 한 마리
▶ k-최근접 이웃 훈련
: 훈련 데이터로 모델을 훈련하고, 테스트 데이터로 모델을 평가
from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier()
kn.fit(train_input, train_target)
kn.score(test_input, test_target)
: 정확도 100%, 테스트 세트의 도미와 빙어를 모두 올바르게 분류함
▶ [25, 150] 데이터는 뭘로 분류하는지 보기
print(kn.predict([[25, 150]]))
: 빙어로 분류함
▶ 산점도 그려보기
: [25, 150] 데이터는 삼각형으로 표현
plt.scatter(train_input[:,0], train_input[:,1])
plt.scatter(25, 150, marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
>> 삼각형([25, 150] 데이터)은 오른쪽 위로 뻗어 있는 도미 데이터에 더 가까움
>> 그런데 이 삼각형 데이터를 빙어라고 판단한 이유는?
>> k-최근접 이웃은 주변의 샘플 중에서 다수인 클래스를 예측으로 사용하기 때문에 왼쪽 아래에 낮게 깔린 빙어 데이터가 가깝다고 판단 내린 것
▶ kneighbors() 메서드
distances, indexes = kn.kneighbors([[25, 150]])
: KNeighborsClassifier 클래스는 주어진 샘플에서 가장 가까운 이웃을 찾아 주는 kneighbors() 메서드를 제공
: 이 메서드는 이웃까지의 거리와 이웃 샘플의 인덱스를 반환
: KNeighborsClassifier 클래스의 이웃 개수인 n_neighbors의 기본값은 5이므로 5개의 이웃이 반환
▶ 산점도 그려보기
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()
: market = 'D'로 지정하면 산점도를 마름모로 변경
>> 이웃 샘플 중 4개의 샘플은 모두 빙어
▶ 데이터 직접 확인해보기
* train_input[indexes] 데이터
print(train_input[indexes])
* train_target[indexes] 데이터
print(train_target[indexes])
: [길이 25, 무게 150] 데이터와 가장 가까운 데이터는 빙어(0) 가 많음(5마리 중 도미는 1마리, 빙어는 4마리)
: 산점도 상으로는 도미가 더 가까워보이지만 k-최근접 이웃 모델이 [25, 150] 데이터를 빙어로 예측하는 것은 타당해보임
* distances 데이터
: 이 문제의 실마리를 찾기 위해 kneighbors() 메서드에서 반환한 distances 배열을 출력
: 이 배열에는 이웃 샘플까지의 거리가 담겨 있음
print(distances)
3. 기준 맞추기
▶ 기준을 맞추기 전의 상태
>> 삼각형 샘플에서 가장 가까운 첫 번째 샘플까지의 거리는 92이고, 그 외 가장 가까운 샘플은 130, 138...
>> 92와 130의 비율 차이가 너무 커 보임
: 92의 거리보다 족히 몇 배는 되어 보이는데 거리가 겨우 130인 것이 이상함
→ x축은 (10~40), y축은 (0~1000)이기 때문에 y축으로 조금만 멀어져도 거리가 아주 큰 값으로 계산
→ 그래서 오른쪽 위의 도미 샘플이 이웃으로 선택되지 못한 것
▶ 기준 맞추기
: x축의 범위도 y축과 동일하게 0~1000으로 맞추기
: 맷플롯립에서 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축)만 고려 대상
4. 데이터 전처리
▶ 데이터 전처리
: 특성 값을 일정한 기준으로 맞추는 작업
: 알고리즘들은 샘플 간 거리에 영향을 많이 받으므로 데이터의 기준이 다르면 알고리즘이 올바르게 예측할 수 없음
: 알고리즘이 거리 기반일 때 특히 그러함(K-최근접 이웃도 포함)
▶ 데이터 전처리 방법
* 표준점수
: 각 특성값이 평균에서 표준편차의 몇 배만큼 떨어져 있는지를 나타냄
: 이를 통해 실제 특성값의 크기와 상관없이 동일한 조건으로 비교 가능
mean = np.mean(train_input, axis=0)
std = np.std(train_input, axis=0)
: 특성마다 값의 스케일이 다르므로 평균과 표준편차는 각 특성별로 계산해야 함
: axis = 0으로 지정해 행을 따라 각 열의 통계 값을 계산하게 함
: 계산된 평균과 표준편차를 출력
print(mean, std)
▶ 표준 점수로 변환하기
= (원본 데이터 - 평균) / 표준편차
: 넘파이 기능을 브로드캐스팅(BROADCASTING)이라고 함
train_scaled = (train_input - mean) / std
▶ [25, 150] 데이터도 표준점수로 변환하기
new = ([25, 150] - mean) / std
5. 전처리 데이터로 모델 훈련하기
▶ 표준점수로 변환한 데이터의 산점도 그려보기
plt.scatter(train_scaled[:,0], train_scaled[:,1])
plt.scatter(new[0], new[1], marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
▶ fit() 함수로 표준점수로 변환한 훈련데이터 훈련
kn.fit(train_scaled, train_target)
▶ 테스트데이터도 표준점수로 변환하기
test_scaled = (test_input - mean) / std
▶ score() 함수로 테스트 데이터 정확도 확인
kn.score(test_scaled, test_target)
>> 정확도 100%
▶ predict() 함수로 new 데이터( [25, 150]을 표준점수화한 데이터) 예측하기
print(kn.predict([new]))
>> 드디어 도미(1)로 예측함
>> 확실히 길이 25, 무게 150인 생선은 도미일 것임
▶ kneighbors() 메서드
distances, indexes = kn.kneighbors([new])
: new 데이터의 최근접 이웃 데이터 5개 뽑아짐
▶ 산점도 그려보기
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()
>> 그래프는 앞서 표준점수로 변환하기 전의 산점도와 거의 동일
>> 크게 달라진 점은 x축, y축의 범위가 -1.5~1.5 사이로 바뀐 것
댓글