[혼공머신] 1주차 01-3 마켓과 머신러닝
본 게시글은 한빛미디어의 혼자 공부하는 머신러닝+딥러닝을 바탕으로 작성되었습니다.
문제 상황 인식
마켓에서 도미, 곤들매기, 농어, 강꼬치고기, 로치, 빙어, 송어를 판다고 할 때, 이를 분류하는 프로그램을 만들자.
분류를 하기 위해서는 특성을 알아야 하는데, 만약 생선의 길이가 30cm 이상이면 모두 도미라면 다음과 같이 간단하게 코드를 만들면 된다.
1
2
if fish_length >= 30:
print("도미")
그러나 세상은 그렇게 호락호락하지 않다. 이렇게 쉽게 모든 문제가 풀렸으면 인생에 고민은 없을 것이다. 세상에는 30cm가 넘는 갈치도 있고, 개복치도 있고, 심지어 고래도 있다. 그렇기 때문에 조금 더 구체적으로 분류를 하고자 한다. 방금 내가 뭐라 했나? ‘분류’?
맞다. 우리는 이러한 구별을 하는 문제를 분류(Classification)라고 한다. 그리고 이번에는 여러 개를 분류하는 것이 아니라, 그냥 도미랑 빙어만 분류할 것이다. 이렇게 두 개의 분류만 있을 때 우리는 이진분류(二進分類, Binary Classification)라고 한다. 또한, 도미와 빙어와 같이 구분이 되는 종류를 클래스(Class)라고 할 것이다. 이렇게 전문적인? 용어가 앞으로 계속 나올 텐데, 우리는 어차피 나중에 다 영어로 자료를 찾아보고 영어로 이러한 것을 쓰는 경우가 더 많을 것이다. 따라서 이러한 단어가 나올 경우, 처음에만 국문을 병기하고, 이후에는 영문으로만 쓰도록 하겠다.
도미 데이터 입력
우선, 기본적인 도미의 데이터이다. 만약 이 코드가 필요하면 컴퓨터의 신이 내려주신 선물인 마법의 주문 Ctrl+C, Ctrl+V, 즉 복사를 하면 된다. 굳이 이 많은 숫자를 하나하나 적는다고 고생하지 않아도 된다. 오히려 그러면 데이터 분석에서 제일 큰 문제 중 하나인 휴먼에러(Human Error)가 생길 우려가 있다.
1
2
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]
코드를 봤는데 숫자 울렁증이 있어 힘들어지더라도 걱정은 하지 않아도 된다. 우리가 저 많은 숫자를 하나하나 다 계산하고 있지는 않을 것이기 때문이다. 만약 지금이 기계식 계산기도 없던 16세기라면 주판 같은 것으로 계산을 했겠지만, 지금의 현대 사회에는 무려 계산을 해주는 컴퓨터가 있다.
그러면 저 데이터를 어떻게 처리할 수 있을까? 먼저, 보기 힘들더라도 데이터의 이름을 찾아보자. 숫자들 사이에 영문자가 있다면 그게 이름일 것이다. 참, 여기서는 파이썬에 대해서는 충분히 알고 있다고 가정하고 설명을 할 것이다.
위 코드에서 찾은 데이터의 이름은 bream_length, bream_weight이다. 각각 생선의 길이, 생선의 무게이다. 이러한 데이터의 이름을 우리는 특성(Feature)이라고 한다. 이때, 우리는 잊어려려서는 안 되는 것이 하나 있다. 바로 단위이다. 위 데이터에는 나와 있지 않으나, 교재에는 길이의 단위가 cm이고, 무게의 단위가 g이라고 명시를 해줬다. 이것이 중요한 이유는 나중에 설명하겠다.
이렇게 우리는 여러 가지 용어도 알았고, 저 어지러운 숫자들이 도미의 길이랑 무게라는 것도 알았다. 그럼 무엇을 해야 할까?
예전에 강의를 들었던 교수님께서 항상 하셨던 말씀이 있다. 데이터 분석을 하기 전에 항상 시각화(Visualization) 과정을 거치라는 말씀이다. 우리가 수치상으로 데이터를 다 파악한 것 같더라도, 이것을 graphical 하게 보면 찾지 못했던 패턴이나 경향성(Tendency)를 발견할 수 있다는 말이다. 따라서 우리는 무조건 데이터 분석을 하기 전에 그래프(Plot)을 그려야 한다.
하지만 우리가 저것들을 손으로 그릴 수는 없으니, 우린 matplotlib 패키지(Package)를 사용할 것이다. 데이터 분석을 하는 사람이라면 누구나 이 패키지를 설치해 두었을 것이라 믿지만, 설치 코드를 아래 작성해 두었다.
1
pip3 install matplotlib
우리는 가장 간단한 plot인 산점도(Scatter Plot)을 그려볼 것이다. Scatter plot을 그리는 코드는 너무 간단해 누구나 알듯이 아래와 같다.
도미 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
축하한다. 우리는 선형적(Linear)인 데이터를 발견하였다. 데이터 분석을 할 때 이렇게 linear 한 데이터가 나오면 너무 행복할 것이다. 그러나 이렇게 쉽게 나오는 경우는 이미 다른 사람들도 다 쉽게 할 수 있어서 우리가 할 수 있는 것은 많이 없을 것이다. 따라서 이러한 데이터는 연습에서나 볼 수 있다고 생각하고 보자.
우리는 지금까지 도미에 대해서 알아보았다. 그럼, 이제 도미를 classification을 통해 구별해야 하는데, 어떤 class와 구별을 해야 할까? 걱정하지 않아도 된다. 한빛미디어는 우리를 위해 14마리의 빙어를 준비해 주었다고 한다. 아래는 그 빙어의 데이터이다.
빙어 데이터 입력
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]
대충 봐도 알겠지만, 빙어는 도미보다 크기도 작고, 무게도 가볍다는 사실을 알 수 있다. 이번에는 한번 아까 만들었던 plot에 또 다른 빙어에 대한 plot을 추가하여 그려보도록 하자.
도미와 빙어 Scatter Plot
1
2
3
4
5
6
7
import matplotlib.pyplot as plt
plt.scatter(bream_length, bream_weight)
plt.scatter(smelt_length, smelt_weight)
plt.xlabel('length')
plt.ylabel('weight')
plt.show
이런, 그래프를 보니 빙어가 생각보다 작았던 모양이다. 도미의 크기에 비해 현저히 작다 보니 데이터의 분포(Distribution)을 확인할 수가 없다. 그래도 얼마 보이지 않는 저 데이터를 보면, 도미에 비해서는 몸길이가 늘어나더라도 무게가 그렇게 많이 늘어나는 것 같아보이지 않는다는 것을 확인할 수 있다.
KNN 알고리즘
지금까지 우리는 어떤 대단한 것을 한 것이 아니라 이제 겨우 도미와 빙어가 어떻게 생겼는지 정도만 파악했다. 그럼 이걸 어떻게 컴퓨터한테 학습을 시키고 그 두 class를 구별하라고 할 수 있을까? 우리는 가장 이해하기 쉬운 K-최근접 이웃(KNN, K-Nearest Neighbors) 알고리즘을 사용해서 classification을 해볼 것이다.
데이터 병합
이제 우리는 classification을 실제로 해야 하기에 도미와 빙어 데이터를 하나의 데이터로 만들어줄 것이다.
1
2
length = bream_length + smelt_length
weight = bream_weight + smelt_weight
우리는 이제 기억할지는 모르겠지만 지난 포스트에서 언급했었던 scikit-learn을 활용해 볼 것이다. 이 scikit-learn에는 기초적인 수준의 알고리즘은 거의 다 있다고 생각하면 된다. 그전에, 우리는 앞서 만든 데이터를 2차원 리스트(List)로 만들 것이다.
1
fish_data = [[l, w] for l, w in zip(length, weight)]
이렇게 하면 물고기의 길이와 무게가 함께 연결되어 있는 list의 list가 만들어지게 된다. 굳이 왜 이렇게 할까? 우리는 물고기들을 무게 또는 길이 중 하나만으로 classification을 하는 것이 아니라, 그 두 개를 결합해서 classification을 할 것이다. 그렇기 때문에 이 두 개가 분리가 되어 있으면 활용을 할 수가 없다. 또한, 방금 도미랑 빙어를 섞었는데 뭐가 빙어의 길이고 뭐가 도미의 무게인지 어떻게 알 것인가? 그렇기 때문에 두 개의 데이터를 서로 연결해서 하나의 생선을 만들어줘야 한다.
그리고 이제 도미랑 빙어를 표시하는 작업을 해볼 것이다. 우선 우리는 방금 만든 데이터 중 처음 35개가 도미고, 그 뒤에 나오는 14개가 빙어라는 것을 알고 있다. 그럼 그것에 맞게 데이터에 대해서 뭐가 도미고, 뭐가 빙어인지 알려주기 위한 정답을 적을 것이다. 우리가 흔히 보는 대학 교재와 같이 정답을 안 적고 네가 맞게 푼지 잘 모르겠다면 네가 공부를 열심히 하지 않은 거야!라고 컴퓨터에게 할 수는 없지 않은가? 따라서 우리는 친절하게 하나하나 정답을 적어줄 것이다. 우리는 1을 도미라고 하고 0을 빙어라고 하자.
정답 데이터 생성
1
fish_target = [1] * 35 + [0] * 14
이렇게 작성하면 정답 리스트가 만들어지게 된다. 이제 이렇게 만든 연습문제로 KNN 알고리즘을 통해 컴퓨터를 학습시킬 것이다.
KNN 알고리즘 학습
1
2
from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier()
먼저 위와 같이 KNN을 위해 package를 로드하고, 모델(Model)을 설정해 준다.
1
knn.fit(fish_data, fish_target)
그리고 연습문제를 학습(Fit)시킬 시간이다. 이렇게 연습문제를 학습시킨 다음에는 무엇을 해야 할까? 맞다. 시험을 해야 한다. 평소에는 우리가 시험을 보는 입장이었다면 이제는 우리가 시험을 보게 하는 입장이다. 컴퓨터는 몇 번이고 시험을 보게 해도 나한테 뭐라고 하지 않으니 마음 놓고 컴퓨터를 시험하자!
알고리즘 시험
1
knn.score(fish_data, fish_target)
1.0
방금 도미가 1이라고 했는데 다 도미인 건가? 라며 1이 나왔다고 해서 당황하면 안 된다. 1은 답을 모두 맞혔다는 것이다. 이런, 내가 대학에서 거의 경험해 본 적 없는 만점이라니라며 슬퍼하지 않아도 된다. 여러분도 열심히 공부했던 연습문제를 시험 문제에 그대로 내면 아마 100점이 나올 것이다. 물론 ‘열심히 공부했던’이라는 기준이 있다면 말이다. 지금 보니 학습 데이터와 테스트 데이터에 대한 개념은 이 장에 나오지 않는 것에 당황해하며 슬쩍 넘어가기로 하자.
우선 교재에서는 또 다른 코드를 알려준다. 한빛미디어가 우리를 위해 싱싱한 생선을 한 마리 가져왔다. 그 생선은 길이가 30cm이고, 무게가 600g이다. 이러한 경우에는 어떻게 할까? 우리는 이를 위한 predict가 있다.
생선 예측
1
knn.predict([[30, 600]])
array([1])
결과가 1이 나왔다. 우리는 아까 1은 도미라고 정하기로 했다. 따라서 이 생선은 도미라는 것을 알 수 있다.
이렇게 코드로만 보면 KNN 알고리즘을 사용하는 것이 매우 편하고 쉬워 보인다. 그러나, KNN 알고리즘은 데이터의 직선거리를 계산하여 어떤 데이터가 가장 가까이 있는지를 확인해 classification을 진행하는 것이기에 데이터가 많아지면 많아질수록 직선거리를 계산하는 소요가 증가한다. 그렇기에 시간도 많이 걸리고 메모리도 많이 필요하다.
그런데 여기서 우리는 굳이 조금 더 살펴볼 것이다. 솔직히 여기까지는 그냥 아무것도 모르는 사람도 충분히 할 수 있다. 물론 더 살펴볼 것도 그렇게 어려운 것은 아니다. 그래서 왜 K-Nearest Neighbors 알고리즘일까? K의 정체는 무엇일까?
KNN 알고리즘은 $K$개의 가까운 이웃을 통해 classification을 하는 모델이다. 즉 어떤 점이 있을 때 $K$개의 가장 가까운 주변의 점들을 찾은 후, 그 점들 중 가장 많은 class를 선택해서 우리의 점에 class를 정하는 것이다. 하지만 우리는 $K$를 정한 기억이 없다. 그럼 어떻게 된 것일까?
매개변수 $K$ 조절
scikit-learn을 만들 정도로 똑똑한 사람들은 그런 것도 다 이미 생각을 해두었다. $K=5$를 기본값으로 하여 학습을 하도록 되어 있고, 우리는 매개변수(Parameter) 변경을 통해 $K$를 원하는 대로 변경할 수 있다. 그러나 이것도 적절히 조절할 필요가 있다. 한번 교재에 나온 것처럼 $K=49$로 설정했을 때 어떤 참사가 일어나는지 확인해 보자.
1
2
3
4
knn49 = KNeighborsClassifier(n_neighbors=49)
knn49.fit(fish_data, fish_target)
knn49.score(fish_data, fish_target)
0.7142857142857143
$K=49$로 바꾸었더니 정확도가 떨어졌다. 만약 $K=49$이라면 데이터 주변의 49개의 데이터, 즉 모든 데이터를 참고해서 class를 정하게 된다. 그런데 우리는 도미가 35마리고 빙어가 14마리였다. 안 그래도 빙어는 크기도 작아서 서러운데 마릿수도 밀려서 도미가 되는 수모를 당하게 되는 것이다. 이 결과를 통해 우리는 $K$를 그냥 내 마음대로 하면 안 된다는 것을 알 수 있다.