에듀테크 프로젝트 대시보드
카운트-민 스케치 (Count-Min Sketch)
블룸 필터가 "있냐 없냐"에 답한다면, 카운트-민 스케치는 "이 항목이 지금까지 몇 번 나왔나?"에 아주 작은 고정 메모리로 답하는 확률적 자료구조다. 항목을 하나도 저장하지 않고, 2차원 카운터 표(깊이 d × 폭 w)와 d개의 해시 함수만 쓴다. 추정값은 항상 실제값 이상(과대추정)이며 절대 과소추정하지 않는다 — 그래서 이름이 Count-Min: 여러 칸 중 최솟값을 답으로 고른다.
초록 = 방금 +1 한 칸 · 주황 = 조회가 확인한 d개 칸 · 파랑 = 그중 최솟값 = 최종 추정값
i(0..d-1)마다 서로 다른 해시 hᵢ(x) mod w로 열을 구한다.O(d).왜 항상 과대추정? 내가 실제로 넣은 만큼은 무조건 더해져 있고, 거기에 남의 충돌분이 얹히기만 한다(카운터는 음수가 안 됨). 그래서 추정값 ≥ 실제값이 항상 성립하고, d개 중 최솟값을 골라 이 부풀림을 최대한 줄인다.
비유: 4명의 목격자에게 "그 사람 몇 번 지나갔어요?"라고 묻는다. 다들 헷갈려서 실제보다 많게 말하지만(닮은 사람과 혼동), 아무도 적게는 말하지 않는다. 가장 낮게 말한 사람의 답이 제일 믿을 만하다.
슬라이더를 움직이면 값이 실시간으로 바뀝니다.
보장: 확률 1−δ 이상으로 추정값 ≤ 실제값 + ε·N.
폭 w를 키우면 충돌이 줄어 오차 ε가 작아지고, 깊이 d를 키우면
"운 나쁘게 모든 행이 다 오염될" 확률 δ가 지수적으로 작아진다.
핵심은 메모리가 스트림 길이 N·서로 다른 항목 수와 무관하게 w·d로 고정이라는 점.
위 데모에 넣은 항목들의 실제 빈도와 스케치 추정을 나란히 비교한다. 작은 표(d=4, w=10)라 충돌이 잦아 과대추정이 눈에 띈다 — 실제 서비스는 w를 수천으로 키운다.
| 질문 | Count-Min Sketch | dict 카운터 | Bloom / HLL |
|---|---|---|---|
| 답하는 것 | 항목별 빈도(횟수) | 항목별 정확한 빈도 | 있냐없냐 / 고유개수 |
| 메모리 | 고정 w·d | 고유 항목 수에 비례 | 고정/작음 |
| 오차 | 과대추정 (≤ +ε·N) | 없음 | 확률적 |
| 과소추정 | 절대 없음 | 없음 | — |
| update / query | O(d) | O(1) | O(k) |
| 삭제·감소 | 불가 (음수 X) — Count-Mean-Min·CU 변형 존재 | 가능 | 보통 불가 |
희소한 항목일수록 위험: 실제로 2번 나온 항목이 큰 항목들과 충돌하면 추정이 크게 부풀 수 있다. 그래서 CMS는 "소수의 초대형 빈발 항목을 찾는" 용도에 강하고, 꼬리(rare item)의 정확한 값에는 약하다.
import hashlib, math class CountMinSketch: def __init__(self, epsilon=0.001, delta=0.01): self.w = math.ceil(math.e / epsilon) # 폭: 오차 ε self.d = math.ceil(math.log(1 / delta)) # 깊이: 실패확률 δ self.table = [[0] * self.w for _ in range(self.d)] def _cols(self, key): h = hashlib.sha256(str(key).encode()).digest() for i in range(self.d): # 행마다 다른 해시 hi = int.from_bytes(h[i*4:i*4+4], "big") yield i, hi % self.w def update(self, key, c=1): for i, j in self._cols(key): self.table[i][j] += c def query(self, key): return min(self.table[i][j] for i, j in self._cols(key)) # cms = CountMinSketch(epsilon=1e-4, delta=1e-3) # 약 108KB # for word in stream: cms.update(word) # cms.query("apple") # 실제값 이상, 대개 근접
합치기(merge): 두 스케치를 칸끼리 그냥 더하면 두 스트림을 합친 스케치가 된다. 덕분에 여러 서버에서 따로 집계한 뒤 한곳에서 합산하기 좋다(분산 집계).
사촌들: 최솟값 대신 편향 보정을 하는 Count-Mean-Min, 넣을 때 최솟값 칸만 올려 과대추정을 줄이는 Conservative Update(CU Sketch), 양수·음수를 모두 다루는 Count Sketch.