빈도는 중요도가 아니다: 게이트를 통과하지 못한 순위 축
우리는 코퍼스 빈도에 따라 도메인 어휘의 순위를 매겼고, 상위 20개는 모두 일반적인 단어였습니다. 계획된 데이터 게이트가 어떻게 잘못된 축의 출시를 막았는지에 대한 설명입니다.
저희는 가장 중요한 용어들이 예산이 빠듯한 프롬프트에 들어갈 수 있도록 도메인 어휘의 순위를 매겨야 했습니다. 가장 명확한 신호는 빈도였습니다. 신뢰할 수 있는 말뭉치에서 각 용어가 얼마나 자주 나타나는지 세고, 그 횟수로 순위를 매기면 끝이었습니다. 저희는 그것을 구축하고 측정했고, 상위 20개 용어는 "상담", "시술", "얼굴", "예약" 및 일반 모델이 이미 완벽하게 알고 있는 16개의 다른 단어들로 나왔습니다. 단 하나의 브랜드 이름도 목록에 오르지 못했습니다. 독특한 어휘를 장려하기 위해 구축했던 축이, 정작 장려할 필요가 전혀 없는 어휘를 장려하고 있었던 것입니다.
이 글은 왜 빈도가 중요도 신호로서 실패했는지, 그 실패 이전에 저희가 피했던 두 가지 함정(사용량 기반 빈도와 원시 횟수), 그리고 실제로 저희를 구원해 준 부분, 즉 코드와 동작 변경 사이에 분포 확인 절차를 두어 그 축이 저렴한 비용으로 실패할 수 있도록 출시를 설계한 것에 관한 것입니다.
설정: 30개의 슬롯, 수천 개의 후보
이 순위의 소비자는 음성 인식을 위한 키워드 편향 프롬프트였습니다. 프롬프트에는 엄격한 토큰 예산이 있으며, 실제로는 약 30개의 용어가 들어갑니다. 어휘 데이터베이스는 수천 개의 확인된 용어를 보유하고 있습니다. 우리가 어떤 순위 함수를 선택하든 어휘의 1%가 전사에 영향을 미칠지 결정하며, 그 외의 모든 것은 존재하지 않는 것과 같습니다.
기존 순위 시스템은 소스 신뢰도 계층(직접 입력한 용어가 사이트에서 추출한 용어보다, 사이트에서 추출한 용어가 기계로 생성한 용어보다 상위인)을 사용하고 토큰 비용을 동점자 처리 기준으로 삼았습니다. 문제는 분포의 중간 부분이었습니다. 수천 개의 용어가 동일한 계층을 공유하고, 해당 계층 내에서는 동점자 처리 기준이 짧은 문자열을 선호했습니다. 인간의 어휘에서 짧은 문자열은 일반적인 경향이 있습니다. 두 글자로 된 일상 단어들이 프롬프트의 최상단으로 올라오는 동안, 전체 시스템이 전사하기 위해 존재했던 브랜드 이름들은 기준점 아래에 있었습니다.
빈도가 해결책처럼 보였습니다. 중요한 용어는 도메인 텍스트에 자주 나타나야 하고, 불분명한 노이즈는 그렇지 않아야 합니다. 그 직관이 틀린 것은 아니지만, 중요한 면에서 불완전합니다.
첫 번째 함정: 사용 빈도가 자신의 오류를 증폭시킨다
첫 번째 후보 말뭉치는 저희의 자체 사용 로그, 즉 사용자가 실제로 말한 내용의 녹취록이었습니다. 이것은 실제 수요를 반영하기 때문에 가장 매력적인 말뭉치입니다. 또한 그것을 생성하는 파이프라인이 바로 여러분이 수정하려는 파이프라인일 때 미묘한 방식으로 오염됩니다.
저희의 음성 인식 엔진은 특정 브랜드 이름을 비슷하게 들리는 경쟁사 용어로 잘못 인식하고 있었습니다. 사용 말뭉치에서는 올바른 용어가 한 번 나타날 때마다 잘못된 용어가 12번 나타났는데, 이는 말뭉치가 화자가 말한 내용이 아닌 엔진이 기록한 내용을 기록하기 때문입니다. 그 말뭉치로 순위를 매기면 잘못된 인식이 편향 프롬프트로 승격되고, 이는 엔진이 잘못된 용어에 대해 더욱 확신하게 만들며, 이는 다시 그 빈도를 더욱 부풀립니다. 오류가 스스로의 성장에 자금을 대는 셈입니다.
저희가 얻은 교훈은 다음과 같습니다. 순위가 바로잡아야 할 시스템의 출력에서 순위 신호를 절대 도출하지 말라. 이런 형태의 피드백 루프는 스스로를 드러내지 않고, 그저 조용히 실패 모드로 수렴할 뿐입니다.
두 번째 함정: 단순 빈도는 합의가 아닌 반복에 보상을 줍니다
두 번째 후보는 신뢰할 수 있는 도메인 웹사이트의 크롤링 빈도였습니다. 이 방법은 텍스트가 인식 엔진을 거치지 않으므로 피드백 루프를 피할 수 있습니다. 하지만 원시 출현 횟수에는 그 자체의 편향이 있습니다. 즉, 모든 페이지(탐색 메뉴, 바닥글, 홍보 배너)에서 한 용어를 반복하는 단일 사이트가, 각각 다른, 진정으로 중요한 용어를 한 번씩 언급하는 10개의 사이트보다 더 많은 표를 얻을 수 있습니다.
우리는 통계를 원시 개수에서 소스 수준의 문서 빈도로 전환했습니다. 즉, 이 용어가 얼마나 많은 개별 사이트에 나타나는가 하는 것입니다. 7개의 독립적인 클리닉에 걸쳐 나타나는 용어는 전체 도메인의 공통 기반이지만, 한 클리닉 사이트에서 400번 나타나는 용어는 그 클리닉의 마케팅입니다. 우리는 또한 신뢰도 등급을 압도하지 않으면서 그 등급 간의 동점을 깨뜨릴 수 있도록 축의 최댓값을 작게 제한했습니다.
이것은 잘못된 아이디어에 대한 올바른 개선이었습니다. 이 방법은 조작과 편중을 해결했지만, 핵심 문제는 여전히 해결할 수 없었습니다.
측정: 어쨌든 일반적인 단어가 이긴다
카운팅, 중복 제거, 출처 귀속 기능을 구축하고 테스트한 후, 실시간 순위에 축을 연결하기 전에 28개 사이트에 대해 카운터를 실행하여 분포의 상위권을 살펴보았습니다. 문서 빈도 기준 상위 20개는 다음과 같습니다.
상담, 시술, 얼굴, 윤곽, 치료, 예약, 통증, 염증, 이마, 조직, 체형, 가슴, 탄력, 지방, 리프팅, 필러, 그리고 비슷한 성격의 몇몇 단어들이었습니다. 이 모든 단어는 어떤 음성 모델이라도 별도의 설정 없이 정확하게 받아쓰는 단어입니다. 브랜드 이름은 하나도 없었습니다.
돌이켜보면 결과는 명백합니다. 문서 빈도는 여러 출처 간의 일치도를 측정하며, 독립적인 출처들이 가장 많이 동의하는 것은 일반적인 언어입니다. 모든 병원이 "상담"과 "예약"을 쓰지만, 특정 장비나 제품을 취급하는 곳은 일부에 불과합니다. 전문 말뭉치에서, 분포의 상위권에서는 출현의 폭이 일반성과 상관관계를 가집니다. 우리가 원했던 독특한 어휘는 중간에 존재합니다. 즉, 여러 출처에 존재하지만 양극단에는 없습니다.
어떤 종류의 빈도든 "이 용어가 얼마나 흔한가?"라는 질문에 답합니다. 우리의 목적에서 중요성이란 "모델이 이 용어에 대해 얼마나 많은 도움이 필요한가?"였습니다. 이 두 질문은 거의 정반대입니다. 모델이 도움을 필요로 하는 용어는 바로 일반 텍스트에서 충분히 표현되지 않은 용어들입니다.
효과가 있었던 부분: 코드와 동작 사이의 게이트
이것이 값비싼 실수가 되는 것을 막아준 방법은 다음과 같습니다. 기획 검토 중에 한 검토자가 바로 이 위험을 지적했습니다. 즉, 크롤링 빈도가 일반성과 상관관계를 가져 불필요한 것을 강등시키는 대신 오히려 홍보할 수 있다는 것이었습니다. 우리는 논쟁으로 이 문제를 해결할 수 없었기에, 측정을 중심으로 출시 계획을 재구성했습니다.
- 이 축은 단위 테스트로 검증된 불변식(invariant)으로 구현되었습니다. 즉, 모든 용어의 빈도가 0이면 순위는 이전 순위와 바이트 단위로 동일합니다. 데이터가 비어 있다는 것은 축이 존재하지 않음을 의미합니다.
- 빈도 테이블은 별도의 배치 단계로 채워졌으며, 이 단계는 예정된 파이프라인에서 아직 의도적으로 활성화되지 않았습니다.
- 데이터 채우기와 도입 사이에는 문서화된 게이트가 있었습니다. 분포의 상위 20개를 검사하여, 고유한 도메인 용어가 지배적이면 축을 도입하고, 일반적인 단어가 지배적이면 테이블을 잘라내고 도입을 건너뜁니다.
게이트는 실패했고, 그 실패는 비용이 적게 들었습니다. 우리는 테이블을 잘라내고, 축 코드를 '데이터 없음' 불변식 뒤에 휴면 상태로 두었으며, 사전에 명시한 대체 방안을 적용했습니다. 즉, 측정으로 드러난 일반적인 단어들을 가져와 프롬프트에서 용어를 완전히 제외하는 불용어(stopword) 목록에 추가하는 것이었습니다. 측정은 헛되지 않았습니다. 직관 대신 데이터를 기반으로 우리가 요청할 수 있었던 최고의 불용어 후보를 만들어 냈습니다.
일반적인 패턴은 다음과 같습니다. 순위 신호가 그럴듯하지만 입증되지 않았을 때, 메커니즘과 데이터를 별도로 배포하고, 그 사이에 분포 확인을 두며, "실패"가 어떤 모습일지와 대체 방안이 무엇인지에 대해 미리 약속하는 것입니다. 대안, 즉 축을 프로덕션에 바로 연결하고 품질 지표의 변화를 관찰하여 평가하는 방식은 몇 주가 걸리고 시스템 전체에 대한 신뢰를 약화시킵니다.
빈도 대신 사용할 수 있는 것
게이트는 우리에게 더 날카로운 질문을 남겼습니다. 빈도가 아니라면, 무엇이 "모델이 이 용어에 대한 도움이 필요하다"는 것을 예측할 수 있을까요? 사후 논의에서 세 가지 신호가 살아남았습니다.
- 토크나이저 파편화. 모델의 토크나이저가 여러 조각으로 분쇄하는 용어는 모델이 거의 본 적이 없는 용어입니다. 이는 코퍼스 없이 어휘만으로 오프라인에서 계산할 수 있으며, 이전 측정에서는 브랜드 이름과 일상적인 단어를 명확하게 구분했습니다.
- 관찰된 혼동. 운영 환경 인시던트에서 수집된 실제 오인식 사전은 부스팅이 필요한 정확한 용어를 명시합니다. 그 규모는 작지만, 모든 항목은 실측 정보입니다.
- 중간 대역 문서 빈도. 빈도를 사용한다면, 유용한 대역은 중간입니다. 즉, 모든 소스가 아닌 몇몇 독립적인 소스에만 존재하는 용어입니다. 분포의 상단은 일반적인 용어이고, 하단은 노이즈입니다.
이 중 어느 것도 단순한 계수만큼 쉽지 않습니다. 오히려 그것이 핵심입니다.
코퍼스 기반 랭킹 시그널을 출시하기 위한 체크리스트
- 수정하려는 시스템이 생성한 코퍼스로 랭킹을 매기지 마십시오. 닫힌 루프는 자신의 오류를 증폭시킵니다.
- 원시 개수보다는 독립적인 소스에 걸친 문서 빈도를 선호하고, 더 강한 시그널을 압도하지 않도록 축에 상한을 두십시오.
- 적용하기 전에 메트릭의 설명이 아닌 분포의 실제 상위 값을 살펴보십시오. 살펴보기 전에 통과 기준을 결정하십시오.
- "데이터 없음"이 "이전 동작"과 같음이 증명되도록 제로 데이터 불변성으로 축을 구현하고, 데이터 채우기와 적용을 별도의 스위치로 유지하십시오.
- 폴백(대체 방안)을 미리 정해두십시오. 우리의 경우 데이터 기반 불용어 확장이었고, 이는 실패한 축을 유용한 결과물로 바꾸어 주었습니다.
- 빈도가 무엇을 측정하는지 기억하십시오. 흔함과 중요성은 어휘가 전문화될 때 정반대 방향을 가리키는데, 이때가 바로 랭킹이 필요한 유일한 때입니다.
그 축은 코드베이스에 여전히 휴면 상태로 남아 있습니다. 만약 우리가 그 게이트를 통과하는 시그널을 찾게 된다면, 적용은 배치 작업 한 번이면 됩니다. 그때까지, 그 게이트는 제 역할을 했습니다. 그럴듯한 아이디어가 프로덕션 환경 대신 스프레드시트에서 실패하도록 한 것입니다.
관련 글
Whisper 프롬프트는 앞에서부터 224 토큰으로 소리 없이 잘립니다.
Whisper의 `prompt` 매개변수에는 숨겨진 224 토큰 제한이 있습니다. 초과분은 경고 없이 앞에서부터 잘리며, 이는 키워드 편향을 버그로 만들 수 있습니다.
도착하지 않은 완료 이벤트: 두 번의 조용한 드롭
항목별 완료 이벤트가 전체 스트림을 닫았고, 클라이언트는 작업을 시작한 후에 구독했습니다. 작업 자체는 성공했지만 양쪽 모두 알림을 놓쳤습니다.
CDN 캐시 뒤에서 페이지 조회수를 손실 없이 계산하기
엣지 캐싱은 대부분의 페이지 조회가 오리진에 도달하지 못하게 하므로 인라인 카운팅이 작동하지 않습니다. 수집을 비콘으로 옮기고 라우팅이 제공한 가드를 교체하세요.
Redis 키스페이스 알림 및 리스를 사용한 GPU 워커 풀 로드 밸런싱
GPU는 비싸고 한 번에 하나의 무거운 작업만 실행하므로, 라운드 로빈 라우팅은 바쁜 워커 뒤에서 지연됩니다. 여기에 Redis 리스와 키스페이스 알림으로 구축된 바쁨 인지 스케줄러가 있습니다.
MongoDB 변경 스트림을 사용한 Cron 스케줄 핫 스와핑
하드코딩된 cron은 스케줄이 변경될 때마다 재배포를 해야 한다는 것을 의미합니다. 스케줄을 데이터베이스에 저장하고 데몬이 변경 스트림으로 편집에 반응하도록 합니다. 여기에 그 설계와 실패 처리 방법이 있습니다.
CAS와 하트비트를 사용한 단일 실행기 작업용 임대 잠금
홀더가 죽으면 일반적인 락은 영원히 교착 상태에 빠집니다. 리스는 만료됩니다. 여기에 `compare-and-swap` 획득과 하트비트를 사용하여 리스를 구축하는 방법과, 설계를 결정하는 실패 모드가 있습니다.