혼자 공부하는 머신 러닝 + 딥러닝
생선 분류 문제
- 한빛 마켓에서 팔기 시작한 생선은 ‘도미’, 곤들매기’, ‘농어’, ‘강꼬치고기’, ‘로치’, ‘빙어’, ‘송어’이다.
- 이 생선들을 프로그램으로 분류한다고 가정해 보자. 어떻게 프로그램을 만들어야 할까.
도미 데이터 준비하기
- 우선 생선의 특징을 알기 쉽게 구분해보자.
- 예를들어 30cm이상인 생선은 도미라고 한다.
1 2 3 4 5 6
| 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]
|
위 리스트에서 첫 번째 도미의 길이는 25.4cm, 무게는 242.0g이다.
각 도미의 특징을 길이와 무게로 표현했음을 알 수 있다.
책에서는 이런 특징을 특성(feature)이라 부른다.
길이인 bream_length를 x축으로 삼는다.
무게인 bream_weight를 y축으로 한다.
이를 토대로 각 도미를 그래프에 점으로 표시해 보자.
- 이런 그래프를 산점도(scatter plot)라 부른다.
1 2 3 4 5 6
| import matplotlib.pyplot as plt
plt.scatter(bream_length, bream_weight) plt.xlabel('length') plt.ylabel('weight') plt.show()
|
- 도미 35마리를 2차원 그래프에 점으로 나타냈다.
- 생선의 길이가 길수록 무게가 많이 나간다고 생각하면 이 그래프는 매우 자연스럽다.
- 이렇게 산점도 그래프가 일직선에 가까운 형태로 나타나는 경우를 선형적(linear)이라고 한다.
빙어 데이터 준비하기
1 2
| 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]
|
- 빙어는 도미에 비해 크기도 작고 무게도 가볍다.
- 빙어의 데이터도 산점도로 그려보자.
- 하는 김에 도미와 빙어의 산점도를 비교한다.
1 2 3 4 5
| plt.scatter(bream_length, bream_weight) plt.scatter(smelt_length, smelt_weight) plt.xlabel('length') plt.ylabel('weight') plt.show()
|
- Matplotlib에 의해 두 산점도 색깔이 다르게 출력된다.
- 주황색 점이 빙어의 산점도이며 도미에 비해 길이도 무게도 작다.
- 또한 빙어는 도미와 달리 길이가 늘어나도 무게가 크게 늘지 않는다는 것을 알 수 있다.
첫 번째 머신러닝 프로그램
- 가장 간단하고 이해하기 쉬운 k-최근접 이웃(k-Nearest Neighbors) 알고리즘을 사용해 도미와 비엉 데이터를 구분해본다.
- 알고리즘 사용 이전에 앞서 준비했던 두 생선의 데이터를 합친다.
1 2
| length = bream_length + smelt_length weight = bream_weight + smelt_weight
|
- 책에서 사용하는 머신러닝 패키지는 사이컷런(scikit-learn)이다.
- 이 패키지를 이용하여 각 특성의 리스트를 세로 방향으로 늘어뜨린 2차원 리스트를 만들어야 한다.
- 이렇게 만들기 위해서 파이썬의 zip() 함수와 리스트 내포(list comprehension)구문을 사용한다.
- zip() 함수는 나열된 리스트 각각에서 하나씩 원소를 꺼내 반환한다.
- zip() 함수와 리스트 내포 구문을 사용해 length와 weight 리스트를 2차원 리스트로 만들어보자.
1
| fish_data = [[l, w] for l, w in zip(length, weight)]
|
- for문은 zip() 함수로 length와 weight 리스트에서 원소를 하나씩 꺼내어 l과 w에 할당한다.
- 그러면 [l, w] 가 하나의 원소로 구성된 리스트가 만들어진다.
[[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]]
1 2
| fish_target = [1] * 35 + [0] * 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]
- 이제 사이킷런 패키지에서 k-최근접 이웃 알고리즘을 구현한 클래스인 KNeighborsClassifier를 임포트한다.
1
| from sklearn.neighbors import KNeighborsClassifier
|
- 임포트한 KNeighborsClassfier 클래스의 객체를 먼저 만든다.
1
| kn = KNeighborsClassifier()
|
- 훈련
- 이 객체에 fish_data와 fish_target을 전달하여 도미를 찾기 위한 기준을 학습시킨다.
- 이런 과정을 머신러닝에서는 훈련(training)이라 부른다.
- 사이킷런에서는 fit() 메서드가 이런 역할을 한다.
- 이 메서드에 fish_data 와 fish_target 을 순서대로 전달해보자.
1
| kn.fit(fish_data, fish_target)
|
KNeighborsClassifier()
- 평가
- fit() 메서드는 주어진 데이터로 알고리즘을 훈련한다.
- 이제 객체(또는 모델) kn이 얼마나 잘 훈련되었는지 평가해봐야 한다.
- 사이킷런에서 모델을 평가하는 메서드는 score()메서드이다.
- 이 메서드는 0에서 1 사이의 값을 반환한다.
- 1은 모든 데이터를 정확히 맞혔다는 것을 나타낸다.
- 예를 들어 0.5라면 절반만 맞혔다는 의미이다.
1
| kn.score(fish_data, fish_target)
|
1.0
- 정확도
- 1.0 이 출력되었다.
- 모든 fish_data의 답을 정확히 맞혔다는 뜻이 된다.
- 이러한 값을 정확도(accuracy)라고 부른다.
k-최근접 이웃 알고리즘
앞에서 첫 번째 머신러닝 프로그램을 성공적으로 만들었다.
여기에서 사용한 알고리즘은 k-최근접 이웃이다.
이 알고리즘은 어떤 데이터에 대한 답을 구할 때 주위의 다른 데이터를 보고 다수를 차지하는 것을 정답으로 사용하다.
- 마치 근묵자흑과 같이 주위의 데이터로 현재 데이터를 판단하는 것이다.
예를 들어, 이전에 출력한 산점도에서 삼각형으로 표시된 새로운 데이터가 있다고 가정해 보자.
이 삼각형은 도미와 빙어 중 어디에 속할까?
이 삼각형이 도미의 데이터 부근에 위치해 있다면 도미라고 판단할 것이다.
k-최급접 이웃 알고리즘도 마찬가지이다.
실제 코드로도 그런지 한 번 확인해 보자.
array([1])
predict() 메서드
- predict() 메서드는 새로운 데이터의 정답을 예측한다.
- 이 메서드도 앞서 fit() 메소드와 마찬가지로 2차원 리스트를 전달해야 한다.
- 그래서 삼각형 포인트를 리스트로 2번 감싼것이다.
- 반환되는 값은 1.
- 우리는 앞서 도미는 1, 빙어는 0으로 가정했다.
- 즉, 삼각형은 도미이다.
k-최근접 이웃 알고리즘을 위해 준비해야 하는 것은 데이터를 모두 가지고 있는 것 뿐이다.
새로운 데이터에 대해 예측할 때는 가장 가까운 직선거리에 어떤 데이터가 있는지를 살피기만 하면 된다.
단점으로는 이런 특징 때문에 데이터가 아주 많은 경우 사용하기 어렵다는 점이 있다.
사이킷런의 KNeighborsClassifier 클래스도 마찬가지이다.
이 클래스는 _fit_X 속성에 우리가 전달한 fish_data를 모두 가지고 있다.
또 _y 속성에 fish_target을 가지고 있다.
[[ 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]]
[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]
- 실제로 k-최근접 이웃 알고리즘은 무언가 훈련되는 게 없는 셈이다.
- fit()메서드에 전달한 데이터를 모두 저장하고 있다가 새로운 데이터가 등장하면 가장 가까운 데이터를 참고하여 어떤 생선인지 구분한다.
- 그럼 가까운 몇 개의 데이터를 참고할까?
- 이는 정하기 나름이다.
- KNeighborsClassifier 클래스의 기본값은 5이다.
- 이 기준은 n_neighbors 매개변수로 바꿀 수 있다.
- 예를 들어, 다음 코드 실행 시 어떤 결과가 나올까?
1
| kn49 = KNeighborsClassifier(n_neighbors=49)
|
- 가장 가까운 데이터 49개를 사용하는 k-최근접 이웃 모델에 fish_data를 적용하면 fish_data에 있는 모든 생선을 사용하여 예측하게 된다.
- 다시 말하면 fish_data의 데이터 49개 중에 도미가 35개로 다수를 차지하므로 어떤 데이터를 넣어도 무조건 도미로 예측할 것이다.
1 2
| kn49.fit(fish_data, fish_target) kn49.score(fish_data, fish_target)
|
0.7142857142857143
- fish_data에 있는 생선 중에 도미가 35개이고 빙어가 14개이다.
- kn49모델은 도미만 올바르게 맞히기 때운에 다음과 같이 정확도를 계산하면 score() 메서드와 같은 값을 얻을 수 있다.
0.7142857142857143
- 확실히 n_neighbors 매개변수를 49로 두는 것은 좋지 않다.
- 기본 값을 5로 하여 도미를 완벽하게 분류한 모델을 사용하기로 한다.
도미와 빙어 분류 (중간 정리)
- 지금까지 도미와 빙어를 구분하기 위해 첫 머신러닝 프로그램을 만들었다.
- 먼저 도미 35마리와 빙어 14마리의 길이와 무게를 측정해서 파이썬 리스트로 만든다.
- 그 다음 도미와 빙어 데이터를 합친 2차원 리스트를 준비했다.
- 사용한 머신러닝 알고리즘은 k-최근접 이웃 알고리즘이다.
- 사이킷런의 k-최근접 이웃 알고리즘은 주변에서 가장 가까운 5개의 데이터를 보고 다수결의 원칙에 따라 데이터를 예측한다.
- 이 모델은 준비된 데이터를 모두 맞혔다.
- 도미와 빙어를 분류하는 문제를 풀면서 KNeighborsClassifier 클래스의 fit(), score(), predict() 메서드를 사용해 보았다.
- 끝으로 k-최근접 이웃 알고리즘의 특징을 알아보았다.
전체 소스 코드
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
|
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]
import matplotlib.pyplot as 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()
length = bream_length + smelt_length weight = bream_weight + smelt_weight
fish_data = [[l, w] for l, w in zip(length, weight)]
print(fish_data)
fish_target = [1] * 35 + [0] * 14 print(fish_target)
from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier()
kn.fit(fish_data, fish_target)
kn.score(fish_data, fish_target)
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()
kn.predict([[30, 600]])
print(kn._fit_X)
print(kn._y)
kn49 = KNeighborsClassifier(n_neighbors=49)
kn49.fit(fish_data, fish_target) kn49.score(fish_data, fish_target)
print(35/49)
|
[[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]]
[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]
[[ 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]]
[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]
0.7142857142857143
마무리
키워드로 끝내는 핵심 포인트
특성 : 데이터를 표현하는 하나의 성질.
- 이 절에서 생선 데이터 각각을 길이와 무게 특성으로 나타냈다.
훈련 : 머신러닝 알고리즘이 데이터에서 규칙을 찾는 과정.
- 사이킷런에서는 fit() 메서드가 하는 역할이다.
k-최근접 이웃 알고리즘 : 가장 간단한 머신러닝 알고리즘 중 하나.
- 사실 어떤 규칙을 찾기보다는 전체 데이터를 메모리에 가지고 있는 것이 전부이다.
모델 : 머신러닝 프로그램에서는 알고리즘이 구현된 객체를 모델이라 부른다.
- 종종 알고리즘 자체를 모델이라고 부르기도 한다.
정확도 : 정확한 답을 몇 개 맞혔는지를 백분율로 나타낸 값이다.
- 사이킷런에서는 0~1 사이의 값으로 출력된다.
- 정확도 = (정확히 맞힌 개수) / (전체 데이터 개수)
핵심 패키지와 함수
matplotlib
- scatter()는 산점도를 그리는 Matplotlib 함수이다.
- 처음 2개의 배개변수로 x축과 y축 값을 전달한다.
- 이 값은 파이썬 리스트 또는 넘파이 배열이다.
- c 매개변수로 색깔을 지정한다.
scikit-learn
- KneighborsClassifier()는 k-최근접 이웃 분류 모델을 만드는 사이킷런 클래스이다.
- fit()은 사이킷런 모델을 훈련할 때 사용하는 메서드이다.
- 처음 두 매개변수로 훈련에 사용할 특성과 정답 데이터를 전달한다.
- predict()는 사이킷런 모델을 훈련하고 예측할 때 사용하는 메서드이다.
- score()는 훈련된 사이킷런 모델의 성능을 측정한다.
- 처음 두 매개변수로 특성과 정답 데이터를 전달한다.
- 이 매서드는 먼저 predict() 메서드로 예측을 수행한 다음 분류 모델일 경우 정답과 비교해 맞게 예측한 개수의 비율을 반환한다.
확인 문제
- 데이터를 표현하는 하나의 성질로써, 예를 들어 국가 데이터의 경우 인구 수, GDP, 면적 등이 하나의 국가를 나타냅니다. 머신러닝에서 이런 성질을 무엇이라 부르나요?
- 가장 가까운 이웃을 참고하여 정답을 예측하는 알고리즘이 구현된 사이킷런 클래스는 무엇인가요?
- SGDClassifier
- LinearRegression
- RandomForestClassifier
- KNeighborsClassifier v
- 사이킷런 모델을 훈련할 때 사용하는 메서드는 어떤 것인가요?