인공지능은 사람처럼 학습하고 추론할 수 있는 지능을 가진 시스템을 만드는 기술로
인공지능은 크게 강인공지능과 약인공지능으로 나눌 수 있다
강인공지능은 인공일반지능이라고도 하고 사람의 지능과 유사하다
(영화 속 전지전능한 AI)
약인공지능은 특정 분야에서 사람을 돕는 보조 AI이다
(음성 비서나 자율 주행도 여기에 포함)
머신러닝은 규칙을 프로그래밍하지 않아도 자동으로 데이터에서 규칙을 학습하는 프로그램
대표적인 라이브러리로 사이킷런이 있다
딥러닝은 인공 신경망이라고 하며, 머신러닝의 한 분야를 가리킨다
대표적인 라이브러리로 텐서플로와 파이토치가 있다
코랩 (Colab)은 웹 브라우저에서 텍스트와 프로그램 코드를 자유롭게
작성할 수 있는 온라인 에디터로 이를 코랩 노트북 또는 노트북이라고 부른다
최소 실행 단위는 셀이며 코드 셀과 텍스트 셀이 있다
정리하면 코랩은 웹 브라우저 기반의 파이썬 코드 실행환경이다
이제 본격적으로 코딩을 시작해보자
예시로 마트에서 생선을 분류한다고 가정해보자
보통은 기계에 규칙을 정해서 생선을 분류하도록 시킨다
ex) 30cm 이상은 도미이다
하지만 이렇게 정확한 기준을 정하기 어려울 때가 있다
이때 머신러닝을 사용하면 누구도 알려주지 않는 규칙을 찾아서 일을 한다
다시 말해서 누가 말해주지 않아도 "30cm~40cm 길이의 생선은 도미이다"를 알아낸다
머신러닝은 기준을 찾을 뿐만 아니라 그 기준을 이용해 도미인지 아닌지를 판별할 수 있다
그러면 바로 생선을 구분하러 가보자!!!
# 도미의 길이와 무게
bream_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]
bream_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]
이렇게 도미의 길이와 무게가 적혀있는 식이다
우리는 이렇게 길이와 무게와 같은 특징을 특성이라고 한다
특성의 데이터의 특징이다
이렇게 숫자로만 표현을 하면 잘 알아보기가 힘들다
그래서 그래프로 표현하면 쉽게 알 수 있는데
길이를 X축 높이를 Y축으로 하여 각 도미를 점으로 표현해봤다
이러한 그래프를 산점도라고 한다
import matplotlib.pyplot as plt # matplotlib의 pyplot 함수를 plt로 줄여서 사용
plt.scatter(bream_length,bream_weight)
plt.xlabel('length')
plt.ylabel('weight')
plt.show
생선의 길이가 길면 무게 많이 나간다고 생각을 하면 그래프의 모습은 자연스럽다
이렇게 그래프가 일적선의 형태로 나타나는 것을 선형적이라고 한다
이번에는 빙어의 데이터를 가지고 산점도를 그려보겠다
도미 데이터와 비교하기 쉽도록 도미 데이터가 그려져 있는 산점도에 추가를 해보자
# 빙어 데이터
smelt_length = [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]
smelt_weight = [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]
plt.scatter(bream_length, bream_weight)
plt.scatter(smelt_length, smelt_weight)
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
이렇게 보면 빙어도 길이에 비례해서 무게가 증가하지만
도미에 비해 길이와 무게가 작은 것을 알 수 있다
또한 빙어의 그래프모습도 선형적이지만 무게가 길이에 영향을 덜 받는 것도 확인할 수 있다
이제 머신러닝을 해보자
가장 간단한 머신러닝인 K-최근접 이웃(K-Nearest Neighbors)를 사용해서
빙어와 도미를 구분해보자
그러기 전에 우리는 빙어와 도미 데이터를 합쳐준다
그리고 나서 머신러닝 패키지로 사이킷런을 사용하게 될텐데
이 패키지를 사용하려면 특성의 리스트를
세로 방향으로 늘어뜨린 2차원 리스트를 만들어야 한다
이걸 쉽게 만드는 방법은 파이썬의 zip()
함수와 리스트 내포 구문을 사용하는 것이다
zip()
함수는 나열된 리스트 각각에서 하나의 원소를 꺼내 반환한다
length = bream_length + smelt_length
weight = bream_length + smelt_weight
fish_data = [[l,w] for l,w in zip(length,weight)]
fish_data
이러한 리스트를 2차원 리스트 혹은 리스트의 리스트라고 부른다
이제 생선 49개의 길이와 무게를 모두 준비했다 마지막으로 준비할 데이터는 정답 데이터다
머신러닝이 데이터에서 규칙을 찾기 위해서는 정답 데이터가 필요한데
만약 해당 데이터가 없으면 정답을 맞출수가 없다
그래서 우리는 도미를 1이라고 하고 빙어를 0이라고 표현하고
정답 리스트로 1이 35번 등장하고 0이 14번 등장하는 리스트를 만들어볼 것이다
fish_target = [1] * 35 + [0] * 14
fish_target
이제 사이킷런 패키지에서 k-최근접 이웃 알고리즘을 구현한 클래스인
KNeighborsClassifier를 임포트하고 임포트한 KNeighborsClassifier 클래스의 객체를 만든다
from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier()
그리고 fish_data
와 fish_target
전달하여 도미를 찾기 위한 기준을 학습시킨다
이러한 과정을 우리는 훈련이라고 한다
fit()
메서드는 주어진 데이터로 알고리즘을 훈련시킨다
kn.fit(fish_data,fish_target)
이제 객체(또는 모델)이 얼마나 잘 훈련되었는지를 평가해보자
사이킷런에서 모델을 평가하는 메서드는 score()
메서드 입니다
값으로는 0에서 1 사이의 값을 반환하는데
1은 모든 데이터를 정확히 맞혔다는 것을 나타내고 0.5는 절반 맞췄다는 의미이다
kn.score(fish_data,fish_target)
# 1.0
값이 1.0이 나왔다 이는 모든 fish_data의 답을 정확히 맞췄다는 뜻이다
우리는 이 값을 정확도라고 한다
정확도 = (정확히 맞힌 개수) / (전체 데이터 개수)
이번에 우리가 사용한 알고리즘은 k-최근접 이웃 알고리즘이다
해당 알고리즘은 데이터에 대한 답을 구할 때
주위의 다른 데이터를 보고 다수를 차지하는 것을 정답을 선택한다
실제로 예시를 들어보겠습니다
predict()
메서드는 새로운 데이터의 정답을 예측합니다
이 메서드도 아까의 fit()
메서드처럼 리스트의 리스트를 전달해야 한다
kn.predict([[30,600]])
# array([1])
반환되는 값을 보면 도미로 예상한 것을 알 수 있다
위의 산점도를 확인해보면 해당 크기의 점의 위치는 도미와 가깝다는 것을 볼 수 있다
이렇게 새로운 데이터에 대해 예측할 때는 가장 가까운 직선거리에
어떤 데이터가 있는지를 살피기만 하면 된다
그렇기에 단점은 데이터가 아주 많은 경우 사용하기가 어렵고
데이터가 크기 때문에 메모리가 많이 필요하고, 직선 거리를 계산하는데도 시간이 걸린다
좀 더 설명을 해보면 사이킷런의 KNeighborsClassifier 클래스는
_fit_X
속성에 fish_data
를 모두 가지고 있고
또 _y
속성에 fish_target
을 가지고 있다
사실상 k-최근접 이웃 알고리즘은 무언가 훈련되어 있다기 보다는
데이터를 모두 저장하고 있다가 새로운 데이터가 등장하면 가장 데이터를 구분하는 것이다
그러면 몇개의 데이터를 참고 할까? 기본값은 5인데
n_neighbors
매개변수로 바꿀 수 있다
kn49 = KNeighborsClassifier(n_neighbors=49) # 참고 데이터를 49개로 한 kn49 모델
kn49.fit(fish_data,fish_target)
kn49.score(fish_data,fish_target) # 0.7142857142857143
pirnt(35/49) # 0.7142857142857143 (49개중 도미가 35개인데 그것만 잘 맞춤)
가장 가까운 데이터 49개를 사용하면 모든 생선을 사용해서 예측하게 된다
그런데 35개가 도미이므로 어떤 데이터를 넣어도 도미를 예측하게 된다
용어 알아두기
이진 분류 -binary classification
머신러닝에서 여러 개의 종류(혹은 클래스) 중 하나를 구별해 내는 문제를 분류라고 하고
2개의 종류(클래스) 중 하나를 고르는 문제를 이진 분류라고 한다맷플롯립 -
matplotlib
파이썬에서 과학계산용 그래프를 그리는 대표 패키지
머신러닝 알고리즘은 크게 지도학습과 비지도학습으로 나눠지게 된다
지도 학습 알고리즘은 훈련하기 위한 데이터와 정답이 필요하다
지도 학습에서는 데이터와 정답을 입력(input)과 타겟(target)이라고 하고,
이 둘을 훈련(train data)라고 부른다
그리고 입력으로 사용된 길이와 무게 같은 것을 특성(feature)라고 한다
지도 학습은 정답이 있으니 알고리즘이 정답을 맞히는 것을 학습한다
예를 들면 도미인지 빙어인지 구분하는 것과 같은 작업을 수행한다
하지만 비지도 학습은 정답 없이 입력 데이터만을 사용한다
그렇기에 정답을 맞추기보다는 데이터를 잘 파악하거나 변형하는데 도움을 준다
우리가 배운 k-최근접 이웃 알고리즘은 지도 학습 알고리즘이다
그런데 우리가 생각을 해보자
우리가 데이터와 타킷을 주고 훈련한 다음,
같은 데이터로 테스트를 하면 모두 맞히는게 정상이다
만약 정확하게 테스트를 하고 싶다면 훈련데이터와 평가데이터가 달라야한다
그래서 일반적으로 이미 준비된 데이터에서 일부를 나눠
훈련에 사용하는 데이터를 훈련 세트와 평가에 사용하는 데이트를 평가 세트로 나눈다
그래서 1장에서 했던 평가는 훈련 데이터를 가지고 평가했기에 100%라고 평가했던 것이다
이번에는 훈련데이터에서 일부를 떼어 테스트 세트로 사용해보겠다
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
데이터를 다음과 같이 생성해줬다
이때 하나의 생선 데이터를 샘플이라고 부른다
총 49개의 데이터가 있으므로 49개의 샘플이 있다
그러면 이제 이 데이터의 처음 35개를 훈련세트로,
나머지 14개를 테스트 세트로 사용해보자
(사실 이러면 안되는데...)
from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier()
바로 모델 객체를 만들어주고, 이제 데이터에서 35개를 선택하여 학습을 시켜주려고 한다
일반적으로 리스트처럼 배열의 요소를 선택할 때는 배열의 위치
즉, 인덱스를 지정한다
파이썬 리스트는 인덱스 외에도 슬라이싱이라는 특별한 연산자를 제공한다
슬라이싱은 :
를 가운데에 두고 인덱스의 범위를 지정하여 여러 개의 원소를 선택할 수 있다
하지만 슬라이싱을 사용할 때에는 마지막 인덱스의 원소는 포함되지 않는다를 주의해야 한다
예를 들어 0:5
로 지정했다면 0~4까지의 5개 원소만 선택되고
인덱스 5인 여섯 번째 원소는 선택되지 않는다
또 0:5
같이 처음부터 시작되는 슬라이싱의 경우 0을 생략할 수도 있다
이와 비슷하게 마지막 원소까지 포함하는 경우 두 번째 인덱스를 생략할 수 있다
그러면 슬라이싱을 이용해서 훈련 데이터와 테스트 데이터로 나누는 작업을 해보겠다
# 훈련 세트로 입력값 중 0부터 34번째 인덱스 까지 사용
train_input = fish_data[:35]
# 훈련 세트로 타겟값 중 0부터 34번째 인덱스 까지 사용
train_target = fish_target[:35]
# 테스트 세트로 입력값 중 35부터 마지막 인덱스 까지 사용
test_input = fish_data[35:]
# 테스트 세트로 타겟값 중 35부터 마지막 인덱스 까지 사용
test_target = fish_target[35:]
kn.fit(train_input,train_target)
kn.score(test_input,test_target) # 0.0
데이터를 나눈 다음 나눈 데이터로 바로 훈련과 평가를 진행했다
하지만 정확도가 0.0이 나왔다
(사실 나는 그럴줄 알고 있었어 ^^)
그냥 간단하게 생각을 해보면 훈련 데이터에는 도미 데이터만 있었고,
테스트 데이터에는 빙어 데이터만 있었다
이렇게 훈련 데이터와 테스트 데이터에 샘플이 골고루 섞여 있지 않으면
샘플링이 한쪽으로 치우쳤다는 의미로 샘플랑 편향이라고 부른다
우리는 훈련 데이터와 테스트 데이터를 나누려면 골고루 섞이게 만들었어야 했다
하지만 이런 작업을 간편하게 처리할 수 있도록 도와주는 파이썬 라이브러리가 있다
바로 넘파이
넘파이(numpy)는 파이썬의 대표적인 배열 라이브러리다
앞에서 파이썬의 리스트로 2차원을 표현할 수 있지만
고차원 리스트를 표현하려면 매우 번거롭다
하지만 넘파이를 사용하면 고차원의 배열을 손쉽게 만들고
조작할 수 있는 간편한 도구를 많이 제공한다
보통의 xy 좌표계와 달리 시작점이 왼쪽 아래가 아니라
왼쪽 위부터 시작한다 배열의 시작점을 이렇게 놓으면 편리한 점이 많다
그러면 생선 데이터를 2차원 넘파이로 만들어보자
바꾸는 방법은 간단한다 넘파이 array()
함수에 파이썬 리스트를 전달하면 끝이다
import numpy as np
input_arr = np.array(fish_data)
target_arr = np.array(fish_target)
print(input_arr)
넘파이는 친절하게 배열의 차원을 구분하기 쉽도록 행과 열을 가지런히 출력을 한다
출력 결과에서 49개의 행과 2개의 열을 쉽게 확인할 수 있다
눈으로 확인하는 것 외에도 넘파이 배열 객체는
배열의 크기를 알려주는 shape 속성을 제공한다
print(input_arr.shape) # (49,2) 이 명령을 사용하면 (샘플 수, 특성 수)를 출력한다
이제 생선 데이터를 넘파이 배열로 준비했으니
이 배열에서 랜덤하게 샘플을 선택해 훈련 데이터와 테스트 데이터로 만들 차례이다
이번에는 배열을 섞은 후에 나누는 방식 대신 무작위로 샘플을 고르는 방법을 사용해볼 것이다
여기서 주의해야하는 점은 input_arr
과 target_arr
에서 같은 위치는
함께 선택되어야 한다는 점이이다
그래서 인덱스를 섞은 다음은 input_arr
과 target_arr
에서 샘플을 선택하면 된다
넘파이에서 arange()
함수를 사용하면 0에서부터 48까지
1씩 증가하는 인덱스를 간단히 만들 수 있다
그 다음에 이 인덱스를 랜덤하게 섞는다
넘파이에서 무작위 결과를 만드는 함수들은 실행할 때마다 다른 결과를 만든다
일정한 결과를 얻으려면 랜덤시드 지정하면 된다
np.random.seed(42)
index = np.arange(49)
np.random.shuffle(index)
print(index)
넘파이 arrange()
함수에 정수 N을 전달하면 0에서부터 N-1까지 1씩 증가하는 배열을 만든다
또 넘파이 random 패키지 아래에 있는 shuffle()
함수는 주어진 배열을 무작위로 섞는다
이제 이를 이용해서 전체 데이터를 나눠볼 것이다
넘파이는 슬라이싱 외에 배열 인덱싱이란 기능을 제공한다
배열 인덱싱은 1개의 인덱스가 아닌 여러 개의 인덱스로
한 번에 여러개의 원소를 선택할 수 있다
print(input_arr[[1,3]])
비슷한 방식으로 리스트 대신 넘파이 배열을 이용해 인덱스를 전달할 수 있다
앞서 만든 index 배열의 처음 35개를 input_arr
와 target_arr
에 전달하여
랜덤하게 35개의 샘플을 훈련 세트로 만들었다
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(train_input[:,0], train_input[:,1])
plt.scatter(test_input[:,0],test_input[:,1])
plt.xlabel('length')
plt.ylabel('weight')
plt.show
파란색이 훈련 데이터이고, 주황색이 테스트 데이터이다
양쪽에 빙어와 도미가 모두 섞여 있다
이제 다시 훈련시켜보자
참고로 fit()
메서드로 실행시킬 때마다 KNeighborsClassifier 클래스의 객체는
이전에 학습한 모든 것을 잃어버린다 이전 모델을 그대로 두고 싶다면
KNeighborsClassifier 클래스 객체를 새로 만들어야 한다
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])
test_target # array([0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0])
결과를 확인해보면 정확도는 100%이고
테스트 데이터에 대한 예측 결과가 정답과 일치한다
predict()
메서드의 출력 결과가 test_target
의 출력과 동일하게
array()
로 감싸져 있다 이를 통해 predict()
메서드가 반환하는 값은
단순한 파이썬 리스트가 아니라 넘파이 배열이다
사실 사이킷런 모델의 입력과 출력은 모두 넘파이 배열이다
테스트 세트는 보통 전체 데이터에서 20~30%를 사용한다
데이터가 아주 많다면 1%만 덜어내도 충분하다numpy
arange()
는 일정한 간격의 정수 또는 실수 배열을 만든다
기본가격은 1이다 매개변수가 1개면 종료 숫자를 의미한다
0에서부터 종료 숫자까지의 배열을 만든다 종료 숫자는 배열에 포함하지 않는다
매개변수가 2개면 시작 숫자, 종료 숫자를 의미하고
매개변수가 3개면 시작 숫자, 종료 숫자, 간격을 의미한다
shuffle()
는 주어진 배열을 랜덤하게 섞는다
다차원의 배열일 경우 첫 번째 축(행)에 대해서만 섞는다
이번에는 넘파이로 데이터를 준비해보자
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
np.column_stack(([1,2,3],[4,5,6]))
전에는 파이썬 리스트를 순회하면서 원소를 하나씩 꺼내 생선 하나의 길이와 무게를
리스트 안의 리스트로 구성했다 하지만 넘파이를 사용하면 쉽게 만들 수 있다
바로 넘파이의 column_stack
함수인데
해당 함수는 전달받은 리스트를 일렬로 세운 다음 차례대로 나란히 연결한다
그리고 연결한 리스트는 파이썬 튜플로 전달한다
np.column_stack(([1,2,3],[4,5,6])) # 3개의 행, 2개의 열
파이썬 튜플은 리스트와 매우 비슷하다
리스트처럼 원소에 순서가 있지만 다른점으로는 한 번 만들어진 튜플은 수정할 수 없다튜플을 사용하면 함수로 전달한 값이 바뀌지 않는다는 것을
믿을 수 있기 때문에 매개변수 값으로 많이 사용한다
fish_data = np.column_stack((fish_length, fish_weight))
print(fish_data[:5])
이번에는 타겟데이터를 만들어보자
이전에는 원소가 하나인 리스트 [1]
,[0]
을 여러 번 곱해서 타깃 데이터를 만들었다
허지만 더 쉽게 구하는 방법이다
바로 np.ones()
와 np.zeros()
함수이다
이 두 함수는 각각 원하는 개수의 1과 0을 채운 배열을 만들어준다
print(np.ones(5)) # [1,1,1,1,1]
그러면 이제 본격적으로 이를 이용하여 1이 35개인 배열, 0이 14개인 배열을 만들어 볼 것이다
그리고 배열을 연결하는 함수 np.concatenate()
를 사용할 것이다
이 역시도 np.column_stack()
함수처럼 연결한 리스트나 배열을 튜플로 전달해야 한다
fish_target = np.concatenate((np.ones(35),np.zeros(14)))
print(fish_target)
이번에는 파이썬 리스트를 사용해서 수동으로 만들지 않고 넘파이 함수를 사용했다
데이터 작으면 파이썬 리스트로 만들거나 넘파일 배열로 만드는 것이나 차이가 없지만
데이터가 클수록 파이썬 리스트는 비효율적이기에 넘파이 배열을 사용하는게 좋다
우리가 과거에는 배열의 인덱스를 직접 섞어서 훈련데이터와 테스트 데이터를 만들었다
하지만 이런 방법은 많이 번거롭다
그런 사람들을 위해서 사이킷런에서는 train_test_split()
이라는 함수를 제공한다
이 함수는 전달되는 리스트나 배열을 비율에 맞게 훈련 데이터와 테스트 데이터로 나눠준다
물론 나누기 전에 알아서 섞어준다
해당 함수는 사이킷런의 model_selection 모듈 아래 있다
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)
사용법은 나누고 싶은 리스트나 배열을 원하는 만큼 전달하면 된다
해당 코드를 보면 fish_data와 fish_target 2개의 배열을 전달했으므로
2개씩 나뉘어 총 4개의 배열이 반환된다
차례대로 처음 2개는 입력 데이터 (train_input, test_input)
나머지 2개는 타깃 데이터 (train_target, test_target)
참고로 일정한 값을 유지하기 위해서 랜덤시드(random_state
)를 설정할 수 있다
이 함수는 기본적으로 25%를 테스트 데이터로 떼어낸다
print(train_input.shape, test_input.shape)
print(train_target.shape, test_target.shape)
# (36, 2) (13, 2)
# (36,) (13,)
# 넘파이 배열의 크기는 파이썬의 튜플로 표현이 된다
# 튜플의 원소가 하나면 원소 뒤에 콤마를 추가한다
# 예시를 보면 타겟데이터는 1차원 배열이므로 원소가 하나인 튜플로 표현된다
도미와 빙어가 잘 섞였는지 확인을 해보자
print(test_target) # [1. 0. 0. 0. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
13개의 테스트 데이터 중 10개가 도미이고 3개가 빙어이다
잘 섞인 것 같지만 빙어의 비율이 부족하다
원래 도미가 35마리이고 빙어가 14마리이므로 두 생선의 비율은 2.5 : 1 정도이다
하지만 이 테스트 데이터는 3.3 : 1
아까 말했던 샘플링 편향이 여기에도 조금 나타나고 있다
이처럼 무작위로 데이터를 나누었을 때 샘플이 골고루 안 섞일 수가 있다
특히 일부 클래스의 개수가 적을 때 이런 일이 생길 수 있다
그래서 우리는 훈련 데이터와 샘플의 비율이 일정하게 만들어서
모델이 샘플을 바르게 학습할 수 있게 해야한다
train_test_split()
함수는 이런 문제를 간단히 해결할 방법이 있다
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.]
사용하니 테스트 데이터의 비율이 2.25 : 1 이 되었다
데이터가 적어서 완벽하게 맞출 수는 없지만 꽤 비슷한 비율이다
이제 데이터가 준비되었으니 바로 훈련을 시켜보자
from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier()
kn.fit(train_input, train_target)
kn.score(test_input, test_target) # 1.0
테스트 데이터의 도미와 빙어를 모두 올바르게 구분을 했다
그런데 여기 25cm의 150kg인 도미가 있다 그래서 모델에서 넣어서 구분을 시켜봤다
print(kn.predict([[25,150]])) # [0.]
예상 다르게 빙어인 0을 출력했다
그래서 다른 데이터와 산점도로 그려봤다
import matplotlib.pyplot as plt
plt.scatter(train_input[:,0],train_input[:,1])
plt.scatter(25,150, marker='^') # marker 매겨변수는 모양을 지정
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
새로운 데이터는 marker 매개변수로 지정하여 삼각형으로 나타냈다
이렇게 하면 구분하기 더 쉽다
데이터를 확인해보면 해당 데이터는 도미 데이터에 더 가깝지만
k-최근접 이웃 모델 특성상 주변의 샘플 중에서 다수인 클래스를
예측으로 사용하기에 빙어라고 출력된 것을 보인다
좀 더 구체적으로 알아보자 KNeighborsClassifier 클래스는
주어진 샘플에서 가장 가까운 이웃을 찾아주는 kneighbors()
메서드를 제공한다
이 메서더는 이웃까지의 거리와 이웃 샘플의 인덱스를 반환한다
기본값이 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')
plt.show()
marker ='D'
로 지정하면 산점도를 마름모로 그려준다
삼각형 샘플에 가장 가까운 5개의 샘플이 초록 다이아몬드로 표시되었다
print(train_input[indexes])
print(train_target[indexes])
확실하게 해당 생선에 가장 가까운 이웃에는 빙어가 앞도적으로 많았다
그래서 해당 클래스를 빙어라고 예측한 것이다
print(distances) # [[ 92.00086956 130.48375378 130.73859415 138.32150953 138.39320793]]
이 배열에는 이웃 샘플까지의 거리가 담겨져 있는데 이상한 점이 있다
가장 가까운 샘플과의 거리는 92인데 그 다음은 130, 138이다
그래프를 보면 비율이 맞아보이지 않는다
(가장 가까운 샘플의 거리를 92라고 했을 때
나머지 샘플과의 거리와 거리 비율이 맞지 않아 보인다)
바로 그 이유는 X축은 간격이 짧고 Y축은 간격이 엄청 길다
그래서 Y축으로 조금만 멀어져도 거리가 아주 큰 값으로 계산이 되고 있다
이 때문에 오른쪽 위의 도미 샘플이 이웃으로 선택되지 못했다
그러면 눈으로 명확히 확인 위해서 x축 범위를 동일하게 0~1000으로 맞추어보자
맷플롯립에서 x축 범위를 지정하려면 xlim()
함수를 사용한다
(비슷하게 y축 범위를 지정하려면 ylim()
함수를 사용한다)
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()
산점도가 거의 일직선으로 나타나고 있다 이런 데이터는 생선의 길이(x축)는
가장 가까운 이웃을 찾는데 크게 영향을 미치지 않는다
오직 생선의 무게 (y축)만 고려 대상이 된다
이렇게 두 특성의 값이 놓인 범위가 매우 다르다는 것을 우리는 스케일이 다르다고 말한다
데이터를 표현하는 기준이 다르면 알고리즘이 올바르게 예측할 수 없다
특히 알고리즘이 거리 기반일 경우에는 더 심하다
그래서 제대로 사용하려면 특성값을 일정한 기준으로 맞춰 주어야 한다
이런 작업을 데이터 전처리라고 한다
이때 가장 널리 사용하는 전처리 방법 중 하나는 표준 점수 (z점수)이다
표준점수는 각 특성값이 평균에서 표준편차의 몇 배만큼 떨어져 있는지를 나타낸다
이를 통해 실제 특성값의 크기와 상관없이 동일한 조건으로 비교할 수 있다
계산하는 방법은 간단하다 평균을 빼고 표준편차로 나눠주면 된다
mean = np.mean(train_input, axis=0)
std = np.std(train_input, axis=0)
넘파이에서는 mean()
을 통해서 평균을 구할 수 있고,
std()
를 통해서 표준편차를 구할 수 있다
train_input은 (36,2)
크기의 배열이다
특성마다 값의 스케일이 다르므로 평균과 표준편차는 각 특성별로 계산해야 한다
이를 위해서 axis=0
을 지정했다 이렇게 하면 열 단위로 통계값을 계산한다
(axis=1
은 행 단위)
print(mean,std) # [ 27.29722222 454.09722222] [ 9.98244253 323.29893931]
각 특성마다의 평균과 표준편차가 주어졌다
이제 원본 데이터에서 평균을 빼고 표준편차로 나누어 표준점수로 변환해보겠습니다
train_scaled = (train_input - mean) / std
이 식은 trian_input의 모든 행에서 mean에 있는 두 평균값을 빼준다
그리고 std에 있는 두 표준편차로 다시 모든 행에 적용해준다
이러한 넘파이 기능을 브로드캐스팅이라고 부른다
브로드캐스팅은 넘파이 배열 사이에서 일어난다
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()
전에 그래프와 크게 달라진 점은
x축과 y축의 범위가 -1.5~1.5 사이로 바뀌었다는 점이다
훈련 데이터의 두 특성이 비슷한 범위를 차지하고 있다
이제 이 데이터셋으로 다시 k-최근접 이웃 모델을 훈련시켜보겠다
kn.fit(train_scaled,train_target)
# 테스트 데이터도 훈련 데이터의 평균과 표준편차로 변환해 한다
test_scaled = (test_input - mean) / std
kn.score(test_scaled, test_target) # 1.0
그러면 이제 아까 그 문제의 물고기도 다시 한 번 넣어보자
print(kn.predict([new])) # [1.]
드디어 도미(1)로 잘 예측하는 것을 확인할 수 있다
distance, 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
이렇게 특성을 표준점수로 바꾸었기에
k-최근접 이웃 알고리즘이 올바르게 거리를 측정할 수 있었다
정리해보자
데이터 전처리는 머신러닝 모델에 훈련 데이털르 주입하기전에 데이터를 가공하는 단계를 이야기한다 때로는 이 데이터 전처리에 많은 시간이 소모되기도 한다표준점수는 훈련 데이터의 스케일을 바꾸는 대표적인 방법 중 하나이다
표준점수를 얻으려면 특성의 평균을 빼고 표준편차로 나누면 된다
그리고 반드시 훈련 데이터의 평균과 표준편차로 테스트 데이터도 바꿔줘야 한다브로드캐스팅은 크기가 다른 넘파이 배열에서 자동으로 사칙 연산을
모든 행이나 열로 확장하여 수행하는 기능을 말한다
그래요 이제 진도를 다 나갔으니 숙제를 해봅시다
1번 문제
머신러닝 알고리즘의 한 종류로서 샘플의 입력과 타깃(정답)을 알고 있을 때
사용할 수 있는 학습 지도방법은 무엇인가요?
① 지도 학습
② 비지도 학습
③ 차원 축소
④ 강화 학습
정답 : ① 지도 학습
지도 학습은 데이터의 입력과 정답 데이터를 같이줘서 학습한다
그래서 모델이 다른 데이터를 받았을 때 정답을 맞히도록 한다
2번 문제
훈련 세트와 테스트 세트가 잘못 만들어져
전체 데이터를 대표하지 못하는 현상을 무엇이라고 부르나요?
① 샘플링 오류
② 샘플링 실수
③ 샘플링 편차
④ 샘플링 편향
정답 : ④ 샘플링 편향
훈련 데이터와 테스트 데이터에서 한 쪽의 데이터가 더 많은 경우에
그 쪽 데이터로 편향이 일어날 수 있다 (이런 경우 성능저하가 일어난다)
이런 현상을 샘플링 편향이라고 한다
그렇기에 원래 데이터와 비슷한 비율을 유지해줘야 한다
3번 문제
사이킷런은 입력 데이터(배열)가 어떻게 구성되어 있을 것으로 기대하나요?
① 행 : 특성, 열 : 샘플
② 행 : 샘플, 열 : 특성
③ 행 : 특성, 열 : 타깃
④ 행 : 타깃, 열 : 특성
정답 : ② 행 : 샘플, 열 : 특성
사이킷런에서 입력 데이터는 행이 샘플(데이터) 열은 각 특성을 뜻한다