[혼공머신] 5주차-1_비지도학습 갑자기 난이도 급상승?!

06-1 군집 알고리즘

타깃을 모르는 비지도 학습

비지도 학습(unsupervised learning) : 타깃이 없을 때 사용하는 머신러닝 알고리즘

 

과일 사진 데이터 준비하기

Jupyter Notebook이나 Google Colab 같은 환경에서 사용되는 shell 명령어

! : Jupyter Notebook에서 셸 명령어를 실행하기 위한 접두사

-O : 저장할 파일 이름을 지정

 

이 배열의 첫 번째 차원(300) : 샘플의 개수

두 번째 차원(100) : 이미지 높이

세 번때 차원(100) : 이미지 너비

이미지 크기는 100×100 (각 픽셀은 넘파이 배열의 원소 하나에 대응)

 

우리가 보는 것과 컴퓨터가 처리하는 방식이 다르기 때문에 종종 흑백 이미지를 이렇게 반전하여 사용합니다.

관심 대상의 영역을 높은 값으로 바꾸었지만 맷플롯립으로 출력할 때 바탕이 검게 나오므로 cmap 매개변수를 ‘gray_r’로 지정하여 다시 반전하여 보기 좋게 출력합니다.

 

밝은 부분이 0에 가깝고 짙은 부분이 255에 가까운 값입니다.

 

fig : 전체 그림(figure)을 의미

axs : 2개의 서브플롯을 담고 있는 배열(리스트처럼 생긴 객체)

plt.subplots(1, 2) : 1행 2열의 서브플롯을 생성하는 함수

cmap='gray_r' : 색상맵을 반전된 흑백으로 지정

 

픽셀값 분석하기

넘파일 배열을 나눌 때 100×100 이미지를 펼쳐서 길이가 10,000인 1차원 배열로 만들면 이미지로 출력하긴 어렵지만 배열을 계산할 때 편리합니다.

axis=0 → “열 단위로 계산” (행을 따라 내려가면서)

  • 즉, 각 열(column)의 값을 기준으로 계산합니다.

  • 예: 평균을 구하면, 각 열의 평균이 나옵니다.

axis=1 → “행 단위로 계산” (열을 따라 가로로)

  • 즉, 각 행(row)의 값을 기준으로 계산합니다.

  • 예: 평균을 구하면, 각 행의 평균이 나옵니다.

 

사과와 파인애플은 많이 겹쳐있어서 픽셀값만으로는 구분하기 쉽지 않음

→ 샘플의 평균값이 아니라 픽셀별 평균값을 비교

각 샘플의 평균값 : 각 이미지를 한 장의 사진 → 한 개의 숫자로 만드는 방식

픽셀별 평균값 : 모든 이미지에서 같은 위치의 픽셀끼리 평균을 냄

 

여기서 axs는 matplotlib의 subplot(여러 개의 그래프 영역)을 한 번에 만들었을 때, 각 그래프 영역(axes 객체)**를 담고 있는 배열

 

 

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

 

abs_diff의 크기 (300, 100, 100)

  • 300 → 사진이 300장 있음 (샘플 개수)
  • 100 → 한 장의 사진 높이 (픽셀 수)
  • 100 → 한 장의 사진 너비 (픽셀 수)

즉, 300장의 흑백 사진이 있고, 각 사진은 100×100 픽셀로 되어 있음.
그래서 전체 데이터는 3차원 배열 (사진 개수, 높이, 너비) 모양이 됨.

axis=(1,2) 의미

  • axis=1 → 사진의 “높이 방향”
  • axis=2 → 사진의 “너비 방향”
  • (1, 2)를 같이 지정하면 → 각 사진의 모든 픽셀을 평균낸다는 뜻.

 abs_mean의 크기 (300,)

 

  • 사진 한 장(100×100) → 평균을 내면 숫자 1개가 됨.
  • 300장의 사진 → 숫자 300개가 나옴.
  • 그래서 (300,) → 길이가 300인 1차원 배열이 됨.

 

apple_index = np.argsort(abs_mean)[:100]

  • abs_mean에는 각 사진이 사과 평균 사진과 얼마나 다른지(차이 평균값)가 들어 있음
  • np.argsort(abs_mean) → 차이가 작은 순서대로 사진의 인덱스(번호)를 정렬
  • [:100] → 그 중에서 가장 사과에 가까운 사진 100장만 선택

apple_index = apple_index.reshape(10, 10)

  • 100장의 사진을 10×10 형태로 바꿈 (나중에 그래프를 10행 10열에 맞춰서 그림)

fig, axs = plt.subplots(10, 10, figsize=(10,10))

  • 10행 10열짜리 빈 그림판(axs)을 만듦
  • figsize=(10,10) → 전체 그림 크기를 10×10 인치로 설정

for i in range(10):

    for j in range(10):

        axs[i, j].imshow(fruits[apple_index[i, j]], cmap='gray_r')

        axs[i, j].axis('off')

  • 10×10 반복문을 돌면서 각 위치(axs[i, j])에 사과에 가장 가까운 사진을 차례대로 그림
  • cmap='gray_r' → 흑백(역상)으로 표시
  • [:100] → 그 중에서 가장 사과에 가까운 사진 100장만 선택

 

군집(clustering) : 비슷한 샘플끼리 그룹으로 모으는 작업

클러스터(cluster) : 군집 알고리즘에서 만든 그룹

 

06-2 k-평균

k-평균(k-means) 군집 알고리즘 이 평균값을 자동으로 찾아줍니다.

이 평균값이 클러스터의 중심에 위치하기 때문에 클러스터 중심(cluster center) 또는 센트로이드(centroid)라고 부릅니다.

 

k-평균 알고리즘 소개

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

 

KMeans 클래스

사이킷런의 k-평균 알고리즘은 sklearn.cluster 모듈 아래 KMeans 클래스에 구현되어 있습니다.

이 클래스에서 설정할 매개변수는 클러스터 개수를 지정하는 n_clusters입니다.

레이블값 0,1,2와 레이블 순서에는 어떤 의미도 없습니다.

실제 레이블 0,1,2가 어떤 과일 사진을 주로 모았는지 알아보려면 직접 이미지를 출력하는 것이 최선입니다.

 

.labels_ 속성 → 각 데이터 포인트가 어느 클러스터에 속하는지 나타내는 배열

np.unique() → 배열 안에서 고유값(중복 없는 값)을 찾아서 정렬해 반환

return_counts=True → 각 고유값이 몇 번 등장했는지도 함께 반환

첫 번째 배열 → 클러스터 번호 목록

두 번째 배열 → 각 클러스터에 속한 샘플 개수

 

draw_fruits(arr, ratio=1)

  • arr 배열(예: 여러 장의 과일 이미지)을 받아서 여러 이미지를 그리드(격자) 형태로 예쁘게 한 번에 보여주는 함수
  • (샘플 개수, 너비, 높이)의 3차원 배열을 입력받으며 가로로 10개씩 이미지를 출력
  • ratio는 각 이미지 크기 조절에 쓰임.

n = len(arr)

  • arr에 들어있는 이미지 개수를 저장

rows = int(np.ceil(n/10))

  • 한 행에 최대 10개씩 놓는다고 가정
  • np.ceil(n/10)n을 10으로 나눈 후 올림 (예: 35 → 3.5 → 4행 필요)
  • int()는 정수형으로 변환
  • 즉, 필요한 행(row) 개수 계산

cols = n if rows < 2 else 10

  • 만약 이미지 개수가 10개 이하면 → 한 행에 다 놓음 (cols = n)
  • 10개 넘으면 → 열은 무조건 10개 (cols = 10)

fig, axs = plt.subplots(rows, cols, figsize=(cols*ratio, rows*ratio), squeeze=False)

  • plt.subplotsrows x cols 크기의 빈 그래프판 만들기
  • figsize → 전체 그림 크기 설정 (가로 cols * ratio, 세로 rows * ratio)
  • squeeze=Falseaxs를 항상 2차원 배열로 만들어서 인덱싱 편하게 함

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')

  • 2중 for문으로 rows행, cols열만큼 반복
  • i*10 + j는 현재 그리드에서 몇 번째 이미지를 그릴지 계산
  • 만약 인덱스가 이미지 개수 n보다 작으면 → axs[i, j].imshow()로 해당 위치에 이미지 그림
  • axs[i, j].axis('off') → 축 눈금이나 테두리 표시 안 함

 

불리언 인덱싱(Boolean Indexing) :  배열(예: NumPy 배열)에서 조건에 맞는 원소들만 선택해서 추출하는 방법

배열에서 True 또는 False 값을 가진 같은 크기의 불리언 배열을 만들어서,

True인 위치에 해당하는 원소들만 골라내는 방식