반응형
5주차 후기
6장에서 k-평균 알고리즘을 공부하면서 제공된 정답 데이터가 없음에도 불구하고, 최적의 클러스터 개수를 찾아 학습하는 과정이 재미있고 흥미로웠다. (신기했다는 표현이 더 맞을 것 같다.)
그치만 주성분 분석의 내용은 이해했지만 세부 원리는 잘 이해가 되지 않았다.
과제를 제출하고 320~322 쪽을 다시 읽어보고, 관련 자료도 좀 더 찾아봐야겠다.
그리고 이번 장은 공부하면서 "이건 업무에 적용할 수 있지 않을까?"라는 생각을 했다. (물론 더 깊게 공부해봐야 겠지만...)
현업에서 활용할 만한 부분이 있을 것 같은 느낌이 드니 공부할 때도 좀 더 즐겁고 집중하며 볼 수 있었다.
어떻게 쓸 수 있을지 고민해봐야겠다.
이제 마지막 6주차가 남았다.
다음주부터 리프레시 휴가로 설날까지 쭉 쉬니 7~9장까지 딥러닝 내용을 쭉 완독해봐야 겠다.
5주차 과제
기본 미션
k-평균 알고리즘 작동 방식 설명하기
- 8개의 점이 존재한다고 가정한다.
- 클러스터의 개수인 k 값을 정한다.
- 데이터에서 무작위로 k 개의 클러스터 중심(centroid)을 설정한다.
- 각 클러스터 중심에서 가까운 데이터들을 클러스터로 묶는다.
- (3)번 과정에서 새로 형성된 클러스터의 중심(centroid)를 다시 계산한다.
- (3)번과 (4)번 과정을 반복한다.
- 다음과 같은 증상이 발생하면 반복을 멈춘다.
- 새로 형성된 클러스터의 중심이 더 이상 변하지 않을 때
- 데이터들이 동일한 클러스터에 계속 남을 때
- 최대 반복 횟수에 다다를 때
참고자료 : The Ultimate Guide to K-Means Clustering
선택 미션
Ch.06(06-3) 확인 문제 풀고, 풀이 과정 정리하기
비지도 학습
- 지도 학습 (Supervised Learning)
- 훈련 데이터(Training Data)로부터 하나의 함수를 유추해내기 위한 기계 학습(Machine Learning)의 한 방법이다.
- 훈련 데이터는 일반적으로 입력 객체에 대한 속성을 벡터 형태로 포함하고 있으며 각각의 벡터에 대해 원하는 결과가 무엇인지 표시되어 있다.
- 이렇게 유추된 함수 중 연속적인 값을 출력하는 것을 회귀분석(Regression)이라 하고 주어진 입력 벡터가 어떤 종류의 값인지 표식하는 것을 분류(Classification)라 한다.
- 비지도 학습 (Unsupervised Learning)
- 타깃(정답)이 없을 때 사용하는 머신러닝 알고리즘
- 사람이 가르쳐 주지 않아도 데이터에 있는 무언가를 학습한다.
군집 알고리즘
# 과일 사진 데이터 다운로드
!wget https://bit.ly/fruits_300_data -O fruits_300.npy
--2024-02-02 13:49:15-- https://bit.ly/fruits_300_data Resolving bit.ly (bit.ly)... 67.199.248.11, 67.199.248.10 Connecting to bit.ly (bit.ly)|67.199.248.11|:443... connected. HTTP request sent, awaiting response... 301 Moved Permanently Location: https://github.com/rickiepark/hg-mldl/raw/master/fruits_300.npy [following] --2024-02-02 13:49:15-- https://github.com/rickiepark/hg-mldl/raw/master/fruits_300.npy Resolving github.com (github.com)... 140.82.113.4 Connecting to github.com (github.com)|140.82.113.4|:443... connected. HTTP request sent, awaiting response... 302 Found Location: https://raw.githubusercontent.com/rickiepark/hg-mldl/master/fruits_300.npy [following] --2024-02-02 13:49:15-- https://raw.githubusercontent.com/rickiepark/hg-mldl/master/fruits_300.npy Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.108.133, 185.199.109.133, 185.199.110.133, ... Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.108.133|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 3000128 (2.9M) [application/octet-stream] Saving to: ‘fruits_300.npy’ fruits_300.npy 100%[===================>] 2.86M --.-KB/s in 0.07s 2024-02-02 13:49:16 (43.6 MB/s) - ‘fruits_300.npy’ saved [3000128/3000128]
import numpy as np
import matplotlib.pyplot as plt
# 다운로드 한 fruits_300.npy 파일 불러오기
fruits = np.load('fruits_300.npy')
print(f"""# 배열 크기 확인
배열 크기 : {fruits.shape}
- 첫 번째 차원 : 샘플의 개수
- 두 번째 차원 : 이미지 높이
- 세 번째 차원 : 이미지 너비
""")
# 배열 크기 확인 배열 크기 : (300, 100, 100) - 첫 번째 차원 : 샘플의 개수 - 두 번째 차원 : 이미지 높이 - 세 번째 차원 : 이미지 너비
# 첫 번째 이미지 출력
print("# 첫 번째 이미지 출력")
plt.imshow(fruits[0], cmap='gray')
plt.show()
# 첫 번째 이미지 출력
# 첫 번째 이미지의 첫 번째 행 출력
print(f"""# 첫 번째 이미지의 첫 번째 행 출력
{fruits[0, 0, :]}
- 값이 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] - 값이 0에 가까우면 검게 나타나고, 높은 값은 밝게 표시된다. - 원래는 사과가 어두운 색이고, 바탕이 밝은 색이지만, 넘파이 배열로 변환할 때 반전되었다. - 알고리즘이 출력을 만들기 위해 덧셈, 곱셈을 하는데 픽셀값이 0이 되면 의미가 없다. 그리고 픽셀값이 높을 수록 출력값이 커져서 의미를 부여하기 좋다. 우리의 관심사는 "사과"이기 때문에 사과의 값을 큰 값(밝은색)으로 반전한다.
# 첫 번째 이미지 값을 재반전 하여 출력
print("# 첫 번째 이미지 재반전 출력")
plt.imshow(fruits[0], cmap='gray_r')
plt.show()
# 첫 번째 이미지 재반전 출력
# matplotlib의 subplots() 함수를 사용하여 여러 개의 그래프를 배열로 쌓기
fig, axs = plt.subplots(1, 2)
axs[0].imshow(fruits[100], cmap='gray_r')
axs[1].imshow(fruits[200], cmap='gray_r')
plt.show()
# 픽셀값 분석하기
# - 사과, 파인애플, 바나나 100개씩
# - 하나의 100x100 이미지를 펼쳐서 길이가 10,000인 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(f"""# 배열의 크기
apple: {apple.shape}
pineapple: {pineapple.shape}
banana: {banana.shape}
""")
# 배열의 크기 apple: (100, 10000) pineapple: (100, 10000) banana: (100, 10000)
# 픽셀 평균값 구하기
# - axis = 0 : 행
# - axis = 1 : 열
print(f"""# 픽셀 평균값
apple: {apple.mean(axis=1)}
pineapple: {pineapple.mean(axis=1)}
banana: {banana.mean(axis=1)}
""")
# 픽셀 평균값 apple: [ 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] pineapple: [ 99.3239 87.1622 97.193 97.4689 98.8892 97.8819 95.1444 92.9461 95.8412 96.9487 93.2863 101.2771 91.6511 98.3901 84.3277 100.7017 99.0229 87.5298 99.4109 91.8568 90.3877 99.5066 95.8498 96.0728 100.0062 95.5283 95.2715 98.1624 93.1395 99.1666 99.9519 96.732 94.16 100.7644 101.1263 98.2852 98.5354 101.3809 95.4862 93.1256 87.6404 93.7146 94.7279 85.5131 95.0937 95.3236 92.7696 94.4375 99.36 95.8924 97.8221 99.4014 99.325 95.025 97.5771 97.3511 99.4891 97.366 101.0062 91.2304 95.2824 95.3544 101.4657 97.5239 98.8419 96.746 98.2922 96.3969 81.9464 93.4927 97.3872 82.4883 95.3665 95.2541 101.3074 97.2391 95.4544 100.5576 100.2927 94.3088 95.7401 97.0982 96.9559 92.9114 97.4164 100.769 97.808 99.2481 101.0643 94.8173 99.2639 98.8539 97.2198 97.0201 94.5039 98.097 95.0201 98.8078 99.4634 100.0257] banana: [19.5487 24.4884 36.7517 18.568 53.572 31.5789 51.9062 28.4703 23.7856 32.1295 29.1737 24.5578 27.7676 41.3082 61.0437 32.6444 38.4187 30.4175 30.0639 21.4994 32.6018 39.6335 47.8397 57.7484 33.6511 23.5018 49.6817 44.0855 46.1559 33.4963 36.0099 69.0751 33.9575 32.8786 60.1911 32.9949 45.3359 56.1694 36.9218 25.6754 32.7901 29.0245 57.7134 44.6563 31.2138 29.836 40.9228 23.7501 43.1543 32.3716 26.0986 55.6189 22.2269 29.5089 24.0435 22.6247 24.1709 46.2714 32.0763 42.5076 44.5522 24.0199 27.68 32.4377 19.2198 22.5083 19.6742 26.1852 25.8368 25.2676 26.4721 34.8503 28.5235 80.8545 36.9371 47.3975 18.7909 33.0428 37.8222 34.7819 33.6051 24.3963 32.7988 19.7609 32.538 28.5407 23.0999 26.3778 27.1965 38.7848 18.7648 30.2297 54.3816 37.9073 32.3126 31.0664 33.1657 37.6051 38.1806 26.2805]
# 픽셀 평균값에 대한 히스토그램
plt.hist(np.mean(apple, axis=1), alpha=0.8)
plt.hist(np.mean(pineapple, axis=1), alpha=0.8)
plt.hist(np.mean(banana, axis=1), alpha=0.8)
print(f""" 결과 해석
- 바나나 사진의 평균값은 40에 집중되어 있음
- 사과와 파인애플 사진의 평균값은 90~100 사이에 모여 있음
- 따라서 픽셀 평균값만으로도 바나나를 구분할 수 있고, 사과와 파인애플은 구분하기 어려움
""")
결과 해석 - 바나나 사진의 평균값은 40에 집중되어 있음 - 사과와 파인애플 사진의 평균값은 90~100 사이에 모여 있음 - 따라서 픽셀 평균값만으로도 바나나를 구분할 수 있고, 사과와 파인애플은 구분하기 어려움
# 픽셀별 평균값 구하기
# - 전체 샘플에 대한 각 픽셀의 평균 계산 > 과일마다 모양이 다르므로 픽셀값이 높은 위치가 다름
fig, axs = plt.subplots(1, 3, figsize=(20,5))
axs[0].bar(range(10000), np.mean(apple, axis=0))
axs[1].bar(range(10000), np.mean(pineapple, axis=0))
axs[2].bar(range(10000), np.mean(banana, axis=0))
plt.show()
# 과일 별 모든 사진을 합쳐서 출력하기
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()
###
# 평균값과 가까운 사진 고르기
###
print("# 사과 사진 100개 찾기")
# 사과 샘플에 대한 평균
abs_diff = np.abs(fruits - apple_mean)
abs_mean = np.mean(abs_diff, axis=(1,2))
print(abs_mean.shape)
# 사과 샘플 평균값과 오차가 가장 작은 샘플 100개 선정
apple_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[apple_index[i*10 + j]], cmap='gray_r')
axs[i, j].axis('off')
plt.show()
# 사과 사진 100개 찾기 (300,)
- 군집(Clustering)
- 비슷한 샘플끼리 그룹으로 모으는 작업
- 대표적인 비지도 학습 작업 중 하나
- 군집 알고리즘에서 만든 그룹을
클러스터(Cluster)
라고 한다.
k-평균(k-means) 군집 알고리즘
개요
- 자동으로 평균값을 찾아주는 군집 알고리즘
- 평균값이 클러스터의 중심에 위치하기 때문에
클러스터 중심(cluster center)
또는센트로이드(centroid)
라고 부른다.
작동 방식
- 무작위로 k개의 클러스터 중심을 정한다.
- 각 샘플에서 가장 가까운 클러스터 중심을 찾아 해당 클러스터의 샘플로 지정한다.
- 클러스터에 속한 샘플의 평균값으로 클러스터 중심을 변경한다.
- 클러스터 중심에 변화가 없을 때까지 2번으로 돌아가 반복한다.
KMeans 클래스
# 데이터 다운로드
!wget https://bit.ly/fruits_300_data -O fruits_300.npy
--2024-02-02 13:50:57-- https://bit.ly/fruits_300_data Resolving bit.ly (bit.ly)... 67.199.248.11, 67.199.248.10 Connecting to bit.ly (bit.ly)|67.199.248.11|:443... connected. HTTP request sent, awaiting response... 301 Moved Permanently Location: https://github.com/rickiepark/hg-mldl/raw/master/fruits_300.npy [following] --2024-02-02 13:50:57-- https://github.com/rickiepark/hg-mldl/raw/master/fruits_300.npy Resolving github.com (github.com)... 140.82.113.4 Connecting to github.com (github.com)|140.82.113.4|:443... connected. HTTP request sent, awaiting response... 302 Found Location: https://raw.githubusercontent.com/rickiepark/hg-mldl/master/fruits_300.npy [following] --2024-02-02 13:50:58-- https://raw.githubusercontent.com/rickiepark/hg-mldl/master/fruits_300.npy Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.108.133, 185.199.109.133, 185.199.110.133, ... Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.108.133|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 3000128 (2.9M) [application/octet-stream] Saving to: ‘fruits_300.npy’ fruits_300.npy 100%[===================>] 2.86M --.-KB/s in 0.07s 2024-02-02 13:50:58 (40.7 MB/s) - ‘fruits_300.npy’ saved [3000128/3000128]
# npy 파일을 읽어 넘파이 배열 생성
import numpy as np
fruits = np.load('fruits_300.npy')
fruits_2d = fruits.reshape(-1, 100*100)
# 사이킷런의 k-평균 알고리즘 : KMeans 클래스
from sklearn.cluster import KMeans
km = KMeans(n_clusters=3, random_state=42)
km.fit(fruits_2d)
print(f"""# 군집 결과 - 각 샘플이 어떤 레이블에 해당하는지 나타낸다. (n_clusters=3이기 때문에 0, 1, 2 중 하나의 값으로 표시)
{km.labels_}
""")
print(f"""# 각 군집 별 개수
{np.unique(km.labels_, return_counts=True)}
""")
# 군집 결과 - 각 샘플이 어떤 레이블에 해당하는지 나타낸다. (n_clusters=3이기 때문에 0, 1, 2 중 하나의 값으로 표시) [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] # 각 군집 별 개수 (array([0, 1, 2], dtype=int32), array([111, 98, 91]))
import matplotlib.pyplot as plt
def draw_fruits(arr, ratio=1):
# 샘플 개수
n = len(arr)
# 한 줄에 10개씩 이미지 출력
rows = int(np.ceil(n/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:
axs[i, j].imshow(arr[i*10 + j], cmap='gray_r')
axs[i, j].axis('off')
plt.show()
# 레이블이 0인 과일 사진 출력
draw_fruits(fruits[km.labels_==0])
# 레이블이 1인 과일 사진 출력
draw_fruits(fruits[km.labels_==1])
# 레이블이 2인 과일 사진 출력
draw_fruits(fruits[km.labels_==2])
클러스터 중심
- KMeans 클래스가 최종적으로 찾은 클러스터 중심은
cluster_centers_
속성에 저장되어 있다. - KMeans 클래스는 훈련 데이터 샘플에서 클러스터 중심까지 거리로 변환해주는
transform()
메서드를 제공한다.
draw_fruits(km.cluster_centers_.reshape(-1, 100, 100), ratio=3)
# 인덱스가 100인 샘플에 transform() 메서드 적용
print(f"""# 인덱스 100인 샘플의 각 레이블 별 클러스터 중심까지의 거리
{km.transform(fruits_2d[100:101])}
# 인덱스 100인 샘플의 가장 가까운 클러스터 중심 레이블
{km.predict(fruits_2d[100:101])}
# 인덱스 100인 샘플 이미지""")
draw_fruits(fruits[100:101])
print(f"\n# k-평균 알고리즘이 최적의 클러스터를 찾기 위해 반복한 횟수 : {km.n_iter_}")
# 인덱스 100인 샘플의 각 레이블 별 클러스터 중심까지의 거리 [[3393.8136117 8837.37750892 5267.70439881]] # 인덱스 100인 샘플의 가장 가까운 클러스터 중심 레이블 [0] # 인덱스 100인 샘플 이미지 # k-평균 알고리즘이 최적의 클러스터를 찾기 위해 반복한 횟수 : 4
최적의 k 찾기
- k-평균 알고리즘의 단점
- 클러스터 개수를 사전에 지정해야 함
하지만 실전에서는 몇 개의 클러스터가 있는지 알 수 없음
- 클러스터 개수를 사전에 지정해야 함
- 군집 알고리즘에서 적절한 k 값을 찾기 위한 완벽한 방법은 없음
- 이너셔(inertia)
- 클러스터에 속한 샘플이 얼마나 가깝게 모여 있는지를 나타내는 값
- 클러스터 중심과 클러스터에 속한 샘플 사이의 거리의 제곱 합
- 엘보우(elbow) 방법
- 클러스터 개수를 늘려가면서 이너셔(inertia)의 변화를 관찰하여 최적의 클러스터 개수를 찾는 방법
- 클러스터 개수를 증가시키면서 이너셔를 그래프로 그리면 감소하는 속도가 꺽이는 지점이 있는데, 이 지점의 클러스터 개수를 최적의 k 값으로 선정
inertia = []
for k in range(2, 7):
km = KMeans(n_clusters=k, 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()
print("# 최적의 클러스터 개수 : 3 (그래프의 기울기가 가장 바뀐 지점)")
# 최적의 클러스터 개수 : 3 (그래프의 기울기가 가장 바뀐 지점)
주성분 분석
차원
- 데이터가 가진 속성 = 특성 = 차원(dimension)
- 1차원 배열 : 1차원 배열은 벡터라고 하고, 원소의 개수를 차원이라고 한다.
- 다차원 배열 : 배열의 축 개수를 차원이라고 한다. 2차원 배열은 행과 열이 차원이다.
- 차원을 줄이면 저장 공간을 절약할 수 있다. → 차원 축소
차원 축소 (dimensionality reduction)
- 특성이 많으면 선형 모델의 성능이 높아지고, 훈련 데이터에 쉽게 과대적합 된다.
- 차원 축소
- 데이터를 가장 잘 나타내는 일부 특성을 선택하여 데이터 크기를 줄인다.
- 지도 학습 모델의 성능을 향상시킬 수 있는 방법
- 대표적인 차원 축소 알고리즘 : 주성분 분석(PCA, Principal Component Analysis)
주성분 분석 (PCA, Principal Component Analysis)
- 데이터에 있는 분산이 큰 방향을 찾는 것
- 분산이 큰 방향 = 데이터를 잘 표현하는 어떤 벡터 =
주성분(principal component)
- 주성분을 사용하여 원본 데이터의 차원을 줄일 수 있다.
- 주성분은 원본 차원과 같고, 주성분으로 바꾼 데이터는 차원이 줄어든다.
- 일반적으로 원본 특성의 개수만큼 주성분을 찾을 수 있다.
PCA 클래스
- 사이킷런의
PCA 클래스
를 사용해 주성분 분석을 할 수 있다. - 비지도 학습이므로
fit()
메서드에 타깃값을 제공하지 않는다.
# 과일 사진 데이터 다운로드
!wget https://bit.ly/fruits_300_data -O fruits_300.npy
--2024-02-02 13:51:33-- https://bit.ly/fruits_300_data Resolving bit.ly (bit.ly)... 67.199.248.11, 67.199.248.10 Connecting to bit.ly (bit.ly)|67.199.248.11|:443... connected. HTTP request sent, awaiting response... 301 Moved Permanently Location: https://github.com/rickiepark/hg-mldl/raw/master/fruits_300.npy [following] --2024-02-02 13:51:33-- https://github.com/rickiepark/hg-mldl/raw/master/fruits_300.npy Resolving github.com (github.com)... 140.82.112.4 Connecting to github.com (github.com)|140.82.112.4|:443... connected. HTTP request sent, awaiting response... 302 Found Location: https://raw.githubusercontent.com/rickiepark/hg-mldl/master/fruits_300.npy [following] --2024-02-02 13:51:34-- https://raw.githubusercontent.com/rickiepark/hg-mldl/master/fruits_300.npy Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.108.133, 185.199.109.133, 185.199.110.133, ... Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.108.133|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 3000128 (2.9M) [application/octet-stream] Saving to: ‘fruits_300.npy’ fruits_300.npy 100%[===================>] 2.86M --.-KB/s in 0.07s 2024-02-02 13:51:34 (42.0 MB/s) - ‘fruits_300.npy’ saved [3000128/3000128]
# 과일 사진을 넘파이 배열로 적재
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)
pca.fit(fruits_2d)
print(f"""# PCA 클래스가 찾은 주성분 배열의 크기
{pca.components_.shape}
""")
print("# 주성분 그림 출력")
draw_fruits(pca.components_.reshape(-1, 100, 100))
print()
# PCA 클래스가 찾은 주성분 배열의 크기 (50, 10000) # 주성분 그림 출력
# 원본 데이터를 주성분에 투영하여 차원을 축소 (10,000개 -> 50개)
print("# 차원 축소")
print(f"before : {fruits_2d.shape}")
fruits_pca = pca.transform(fruits_2d)
print(f"after : {fruits_pca.shape}")
output # 차원 축소 before : (300, 10000) after : (300, 50)
원본 데이터 재구성
- PCA 클래스의
inverse_transform()
메서드 사용
fruits_inverse = pca.inverse_transform(fruits_pca)
print(f"""# 원본 데이터 재구성
before : {fruits_pca.shape}
after : {fruits_inverse.shape}
""")
fruits_reconstruct = fruits_inverse.reshape(-1, 100, 100)
for start in [0, 100, 200]:
draw_fruits(fruits_reconstruct[start:start+100])
print("\n")
# 원본 데이터 재구성 before : (300, 50) after : (300, 10000)
설명된 분산(explained variance)
설명된 분산
: 주성분이 원본 데이터의 분산을 얼마나 잘 나타내는지 기록한 값explained_variance_ratio_
값으로 주성분의 설명된 분산 비율을 볼 수 있다.- 첫 번째 주성분의 설명된 분산이 가장 크다.
- 분산 비율을 모두 더하면 총 분산 비율을 얻을 수 있다.
print(f"# 총 분산 비율 : {np.sum(pca.explained_variance_ratio_) * 100}%\n")
print("# 설명된 분산을 그래프로 출력")
plt.plot(pca.explained_variance_ratio_)
plt.show()
print("=> 처음 10개의 주성분이 대부분의 분산을 표현한다.")
# 총 분산 비율 : 92.15811232595493% # 설명된 분산을 그래프로 출력 => 처음 10개의 주성분이 대부분의 분산을 표현한다.
다른 알고리즘과 함께 사용하기
과일 사진 원본 데이터
와PCA로 축소한 데이터
를 지도 학습에 적용해 보고, 어떤 차이가 있는지 알아보기 → 로지스틱 회귀 모델 사용
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression(max_iter=1000)
# 타깃값 생성 (0: 사과, 1: 파인애플, 2: 바나나)
target = np.array([0]*100 + [1]*100 + [2]*100)
print(f"""# 타깃값 생성
{target}
""")
# 타깃값 생성 [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 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 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 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2]
from sklearn.model_selection import cross_validate
# 원본 데이터
scores = cross_validate(lr, fruits_2d, target)
print(f"""# 원본 데이터 - 로지스틱 회귀 모델 교차 검증 결과
test_score : {np.mean(scores['test_score'])}
fit_time : {np.mean(scores['fit_time'])}
""")
# PCA로 축소한 데이터
scores = cross_validate(lr, fruits_pca, target)
print(f"""# PCA로 축소한 데이터 - 로지스틱 회귀 모델 교차 검증 결과
test_score : {np.mean(scores['test_score'])}
fit_time : {np.mean(scores['fit_time'])}
""")
print("=> PCA로 특성을 50개로 축소한 데이터의 정확도가 더 높거나 같고, 훈련 시간도 훨씬 빠르다.")
# 원본 데이터 - 로지스틱 회귀 모델 교차 검증 결과 test_score : 0.9966666666666667 fit_time : 1.981255006790161 # PCA로 축소한 데이터 - 로지스틱 회귀 모델 교차 검증 결과 test_score : 0.9966666666666667 fit_time : 0.04311800003051758 => PCA로 특성을 50개로 축소한 데이터의 정확도가 더 높고, 훈련 시간도 훨씬 빠르다.
# 직접 주성분 개수를 지정하는 대신 <설명된 분산의 비율>을 지정하면,
# PCA 클래스는 지정된 비율에 도달할 때까지 자동으로 주성분을 찾는다.
pca = PCA(n_components=0.5)
pca.fit(fruits_2d)
print(f"# 자동으로 찾은 주성분 개수 : {pca.n_components_}\n")
# 원본 데이터 변환
fruits_pca = pca.transform(fruits_2d)
print(f"# 변환된 데이터 크기 : {fruits_pca.shape}")
# 교차 검증
scores = cross_validate(lr, fruits_pca, target)
print(f"교차 검증 점수 : {np.mean(scores['test_score'])}")
print(f"교차 검증 수행 시간 : {np.mean(scores['fit_time'])}")
# 자동으로 찾은 주성분 개수 : 2 # 변환된 데이터 크기 : (300, 2) 교차 검증 점수 : 0.9966666666666667 교차 검증 수행 시간 : 0.04672560691833496
# 차원 축소된 데이터를 사용해 k-평균 알고리즘으로 클러스터 찾기
from sklearn.cluster import KMeans
km = KMeans(n_clusters=3, random_state=42, n_init='auto')
km.fit(fruits_pca)
print(np.unique(km.labels_, return_counts=True))
(array([0, 1, 2], dtype=int32), array([110, 99, 91]))
for label in range(0, 3):
draw_fruits(fruits[km.labels_ == label])
print("\n")
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()
반응형
'프로그래밍 > AI' 카테고리의 다른 글
[혼공머신 11기] 혼공학습단 11기 활동 회고 (1) | 2024.02.18 |
---|---|
[혼공머신 11기] 6주차 스터디 및 과제 (0) | 2024.02.18 |
[혼공머신 11기] 4주차 스터디 및 과제 (1) | 2024.01.28 |
[혼공머신 11기] 3주차 스터디 및 과제 (0) | 2024.01.20 |
[혼공머신 11기] 2주차 스터디 및 과제 (1) | 2024.01.13 |