Post

머신러닝 5: 혼공머신 6장

비지도 학습

군집 알고리즘

비지도 학습이란?

  • 사름이 가르쳐 주지 않아도 데이터에 있는 무언가 학습하는 것
  • 즉 타깃을 몰라도 학습하는 알고리즘

데이터 준비하기

  • !wget 명령어를 이용하여 사용할 데이터셋을 다운로드할 수 있다.
    1
    
      !wget https://bit.ly/fruits_300 -O fruits_300.npy
    
  • 다음, 다운로드한 데이터셋을 로드하고 그 넘파이 배열의 크기를 확인한다.
    1
    2
    
      fruits = np.load('fruits_300.npy')
      print(fruits.shape) # (300, 100, 100)을 출력
    
  • 배열의 첫 번째 차원은 샘플의 개수이고 둘 번째 차원, 세 번째 차원은 각각 이미지 높이, 이미지 너비이다.
    • 즉 이미지 크기는 100 x 100이다.
  • 각 픽셀은 넘파이 배열의 원소 하나에 대응한다.

    photo 1

  • 3차원 배열이라 처음 2개의 인덱스를 0으로 지정하고 마지막 인덱스는 지정하지 않거나 슬라이싱 연산자를 쓰면 첫 번째 이미지의 첫 번째 행을 모두 선택할 수 있다.
    1
    
      print(fruits[0, 0 , :]) # 첫 번째 행에 있는 픽셀 100개에 들어 있는 값을 출력
    
  • 출력된 넘파이 배열은 흑백 사진을 담고 있어 0~255까지의 정숫값을 가진다.
    • 0에 가까울수록 검게 나타나고 높은 값을 밝게 나타난다.
  • 맷플롯립의 imshow() 함수를 이용하여 넘파이 배열로 저장된 이미지를 쉽게 그릴 수 있다.
    1
    
      plt.imshow(fruits[0], cmap='gray') # 흑백 사진이라 cmap 매개변수를 'gray'로 지정
    
  • 출력된 이미지는 바탕이 검게 나타나고 물체는 밝게 나타난다.
    • 관심 대상은 물체라서 이미지를 넘파이 배열로 변환할 때 반전되었다.
    • cmap 매개변수를 ‘gray_r’로 지정하면 다시 반점할 수 있다.
      1
      
        plt.imshow(fruits[0], cmap='gray_r')
      
  • 맷플롯립의 subplots() 함수를 이용하여 여러 개의 그래프를 배열처럼 쌓을 수 있다.
    1
    2
    3
    
      fig, axs = plt.subplots(1, 2) # 하나의 행과 2개의 열을 지정
      axs[0].imshow(fruits[100], cmap='gray_r') # 첫 번째 그래프를 담고 있다
      axs[1].imshow(fruits[200], cmap='gray_r') # 둘 번째 그래프를 담고 있다 
    

픽셀값 분석하기

  • fruits 데이터를 나눠보고 100x100 이미지를 펼져서 10000인 1차원 배열로 만든다.
    • 이렇게 펼치면 이미지로 출력하기는 어렵지만 배열을 계산할 때 편리하다.

      photo 2

    • reshape() 메서드를 이용하여 둘 번째 차원(100)과 세 번째 차원(100)을 10000으로 합치고 첫 번째 차원을 -1 지정하며 자동으로 남은 차원을 할당한다.

      1
      2
      3
      4
      
        apple = fruits[0:100].reshape(-1, 100*100)
        pineapple = fruits[100:200].reshape(-1, 100*100)
        banana = fruits[200:300].reshape(-1, 100*100)
        '''NOTE: 실전에서는 어떤 글래스가 몇 개가 입력될지 알 수 없어서 이를 참고해야 된다.'''
      
  • 넘파이의 mean() 메서드를 이용하여 클래스에 있는 모두 샘플에 대한 평균을 계산할 수 있다.
    • axis 매개변수를 이용하여 어떤 클래스의 행이나 열에 대한 평균을 계산할 수 있다.
      • axis=0: 첫 번째 축인 행
      • axis=1: 둘 번째 축인 열
        1
        
          apple.mean(axis=1)
        
  • 히스토그램을 그려보면 평균값이 어떻게 분포되어 있는지 볼 수 있다.
    1
    2
    3
    4
    5
    
      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'])
    
  • 픽셀별 평균값을 비교해 보면:
    1
    2
    3
    4
    5
    
      fig, axs = plt.subplots(1, 3, figsize=(20,5))
      # 맷플롯립의 bar() 함수를 이용하여 픽셀 10000개에 대한 평균값을 막대그래프로 그려본다.
      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))
    

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

  • reshape() 함수를 이용하여 픽셀 평균값을 100x100 크기로 다시 바꾸고 절댓값 오차를 이용하여 이 대표 이미지와 가까운 사진을 골라내고 클래스를 구분해 본다.
    1
    2
    3
    
      abs_diff = np.abs(fruits - apple_mean) # 절댓값을 계산하는 함수
      abs_mean = np.mean(abs_diff, axis=(1,2))
      abs_mean.shape # (300,)을 출력
    
  • np.argsort() 함수를 이용하여 작은 것에서 큰 순서대로 나열한 abs_mean 배열의 인덱스를 반환한다.
    1
    2
    3
    4
    5
    6
    7
    8
    
      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')
    
      plt.show() # 가장 가까운 100개의 이미지를 출력
    

군집이란?

  • 비슷한 샘플끼리 그룹으로 모으는 과정
  • 대표적인 비지도 학습 작업이다

    클러스터란?

  • 군집 알고리즘에서 만든 그룹

k-평균

  • 실전에서는 샘플에 어떤 클래스가 들어 있는지 알고 못하는데 k-평균 알고리즘을 이용하여 평균값을 자동으로 찾을 수 있다.
    • 이 평균값이 클러스터의 중심에 위치해서 클러스터 중심 또는 센트로이드라고 부른다.

k-평균 알고리즘 소개

  • k-평균 알고리즘의 자동 방식은 다음과 같다.
    1. 무작위로 k개의 클러스터 중심을 정한다.
    2. 각 샘플에서 가장 가까운 클러스터 중심을 찾아 해당 클러스터의 샘플로 지정한다.
    3. 클러스터에 속한 샘플의 평균값으로 클러스터 중심을 변경한다.
    4. 클러스터 중심을 변경하지 않을 때까지 2번으로 돌아가 반복한다.
  • reshape() 함수를 이용하여 3차원 데이터를 2차원으로 변환하고 사이킷런의 KMeans 클래스를 이용하여 k-평균 알고리즘을 구현한다.
    1
    2
    3
    4
    5
    
      fruits_2d = fruits.reshape(-1, 100*100)
    
      from sklearn.cluster import KMeans
      km = KMeans(n_cluster=3, random_state=42)
      km.fit(fruits_2d)
    
  • labels_ 속성을 이용하여 각 샘플이 어떤 레이블에 해당하는지 볼 수 있다.
    • 레이블 개수는 n_cluster의 값에 따른다.
      1
      
        km.labels_
      

클러스터 중심

  • cluster_centers_ 속성을 이용하여 최종적으로 찾은 클러스터 중심을 볼 수 있다.
    1
    
      draw_fruits(km.cluster_centers_.reshape(-1, 100, 100), ratio=3)
    
  • transform() 메서드를 적용하여 샘플에서 클러스터 중심까지 거리를 변환한다.
    1
    
      print(km.transform(fruits_2d[100:101])) # 인덱스가 100인 샘플에 적용
    
    • 출력된 배열 원소들이 레이블에 대응하고 가장 작은 원소는 샘플이 어떤 레이블을 속하는지를 해당한다.
      • 이를 predict() 함수로 확인할 수 있다.
        1
        
          print(km.predict(fruits_2d[100:101]))
        
  • n_iter_ 속성을 이용하여 알고리즘이 반복한 횟수를 알아볼 수 있다.
    1
    
      print(km.n_iter_)
    

최적의 k 찾기

  • 실전에서 타깃 클래스가 몇 개 있는지 알 수 없는데 적절한 클러스터 개수를 찾기 위한 대표적인 방법은 엘보우 방법이다.
  • 이너셔란: 클러스터 중심과 클러스터에 속한 샘플 사이의 거리의 제곱 합이다
    • 즉, 클러스터에 속한 샘플이 얼마나 가깝게 모여 있는지를 나타내는 값이다
  • 일반적으로 클러스터 개수가 늘어나면 클러스터 개개의 크기는 줄어들기 때문에 이너셔도 줄어든다.
  • 엘보우 방법은 클러스터 개수를 늘려가면서 이너셔의 변화를 관찰하여 최적의 클러스터 개수를 찾는 방법이다. photo 3

  • inertia_ 속성을 이용하여 이너셔를 출력할 수 있다.
    1
    2
    3
    4
    5
    6
    7
    
      inertia = []
      for k in range(2, 7):
          km = KMeans(n_cluster=k, random_state=42)
          km.fit(fruits_2d)
          inertia.append(km.inertia_)
    
      plt.plot(range(2, 7), inertia)
    

    주성분 분석

    차원 축소란?

  • 원본 데이터의 특성을 적은 수의 새로운 특성으로 변화하는 비지도 학습의 한 종류이다.
  • 대표적인 알고리즘은 주성분 분석 (Principal Component Analysis 또는 PCA)이다.

PCA 소개

  • 데이터에서 가장 분산이 큰 방향을 찾는 알고리즘이다.
    • 원본 데이터를 주성분에 투영하여 새로운 특성을 만들 수 있다.
    • 찾은 벡터를 주성분이라 한다.
    • 일반적으로 주성분은 원본 특성의 개수만큼 찾을 수 있다.

    photo 4

PCA 클래스

  • 사이킷런은 sklearn.decomposition 모듈 아래 PCA 클래스로 주성분 분석 알고리즘을 제공한다.
    • PCA 객체 만들 때 n_components 매개변수에 주성분의 개수를 지정해야 된다.
      1
      2
      
        pca = PCA(n_components=50)
        pca.fit(fruits_2d)
      
  • components_ 속성을 이용하여 찾은 주성분을 확인할 수 있다.
    1
    
      print(pca.components_.shape) # (50, 10000)을 출력
    
  • 주성분을 찾았으므로 원본 데이터를 주성분에 투영하여 특성의 개수를 10000개에서 50개로 줄일 수 있다.
  • transform() 메서드를 이용하여 원본 데이터의 차원을 50으로 줄일 수 있다.
    1
    2
    3
    4
    
      print(fruits_2d.shape) # (300, 10000)을 출력
    
      fruits_pca = pca.transform(fruits_2d)
      print(fruits_pca.shape) # (300, 50)을 출력
    
    • fruits_2d는 원래 (300, 10000) 크기의 배열이었는데 주성분 분석을 진행한 후 (300, 50) 크기의 배열로 변환하였다.

원본 데이터 재구성

  • 10000개의 특성을 50개로 줄이기로 인하여 어느 정도 손실이 발생할 수밖에 없어도 최대한 분산이 큰 방향으로 데이터를 투영했기 때문에 원본 데이터를 상당 부분 재구성할 수 있다.
  • inverse_transform() 메서드를 이용하여 복원할 수 있다.
    1
    2
    
      fruits_inverse = pca.inverse_transform(fruits_pca)
      print(fruits_inverse.shape) # (300, 10000)을 출력
    

설명된 분산

  • 주성분이 원본 데이터의 분산을 얼마나 잘 나타내는지 기록한 값이다
  • explained_variance_ratio_ 속성에 각 주성분의 설명된 분산 비율이 기록되어 있다.
    1
    
      print(np.sum(pca.explained_variance_ratio_)) # 0.92...을 출력
    
  • 맷플롯립의 plot() 함수를 이용하여 설명된 분산을 그래프로 출력할 수 있다.
    1
    
      plt.plot(pca.explained_variance_ratio_)
    
    • 첫 번째 주성분의 설명된 분산이 가장 큰 것을 확인할 수 있다.

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

  • 비지도 학습에 맞는 원본 데이터셋과 PCA로 축소한 데이터셋을 지도 학습에 적용해 보려고 해서 타깃 배열을 만들어야 된다.
    1
    
      target = np.array([0]*100 + [1]*100 + [2]*100)
    
  • 로지스틱 회귀 모델에서 성능을 가늠해 보기 위하여 교차 검증을 수행한다.
    1
    2
    3
    4
    5
    6
    7
    
      # 원본 데이터셋을 이용하면
      scores = cross_validation(lr, fruits_2d, target)
      print(np.mean(scores['test_score']), np.mean(scores['fit_time'])) # 0.9966..., 0.9422....을 출력
    
      # 축소한 데이터셋을 이용하면
      scores = cross_validation(lr, fruits_pca, target)
      print(np.mean(scores['test_score']), np.mean(scores['fit_time'])) # 1.0, 0.032....을 출력
    
    • 결과를 비교하면 축소한 데이터셋의 성능이 원본 데이터셋의 성능보다 더 좋고 훈련 시간도 감소하였다.
  • 또한 n_components 매개변수에 0~1 사이의 비율을 실수로 입력해도 된다.
    1
    2
    3
    
      pca = PCA(n_components=0.5)
      pca.fit(fruits_2d)
      print(pca.n_components_) # 2를 출력 (즉 특성 2개 가진다)
    
  • 축소된 데이터를 이용하여 k-평균 알고리즘으로 클러스터를 찾을 수 있다.
    1
    2
    3
    
      km = KMeans(n_clusters=3, random_state=42)
      km.fit(fruits_pca) # n_components = 2
      print(np.unique(km.labels_, return_counts=True)) # (array([0, 1, 2], dtype=int32)), array([91, 99, 110])을 출력
    
    • 원소 데이터셋을 이용할 때와 비슷한 결과 나온다.
This post is licensed under CC BY 4.0 by the author.

Trending Tags