chapter1_3
혼자 공부하는 머신 러닝 + 딥러닝
생선 분류 문제
- 한빛 마켓에서 팔기 시작한 생선은 ‘도미’, 곤들매기’, ‘농어’, ‘강꼬치고기’, ‘로치’, ‘빙어’, ‘송어’이다.
- 이 생선들을 프로그램으로 분류한다고 가정해 보자. 어떻게 프로그램을 만들어야 할까.
도미 데이터 준비하기
- 우선 생선의 특징을 알기 쉽게 구분해보자.
- 예를들어 30cm이상인 생선은 도미라고 한다.
1 | #if fish_length >= 30: |
- 하지만 이는 절대적인 기준이 될 수 없다.
- 기준을 제대로 파악하기 위한 과정을 수행해보자.
- 데이터는 다음 링크를 참고하라.: https://gist.github.com/rickiepark/b37d04a95a42ef6757e4a99214d61697
- 다음 코드는 35마리의 도미의 길이와 생선의 무게에 대한 데이터이다.
1 | 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, |
위 리스트에서 첫 번째 도미의 길이는 25.4cm, 무게는 242.0g이다.
각 도미의 특징을 길이와 무게로 표현했음을 알 수 있다.
책에서는 이런 특징을 특성(feature)이라 부른다.
길이인 bream_length를 x축으로 삼는다.
무게인 bream_weight를 y축으로 한다.
이를 토대로 각 도미를 그래프에 점으로 표시해 보자.
- 이런 그래프를 산점도(scatter plot)라 부른다.
1 | import matplotlib.pyplot as plt # matplotlib의 pyplot 함수를 plt로 줄여서 사용 |
- 도미 35마리를 2차원 그래프에 점으로 나타냈다.
- 생선의 길이가 길수록 무게가 많이 나간다고 생각하면 이 그래프는 매우 자연스럽다.
- 이렇게 산점도 그래프가 일직선에 가까운 형태로 나타나는 경우를 선형적(linear)이라고 한다.
빙어 데이터 준비하기
- 이번엔 빙어의 데이터를 준비해 보자.
- 데이터는 다음 링크를 참고하라 : https://gist.github.com/rickiepark/1e89fe2a9d4ad92bc9f073163c9a37a7
- 다음 코드는 14 마리의 빙어에 대한 데이터이다.
1 | 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] |
- 빙어는 도미에 비해 크기도 작고 무게도 가볍다.
- 빙어의 데이터도 산점도로 그려보자.
- 하는 김에 도미와 빙어의 산점도를 비교한다.
1 | plt.scatter(bream_length, bream_weight) |
- Matplotlib에 의해 두 산점도 색깔이 다르게 출력된다.
- 주황색 점이 빙어의 산점도이며 도미에 비해 길이도 무게도 작다.
- 또한 빙어는 도미와 달리 길이가 늘어나도 무게가 크게 늘지 않는다는 것을 알 수 있다.
첫 번째 머신러닝 프로그램
- 가장 간단하고 이해하기 쉬운 k-최근접 이웃(k-Nearest Neighbors) 알고리즘을 사용해 도미와 비엉 데이터를 구분해본다.
- 알고리즘 사용 이전에 앞서 준비했던 두 생선의 데이터를 합친다.
1 | length = bream_length + smelt_length |
- 책에서 사용하는 머신러닝 패키지는 사이컷런(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] 가 하나의 원소로 구성된 리스트가 만들어진다.
1 | 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]]
2차원 리스트
- 첫 번째 생선의 길이 25.4cm와 무게 242.0g이 하나의 리스트를 구성하고 이런 리스트가 모여 전체 리스트를 만들었다.
- 이런 리스트를 2차원 리스트 혹은 리스트의 리스트라고 부른다.
생선 49개의 데이터가 준비되었다.
이제 마지막으로 준비할 데이터는 정답 데이터이다.
각 데이터가 실제로는 어떤 생선인지 정답지를 만드는 작업니다.
정답 리스트
- 머신러닝은 물론이고 컴퓨터 프로그램은 문자를 직접 이해하지 못한다.
- 대신 도미와 빙어를 숫자 1과 0으로 표현해보자
- 앞서 도미와 방어를 순서대로 나열했기에 정답 리스트는 1이 35번 등장하고 0이 14번 등장한다.
1 | fish_target = [1] * 35 + [0] * 14 |
[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-최급접 이웃 알고리즘도 마찬가지이다.
실제 코드로도 그런지 한 번 확인해 보자.
1 | kn.predict([[30, 600]]) # 2차원 리스트 |
array([1])
predict() 메서드
- predict() 메서드는 새로운 데이터의 정답을 예측한다.
- 이 메서드도 앞서 fit() 메소드와 마찬가지로 2차원 리스트를 전달해야 한다.
- 그래서 삼각형 포인트를 리스트로 2번 감싼것이다.
- 반환되는 값은 1.
- 우리는 앞서 도미는 1, 빙어는 0으로 가정했다.
- 즉, 삼각형은 도미이다.
k-최근접 이웃 알고리즘을 위해 준비해야 하는 것은 데이터를 모두 가지고 있는 것 뿐이다.
새로운 데이터에 대해 예측할 때는 가장 가까운 직선거리에 어떤 데이터가 있는지를 살피기만 하면 된다.
단점으로는 이런 특징 때문에 데이터가 아주 많은 경우 사용하기 어렵다는 점이 있다.
사이킷런의 KNeighborsClassifier 클래스도 마찬가지이다.
이 클래스는 _fit_X 속성에 우리가 전달한 fish_data를 모두 가지고 있다.
또 _y 속성에 fish_target을 가지고 있다.
1 | print(kn._fit_X) |
[[ 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 | print(kn._y) |
[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개로 한 kn49 모델 |
- 가장 가까운 데이터 49개를 사용하는 k-최근접 이웃 모델에 fish_data를 적용하면 fish_data에 있는 모든 생선을 사용하여 예측하게 된다.
- 다시 말하면 fish_data의 데이터 49개 중에 도미가 35개로 다수를 차지하므로 어떤 데이터를 넣어도 무조건 도미로 예측할 것이다.
1 | kn49.fit(fish_data, fish_target) |
0.7142857142857143
- fish_data에 있는 생선 중에 도미가 35개이고 빙어가 14개이다.
- kn49모델은 도미만 올바르게 맞히기 때운에 다음과 같이 정확도를 계산하면 score() 메서드와 같은 값을 얻을 수 있다.
1 | print(35/49) |
0.7142857142857143
- 확실히 n_neighbors 매개변수를 49로 두는 것은 좋지 않다.
- 기본 값을 5로 하여 도미를 완벽하게 분류한 모델을 사용하기로 한다.
도미와 빙어 분류 (중간 정리)
- 지금까지 도미와 빙어를 구분하기 위해 첫 머신러닝 프로그램을 만들었다.
- 먼저 도미 35마리와 빙어 14마리의 길이와 무게를 측정해서 파이썬 리스트로 만든다.
- 그 다음 도미와 빙어 데이터를 합친 2차원 리스트를 준비했다.
- 사용한 머신러닝 알고리즘은 k-최근접 이웃 알고리즘이다.
- 사이킷런의 k-최근접 이웃 알고리즘은 주변에서 가장 가까운 5개의 데이터를 보고 다수결의 원칙에 따라 데이터를 예측한다.
- 이 모델은 준비된 데이터를 모두 맞혔다.
- 도미와 빙어를 분류하는 문제를 풀면서 KNeighborsClassifier 클래스의 fit(), score(), predict() 메서드를 사용해 보았다.
- 끝으로 k-최근접 이웃 알고리즘의 특징을 알아보았다.
전체 소스 코드
1 | # 마켓과 머신러닝 |
[[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-최근접 이웃 분류 모델을 만드는 사이킷런 클래스이다.
- n_neighbors 매개변수로 이웃의 개수를 지정한다.
- 기본값은 5이다.
- p 매개변수로 거리를 재는 방법을 지정한다.
- 1일 경우 맨해튼 거리(https://bit.ly/man_distance)를 사용한다.
- 2일 경우 유클리디안 거리(https://bit.ly/euc_distance)를 사용한다.
- 기본 값은 2이다.
- fit()은 사이킷런 모델을 훈련할 때 사용하는 메서드이다.
- 처음 두 매개변수로 훈련에 사용할 특성과 정답 데이터를 전달한다.
- predict()는 사이킷런 모델을 훈련하고 예측할 때 사용하는 메서드이다.
- 특성 데이터 하나만 매개변수로 받는다.
- score()는 훈련된 사이킷런 모델의 성능을 측정한다.
- 처음 두 매개변수로 특성과 정답 데이터를 전달한다.
- 이 매서드는 먼저 predict() 메서드로 예측을 수행한 다음 분류 모델일 경우 정답과 비교해 맞게 예측한 개수의 비율을 반환한다.
- KneighborsClassifier()는 k-최근접 이웃 분류 모델을 만드는 사이킷런 클래스이다.
확인 문제
- 데이터를 표현하는 하나의 성질로써, 예를 들어 국가 데이터의 경우 인구 수, GDP, 면적 등이 하나의 국가를 나타냅니다. 머신러닝에서 이런 성질을 무엇이라 부르나요?
- 특성 v
- 특질
- 개성
- 요소
- 가장 가까운 이웃을 참고하여 정답을 예측하는 알고리즘이 구현된 사이킷런 클래스는 무엇인가요?
- SGDClassifier
- LinearRegression
- RandomForestClassifier
- KNeighborsClassifier v
- 사이킷런 모델을 훈련할 때 사용하는 메서드는 어떤 것인가요?
- predict()
- fit() v
- score()
- transform()
Reference : 혼자 공부하는 머신러닝 + 딥러닝