계속해서 1일 차에 했던 물고기 분류 머신러닝에 더 자세히 대해 알아보자
앞서 했던 머신러닝은 그저 정해진 답에 맞추는 것이었기 때문에 실제 사용하기에는 문제가 있다.
이를 해결하기 위한 방법이 머신러닝 알고리즘에 학습을 해주는 것이다. 이를 지도학습이라고 하는데
지도학습에서 데이터와 정답을 입력(input)과 타깃(target)이라고 하고 이 둘을 합쳐 훈련 데이터라고 부른다.
지도학습을 위해 데이터를 훈련 세트, 테스트 세트로 나누어 주기 앞서 이전에 데이터를 불러와보자
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)]
#타겟 변환(1차원 리스트)
fish_target = [1]*35 + [0]*14
# 사이킷런 에서 K-최근접 알고리즘 사용
from sklearn.neighbors import KNeighborsClassifier#
#객체 생성
kn = KNeighborsClassifier()
2-1 훈련 세트와 테스트 세트
여기서 fish_data는 기본적인 파이썬 리스트의 접근(인덱스 호출, 슬라이싱)이 모두 사용 가능하다.
슬라이싱을 이용해서 데이터를 훈련 세트, 테스트 세트로 나눌 수 있다.
# 훈련 세트 0~34 번째 인덱스 사용
train_input = fish_data[:35]
train_target = fish_target[:35]
#테스트 세트 35~마지막(49) 인덱스 사용
test_input = fish_data[35:]
test_target = fish_target[35:]
위와 같이 데이터를 준비했으니 훈련세트로 fit() 메소드를 이용하여 훈련하고 테스트 세트를 호출해 평가해 보자
# 객체에 훈련 세트를 넣음
kn.fit(train_input,train_target)
print("테스트 머신러닝 결과")
# 객체에 테스트 세트를 비교하여 확인
kn.score(test_input,test_target)
테스트 머신러닝 결과 : 0.0
왜 다음과 같은 결과가 나왔을까? 이유는 간단하다. 초기 데이터를 보면 앞에는 도미 뒤에는 빙어가 있는데 데이터의 슬라이싱을 단순히 35를 기준으로 앞 뒤로 나누었으니 도미와 빙어를 비교할 수 조차 없다. 이를 해결하기 위해서는 데이터에 정보가 고르게 섞여 있어야 한다. 이를 해결하기 위해 Numpy를 이용해 보자
#넘파이 호출
import numpy as np
# 파이썬 리스트를 -> 넘파이 배열로
input_arr = np.array(fish_data)
target_arr = np.array(fish_target)
print("넘파이 형태")
print(input_arr)
print("\n")
#샘플수와 특성수를 알려줌
print("샘플 수와 특성 수")
print(input_arr.shape)
print("\n")
#넘파이에는 랜덤 함수가 있어 데이터를 섞을수 있다.
#seed는 시드별로 랜덤한 순서를 가지고 있어 기존 정보의 순서를 바꿔줌
#0~48넘파이 배열 생성
index = np.arange(49)
#shuffle 넘파이를 무작위 섞기
np.random.shuffle(index)
print(index)
넘파이 형태:
[[ 25.4 242. ]
[ 26.3 290. ]
[ 26.5 340. ]
[ 29. 363. ]
[ 29. 430. ]
[ 29.7 450. ]
[ 29.7 500. ]
-------
[ 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]]
샘플 수와 특성 수: (49, 2)
[21 14 45 19 29 47 48 37 26 40 3 36 0 24 23 2 27 18 39 1 42 32 31 7
12 43 6 5 16 33 25 9 8 11 17 10 28 46 34 4 30 13 44 41 38 15 22 35
결과를 보면 데이터가 고르게 섞인 것을 확인할 수 있다. 이제 데이터를 다시 나누어 보고 결과를 확인해 보자
#35개 샘플 데이터 생성
train_input = input_arr[index[:35]]
train_target = target_arr[index[:35]]
#14개 테스트 데이터 생성
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()
kn.fit(train_input,train_target)
a=kn.score(test_input,test_target)
print(a)
# 테스트 데이터 확인
kn.predict(test_input)
plt.scatter(train_input [:,0], train_input [:,1])
에 관해 간략하게 설명하면 기본적으로 넘파이는 2차원의 데이터 이기 때문에 인덱싱이 약간 다르다
input 뒤에 [,]는 행과 열에 접근하는 방법인데 input []에서 [A, B] 에는 각각 행, 열 에 대한 정보인데. 각각에 대해 슬라이싱 한 값을 추합 하면 된다. 즉 [ : , 0]이라는 것은 / 행에는' : '라는 것은 전체에 대한 슬라이싱을 의미하고 열에 '0'으로 되어 있는 것은 0번째 열을 의미한다
1.0
array([0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0])
print(test_target)
[1 1 0 1 1 1 1 0 0 0 1 1 0 1]
위에 결과를 확인해 보면 test_input의 결과와 test_target이 일치하는 것을 알 수 있다.
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]
import numpy as np
# .column_stack 는 리스트 내포를 대신함
fish_data = np.column_stack((fish_length, fish_weight))
print("\n")
#타겟(정답)데이터 생성
# one 35 , zero 14 순으로됨
fish_target = np.concatenate((np.ones(35), np.zeros(14)))
print(fish_target)
print("\n")
넘파이의 메소드인 column_stack(())과 concatenate()를 이용하면 이전에 정보를 보다 쉽게 설정할 수 있다.
이제 훈련세트와 테스트 세트로 나누면 되는데 이는 사이킷런에서 제공하는 train_test_split() 함수를 이용하면 된다.
#훈련 테스트 세트 나누기
#사이킷런 에 포함된 model_selection에는 train_test_split 있음
# train_test_split은 훈련과 테스트 세트를 나눠주기 위한 모듈
from sklearn.model_selection import train_test_split
# 사용방법
# 나누고 싶은 튜플(4가지 :훈련 입력,테스트 입력,훈련 정답, 테스트 정답) = train_test_split(원본 데이터,정답 , 방식)
train_input, test_input, train_target, test_target = train_test_split(fish_data, fish_target,stratify = fish_target, random_state=42)
#straify에 매개변수에 타깃데이터를 전달하면 일정 비율에 맞게 데이터를 나눈다
print(test_target)
[[ 25.4 242. ]
[ 26.3 290. ]
[ 26.5 340. ]
[ 29. 363. ]
[ 29. 430. ]]
[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.]
이제 결과를 확인해 보면
#수상한 도미 한마리
#새로운 도미의 데이터를 추가
from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier()
kn.fit(train_input,train_target)
kn.score(test_input,test_target)
print(kn.predict([[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()
[0.]
위와 같이 [0] 즉 머신러닝은 해당 데이터를 도미가 아닌 빙어로 인식하게 된다, 왜 이런 결과가 나올까?
확인하기 위해 해당 데이터와 가까운 값을 알아보자
#특이점에 가까운 점 5개 표시
# .kneighbors([])는 KNeighborsClassifier에 재공되는 메서드로
# 입력한 값에 가까운 값을 찾아줌, 기본값이 5
didtances, indexes = kn.kneighbors([[25,150]])
import matplotlib.pyplot as plt
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("수상한 도미에 가까운 점")
print(train_input[indexes])
print(train_target[indexes])
kneighbors(기준값)을 사용하면 가까운 점을 찾아주는데 기본값이 5이므로 가까운 5개의 점을 구해준다.
이는 시스템상의 거리를 기반으로 계산된다.
수상한 도미에 가까운 점
[[[ 25.4 242. ]
[ 15. 19.9]
[ 14.3 19.7]
[ 13. 12.2]
[ 12.2 12.2]]]
[[1. 0. 0. 0. 0.]]
결과를 확인해 보면 1마리의 도미를 제외한 나머지가 빙어인 것을 볼 수 있다. 눈치챈 사람들도 있겠지만 이런 오류가 발생한 이유는 X와 Y축의 스케일 즉 비율이 다르기 때문이다. 따라서 두 축의 스케일을 맞춰준다면
#따라서 x축과 y축의 스케일을 맞춰주면
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()
이제 확실하게 빙어에 가까운 것을 알 수 있다. 우리는 해당 데이터가 도미인 것을 알고 있지만 컴퓨터는 빙어로 인식한다는 것을 알게 됐다. 과연 이를 해결하기 위해서는 어떻게 해야 할까. 우리가 이전까지 했던 것은 단순히 데이터를 표현하는 것에 그치지 않는데 이러한 오류를 방지하기 위해 데이터를 일정 기준에 맞춰 주는 작업을 데이터 전처리라고 한다. 가장 널리 쓰이는 전치리 방법이 표준 점수이다.
표준점수는 값에 평균을 빼고 표준편차를 나누어준 값을 말한다. 이를 코드로 살펴보자
#표준 점수 : 특성간의 스케일 차이가 클때 이 차이를 줄이기 위해 사용
mean = np.mean(train_input,axis=0) #평균
std = np.std(train_input,axis=0) #표준 편차
print(mean,std)
#표준 점수 -훈련세트
train_scaled = (train_input - mean) / std
#최종 조정
kn.fit(train_scaled,train_target)
# 스케일 조정후 테스트 세트
test_scaled = (test_input - mean) / std
# 스케일 조정 후 결과()
new = ([25,150]-mean) / std
print("스케일 조정후 점수 :",kn.score(test_scaled,test_target))
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()
표준점수를 훈련세트와 테스트 세트, 이상한 도미에 모두 적용시키고 결과를 확인해 보면
스케일 조정후 점수 : 1.0
이제 오류 없이 원하는 결과가 나오는 것을 확인할 수 있다.
'머신러닝' 카테고리의 다른 글
머신 러닝(with Google Colab) - 4일차(1) (0) | 2024.10.16 |
---|---|
머신 러닝(with Google Colab) - 3일차(2) (4) | 2024.10.16 |
머신 러닝(with Google Colab) - 3일차(1) (0) | 2024.10.14 |
머신 러닝(with Google Colab) - 1일차 (0) | 2024.10.11 |
머신 러닝(with Google Colab) - 0일차 (6) | 2024.10.11 |