본문 바로가기
혼공학습단 머신러닝+딥러닝

[혼공머신] 1주차 -1 오프닝과 k-최근접 이웃 알고리즘

by 알래스카코코넛 2024. 1. 5.
반응형

0. 혼공학습단 머신러닝+딥러닝 시작 오프닝 멘트 

 여름방학 때 혼공C로 참여하고 반 년이 흘러 혼공머신으로 돌아왔습니다. 머신러닝은 파이썬을 쓰니까 두 주제가 연관이 없을 것 같은데 왜 머신러닝으로 참여하냐! 하시면 제가 데이터사이언스학과를 복수전공 하는데 (원전공은 컴퓨터교육과), 해당 학과 전공 수업 중에서 '기계학습입문' 수업 주교재가 혼공머신이었어서 그렇습니다. 마침 교재가 준비되어 있어서 흠흠.... 

 

 특히 수업이 주차별로는 코딩 + 기말고사는 이론 전범위라 책 페이지가 몇 장 날라가도록 읽어봤는데 지금까지 읽어본 머신러닝 입문서중에는 제일 좋더라고요. 참고로 저의 좋은 입문서 기준은

 

1. 모국어 위주로 설명되어야 한다.

 당연히 원서가 가장 좋은 건 알지만, 입문자 입장에서는 모국어로 해도 못 알아듣는 말이 많은데 굳이 입문서로 영어교재를 가져오는 교수님들을 정말 싫어합니다. 한국어만 읽으면 외국어 논문 읽을 때 어떻게 할 거냐, 외국인 전문가랑 대화는 어떻게 하냐... 이런 반박은 종종 봤는데, 이제 막 입문한 학부생이 외국어 논문을 읽을 경험이... 국내 논문도 사실 새내기 때는 잘 안봐요. 그리고 외국인 전문가는 학부 1~2학년에게 많은 것을 기대하지 않습니다. 오히려 

영어라 못 알아들음 -> 공부 안함 -> 머리에 남은게 없음 루트를 타는 학생이 더 많아서 입문서만큼은 모국어 아니면 극도로 배척파인데, 혼공 시리즈는 꼭 필요한 단어 외에는 전부 한국어로 되어서 좋아했습니다. 

 

2. 코드 전문을 보여줘야 한다.

 설명 틈틈이 코드를 적은 걸로 대체하는 책들은 전체적인 흐름을 파악하기가 곤란해서 도움이 잘 안될 때가 많아요. 예를 들어 설명 중간에 '이 코드의 앞에 사용하였던 함수를 이용하여~~' 이런 부분이 있으면 그 함수가 뭐지 ??? 상태가 발생해서 답답하고 헷갈리는데, 여기는 마지막에 코드 전문이 있어서 이해하는데 도움이 많이 됩니다. 예전에는 그 앞에 그거 이러면 찰떡같이 잘 알아들었던 것 같은데 컴퓨터를 몇 년 붙잡고 있으니 나도 컴퓨터처럼 명확히 지정 안하면 아웃풋이 이상하게 나오더라... 그러므로 컴퓨터 전공자들에게는 변수 지정을 명확하게 해줍시다.

컴퓨터 전공자들의 머리속... 일단 나는 맞다

 

3. 입문자의 입장에서 이게 뭐지? 싶은 내용이 잘 설명되어 있다. 

 교육학적으로도 이 점이 입문서에서 가장 중요한 부분 중 하나인데, 입문자들은 경험자에 비해서 '당연하다'라고 받아들이는 부분이 훨신 적다는 점을 놓지면 안됩니다. 지금 교직 과목을 들은 지 좀 되어서 기억이 가물가물하긴 한데, 기억을 더듬어서 떠올려보면 지금 우리는 A랑 a가 둘 다 '에이'라는 걸 당연히 알지만, 처음 알파벳을 배우는 사람은 저거 생긴게 다른데 왜 같은 글자야?? 대문자랑 소문자? 그게 뭔데? 이렇게 나올 수 있어요. 입문자의 입장에서 당연한 지식은 거의 0에 수렴하니까 백지에 코딩한다는 생각으로 하나하나 가르쳐주는게 좋은데, 혼공머신은 '사이킷런의 입력 데이터(배열)의 행은 샘플, 열은 특성으로 구성되어 있습니다.'처럼 기본부터 알려줘서 이해하기 쉬웠습니다.

 

 열심히... 정말 열심히 혼공머신을 학교 열람실에서 휴대폰보다 더 자주 본 결과 이번 학기 성적도 잘 나왔습니다. 그리고 어디 가서 머신러닝 아는 척 할 수 있음. (바이럴 아님) 아래에는 냅다 제 이번학기 성적 있으니 보기 싫으면 빠르게 내리십쇼. 이거 안 보여주면 기계학습입문 C+가 요약하는거 아니냐! 이런 의심의 눈초리가 나올까봐 올리는데 갑자기 민망해진 주인장 이미지 슬쩍 내릴 수도 있어요~~ 

B+ 하나는 인간미 짜잔

1. Ch.1 나의 첫 머신러닝 

1-1. 인공지능과 머신러닝, 딥러닝

 위에 지원동기 아닌 지원동기는 여기까지 하고 이제 본 진도인 챕터 요약으로 넘어가겠습니다.

전공 수업에서 교수님들께서 과제로 내 주신 연습문제 풀기를 할 때 문제와 풀이를 이해못했지만 일단 제출하려고 인터넷 답을 베꼈을 때, 튜링 테스트에서 전제로 삼았던 '컴퓨터가 인간처럼 대화를 할 수 있다면 그 컴퓨터는 인간처럼 사고할 수 있다고 여긴다'는 틀렸구나! 를 느꼈다. 왜냐? 나도 아는 척 답을 달았지만 실제로 사고하고 문제를 이해한 건 아니기 때문에...

 

아무튼 제가 하고 싶은 말은, 이렇게 인공지능의 개념에 대해 언급이 있었던 튜링테스트가 사실은 1950년에 등장한 것이라는 점입니다. AI가 네비게이션이나 컴퓨터 등등에 장착된지는 얼마 안 된 것 같은데, 거의 100년을 바라보고 있다는 사실... 심지어 인공지능이 소설에 등장한 적은 150년 전이라네요. 중간중간 AI 겨울, AI 황금기, AI 붐 등을 겪으면서 최근 인공지능이 엄청 핫해졌고, 특히 알파고 vs 이세돌 (이세계아이돌 아님. 바둑기사 이세돌님) 바둑 대국이 2016년에 치뤄지면서 각광을 받고 있습니다. 2016년이면 제가 중학생이던 시절인데, 이걸 전공하게 될 줄은... 험험. 그리고 2015년에 딥러닝의 영혼의 파트너 텐서플로가 출시되고, 2018년 파이토치가 출시되어 파이썬 라이브러리로 쉽게 사용할 수 있습니다. 저 친구들이 없었으면 딥러닝이 훨씬 더 빡세졌을 듯... 고마워요 구글!(페이스북도!)

 

 인공지능은 크게 약인공지능과 강인공지능으로 나눌 수 있는데, 보통 흔히 볼 수 있는 인공지능은 약인공지능입니다. 특정 분야의 보조 역할을 하고 메인은 사람의 몫으로 남겨두는 인공지능이 약인공지능으로, 유튜브 알고리즘, 음성비서(시리, 빅스비 등), 파파고 등이 주된 예시에요.

강인공지능은 얘가 사람인지 인공지능인지 구별이 거의 어려운 지능을 가진 컴퓨터 시스템으로, 일상 생활에서는 영화에서밖에 볼 일이 없습니다. 그런데 요즘 인공지능 발전이 워낙 뛰어나서 곧 보게될 수도...? 

 

 위에서 쭉 봤듯이 인공지능은 개념이 아주아주 넓습니다. RPG게임에서 플레이어를 공격하기 위해 따라오는 잡몸의 행동 알고리즘도 AI의 정의에 부합합니다. 이 AI 안에 '데이터를 바탕으로 규칙을 스스로 학습하는 알고리즘' 머신러닝이 있고, 이 머신러닝의 세부 범주로 인공신경망을 사용하는 딥러닝이 있으니 상하관계를 절대 헷갈리지 않도록 합쉬당. 근데 안 헷갈리긴 함. 


1-2의 코랩과 주피터 노트북 정리는 기본 미션으로 패스


1-3. 마켓과 머신러닝 

 가장 간단한 머신러닝 알고리즘 중 하나인 k-최근접 이웃으로 머신러닝을 해봅시다.

k-최근접 이웃은 가장 가까운 k개의 데이터에 대한 정보를 바탕으로 원하는 데이터를 분류하는 방법입니다. 그림으로 설명하자면, 학생들의 키와 몸무게 데이터가 있다고 해봅시다. 

파란색은 6학년, 주황색은 1학년

 

 갑자기 하늘에서 키는 160cm, 몸무게는 50kg인 학생이 뚝 떨어졌습니다! 그런데 하늘에서 떨어져서 그런지, 자기가 몇 살인지 기억이 안난다고 하네요. 그럼 이 학생을 어느 1학년과 6학년 중 어느 학년으로 넣어야 할까요? 아마 대부분 '6학년 그룹이랑 데이터가 유사하니까 6학년에 넣읍시다!'라고 할 것입니다. 여기서 선형 방정식을 그려서.... 다항 곡선에 따르면... 이러는 분은 아마 없겠...죠....? 이건 다른 챕터에서 다룹니당. 아무튼 이런 식으로 근접한 주변 데이터의 그룹을 바탕으로 분류를 실시하는 머신러닝 모델이 k-최근접 이웃입니다. 정확히 정의하자면, 가장 가까운 k개의 데이터의 특성을 바탕으로 분류를 실행한다. 가 k-최근접 이웃 알고리즘의 정의입니다. 

 

 이번에는 교재에 있는 데이터랑 예시로 코딩을 해 보면서 k-최근접 이웃 알고리즘을 연습해보겠습니다. 

  • 목표: 도미(bream)과 빙어(smelt) 자료가 주어졌다. 이 데이터를 바탕으로 새로운 생선 데이터가 주어졌을 때, 그 생선이 도미인지 빙어인지 분류해보자! (무게의 단위는 g, 길이의 단위는 cm)
  • 주어진 자료: 도미와 빙어 각각의 무게와 길이
  • 사용 알고리즘: k-최근접 이웃 알고리즘 

이 문제를 해결하려면 앞의 하늘에서 뚝 떨어진 학생을 분류하는 문제처럼

  1. 미리 주어진 데이터를 산점도(위에 점으로 나타낸 그래프)로 표시한다.
  2. 우리가 k-최근접 이웃 알고리즘을 사용한다는 정보를 컴퓨터에게 알려준다.
  3. 알고자 하는 생선의 데이터는 어디에 있는지 산점도 위에 찍어본다.

이런 흐름을 따른다는 것을 알 수 있습니다. 그럼 이 흐름대로 문제 해결을 진행해 보겠습니다. 

 

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, 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] #도미 무게

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]  #빙어 무게 

#matplotlib로 그래프 그리기 
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()

 

파란색이 도미, 주황색이 빙어

 

 35마리의 도미, 14마리의 빙어 데이터가 산점도로 표시되었습니다!

 

2. 우리가 k-최근접 이웃 알고리즘을 사용한다는 정보를 컴퓨터에게 알려준다.

 컴퓨터에게 머신러닝을 시키기 전에 해야할 일이 있습니다. 보통 택배를 배송할 때, 출발지에서 바로 목적지로 보내나요? 아니죠. 주로 우체국이나 택배사에 전달해서 하루치를 모으고, 우체국에서 무게나 목적지 별로 분류 후 배송이 시작됩니다. 택배 하나하나를 얘는 부산으로 바로 보내고, 얘는 서울으로 보내고.. 이러면 배송 효율이 형용할 수 없을 만큼 낮아질 것입니다. 머신러닝에서도 마찬가지로, 데이터 하나하나를 얘는 빙어, (새로운 리스트에서) 얘는 도미, (또 새로운 리스트에서) 얘도 도미.... 이걸 데이터 수만큼 반복하면 상당히 귀찮아지므로 하나의 리스트로 일단 모은 후 얘네는 도미! 얘네는 빙어!! 이렇게 분류하는 편이 훨씬 쉽습니다. 

 

 그럼 리스트로 모아주는 과정을 위해 위에 bream_weight와 smelt_weight를 weight로, bream_length와 smelt_length를 length로 묶어줍시다. 그런데 여기서 보통을 태클이 들어옵니다. 잉? 이렇게 묶으면 누가 도미고 누가 빙어인지 어떻게 알아요? 이러다가 얘네 데이터 섞일라!! 이렇게 섞일 수도 있다는 우려를 막기 위해, zip함수를 이용해서 한 마리의 무게와 길이를 순서를 잘 지켜서 상자에 넣듯이 분류해줍시다. 

 

length = bream_length+smelt_length
weight = bream_weight+smelt_weight

fish_data = [[l, w] for l, w in zip(length, weight)]

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]]

 

데이터들이 안 섞이고 하나의 리스트에 잘 들어갔습니다! 결과를 해석하자면, [25.4, 242.0]은 길이가 25.4cm, 무게가 242.0g인 생선 하나라는 뜻입니다. 그런데 이대로는 누가 도미고 누가 빙어인지 모르니, 얘네의 이름표도 하나의 리스트로 담아줍시다. 

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, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
 

아웃풋을 해석하면, 앞의 '1' 35개는 도미, '0' 14개는 빙어라는 뜻입니다. 1이나 0은 바뀌어도 무방하나, 대신 fish_target의

1이나 0의 순서가 바뀌면 fish_data의 순서도 똑같이 바뀌어야 하는 점은 잊으면 안됩니다! 두 리스트는 공동운명체(중요!)

이제 본격적으로 우리가 k-최근접 이웃 알고리즘을 사용한다는 점을 컴퓨터에게 알려줍시다.
from sklearn.neighbors import KNeighborsClassifier  #우리 k-최근접 이웃 사용함!!

kn = KNeighborsClassifier()   #k-최근접 이웃을 사용할 공장 생성 
kn.fit(fish_data, fish_target)   #공장 가동

kn.score(fish_data, fish_target) #성과 출력

 

 먼저 sklearn(사이킷런이라 부름, sk에서 만든거 아닙니다. sk가 배우는 것도 아닙니다) 패키지에서 k-최근접 클래스를 가져옵니다. 물론 처음부터 싹 다 직접 알고리즘을 만들어도 되지만, 굳이 봉지라면을 두고 밀가루 반죽부터 시작하는거랑 같으니 추천하지 않아요. 우리는 사이킷런에게 감사를 표하고 스윽 갖다쓰면 됩니당. 고마워요 사이킷런! 

 

다음은 k-최근접 이웃을 사용할 공장인 kn을 선언해 줍시다. 이 공장을 전문 용어로 '모델'이라고 하니, 용어도 잘 알아두면 죠습니다. 그리고 아주 간단하게 kn.fit()를 사용하면 공장이 직접 위이잉 가동해서 데이터를 학습시킵니다. 마지막으로 kn.score()을 사용하면 공장의 성과까지 숫자로 알려줍니다. 사이킷런 만세! 

 

참고로 kn.score()은 0에서 1까지의 값을 반환하고, 1이면 100퍼센트 정확함, 0이면 다 틀렸다를 의미합니다. 0.5면 반만 맞았다는 뜻이죠. 

 

 3. 이제 알고자 하는 데이터를 산점도 위에 찍어봅시다. 

길이가 30cm, 무게가 600g인 데이터를 ▲로 표시해서 보겠습니다.

세모 = 정체를 알고자 하는 데이터

 

아주 정직하게 파란색, 즉 도미들 사이에 있네요. 그럼 우리의 kn은 세모를 도미로 출력할까요? 

kn.predict([[30, 600]])  #predict로 어느 소속인지 예측

array([1])

 

 이 코드의 output이 나타내는 바는 세모가 '1'이 있는 그룹에 속한다는 뜻입니다. 우리는 위의 fish_target에서 1이 도미, 0이 빙어로 정의한 바 있으니 kn은 잘 분류하는데 성공했습니다! 참고로 모델명.predict()는 머신러닝에서 늘 쓰이는 클래스니까 꼭 기억해둡시다. 

 약간 완벽주의를 추구해서 '저는 array([1])같이 암호같은 아웃풋 말고, 완벽하게 도미인지 빙어인지 따악 알려주면 좋겠는데요?' 이러면 끝에

res = kn.predict([[30, 600]])

if res[0] == 1:
    print("도미입니다")
else:
    print("빙어입니다")

 

이런 코드를 작성해주면 자연어 형태로 출력해줍니다. 

 

그런데 k-최근접 이웃 알고리즘은 가까운 k개의 데이터를 참고한다고 했는데, 여기서 k는 몇 개일까요? 기본적으로 k = 5이며, 사용자가 매개변수로 바꿀 수 있습니다. 아까 공장을 만들던 코드

from sklearn.neighbors import KNeighborsClassifier  #우리 k-최근접 이웃 사용함!!

kn = KNeighborsClassifier()   #k-최근접 이웃을 사용할 공장 생성 
kn.fit(fish_data, fish_target)   #공장 가동

kn.score(fish_data, fish_target) #성과 출력

 

 

 여기서 kn = KNeighborsClassifier() 부분의 괄호 안에 왜 아무것도 없지? 라고 생각하셨다면 훌륭한 의문을 제시한 겁니다. 개발자들은 수학자처럼 귀찮음이 심해서 절대 쓸데없는 사족을 코드에 붙이지 않아요. 오죽하면 거의 모든 라이브러리 (matplotlib, numpy, pandas....)에 널리널리 알려진 약어까지 있겠어요. KNeighborsClassifier()의 괄호도 뭔가 이유가 있으니까 있는 겁니당. 이쯤되면 슬슬 눈치챘을 만도 한데... k를 저 괄호를 통해서 조정할 수 있습니다. 예를 들어 k를 입력한 모든 데이터의 개수인 49로 바꾸려면

 

from sklearn.neighbors import KNeighborsClassifier  #우리 k-최근접 이웃 사용함!!

#여기가 바뀜
kn = KNeighborsClassifier(n_neighbors = 49)   #k를 5->49로 변경
#아래는 안바뀜 

kn.fit(fish_data, fish_target)   #공장 가동

kn.score(fish_data, fish_target) #성과 출력

 

 그런데 이렇게 바꾸면 어떤 데이터를 넣어도 14개인 빙어보다는 35개인 도미 데이터가 더 많으니까 알고자 하는 데이터를 도미로 인식한다는 문제가 발생합니다. 따라서 적절한 k를 찾는 작업은 k-최근접 이웃 알고리즘에서 정말 정말 중요하니 별을 다섯 개 치고 넘어갑시다.

 

한 게시물 안에 Ch.1이랑 Ch.2를 다 하려니까 생각보다 기네요... Ch.2는 다음 게시물에 투비컨티뉴.

 

반응형