단원 홈
2단원 · 10차시

이름표 없이 묶다
중심이 스스로 걸어가는 알고리즘

8차시의 씨앗에는 품종 A·B가 적혀 있었고, 9차시의 학생 24명에게는 시험 점수가 적혀 있었습니다. 오늘 만날 학생 72명에게는 아무것도 적혀 있지 않습니다. 정답표가 한 줄도 없는 데이터를 기계는 무슨 근거로 나눌까요? 그리고 '몇 무리로 나눌지'는 누가 정할까요?

성취기준 12인기02-03
비지도학습군집 k-평균배정과 이동 응집도 SSE지역 최적 팔꿈치
🎯 학습 목표
  • k-평균의 두 걸음(배정·이동)을 순서대로 말하고, 점 몇 개에 대해 한 회차를 손으로 계산할 수 있다.
  • 시작 중심을 바꿔 가며 같은 코드가 다른 답을 내는 장면을 직접 만들고, 그 현상의 이름과 실무의 대처법을 설명할 수 있다.
  • K를 바꿔 가며 응집도(SSE)가 꺾이는 자리를 찾고, 'SSE가 가장 작은 K를 고르면 된다'는 주장을 숫자로 반박할 수 있다.
🤔

여는 장면 — 정답표가 사라진 자리

지난 두 시간을 떠올려 봅시다. 8차시에서는 씨앗 55알의 길이와 무게를 재고 가까운 이웃에게 품종을 물었습니다. 9차시에서는 학생 24명의 공부 시간과 시험 점수로 직선 하나를 찾았습니다. 두 번 다 데이터에는 정답이 함께 적혀 있었습니다 — 이 씨앗은 A 품종, 저 학생은 78.6점.

기계는 그 정답을 보고 맞았는지 틀렸는지 알았습니다. 틀리면 고쳤고, 그 고침이 곧 학습이었습니다. 7차시의 말로 하면 지도학습입니다.

이제 오늘의 데이터를 봅시다. 한 반 학생 72명에게서 하루 공부 시간과 하루 스마트폰 사용 시간 두 가지만 걷었습니다. 앞 네 명은 이렇습니다.

학생하루 공부 시간(시간) 하루 스마트폰 시간(시간)정답(어느 무리인가)
첫째2.794.82없음
둘째5.841.37없음
셋째3.034.96없음
넷째5.910.94없음
72명 전체에서 공부 시간은 0.76 ~ 6.89시간, 스마트폰 시간은 0.87 ~ 6.20시간에 퍼져 있습니다.

오른쪽 끝 칸이 오늘의 전부입니다. 비어 있습니다. 누구도 이 학생들을 미리 분류해 두지 않았습니다. 8차시라면 여기서 막힙니다 — 이웃에게 물어봐야 이웃도 자기가 뭔지 모르니까요. 9차시도 막힙니다 — 맞힐 점수가 없으니 손실을 잴 수가 없습니다.

🎯 오늘의 물음

정답이 한 줄도 없는 점 72개를 기계는 무슨 근거로 나누는가? 그리고 몇 무리로 나눌지는 누가 정하는가?

답으로 가기 전에, 여러분이 먼저 해 보세요. 아래 그림은 그 72명을 가로축 공부 시간, 세로축 스마트폰 시간으로 찍은 것입니다. 몇 덩어리로 보이나요? 손가락으로 동그라미를 쳐 보세요.

01234567 01234567 하루 공부 시간 (시간) 하루 스마트폰 시간 (시간) 점 72개 · 이름표 없음
오늘의 학생 72명 — 점 하나가 학생 한 명입니다. 모든 점이 같은 색인 것이 곧 '정답표가 없다'는 뜻입니다. 그래도 사람의 눈에는 덩어리가 보이지요. 우리가 오늘 할 일은 이 '보인다'를 계산으로 바꾸는 것입니다.

사람은 이 일을 0.5초 만에 합니다. 그런데 어떻게 했는지 설명해 보라고 하면 막힙니다. "가까운 것끼리요"라고 답하겠지요. 좋습니다 — 오늘 배울 알고리즘이 하는 말이 정확히 그것입니다. 다만 기계는 '가깝다'를 5차시에서 배운 거리로 재고, '끼리'를 두 줄짜리 규칙으로 씁니다.

1

이름표가 사라지면 무엇이 남는가

7차시에서 학습의 갈래를 셋으로 나눴습니다. 정답을 주고 맞히게 하는 지도학습, 정답 없이 데이터의 짜임만 보게 하는 비지도학습, 해 보고 상을 받는 강화학습이었습니다. 오늘 다루는 군집화(clustering)는 비지도학습의 대표 선수입니다.

이름표가 없다는 것은 생각보다 큰 사건입니다. 세 가지가 한꺼번에 사라집니다.

사라진 것지도학습에서는군집에서는
맞았는지 아는 법 예측과 정답을 견준다 견줄 정답이 없다
고칠 방향 틀린 쪽으로 가중치를 민다(9차시) 어느 쪽이 틀린 쪽인지 모른다
무리의 이름 'A 품종' 처럼 처음부터 붙어 있다 번호만 있다. 뜻은 사람이 읽는다

그러면 무엇이 남을까요? 점들 사이의 거리가 남습니다. 5차시에서 만든 거리 함수를 떠올려 봅시다. 두 점의 좌표 차이를 제곱해서 더하고 뿌리를 씌우는 그 함수 말입니다.

ℹ️ 5차시에서 만든 거리 함수

def dist(a, b): return sum((a[i] - b[i]) ** 2 for i in range(len(a))) ** 0.5

오늘 코드에는 이 함수 대신 dist2()가 나옵니다. 뿌리를 씌우지 않은 판이에요. 왜 그래도 되는지는 바로 아래에서 숫자로 확인합니다. 거리를 재는 생각 자체는 5차시 것 그대로입니다.

거리만 있으면 이런 문장을 쓸 수 있습니다. "서로 가까운 점들은 한 무리다." 말은 되는데, 이대로는 프로그램이 될 수 없습니다. '가깝다'의 기준이 없고, 무리의 대표가 누구인지도 정해지지 않았기 때문입니다. 이 문장을 실행 가능한 규칙으로 바꾼 것이 오늘의 주인공입니다.

📖 이름이 두 개인 알고리즘

이 방법을 처음 쓴 사람은 1957년 벨 연구소의 스튜어트 로이드(Stuart Lloyd)입니다. 전화 신호를 몇 단계로 나눠 보낼지 정하는 문제였어요. 그런데 그의 보고서는 사내 문서로만 돌다가 1982년에야 정식으로 실렸습니다. 그사이 1967년에 제임스 매퀸(James MacQueen)이 같은 방법을 따로 발표하면서 k-평균(k-means)이라는 이름을 붙였습니다. 그래서 오늘날 이 알고리즘은 '로이드 알고리즘'과 'k-평균' 두 이름으로 불립니다.

2

두 걸음이 전부다 — 배정과 이동

k-평균이 하는 일은 두 줄로 끝납니다. 정말 두 줄입니다.

① 배정 — 각 점을 가장 가까운 중심에

점 하나를 놓고 중심 K개까지의 거리를 전부 잰다. 그중 가장 짧은 중심에 그 점을 붙인다. 72명 전부에 대해 되풀이한다.

d = [dist2(p, c) for c in centers]
k = d.index(min(d))

② 이동 — 붙은 점들의 평균 자리로

한 중심에 붙은 점들의 가로 평균, 세로 평균을 구한다. 중심을 그 자리로 옮긴다. 중심 K개 모두에 대해 되풀이한다.

[sum(v[0] for v in g) / len(g),
 sum(v[1] for v in g) / len(g)]

이 둘을 번갈아 되풀이합니다. 배정 → 이동 → 배정 → 이동…. 중심이 옮겨 가면 가장 가까운 중심이 바뀌는 점이 생기고, 배정이 바뀌면 평균이 바뀌어 중심이 또 옮겨 갑니다. 이 되먹임이 어느 순간 멎습니다. 그때가 답입니다.

① 배정 각 점을 가장 가까운 중심에 붙인다 중심은 아직 그대로. 선만 새로 그어졌다. ② 이동 붙은 점들의 평균 자리로 중심을 옮긴다 중심이 걸어간다. 그러면 배정이 또 달라진다.
회색 세모는 옮기기 전 중심, 초록 세모는 옮긴 뒤 중심입니다. 두 걸음을 번갈아 되풀이하는 것이 k-평균의 전부입니다.

배정 한 걸음을 손으로 — 첫 학생 하나

말로만 보면 쉬운데, 한 번 손으로 해 봐야 붙습니다. 우리 데이터의 첫 학생 (2.79, 4.82)를 놓고, 중심 셋이 (0.79, 5.46) · (1.47, 5.32) · (1.03, 5.04)에 있다고 합시다. (이 셋은 잠시 뒤 프로그램이 실제로 고르게 되는 시작 중심입니다.)

중심가로 차이세로 차이 거리의 제곱실제 거리
1번 (0.79, 5.46)2.00−0.64 4.40962.0999
2번 (1.47, 5.32)1.32−0.50 1.99241.4115
3번 (1.03, 5.04)1.76−0.22 3.14601.7737
가장 작은 것은 2번 중심입니다. 그러므로 첫 학생은 2번 무리에 붙습니다.

여기서 눈여겨볼 것이 하나 있습니다. 거리의 제곱 칸과 실제 거리 칸의 순위가 같습니다. 4.4096 > 3.1460 > 1.9924 이고, 2.0999 > 1.7737 > 1.4115 입니다. 당연합니다 — 제곱근은 값이 커질수록 결과도 커지는 함수라 순서를 뒤집지 않기 때문입니다. 우리에게 필요한 것은 '누가 가장 가까운가' 하나뿐이니, 뿌리를 씌우는 계산을 회차마다 216번(72명 × 중심 3개) 아낄 수 있습니다. 오늘 코드가 dist 대신 dist2를 쓰는 까닭입니다.

이동 한 걸음을 손으로 — 6명의 평균

72명을 전부 배정하고 나면, 1번 중심에는 6명이 붙습니다. 이 여섯 명입니다.

💡 계산해 보세요

(0.79, 5.46) (1.00, 6.20) (1.09, 5.65) (1.13, 5.78) (0.93, 5.33) (0.76, 5.78)

가로 합 = 0.79 + 1.00 + 1.09 + 1.13 + 0.93 + 0.76 = 5.70 → 5.70 ÷ 6 = 0.95
세로 합 = 5.46 + 6.20 + 5.65 + 5.78 + 5.33 + 5.78 = 34.20 → 34.20 ÷ 6 = 5.70
→ 1번 중심의 새 자리는 (0.95, 5.70)입니다.

같은 회차에서 3번 중심에는 3명만 붙어 (1.25, 5.12) (0.94, 5.05) (1.03, 5.04)의 평균인 (1.07, 5.07)로 조금 옮겨 가고, 2번 중심에는 나머지 63명이 몰려 (3.97, 3.84)로 훌쩍 걸어갑니다. 1회 만에 2번 중심이 2.909만큼 움직인 것입니다 — 데이터 폭이 6 남짓인 판에서 꽤 큰 걸음이지요.

왜 하필 평균일까요? 다른 자리를 골라도 될 것 같은데요. 이 물음의 답은 개념 3에서 나옵니다. 잠깐 기억해 두세요 — 무엇을 잘하려는지 정하고 나면, 중심을 어디로 옮길지도 따라서 정해집니다.

3

언제 멈추는가 — 수렴과 응집도

두 걸음을 되풀이한다고 했는데, 언제까지 되풀이할까요? 답은 놀랄 만큼 단순합니다. 중심이 한 발짝도 움직이지 않으면 끝입니다. 코드로는 한 줄이에요.

ℹ️ 멈추는 조건

if new == centers: — 새로 구한 중심 목록이 지금 중심 목록과 같으면 멈춘다. 이 한 줄이 수렴(convergence)의 정의입니다. 중심이 그대로면 배정도 그대로이고, 배정이 그대로면 평균도 그대로이므로, 그다음은 아무리 돌려도 똑같습니다.

실제로 우리 데이터를 K = 3으로 돌리면 이렇게 걸어갑니다. 시작 중심은 학생 72명 중 16번 · 7번 · 28번을 골라 그 자리에 두었습니다. (컴퓨터는 첫 학생을 0번이라 부르므로, 16번은 앞에서 열일곱째 학생입니다. 어떻게 골랐는지는 개념 4에서 다룹니다.)

회차중심 셋묶음 크기 SSE가장 많이 걸은 거리
0 (시작)[0.79, 5.46] [1.47, 5.32] [1.03, 5.04] ———
1[0.95, 5.70] [3.97, 3.84] [1.07, 5.07] 6 / 63 / 3329.542.909
2[1.48, 5.84] [4.85, 3.16] [1.54, 5.17] 14 / 45 / 13202.021.112
3[2.00, 5.88] [5.97, 1.66] [2.40, 5.13] 16 / 26 / 3058.061.874
4[1.39, 5.62] [6.13, 1.40] [3.07, 5.17] 21 / 24 / 2727.980.668
5[1.46, 5.52] [6.13, 1.40] [3.37, 5.19] 26 / 24 / 2225.580.301
6(그대로) 26 / 24 / 2225.580.000 → 끝
6회에 멈춥니다. 중심이 실제로 움직인 것은 5번이고, 6회째는 "안 움직였다"를 확인하는 회차입니다. 최종 중심을 소수 셋째 자리까지 적으면 [1.460, 5.517] · [6.132, 1.402] · [3.367, 5.187], SSE는 25.5772입니다.

표에서 두 가지를 읽어 봅시다.

첫째, 걸음이 점점 짧아집니다. 2.909 → 1.112 → 1.874 → 0.668 → 0.301 → 0. 쭉 줄기만 하는 것은 아니고 3회에 잠깐 다시 커지지만, 큰 흐름은 줄어드는 쪽입니다. 중심이 제자리를 찾아갈수록 옮길 거리가 없어지는 것이지요.

둘째, 누가 어느 무리인지가 중심보다 먼저 굳습니다. 표의 '묶음 크기'는 그 회차의 ① 배정에서 나온 값입니다. 4회는 21 / 24 / 27이었다가 5회에 26 / 24 / 22가 되고 6회에도 그대로예요. 그런데 5회의 배정은 4회가 만들어 놓은 중심으로 나눈 것입니다. 즉 4회를 마친 순간 분할은 이미 정답이었습니다. 그때의 SSE는 27.98이고, 중심이 한 번 더 걸어간 5회에야 25.58이 됩니다 — 분할이 먼저 굳고, 중심의 좌표가 뒤늦게 자리를 잡습니다.

SSE — 잘 묶였는지 재는 자

표에 계속 나온 SSE가 무엇인지 이제 밝힙니다. 응집도(SSE, sum of squared errors)는 이렇게 잽니다.

ℹ️ 응집도(SSE)의 정의

sum(min(dist2(p, c) for c in centers) for p in points)

점 하나마다 가장 가까운 중심까지의 거리를 제곱해서, 72개를 전부 더한 값입니다. 점들이 중심 가까이 뭉쳐 있으면 작아지고, 흩어져 있으면 커집니다. 작을수록 잘 묶였다는 뜻이에요. 9차시의 손실과 성격이 같습니다 — 다만 손실은 정답과의 차이였고, 이것은 중심과의 차이입니다.

이제 개념 2에서 미뤄 둔 물음에 답할 수 있습니다. 왜 하필 평균으로 옮기는가?

SSE는 '중심까지 거리의 제곱 합'입니다. 그런데 어떤 점들의 모임에서 거리 제곱 합을 가장 작게 만드는 자리가 바로 그 점들의 평균입니다. 중심을 미지수로 두고 제곱 합을 쓰면 아래로 볼록한 이차식이 되고, 그 최솟값이 평균에서 잡히기 때문이에요. 그러니 '이동' 단계에서 평균으로 옮기는 것은 취향이 아니라 SSE를 그 상황에서 가장 많이 깎는 유일한 선택입니다.

⚠️ 바꿔 보면 나빠진다

'평균 대신 중앙값으로 옮기면 어떨까?'를 실제로 돌려 보았습니다. 같은 시작점에서 SSE가 25.5772 → 72.5611로 나빠집니다. 묶음 크기는 26 / 24 / 22로 똑같아 보이는데 속에 든 사람이 다릅니다. 손실을 SSE로 정한 순간 '평균으로 옮긴다'는 규칙도 함께 정해진 것입니다.

덧붙여, SSE는 회차마다 줄기만 하고 늘어나지 않습니다. 배정 단계는 각 점을 더 가까운 중심으로 옮기니 줄고, 이동 단계는 방금 본 이유로 줄기 때문입니다. 표에서 329.54 → 202.02 → 58.06 → 27.98 → 25.58로 한 번도 오르지 않은 것이 그 증거입니다. 점의 개수가 유한하니 나눌 수 있는 가짓수도 유한하고, 줄기만 하는 값이 유한한 후보 안에서 돌면 언젠가 반드시 멈춥니다. k-평균이 무한 루프에 빠지지 않는 까닭입니다.

세 덩어리로 흩어진 점들 위에서 k-평균이 Iteration #0부터 #14까지 돌며 중심을 옮기고, 검은 경계선이 움직이면서 점들의 소속 표시(+·×·○와 색)가 바뀌다가 멈추는 애니메이션.
검은 선은 세 중심이 나눠 가진 영역의 경계입니다. 이 그림은 시작 중심 둘이 서로 가까이 붙은 불리한 자리에서 출발합니다. 중심이 걸어갈 때마다 경계가 옮겨 가고 점들의 소속이 바뀝니다. 회차가 갈수록 바뀌는 점이 줄어들고, 마지막 장면(Iteration #14)에서는 세 덩어리가 각자 자기 중심을 찾았습니다. 바뀌는 점이 하나도 없는 순간이 수렴이고, 우리 데이터에서 그 순간은 6회째였습니다. 출처: Chire, Wikimedia Commons (CC BY-SA 4.0)
⚠️ 그래도 상한은 둔다

"반드시 멈춘다"가 증명되어 있어도, 오늘 코드에는 MAX_ITER = 20이라는 상한이 박혀 있습니다. 여러분이 뒤에서 채울 빈칸 하나가 바로 멈추는 조건인데, 그 칸을 잘못 채우면 영원히 도는 프로그램이 되어 브라우저 탭이 멎기 때문입니다. 참고로 씨앗 1~300과 K 1~7을 조합해 2,100번을 전수로 돌려 상한에 걸린 경우는 0건이었고, 가장 많이 움직인 경우도 15번이었습니다. 20은 넉넉합니다.

4

같은 데이터, 같은 코드, 다른 답

개념 3에서 한 가지를 슬쩍 넘어갔습니다. 시작 중심을 어떻게 골랐는가?

답은 시시합니다. 학생 72명 중 K명을 무작위로 뽑아 그 자리에 두었습니다. 씨앗(seed) 4로 뽑으니 16번 · 7번 · 28번이 나왔습니다. 이런 방식은 대충 정한 것처럼 보이지만, 사실 널리 쓰이는 표준적인 출발입니다. 데이터가 있는 자리에 중심을 두어야 아무도 안 붙는 중심이 생길 확률이 낮아지거든요.

그런데 여기에 이 차시에서 가장 중요한 함정이 숨어 있습니다. 씨앗만 7로 바꿔 보겠습니다. 데이터도 그대로, 코드도 한 글자 안 고쳤습니다.

씨앗뽑힌 학생멈춘 회차 묶음 크기SSE수렴했나
416 · 7 · 286회 26 / 24 / 2225.5772O
723 · 10 · 463회 48 / 8 / 1668.2336O
둘 다 "수렴했다"고 보고합니다. 그런데 SSE가 2.7배 차이 납니다.

씨앗 7이 한 일을 그림으로 옮기면 이렇습니다. 오른쪽 아래에 있던 24명짜리 덩어리를 8명 + 16명으로 쪼개고, 왼쪽 위와 가운데 위에 있던 26명 + 22명을 48명 한 무리로 합쳐 버렸습니다. 우리는 이 데이터를 만든 사람이라 정답을 압니다. 견주어 보면 이렇습니다.

씨앗 4씨앗 7
제자리에 들어간 학생72 / 7250 / 72
SSE25.577268.2336
멈춘 회차6회3회
씨앗 7에서는 22명이 남의 무리에 들어갔습니다. ⚠️ 이 대조는 수업용 장치일 뿐입니다 — 진짜 비지도 문제에는 견줄 정답이 애초에 없습니다.
⚠️ 이 차시에서 가장 중요한 문장

'수렴했다'는 '맞았다'가 아닙니다. 씨앗 7은 3회 만에 깔끔하게 멈췄습니다. 오류도 경고도 없었습니다. k-평균에는 틀렸다고 알려 주는 장치가 없습니다. 멈췄다는 것은 '더 좋아질 방향을 찾지 못했다'는 뜻일 뿐, '이보다 좋은 답이 없다'는 뜻이 아닙니다.

이 현상에는 이름이 있습니다. 지역 최적(local optimum)입니다. 골짜기 여러 개가 있는 산에서 한 골짜기 바닥에 앉아 있는 것과 같아요. 주변은 다 오르막이니 더 내려갈 곳이 없어 보이지만, 산 너머에 더 깊은 골짜기가 있습니다.

ℹ️ 9차시와 정반대다

9차시 경사하강법에서는 출발점을 (0, 0) · (100, −50) · (−30, 200) · (8.93, 42.25)로 네 번 바꿔 돌려도 네 경우 모두 w 8.93457 · b 42.24510 · 손실 7.32469로 똑같은 답에 닿았습니다. 손실 함수가 골짜기 하나뿐인 그릇 모양이었기 때문입니다. k-평균은 그렇지 않습니다. 골짜기가 여러 개예요. 같은 '조금씩 좋아지는' 방법인데도 결과가 이렇게 갈립니다.

언제 실패하는가 — 씨앗 20개를 전부 돌려서

씨앗 7만 나쁜 것일까요, 아니면 흔한 일일까요? 세어 보면 됩니다. 씨앗 1부터 20까지 스무 번 돌린 결과입니다.

결과SSE묶음 크기 몇 개의 씨앗어느 씨앗
제대로 나눔25.577226 / 24 / 22 15개나머지 전부
실패 ①64.416848 / 14 / 10 3개13 · 16 · 20
실패 ②65.016048 / 13 / 11 1개18
실패 ③68.233648 / 8 / 16 1개7
20개 중 15개 성공, 5개 실패. 실패한 셋 다 48명짜리 덩어리를 그대로 안고 있습니다. 씨앗을 1~300까지 넓히면 236개(78.7%)가 성공합니다. (묶음 크기를 적은 순서는 시작 중심을 뽑은 순서라 씨앗마다 다릅니다. 여기서는 48을 앞에 두어 나란히 놓았을 뿐이니, 화면의 순서와 다르더라도 놀라지 마세요.)

실패한 다섯(7 · 13 · 16 · 18 · 20)에 공통점이 있을까요? 처음 떠오르는 짐작은 "시작 중심 셋이 같은 덩어리에서 뽑혔을 때"입니다. 그럴듯하죠. 그런데 확인해 보면 틀립니다.

  • '같은 덩어리에서 둘 이상 뽑힌' 씨앗은 13개다. 그런데 실패는 5개뿐이다. 겹쳤어도 여덟 번은 잘 나눴다.
  • 씨앗 4를 보라. 셋이 전부 왼쪽 위 한 덩어리 안에서 뽑혔는데도 6회를 걸어 제대로 나눴다.
  • 실패한 다섯은 '멀리 떨어진 오른쪽 아래 덩어리에서 둘 이상 뽑힌' 씨앗 목록과 정확히 일치한다. 프로그램이 두 목록을 직접 견주어 True를 찍는다.
  • 거꾸로, 시작 중심 셋이 서로 다른 세 덩어리에서 나온 경우는 20번 중 7번인데, 그 7번은 모두 성공했다.

왜 그럴까요? 왼쪽 위 덩어리와 가운데 위 덩어리는 서로 가깝습니다. 거기서 중심 둘이 뽑혀도 두 중심이 서로를 밀어내며 갈라섭니다. 반면 오른쪽 아래 덩어리는 멀리 떨어져 있습니다. 거기에 중심 둘이 갇히면, 남은 중심 하나가 위쪽 48명을 통째로 떠맡아야 합니다. 그리고 위쪽 48명은 한 무리로 묶여도 그럭저럭 뭉쳐 보여서 갈라질 힘이 생기지 않습니다.

이 설명이 정말 맞는지, 씨앗에 기대지 말고 손으로 찍어서 확인해 보았습니다. 시작 중심 셋을 한 덩어리 안에 아무렇게나 던져 넣고 1,000번씩 돌린 결과입니다.

시작 중심 셋을 어디에 던졌나1,000번 중 실패결론
오른쪽 아래 덩어리 안 (멀리 떨어진 것) 840번 (84.0%)대부분 진다
가운데 위 덩어리 안 17번 (1.7%)거의 이긴다
왼쪽 위 덩어리 안 0번 (0.0%)거의 지지 않는다
판 전체 아무 데나 420번 (42.0%)절반 가까이 진다
씨앗 4가 셋 다 왼쪽 위 덩어리에서 뽑히고도 제대로 나눈 것은 우연이 아니었습니다. 그 덩어리에서는 1,000번을 던져 한 번도 지지 않았습니다. 다만 0%가 곧 '절대 안 진다'는 아닙니다 — 훨씬 더 많이 던져 보면 극히 드물게 지는 경우도 나옵니다.

⚠️ 다만 "오른쪽 아래에 몰아 찍으면 반드시 진다"고 말하면 안 됩니다. 840번이 졌다는 것은 160번은 이겼다는 뜻이기도 합니다. 시작점이 답을 정하는 것이 아니라 흔드는 것입니다.

💡 실무에서는 어떻게 하나

답은 싱겁습니다. 여러 번 돌려서 SSE가 가장 작은 것을 고릅니다. 20번 돌리면 15번은 25.5772가 나오니 그중 최솟값을 고르면 됩니다. 널리 쓰이는 라이브러리에도 '여러 번 돌려 가장 좋은 것을 고른다'가 기본 설정으로 들어 있습니다. 더 나아가 시작 중심을 서로 멀리 떨어뜨려 뽑는 방법(k-means++)도 널리 쓰입니다. 방금 우리가 찾아낸 실패 규칙 — 멀리 떨어진 덩어리에 중심이 몰리면 안 된다 — 을 그대로 뒤집은 아이디어입니다.

조용한 고장 하나 — 빈 무리

실패에는 한 가지 유형이 더 있습니다. 씨앗 274로 돌리면 묶음 크기가 48 / 24 / 0이 나옵니다. 중심 하나에 아무도 붙지 않은 것입니다. 시작 중심 셋을 거의 같은 자리에 몰아 두어도 같은 일이 생깁니다 — 직접 찍어 보면 2회 만에 멈추고 묶음이 0 / 24 / 48, SSE는 70.2012가 나옵니다.

이때 프로그램은 어떻게 될까요? 오류가 날까요? 무한 루프에 빠질까요? 둘 다 아닙니다. 깔끔히 '수렴'하고 답을 내놓습니다. 다만 K = 3이라고 써 놓고 실제로는 두 무리인 답입니다. 화면에는 아무 경고도 뜨지 않습니다. 결과 표의 0을 사람이 발견해야만 알 수 있어요.

5

K는 누가 정하는가 — 팔꿈치와 함정

지금까지 K = 3으로 놓고 이야기했습니다. 그런데 왜 3인가요? 사실은 그림을 보고 "세 덩어리네" 하고 사람이 정한 것입니다. 정직하게 말하면 k-평균은 무리의 개수를 스스로 찾지 못합니다. 그것이 이 알고리즘의 가장 큰 약점이에요.

그러면 SSE를 자로 쓰면 되지 않을까요? SSE가 가장 작아지는 K를 고르는 것이지요. 좋은 생각처럼 들립니다. K를 1부터 7까지 바꿔 가며 SSE를 재 봅시다. (K마다 씨앗 12개를 다 돌려 가장 좋은 값을 적었습니다. 개념 4에서 배운 대로요.)

KSSE앞 K보다 줄어든 양 직전 SSE의 몇 %
1552.34——
270.20482.1487.3%
325.5844.6263.6%
419.795.7822.6%
516.033.7619.0%
612.863.1819.8%
710.382.4819.3%
SSE는 K가 커질수록 한 번도 빠짐없이 줄어듭니다. 그래서 이 표만 보면 K = 7이 최고입니다.

여기서 멈추면 안 됩니다. K를 계속 키우면 어떻게 될까요? K = 72, 즉 학생 수와 같게 하면 중심을 모든 점 위에 하나씩 놓을 수 있습니다. 그러면 모든 점이 자기 중심 위에 정확히 얹히니 SSE = 0.0입니다.

⚠️ 지표를 최소화하면 되는 게 아니다

'SSE가 가장 작은 K를 고른다'를 끝까지 밀면 답은 언제나 K = 데이터 수입니다. 학생 72명을 72개 무리로 나누면 SSE는 0입니다. 완벽한 점수이고, 완벽하게 쓸모없는 답입니다. 지표를 가장 좋게 만드는 것과 문제를 푸는 것은 다른 일입니다. 이 교훈은 11차시(과적합)와 12차시(정확도 98%짜리 쓸모없는 모델)에서 다시, 더 크게 만납니다.

그러면 K는 어떻게 정할까요? 표를 다시 보되 SSE 값이 아니라 줄어든 양을 보세요.

무리의 개수 K SSE 123 456 7 5522760 팔꿈치 552.34 70.20 25.58 19.79 여기서부터는 거의 평평하다
K = 1에서 3까지는 뚝뚝 떨어지다가, K = 4부터 곡선이 팔을 편 듯 평평해집니다. 꺾이는 자리가 팔꿈치입니다.

줄어든 양을 보면 이야기가 달라집니다. 482.14 → 44.62 → 5.78 → 3.76 → 3.18 → 2.48. K = 3에서 4로 갈 때의 감소(5.78)는 K = 2에서 3으로 갈 때의 감소(44.62)의 7.7분의 1입니다. 그리고 K = 4 이후로는 직전 SSE의 19~23%씩 거의 일정하게 줄어듭니다.

일정하게 줄어든다는 것은 새로 나눌 덩어리가 더는 없다는 뜻입니다. 멀쩡한 덩어리를 억지로 반으로 자르면 SSE는 얼마간 줄기 마련이거든요. 그러니 뚝 떨어지다가 평평해지기 시작하는 자리를 고르면 됩니다. 이것이 팔꿈치 방법(elbow method)입니다. 우리 데이터의 팔꿈치는 K = 3이고, 눈으로 센 덩어리 수와 같습니다.

💡 팔꿈치가 만능은 아니다

우리 데이터는 팔꿈치가 또렷했습니다. 하지만 실제 데이터에서는 곡선이 완만하게만 내려가 어디가 팔꿈치인지 사람마다 다르게 보이는 경우가 훨씬 많습니다. 그래서 실무에서는 팔꿈치 하나만 쓰지 않고, 문제에 대한 지식을 함께 씁니다. "우리 회사는 마케팅 메시지를 네 종류밖에 못 만든다"면 K = 4로 가는 식이지요. K를 정하는 것은 끝까지 사람의 일입니다.

6

이름을 붙이는 일은 누구의 몫인가

K = 3으로 제대로 나눈 결과를 다시 봅시다. 최종 중심 셋과 인원은 이랬습니다.

무리중심 (공부 시간, 폰 시간) 인원사람이 읽으면
첫째(1.460, 5.517)26명 공부 1.5시간 · 폰 5.5시간
둘째(6.132, 1.402)24명 공부 6.1시간 · 폰 1.4시간
셋째(3.367, 5.187)22명 공부 3.4시간 · 폰 5.2시간
알고리즘이 낸 것은 왼쪽 세 칸까지입니다. 오른쪽 칸은 우리가 읽은 것입니다.

이 셋에 이름을 붙여 보라고 하면 교실에서 여러 답이 나올 겁니다. '폰을 많이 쓰는 무리', '공부에 집중한 무리', '중간 무리'. 그런데 여기서 반드시 짚어야 할 것이 있습니다.

⚠️ 알고리즘은 이름을 붙이지 않았다

알고리즘이 내놓은 것은 0번 · 1번 · 2번이라는 번호와 좌표뿐입니다. 게다가 그 번호는 시작 중심을 뽑은 순서일 뿐이라, 씨앗이 바뀌면 같은 무리가 다른 번호를 받습니다. '알뜰 고객' 이니 '공부형' 이니 하는 이름은 전부 사람이 붙인 해석입니다. 그리고 해석에는 책임이 따릅니다 — 26명을 '폰 중독 무리'라고 이름 붙이는 순간, 그 이름은 데이터에 없던 판단을 데이터에 있는 것처럼 만듭니다.

지도학습과 견주면 차이가 뚜렷합니다. 8차시에서 씨앗을 'A 품종'이라고 부를 수 있었던 것은 누군가 미리 그렇게 적어 두었기 때문입니다. 군집에는 그런 사람이 없습니다. 그래서 군집 결과에는 맞고 틀림이 없습니다. SSE로 '얼마나 뭉쳤는지'는 잴 수 있어도, '올바른 무리인지'는 잴 수 없습니다.

그래도 쓸모가 있다 — 세 가지 쓰임

고객 세분화

쇼핑몰이 구매 금액·방문 빈도·장바구니 크기로 손님을 묶습니다. 무리마다 다른 안내를 보내려는 것이지요. 어떤 무리가 있는지 미리 알 수 없으니 군집이 맞는 도구입니다.

이상 탐지

어느 무리에도 잘 붙지 않는 점을 찾습니다. 기계 고장 신호, 카드 부정 사용, 서버 침입이 이런 모양입니다. 이상은 드물어서 이름표를 모으기가 어렵기 때문에 비지도가 유리합니다.

이미지 색 압축

사진에 쓰인 1,600만 가지 색을 K = 16으로 묶어 대표 색 16개만 남깁니다. 각 화소를 자기 무리의 대표 색으로 바꾸면 파일이 확 작아지는데 눈에는 비슷해 보입니다.

이상 탐지는 우리 데이터로도 확인할 수 있습니다. 학생 한 명을 (12.0, 12.0)에 놓아 봅시다. 하루에 12시간 공부하고 12시간 폰을 쓴다는, 물리적으로 말이 안 되는 기록입니다. 돌려 보면 이렇게 됩니다.

셋째 무리 중심셋째 무리 인원전체 SSE
이상치 없음(3.37, 5.19)22명25.5772
이상치 하나 넣음(3.74, 5.48)23명141.2697
점 하나가 전체 SSE를 5.5배로 부풀립니다.

여기서 두 가지를 동시에 배웁니다. 첫째, SSE가 갑자기 치솟으면 이상한 점이 섞였다는 신호일 수 있습니다 — 이것이 이상 탐지입니다. 둘째, k-평균은 이상치에 약합니다. 평균으로 옮기는 방식이라 멀리 있는 점 하나가 중심을 끌고 갑니다. 4차시에서 1750cm 학생 한 명이 평균 키를 166cm에서 198cm로 끌어올렸던 것과 같은 일입니다. 그때 배운 전처리를 안 하면 여기서 대가를 치릅니다.

같은 붓꽃 150송이를 세 축의 3차원 산점도로 두 번 그린 그림. 왼쪽 'k-Means Clusters'는 k-평균이 나눈 Cluster 1·2·3을, 오른쪽 'Iris Species'는 실제 품종 setosa·versicolor·virginica를 +·×·○ 기호와 빨강·파랑·초록으로 표시했다.
8차시에서 산점도로 만난 붓꽃 데이터(세 품종 150송이)로 k-평균을 돌린 결과입니다. 왼쪽은 k-평균이 정답표 없이 나눈 세 무리, 오른쪽은 사람이 적어 둔 실제 품종입니다. 두 그림을 견주어 보세요 — 이 실행에서 k-평균은 setosa(빨강 +)를 둘로 쪼개고(Cluster 1·2), versicolor와 virginica는 한 무리로 합쳤습니다(Cluster 3). 개념 4의 씨앗 7과 같은 모양의 지역 최적이지요. 또 하나, 왼쪽에 붙은 이름은 'Cluster 1·2·3'이라는 번호뿐입니다. '세토사'라는 이름은 알고리즘이 아니라 사람이 붙였습니다. 출처: Chire, Wikimedia Commons (Public domain)
💻

손으로 ① — 중심을 내가 찍고, 한 걸음씩 걸려 본다

아래 판에 학생 72명이 전부 찍혀 있습니다. 개념 3의 표에 나온 그 데이터입니다. 👣 한 걸음을 누르면 ① 배정과 ② 이동이 번갈아 한 단계씩 진행됩니다. 중심이 지나온 자리는 선으로 남습니다.

중요한 것은 시작 중심을 여러분이 정한다는 점입니다. 씨앗 번호로 뽑을 수도 있고, 판 위를 직접 눌러 원하는 자리에 놓을 수도 있습니다. 일부러 나쁜 자리에 찍어 보세요. 그것이 오늘 과제 중 하나입니다. 공책을 펴 두세요. 아래 세 과제의 답은 확인 문제에서 다시 묻습니다.

🎯 k-평균 재생기 — 중심이 걸어가는 것을 회차마다 본다 INTERACTIVE

시작 중심을 씨앗으로 뽑거나 직접 찍은 뒤, 👣 한 걸음으로 배정과 이동을 번갈아 봅니다. ▶ 끝까지는 멈출 때까지 자동으로 돌립니다. 숫자 칸의 값은 파이썬 실습(손으로 ②)에서 나오는 값과 소수점까지 같습니다 — 견주어 보세요.

3
아직 배정 안 된 학생 무리 1~7 = ★ = 중심 · 같은 색 굵은 선 = 그 중심이 지나온 자취
회차0
다음 걸음① 배정
응집도 SSE—
묶음 크기—
가장 많이 걸은 거리—
제자리(72명 중)—
지금까지 만난 최소 SSE—
씨앗 4로 시작 중심 셋을 뽑았습니다. 👣 한 걸음을 눌러 배정부터 시작하세요.
[안내] 학생 72명 · K = 3 · 씨앗 4로 시작합니다.

※ 상한은 20회입니다. 씨앗 1~300과 K 1~7을 조합해 2,100번을 전수로 돌려 상한에 걸린 경우는 0건이었으니, 정상으로 쓰면 여기에 걸릴 일은 없습니다.
※ '제자리' 칸은 데이터를 만든 우리만 아는 정답과 견준 값입니다. 진짜 비지도 문제에는 이런 칸이 없습니다 — 수업에서 확인하려고 얹어 둔 것입니다.

과제 ① 표를 채운다 — 씨앗만 바꿔 네 번

K를 3에 두고, 씨앗을 바꿔 가며 ▶ 끝까지를 네 번 누릅니다. 매번 씨앗을 바꾸면 자동으로 처음 상태로 돌아갑니다.

씨앗멈춘 회차묶음 크기SSE제자리 / 72제대로 나눴나
4
7
13
18

SSE 칸을 먼저 보세요. 네 줄이 전부 같습니까? 다르다면 그것이 오늘의 첫 결론입니다 — 같은 데이터에 같은 코드를 돌렸는데 답이 갈렸다. 그리고 넷 다 "멈췄다"고 보고했다는 것도 함께 적어 두세요.

과제 ② 반례를 만든다 — 내 손으로 지게 만들기

이제 시작 중심을 '내가 직접 찍기'로 바꿉니다. 판을 누르면 그 자리에 중심이 놓입니다. K개를 다 찍어야 걸음을 뗄 수 있어요.

  • 일부러 지게 만들기. 중심 셋을 모두 오른쪽 아래 덩어리 안에 찍어 보세요. 개념 4의 표대로 열 번 중 여덟 번은 집니다 — 두세 번 안에 성공할 거예요. SSE가 64.4168 · 65.0160 · 68.2336 중 하나가 나오면 반례를 만든 것입니다. 그때 묶음 크기와 '제자리' 칸을 함께 적으세요. (제자리가 50 / 72면 22명이 남의 무리에 들어간 것입니다.)
  • 거꾸로도 해 보기. 이번에는 중심 셋을 왼쪽 위 덩어리 안에만 몰아 찍습니다. 거의 언제나 SSE 25.5772가 나올 겁니다 — 1,000번을 던져 한 번도 안 졌던 그 자리입니다. 같은 '몰아 찍기'인데 왜 여기서는 안 질까요? 한 줄로 적어 보세요.
  • 가장 빨리 끝내기. 이번에는 세 덩어리에 하나씩 찍습니다. 몇 회 만에 멈췄나요? 씨앗 4의 6회보다 적나요? (씨앗 2 · 5 · 9 · 10은 2회 만에 끝납니다. 여러분은 몇 회에 끝냈나요?)
  • 빈 무리 만들기. 중심 셋을 거의 같은 자리에 겹쳐 찍어 보세요. 묶음 크기 셋 중에 0이 나오면 성공입니다. 이때 오류가 나던가요? 프로그램은 뭐라고 보고하던가요?
  • 중심을 데이터 밖에 찍기. 점이 하나도 없는 오른쪽 위 빈 곳에 중심 하나를 찍어 보세요. 그 중심은 어디로 걸어가나요? 자취를 눈으로 따라가 보세요.

과제 ③ 팔꿈치를 내 손으로 찾는다

K 슬라이더를 1부터 7까지 옮기며 ▶ 끝까지를 돌립니다. K마다 씨앗을 서너 개 바꿔 가며 돌리고, 그중 가장 작은 SSE를 적으세요. 개념 4에서 배운 대로, 한 번만 돌린 값은 못 믿습니다.

K1234567
가장 작은 SSE
앞 K보다 줄어든 양 —

아랫줄을 다 채운 뒤 어디에서 줄어드는 양이 갑자기 작아지는지 동그라미를 치세요. 거기가 팔꿈치입니다. 그리고 한 줄로 답해 보세요 — K를 계속 키우면 SSE는 어디까지 내려갈까?

💡 K = 1로 두고 한 번 돌려 보세요

중심이 하나뿐이면 고를 것도 없습니다. 72명 전부가 그 하나에 붙으니까요. 그래서 중심은 한 걸음에 72명의 평균 자리 (3.600, 4.044)로 가고, 2회에 멈춥니다(실제로 움직인 것은 1번). 씨앗을 무엇으로 바꿔도 답이 똑같습니다 — 골짜기가 하나뿐이라 지역 최적이 없는 것이지요. 그때 SSE가 552.34이고, 이 값이 오늘의 기준선입니다 — '전혀 나누지 않았을 때'의 흩어진 정도예요. K = 3의 25.58은 그 기준선의 4.6%입니다.

💻

손으로 ② — k-평균을 직접 짠다

시뮬레이터는 누군가 미리 만들어 둔 것입니다. 이제 코드로 갑니다. 아래 프로그램은 304줄이지만 겁먹을 것 없습니다. k-평균 알고리즘 자체는 [B] 구역의 87줄이고, 나머지는 데이터를 만들고 화면에 그리고 실험을 돌리는 부분이라 이미 다 채워져 있습니다. 여러분이 채울 자리는 다섯 곳(????? 여섯 칸)뿐입니다.

⚠️ 빈칸 다섯 곳은 전부 개념 절에 나왔다

① dist2() — 두 점 사이 거리의 제곱. 가로 차이의 제곱 + 세로 차이의 제곱. 뿌리는 씌우지 않습니다(개념 2).
② assign() — 거리 목록 d에서 가장 작은 값이 몇 번째인지. min(d)만 쓰면 거리 값이 나오지 번호가 안 나옵니다. 가장 자주 걸리는 자리입니다.
③ move() — 붙은 점들의 가로 평균과 세로 평균 두 칸(개념 2의 6명 계산).
④ inertia() — 각 점에서 가장 가까운 중심까지의 거리 제곱. '가장 가까운'이 곧 min입니다(개념 3).
⑤ kmeans() — 멈추는 조건. 새 중심 목록과 지금 중심 목록을 견줍니다(개념 3).

⑤를 잘못 채워도 탭은 멎지 않습니다. MAX_ITER = 20이 끊어 주거든요. 대신 "20회 상한에 걸려 멈췄다(수렴 못 함)"이라는 줄이 뜹니다.

제대로 채웠다면 [C] 구역에 개념 3의 표와 똑같은 여섯 줄이 찍힙니다. 329.54 → 202.02 → 58.06 → 27.98 → 25.58 → 그리고 중심이 한 발짝도 움직이지 않았다. 6회에서 끝.

그다음이 진짜입니다. 프로그램은 이어서 이렇게 합니다.

  • [D] 나뉜 결과를 글자 그림으로 그린다. A · B · C가 무리이고 *가 중심이다. 중심은 그 칸의 점 하나를 덮어쓰니 그림에서 점을 세면 72개가 안 됩니다.
  • [E] 씨앗만 7로 바꿔 같은 일을 한다. 3회에 멈추고 SSE 68.2336이 나온다. 그림에서 한 덩어리가 통째로 한 글자가 된 것을 눈으로 확인하세요.
  • [F] 씨앗 1~20을 전부 돌려 성공·실패를 센다. 그리고 실패의 규칙을 프로그램이 스스로 검사해 두 목록이 같은가? True를 찍는다.
  • [G] K를 1~7로 바꿔 팔꿈치 표를 만든다. 과제 ③에서 손으로 채운 표와 견주세요.
  • [H] K = 72이면 SSE가 얼마인지 직접 계산해 보여 준다.
💡 다 돌아갔으면 부숴 보세요

코드 맨 아래에 도전 다섯이 주석으로 있습니다. 값 하나씩만 바꿔 보세요. 아래는 미리 돌려 본 답이니, 여러분 화면의 숫자와 맞는지 확인하는 데 쓰세요.

무엇을 바꾸나어떻게 되나
K를 2 · 4 · 5로
(씨앗 4 고정)
K=2 → 5회 · 묶음 [24, 48] · SSE 70.2012
K=4 → 9회 · 묶음 [8, 18, 22, 24] · SSE 21.7435
K=5 → 7회 · 묶음 [6, 7, 13, 22, 24] · SSE 19.6703
MAX_ITER를
1 · 2 · 3 · 4 · 5로
SSE 329.54 → 202.02 → 58.06 → 27.98 → 25.5772
⚠️ 4회와 5회는 묶음이 이미 [22, 24, 26]인데 SSE가 다르다.
6회에야 '수렴 True'가 뜬다.
move()의 평균을
중앙값으로
씨앗 4: SSE 25.5772 → 72.5611
씨앗 7: 68.2336 → 76.5218 · 씨앗 13: 64.4168 → 70.6596
→ 셋 다 나빠진다.
학생 한 명을
(12.0, 12.0)에
셋째 중심 [3.37, 5.19] → [3.74, 5.48]
전체 SSE 25.5772 → 141.2697 (5.5배)
시작 중심 셋을
거의 같은 자리에
2회에 '수렴' · 묶음 [0, 24, 48]
⚠️ 오류도 무한 루프도 아니다. K=3인데 답은 2무리.
📖

정리 — 세 알고리즘을 다 짜 보고 나서

8 · 9 · 10차시에 걸쳐 알고리즘 셋을 직접 짰습니다. 이제야 이 표가 읽힙니다. 셋을 나란히 놓아 보세요.

8차시 k-NN9차시 경사하강10차시 k-평균
이름표가 필요한가 필요하다 (품종 A/B)필요하다 (시험 점수) 필요 없다
학습의 갈래 지도 · 분류지도 · 회귀비지도 · 군집
'학습'이 남기는 것 없다. 데이터를 통째로 들고 있는다 숫자 둘 (w, b) 중심 K개의 좌표
되풀이하는 고리 없다 (물어볼 때 그때 계산) 예측 → 오차 → 기울기 → 갱신 배정 → 이동
출발점을 바꾸면 해당 없음 답이 같다 답이 갈린다
사람이 정해 주는 값 k (이웃 수)학습률 · 회차K (무리 개수)
잘했는지 재는 자 정확도손실(평균 제곱 오차)응집도(SSE)
오늘까지 잰 값 k=5 시험 93.3%
(기준선 66.7%)
손실 7.3275
(공식 해 7.32469)
SSE 25.5772
(K=1 기준선 552.34)

표에서 가장 눈에 띄는 줄은 '출발점을 바꾸면'입니다. 9차시의 손실 함수는 골짜기가 하나뿐인 그릇이라 어디서 출발해도 같은 바닥에 닿았습니다. k-평균은 골짜기가 여럿이라 출발점이 답을 바꿉니다. '조금씩 좋아지는 방향으로 간다'는 같은 생각인데, 지형이 다르면 결과가 다릅니다.

또 하나. '학습이 남기는 것' 줄을 보세요. k-NN은 아무것도 남기지 않고 데이터를 통째로 안고 있습니다. 경사하강은 숫자 둘로 줄였습니다. k-평균은 중심 K개로 줄였습니다. 학습이란 결국 '많은 데이터를 몇 개의 숫자로 줄이는 일'이라는 감각을, 13차시 퍼셉트론부터는 훨씬 큰 규모로 다시 만나게 됩니다.

🎯 어느 것을 고를까 — 결정의 길잡이
  • 맞혀야 할 정답이 데이터에 적혀 있는가? 없으면 군집부터 생각한다.
  • 맞힐 것이 종류인가 숫자인가? 종류면 분류(k-NN), 숫자면 회귀(선형회귀).
  • 이름표를 붙이는 데 돈과 시간이 얼마나 드는가? 사진 100만 장에 사람이 일일이 이름표를 다는 일을 떠올려 보세요. 비지도가 매력적인 가장 현실적인 이유가 이것입니다.
  • 무리가 몇 개인지 아는가? 모르면 팔꿈치로 짐작하되, 끝까지 사람이 정한다는 것을 잊지 않는다.
⚠️ 오늘 배운 것의 한계를 정확히 적어 두자

k-평균은 동그란 덩어리를 찾는 데 강합니다. 길쭉한 초승달 모양이나 도넛처럼 생긴 무리는 잘 못 찾아요 — 중심에서 가까운 것끼리 묶는 규칙 자체가 동그란 모양을 전제하기 때문입니다. 또 모든 점을 반드시 어느 무리엔가 넣습니다. '어디에도 안 속함'이라는 답을 낼 줄 모릅니다. 그래서 이상치 하나가 중심을 끌고 가고, SSE를 5.5배로 부풀립니다. 오늘 만든 것은 군집의 전부가 아니라 가장 단순한 한 가지입니다.

✅

확인 문제

✍️ 문제마다 답을 쓰고 제출하기를 누르세요. 제출하면 모범 답안이 열리고, 제출한 답은 선생님께 전달됩니다.

1. k-평균의 두 걸음을 순서대로 쓰고, 알고리즘이 언제 멈추는지 한 문장으로 답하시오. 또 이동 단계에서 왜 하필 평균 자리로 옮기는지 까닭을 적으시오.
📖 모범 답안

① 배정 — 각 점에서 중심 K개까지의 거리를 재고, 가장 가까운 중심에 그 점을 붙인다.
② 이동 — 한 중심에 붙은 점들의 가로 평균·세로 평균을 구해 중심을 그 자리로 옮긴다.

멈추는 때 — 이동 단계에서 새로 구한 중심이 지금 중심과 똑같으면 멈춘다 (new == centers). 중심이 그대로면 배정도 그대로이고, 배정이 그대로면 평균도 그대로라 그다음은 아무리 돌려도 같기 때문이다. 이것을 수렴이라 한다.

평균인 까닭 — 잘했는지 재는 자를 SSE(중심까지 거리의 제곱 합)로 정했기 때문이다. 어떤 점들의 모임에서 거리 제곱 합을 가장 작게 만드는 자리가 바로 평균이다. 그래서 평균으로 옮기는 것은 취향이 아니라 SSE를 그 상황에서 가장 많이 깎는 유일한 선택이다. 실제로 평균 대신 중앙값으로 바꿔 돌리면 SSE가 25.5772에서 72.5611로 나빠진다.

2. 어떤 회차에서 3번 중심에 학생 셋이 붙었다. 좌표는 (1.25, 5.12) · (0.94, 5.05) · (1.03, 5.04)이다. 3번 중심의 새 자리를 소수 둘째 자리까지 구하시오. 그리고 같은 회차에서 2번 중심에는 63명이 붙어 (3.97, 3.84)로 옮겨 갔다. 두 중심의 걸음 길이가 크게 다를 수밖에 없는 까닭을 설명하시오.
📖 모범 답안

계산 — 가로 합 = 1.25 + 0.94 + 1.03 = 3.22 → 3.22 ÷ 3 = 1.0733…
세로 합 = 5.12 + 5.05 + 5.04 = 15.21 → 15.21 ÷ 3 = 5.07
→ 새 자리는 (1.07, 5.07). 원래 자리 (1.03, 5.04)에서 거의 안 움직였다.

까닭 — 새 중심은 붙은 점들의 평균이므로, 붙은 점들이 좁은 곳에 모여 있으면 평균도 그 근처에 잡힌다. 3번 중심에는 자기 바로 옆의 세 명만 붙었으니 거의 제자리다. 반면 2번 중심에는 72명 중 63명이 몰렸고, 그 63명은 판 전체에 흩어져 있다. 그 평균은 판의 한복판(3.97, 3.84)이 되므로 중심이 2.909만큼 크게 걸어간다.

즉 걸음 길이는 '붙은 점이 얼마나 넓게 퍼져 있는가'가 정한다. 회차가 갈수록 걸음이 짧아지는 것도 같은 이유다 — 각 중심이 제 덩어리만 맡게 되면서 붙은 점들의 퍼진 범위가 좁아진다.

3. 오늘 시뮬레이터와 파이썬으로 직접 돌린 결과다. K = 3, 씨앗 4로 돌렸을 때 회차별 SSE 다섯 개를 순서대로 적고, 몇 회에 멈추었는지, 중심이 실제로 움직인 것은 몇 번인지 쓰시오. 또 4회와 5회의 묶음 크기와 SSE를 견주어 무엇을 알 수 있는지 한 줄로 적으시오.
📖 모범 답안

회차별 SSE — 1회 329.54 → 2회 202.02 → 3회 58.06 → 4회 27.98 → 5회 25.58, 그리고 6회에 걸음이 0.000이 되어 끝난다.

6회에 멈추고, 실제로 움직인 것은 5번이다. 6회째는 "안 움직였다"를 확인하는 회차이므로 걸음 수에 넣지 않는다. 최종 중심은 [1.460, 5.517] · [6.132, 1.402] · [3.367, 5.187], 묶음 26 / 24 / 22, SSE 25.5772다.

4회와 5회 — 표의 묶음 크기는 그 회차의 ① 배정 결과다. 4회는 21 / 24 / 27, 5회는 26 / 24 / 22이고 6회도 26 / 24 / 22다. 그런데 5회의 배정은 4회가 만든 중심으로 나눈 것이므로, 4회를 마친 순간 이미 분할은 정답이었다. 그때의 SSE는 27.98이고, 중심이 한 번 더 걸어간 5회에야 25.58이 된다. → 분할(누가 어느 무리인가)이 먼저 굳고, 중심의 좌표가 뒤늦게 자리를 잡는다. 묶음 크기만 보고 "다 끝났다"고 판단하면 안 된다는 뜻이다.

4. 오늘 직접 만든 팔꿈치 표를 근거로 답하시오. K를 1에서 7까지 바꿨을 때 가장 작은 SSE는 552.34 / 70.20 / 25.58 / 19.79 / 16.03 / 12.86 / 10.38이었다. (가) 이 데이터의 적정 K는 얼마이고 그 근거는 무엇인가? (나) "SSE가 가장 작은 K를 고르면 된다"는 주장을 반박하시오.
📖 모범 답안

(가) 적정 K = 3. 근거는 SSE 값 자체가 아니라 줄어드는 양이다. 줄어든 양은 482.14 → 44.62 → 5.78 → 3.76 → 3.18 → 2.48이다. K = 3에서 4로 갈 때의 감소(5.78)는 K = 2에서 3으로 갈 때(44.62)의 7.7분의 1이다. 그리고 K = 4부터는 직전 SSE의 19~23%씩 거의 일정하게만 줄어든다. 일정하게 줄어든다는 것은 새로 나눌 덩어리가 더 없고 멀쩡한 덩어리를 억지로 자르고 있다는 뜻이다. 뚝 떨어지다 평평해지기 시작하는 자리가 팔꿈치이고, 여기서는 K = 3이다.

(나) 반박 — SSE는 K를 키우면 반드시 줄어든다. 중심이 늘면 각 점이 더 가까운 중심을 얻을 수 있기 때문이다. 극단으로 밀어 K = 72(학생 수와 같게)로 두면 중심을 모든 점 위에 하나씩 놓을 수 있고, 그때 SSE = 0.0이다. 완벽한 점수이면서 아무것도 알려 주지 않는 답이다. 그러므로 'SSE가 가장 작은 K'는 언제나 '데이터 개수'라는 쓸모없는 답이 되어, K를 고르는 기준이 될 수 없다. K는 팔꿈치와 문제에 대한 지식으로 사람이 정한다.

5. 씨앗을 7로 바꿨더니 3회 만에 깔끔히 수렴했는데 묶음이 48 / 8 / 16, SSE는 68.2336이 나왔다. 오류도 경고도 없었다. (가) 이 현상의 이름은 무엇이며 실무에서는 어떻게 대처하는가? (나) 씨앗 4는 시작 중심 셋이 모두 같은 덩어리에서 뽑혔는데도 제대로 나눴다. 그렇다면 실패의 진짜 조건은 무엇인가?
📖 모범 답안

(가) 지역 최적(local optimum)이다. 주변을 아무리 둘러봐도 더 나은 곳이 없어 멈췄지만, 멀리에는 더 좋은 답(SSE 25.5772)이 있다. k-평균에는 틀렸다고 알려 주는 장치가 없어서 '수렴했다'가 곧 '맞았다'가 아니다.

대처 — ① 시작 중심을 바꿔 여러 번 돌리고 SSE가 가장 작은 것을 고른다. 씨앗 1~20 중 15개가 25.5772를 냈으므로 몇 번만 돌려도 좋은 답을 만난다. ② 시작 중심을 서로 멀리 떨어뜨려 뽑는다(k-means++).

(나) '같은 덩어리에서 둘 이상 뽑히면 실패'는 틀린 규칙이다. 씨앗 20개 중 그런 경우는 13개인데 실패는 5개뿐이다(씨앗 4도 그중 하나다). 실패한 다섯(7 · 13 · 16 · 18 · 20)은 '멀리 떨어진 오른쪽 아래 덩어리에서 둘 이상 뽑힌' 씨앗 목록과 정확히 일치한다.

까닭은 이렇다. 왼쪽 위와 가운데 위 덩어리는 서로 가까워서, 거기서 중심 둘이 뽑혀도 둘이 서로를 밀어내며 갈라선다. 반면 멀리 떨어진 덩어리에 중심 둘이 갇히면 남은 중심 하나가 위쪽 48명을 통째로 떠맡게 되고, 그 48명은 한 무리로도 그럭저럭 뭉쳐 보여서 갈라질 힘이 생기지 않는다. 실제로 시작 중심 셋이 서로 다른 세 덩어리에서 나온 7번은 7번 모두 성공했다.

6. 학생 한 명을 (12.0, 12.0)에 놓았더니 전체 SSE가 25.5772에서 141.2697로 뛰었다. (가) 이 사실을 쓸모 있게 쓰는 방법을 한 가지 들고, 그것이 왜 비지도학습에 잘 맞는지 설명하시오. (나) 같은 사실이 k-평균의 약점이기도 하다. 왜 그런지, 그리고 4차시에서 배운 무엇과 이어지는지 쓰시오. (다) 나뉜 세 무리에 '공부형 · 폰형 · 중간형' 같은 이름을 붙이는 일은 누가 하는가?
📖 모범 답안

(가) 이상 탐지(anomaly detection). 어느 무리에도 잘 붙지 않는 점, 또는 그 점 때문에 SSE가 갑자기 치솟는 것을 이상 신호로 읽는다. 기계 고장, 카드 부정 사용, 서버 침입 같은 일이 이런 모양이다. 이상한 일은 드물어서 이름표를 모으기가 어렵다 — "이것이 침입 사례다"라고 적힌 데이터를 수천 건 모으는 것이 거의 불가능하다. 그래서 정답표 없이 '보통과 다른 것'을 찾는 비지도 방식이 잘 맞는다.

(나) 이상치에 약하다. 이동 단계가 평균으로 옮기기 때문이다. 평균은 멀리 있는 값 하나에 크게 끌려간다. 실제로 셋째 무리의 중심이 (3.37, 5.19)에서 (3.74, 5.48)로 끌려갔고 그 무리 인원도 22명에서 23명으로 늘었다. 게다가 k-평균은 모든 점을 반드시 어느 무리엔가 넣으므로 '어디에도 안 속함'이라는 답을 낼 줄 모른다. → 4차시의 결측·이상치 처리와 이어진다. 1750cm 학생 한 명이 평균 키를 166cm에서 198cm로 끌어올렸던 것과 정확히 같은 일이다. 전처리를 건너뛰면 여기서 대가를 치른다.

(다) 사람이 한다. 알고리즘이 내놓은 것은 번호(0 · 1 · 2)와 중심 좌표뿐이고, 그 번호조차 시작 중심을 뽑은 순서일 뿐이라 씨앗이 바뀌면 같은 무리가 다른 번호를 받는다. 이름은 전부 사람의 해석이며, 해석에는 책임이 따른다 — 26명에게 '폰 중독 무리'라는 이름을 붙이는 순간, 데이터에 없던 판단이 데이터에 있는 것처럼 보이게 된다.

🔁 되돌아보기

오늘 정답표가 한 줄도 없는 점 72개를 배정과 이동 두 걸음만으로 나눴고, 중심이 6회에 걸쳐 걸어가 SSE를 첫 회차 329.54에서 25.5772까지 떨어뜨렸습니다 (나누지 않았을 때의 기준선 552.34의 4.6%입니다). 그리고 씨앗 하나만 바꿔 같은 코드가 지는 장면도 직접 만들어 봤습니다. 다음 시간에는 8차시의 k-NN을 다시 꺼내 훈련 데이터에서 100점을 받는 것이 왜 나쁜 소식인지, 그리고 데이터를 훈련용과 시험용으로 갈라서 재는 법을 배웁니다.

🔎

더 알아보기

시작 중심을 똑똑하게 뽑는 법, 팔꿈치를 믿으면 안 되는 때, 그리고 사진 속의 k-평균

오늘의 학생 72명 첫 중심 멀다 → D² 크다 둘째 중심이 뽑힐 확률 왼쪽 위 덩어리 66.8% 가운데 위 덩어리 32.4% 오른쪽 아래 덩어리 0.8% 뽑힐 확률 ∝ 가장 가까운 중심까지 거리의 제곱(D²)
원리 더 깊이

k-means++ — 멀리 있는 점일수록 중심으로 뽑힐 확률을 높인다

개념 4에서 찾은 실패 규칙은 "멀리 떨어진 덩어리에 중심이 둘 이상 몰리면 진다"였습니다. 2007년 데이비드 아서(David Arthur)와 세르게이 바실비츠키(Sergei Vassilvitskii)가 발표한 k-means++는 이 문제를 시작 중심을 뽑는 단계에서 막습니다. 첫 중심은 아무 점이나 무작위로 고르고, 둘째부터는 각 점이 이미 뽑힌 중심 중 가장 가까운 것까지 거리의 제곱(D²)에 비례하는 확률로 뽑힙니다.

오늘 데이터로 직접 계산해 보았습니다. 첫 중심이 여는 장면 표의 둘째 학생 (5.84, 1.37) — 오른쪽 아래 덩어리 — 에 떨어지면, 둘째 중심이 같은 덩어리에서 또 뽑힐 확률은 0.8%뿐입니다. 가까운 점은 D²가 작아 거의 뽑히지 않고, 멀리 있는 두 덩어리가 확률의 99% 이상을 나눠 가집니다.

그래도 만능은 아닙니다. 같은 72명으로 여러 번 되풀이해 돌려 보면, k-means++로 뽑을 때가 무작위로 세 명을 뽑을 때보다 지는 횟수가 뚜렷이 적지만 0이 되지는 않습니다. 확률일 뿐이라 나쁜 조합이 여전히 나올 수 있어요. 그래서 실무에서는 k-means++로 뽑기와 여러 번 돌려 SSE가 가장 작은 것 고르기를 함께 씁니다. scikit-learn 같은 널리 쓰이는 라이브러리도 k-means++를 기본 시작 방법으로 둡니다.

가로축 Number of clusters k(1~100), 세로축 SSE (Inertia)인 점그래프. k=1에서 약 670이던 SSE가 앞쪽에서 가파르게 떨어지고 k가 커질수록 0 근처로 평평해진다.
오해 바로잡기

덩어리가 하나도 없어도 팔꿈치는 생긴다

그래프는 무리 개수 k를 1부터 100까지 늘리며 SSE를 잰 것입니다. 앞쪽이 가파르고 뒤가 평평한, 전형적인 팔꿈치 모양이지요. 그런데 이 데이터는 점을 판 위에 고르게 흩뿌린 것이라 덩어리가 애초에 없습니다. SSE는 K를 늘리면 언제나 줄고, 줄어드는 양은 대개 점점 작아지므로 곡선은 거의 언제나 어딘가에서 휘어 보입니다. 가로축을 넓게 잡을수록 더 그렇습니다.

오늘 판과 같은 넓이에 72점을 고르게 흩어 놓고 K를 하나씩 늘려 가며 SSE를 재 보아도 마찬가지입니다. 곡선만 보면 휘어 있지만, 직전 SSE보다 줄어든 비율을 계산해 보면 K가 늘 때마다 천천히 미끄러질 뿐입니다. 오늘 데이터처럼 87.3% · 63.6%에서 22.6%로 뚝 끊기는 자리가 없어요.

그래서 팔꿈치는 '덩어리가 몇 개인가'의 증거가 아니라 짐작의 출발점입니다. 통계학자들은 이 점을 보완하려고 실루엣 계수(점이 제 무리에 얼마나 잘 붙어 있는지를 잰다)나, 실제 데이터의 SSE 곡선을 고르게 흩뿌린 가짜 데이터의 곡선과 견주는 갭 통계량 같은 방법을 만들었습니다.

그래프: 고른 분포 데이터의 k별 SSE · 출처: Chire, Wikimedia Commons (CC BY-SA 4.0)

부채를 든 채 옆으로 돌아보는 에이다 러브레이스의 초상화. 색이 15가지로 줄어 배경과 드레스에 얼룩덜룩한 색 경계가 보인다.
현장

사진 속의 k-평균 — 색 15가지로 그린 에이다 러브레이스

개념 6에서 본 이미지 색 압축을 실제로 한 그림입니다. 앨프리드 에드워드 샬롱이 그린 에이다 러브레이스(최초의 프로그램으로 꼽히는 해석 기관 노트를 쓴 사람)의 초상화를 k-평균으로 K = 15, 곧 색 15가지로 줄였습니다. 배경과 드레스에 얼룩처럼 보이는 경계가 바로 한 색 무리와 다른 색 무리가 만나는 자리입니다.

방법은 오늘 짠 코드와 똑같습니다. 화소 하나를 (빨강, 초록, 파랑) 세 값을 가진 점 하나로 보면, 이 그림은 697 × 999 ≈ 약 70만 개의 점이 3차원 색 공간에 흩어진 데이터입니다. ① 배정 — 화소마다 가장 가까운 대표 색을 찾고, ② 이동 — 대표 색을 붙은 화소들의 평균 색으로 옮깁니다. 학생 72명의 2차원이 화소 70만 개의 3차원으로 바뀌었을 뿐이에요. 이 그림은 50회째 반복 결과로, 그다음 회차에서 멈췄습니다.

왜 압축이 될까요? 원래 화소는 색마다 24비트(빨강·초록·파랑 각 8비트)가 필요하지만, 15색 중 하나를 가리키는 번호는 4비트면 충분합니다. 화소 정보가 약 6분의 1로 줄어요. 이 차시의 움직이는 그림(GIF)도 한 장면에 최대 256색의 팔레트만 쓰는 형식이라, 만들 때 이런 색 줄이기를 거칩니다.

그림: 에이다 러브레이스 초상(A. E. 샬롱)을 k-평균으로 15색으로 줄인 것 · 출처: E.Le Morvan, Wikimedia Commons (CC BY-SA 4.0)