머신러닝 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이다.
각 픽셀은 넘파이 배열의 원소 하나에 대응한다.
- 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차원 배열로 만든다.
이렇게 펼치면 이미지로 출력하기는 어렵지만 배열을 계산할 때 편리하다.
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)
- axis 매개변수를 이용하여 어떤 클래스의 행이나 열에 대한 평균을 계산할 수 있다.
- 히스토그램을 그려보면 평균값이 어떻게 분포되어 있는지 볼 수 있다.
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-평균 알고리즘의 자동 방식은 다음과 같다.
- 무작위로 k개의 클러스터 중심을 정한다.
- 각 샘플에서 가장 가까운 클러스터 중심을 찾아 해당 클러스터의 샘플로 지정한다.
- 클러스터에 속한 샘플의 평균값으로 클러스터 중심을 변경한다.
- 클러스터 중심을 변경하지 않을 때까지 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_
- 레이블 개수는 n_cluster의 값에 따른다.
클러스터 중심
- 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]))
- 이를 predict() 함수로 확인할 수 있다.
- 출력된 배열 원소들이 레이블에 대응하고 가장 작은 원소는 샘플이 어떤 레이블을 속하는지를 해당한다.
- n_iter_ 속성을 이용하여 알고리즘이 반복한 횟수를 알아볼 수 있다.
1
print(km.n_iter_)
최적의 k 찾기
- 실전에서 타깃 클래스가 몇 개 있는지 알 수 없는데 적절한 클러스터 개수를 찾기 위한 대표적인 방법은 엘보우 방법이다.
- 이너셔란: 클러스터 중심과 클러스터에 속한 샘플 사이의 거리의 제곱 합이다
- 즉, 클러스터에 속한 샘플이 얼마나 가깝게 모여 있는지를 나타내는 값이다
- 일반적으로 클러스터 개수가 늘어나면 클러스터 개개의 크기는 줄어들기 때문에 이너셔도 줄어든다.
- 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 소개
- 데이터에서 가장 분산이 큰 방향을 찾는 알고리즘이다.
- 원본 데이터를 주성분에 투영하여 새로운 특성을 만들 수 있다.
- 찾은 벡터를 주성분이라 한다.
- 일반적으로 주성분은 원본 특성의 개수만큼 찾을 수 있다.
PCA 클래스
- 사이킷런은 sklearn.decomposition 모듈 아래 PCA 클래스로 주성분 분석 알고리즘을 제공한다.
- PCA 객체 만들 때 n_components 매개변수에 주성분의 개수를 지정해야 된다.
1 2
pca = PCA(n_components=50) pca.fit(fruits_2d)
- PCA 객체 만들 때 n_components 매개변수에 주성분의 개수를 지정해야 된다.
- 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.