ML 세션 - Week6.비지도 학습

» AI

정답

  1. 0, 클러스터 중심까지의 거리가 가장 가까워서
  2. n_clusters=k, inertia, k=3
  3. pca.transform(fruits_2d), pca.inverse_transform(fruits_pca)
  4. 주성분이 원본 데이터의 분산을 얼마나 잘 나타내는지 기록한 값, n_components=0.5
  5. 110,99,91, 비슷하다

비지도 학습

타겟 데이터 없을 때 사용하는 머신러닝 알고리즘

  • 군집이란 비슷한 샘플끼리 그룹으로 모으는 작업
    • 대표적인 비지도 학습 작업
  • 클러스터란 군집 알고리즘에서 만든 그룹

데이터 준비

  •   !wget https://bit.ly/fruits_300_data -O fruits_300.npy
    
      import numpy as np
      import matplotlib.pyplot as plt
      fruits = np.load('fruits_300.npy') # 넘파이에서 npy 파일 로드
      print(fruits.shape) # (300, 100, 100) -> 100 * 100 이미지 300개
    
      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에 가까울 수록 검은 색 높을수록 흰색
    
      plt.imshow(fruits[0], cmap='gray') 
      # imshow()함수로 넘파이 배열로 저장된 이미지 그리기
      # 흑백 이미지라 cmap 매개변수 gray
      plt.show()
    
  • alt text
  • alt text
  • 컴퓨터는 흰색에 집중함 -> 바탕을 검은색, 물질을 희게 반전
    • alt text
    •   plt.imshow(fruits[0], cmap='gray_r') # gray_r로 다시 반전
        plt.show()
      
    • alt text
  •   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()
    
  • alt text

픽셀값 분석

  • 사용하기 쉽게 데이터 가공
  • alt text
  •   # apple pineapple banana로 데이터 나누기
      # reshape()로 두 번째 차원과 세 번째 차원 합치기
      # 첫 번째 차원 -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)
    
  • 샘플의 픽셀 평균값 계산
    • mean() 메서드 이용
    •   print(apple.mean(axis=1)) 
        # axis=0이면 행을 따라 계산, 1이면 열을 따라 계산
        # 가로로 값 나열해서 axis=1로
      
    • alt text
    • 사과, 파인애플, 바나나 히스토그램 겹쳐 보기
    •   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()
      
    • alt text
    • 사과랑 파인애플 구분이 어려움
  • 픽셀별 평균값 비교
    • 과일의 모양에 따라 픽셀값이 높은 위치가 다를 것
    • axis=0으로 픽셀 평균 계산
    •   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))
        axs[2].bar(range(10000), np.mean(banana, axis=0))
        plt.show()
      
    • alt text
    • 사과는 사진 아래쪽으로 갈수록 값이 높아짐
    • 파인애플은 비교적 고르면서 높음
    • 바나나는 중앙 픽셀값이 높음
    • 픽셀 평균값 이미지로 출력해서 비교하기
    •   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()
      
    • alt text

평균값과 가까운 사진 고르기

  • 절댓값 오차 사용
    • fruits 배열 샘플 - 평균
    •   abs_diff = np.abs(fruits - apple_mean) # abs()로 절댓값 계산
        abs_mean = np.mean(abs_diff, axis=(1,2))
      
        apple_index = np.argsort(abs_mean)[:100]
        # argsort()는 작은 것에서 큰 순서대로 나열한 인덱스 반환 -> 평군과 오차가 가장 작은 샘플 100개 고름
        fig, axs = plt.subplots(10, 10, figsize=(10,10)) # 그래프가 많아서 figsize으로 전체 그래프 크기를 조금 크게 지정
        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') # axis('off')로 좌표축 생략
        # 10 * 10 격자 그래프 그리기 
        plt.show()
      
    • alt text

k-means 알고리즘

  1. 무작위로 k개의 클러스터 중심 정하기
  2. 각 샘플에서 가장 가까운 클러스터 중심 찾아 해당 클러스터 샘플로 지정
  3. 클러스터에 속한 샘플의 평균값으로 클러스터 중심 변경
  4. 클러스터 중심에 변화가 없을 때까지 2번으로 돌아가 반복

alt text

  •   fruits_2d = fruits.reshape(-1, 100*100)
      from sklearn.cluster import KMeans
      km = KMeans(n_clusters=3, random_state=42) # n_clusters로 클러스터 개수 지정
      km.fit(fruits_2d)
      print(km.labels_) # labels: 샘플이 어떤 레이블에 속하는지
      print(np.unique(km.labels_, return_counts=True))
      # (array([0, 1, 2], dtype=int32), array([111,  98,  91]))
      # 0: 91개, 1: 98개, 2: 111개
    
      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()
      # 3차원 배열 입력 받아 가로로 10개씩 이미지 출력
      # figsize는 ratio에 비례하여 커짐
    
      draw_fruits(fruits[km.labels_==0]) # 값이 0인 위치는 true, 그 외는 false
      draw_fruits(fruits[km.labels_==1])
      draw_fruits(fruits[km.labels_==2])
    
  • alt text
  • alt text
  • alt text

클러스터 중심

  • cluster_centers_ 속성에 저장
  •   draw_fruits(km.cluster_centers_.reshape(-1, 100, 100), ratio=3)
    
  • alt text
  •   print(km.transform(fruits_2d[100:101])) # transform()은 훈련 데이터 샘플에서 클러스터 중심까지 거리로 변환
      # [[3393.8136117  8837.37750892 5267.70439881]] : 세 번째 클러스터까지의 거리가 가장 작음 -> 레이블 2
    
      print(km.predict(fruits_2d[100:101])) # 2
      draw_fruits(fruits[100:101]) # 파인애플임
    
      print(km.n_iter_) # n_iter_에 클러스터 중심을 옮기면서 반복한 횟수 저장
    

최적의 k 찾기

  • 엘보우 방법
    • 이너셔: 클러스터 중심과 샘플 사이의 거리의 제곱 합
      • 클러스터에 속한 샘플이 얼마나 가깝게 모여 있는지 나타냄
      • 클러스터 개수가 늘어나면 클러스터의 각각의 크기는 줄어들면서 이너셔도 줄어듦
      • alt text
      • 꺾이는 지점부터는 클러스터 개수를 늘려도 밀집된 정도가 크게 개선되지 않음
      • 이 꺾이는 지점이 엘보우 같아서 엘보우 방법
    • 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()
      
    • alt text
    • k=3에서 엘보우

차원과 차원 축소

  • 차원 = 특성
  • 차원 축소
    • 비지도 학습 작업 중 하나
    • 데이터를 가장 잘 나타내는 일부 특성을 선택해 데이터 크기를 줄이고 지도 학습 모델의 성능 향상
    • PCA(주성분 분석)

PCA

  • 데이터에 있는 분산이 큰 방향 찾기
    • 분산이란 데이터가 퍼져있는 정도
  • alt text
  • 데이터의 분포를 가장 잘 표현하는 방향 벡터 = 주성분
  • 주성분 벡터의 원소 개수는 원본 데이터셋에 있는 특성 개수와 같음
  • 주성분은 원본 차원과 같고 주성분으로 바꾼 데이터는 차원이 줄어듦
  1. 주성분 찾기
  2. 두 번째 주성분(주성분 벡터에 수직이고 분산이 가장 큰 다음 방향) 찾기
  •   from sklearn.decomposition import PCA
      pca = PCA(n_components=50) # n_components로 주성분 개수 지정
      pca.fit(fruits_2d)
      print(pca.components_.shape) # (50, 10000)
      # 50개의 주성분, 1000개의 원본 데이터 특성 개수
      draw_fruits(pca.components_.reshape(-1, 100, 100)) # 주성분 그림으로 그려보기
    
  • alt text
  •   print(fruits_2d.shape) # 원본 데이터 (300, 10000)
      fruits_pca = pca.transform(fruits_2d) # transform()으로 차원 축소
      print(fruits_pca.shape) # (300, 50) 10000->50으로 차원 축소
    

원본 데이터 재구성

  • inverse_transform()으로 원본 데이터 재구성
  •   fruits_inverse = pca.inverse_transform(fruits_pca)
      print(fruits_inverse.shape) # (300, 10000)
    
      fruits_reconstruct = fruits_inverse.reshape(-1, 100, 100) # 100 * 100 크기로 변환
      for start in [0, 100, 200]:
          draw_fruits(fruits_reconstruct[start:start+100]) # 100개씩 나눠서 출력
          print("\n")
    
  • alt text

설명된 분산

  • 주성분이 원본 데이터의 분산을 얼마나 잘 나타내는지 기록한 값
  • explained_variance_ratio_에 기록
  • 첫번째 주성분의 설명된 분산이 가장 큼
  •   print(np.sum(pca.explained_variance_ratio_)) # 0.9215651897863715
      plt.plot(pca.explained_variance_ratio_) # 설명된 분산의 비율 그래프로 그려보기
    
  • alt text
    • 처음 10개의 주성분이 대부분의 분산 표현

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

  •   from sklearn.linear_model import LogisticRegression # 로지스틱 회귀 모델
      lr = LogisticRegression()
    
      target = np.array([0] * 100 + [1] * 100 + [2] * 100) # 타깃 데이터
    
      from sklearn.model_selection import cross_validate
      scores = cross_validate(lr, fruits_2d, target) # 원본 데이터로 교차 검증 수행
      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 = PCA(n_components=0.5)
      # 주성분의 개수 지정 대신 설명된 분산의 비율 입력
      pca.fit(fruits_2d)
      print(pca.n_components_) # 2
      # 2개의 특성 만으로 원본 데이터에 있는 분산의 50% 표현 가능
    
      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'])) # 0.9933333333333334
      print(np.mean(scores['fit_time'])) # 0.03713240623474121
    
      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")
    
  • alt text
  • 차원 줄이면 시각화 하기도 쉬움
  •   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()
    
  • alt text