생선 분류 문제
한빛 마켓에서 팔기 시작한 생선: ['도미', '곤들매기', '농어', '강꼬치고치', '로치', '빙어', '송어']
-> 이 생선들을 프로그램으로 분류하려고 한다.
# 생선 길이>30cm -> '도미'라고 분류한다면?
if fish_length >= 30:
print('도미')
하지만 생선길이>30cm인 생선이 무조건 도미라고 할 수 없음.
- 일반적인 프로그램: '누군가 정해준 기준대로 일함
- 머신 러닝(ML: Machine Learning): 누군가 알려주지 않아도 알아서 기준을 찾아서 일함
결론: 머신러닝 기법을 통해 생선의 길이, 무게 데이터를 기반으로 분류해보자!
'도미/빙어 분류'와 같이 두 가지 클래스(class)로 분리 하는 것 -> 이진분류(Binary Classification)
Dataset 준비
(Dataset 출처: 'https://www.kaggle.com/aungpyaeap/fish-market' (페이지 사라짐) -> 'https://github.com/Ankit152/Fish-Market'(Github))
도미 데이터 준비 ('http://bit.ly/bream_list'에서 복사 후 사용)
# 길이(cm), 무게(g)를 포함한 도미 35마리 데이터셋 정의
# 여기서 도미의 길이(cm), 무게(g)를 특성(feature)라고 부른다.
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]
산점도(Sactter plot): 도미의 특성을 그래프에 점으로 표시해보자! (Matplotlib 패키지 사용)
#!pip install matplotlib # matplotlib 패키지 설치
import matplotlib.pyplot as plt # matplotlib의 pylot 함수를 plt로 줄여서 사용
plt.scatter(bream_length, bream_weight) # scatter 함수로 산점도 그리기
plt.title('bream') # 그래프 제목은 'bream'
plt.xlabel('length') # x축은 길이
plt.ylabel('weight') # y축은 무게
plt.show() # 그래프 출력
Output
- x축: 길이(cm), y축: (무게) 2개의 특성을 사용한 2차원 그래프
- 생선의 길이가 길수록, 무게도 많이 나감
- 이렇게 Scatter plot(산점도 그래프)가 일직선 가까운 형태로 나타나는 경우 -> 선형적이다.
빙어 데이터 준비 ('https://bit.ly/smelt_list'에서 복사 후 사용)
# 길이(cm), 무게(g)를 포함한 빙어 14마리 데이터셋 정의
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]
데이터를 보면 알 수 있듯이, 빙어의 크기는 작고 무게는 가벼움.
산점도(Sactter plot): 빙어의 특성을 그래프에 점으로 표시해보자! (Matplotlib 패키지 사용)
plt.scatter(smelt_length, smelt_weight) # scatter 함수로 산점도 그리기
plt.title('Smelt') # 그래프 제목은 'bream'
plt.xlabel('length') # x축은 길이
plt.ylabel('weight') # y축은 무게
plt.show() # 그래프 출력
Output
도미, 빙어의 특성을 한 번에 산점도에 뿌려보자! (Matplotlib 패키지 사용)
plt.scatter(bream_length, bream_weight) # 도미 데이터
plt.scatter(smelt_length, smelt_weight) # 빙어 데이터
plt.title('Bream and Smelt') # 그래프 제목
plt.xlabel('length') # x축은 길이
plt.ylabel('weight') # y축은 무게
plt.legend(['bream', 'smelt']) # 범례 추가
#plt.scatter(bream_length, bream_weight, c='r'); plt.scatter(smelt_length, smelt_weight, c='b') # 각 클래스의 색 지정 가능(r: red, b: blue)
plt.show() # 그래프 출력
Output
- 빙어는 도미에 비해 길이, 무게가 매우 작다.
- 빙어도 도미와 비슷하게 무게가 비례하지만(선형적) 늘어나는 정도가 다르다. -> 빙어는 길이가 늘어나도, 무게는 많이 늘지 않음
머신러닝을 위한 데이터셋 준비
K-최근접 이웃(K-Nearest Neighbors, K-NN)이라는 머신러닝 알고리즘을 사용하기 전, 도미와 빙어 데이터를 하나의 데이터로 합친다.
length = bream_length + smelt_length # 도미와 빙어의 길이를 합친 리스트
weight = bream_weight + smelt_weight # 도미와 빙어의 무게를 합친 리스트
print(f'length: {length}')
print(f'weight: {weight}')
Output
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]
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]
Scikit-learn이라는 머신러닝 패키지를 사용하려면, 데이터셋을 아래와 같이 세로 방향으로 늘어뜨린 2차원 리스트를 만들어야 한다.
파이썬 내장 함수인 zip() 함수 사용
fish_data = [[l, w] for l, w in zip(length, weight)] # zip 함수로 두 리스트를 하나로 묶은 후, for문으로 2차원 리스트로 변환
print(f'fish_data: {fish_data}')
Output
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]]
생선들의 특성(feature) 데이터를 준비했으니, 정답(target) 데이터를 준비하자!
머신러닝 알고리즘은 도미/방어를 구분하는 규칙을 찾는다. 그러려면, 데이터의 특성에 대한 정답도 알려주어야 한다.
'문자'를 이해하지 못하는 컴퓨터를 위해 숫자로 표시해주자.
-> [도미: 1, 빙어:0], 보통 찾으려는 대상을 1로 설정
ex) 특성(feature): [25.4, 242.0], 정답(target): 도미
fish_target = [1]*35 + [0]*14 # 도미는 1, 빙어는 0으로 레이블링
print(f'fish_target: {fish_target}')
Output
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]
Sckit-learn(머신러닝 패키지) 사용
##Scikit-learn
#!pip install scikit-learn # scikit-learn 패키지 설치
from sklearn.neighbors import KNeighborsClassifier # KNeighborsClassifier 클래스 임포트
kn = KNeighborsClassifier(n_neighbors=5) # K-최근접 이웃 분류기 모델 생성, n_neighbors 매개변수는 이웃의 개수를 지정(기본값은 5)
kn.fit(fish_data, fish_target) # fit 메서드로 훈련, kn.fit(특성, 타깃)
score = kn.score(fish_data, fish_target) # score 메서드로 정확도 계산, kn.score(특성, 타깃), 정확도 = (정확히 맞힌 개수) / (전체 데이터 개수)
print(f'score: {score}')
Output
score: 1.0
K-최근접 이웃(KNN: K-Nearest Neighbors)
- 데이터 분류 작업에서 활용되는 기초가 되는 알고리즘
- 새로운 입력으로 들어온 데이터를 특정값으로 분류하는데, 현재 데이터와 가장 가까운 k개의 데이터를 찾아 가장 많은 분류 값으로 현재 데이터를 분류하는 알고리즘
- 이러한 모델의 특징 때문에, 데이터가 많아지면 많은 계산, 메모리, 시간 소모
KNN Example
예를 들어, 삼각형으로 표시된 데이터가 있다. 이 삼각형은 도미/빙어 중 어디에 속할까?
우리는 직관적으로 삼각형은 '도미'라고 판단할 수 있다. (삼각형 주변에 다른 도미 데이터가 많기 때문)
KNN 알고리즘도 마찬가지다.
# KNN모델이 삼각형을 어떻게 분류하는지 확인
kn.predict([[30, 600]]) # kn 모델로 [30, 600] 데이터 예측
Output
array([1])
prdedict() 메소드로 예측한 결과, 앞서 가정한 1번인 '도미'로 예측한다
모델에 전달한 데이터(Data) 확인 방법
print(kn._fit_X) # fish_data를 출력
Output
[[ 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]]
print(kn._y) # fish_target을 출력
Output
[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]
참고할 데이터의 이웃 수(n_neighbors)를 지정해보자!
kn49 = KNeighborsClassifier(n_neighbors=49) # 이웃의 개수를 49로 지정, 가장 가까운 데이터 49개를 참고함
kn49.fit(fish_data, fish_target) # 모델 훈련
kn49_score = kn49.score(fish_data, fish_target) # 정확도 계산
print(f'kn49_score: {kn49_score}')
Output
kn49_score: 0.7142857142857143
fish_data 데이터 49개 중에 도미가 35개로 다수를 차지하므로, 어떤 데이터를 넣어도 무조건 '도미'로 예측한다.
확률 확인
print(35/49) # 도미가 35개이므로 도미로 예측하면 35개가 맞고, 빙어가 14개이므로 빙어로 예측하면 14개가 맞음
Output
0.7142857142857143
도미/빙어 분류 전체 소스 코드
데이터 준비 및 시각화
# 도미 데이터 준비
## 길이(cm), 무게(g)를 포함한 도미 35마리 데이터셋 정의
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]
# 빙어 데이터 준비
## 길이(cm), 무게(g)를 포함한 빙어 14마리 데이터셋 정의
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]
# 도미, 빙어 데이터 산점도(Scatter plot) 그리기
plt.scatter(bream_length, bream_weight) # 도미 데이터
plt.scatter(smelt_length, smelt_weight) # 빙어 데이터
plt.xlabel('length') # x축은 길이
plt.ylabel('weight') # y축은 무게
plt.title('Bream and Smelt') # 그래프 제목
plt.legend(['bream', 'smelt']) # 범례 추가
plt.show() # 그래프 출력
Output
특성(Feature) 데이터, 정답(Target) 데이터 준비
# 특성(Feature) 데이터 준비
## 도미, 빙어 데이터 합치기
length = bream_length + smelt_length # 도미와 빙어의 길이를 합친 리스트
weight = bream_weight + smelt_weight # 도미와 빙어의 무게를 합친 리스트
fish_data = [[l, w] for l, w in zip(length, weight)] # zip 함수로 두 리스트를 하나로 묶은 후, for문으로 2차원 리스트로 변환
# 정답(Target) 데이터 준비
fish_target = [1]*35 + [0]*14 # 도미는 1, 빙어는 0으로 레이블링
print(f'feature_data: {fish_data}')
print(f'target_data: {fish_target}')
Output
feature_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]]
target_data: [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]
Sckit-learn을 이용한 K-최근점(KNN:K-Nearest neighbor) 알고리즘
from sklearn.neighbors import KNeighborsClassifier # KNeighborsClassifier 클래스 임포트
kn = KNeighborsClassifier(n_neighbors=5) # K-최근접 이웃 분류기 모델 생성, n_neighbors 매개변수는 이웃의 개수를 지정(기본값은 5)
kn.fit(fish_data, fish_target) # fit 메서드로 훈련, kn.fit(feature_data, target_data)
score = kn.score(fish_data, fish_target) # score 메서드로 정확도 계산, kn.score(feature_data, target_data)
print(f'score: {score}')
# 새로운 데이터로 예측
plt.scatter(bream_length, bream_weight) # 도미 데이터
plt.scatter(smelt_length, smelt_weight) # 빙어 데이터
plt.scatter(30, 600, marker='^') # 새로운 데이터 표시, marker 매개변수로 모양을 변경
plt.xlabel('length') # x축은 길이
plt.ylabel('weight') # y축은 무게
plt.title('Bream and Smelt') # 그래프 제목
plt.legend(['bream', 'smelt', 'new']) # 범례 추가
plt.show() # 그래프 출력
new_data = [[30, 600]] # 새로운 데이터 생성
newdata_predict = kn.predict(new_data) # kn 모델로 [30, 600] 데이터 예측
print(f'new_data predict result: {newdata_predict}') # 새로운 데이터의 예측 결과 출력
Output
score: 1.0
new_data predict result: [1]
KNN모델에 입력된 데이터 확인
print(f'feature_data: {kn._fit_X}\n') # fish_data를 출력
print(f'target_data: {kn._y}') # fish_target을 출력
Output
feature_data: [[ 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]]
target_data: [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]
'Data Science > 혼자 공부하는 머신러닝' 카테고리의 다른 글
[혼공머] Chapter 03-2. 선형 회귀 (0) | 2024.06.01 |
---|---|
[혼공머] Chapter 03-01. 회귀 알고리즘과 모델 규제 (0) | 2024.06.01 |