핀아의 저장소 ( •̀ ω •́ )✧

[ML] 혼자 공부하는 머신러닝_2장 본문

Big Data/ML & DL

[ML] 혼자 공부하는 머신러닝_2장

_핀아_ 2023. 5. 17. 15:03
💡 학습 목표
1. 지도 학습과 비지도 학습의 차이점 비교
2. 훈련 세트와 테스트 세트의 정의
3, 샘플링 편항 및 해결 방안
4. 데이터 전처리의 필요성

목차

  1. 지도 학습 vs 비지도 학습
  2. 훈련 세트와 테스트 세트
  3. 샘플링 편향
    1. 샘플링 편향 문제 해결하기
  4. 데이터 전처리

1️⃣ 지도 학습 vs 비지도 학습

머신러닝 알고리즘은 크게 지도 학습비지도 학습으로 나눌 수 있음

지도 학습 (supervised learning)

  • 지도 학습 알고리즘은 훈련 데이터(training data)가 필수로 필요함.
  • 일반적으로 지도 학습에서 데이터는 입력(input), 정답은 타깃(target)이라 표현하며 이 둘을 합쳐 훈련 데이터(training data)라 부름
  • 지도 학습은 정답이 있으니 알고리즘이 정답을 맞히는 것을 학습하여 새로운 데이터를 예측하는데 활용함
  • 1장에서 사용한 K-최근접 이웃 알고리즘도 지도 학습 알고리즘 중 하나임

 비지도 학습 (unsupervised learning)

  • 타깃 없이 입력 데이터만 있을 때 사용함
  • 타깃을 사용하지 않으므로 정답을 맞힐 수 없으나 데이터를 파악하거나 변형하는 데 도움을을 주는데 활용함

2️⃣ 훈련 세트와 테스트 세트

💡 훈련 세트와 테스트 세트의 개념에 대해 알아보고 1장에서 사용된 도미와 빙어 데이터를 훈련 세트와 테스트 세트로 나눠 모델 학습을 수행 해봄
  • 머신러닝 알고리즘의 성능을 제대로 평가하려면 훈련 데이터와 평가에 사용할 데이터가 달라야함
  • 일반적으로 이미 준비된 데이터 중에서 일부를 떼어 내어 활용하는 경우가 많음
  • 평가에 사용하는 데이터를 테스트 세트(test set), 훈련에 사용되는 데이터를 훈련 세트(train set)라 부름
  • 일반적으로 훈련 세트의 양은 클 수록 좋으며 테스트 세트를 제외한 모든 데이터를 사용함
  • 테스트 세트는 전체 데이터의 20% ~ 30%의 비율로 사용되며 전체 데이터가 아주 크다면 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
]

fish_data = [[l, w] for l, w in zip(fish_length, fish_weight)]
fish_target = [1]*35 + [0]*14

#훈련 데이터
train_input = fish_data[:35]
train_target = fish_target[:35]

#테스트 데이터
test_input = fish_data[35:]
test_target = fish_target[35:]

 K-최근접 이웃 알고리즘 정의 및 훈련

from sklearn.neighbors import KNeighborsClassifier

kn = KNeighborsClassifier()
kn = kn.fit(train_input, train_target)
kn.score(test_input, test_target)
'''
결과 : 0.0
''' 

plt.scatter([tmp[0] for tmp in train_input], [tmp[1] for tmp in train_input], label="train set")
plt.scatter([tmp[0] for tmp in test_input], [tmp[1] for tmp in test_input], label="test set")
plt.xlabel('length')
plt.ylabel('weight')
plt.legend()
plt.show()

3️⃣ 샘플링 편향

💡 2절의 K-최근접 이웃 알고리즘 정의 및 훈련의 결과 모델 정확도가 0.0(0%)임을 확인 할 수 있음
1장에서 동일한 데이터로 모델을 학습시킨 K-최근접 이웃 알고리즘의 모델 정확도는 1.0(100%)이 나온것과 비교하여 차이점이 무엇인지 확인해봄
  • 샘플링 편향이란 훈련 세트와 테스트 세트에 샘플이 고르게 섞여 있지 않을때 나타나는 현상임
  • 위 예의 생선 데이터의 훈련 세트를 보면 도미만 존재하기 때문에 테스트 세트가 무엇이든 무조건 도미로 판단함
  • 반면 테스트 세트는 빙어만 존재하기 때문에 모델의 정확도는 0.0으로 측정됨

샘플링 편향 문제 해결하기

  • 훈련 세트와 테스트 세트를 나누기 전에 데이터를 잘 섞거나 골고루 샘플을 뽑아서 훈련 세트와 테스트 세트를 만들어야함
  • 이 작업을 간편하게 처리할 수 있도록 도와주는 넘파이(numpy) 라이브러리가 존재함

🔸 샘플링 편향 문제를 해결 하여 데이터 셋 분리

import numpy as np

#list to array
input_arr = np.array(fish_data)
target_arr = np.array(fish_target)
print(input_arr)
'''
[[  25.4  242. ]
 [  26.3  290. ]
 [  26.5  340. ]
 [  29.   363. ]
 [  29.   430. ]
	...
]
'''
print(input_arr.shape)
#(49, 2)

#랜덤 셔플링을 하기위한 index 생성
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
]
'''
#훈련 세트와 테스트 세트 분리
train_input = input_arr[index[:35]]
train_target = target_arr[index[:35]]
test_input = input_arr[index[35:]]
test_target = target_arr[index[35:]

import matplotlib.pyplot as plt
plt.scatter(test_input[:, 0], test_input[:, 1], label="test set")
plt.xlabel('length')
plt.ylabel('weight')
plt.legend()
plt.show()

🔸 K-최근접 이웃 알고리즘 정의 및 훈련

from sklearn.neighbors import KNeighborsClassifier

kn = KNeighborsClassifier()
kn = kn.fit(train_input, train_target)
kn.score(test_input, test_target)
'''
결과 : 1.0
'''

🔸 테스트 데이터 분류 예측

#테스트 데이터 예측 하기
kn.predict(test_input)
#array([0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0])

#실제 테스트 예측 값과 비교
print(test_target)
#array([0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0])

4️⃣ 데이터 전처리

💡 머신러닝 모델에 훈련 데이터를 주입하기 전 가공하는 단계로 특성값을 일정한 기준으로 맞추어 주는 작업임
전처리 데이터로 훈련했을 때의 차이를 알아보고 사용된 스케일 변환 방법을 배움

 넘파이로 데이터 준비하기

💡 넘파이를 사용하기에 조금 더 세련된 방법으로 도미와 빙어 데이터를 합침
# 도미 데이터
# 빙어 데이터
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
fish_data = np.column_stack((fish_length, fish_weight))
fish_target = np.concatenate((np.ones(35), np.zeros(14)))

print(fish_data[:5])
'''
[[ 25.4 242. ]
 [ 26.3 290. ]
 [ 26.5 340. ]
 [ 29.  363. ]
 [ 29.  430. ]]
'''
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.]
'''

사이킷런으로 훈련 세트와 테스트 세트 나누기

💡 앞서 인덱스를 생성하여 번거로운 방법을 통해 데이터 셋을 분류함
사이킷런을 사용하여 세련된 방법으로 훈련 세트와 테스트 세트를 나눠봄
from sklearn.model_selection import train_test_split

train_input, test_input, train_target, test_target = 
train_test_split(fish_data, fish_target, stratify=fish_target, random_state=42)

'''
param
1) test_size
- 테스트 데이터 세트의 비율(default = 0.25)
2) stratify
- 데이터 평향이 발생하지 않도록 target 비율에 맞게 데이터들이 랜덤하게 섞임
'''

print(train_input.shape, test_target.shape)
#(36, 2) (13,)

#데이터가 잘 섞였는지 확인
print(test_target)
#[0. 0. 1. 0. 1. 0. 1. 1. 1. 1. 1. 1. 1.]

K-최근접 이웃 알고리즘 생성 및 훈련

from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier()
kn.fit(train_input, train_target)
kn.score(test_input, test_target)
'''
결과 : 1.0
'''

수상한 생선 데이터 분류 예측

길이가 25cm, 무게가 150g인 생선의 종류를 예측해보자

import matplotlib.pyplot as plt
plt.scatter(train_input[:, 0], train_input[:, 1], label='train set')
plt.scatter(25, 150, marker='^', label='new fish')
plt.xlabel('length')
plt.ylabel('weight')
plt.legend()
plt.show()

new_fish = [[25, 150]]
print(kn.predict(new_fish))
#[0.], 빙어로 예측

⚠️실제 25cm, 150g인 생선은 도미며 예측 값이 틀림을 알 수 있음

수상한 생선 데이터에서 가까운 샘플 확인하기

💡 KNeighborsClassifier 클래스는 주어진 샘플에서 가장 가까운 이웃을 찾아주는 kneighbors() 메서드를 제공함 KNeighborsClassifier 클래스의 이웃 개수인 n_neighbors의 기본 값은 5이므로 5개의 이웃이 반환됨
new_fish = [[25, 150]]
distances, indexes = kn.kneighbors(new_fish)

print(distances)
# array([[ 92.00086956, 130.48375378, 130.73859415, 138.32150953, 138.39320793]])
print(indexes)
# array([[21, 33, 19, 30,  1]])

import matplotlib.pyplot as plt
plt.scatter(train_input[:, 0], train_input[:, 1], label='test set')
plt.scatter(25, 150, marker='^', label='new fish')
plt.scatter(train_input[indexes, 0], train_input[indexes, 1], marker='D', label='neighbors sample')
plt.xlabel('length')
plt.ylabel('weight')
plt.legend()
plt.show()

⚠️새로운 생선 데이터의 이웃에는 도미보다 빙어가 더 많이 분포 되어있음을 알 수 있음

스케일 변환

💡 사람이 데이터의 산점도를 봤을때 새로운 생선의 이웃 샘플은 빙어보다 도미가 더 많아 보임에도 실제 거리를 측정한 결과 빙어가 훨씬 많았음
이 이유에 대해 알아보고 스케일 변환 과정을 통해 올바르게 예측 할 수 있도록 조정해봄
  • x축, y축 기준 맞추기

x축(length)과 y축(weight)의 범위 차가 너무 크기 때문에 y축으로 조금만 멀어져도 거리가 아주 큰 값으로 계산되는 문제가 발생함

xlim() 메서드를 통해 x축과 y축의 범위를 동일하게 맞춰 데이터의 산점도를 확인함

import matplotlib.pyplot as plt
plt.scatter(train_input[:,0], train_input[:,1], label='train set')
plt.scatter(25, 150, marker='^', label='new fish')
plt.scatter(train_input[indexes,0], train_input[indexes,1], marker='D', label='neighbors sample')
plt.xlim((0, 1000))
plt.xlabel('length')
plt.ylabel('weight')
plt.legend()
plt.show()

x축과 y축의 범위를 동일하게 맞춘 결과 모든 데이터가 수직으로 늘어선 형태가 되었음

생선의 길이(x축)는 가장 가까운 이웃을 찾는데 큰 영향을 미치지 않았고 생선의 무게(y축)만 고려 대상이 됨

  • 생선의 길이와 무게의 값이 놓인 범위가 매우 다르기 때문에 두 특성의 스케일을 조정해야함
  • K-최근접 이웃과 같은 거리 기반 알고리즘일 경우 더더욱 데이터 전처리 과정을 통해 스케일 변환을 수행해야함

◾ 훈련 데이터 스케일 변환 하기

가장 널리 사용되는 전처리 방법 중 하나는 표준점수(standard score)를 사용하여 스케일을 변환함

Z = (특성-평균)/표준편차
#평균
mean = np.mean(train_input, axis = 0)
#표준편차
std = np.std(train_input, axis = 0)
'''
axis = 0, 행을 따라 각 열의 값을 계산함
'''

print(mean, std)
#[ 27.29722222 454.09722222] [  9.98244253 323.29893931]

#표준점수
train_scaled = (train_input - mean) / std

#넘파이의 브로드캐스팅 기능을 이용하여 계산

⚠️ 브로드캐스팅
크기가 다른 넘파이 배열에서 자동으로 사칙 연산을 모든 행이나 열로 확장하여 수행하는 기능

전처리 데이터(스케일 변환 데이터)로 모델 훈련 및 수상한 생선 데이터 분류 예측

💡 표준점수로 변환한 train_scaled를 사용하여 모델을 학습하기 전에 테스트 데이터새롭게 예측할 데이터도 표준점수로 변환하는 과정이 필요함

그 이유는 훈련 세트를 스케일 변환 하게 되면 특성(length, weight)의 값이 변하게됨
그에 맞춰 테스트 데이터 및 수상한 생선 데이터 또한 스케일 변환 해줄 필요가 있음
주의 사항은 훈련 데이터 스케일 변환에 사용된 평균과 표준편차 값을 사용하여 동일한 수치로 테스트 데이터 및 새롭게 예측할 데이터의 스케일 변환을 수행해야함
#수상한 생선 데이터를 스케일 변환 하지 않으면 나타나는 문제점 확인
import matplotlib.pyplot as plt

#표준점수 변환 전 new fish 데이터 확인
plt.scatter(train_scaled[:,0], train_scaled[:,1], label='train set')
plt.scatter(25, 150, marker='^', label='new fish')
plt.xlabel('length')
plt.ylabel('weight')
plt.legend()
plt.show()

  • 모델 학습 후 표준점수 스케일 변환된 테스트 데이터를 기반으로 모델 정확도 측정
#모델 학습
kn.fit(train_scaled, train_target)

#테스트 데이터 표준점수 스케일 변환
test_scaled = (test_input - mean) / std
#모델 정확도 측정
kn.score(test_scaled, test_target)

'''
결과: 1.0
'''
  • new fish 예측 후 산점도를 통해 이웃 샘플 확인
#수상한 생선 데이터 스케일 변환
new = ([25, 150] - mean) / std
print(kn.predict([new]))
#[1.0], 도미로 예측

import matplotlib.pyplot as plt
distances, indexes = kn.kneighbors([new])
plt.scatter(train_scaled[:, 0], train_scaled[:, 1], label='train_scaled')
plt.scatter(new[0], new[1], marker='^')
plt.scatter(train_scaled[indexes, 0], train_scaled[indexes, 1], marker='D', label='neighbors sample')
plt.xlabel('length')
plt.ylabel('weight')
plt.legend()
plt.show()

Comments