SHIN JI HAN 2024. 9. 22. 03:35
728x90
728x90
Reporting Date: September. 22, 2024

머신러닝 알고리즘에 주입할 데이터를 준비하는 방법을 배우고,
데이터 형태가 알고리즘에 미치는 영향에 대해 다루고자 한다.


목차

02-1. 훈련 세트 & 테스트 세트

02-2. 훈련 세트 & 테스트 세트


 

 

 

02 - 1 .  훈련 세트 & 테스트 세트

 

2-1. 훈련 데이터와 테스트 데이터

Run, share, and edit Python notebooks

colab.research.google.com

 

 

지도 학습 (supervised learning)

입력 데이터(input)와 그에 해당하는 정답(target, label)이 필요하다.

 

모델은 주어진 입력과 정답을 사용하여 학습하고,
새로운 입력에 대해 정확한 예측을 할 수 있도록 훈련된다.

 

 

용어 정의

 

 

비지도 학습 (unsupervised learning)

정답이 없이 입력 데이터만 사용하여 패턴을 찾는다.
주로 군집화차원 축소 등의 작업에 사용된다.

 

 

훈련 세트를 이용해 모델이 데이터를 학습하는 과정 ≒ 학생이 시험을 준비하는 과정

 

훈련 세트와 테스트 세트가 동일할 경우,
모델은 이미 답을 알고 있으므로 100%의 정확도를 달성할 수 있다.

그러나, 이는 학생이 시험 전에 문제와 답을 미리 알고 있는 것과 같기에
실제 성능을 평가하는 방식으로는 적절하지 않다.

 

 

따라서, 모델의 성능을 올바르게 평가하려면
훈련에 사용한 데이터와는 다른 데이터(테스트 세트)를 사용하여 평가
해야 한다.

이를 통해 모델이 실제로 새로운 데이터에서 얼마나 잘 예측하는지를 측정할 수 있다.

 

 

일반적으로 데이터셋을 준비할 때,

훈련 데이터(training set)테스트 데이터(test set)로 나누는 것이 일반적이며,
이외에도 검증 데이터(validation set)를 추가로 사용하는 경우도 있다.

 

이것은 모델이 학습한 패턴을 일반화하는 능력을 평가할 수 있게 되어,
과적합(overfitting)을 방지하고, 실제 환경에서의 성능을 더 정확하게 반영할 수 있다.

 

 

도미와 빙어의 데이터를 합친 하나의 파이썬 리스트

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차원 리스트

# 35(도미) + 14(빙어) = 49개의 샘플
# 사용하는 특성: 길이 & 무게 ⇨ 2개

fish_data = [[l, w] for l, w in zip(fish_length, fish_weight)]
fish_target = [1]*35 + [0]*14 # 훈련 데이터: 35, 테스트 데이터: 14

 

 

모델 객체 만들기

from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier()

 

 

샘플링 편향 (sampling bias)

데이터가 고르게 섞이지 않아서 발생하는 문제.

테스트 세트에 빙어만 포함되어 있고, 훈련 세트에 도미만 있을 경우,
모델은 도미만 학습하게 되어, 테스트 세트에서 무조건 도미로 예측하게 되며,

이는 올바른 평가가 되지 않는다.

 

이 문제를 해결하기 위해서는
데이터를 섞어서(train-test split) 훈련 세트와
테스트 세트에 도미와 빙어가 골고루 포함되도록 해야 한다.

Numpy 나  scikit-learn을 사용하여 데이터를 무작위로 섞을 수 있으며,
Numpy 에 경우, 고차원 배열 제작하고, 이를 조작할 수 있는 도구를 제공한다.

 

 

생선 데이터를 2차원 Numpy 배열로 변환하기

import numpy as np

input_arr = np.array(fish_data)
target_arr = np.array(fish_target)

print(input_arr)

이하 생략

 

 

배열의 크기 확인하기

input_arr.shape
# (샘플 수, 특성 수)

49개의 샘플과 2개의 특성

 

 

주어진 배열을 무작위로 섞기

np.random.seed(42)  # 난수 생성기 시드 고정
index = np.arange(49)  # 0부터 48까지의 숫자 배열 생성
np.random.shuffle(index)  # 배열을 무작위로 섞음
index

 

 

무작위로 섞인 인덱스를 사용해 전체 데이터를
훈련 세트와 테스트 세트로 나누기

# 섞인 인덱스의 처음 35개로 훈련 데이터를 만듦
train_input = input_arr[index[:35]]  

# 훈련 데이터에 대응되는 타깃값도 동일하게 선택
train_target = target_arr[index[:35]]


# 섞인 인덱스에서 35번째 이후를 테스트 데이터로 설정
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])

# x축과 y축에 대한 라벨
plt.xlabel('length')
plt.ylabel('weight')


# 아래 산점도를 통해, 도미와 빙어가 
# 각각의 세트에 잘 섞였음을 알 수 있다.
더보기
파란색: 훈련 세트, 주황색: 테스트 세트

 

 

k–최근접 이웃(k–Nearest Neighbors, KNN)
알고리즘을 사용하여 모델을 훈련하고, 테스트 세트에서 그 성능을 평가하기

# 이전에 만든 모델 객체 사용하기
kn = kn.fit(train_input, train_target)
kn.score(test_input, test_target)

100%의 정확도

 

 

주어진 테스트 데이터에 대해 각 샘플이 어느 클래스에 속하는지 예측한 결과를 반환

kn.predict(test_input)

 

예측 결과를 실제 정답(test_target)과 비교하기

test_target

테스트 세트에 대한 예측 결과가 정답과 일치한다.


 

 

 

02 - 2 .  훈련 세트 & 테스트 세트

 

2-2. 데이터 전처리

Run, share, and edit Python notebooks

colab.research.google.com

# 이전과 동일한 데이터
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]

 

 

두 개 이상의 1차원 배열을 열(column) 단위로 결합하여 2차원 배열 만들기

import numpy as np

# 생선의 길이와 무게를 열 단위로 합침
fish_data = np.column_stack((fish_length, fish_weight))
print(fish_data[:5])

각 행마다 2개의 열(길이, 무게)

 

 

각 생선이 도미인지 빙어인지 나타내는 라벨(타깃값)을 담고 있다

fish_target = np.concatenate((
    
    # 도미: 1, 빙어: 0
    np.ones(35), np.zeros(14)))

fish_target

 

 

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

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)

train_input.shape, test_input.shape

# train_test_split: 
# 입력된 데이터의 약 25%를 테스트 세트로 분리
# 나머지 75%를 훈련 세트로 사용

# 4개의 배열 반환
# train_input: (36, 2)
# test_input:  (13, 2)

# train_target: (36, )
# test_target:  (13, )

4개의 배열 반환

 

 

훈련 세트와 테스트 세트의 타깃 데이터 크기 확인하기

train_target.shape, test_target.shape

 

 

훈련 세트와 테스트 세트 샘플의 클래스 비율 확인하기

test_target

# 도미(1):빙어(0) = 35:14 = 2.5:1
# test_target의 비율 = 3.3:1

# 샘플링 편향이 나타나므로, 
# 클래스 비율에 맞게 데이터를 재분할 해야 한다.

 

 

클래스 비율에 맞게 데이터 재분할하기

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

test_target

# stratify = fish_target: 
# 각 클래스의 비율이 훈련 세트와 테스트 세트에 
# 동일하게 유지되도록 데이터를 분할한다


# 결과적으로 빙어가 1개 증가하여
# 테스트 세트의 비율이 2.25:1이 되었다

 

 

k–최근접 이웃(k–Nearest Neighbors, KNN)
알고리즘을 사용하여 모델을 훈련하고, 테스트 세트에서 그 성능을 평가하기

from sklearn.neighbors import KNeighborsClassifier

kn = KNeighborsClassifier()
kn.fit(train_input, train_target)
kn.score(test_input, test_target)

100%의 정확도

 

 

올바르게 분류하는지 확인하기

kn.predict([[25, 150]]) # 도미(1) 샘플

올바르게 예측하지 않음

 

 

이 샘플을 다른 데이터와 함께 산점도로 나타내기

import matplotlib.pyplot as plt

plt.scatter(train_input[:,0], train_input[:,1])
plt.scatter(25, 150, marker='^') # 삼각형(△)
plt.xlabel('length')
plt.ylabel('weight')
더보기
샘플은 도미 데이터 주변에 있으나, 모델은 빙어 데이터와 가깝다고 판단

 

 

k–최근접 이웃(KNN) 알고리즘은 새로운 샘플의 클래스를 예측할 때,
해당 샘플 주변의 이웃 샘플들 중 다수인 클래스 예측값으로 사용한다.

 

이 샘플 주변의 이웃들을 알아보려면,
KNeighborsClassifier 클래스의
kneighbors() 메서드를 사용할 수 있다.

 

이 메서드는 지정한 샘플에서 가장 가까운 이웃들의 거리이웃 샘플의 인덱스를 반환한다.
기본적으로, 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')
더보기
삼각형 샘플과 가까운 5개의 이웃 샘플이 초록 마름모로 표시

 

 

5개의 샘플에 대한 데이터 확인

train_input[indexes], train_target[indexes]

가장 가까운 생선 4개가 빙어(0)인 것을 확인

 

 

문제의 원인 파악을 위한,  distances 배열 출력

distances

각 값은 해당 이웃까지의 거리를 의미

 

 

위에서  출력된 거리를 그래프에 대입하여 비교

더보기
범위 차이가 문제의 요인임을 확인

 

 

따라서, 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')

 

 

위와 같이, 두 특성(길이와 무게)의 스케일이 다르면,
k–최근접 이웃(KNN)과 같은 거리 기반 알고리즘에서 문제가 발생할 수 있다.

 

이 경우, 특성 값의 범위가 큰 변수(무게)가 모델에 더 큰 영향을 미치게 된다.
산점도가 거의 수직으로 나타난 것도 그 이유이다.

 

이를 해결하려면 특성 스케일링(Feature Scaling)을 적용하여,
두 특성의 값을 동일한 범위로 맞추는 것이 좋다.

 

일반적으로 표준화(standardization) 또는 정규화(normalization)를 사용하며,
이러한 작업은 데이터 전처리(Data Preprocessing)의 일환이다.

 

표준화의 대표적인 예시로, 표준점수(Z-score)가 있다.

각 데이터가 평균으로부터 얼마나 떨어져 있는지를 나타내는 지표로,
데이터의 분포가 평균을 기준으로 얼마나 퍼져 있는지를 평가하는 데 유용하다.

 

 

표준점수 계산을 위한, 평균과 표준편차 계산 및 출력

mean = np.mean(train_input, axis = 0)
std = np.std(train_input, axis = 0)
mean, std # 평균, 표준편차

# axis = 0: 
# 행(row) 방향, 세로 방향으로
# 이동하며, 각 열의 평균을 계산

 

 

평균을 빼고, 표준편차로 나누어 표준점수로 변환

train_scaled = (train_input - mean) / std
# ↪ 브로드캐스팅(broadcasting): 
# NumPy에서 배열 간의 연산을 할 때, 

# 차원이 맞지 않는 배열을 자동으로 
# 조정하여 연산을 가능하게 하는 기능

plt.scatter(train_scaled[:,0], train_scaled[:,1])
plt.scatter(25, 150, marker='^')
plt.xlabel('length')
plt.ylabel('weight')
더보기
우측 상단에 샘플 하나가 홀로 존재

 

 

동일한 비율을 변환하지 않으면, 계산과정에서 값의 범위가 크게 달라진다.
따라서, 동일한 기준을 적용하여, 다시 산점도를 그린다.

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')
더보기
x축과 y축의 범위가 바뀜

 

 

위 데이터셋으로 k–최근접 이웃 모델을 훈련한다

kn.fit(train_scaled, train_target)

 

 

모델 평가

test_scaled = (test_input - mean) / std
kn.score(test_scaled, test_target)

100%의 정확도

 

 

모델이 새로 추가된 샘플을 정확히 예측하는지 확인 

kn.predict([new])

25cm, 150g의 생선을 도미로 예측하는 데 성공

 

 

이 샘플의 k–최근접 이웃을 구하고, 산점도를 그린다

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')
더보기
삼각형 샘플과 가까운 5개의 이웃 샘플이 모두 도미인 것을 확인

Mapo금빛나루 | | 공유 마당 (copyright.or.kr)

교제: 혼자 공부하는 머신러닝 + 딥러닝


728x90
반응형