임계값은 고르는 게 아니라 재는 것
유사도 0.85. 이 숫자를 어디서 가져왔냐는 질문에 "그쯤이 적당해 보여서"라고 답하지 않으려고 한 일.
지난 글에서 에이전트에 붙인 기억 서비스 이야기를 했다. 이번엔 그 안에 박혀 있는 숫자 하나에 대한 이야기다.
사용자가 "나 12월에 이사 가"라고 말하면 모델이 그 사실을 기억으로 쓴다. 그런데 지난달에 이미 "11월에 이사 간다"는 기억이 저장돼 있었다면? 새로 쓸 것인가, 기존 것을 덮을 것인가, 아니면 둘 다 남길 것인가.
판단 기준은 두 문장이 같은 사실인가이다. 그리고 그건 임베딩 유사도로 잰다. 그러면 몇부터 같은 사실인가.
이 숫자가 어디에 박히는가
노드에 커서를 올리면 연결과 설명이 나타납니다.
임계값이 두 개 필요하다. 하나는 같은 분류 안에서 중복을 병합할 때, 다른 하나는 자동으로 쌓인 낡은 기억을 덮어쓸 때. 뒤쪽이 더 위험하다. 앞은 중복이 남는 실수지만, 뒤는 남의 사실을 지우는 실수다.
감으로 정하면 어떻게 되는가
처음엔 0.9로 시작했다. 높으면 안전할 것 같아서였다.
실환경에서 바로 걸렸다. 사용자가 "12월로 이사 날짜가 바뀌었다"고 정정했는데, 낡은 「11월 이사」 기억이 검색 결과 1위로 계속 올라왔다. 정정이 반영되지 않은 게 아니라, 정정과 낡은 기억이 둘 다 살아서 낡은 쪽이 더 잘 검색된 것이다.
임계가 높으면 안전한 게 아니라, 틀린 기억이 오래 산다.
그래서 쟀다
두 문장 쌍을 모아 실제 운영과 같은 임베딩 모델로 유사도를 냈다. 쌍은 넷으로 나눴다. 무엇을 원하는지가 무리마다 다르기 때문이다.
- 같은 사실 — 표현만 다르다. 덮어써야 한다
- 정정 — 값이 바뀌었다. 덮어써야 한다. 낡은 값이 남으면 위의 사고가 난다
- 상세 추가 — 같은 사실에 근거와 경위가 붙었다. 덮어쓰면 그 부연을 잃는다
- 별개 사실 — 분류만 같고 내용이 다르다. 덮어쓰면 남의 기억이 사라진다
앞의 둘은 임계 위에 있어야 하고, 뒤의 둘은 아래에 있어야 한다. 임계값을 정한다는 건 이 네 분포를 한 축에 늘어놓고 자를 자리를 찾는 일이다.
0.80을 눌러 보면 상세 추가 넷 중 둘이 덮인다. 0.90으로 올리면 아무것도 잘못 덮지 않지만 같은 사실 스무 개 중 여섯 개를 놓친다. 0.85에서만 보호해야 할 두 무리가 온전하다.
여유는 대칭이 아니다
0.85가 중간이라서 고른 게 아니다. 두 방향의 실패 비용이 다르기 때문이다.
여유가 얇은 쪽이 덜 위험한 쪽이다. 상세 추가가 덮이면 "왜 그랬는지"를 잃지만 "무엇인지"는 남고 이력에서 복구된다. 별개 사실이 덮이면 그 사실 자체가 사라지고, 사라진 줄도 모른다.
그래서 위험한 축에 여유를 몰아줬다. 0.22와 0.006이라는 비대칭은 실수가 아니라 결정이다.
같은 원칙이 중복 병합 임계에도 적용됐다. 이쪽은 쌍 28개로 따로 쟀다.
| 무리 | 뜻 | min | max | mean |
|---|---|---|---|---|
| 근사 중복 | 같은 사실, 표현만 다름 — 병합 대상 | 0.8217 | 0.9706 | 0.8885 |
| 정당 갱신 | 같은 주제, 사실이 바뀜 | 0.7508 | 0.9543 | 0.8612 |
| 별개 사실 | 분류만 같음 — 병합 금지 | 0.3225 | 0.7113 | 0.5291 |
두 분포가 겹치지 않았다. 간격 0.1104. 이 안 어디를 잘라도 되지만, 병합 금지 쪽에 여유를 크게 둬서 0.80으로 정했다 — 아래로 0.089, 위로 0.022.
유사도로 안 되는 구간
측정하다 보니 임계값으로 풀리지 않는 지대가 드러났다.
정정이라는 성격은 같은데, 문장 틀이 유지되면 잡히고 바뀌면 안 잡힌다. 임계를 낮춰서 해결할 수 없다 — 낮추면 진짜 별개 사실까지 함께 덮이기 때문이다.
이 지대는 유사도의 문제가 아니라 유사도로 물을 수 없는 질문이다. 답은 모델이 "이 기억을 대체한다"고 명시적으로 지목하게 하는 것이지, 숫자를 조정하는 게 아니다. 측정의 성과는 0.85를 얻은 게 아니라 0.85로 안 되는 구간의 경계를 안 것이었다.
검색 쪽에서는 임계를 아예 버렸다
쓰기 경로에서 임계가 통했으니 읽기 경로에도 두면 되겠다고 생각했다. 검색 결과에서 점수가 낮은 것을 잘라내는 흔한 방법이다.
재보니 반대였다.
자를수록 나빠졌다. 원인은 질의 유형마다 점수 스케일이 다르다는 것이었다 — 주제를 묻는 질의는 1위가 0.397, 개인 사실을 묻는 질의는 1위가 0.092였다. 같은 축에 놓고 자르면 개인 사실 쪽은 정답부터 잘린다.
임베딩 점수는 절대적인 값이 아니다. 같은 0.3이라도 어떤 질의에서는 최상위고 어떤 질의에서는 잡음이다. 쓰기에서는 두 문장을 직접 비교하니 절대값이 의미를 갖지만, 검색에서는 질의마다 축이 달라진다.
그래서 검색에는 절대 임계를 두지 않고 순서만 쓴다. 대신 결과를 좁히는 일은 점수가 아니라 조건으로 했다. 사용자 범위로 거른 것만으로 재현율이 이만큼 올랐다.
+20%p. 스코프 필터를 보안 장치로만 생각했는데, 실제로는 검색 품질 장치이기도 했다. 확실히 아닌 것을 먼저 빼는 편이 점수로 자르는 것보다 낫다.
이 측정이 증명하지 못하는 것
정직하게 적어 둔다.
통계적 유의성 검정이 아니다. 실물 쌍의 경험적 분포가 갈라지는지를 본 결정 지원용 측정이다. 임베딩은 결정적이라 같은 쌍은 항상 같은 값을 낸다. 반복 측정에 의미가 없고, 불확실성의 원천은 하나뿐이다 — 이 표본이 모집단을 대표하는가.
보호해야 할 한쪽이 희소하다. 상세 추가 무리는 쌍이 넷뿐이다. 그 형태의 기억 자체가 코퍼스에 드물어서 2차 채굴에서도 못 늘렸다. 앞서 본 0.006이라는 간격은 표본으로는 못 좁힌다. 운영 로그의 점수 분포를 보면서 보정해야 하고, 0.85 근처가 자주 찍히면 그게 임계를 올리라는 신호다.
도메인이 좁다. 세 가지 업무 페르소나의 대화에서 뽑은 쌍이다. 다른 도메인의 문장 형태는 재보지 않았다.
남은 것
임계값은 고르는 게 아니라 재는 것이다. 0.85는 판단이 아니라 관측이다. 누가 물으면 표를 보여주면 된다.
두 방향의 실패 비용을 먼저 정하라. 어느 쪽으로 틀리는 게 덜 아픈지 정하고 나면 여유를 어디에 몰아줄지가 자동으로 나온다. 그걸 안 정하면 임계는 그냥 가운데로 간다.
측정의 성과는 숫자가 아니라 경계다. 문장 틀이 바뀐 정정은 어떤 임계로도 못 잡는다는 걸 알게 된 것이, 0.85를 얻은 것보다 값이 컸다. 안 되는 구간을 알면 거기엔 다른 장치를 만들면 된다.
같은 도구가 다른 자리에서 반대로 작동한다. 쓰기에서 통한 절대 임계가 검색에서는 정답부터 잘라냈다. 축이 고정된 자리와 질의마다 움직이는 자리는 다른 문제다.