사내 규정이 다섯 건일 때는 전부 붙여도 됐다. 그런데 9월이 끝나 갈 무렵 총무팀이 인사·복리후생 문서까지 넘기겠다고 하면서 수가 200건으로 늘었다. 넘겨받은 폴더를 열어 파일 목록을 끝까지 내려 보고 나서야 개발팀은 지금까지의 방식이 문서가 다섯 건이라서 통했다는 것을 알았다.
이제는 질문마다 필요한 것만 골라 넣어야 하는데, 고르는 일이 간단하지 않다. 직원은 “업무 관련 강의를 결제하려는데 얼마까지 지원되나요?”라고 묻는데 문서에는 “직원 교육비 한도는 분기당 30만 원이다”라고 적혀 있다. 겹치는 단어가 하나도 없다. 사람은 두 문장이 같은 일을 말한다는 것을 바로 아는데, 단어를 맞춰 보는 검색은 이 문서를 놓친다.
총무팀 담당자였다면 두 문장을 나란히 놓고 같은 이야기라고 답했을 것이다. 사람 없이 그 답을 얻으려면 두 문장이 얼마나 가까운지를 숫자로 잴 수 있어야 한다. 임베딩은 문장을 그렇게 잴 수 있는 숫자로 바꾼다. 개발팀은 벡터 데이터베이스를 설치하기 전에 문서 다섯 개로 계산과 판정부터 확인해 보기로 했다.
텍스트를 벡터로
벡터는 순서가 있는 숫자 묶음이다. [0.2, -0.4, 0.7]은 좌표 셋인 벡터고, 임베딩 함수는 문장이나 문서를 이런 숫자 묶음으로 바꾼다. 검색기는 질문의 벡터와 문서의 벡터를 비교한다.
해시는 같은 바이트를 식별하지만 임베딩은 비슷한 뜻의 문장이 가까이 오도록 학습된 결과다. ‘가깝다’를 가르치는 방법이 대조 학습이다. 질문과 그 답이 있는 문서를 한 쌍으로 묶고 같은 배치의 다른 문서는 짝이 아닌 것으로 두면, 짝끼리는 유사도가 오르고 짝이 아닌 것과는 내리도록 매개변수가 조금씩 움직인다. 이 과정을 수백만 쌍에 반복하면 겹치는 단어가 없어도 같은 질문의 답으로 함께 나오던 표현끼리 가까운 자리에 모인다.
모델은 “강의 결제”와 “교육비”의 뜻을 알지 못한다. 단지 학습 자료에서 두 말이 나란히 나온 적이 많아 서로 가까워졌을 뿐이다. 그래서 학습 자료에 없던 관계는 잘 안 잡힌다. 사내에서만 쓰는 약어, 신제품 이름, 조직 고유의 절차 용어가 그런 자리다. 개발팀이 고른 다국어 E5의 학습 방법은 기술 보고서에 있다. Multilingual E5 기술 보고서
문서 벡터는 미리 계산해 두고 질문이 오면 질문 벡터만 만들어 비교한다. 문서 내용이나 임베딩 모델이 바뀌면 저장된 벡터도 다시 만들어야 한다. 그래서 검색 품질을 지키려면 문서가 어떻게 바뀌는지도 같이 관리해야 한다. 티켓 번호 IT-1042, 정책 코드, 이메일처럼 문자 자체가 식별자인 것은 임베딩이 아니라 정확 일치 검색이나 구조화 필드로 찾는다.
내적·길이·코사인 유사도
두 벡터의 내적은 같은 자리의 숫자를 곱해 더한 값이다. , 면 이다. 차원이 다르면 대응할 좌표가 없으니 계산을 거부한다.
벡터의 길이(L2 노름)는 좌표의 제곱을 더해 제곱근을 취한 값이다. 의 길이는 5고, 각 좌표를 5로 나눈 은 길이가 1이 된다. 이 연산을 L2 정규화라고 하는데 방향은 그대로 두고 크기만 맞춘다.
코사인 유사도는 내적을 두 길이의 곱으로 나눈 값이다. 영벡터에는 적용할 수 없는데, 이때 점수 0으로 바꾸지 말고 오류를 낸다.
“승인이 필요하다”와 “승인이 필요하지 않다”는 주제도 단어도 거의 같아서 임베딩에서는 높은 유사도가 나온다. 점수로는 두 문장이 같은 표현 공간에서 얼마나 가까운지까지만 알 수 있다. 한쪽이 다른 쪽을 뒷받침하는지는 답변을 만들 때 따로 읽어야 한다. 두 벡터를 미리 길이 1로 정규화했다면 코사인은 내적과 같고 유클리드 거리의 제곱은 라서 코사인 내림차순과 거리 오름차순이 일치한다. 정규화하지 않은 벡터에서는 이 관계가 없으니 저장소의 거리 설정을 바꿀 때 확인한다.
코사인을 계산할 때는 큰 숫자를 바로 제곱하면 오버플로가 나므로 먼저 최대 절댓값으로 나누고, NaN·무한대·빈 벡터·영벡터는 거부한다.
코사인 구현이 맞는지 먼저 확인하고 임베딩이 업무 관련성을 잘 표현하는지는 따로 검증한다.
E5 임베딩 어댑터
대표 구현은 intfloat/multilingual-e5-small이고 리비전 614241f622f53c4eeff9890bdc4f31cfecc418b3를 고정했다. 실행 환경은 로컬 추론과 같은 CPU float32, 4스레드다.
E5는 질문 앞에 query: , 문서 앞에 passage: 를 붙이고 한국어에도 같은 접두사를 쓴다. 모델 카드가 안내하는 대로 토큰별 출력을 마스크 평균으로 모으고 L2 정규화한다. 입력은 512토큰까지 받는다. 이 규칙은 이 모델의 것이라 다른 임베딩 모델에 그대로 쓰지 않는다. 부르는 쪽이 질문인지 문서인지만 말하고 접두사는 어댑터가 붙이는 쪽이, 사용자가 접두사를 기억해 붙이는 쪽보다 일관성을 지키기 쉽다. E5 모델의 사용 예제와 제한
배치에는 길이가 다른 텍스트가 섞인다. 짧은 텍스트를 같은 길이로 맞추려고 넣은 패딩은 평균에서 빼야 한다. 분자는 패딩을 0으로 만든 뒤 토큰 축으로 더한 값, 분모는 유효한 토큰 수다. 같은 짧은 질문을 단독으로 처리한 벡터와 더 긴 질문과 묶어 처리한 벡터의 최대 좌표 차이는 약 로, 허용 오차 안이다. 긴 문서는 조용히 자르지 않는다. 접두사와 특수 토큰을 포함해 512토큰을 넘으면 거부한다. 문서 끝의 예외 조건을 잘라 내고 벡터를 만들면 저장된 원문과 벡터가 표현한 내용이 달라진다. 그 밖에 텍스트당 20,000자, 배치당 16개라는 한도도 뒀다.
E5를 차원이 같은 다른 모델로 바꾸면 돌아오는 숫자는 똑같이 384개다. 그런데 두 모델의 좌표는 각자 자기 공간에 있어서 섞어서 비교하면 값이 엉킨다. 모델 A의 문서 벡터와 모델 B의 질문 벡터로 내적을 구할 수는 있지만 그 값에 검색 의미는 없다. 그래서 벡터에는 좌표만이 아니라 어느 공간의 것인지도 함께 적어 두고, 공간이 다르면 검색기가 재색인이 필요하다는 오류를 낸다. 모델을 고를 때는 한국어 지원 외에 실제 문서 길이, 전문 용어, 호출 지연, 차원 수, 데이터 전송 조건, 재색인 비용을 본다. MTEB는 여러 임베딩 작업을 평가하고 모든 작업에서 앞서는 방법은 없다고 보고했다. 어느 작업의 점수인지 구별하고 이 서비스의 질문 집합으로 다시 평가한다. MTEB 논문
전수 검색 기준선
문서 다섯 개면 색인 없이 모든 점수를 계산할 수 있다. 이 전수 계산은 순위가 어디서 나왔는지 추적하고 근사 검색이 놓친 결과를 비교할 기준선이다. 대규모 서비스의 최종 구현으로 쓰지는 않는다.
검색 단위 하나에는 청크 식별자, 조직, 문서 식별자, 원문, 벡터가 들어간다. 여기서는 짧은 문서 하나가 곧 청크 하나다. 요청 주체는 서버가 확인한 읽기 가능 문서 집합을 들고 온다. 클라이언트가 보낸 권한 목록은 쓰지 않는다. 권한이 회수된 주체로 다시 부르면 그 문서는 결과에서 빠진다.
문서 수 , 차원 일 때 전수 계산은 대략 개의 좌표를 다루고, 전체 정렬 비용은 이다. k를 줄여도 계산하는 후보 수는 줄지 않는다. 벡터만 float32로 저장한다고 치면 백만 개의 384차원 벡터는 바이트, 약 1.536GB다. 실제 저장소에는 문서, 메타데이터, 색인 구조가 더 들어간다.
가상 문서로 재 본 점수
가상 문서는 교육비, 휴가, 국내 출장 교통비, 계정 비밀번호, 노트북 고장 안내 다섯 개다. 개발팀은 원문과 질의, 정답 문서 식별자, 실제 상위 세 결과를 함께 적어 뒀다.
최초 실행은 모델 다운로드를 포함해 로드에만 약 41.22초가 걸렸고, 정작 문서 다섯 개의 임베딩 생성은 약 0.036초였다.
| 사례 | 질문의 핵심 | 실제 1위 | 코사인 | 판정 |
|---|---|---|---|---|
| E01 | 업무 강의 결제 지원액 | edu-v2 | 0.8750 | 정답 문서 검색 |
| E02 | 휴가 신청과 승인 | leave-v1 | 0.8383 | 정답 문서 검색 |
| E03 | 암호를 기억하지 못함 | account-v1 | 0.8703 | 정답 문서 검색 |
| E04 | 팀장 승인 없이 교육비 선결제 가능한가 | edu-v2 | 0.8964 | 반대 조건을 확인할 문서 검색 |
| E05 | 해외 출장 호텔 숙박비 한도 | travel-v1 | 0.8455 | 주제는 관련되지만 답을 담은 문서가 없음 |
E01은 ‘강의 결제’에서 ‘교육비’ 문서를, E03은 ‘암호’에서 ‘비밀번호 재설정’ 문서를 찾았다. 이 다섯 문서와 질문에서 본 결과고, 회사 고유 약어나 오탈자, 복합 질문까지 된다는 증명은 아니다. E04는 승인 없이 먼저 결제해도 되는지 묻는데 검색된 문서는 구매 전 승인을 요구한다. 검색기는 맞는 근거를 찾아냈으니 이제 봇이 그 조건을 읽고 승인이 먼저라고 답해야 한다. 점수가 높다고 봇이 직원의 전제를 그대로 받아 주면 틀린 답이 나간다.
정답 문서 집합을 , 상위 개를 라고 하면 Recall@k는 이다. 정밀도는 반환한 결과 중 관련 결과의 비율이다. 관련성의 기준이 주제 연관인지 답변 근거인지 먼저 정한다. 여기서 관련 문서는 답변에 필요한 사실을 담은 문서다. E01–E04는 정답이 하나씩이고 모두 1위라 Recall@1이 각각 1이지만, 그 결과를 한국어 검색 정확도 100%라고 부르지 않는다. E05는 이 비어 분모가 0이니 정답 없는 사례는 따로 집계한다. 정답 문서가 둘 필요한 질문에서는 하나만 찾아도 ‘관련 결과 있음’은 통과하므로 재현율과 근거 완전성을 함께 본다. 정보 검색의 정밀도와 재현율
실패 분석은 정답 근거가 수집 자료에 있는지부터 본다. 없는 자료는 모델을 바꿔도 못 찾는다. 자료에 있는데도 못 찾았다면 파싱이나 길이 제한으로 빠진 부분을 보고, 그다음으로 권한·시행일 필터와 임베딩 설정, 후보 수와 순위를 차례로 확인한다. 한국어에서는 동의 표현, 조사와 어미, 띄어쓰기, 사내 약어와 영문 혼용, 그리고 ‘30만 원’과 ‘300만 원’, ‘가능’과 ‘불가능’처럼 숫자·부정이 바뀐 문서를 별도 사례로 둔다. 그런 문서는 주제가 비슷해 순위가 높게 나올 수 있어서, 결과에 원문과 식별자를 남겨 눈으로 확인한다. 정확 일치가 필요한 식별자는 필터나 키워드 검색으로 처리하고 두 검색을 함께 쓰는 하이브리드와 재순위화는 이 기준선 위에서 비교한다.
분류·추천·이상 탐지
같은 벡터 비교를 검색 밖에서도 쓸 수 있다. 다만 무엇을 잘한 것으로 칠지는 작업마다 새로 정한다. 손으로 만든 2차원 벡터로 세 계산을 따라가 보면 이렇다.
| 작업 | 계산 | 나온 값 | 이 값이 아닌 것 |
|---|---|---|---|
| 분류 | 가장 가까운 대표 벡터를 후보로. 1위·2위 점수 차이도 반환 | 계정 , 장비 에 문의 → 계정, 차이 0.8835 | 확률 |
| 추천 | 관심 벡터에 가까운 문서 순서 | 관심 에 ‘설정 안내’가 ‘수리 안내’보다 먼저 | 유용함의 증명 |
| 이상 탐지 | 1 − 정상 대표들과의 최대 코사인 | → 약 1.7071 | 0~1의 이상 확률 |
분류에서는 대표 예시가 충분한지, 두 의도가 섞인 문의와 어느 범주에도 없는 입력이 있는지 보고, 임계값은 실제 자료에서 오분류와 유보율을 함께 재서 정한다. 추천에서는 읽은 자료 제외, 접근 권한, 다양성을 반영하고 클릭이 아니라 문의 해결률과 피드백을 본다. 이상 점수가 높으면 악성이나 오류로 보지 말고 먼저 들여다볼 대상으로 삼는다. 새 제품 문의, 다른 언어, 자료 분포의 변화도 멀게 나온다. 분류든 추천이든 이상 탐지든 입력 표현과 비교 규칙, 업무 판정을 나눠 두면 개발팀은 어디를 고쳐야 할지 바로 안다.
총무팀이 넘긴 폴더는 그대로 200건이지만 이제 개발팀은 질문이 들어올 때마다 그중 두세 건만 골라 넣는다. 직원이 ‘강의 결제’라고만 물어도 그 말과 겹치는 단어가 하나도 없는 교육비 문서가 1위에 오른다.