GitHub - rickiepark/hg-mldl: <혼자 공부하는 머신러닝+딥러닝>의 코드 저장소입니다.
<혼자 공부하는 머신러닝+딥러닝>의 코드 저장소입니다. Contribute to rickiepark/hg-mldl development by creating an account on GitHub.
github.com
혼자 공부하는 머신러닝+딥러닝
이 책은 수식과 이론으로 중무장한 머신러닝, 딥러닝 책에 지친 ‘독학하는 입문자’가 ‘꼭 필요한 내용을 제대로’ 학습할 수 있도록 구성 되어 있습니다. 구글 머신러닝 전문가(Google ML expert)
Chapter 06
6-1. 군집 알고리즘
- 히스토그램(histogram) : 값이 발생한 빈도를 그래프로 표시한 것으로 보통 x축이 값의 구간(계급)이고, y축은 발생 빈도(도수)임.
- 군집(clustering) : 비슷한 샘플끼리 그룹으로 모으는 작업으로 대표적인 비지도 학습 작업 중 하나
- 타깃을 모르는 비지도 학습
#사진 데이터 준비하기 -> 파일 다운로드
!wget https://bit.ly/fruits_300_data -O fruits_300.npy
코랩은 ! 로 시작하면 이후 명령을 파이썬 코드가 아니라 리눅스 셀(shell) 명령으로 이해함.
wget 명령 : 우너격 주소에서 데이터를 다운로드하여 저장
-O 옵션 : 저장할 파일 이름 지정
#데이터 로드
import numpy as np
import matplotlib.pyplot as plt
fruits = np.load('fruits_300.npy')
#배열의 크기 화인
print(fruits.shape)
(300, 100, 100)
#샘플의 개수, 이미지 높이, 이미지 너비
#첫 번째 행의 픽셀 값
print(fruits[0, 0, :])
[ 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1
2 2 2 2 2 2 1 1 1 1 1 1 1 1 2 3 2 1
2 1 1 1 1 2 1 3 2 1 3 1 4 1 2 5 5 5
19 148 192 117 28 1 1 2 1 4 1 1 3 1 1 1 1 1
2 2 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]
#imshow 함수로 넘파이 배열로 저장된 이미지 그리기
plt.imshow(fruits[0], cmap='gray') # 흑백 이미지
plt.show()
0에 가까울수록 검게 나타남
이 흑백 이미지는 사진으로 찍은 이미지를 넘파이 배열로 변환할 때 반전시킨 것 (컴퓨터는 255에 가까운 바탕에 집중)
# cmap='gray_r' 다시 반전하여 우리 눈에 보기 좋게 출력
plt.imshow(fruits[0], cmap='gray_r')
plt.show()
이 그림에선 밝은 부분이 0에 가깝고 짙은 부분이 255에 가까운 값
fig, axs = plt.subplots(1, 2) # subplots : 여러 개의 그래프를 배열처럼 쌓을 수 있도록 도움(행, 열 지정)
axs[0].imshow(fruits[100], cmap='gray_r')
axs[1].imshow(fruits[200], cmap='gray_r')
plt.show()
- 픽셀값 분석하기
#reshape 함수를 이용해 두 번째 차원과 세 번째 차원을 100000으로 합침
#첫 번째 차원을 -1로 지정하면 자동으로 남은 차원을 할당
apple = fruits[0:100].reshape(-1, 100*100)
pineapple = fruits[100:200].reshape(-1, 100*100)
banana = fruits[200:300].reshape(-1, 100*100)
#배열의 크기 확인
print(apple.shape)
(100, 10000)
#샘플의 픽셀 평균값 계산(축 1 - 가로 계산)
print(apple.mean(axis=1))
[ 88.3346 97.9249 87.3709 98.3703 92.8705 82.6439 94.4244 95.5999
90.681 81.6226 87.0578 95.0745 93.8416 87.017 97.5078 87.2019
88.9827 100.9158 92.7823 100.9184 104.9854 88.674 99.5643 97.2495
94.1179 92.1935 95.1671 93.3322 102.8967 94.6695 90.5285 89.0744
97.7641 97.2938 100.7564 90.5236 100.2542 85.8452 96.4615 97.1492
90.711 102.3193 87.1629 89.8751 86.7327 86.3991 95.2865 89.1709
96.8163 91.6604 96.1065 99.6829 94.9718 87.4812 89.2596 89.5268
93.799 97.3983 87.151 97.825 103.22 94.4239 83.6657 83.5159
102.8453 87.0379 91.2742 100.4848 93.8388 90.8568 97.4616 97.5022
82.446 87.1789 96.9206 90.3135 90.565 97.6538 98.0919 93.6252
87.3867 84.7073 89.1135 86.7646 88.7301 86.643 96.7323 97.2604
81.9424 87.1687 97.2066 83.4712 95.9781 91.8096 98.4086 100.7823
101.556 100.7027 91.6098 88.8976]
# 맷플롯립의 hist() 함수로 히스토그램 그리기
plt.hist(np.mean(apple, axis=1), alpha=0.8) #alpha 값이 1보다 작으면 투명도를 줄 수 있음
plt.hist(np.mean(pineapple, axis=1), alpha=0.8)
plt.hist(np.mean(banana, axis=1), alpha=0.8)
plt.legend(['apple', 'pineapple', 'banana']) #legend 함수를 사용해 범례 만들 수 있음
plt.show()
#샘플의 평균값이 아닌 픽셀별 평균값 비교
fig, axs = plt.subplots(1, 3, figsize=(20, 5))
axs[0].bar(range(10000), np.mean(apple, axis=0)) #bar() 함수를 사용해 평균값을 막대 그래프로 그리기
axs[1].bar(range(10000), np.mean(pineapple, axis=0)) #픽셀의 평균 구하기 - axis=0 으로 설정
axs[2].bar(range(10000), np.mean(banana, axis=0))
plt.show()
#픽셀 평균값을 100 X 100 크기로 바꿔서 이미지처럼 출력하여 비교
apple_mean = np.mean(apple, axis=0).reshape(100, 100)
pineapple_mean = np.mean(pineapple, axis=0).reshape(100, 100)
banana_mean = np.mean(banana, axis=0).reshape(100, 100)
fig, axs = plt.subplots(1, 3, figsize=(20, 5))
axs[0].imshow(apple_mean, cmap='gray_r')
axs[1].imshow(pineapple_mean, cmap='gray_r')
axs[2].imshow(banana_mean, cmap='gray_r')
plt.show()
- 평균값과 가까운 사진 고르기
#절댓값 오차 사용
abs_diff = np.abs(fruits - apple_mean) #abs_diff는 (300,100,100) 크기의 배열
abs_mean = np.mean(abs_diff, axis=(1,2)) #각 샘플에 대한 평균을 구하기 위해 axis 1, 2 모두 지정
print(abs_mean.shape)
(300,)
#값이 작은 순서대로 100개 고르기
apple_index = np.argsort(abs_mean)[:100] # np.argsort() 함수 : 작은 것에서 큰 순서대로 내열한 배열의 인덱스 반환
fig, axs = plt.subplots(10, 10, figsize=(10,10)) # subplots: 10X10 총 100개의 서브 그래프 만들기, figsize: 그래프의 크기(기본값 8,6)
for i in range(10):
for j in range(10):
axs[i, j].imshow(fruits[apple_index[i*10 + j]], cmap='gray_r')
axs[i, j].axis('off') # 깔끔하게 이미지만 그리기 위해 좌표축을 그리지 않음
plt.show()
abs_diff = np.abs(fruits - banana_mean)
abs_mean = np.mean(abs_diff, axis=(1,2))
banana_index = np.argsort(abs_mean)[:100]
fig, axs = plt.subplots(10, 10, figsize=(10,10))
for i in range(10):
for j in range(10):
axs[i, j].imshow(fruits[banana_index[i*10 + j]], cmap='gray_r')
axs[i, j].axis('off')
plt.show()
6-2. k-평균
- k-평균 알고리즘(k-means algorithm) : 처음에 랜덤하게 클러스터 중심을 정하여 클러스터를 만들고 그다음 클러스터의 중심을 이동하여 다시 클러스터를 결정하는 식으로 반복해서 최적의 클러스터를 구성하는 알고리즘.
- 평균값을 자동으로 찾아줌. 이 평균값이 클러스터의 중심에 위치하기 때문에 클러스터 중심 또는 센트로이드(centroid) 라고 부름.
k-평균 알고리즘의 작동 방식
1. 무작위로 k개의 클러스터 중심을 정한다.
2. 각 샘플에서 가장 가까운 클러스터 중심을 찾아 해당 클러스터의 샘플로 지정한다.
3. 클러스터에 속한 샘플의 평균값으로 클러스터 중심을 변경한다.
4. 클러스터 중심에 변화가 없을 때까지 2번으로 돌아가 반복한다.
- KMeans 클래스
!wget https://bit.ly/fruits_300_data -O fruits_300.npy
import numpy as np
fruits = np.load('fruits_300.npy')
fruits_2d = fruits.reshape(-1, 100*100) #k평균 모델을 훈련하기 위해 3차원 배열을 (샘플 개수, 너비X높이) 크기를 가진 2차원 배열로 변경
from sklearn.cluster import KMeans
km = KMeans(n_clusters=3, random_state=42)
km.fit(fruits_2d) #비지도 학습이므로 fit() 함수에서 타깃 데이터를 사용하지 않음
# 군집된 결과, labels_의 길이는 샘플 개수로 각 샘플이 어떤 레이블에 해당되는지 나타냄
print(km.labels_)
[2 2 2 2 2 0 2 2 2 2 2 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 0 2 0 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2 2 0 0 2 2 2 2 2 2 2 2 0 2
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 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 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 1 1 1 1 1 0 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,1,2 로 모은 샘플의 개수 확인
print(np.unique(km.labels_, return_counts=True))
(array([0, 1, 2], dtype=int32), array([111, 98, 91]))
import matplotlib.pyplot as plt
def draw_fruits(arr, ratio=1): # figsize는 ratio=1에 비례하여 커짐
n = len(arr) # n은 샘플 개수입니다
# 한 줄에 10개씩 이미지를 그립니다. 샘플 개수를 10으로 나누어 전체 행 개수를 계산합니다.
rows = int(np.ceil(n/10))
# 행이 1개 이면 열 개수는 샘플 개수입니다. 그렇지 않으면 10개입니다.
cols = n if rows < 2 else 10
fig, axs = plt.subplots(rows, cols,
figsize=(cols*ratio, rows*ratio), squeeze=False)
for i in range(rows):
for j in range(cols):
if i*10 + j < n: # n 개까지만 그립니다.
axs[i, j].imshow(arr[i*10 + j], cmap='gray_r')
axs[i, j].axis('off')
plt.show()
#배열에서 값이 0인 위치는 True 그외는 False, True인 위치의 원소만 모두 추출
draw_fruits(fruits[km.labels_==0])
draw_fruits(fruits[km.labels_==1])
draw_fruits(fruits[km.labels_==2])
- 클러스터 중심
최종적으로 찾은 클러스터 중심은 cluster_centers_ 속성에 저장되어 있음
이 배열은 fruits_2d 샘플의 클러스터 중심이기 때문에 이미지로 출력하려면 100X100 크기의 2차원 배열로 바꿔야 함.
draw_fruits(km.cluster_centers_.reshape(-1, 100, 100), ratio=3)
KMeans 클래스는 훈련 데이터 샘플에서 클러스터 중심까지 거리로 변환해 주는 transform() 메서드를 가지고 있음
-> 특성값을 변환하는 도구로 사용 가능
print(km.transform(fruits_2d[100:101])) #슬라이싱 연산자를 이용해 (1,10000) 크기의 배열 전달
[[3393.8136117 8837.37750892 5267.70439881]]
# 가장 가까운 클러스터 중심을 예측하여 출력(가장 작은 수)
print(km.predict(fruits_2d[100:101]))
[0]
#확인
draw_fruits(fruits[100:101])
# 알고리즘이 반복한 횟수
print(km.n_iter_)
4
- 최적의 k 찾기
- 이너셔(inertia) : k-평균 알고리즘은 클러스터 중심과 클러스터에 속한 샘플 사이의 거리를 잴 수 있는데 이 거리의 제곱 합을 이너셔라고 함. 즉 클러스터의 샘플이 얼마나 가깝게 있는지를 나타내는 값임.
- 엘보우(elbow) : 클러스터 개수를 늘려가면서 이너셔의 변화를 관찰하여 최적의 클러스터 개수를 찾는 방법
#KMeans 클래스는 자동으로 이너셔를 계산해서 inertia_ 속성으로 제공
inertia = []
for k in range(2, 7):
km = KMeans(n_clusters=k, n_init='auto', random_state=42)
km.fit(fruits_2d)
inertia.append(km.inertia_)
plt.plot(range(2, 7), inertia)
plt.xlabel('k')
plt.ylabel('inertia')
plt.show()
엘보우 지점보다 클러스터 개수가 많아지면 이너셔의 변화가 줄어들면서 군집 효과도 줄어듦.
6-3. 주성분 분석
- 차원 축소(dimensionality reduction) : 데이터를 가장 잘 나타내는 일부 특성을 선택하여 데이터 크기를 줄이고 지도 하급 모델의 성능을 향상시킬 수 있는 방법, 줄어든 차원을 다시 원본 차원으로 손실을 최대한 줄이면서 복원할수 있음.
- 주성분 분석(principal component analysis, PCA) : 차원 축소 알고리즘의 하나로 데이터에서 가장 분산이 큰 방향을 찾는 방법이며 이런 방향을 주성분이라 함. 원본 데이터를 주성분에 투영하여 새로운 특성을 만들 수 있음.
주성분은 원본 차원과 같고 주성분으로 바꾼 데이터는 차원이 줄어듦!
일반적으로 원본 특성의 개수만큼 찾을 수 있음.
- PCA 클래스
# 과일 사진 데이터 다운로드 및 넘파이 배열로 적재
!wget https://bit.ly/fruits_300_data -O fruits_300.npy
import numpy as np
fruits = np.load('fruits_300.npy')
fruits_2d = fruits.reshape(-1, 100*100)
from sklearn.decomposition import PCA
pca = PCA(n_components=50) #n_components=50 주성분 개수 지정
pca.fit(fruits_2d) #비지도학습이므로 타깃값 제공 X
#PCA 클래스가 찾은 주성분은 components_ 속성에 저장되어 있음
print(pca.components_.shape)
(50, 10000) #두 번째 차원은 항상 원본 데이터의 특성 개수와 같음
import matplotlib.pyplot as plt
def draw_fruits(arr, ratio=1):
n = len(arr) # n은 샘플 개수입니다
# 한 줄에 10개씩 이미지를 그립니다. 샘플 개수를 10으로 나누어 전체 행 개수를 계산합니다.
rows = int(np.ceil(n/10))
# 행이 1개 이면 열 개수는 샘플 개수입니다. 그렇지 않으면 10개입니다.
cols = n if rows < 2 else 10
fig, axs = plt.subplots(rows, cols,
figsize=(cols*ratio, rows*ratio), squeeze=False)
for i in range(rows):
for j in range(cols):
if i*10 + j < n: # n 개까지만 그립니다.
axs[i, j].imshow(arr[i*10 + j], cmap='gray_r')
axs[i, j].axis('off')
plt.show()
draw_fruits(pca.components_.reshape(-1, 100, 100))
원본 데이터를 주성분에 투영하여 특성의 개수를 10000개에서 50개로 줄일 수 있음
print(fruits_2d.shape)
fruits_pca = pca.transform(fruits_2d) #transform 함수를 사용해 원본 데이터의 차원 줄이기
print(fruits_pca.shape)
(300, 10000)
(300, 50)
- 원본 데이터 재구성
#특성 복원
fruits_inverse = pca.inverse_transform(fruits_pca)
print(fruits_inverse.shape)
fruits_reconstruct = fruits_inverse.reshape(-1, 100, 100) #100X100 크기로 바꾸어 100개씩 나누어 출력
for start in [0, 100, 200]:
draw_fruits(fruits_reconstruct[start:start+100])
print("\n")
- 설명된 분산
- 설명된 분산(explained variance) :주성분이 원본 데이터의 분산을 얼마나 잘 나타내는지 기록한 값
PCA 클래스의 explained_variance_ratio_ 에 각 주성분의 설명된 분산 비율이 기록되어 있음. 첫 번째 주성분의 설명된 분산이 가장 크며, 이 분산 비율을 모두 더하면 50개의 주성분으로 표현하고 있는 총 분산 비율을 얻을 수 있음.
print(np.sum(pca.explained_variance_ratio_))
0.9215651897863715
#설명된 분산의 비율을 그래프로 그려 적절한 주성분의 개수를 찾는 데 도움을 받을 수 있음
plt.plot(pca.explained_variance_ratio_)
- 다른 알고리즘과 함께 사용하기
로지스틱 회귀
#로지스틱 회귀
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
지도 학습 모델을 사용하려면 타깃값이 있어야 함.
target = np.array([0] * 100 + [1] * 100 + [2] * 100)
from sklearn.model_selection import cross_validate
#먼저 원본 데이터인 fruits_2d 사용
scores = cross_validate(lr, fruits_2d, target) #cross_validate로 교차 검증 수행
print(np.mean(scores['test_score']))
print(np.mean(scores['fit_time']))
0.9966666666666667 #교차 검증 점수
1.819899892807007 #교차 검증 폴드의 훈련 시간
scores = cross_validate(lr, fruits_pca, target)
print(np.mean(scores['test_score']))
print(np.mean(scores['fit_time']))
1.0
0.032833099365234375
차원 축소는 저장 공간뿐만 아니라 머신러닝 모델의 훈련 속도도 높일 수 있음
pca = PCA(n_components=0.5) #n_components=0.5 : 설명된 분산의 비율 지정 가능
pca.fit(fruits_2d)
#몇 개의 주성분을 찾았는지 확인
print(pca.n_components_)
2
#원본 데이터 변환
fruits_pca = pca.transform(fruits_2d)
print(fruits_pca.shape)
(300, 2)
scores = cross_validate(lr, fruits_pca, target)
print(np.mean(scores['test_score']))
print(np.mean(scores['fit_time']))
0.9933333333333334 #2개의 특성을 사용했을 뿐인데 정확도 높음
0.03713240623474121
k- 평균 알고리즘
from sklearn.cluster import KMeans
km = KMeans(n_clusters=3, random_state=42)
km.fit(fruits_pca)
print(np.unique(km.labels_, return_counts=True))
(array([0, 1, 2], dtype=int32), array([110, 99, 91]))
#KMeans가 찾은 레이블을 사용해 과일 이미지 출력
for label in range(0, 3):
draw_fruits(fruits[km.labels_ == label])
print("\n")
훈련 데이터의 차원을 줄이면 시각화하기 비교적 쉬워짐
fruits_pca : 2개의 특성이 있기 때문에 2차원으로 표현 가능
# [km.labels_를 사용해 클러스터별로 나누어 산점도 그리기
for label in range(0, 3):
data = fruits_pca[km.labels_ == label]
plt.scatter(data[:,0], data[:,1])
plt.legend(['apple', 'banana', 'pineapple'])
plt.show()
'혼공 ML+DL' 카테고리의 다른 글
4주차 Chapter 05 (트리 알고리즘) (0) | 2024.02.20 |
---|---|
3주차 Chapter 04 (다양한 분류 알고리즘) (1) | 2024.02.13 |
2주차 Chapter 03 (회귀 알고리즘과 모델 규제) (1) | 2024.02.06 |
1주차 Chapter 01 (나의 첫 머신러닝), Chapter 02 (데이터 다루기) (0) | 2024.01.31 |