--06.분류.iptnb--
여러개의 종류(class 라고 부릅니다) 중 하나를 구별해 내는 문제를 분류 (classification) 라고 부릅니다.
만약 2개의 클래스중 하나를 고르는 문제를 이진 분류(binary classification) 이라고 함
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import os
base_path=r'/content/drive/MyDrive/dataset'
file_path = os.path.join(base_path, 'fish.csv')
fish_df = pd.read_csv(file_path)
fish_df
fish_df.info()
fish_df.Species.unique()
fish_df.Species.value_counts()
"""
bream_length = fish_df[fish_df.Species == 'Bream']['Length'].to_list()
print(bream_length)
#무게
bream_weight = fish_df[fish_df.Species == 'Bream']['Weight'].to_list()
print(bream_weight)
smelt_length = fish_df[fish_df.Species == 'Smelt']['Length'].to_list()
print(smelt_length)
#무게
smelt_weight = fish_df[fish_df.Species == 'Smelt']['Weight'].to_list()
print(smelt_weight)
plt.scatter(bream_length, bream_weight)
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
"""
생선의 길이(length)가 길수록 무게가 많이 나가는건 자연스러운 관계입니다.
이와 같이 그래프가 일직선에 가까운 형태로 나타나는 경우를 '선형(linear)'하다 라고 한다.
"""
None
print(bream_length)
print(bream_weight)
print('-' * 20)
print(smelt_length)
print(smelt_weight)
plt.scatter(bream_length, bream_weight)
plt.scatter(smelt_length, smelt_weight)
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
"""
주황색이 빙어의 산점도 데이터다.
빙어는 도미에 비해 길이도 무게도 매우 작습니다.
그러나, 그보다 더 중요하게 바라보아야 하는것은 연관정도입니다.
빙어도 도미와 비슷하게 길이와 무게가 비례하지만, 늘어나는 정도가 조금 다릅니다.
빙어의 산점도도 선형적이긴 하지만, 무게가 길이에 영향을 덜 받는다고 볼 수 있다.
이제 위 두 생성의 데이터로 생선을 분류(classification) 하기 위한 머신러닝 프로그램을 작성해보자.
"""
None
length = bream_length + smelt_length
weight = bream_weight + smelt_weight
print(length)
print(weight)
"""
scikit-learn 에선 'data'를
각 '특성들' 의 '배열' 형태로 만들어야 한다 -> 2차원 배열
길이 무게
[ ↓ ↓
[25.4, 242.0],
[26.3, 290.0],
[26.5, 340.0],
...
[15.0, 19.9]
]
fish_data = [
[l, w]
for l, w in zip(length, weight)
]
print(fish_data)
"""
'데이터'는 준비되었다.
이제, 머신 러닝 알고리즘에게 이 데이터가 '어떤 생선' 인지도 '답안지' 도 알려주어야 합니다.
그래야 학습(지도학습) 을 할수 있습니다.
그러면 각각의 데이터에 '도미', '빙어' 라고 알려주면 될까요?
머신러닝 알고리즘은 '문자를 이해 못함' 이들 데이터도 '숫자' 로 표현하여 알려주어야 함
도미는 1 로, 빙어는 0 으로 표현된 '답안'을 준비해보겠습니다
★ 이러한 답안을 label(레이블), 혹은 'target(타켓)' 이라 합니다
"""
None
"""
머신러닝 쪽에서는
data 를 'x' 로, target 을 'y' 로 표기하는 경우가 많다
특히 data 는 'feature 들의 벡터' 라서 대문자 'X' 로 표기하곤 한다.
"""
None
fish_target = [1] 35 + [0] 14
print(fish_target)
모델(model) 이란
머신러닝 알고리즘을 구현한 프로그램,
혹은, 프로그램은 아니더라도 알고리즘을 (수식등으로) 구체화 하여 표현한 것.
from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier()
어떤 데이터에 대한 답을 구할때 '주변의 다른 데이터'를 보고 '다수를 차지하는 것을 정답'으로 사용함.
plt.scatter(bream_length, bream_weight)
plt.scatter(smelt_length, smelt_weight)
plt.scatter(30, 600, marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
fish_data
print(fish_target)
kn.fit(fish_data, fish_target)
kn.score(fish_data, fish_target)
kn.predict([[30, 600]])
"""
array([1])
'1' 값을 예측함. '1' 은 '도미' 니까 정확히 예측한것
"""
None
kn.predict([[30, 600],[12, 0.5]])
"""
KNN 의
장점: 데이터만 있으면 된다.
단점: 데이터가 너무 많으면 사용하기 힘들다 (많은 메모리, 많은 거리 계산, ...)
"""
None
print(kn._fit_X)
print(kn._y)
"""
실제로 KNN 알고리즘은 딱히 무엇가 훈련(train) 되는게 없는 셈이다.
fit() 에 전달한 데이터를 저장하고 있다가 새로운 데이터가 등장하면 가장 '가까운 데이터들'을 참고하여
어떤 생선인지만 구분하는 겁니다.
그러면 '가까운 몇개의 데이터'를 참고할게 될까?
이는 n_neighbors 값으로 정해줄수 있다. (기본값 5)
"""
None
kn49 = KNeighborsClassifier(n_neighbors=49) # 인접하는 참고 데이터 49개로 설정
kn49.fit(fish_data, fish_target)
kn49.score(fish_data, fish_target)
"""
KNeighborsClassifier 의 매개변수들
n_neighbors: 이웃의 개수
p : 매개변수 거리 재는 방법.
1 - 맨해튼 거리
2 - 유클리디안 거리 (디폴트)
n_jobs: 사용할 CPU 코어 지정. 이웃간의 거리 계산속도를 높일수는 있지만 fit() 메소드에는 영향이 없다
1 - 기본값
-1 - 모든 CPU 코어 사용
"""
None
kn = KNeighborsClassifier()
kn.fit(fish_data, fish_target)
for n in range(5,50) :
kn.n_neighbors = n
score = kn.score(fish_data, fish_target)
print(n, score)
if score < 1:
break
주어진 '문제'와 '답안지' 로 열심히 공부해서 문제집 문제집 100점 맞으면 그 학생은 잘한다 할수 있나? 진짜 잘하는지는 문제집에 없던 문제들을 풀어도 점수가 좋아야 한다.
그래서 data 와 target을 학습(training) 하는데 다 집어 넣는 것이 아니라, 훈련세트와 테스트세트를 나누어서 모델 훈련 및 검증에 사용한다
fish_data[4] # 하나의 데이터를 'sample(샘플)' 이라고 함.
fish_data[0:5] # 이것도 샘플이라 할 수 있다.
fish_data[44:]
"""
데이터의 처음 35개를 train 세트 (훈련세트), 나머지 14개를 test 세트 (테스트 세트)로 사용해보도록 해보자
[
[25.4, 242.0], ─┐
[26.3, 290.0], │ train 세트 35개
[26.5, 340.0], │
... ─┘
... ─┐
... │ test 세트 14개
[15.0, 19.9] ─┘
]
"""
None
train_input = fish_data[:35]
train_target = fish_target[:35]
test_input = fish_data[35:]
test_target = fish_target[35:]
kn.fit(train_input, train_target)
kn.score(test_input, test_target)
"""
↑ 정확도 가 0.0 이 나왔다. 하나도 맞추지 못하고 있다?! 왜 그럴까?
바로 '샘플링 편향 (sampling bias)' 문제다
train 세트에 도미 만 잔뜩 있어서 이를 훈련시키고
test 세트에는 빙어 만 잔뜩 있었으니 결과는 0 이 나온거다
해결 -> 골고루 섞어야 한다
"""
None
input_arr = np.array(fish_data)
target_arr = np.array(fish_target)
input_arr.shape
np.random.seed(42)
index = np.arange(49)
np.random.shuffle(index)
print(index)
train_input = input_arr[index[:35]]
train_target = target_arr[index[:35]]
input_arr[13], train_input[0]
test_input = input_arr[index[35:]]
test_target = target_arr[index[35:]]
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 = KNeighborsClassifier()
kn.fit(train_input, train_target)
kn.score(test_input, test_target)
kn.predict(test_input)
test_target
kn.predict([[25,150]])
plt.scatter(bream_length, bream_weight)
plt.scatter(smelt_length, smelt_weight)
plt.scatter(25, 150, marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
"""
↑ 흠... 저 정도면... 도미(1) 로 분류되어야 하는거 아닐까?
도미에 더 가깝잖아?
"""
None
np.column_stack(([1, 2, 3], [4, 5, 6]))
fish_length = bream_length + smelt_length
fish_weight = bream_weight + smelt_weight
fish_data = np.column_stack((fish_length, fish_weight))
print(fish_data[:5])
fish_target = np.concatenate((np.ones(35), np.zeros(14)))
fish_target
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)
print(train_input.shape, test_input.shape)
print(train_target.shape, test_target.shape)
print(train_target)
print(test_target)
"""
[1. 0. 0. 0. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
↑ 잘 섞인듯? 하지만, 빙어의 비율이 조금 모잘라 보인다
도미:빙어 = 35:14 이므로 이는 2.5:1 되어야 한다.
그러나 위 결과는 3.3:1 이다. (샘플링 편향: sampling bias 발생!)
train:test 의 비율이 일정하지 않다면 모델이 일부 샘플을 올바르게 학습할수 없을것이다.
train_test_split() 에 stratify 매개변수에 target 을 전달하면 클래스 비율에 맞게 데이터를 나눈다.
train 데이터가 작거나 특정 클래스의 샘플 개수가 적을 때 특히 유용.
"""
None
train_input, test_input, train_target, test_target = train_test_split(fish_data, fish_target, random_state=42, stratify=fish_target)
print(test_target)
"""
↑ 빙어(0) 이 하나 더 늘었다. 이제 test 세트 비율이 2.25:1 이 되었다. 꽤 비슷한 비율이 되었다.
"""
None
kn = KNeighborsClassifier()
kn.fit(train_input, train_target)
kn.score(test_input, test_target)
kn.predict([[25, 150]]) # 여전히 빙어(0)로 결과가 나온다.
plt.scatter(bream_length, bream_weight)
plt.scatter(smelt_length, smelt_weight)
plt.scatter(25, 150, marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
"""
분명히 (25, 150) 은 빙어(0) 보다 도미(1) 에 가까이 있다.
그런데 왜 모델은 멀리 떨어진 왼쪽 아래에 낮게 깔린 빙어 데이터에 가깝다고 판단한걸까?
KNN 은 주변의 샘플 중에서 '다수' 인 클래스를 예측으로 사용합니다.
이 샘플(25, 150)의 주변샘플은 어떠한 것들이 있는지 들여다 봅시다.
kneighbors() 메소드를 통해 할수 있다
"""
None
distances, indexes = kn.kneighbors([[25, 150]])
distances
indexes
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') # neighbor 들을 따로 찍어보자 (마름모)
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
"""
5개의 이웃 중에서 도미(1)는 한개, 빙어(0)는 무려 4개?
이웃의 데이터 학인
"""
None
print(train_input[indexes])
"""
[[[ 25.4 242. ] <- 요건 도미 구..
[ 15. 19.9] <- 다음 네개는 확실히 빙어다..
[ 14.3 19.7] <- 빙어
[ 13. 12.2] <- 빙어
[ 12.2 12.2]]] <- 빙어
↓ 이는 target 데이터로 보면 더 명확하다
"""
None
print(train_target[indexes])
"""
↑ 길이 25cm, 무게 150g 인 생선의 가장 가까운 이웃에 빙어가 압도적으로 많다.
그래서 모델은 빙어라고 예측한거다.
왜일까? scatter plot 에서 보면 직관적으로 '도미' 에 더 가깝게 보이는데?
이를 확인해보기 위해선 kneighbors() 에서 리턴한 distances 배열을 들여다 보면 알수 있다. <-- 이웃간의 거리가 담겨있다
"""
None
print(distances)
"""
위 scatter plot 을 살펴보면, 첫번째 샘플과의 거리가 92이고, 그외 가까운 샘플들은 모두 130, 138 이다.
그런데... 그래프에 보여진 거리 비율이 이상하지 않나요?
어림짐작으로 보아도 92 보다 족히 몇배는 되어 보이는데, 겨우 거리가 130이라니? 수상하다?
눈치 채셨나요?
x 축의 범위와 y 축의 범위가 다르다!
x축 (length)의 범위는 (10 ~ 40)
y축 (weight)의 범위는 (0 ~ 1000) x축보다 더 넓다!
따라서 y 축으로 조금만 멀어져도 '거리'가 아주 큰 값으로 계산된다. 그래서 가까이 보이는 도미 샘플이 이웃으로 선택되지 못한거다
이를 눈으로 명확하게 확인하기 위해 x축의 범위도 동일하게 0 ~ 1000으로 맞추어 보자.
"""
None
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)) # x 축도 0 ~ 1000 으로 맞추어 보자.
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
"""
산점도가 거의 일직선으로 보인다. 생선의 legnth 는 가장 가까운 이웃을 찾는데 크게 영향을 못미침을 알수 있다.
거의 생선의 weight (y축) 만 거리계산에 크게 영향을 줄것이다.
"""
"""
두 개의 feature(특성), 즉 length 와 weight 의 값이 놓인 범위가 매우 다르다.
이를 두 feature 의 'scale(스케일)이 다르다' 라고도 말합니다.
특성간 스케일이 다른 일은 얼마든지 있을수 있습니다.
어떤 사람이 방의 넓이를 재는데, 세로는 cm 로 가로는 inch 로 쟀다면 정사각형인 방도 직사각형처럼 보일겁니다
데이터를 표현하는 기준이 다르면, 머신러닝 알고리즘이 제대로 학습되고 제대로 예측하기 힘듭니다.
(특히 KNN 과 같이 '거리' 기반이라면 더더욱..)
따라서!
feature 들이 머신러닝에서 서로 동등한 영향력을 행사하도록 할려면 일정한 기준으로 맞추어야 한다
이러한 작업들을 데이터 전처리 (data preprocessing) 이라 하고,
feature 들을 일정한 기준으로 맞추는 작업을 스케일링(scaling) 한다고 함
"""
None
"""
스케일링의 대표적인 방법 2가지
표준점수 (standard score) 를 사용하여 표준화를 해봅시다
표준점수는 z 점수라고도 합니다
표준점수는 각 feature 값이 평균에서 표준편차의 몇배만큼 떨어져 있는지를 나타냄.
이를 통해 실제 특성값의 크기와 상관없이 동일한 조건으로 비교 가능.
"""
None
mean = np.mean(train_input, axis = 0)
std = np.std(train_input, axis = 0)
print(mean, std) # 각 특성마다 평균과 표준편차 구해짐.
train_scaled = (train_input - mean) / std # broadcating에 의해 연산
print(train_scaled)
plt.scatter(train_scaled[:, 0], train_scaled[:, 1])
plt.scatter(25, 150, marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
"""
↑ 예상과 다르다.
오른쪽 맨 꼭대기에 수상한 샘플 하나만 덩그러니 떨어져 있다.
이렇게 된이유는?
train 세트를 mean(평균) 으로 빼고 std(표준편차)로 나누어 주었기 때문에
'값의 범위'가 크게 달라졌습니다.
따라서! 샘플 [25, 150] 을 동일한 비율로 변환하지 않으면 이런 현상이 발생한다
명심!
반드시 train 세트의 mean, std 를 이용해서 '샘플도 변환'해야 한다!
"""
None
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축 (length) 와 y축(weight) 의 범위가 -1.5 ~ 1.5 사이로 바뀌었다는 점.
train 데이터의 두가지 feature(특성) 이 비슷한 범위를 차지하고 있다.
이제 이 데이터 셋으로 KNN 모델을 다시 훈련해보자
"""
None
kn.fit(train_scaled, train_target)
"""
test 세트로 평가할때도 train 세트의 평균과 표준편차로 변환해야 같은 비율로 산점도를 그릴수 있다.
↓마찬가지로 test 세트도 train 세트의 평균과 표준변차로 변환하자
"""
None
test_scaled = (test_input - mean) / std
kn.score(test_scaled, test_target)
kn.predict([new])
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()