가족 아카이브에서의 얼굴 인식은 이미 해결된 문제처럼 들리지만, 이 아카이브 속 얼굴들이 생김새 자체가 바뀐다는 걸 떠올리면 이야기가 달라져요. 갓난아기와 그 아기가 자라 열 살이 됐을 때는 사람이 봐도 다르게 보일 뿐 아니라, 인식 모델이 만들어내는 수치 벡터인 얼굴 임베딩도 서로 너무 멀리 떨어져서 하나의 유사도 기준선으로는 그 여정의 양 끝을 동시에 붙잡을 수 없어요. 갓난아기와 열 살 아이를 같은 사람으로 묶을 만큼 느슨한 기준으로 전체 아카이브에 단 한 번의 전역 클러스터링을 돌리면, 서로 다른 사람들까지 묶이기 시작해요. 반대로 낯선 사람들을 구분할 만큼 기준을 조여 버리면, 아이마다 나이가 들면서 여러 "사람"으로 쪼개지고 말아요.
이 글에서 다루려는 핵심 문제는 바로 이거예요. "얼굴을 어떻게 인식하는가"는 성숙한 모델 덕분에 대체로 해결된 문제지만, "유년기 20년에 걸쳐 얼굴이 겪는 변화 속에서 어떻게 동일 인물임을 계속 추적하는가"는 완전히 다른 문제이고, 이에 대해 다뤄진 자료도 훨씬 적어요.
구간을 먼저 나누고, 그다음에 이어붙여요
효과가 있었던 접근법은 하나의 클러스터링이 20년을 통째로 감당하게 하는 대신, 각각 반년씩만 담당하는 여러 개의 작고 쉬운 클러스터링을 돌린 다음 그 결과들을 이어붙이는 방식이었어요.
1단계: 반년 단위 구간 안에서 코사인 유사도 0.50이라는 엄격한 기준으로 그리디 클러스터링을 해요. 6개월이라는 구간 안에서는 나이에 따른 변화가 정말 작아요. 아이의 얼굴 구조는 6개월 만에 잘 조정된 클러스터링을 위협할 정도로 바뀌지 않기 때문에, 엄격한 기준을 그대로 엄격하게 유지하면서도 신뢰할 수 있어요.
진짜 요령은 2단계에 있어요. 각 반년 구간의 클러스터를 가져와서, 훨씬 느슨한 코사인 0.42 기준으로 최대 네 개의 인접 구간에 걸쳐 이어붙이고, union-find로 체인을 전이적으로 합쳐요. 논리는 이래요. 10년을 가로지르는 직접 매칭이라면 깨질 만한 나이 변화라도, 반년에서 다음 반년으로 넘어가는 한 번의 도약이라면 충분히 다리를 놓을 수 있을 만큼 작다는 거예요. 그래서 "갓난아기부터 열 살까지"를 한 번의 유사도 검사로 커버할 필요 없이, 시스템은 그 자체로는 쉬운 짧은 도약들을 이어가기만 하면 되고, 그것들이 모여 결국 긴 거리를 메워요. union-find가 여기서 딱 맞는 구조인 이유는 정체성이 전이적이기 때문이에요. 구간 A의 클러스터가 구간 B의 클러스터와 연결되고, B가 다시 C와 연결된다면, A와 C는 한 번도 직접 비교된 적이 없어도 결국 같은 그룹으로 묶여야 해요.
체인을 인접 구간 네 개(반년 단위 구간이니 2년치)로 제한한 건 실수가 아니라 의도적인 한계예요. 느슨한 0.42 기준으로 체인이 끝없이 이어지게 두면 오류가 누적돼요. 두 번째 도약에서 살짝 너무 관대했던 병합이 네 번째 도약에서 또 살짝 너무 관대한 병합의 근거가 되고, 결국 그렇게 느슨한 병합이 한 번씩 쌓이면서 서로 다른 두 아이가 하나의 정체성을 공유하게 돼 버려요. 범위를 제한하면 각 체인의 최악의 오류를 일정 범위 안에 묶어둘 수 있어요.
엉성한 클러스터는 적은 클러스터보다 나빠요
체인 연결은 후보 인물 클러스터를 만들어내는데, 그중 누군가에게 보여줄 만한 가치가 있는 건 전부가 아니에요. 체인 연결 뒤에는 순도 필터가 돌면서 내부적으로 충분히 일관되지 않아 믿을 수 없는 클러스터를 걸러내는데, 이 필터는 꽤 공격적이에요. 기준이 된 아카이브에서는 목록에 오른 클러스터 수를 545개에서 281개로 줄였어요. 이건 살짝 다듬는 수준이 아니라, 원본 결과의 절반 이상을 버리는 거예요. 대안이라면 545개의 후보 "사람"을 그대로 보여주는 건데, 그중 3분의 1은 사실 조각이거나 잘못된 병합이에요. 그건 더 적더라도 깨끗한 목록을 보여주는 것보다 나빠요. 클러스터링 알고리즘이 벌여놓은 걸 직접 정리하고 싶은 사람은 없어요. 사람들이 원하는 건 이미 대부분 맞는 짧은 목록이에요.
수정 사항은 재클러스터링을 견뎌내야 해요
이건 한 번으로 끝나는 과정이 아니에요. 아카이브가 커지거나 모델이 개선될 때마다 클러스터링은 다시 돌아가는데, 그때마다 누군가 이미 해둔 수정 — 클러스터 이름 바꾸기, 실제로는 같은 사람이었던 두 클러스터 합치기, 아니었던 클러스터 나누기 — 이 사라진다면 받아들일 수 없어요. 그래서 모든 수정 사항은 이름을 키로 하는 지속적인 오버라이드 테이블에 클러스터링 결과와 별도로 기록되고, 재클러스터링이 끝날 때마다 다시 적용돼요. 클러스터링 알고리즘은 제안만 하고, 최종 결정권은 오버라이드 테이블에 있어요. 이건 자동으로 생성된 코드에 대한 버전 관리와 같은 원리예요. 다시 만들어낼 수 있는 결과물이 사람이 직접 손댄 부분을 조용히 덮어써 버리는 일은 절대 없어야 해요.
정직한 한계: 형제자매
이 시스템에 대해 제가 할 수 있는 가장 유용한 말은, 어디서 똑똑한 척을 하지 않는가예요. 이 아카이브 속 형제자매는 서로 코사인 유사도 약 0.33을 기록해요. 무작위로 고른 낯선 두 사람보다는 가깝지만, 클러스터링이 실제로 쓰는 0.50이나 0.42 기준보다는 확실히 낮아요. 더 공격적인 시스템이라면 스스로를 설득해서 병합해 버릴 만큼 가깝고, 그러면서도 가끔은 틀릴 만큼 멀어요. 이 경계선에서 도박을 하는 대신, 시스템은 아슬아슬한 유사도를 실행 지시가 아니라 질문해야 할 신호로 다뤄요. 이런 경우는 사람이 확인해야 할 병합 제안으로만 표시되고, 절대 자동으로 처리되지 않아요. 잘못된 자동 병합은 두 사람의 타임라인을 동시에 조용히 망가뜨리지만, 거절당한 제안은 아무 비용도 들지 않아요.