데이터 다루기

신민제·2023년 6월 26일
0

머신러닝, 딥러닝

목록 보기
1/6

머신러닝 알고리즘에는 "지도학습" 과 "비지도 학습" 두가지가 있다.

먼저 지도학습이란, 쉽게 말해 학습을 할 때 정답지가 있는 학습을 말한다. 입력과 타깃을 통해 모델을 훈련한 다음 새로운 데이터를 예측하는 데 활용하는 것이 "지도학습" 이다.
비지도 학습이란, 타깃 데이터가 없다. 즉 정답지가 없는 것이다. 정답지가 없기에 무엇을 예측하는 것이 아닌, 입력 데이터에서 어떠한 특징을 찾는 데 주로 활용되는 것이 "비지도 학습"이다.

그럼 실습을 진행해 보자.
먼저 생선의 어종(도미와 빙어)을 맞추는 모델으로 연습해보자.

다음은 생선의 길이와 무게에 대한 데이터이다. 데이터는 http://bit.ly/bream_smelt에서 가져왔다.

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
print(fish_data)

이 코드를 실행하게 되면,
[[25.4, 242.0], [26.3, 290.0], [26.5, 340.0], [29.0, 363.0], [29.0, 430.0], [29.7, 450.0], [29.7, 500.0], [30.0, 390.0], [30.0, 450.0], [30.7, 500.0], [31.0, 475.0], [31.0, 500.0], [31.5, 500.0], [32.0, 340.0], [32.0, 600.0], [32.0, 600.0], [33.0, 700.0], [33.0, 700.0], [33.5, 610.0], [33.5, 650.0], [34.0, 575.0], [34.0, 685.0], [34.5, 620.0], [35.0, 680.0], [35.0, 700.0], [35.0, 725.0], [35.0, 720.0], [36.0, 714.0], [36.0, 850.0], [37.0, 1000.0], [38.5, 920.0], [38.5, 955.0], [39.5, 925.0], [41.0, 975.0], [41.0, 950.0], [9.8, 6.7], [10.5, 7.5], [10.6, 7.0], [11.0, 9.7], [11.2, 9.8], [11.3, 8.7], [11.8, 10.0], [11.8, 9.9], [12.0, 9.8], [12.2, 12.2], [12.4, 13.4], [13.0, 12.2], [14.3, 19.7], [15.0, 19.9]]

다음과 같은 결과가 나온다. 이는 fish_length와 fish_weigth를 각각 1개씩 대조시켜 데이터를 묶은 형태이다. 이 때 하나의 생선 데이터(ex [25.4,242.0])를 샘플이라고 부른다. 49개의 샘플 중 처음 35개를 훈련세트로, 나머지 14개를 테스트 세트로 사용하도록 하겠다.

> from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier()
print(fist_data[0:5])
#print(fish_data[:5])로도 같은 결과값을 얻을 수 있다.

이 때 주의할 점은, 마지막 인덱스의 원소는 포함되지 않는다. 위의 코드를 실행시키면 0~4까지의 원소만 선택되고 5번째 인덱스인 6번째 원소는 선택되지 않는다.

이제 직접 모델에 적용시켜 보자.

#훈련세트로 입력값 중 0~34번째의 인덱스 사용>
train_input = fish_data[:35]
#훈련세트로 타깃값 중 0~34번째의 인덱스 사용
train_target = fish_target[:35]
test_input = fish_data[35:]
test_target = fish_target[35:]
#데이터들로 훈련하기
kn = kn.fit(train_input, train_target)
#score 메서드를 호출해 평가해 보기
kn.score(test_input, test_target)

이러면 정확도가 0.0으로 출력되는데, 이유는 "샘플링 편향" 때문이다. 이런 샘플링 편향을 없애기 위해서는 샘플들을 적절히 섞어주는 과정이 필요하다.

샘플링 편향: 훈련세트와 데이터세트에 샘플이 골고루 섞여있지 않고 샘플링이 한쪽으로 치우친 것

import numpy as np
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. ][ 29.7 450. ]
[ 29.7 500. ][ 30. 390. ]
[ 30. 450. ][ 30.7 500. ]
[ 31. 475. ][ 31. 500. ]
[ 31.5 500. ][ 32. 340. ]
[ 32. 600. ][ 32. 600. ]
[ 33. 700. ][ 33. 700. ]
[ 33.5 610. ][ 33.5 650. ]
[ 34. 575. ][ 34. 685. ]
[ 34.5 620. ][ 35. 680. ]
[ 35. 700. ][ 35. 725. ]
[ 35. 720. ][ 36. 714. ]
[ 36. 850. ][ 37. 1000. ]
[ 38.5 920. ][ 38.5 955. ]
[ 39.5 925. ][ 41. 975. ]
[ 41. 950. ][ 9.8 6.7]
[ 10.5 7.5][ 10.6 7. ]
[ 11. 9.7][ 11.2 9.8]
[ 11.3 8.7][ 11.8 10. ]
[ 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]]

다음과 같은 형태로 출력이 된다.

numpy는 파이썬의 대표적인 배열 라이브러리이다. 고차원의 배열을 쉽게 만들고 조작할 수 있는 도구라고 생각하면 된다.

샘플링 편향을 없애기 위해 데이터들을 섞어 보자. 데이터들을 섞기 위해서는 기본적으로 데이터의 인덱스들을 먼저 섞어주어야 한다.

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]

위의 결과는 0~48까지의 인덱스를 적절하게 섞은 결과이다. 첫번째 인덱스가 13이라는 것은 기존 데이터 세트에서 13번째 인덱스(14번째의 값)를 말하는 것이다.

train_input = input_arr[index[:35]]
train_target = input_arr[index[:35]]
print(input_arr[13], train_input[0])

[ 32. 340.][ 32. 340.]

다음은 데이터 전처리에 대해 알아보겠다.

데이터 전처리란, 알고리즘이 거리 기반으로 데이터를 예측할 때, 데이터를 표현하는 기준이 다르면 올바르게 예측할 수 없다. 이런 알고리즘들은 샘플 간의 거리에 영향을 많이 받기 때문에 제대로 사용하기 위해서는 특성값을 일정한 기준으로 맞추어 주어야 하는데 이러한 작업을 "데이터 전처리" 라고 한다. 여기서 주의할 점은 모든 알고리즘은 거리 기반이 아니다. 그 예로 "트리기반 알고리즘" 이 있는데 이는 나중에 자세히 다뤄보도록 하겠다.

import numpy as np
np.column_stack(([1,2,3], [4,5,6]))
fish_data = np.column_stack((fish_length, fish_weight))

이는 fish_length와 fish_weight를 서로 연결시키는 과정이다. 다음은 타겟 데이터를 만들어 보자.

fish_target = np.concatenate((np.ones(35), np.zeros(14)))
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.]

여기서 np.ones와 np.zeros는 각각 1과 0의 배열들을 생성하는 함수이다. 앞서 사용했던 np.stack함수는 데이터들을 세로로 붙여주는 형식이었다면, np.concatenate는 데이터를 가로로 붙여주는 형태이다.

np.concatenate의 데이터 연결 형태

np.stack의 데이터 연결 형태

다음은 사이킷런으로 훈련 세트와 테스트 세트를 나눠보겠다.

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)

(36, 2) (13, 2)

print(train_target.shape, test_target.shape)

(36,) (13,)

print(test_target)

[1. 0. 0. 0. 1. 1. 1. 1. 1. 1. 1. 1. 1.]

13개의 테스트 세트 중에 도미(1)가 10개고, 빙어(0)가 3개인 것을 볼 수 있다. 원래 데이터에서의 도미와 빙어의 비율은 2.5:1 이었지만, 테스트 세트에서의 비율은 3.3:1이다. 샘플링 편향이 나타난 것을 볼 수 있다. 이렇게 무작위로 데이터를 나누면 샘플이 골고루 섞이지 않을 수 있다. 여기서 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)

[0. 0. 1. 0. 1. 0. 1. 1. 1. 1. 1. 1. 1.]

비율이 원래의 데이터와 비슷하게 구성되어있음을 확인할 수 있다. 그럼 이를 바탕으로 알고리즘을 통해 훈련해 보겠다.

from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier()
kn.fit(train_input, train_target)
kn.score(test_input, test_target)
print(kn.predict([[25, 150]]))

[0.]

도미크기의 데이터를 넣었지만, 알고리즘은 이를 빙어로 판단했다. 왜 그럴까?

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

이상했던 샘플은 마커를 바꾸고 보니, 표시된 점이 도미 데이터에 더 가까운 것을 볼 수 있다. 하지만 알고리즘은 이를 빙어로 판단했다. 이는 기준이 명확하지 않아서 발생한 문제이다. 그래프에서는 x축은 범위가 좁고, y축은 범위가 넓다. 그렇기에 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)

[ 27.29722222 454.09722222][ 9.98244253 323.29893931]

train_scaled = (train_input-mean)/std

위를 통해서 표준점수를 train_scaled로 변환했다.

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


이제 이 데이터셋으로 k-최근접이웃 모델을 다시 훈련해보자.

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

1.0

완벽하게 분류되는 것을 볼 수 있다. 그럼 25,150의 샘플을 한번 넣어보고 어떻게 판단하는지 알아보자.

print(kn.predict([new]))

[1.]

도미로 예측한 것을 볼 수 있다. 앞에서는 스케일이 달랐기 때문에 도미를 빙어라고 예측했지만, 이렇게 스케일을 맞춰주니 정상적으로 예측한다. 오늘은 여기서 마치겠다.

0개의 댓글