diff --git "a/week1/0\355\214\200_\355\231\215\352\270\270\353\217\231.md" "b/week1/0\355\214\200_\355\231\215\352\270\270\353\217\231.md" deleted file mode 100644 index e69de29..0000000 diff --git "a/week5/3\355\214\200_\354\236\204\354\240\225\354\204\255.md" "b/week5/3\355\214\200_\354\236\204\354\240\225\354\204\255.md" new file mode 100644 index 0000000..4ba437a --- /dev/null +++ "b/week5/3\355\214\200_\354\236\204\354\240\225\354\204\255.md" @@ -0,0 +1,488 @@ + +## 비지도 학습 - 군집 알고리즘 + +### 1\. 비지도 학습이란? + +머신러닝의 한 종류로 훈련 데이터에 타깃(라벨)이 없다. 타깃이 없기 때문에 외부의 도움 없이 스스로 유용한 무언가를 학습해야 한다. 대표적인 비지도 학습 작업은 군집, 차원 축소 등이 있다. + +--- + +### 2\. 군집이란? + +군집은 비슷한 샘플끼리 하나의 그룹으로 모으는 대표적인 비지도 학습 방법이다. 군집 알고리즘으로 모은 샘플 그룹을 클러스터라고 부른다.  + +--- + +### 3\. 군집 알고리즘 예시 + +#### 데이터셋 준비 + +이번 예시로 사용할 데이터 셋은 사과, 바나나, 파인애플의 흑백 사진들이다.  + +``` +!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') +print(fruits.shape) # output : (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] +''' +``` + +shape 으로 300 장의 100 x 100 크기 사진으로 구성된 것을 확인할 수 있다.  + +``` +plt.imshow(fruits[0], cmap='gray') +plt.show() +``` + +matplotlib의 imshow() 로 이미지를 출력할 수 있다.  + +``` +plt.imshow(fruits[0], cmap='gray_r') +plt.show() +``` + +cmap 의 값으로 gray\_r 을 주게 되면 반전된 값이 출력된다. + +#### 픽셀 값 분석하기 + +사과, 바나나, 파인애플의 각 데이터(이미지)를 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) #output: (100,10000) +``` + + 사과 사진들의 픽셀의 평균 값을 구해보자. + +``` +print(apple.mean(axis=1)) +``` + +axis = 1 일 때는 열방향(왼쪽에서 오른쪽)으로 계산한다. 즉, 100개의 평균 값이 담긴 배열을 생성한다. + +각 과일 별로 픽셀의 평균 값을 히스토그램을 이용해 표현해보자. + +``` +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() +``` + +히스토그램을 확인해보면 바나나의 평균 사진의 픽셀 값은 20~80 사이에 분포되어 있어서 사진의 픽셀 평균 값으로 판단할 수 있지만 사과와 파인애플은 비슷한 범위에 분포되어 있기 때문에 판단이 힘들다.  + +그렇다면 사진의 픽셀 평균값이 아니라 각 픽셀별 평균(ex. 모든 사진의 1번 픽셀의 평균) 을 사용하면 어떨까? 이를 막대그래프를 이용해서 시각화해보자. + +``` +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() +``` + +좌측부터 사과, 파인애플, 바나나의 각 픽셀(1~10000번)의 평균 값 막대그래프이다. 각 과일마다 그래프의 개형이 다른 것을 이용해 구분할 수 있을 것 같다.  + +#### 평균값과 가까운 사진 고르기 + +각 사진의 오차 평균을 구해보자. + +``` +abs_diff = np.abs(fruits - apple_mean) +abs_mean = np.mean(abs_diff, axis=(1,2)) +print(abs_mean.shape) #output: (300,) +``` + +abs\_mean 은 모든 사진에 대한 오차 평균들의 배열이다.  + +다음으로 100개의 사과 사진은 10 x 10 로 이루어진 그래프를 만들어보자. + +``` +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() +``` + +이렇게 비슷한 샘플끼리 그룹으로 모으는 작업을 **군집**이라 한다. 이렇게 군집 알고리즘에서 만든 그룹을 **클러스터**라 한다.  + +우리는 미리 사과, 바나나, 파인애플이라는 것을 알고 클러스터를 만들었지만 실제 비지도 학습에서는 어떤 과일일지 모르기 때문에 이처럼 샘플의 평균값을 미리 구할 수 없다. 그렇다면 어떻게 비슷한 샘플끼리 그룹으로 모을까? 다음 게시물에서 다룰 k-평균 알고리즘을 통해 해결할 수 있다. + +--- + + + +## 비지도 학습 - k 평균 알고리즘 + +### 1\. K - 평균 알고리즘이란? + +**k-평균 알고리즘**은 처음에 랜덤하게 클러스터 중심을 정하고 클러스터를 만든다. 그 다음 클러스터의 중심을 이동하고 다시 클러스터를 만드는 식으로 반복해서 최적의 클러스터를 구성하는 알고리즘이다. **클러스터 중심**은 k-평균 알고리즘이 만든 클러스터에 속한 샘플의 또 다른 특성으로 사용하거나 새로운 샘플에 대한 예측으로 활용할 수 있다.  + +k-평균 알고리즘 작동방식은 다음과 같다. + +1. 무작위로 k 개의 클러스터 중심을 정한다. +2. 각 샘플에서 가장 가까운 클러스터 중심을 찾아 해당 클러스터의 샘플로 지정한다. +3. 클러스터에 속한 샘플의 평균값으로 클러스터 중심을 변경한다. +4. 클러스터 중심에 변화가 없을 때까지 2번으로 돌아가 반복한다. + +--- + +### 2\. K - 평균 알고리즘 예시 + +#### 데이터셋 + +Chapter 6.1 군집 알고리즘에서 사용한 데이터셋과 동일하다. + +``` +!wget https://bit.ly/fruits_300_data -O fruits_300.npy +``` + +#### KMeans 클래스 + +``` +import numpy as np + +fruits = np.load('fruits_300.npy') +# 사진의 2차원 배열 -> 1차원 배열 +fruits_2d = fruits.reshape(-1, 100*100) +``` + +사이킷런의 k-평균 알고리즘은 sklearn.cluster 모듈 아래 KMeans 클래스에 구현되어 있다.  + +``` +from sklearn.cluster import KMeans + +km = KMeans(n_clusters=3, random_state=42) +km.fit(fruits_2d) +print(km.labels_) +``` + +KMeans 클래스를 사용할 때 클러스터 개수를 지정하는 n\_clusters를 매개변수로 주어야 한다. 군집된 결과는 KMeans 의 labels\_ 속성으로 확인할 수 있다. 이 때 클러스터 개수를 3으로 지정했기 때문에  labels\_의 배열 값은 0,1,2 중 하나이다. + +우선, 각 레이블의 샘플 개수를 확인해보자. + +``` +print(np.unique(km.labels_, return_counts=True)) +''' +(array([0, 1, 2], dtype=int32), array([111, 98, 91])) +''' +``` + + 레이블 0에 대해 111개, 1에 대해 98개, 2에 대해 91개의 샘플이 있다는 것을 확인할 수 있다. + +그 다음 클러스터 안에 있는 샘플 사진들을 보여주는 함수를 만들어보자. + +``` +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() +``` + +위 함수를 이용해서 labels\_ == 0, 1, 2 일 때 샘플들을 확인해보자. + +``` +draw_fruits(fruits[km.labels_==0]) +draw_fruits(fruits[km.labels_==1]) +draw_fruits(fruits[km.labels_==2]) +``` + +#### 클러스터 중심 + +KMeans 클래스가 최종적으로 찾은 클러스터 중심은 cluster\_centers\_ 속성에 저장되어 있다. 이를 2차원 배열로 변환하여 이미지로 출력해보자. + +``` +draw_fruits(km.cluster_centers_.reshape(-1, 100, 100), ratio=3) +``` + +KMeans의 transform 메서드로 훈련 데이터 샘플에서 클러스터 중심까지 거리로 변환할 수 있다. + +``` +print(km.transform(fruits_2d[100:101])) +''' +[[3393.8136117 8837.37750892 5267.70439881]] +''' +``` + +첫 번째 값이 젤 작으므로 100번 째 과일은 0의 라벨을 가진다. 이를 predict 메서드로 확인해보자. + +``` +print(km.predict(fruits_2d[100:101])) +''' +[0] +''' +``` + +k-평균 알고리즘은 앞에서 설명했듯이 반복적으로 클러스터 중심을 옮기면서 최적의 클러스터를 찾는다. 반복 횟수는 KMeans의 n\_iter\_ 속성에 저장된다. + +``` +print(km.n_iter_) # 4 +``` + +--- + +#### 최적의 k 찾기 + +최적의 클러스터 개수를 찾는 대표적인 방법은 엘보우 방법이 있다. 이너셔는 클러스터 중심과 샘플 사이 거리의 제곱 합이다. 클러스터 개수에 따라 이너셔 감소가 꺾이는 지점이 적절한 클러스터 개수 k가 될 수 있다.  + + +KMeans 클래스는 자동으로 이니셔를 계산해서 inertia\_속성으로 제공한다.  + +``` +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() +``` +--- + +--- + +## 차원 축소 / 주성분 분석 + +### 1\. 차원 축소란? + +차원 축소는 데이터를 가장 잘 나타내는 일부 특성을 선택하여 데이터 크기를 줄이고 지도 학습 모델의 성능을 향상시킬 수 있는 방법이다. 또한 줄어든 차원에서 다시 원본 차원으로 손실을 최대한 줄이면서 복원할 수도 있다. 대표적인 차원 축소 알고리즘으로 주성분 분석(PCA)이 있다. + +### 2\. 주성분 분석이란?  + +주성분 분석은 데이터에서 가장 분산이 큰 방향을 찾는 방법이다. 이 방향을 주성분이라 한다. 원본 데이터를 주성분에 투영하여 새로운 특성을 만들 수 있다. + +--- + +### 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) +``` + +#### PCA 클래스 + +사이킷런은 sklearn.decomposition 모듈 아래 PCA 클래스로 주성분 분석 알고리즘을 제공한다.  + +``` +from sklearn.decomposition import PCA + +pca = PCA(n_components=50) +pca.fit(fruits_2d) +``` + +PCA 객체를 만들 때 n\_components 매개변수에 주성분 개수를 지정해야한다. components\_ 속성으로 확인해보자. + +``` +print(pca.components_.shape) +''' +(50,10000) +''' +``` + +KMeans 예제에서 사용한 draw\_fruis() 함수를 그대로 사용해서 주성분을 그림으로 그려보자. + +``` +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)) +``` + +transform 메서드를 이용하여 과일 데이터셋의 차원 축소를 해보자. + +``` +print(fruits_2d.shape) # (300, 10000) +fruits_pca = pca.transform(fruits_2d) +print(fruits_pca.shape) # (300, 50) +``` + +데이터가 10000 -> 50으로 축소된 것을 확인할 수 있다. fruits\_pca는 50개의 특성을 가진 데이터 배열이다.  + +#### 원본 데이터 재구성 + +inverse\_transform() 메서드를 통해 축소된 데이터를 원본 데이터로 상당 부분 재구성할 수 있다. 앞에서 만든 fruits\_pca 배열을 원본 데이터로 복구해보자. + +``` +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") +``` + +#### 설명된 분산 + +주성분이 원본 데이터의 분산을 얼마나 잘 나타내는지 기록한 값을 **설명된 분산**이라고 한다. PCA 의 explained\_varience\_ratio\_에 설명된 분산을 저장한다. 이를 다 더하면 50개의 주성분으로 표현하고 있는 총 분산 비율을 얻을 수 있다. + +``` +print(np.sum(pca.explained_variance_ratio_)) +''' +0.9215039913802381 +''' +``` + +92%가 넘는 분산을 유지하고 있는 것을 확인할 수 있다. 설명된 분산을 그래프로 그려보자. + +``` +plt.plot(pca.explained_variance_ratio_) +``` + +첫 번째 설명된 분산 값이 제일 높은 것을 확인할 수 있다. 대략 10개의 주성분이 대부분의 분산을 표현하고 있다.  + +--- + +### 4\. 다른 알고리즘과 함께 사용하기 + +#### 로지스틱 회귀에 적용하기 + +로지스틱 회귀를 이용하여 3개의 과일 사진을 분류하는 모델을 만들어보자. + +``` +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 +0.6327765941619873 +''' + +scores = cross_validate(lr, fruits_pca, target) +print(np.mean(scores['test_score'])) +print(np.mean(scores['fit_time'])) +''' +1.0 +0.0364443302154541 +''' +``` + +정확성 측면에서는 둘 다 과대적합이 나왔지만, 걸린 시간 측면에서는 축소된 데이터를 사용했을 때 20배 빠른 결과를 확인할 수 있다.  + +이번에는 설명된 분산의 50%에 달하는 주성분을 찾도록 PCA 모델을 만들어보자. + +``` +pca = PCA(n_components=0.5) +pca.fit(fruits_2d +print(pca.n_components_) # 2 +``` + +n\_components 매개변수에 정수 값이 아닌 비율을 넣어주면 된다. 50%에 달하는 주성분을 갖게 하는 n\_components\_ 값은 2인 것을 확인할 수 있다.  + +``` +fruits_pca = pca.transform(fruits_2d) +print(fruits_pca.shape) # (300, 2) +``` + +주성분이 2개인 데이터셋으로 교차검증을 해보자. + +``` +scores = cross_validate(lr, fruits_pca, target) +print(np.mean(scores['test_score'])) +print(np.mean(scores['fit_time'])) +''' +0.9966666666666667 +0.046887540817260744 +''' +``` + +2개의 주성분을 사용해도 과대적합이 일어난 것을 확인할 수 있다. + +--- + +#### 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)) + +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() +``` + +--- + +### **참고 자료** + +**\[혼공머신 chapter 6-1 군집 알고리즘.ipynb\] : [https://colab.research.google.com/github/rickiepark/hg-mldl/blob/master/6-1.ipynb#scrollTo=qfV19WdxS0HI](https://colab.research.google.com/github/rickiepark/hg-mldl/blob/master/6-1.ipynb#scrollTo=qfV19WdxS0HI)** + +**\[혼공머신 - 6.2 K-평균.ipynb\] : [https://colab.research.google.com/github/rickiepark/hg-mldl/blob/master/6-2.ipynb#scrollTo=FwU\_s1Tnu9lS](https://colab.research.google.com/github/rickiepark/hg-mldl/blob/master/6-2.ipynb#scrollTo=FwU_s1Tnu9lS)** + +**\[혼공머신 - 6.3 PCA.ipynb\] : [https://colab.research.google.com/github/rickiepark/hg-mldl/blob/master/6-3.ipynb#scrollTo=FwU\_s1Tnu9lS](https://colab.research.google.com/github/rickiepark/hg-mldl/blob/master/6-3.ipynb#scrollTo=FwU_s1Tnu9lS)**