본문 바로가기

MLOps 개발자 양성과정/ml&dl

[Day-54] chap.06_비지도 학습

 

1. 군집 알고리즘

 <키워드 정리>
비지도 학습
: 훈련 데이터에 타깃이 없어(정답지 없음)
- 스스로 유용한 무언가를 학습해야 해.
- 대표적인 알고리즘 : 군집, 차원 축소

군집(clustering)
: 비슷한 샘플끼리 하나의 그룹으로 모으는 대표적인 비지도 학습 작업
- 군집 알고리즘으로 모은 샘플 그룹을 클러스터(cluster)

 

🔎 npy파일
넘파이 배열의 기본 저장 포맷 npy

이미지 데이터 각 픽셀을 수치화하여 npy 파일로 만들 수 있어

1. 코랩에서 npy 파일 다운받기
=> wget 리눅스 명령어 사용해야 함.
- 코랩은 파이썬 기반으로 이루어져있기 때문에 그냥 쓸 수 없어 !(느낌표) 붙이기
!wget https://bit.ly/fruits_300_data -O fruits_300.npy
-O옵션 저장할 파일 이름 지정
=> 코랩 콘텐츠 폴더에 저장됨(파일 닫으면 

2. 넘파이에서 npy 파일 로드하기
=> 넘파이가 가지고 있는 load() 메서드 사용
import numpy as np
fruits = np.load('fruits_300.npy')
- 동일한 위치에서 실행하면 경로명 없어도 됨.

3. 넘파이 배열로 저장된 이미지 그리기
=> 맷플롯립에 imshow() 함수 사용
import matplotlib.pyplot as plt
plt.imshow(fruits[0], cmap='gray')
plt.show()


+ 그린 이미지 파일로 저장하기
=> PIL패키지의 Image.fromarray()사용하여 numpy배열을 PIL이미지로 변환
=> save()를 통해 저장
import numpy as np

from PIL import Image
im = Image.fromarray(fruits[0])
im.save("filename.jpeg")

 

 

 


2. k-평균

<키워드 정리>
k-평균 알고리즘
: 처음에 랜덤하게 클러스터 중심을 정하고 클러스터를 만든다. 그다음 클러스터의 중심을 이동하고 다시 최적의 클러스터를 구성하는 알고리즘

from sklearn.cluster import KMeans
km = KMeans(n_clusters=3, random_state=42)
- n_clusters 클러스터의 개수 지정(기본값 8)
- 군집된 결과 labels_ 속성에 저장됨 (배열의 길이=샘플의 개수)


클러스터 중심(센트로이드)
: k-평균 알고리즘이 만든 클러스터에 속한 샘플의 특성 평균값
- 가장 가까운 클러스터 중심을 샘플의 또 다른 특성으로 사용하거나 새로운 샘플에 대한 예측으로 활용할 수 있음
- clusters_centers_ 속성에 저장됨
- n_init  최적의 클러스터를 찾기 위해 알고리즘이 반복한 횟수를 지정(기본값 10)
- 최적의 클러스터를 찾기 위해 알고리즘이 반복한 횟수 n_iter_ 속성에 저장됨
- max_iter 한 번 실행에서 최적의 센트로이드를 찾기 위해 반복할 수 있는 최대 횟수(기본값 200)


엘보우 방법
: 클러스터 개수를 늘려가면서 이너셔의 변화를 관찰하여 최적의 클러스터 개수를 찾는 방법
- 클러스터 개수에 따라 이너셔 감소가 꺽이는 지점이 적절한 클러스터 개수k

이너셔
- 클러스터 중심과 샘플 사이 거리의 제곱합
- 클러스터의 샘플이 얼마나 가깝게 모여있는지를 나타내는 값
- 자동으로 이너셔를 계산해 inertia_ 속성에 저장됨

 

 

🔍 k-means 알고리즘으로 군집화하는 5단계

1. 군집의 개수(k) 설정하기
- 몇 개의 군집으로 군집화할지는 사람이 정해야 함
=>군집의 개수 설정을 어떻게 하냐에 따라 결과가 크게 달라짐
=> 군집의 개수 설정을 위한 방법론 존재
① Rule of thumb ② Elbow Method ③ 정보 기준 접근법(Information Criterion Approach)

2. 초기 중심점 설정하기(center of cluster / centeroid = 무게중심)
=> 초기 중심 값 설정을 위한 방법
① Randomly Select ② Manually assign ③ K-means++

3. 데이터를 군집에 할당하기
- 거리 상 가장 가까운 군집(중심점)으로 주어진 모든 데이터를 할당한다.
- 거리 측정 방법은 일반적으로 유클리드 거리(피타고라스)로 측정한다.
(유클리드: / 맨하탄: 빌딩 사이 어떻게 빨리 갈까) 

4. 중심점 재설정하기
- 군집의 중심점을 그 군집에 속하는 데이터들의 중간(평균)에 위치한 지점으로 재설정한다.

5. 데이터를 군집에 재할당하기
- 3에서 했던 방법과 똑같이 시행하며 더 이상 중심점의 이동이 없을 때까지 4와 5를 반복한다.

 

 

 


3. 주성분 분석

 

❓ k-means 알고리즘 한계?

너무 많은 사진이 등록되어 저장공간이 부족해

=> 군집이나 분류에 영향을 끼치지 않으면서 업로드된 사진의 용량를 줄이자

 

=> 주성분 분석

- 크기가 줄어든만큼 처리속도 빨라짐 처리속도 높이자

- 이미지 한 장 2차원 배열 => 1차원 배열로 바꿔

일반적으로 주성분은 원본 특성의 개수만큼 찾을 수 있다.

(ex. 사과 한 개의 특성 10000개)

- 차원을 줄여서 저장공간을 절약하자

- 차원축소알고리즘

 

<키워드 정리>
차원 축소
: 원본 데이터의 특성을 적은 수의 새로운 특성으로 변환하는 비지도 학습의 한 종류
- 저장공간을 줄이고 시각화하기 쉬움
- 다른 알고리즘의 성능을 높일 수도 있음

주성분 분석
: 차원 축소 알고리즘의 하나로 데이터에서 가장 분산이 큰 방향을 찾는 방법
- 원본 데이터를 주성분에 투영하여 새로운 특성을 만들 수 있음
- 일반적으로 주성분은 원본 데이터에 있는 특성 개수보다 작음

from sklearn.decomposition import PCA
pca = PCA(n_components=50)
* 객체 만들 때 주성분 지정해야 함
n_components= 개수 or 0~1 사이의 비율

- PCA 클래스가 찾은 주성분 components_ 속성에 저장됨
- transform() 메서드를 사용해 차원을 줄일 수 있음
- inverse_transform() 메서드를 사용해 차원을 복원할 수 있음(100%x 손실 발생)


설명된 분산
: 주성분 분석에서 주성분이 얼마나 원본 데이터의 분산을 잘 나타내는지 기록한 것
- explained_variance_ratio_에 설명된 분산 비율이 기록됨
- 첫 번째  데이터가 가장 분산이 큼
- 분산 비율을 모두 더하면 총 분산 비율을 얻을 수 있음

 

⚡경고 메시지 

STOP: TOTAL NO. of ITERATIONS REACHED LIMIT
=> 스케일링 부족해서 최대학습을 진행하지 못했다 
 

< 코드정리>

6-1. 
# 300개의 사진을 넘파이 배열로 바꿔서 저장시켜놓은 npy 파일
!wget https://bit.ly/fruits_300_data -O fruits_300.npy

# npy 파일 로드하기   
# 시각화 이미지로 복원할 거야
import numpy as np
import matplotlib.pyplot as plt
fruits = np.load('fruits_300.npy') # 동일한 위치에서 실행하면 경로명 없어도 돼

# 타입 확인
type(fruits) >>> numpy.ndarray # 넘파이의 배열로
print(fruits.shape) >>> (300, 100, 100) # 3차원 배열 (샘플의 개수, 높이, 너비) => 배열의 크기 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] 
# 0검정색 255하얀색

# 넘파이 배열로 저장된 이미지 그리기
plt.imshow(fruits[0], cmap='gray')
plt.show()

#반전 시키기 (데이터는 그대로야)
plt.imshow(fruits[0], cmap='gray_r')
plt.show()

# 그린 이미지 파일로 저장
import numpy as np
from PIL import Image
im = Image.fromarray(fruits[0]) #이미지 클래스 안에 fromarray함수 사용해 변수에 저장
im.save("filename.jpeg") #save 함수 사용해 로컬에 저장

# 하나의 차트에 여러 이미지 출력
fig, axs = plt.subplots(1,2) # 몇 행 몇 열로 쪼개겠다
axs[0].imshow(fruits[100], cmap='gray_r')
axs[1].imshow(fruits[200], cmap='gray_r')
plt.show()
# 0~99 사과 #100~199 파인애플 #200~ 바나나라고 유추,,,

# 픽셀값 분석하기
# 데이터 연산 속도 때문에 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)
# 총 100개의 데이터 / 각각 10000개씩 있어

# axis =1 방향으로 각 데이터에 대한 평균을 구해 # 100개에 대한 각각 평균
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]
                                 
# 히스토그램 그리기
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)
plt.legend(['apple', 'pineapple', 'banana'])
plt.show()

# 다시 axis =0 방향으로 각 픽셀별 평균을 구해
fig, axs = plt.subplots(1, 3, figsize=(20,5))
axs[0].bar(range(10000), np.mean(apple, axis=0)) #bar차트 반드시 x축 y축 값 있어야해
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()
# 평균낸 이미지 합쳐서 뭉게진 것처럼 보여

# 평균값과 가까운 사진 고르기
# 절댓값 오차 사용
abs_diff = np.abs(fruits - apple_mean)
abs_mean = np.mean(abs_diff, axis=(1,2)) # 시각화 위해 차원으로 만들어줘 # 3차원
# abs_mean = np.mean(abs_diff) 는 1차원이야
print(abs_mean.shape)

# 가장 작은 순서대로 100개 골라
# 넘파이 정렬 함수 #기본값 오름차순
apple_index = np.argsort(abs_mean)[:100] # 정렬된 인덱스 번호만 반환(실제값x)
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()

pineapple_index = np.argsort(abs_mean)[100:200] # 정렬된 인덱스 번호만 반환(실제값x)
fig, axs = plt.subplots(10, 10, figsize=(10,10))
for i in range(10):
  for j in range (10):
    axs[i, j].imshow(fruits[pineapple_index[i*10 + j]], cmap='gray_r')
    axs[i, j].axis('off') #축을 꺼줘
plt.show()

banana_index = np.argsort(abs_mean)[200:] # 정렬된 인덱스 번호만 반환(실제값x)
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.
# 6-2.k-means

!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) # 2차원 배열로 변경

# 변수 확인 반드시 하기
print(fruits_2d.shape) >>> (300, 10000)

# k-means 클래스
# 비지도 학습 타깃 데이터 없음
from sklearn.cluster import KMeans
km = KMeans(n_clusters=3, random_state=42) #중심점 3개로 잡았어
km.fit(fruits_2d) # 모델 만들었으면 fit
# labels_배열의 값 0,1,2 세 개 중 하나가 들어갔어 #실제로 모이는 건 x

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]
# 레이블의 순서는 의미 없음
#pc 성능 마다 그 때 그 때 어느걸 먼저 계산하느냐
# 센터 포인트 랜덤하게 잡혀서

# 유니크한 값 몇 개씩 있는지 카운트
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):
  n = len(arr) # 배열의 길이 알아내

  # 한 줄에 10개씩 이미지 그리기 (샘플 개수 10으로 나누어 전체 행 개수 계산)
  rows = int(np.ceil(n/10)) # ceil 올림 나눗셈 들어가면 뒤에 .이 붙어
  # 행이 1개이면 열의 개수는 샘플개수
  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()
############################################### 

draw_fruits(fruits[km.labels_==0])
draw_fruits(fruits[km.labels_==1])
draw_fruits(fruits[km.labels_==2])

# 클러스터 중심
print(km.cluster_centers_) >>> [[1.         1.         1.         ... 1.         1.         1.        ]
                                 [1.10204082 1.07142857 1.10204082 ... 1.         1.         1.        ]
                                 [1.01098901 1.01098901 1.01098901 ... 1.         1.         1.        ]]

print(km.cluster_centers_.shape) >>> (3, 10000)
# 센터포인트 세 개 만들어놨어

draw_fruits(km.cluster_centers_.reshape(-1,100, 100), ratio=3)

# kk-means 자동으로 거리를 확인해주는 transform 메서드
# 특성값을 변환하는 도구로도 사용할 수 있어 (StandardClas처럼)
print(km.transform(fruits_2d[100:101])) >>> [[3393.8136117  8837.37750892 5267.70439881]]

# predict으로 확인
print(km.predict(fruits_2d[100:101])) >>> [0]

draw_fruits(fruits[100:101])

# 최적의 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()
# 엘보우 찾아 센터가 3개일때
# 더 많아지면 별 다른 변화가 없을 거야

 

6-3. 주성분 분석
!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)
pca.fit(fruits_2d)

# PCA 클래스가 찾은 주성분 속성
print(pca.components_.shape) >>> (50, 10000)

########### 과일그리는 함수 #############
import matplotlib.pyplot as plt
def draw_fruits(arr, ratio=1):
  n = len(arr) # 배열의 길이 알아내

  # 한 줄에 10개씩 이미지 그리기 (샘플 개수 10으로 나누어 전체 행 개수 계산)
  rows = int(np.ceil(n/10)) # ceil 올림 나눗셈 들어가면 뒤에 .이 붙어
  # 행이 1개이면 열의 개수는 샘플개수
  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()
##########################################

# 제일 처음 게 비슷하고 가면 갈수록 분산값 이상한 모양으로 보여
draw_fruits(pca.components_.reshape(-1,100,100))

# 원본 데이터 축소
# 특성에 맞춰서 투영시킬 거야
print(fruits_2d.shape) >>> (300, 10000)

fruits_pca = pca.transform(fruits_2d) # 투영시켜
print(fruits_pca.shape) >>> (300, 50)
# 축소 데이터 특성  주요성분 50개

# 원본 데이터 재구성
# 주요성분을 가지고 다시 복원시킬 때 90%이상이지만 어느 정도 손실이 있음(뭉게져)
fruits_inverse = pca.inverse_transform(fruits_pca) # 역으로 복원
print(fruits_inverse.shape) >>> (300, 10000)

fruits_reconstruct = fruits_inverse.reshape(-1, 100, 100)
for start in [0, 100, 200]:
  draw_fruits(fruits_reconstruct[start:start+100])
  print("/n")
  
# 설명된 분산
print(pca.explained_variance_ratio_) >>> [0.42357017 0.09941755 0.06577863 0.04031172 0.03416875 0.03281329
                                         0.02573267 0.02054963 0.01372276 0.01342773 0.01152146 0.00944596
                                         0.00878232 0.00846697 0.00693049 0.00645188 0.00578895 0.00511202
                                         0.0048638  0.00480344 0.00447837 0.00437314 0.00408041 0.00389479
                                         0.00372431 0.00359213 0.00331452 0.00317816 0.0030432  0.0030368
                                         0.00288899 0.00275878 0.00264793 0.00255721 0.00252146 0.00246913
                                         0.00239401 0.00230534 0.00220818 0.00215509 0.00213161 0.00196391
                                         0.00193053 0.00190962 0.00184563 0.00182855 0.00170514 0.00167385
                                         0.00163105 0.00159452]
                                         
print(np.sum(pca.explained_variance_ratio_)) >>> 0.9214965776590037
#합해봤더니 92%넘는 분산값 유지

plt.plot(pca.explained_variance_ratio_)
plt.show()


############### 다른 알고리즘과 함께 사용하기 ############

######## 로지스틱
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()

# 로지스틱 지도학습 정답지 있어야 해
target = np.array([0]*100 + [1]*100 + [2]*100)
# 우리 데이터 100개씩 사과/파인애플/바나나 나눠져있어서 가능..

# 교차검증 수행
from sklearn.model_selection import cross_validate
scores = cross_validate(lr, fruits_2d, target) #교차 검증 (어느 모델을 할 거야, 원본 데이터, 정답지)
print(np.mean(scores['test_score'])) >>> 0.9966666666666667
print(np.mean(scores['fit_time'])) >>> 1.3755132675170898

# 주성분에 투영시킨 데이터 # 축소데이터 가지고 교차검증
scores = cross_validate(lr, fruits_pca, target)
print(np.mean(scores['test_score'])) >>> 1.0
print(np.mean(scores['fit_time'])) >>> 0.024224376678466795
# 시간 많이 단축되었어 #fit_time은 코랩속도 컴퓨터마다 달라

# n_components 매개변수 설정
# 정수로 넣으면 개수 # 0~1 사이의 실수로 넣으면 비율
pca = PCA(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']))
# 경고 메시지 달라
# 1. 반복횟수 2. 스케일링 부족해서 최대학습을 진행하지 못했다 


############## k-means
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]))

# 과일 이미지 출력
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()