흔히 많이 겪었던 것은 자연어 도서 검색을 만들면서 벡터 검색에 기대했던 것 중 하나는 사용자가 정확한 키워드를 몰라도 의미만 비슷하면 원하는 책을 찾을 수 있다는 것이었다.
예를 들어 다음 네 문장은 표현은 다르지만 검색 의도는 상당히 비슷하다.
파이썬 입문 책
파이썬 처음 공부할 때 읽을 책
코딩 초보자가 보기 쉬운 파이썬 책
프로그래밍을 처음 시작하는데 Python 책 추천
기존 키워드 검색이라면 문장에 포함된 단어 차이가 검색 결과에 직접 영향을 줄 수 있다.
하지만, embedding을 이용한 벡터 검색에서는 텍스트를 벡터 형태로 변환한 뒤 저장된 문서 벡터와 가까운 정도를 이용해 검색할 수 있다. OpenAI에서도 vector store를 semantic search에 활용할 수 있도록 제공하고 있다.
그래서 처음에는 같은 의미의 문장이라면 검색 결과 역시 거의 똑같을 것이라고 생각하기 쉽다.
그런데 실제 벡터 검색 구조를 들여다보면 반드시 그렇지는 않다.
의미가 비슷하다는 것과 동일한 벡터라는 것은 다른 이야기이기 때문이다.
이 차이를 이해하지 않으면 자연어 검색 결과가 조금씩 달라질 때 원인을 전부 Elasticsearch의 k나 num_candidates 문제로 오해할 수 있다.
같은 뜻이라고 해서 같은 입력은 아니다
먼저 다음 두 문장을 보자.
파이썬 입문 책
코딩을 처음 배우는 사람이 읽기 쉬운 파이썬 책
사람이 보면 의도가 거의 같다.
하지만 embedding 모델에 전달되는 입력 문자열은 분명히 다르다.
첫 번째 문장에는 주로 다음 개념이 강하게 들어 있다.
Python
입문
책
두 번째에는 훨씬 많은 정보가 들어 있다.
코딩
처음
배우는 사람
읽기 쉬움
Python
책
결국 두 문장이 비슷한 의미 영역에 위치할 수는 있어도 완전히 동일한 벡터가 될 이유는 없다.
이 부분이 중요하다.
벡터 검색에서 우리가 원하는 것은:
같은 뜻이면 반드시 같은 벡터가 된다.
가 아니다.
조금 더 현실적으로는:
의미적으로 관련된 텍스트가 벡터 공간에서도 서로 가까운 위치에 표현되어 검색에 활용할 수 있다.
에 가깝다.
따라서 문장을 바꾸면 query vector 역시 달라질 수 있고, query vector가 움직이면 주변 문서와의 거리도 조금씩 달라질 수 있다.
검색 순위는 Query 하나만 보고 결정되는 것이 아니다
벡터 검색에서 흔히 놓치기 쉬운 부분이 있다.
사용자의 검색 문장이 좋은 embedding을 만들었느냐만 중요한 것이 아니다.
검색 순위는 결국 Query Vector와 Document Vector의 관계로 결정된다.
내 도서 검색 구조를 단순하게 표현하면 다음과 같다.
사용자 검색어
↓
Query Embedding
↓
Query Vector
↓
비교
↓
도서 A Embedding
도서 B Embedding
도서 C Embedding
도서 D Embedding
↓
유사도 계산
↓
순위 결정
내 검색 시스템에서는 Elasticsearch에서 embedding 필드를 대상으로 벡터 검색을 사용하고 있으며, 유사도를 이용해 가까운 도서를 찾는 구조를 사용했다.
여기에서 생각해볼 상황이 있다.
세 권의 책이 있다고 가정하자.
A: 파이썬 기초 프로그래밍
B: 처음 배우는 코딩
C: 초보자를 위한 데이터 분석
그리고 첫 검색어가:
파이썬 입문 책
이라고 하자.
이 검색어의 벡터는 A에 가장 가까울 가능성이 있다.
그런데 검색어를:
코딩을 처음 배우는 사람이 읽기 쉬운 책
으로 바꾸면 상황이 달라질 수 있다.
두 번째 문장에서는 Python보다:
코딩
처음
초보자
쉬운
이라는 의미가 상대적으로 더 중요해질 수 있다.
그러면 B와의 상대적 거리가 가까워질 가능성도 있다.
즉 검색 의도는 비슷하지만 어떤 의미를 더 강조하느냐에 따라 주변 문서와의 상대적인 유사도가 달라질 수 있다.
순위가 바뀌었다고 검색이 실패한 것은 아니다
이 부분에서 또 하나 중요한 문제가 생긴다.
간단히 예를 들어 첫 검색에서는:
1위 A
2위 B
3위 C
였고, 표현을 바꾸자:
1위 B
2위 A
3위 C
가 됐다고 하자.
그러면 검색 품질이 나빠졌다고 해야 할까?
반드시 그렇지는 않다.
A와 B가 모두 사용자의 의도에 적합한 책이라면 1위와 2위가 바뀌었다는 사실 자체는 큰 문제가 아닐 수도 있다.
오히려 정말 확인해야 하는 것은 다음이다.
관련 도서가 상위권에서 계속 유지되는가?
검색 문장 표현만 조금 바꿨는데:
A/B/C
가 모두 사라지고 완전히 다른 도서들이 올라온다면 검색 안정성을 의심할 수 있다.
반대로:
A/B/C의 순서만 조금 변경
되는 정도라면 의미 검색 자체는 꽤 안정적으로 동작하고 있다고 볼 수도 있다.
그래서 자연어 벡터 검색을 평가할 때 1위 결과 하나만 비교하면 부족하다.
비슷한 점수의 도서가 많으면 작은 차이도 순위를 바꿀 수 있다
검색 결과 순위가 변하는 이유를 조금 더 깊게 보면 유사도 점수의 간격도 생각해야 한다.
설명을 위한 가상의 점수를 보자.
첫 번째 검색어에서:
A = 0.812
B = 0.809
C = 0.798
이라고 가정하자.
A와 B의 차이는 매우 작다.
검색 문장을 약간 바꾸면서 Query Vector가 조금 움직인 결과:
A = 0.807
B = 0.813
C = 0.800
처럼 바뀔 수도 있다.
그러면 B가 1위가 된다.
여기에서 중요한 것은 A의 검색 품질이 갑자기 크게 나빠진 것이 아니다.
원래 두 문서가 매우 가까운 경쟁 관계에 있었기 때문에 Query Vector의 작은 변화만으로도 순위가 뒤집힌 것이다.
이런 상황에서는:
1위가 바뀌었다.
만 기록해서는 검색 시스템을 제대로 평가하기 어렵다.
오히려 다음을 같이 봐야 한다.
TOP 5 결과가 얼마나 겹치는가
관련 도서가 계속 상위권에 있는가
점수 차이가 얼마나 큰가
검색어를 조금 바꿨을 때 완전히 다른 영역으로 이동하는가
이런 기준이 필요하다.
그래서 자연어 검색에서는 Top 1보다 Top K가 더 중요할 수 있다
정확한 ISBN 검색이라면 하나의 정확한 결과가 중요하다.
예를 들어:
9788932920276
처럼 정확한 식별값을 검색했는데 다른 책이 나오면 명백한 문제다.
하지만:
최근 아무것도 하기 싫을 때 읽을 책
같은 자연어 검색에는 하나의 정답만 존재하지 않는다.
좋은 답이 여러 개 있을 수 있다.
그래서 자연어 추천·검색 시스템에서는:
1등 책이 항상 같은가?
보다:
상위 후보에 적절한 책들이 충분히 포함되어 있는가?
가 더 중요한 평가 기준이 될 수 있다.
이 관점은 RAG와 연결할 때 더 중요해진다.
검색된 책 중 몇 개를 LLM Context로 넘긴다면 Retrieval의 역할은 반드시 완벽한 한 권을 1등으로 결정하는 것이 아닐 수 있다.
오히려:
좋은 후보군 확보
↓
LLM이 사용자 질문과 함께 비교
↓
최종 추천 설명
이라는 구조를 만들 수도 있다.
즉 벡터 검색에서는 좋은 후보를 놓치지 않는 것이 상당히 중요하다.
검색 문장만 볼 것이 아니라 Document Embedding도 봐야 한다
검색 결과가 좋지 않을 때 Query 문장만 계속 수정하는 것도 위험하다.
예를 들어 다음 검색어가 있다고 하자.
직장 생활이 지쳐서 위로받고 싶을 때 읽을 책
Query Embedding 자체는 정상적으로 생성되었다.
그런데 도서의 Document Embedding을 만들 때 입력한 텍스트가:
title만 사용
되었다고 가정해보자.
어떤 책의 실제 내용은 직장인의 번아웃과 감정 회복에 아주 적합하지만 제목이:
다시 시작하는 마음
같이 추상적이라면 제목만으로는 사용자 질문과 의미적 연결이 충분히 강하게 나타나지 않을 수 있다.
반면 Document Embedding을 만들 때:
제목
+
키워드
+
설명
을 적절히 구성했다면 해당 책의 내용과 관련된 의미 정보가 훨씬 풍부해질 가능성이 있다.
따라서 벡터 검색의 품질은:
Query를 어떻게 embedding했는가
만의 문제가 아니다.
Document를 무엇으로 embedding했는가
도 똑같이 중요하다.
그런데 정보를 많이 넣는 것도 무조건 좋은 것은 아니다
그러면 또 이런 생각을 할 수 있다.
제목만 넣어서 정보가 부족하다면 모든 데이터를 다 넣으면 되는 것 아닌가?
예를 들어 도서 한 권에 대해:
제목
저자
출판사
ISBN
키워드
설명
목차
리뷰
카테고리
출판연도
를 전부 합쳐 embedding하면 검색이 더 좋아질 것처럼 보인다.
하지만 정보가 많다고 항상 좋은 표현이 되는 것은 아니다.
왜냐하면 검색 목적과 관계없는 정보까지 하나의 표현 안에 섞일 수 있기 때문이다.
예를 들어 사용자가:
파이썬을 처음 공부하는 사람에게 쉬운 책
을 찾는데 ISBN 숫자나 출판사 이름이 의미 검색에 큰 도움이 되지 않을 수 있다.
또 책 설명이 지나치게 길고 여러 주제를 동시에 다루면 어떤 의미를 중심으로 embedding되어야 하는지가 복잡해질 수 있다.
그래서 Document Embedding을 만들 때 중요한 질문은:
데이터를 얼마나 많이 넣을까?
보다는:
사용자가 어떤 의미를 기준으로 이 책을 찾을 것인가?
가 되어야 한다고 생각한다.
내가 도서 검색이라면 먼저 embedding 입력을 분리해서 테스트할 것이다
지금 다시 평가한다면 최소 세 가지 Document Embedding 방식을 비교해보고 싶다.
조건 A — 제목만
title
장점은 매우 단순하다.
하지만 자연어 주제 검색에는 정보가 부족할 가능성이 있다.
조건 B — 제목 + 키워드
title
keywords
책의 핵심 주제를 조금 더 반영할 수 있다.
조건 C — 제목 + 키워드 + 설명
title
keywords
description
책의 실제 내용을 설명할 정보가 가장 많다.
그리고 동일한 검색어를 넣어본다.
파이썬 처음 공부할 때 볼 책
프로그래밍 초보자용 Python 책
코딩을 처음 배우는 사람이 이해하기 쉬운 파이썬 책
그 뒤 다음을 비교한다.
각 방식의 Top 5
관련 도서 포함 여부
표현 변경 시 결과 안정성
검색 결과의 다양성
이렇게 하면:
description을 넣으니까 왠지 좋아졌다.
가 아니라,
어떤 검색 유형에서 어떤 Document Representation이 실제로 더 안정적이었는지
를 확인할 수 있다.
검색어가 짧을수록 애매함이 커질 수도 있다
다음 검색어를 생각해보자.
파이썬 책
사람도 이 검색어만 보고 정확한 의도를 알기 어렵다.
사용자는:
입문서를 원하는가?
웹 개발 책을 원하는가?
데이터 분석 책을 원하는가?
머신러닝 책을 원하는가?
문법 레퍼런스를 원하는가?
를 말하지 않았다.
Embedding 모델 역시 사용자가 말하지 않은 의도를 마법처럼 알 수는 없다.
따라서 짧은 검색어에서 검색 결과가 넓게 퍼지는 것은 반드시 모델의 실패라고 볼 수 없다.
입력 자체가 애매하기 때문이다.
반대로:
판다스를 처음 사용하는 사람이 데이터 분석을 배우기 좋은 파이썬 책
처럼 의도가 구체적이면 검색 벡터에도 더 많은 의미 정보가 들어간다.
그러면 관련 후보를 좁히는 데 도움이 될 수 있다.
여기에서 자연어 검색 UI를 설계할 때도 힌트를 얻을 수 있다.
사용자에게:
원하는 책을 검색하세요.
만 보여주는 것보다:
어떤 상황에서 읽고 싶은 책인지 문장으로 입력해보세요.
처럼 자연어 입력을 유도하는 것이 벡터 검색의 장점을 더 활용할 수 있을 수도 있다.
하지만 문장이 길수록 무조건 좋은 것도 아니다
이번에는 반대 문제다.
검색어를 자세하게 쓰는 것이 도움이 될 수 있지만 의미 없는 조건까지 너무 많이 붙으면 검색 의도가 오히려 복잡해질 수 있다.
예를 들어:
회사에 다니고 있고 요즘 피곤하고 주말에는 농구를 하는데
최근 인간관계가 조금 힘들어서 너무 두껍지 않고 표지가 예쁜
책 중에 출퇴근하면서 읽을 수 있는 책
이라는 검색어가 있다고 하자.
이 문장에는 여러 신호가 섞여 있다.
회사
피곤함
농구
인간관계
두께
표지
출퇴근
그런데 실제로 중요한 의도가:
인간관계 때문에 지쳐서 위로받을 책
이라면 나머지 정보는 검색 과정에서 중요한 의미와 경쟁할 수 있다.
특히 도서 embedding 데이터에 표지가 예쁨, 출퇴근, 농구 같은 속성이 존재하지 않는다면 해당 조건은 검색에 제대로 활용되지 못할 수도 있다.
결국 자연어 검색에서도:
길게 쓰면 좋다.
가 아니라:
검색 대상 데이터가 표현할 수 있는 조건을 구체적으로 쓰는 것이 좋다.
가 더 정확하다.
Metadata에 없는 조건은 벡터가 해결해주지 못할 수도 있다
이 부분은 실제 서비스 설계에서 중요하다.
사용자가:
300페이지 이하의 책
을 원한다고 하자.
그런데 내 Elasticsearch 데이터에는 페이지 수가 저장되어 있지 않다.
이 경우 embedding 검색만으로 정확하게 조건을 만족시키기는 어렵다.
또:
우리 도서관에 있는 책
이라는 조건도 embedding에 기대해서는 안 된다.
내 서비스에서는 이런 조건을 libCode 필터로 별도로 처리한다.
즉 검색 조건에는 두 종류가 있다.
의미적인 조건
초보자가 읽기 쉬운
마음이 지쳤을 때
경제 공부를 처음 시작할 때
이런 것은 embedding search가 활용하기 좋다.
구조적인 조건
특정 도서관
특정 출판연도
특정 ISBN
특정 카테고리
이런 값은 필터나 정확한 필드 검색으로 처리하는 편이 더 명확하다.
자연어 입력 하나를 embedding으로 변환한다고 모든 검색 조건을 벡터 검색 하나로 해결해야 하는 것은 아니다.
오히려 의미 검색과 구조적 필터를 분리하는 것이 서비스에서는 훨씬 관리하기 쉽다.
Query 표현이 달라졌는데 결과가 크게 바뀌면 어디부터 볼까
이제 실제 문제 상황을 생각해볼 수 있다.
다음 두 질문이 있다.
파이썬 초보 책 추천
Python을 처음 공부하는 사람이 읽을 입문서
내가 보기에는 거의 같은 검색이다.
그런데 Top 10 결과가 거의 겹치지 않는다고 하자.
그때 바로:
Embedding 모델이 이상하다.
라고 결론 내리지는 않을 것이다.
나는 다음 순서로 확인할 것이다.
1. 두 검색어의 결과를 그대로 저장한다
Query A → Top 10
Query B → Top 10
먼저 실제 차이가 얼마나 큰지 확인한다.
2. 두 Query Vector의 유사성을 확인한다
두 검색어의 embedding끼리도 유사도를 계산해볼 수 있다.
의미가 매우 비슷하다고 예상했는데 query embedding 자체가 크게 떨어져 있다면 검색 문장 표현의 영향을 더 살펴볼 수 있다.
반대로 두 query vector가 매우 가까운데 결과가 크게 바뀐다면 다른 원인도 생각해야 한다.
3. 상위 Document의 점수 간격을 본다
1위부터 10위까지 유사도 차이가 매우 작은 상황이라면 작은 query 변화만으로도 순위가 많이 바뀔 수 있다.
즉:
결과 순위 변화가 큼
이 반드시:
의미 표현이 크게 달라짐
을 뜻하지는 않는다.
4. Document Embedding 입력을 확인한다
관련 책의 embedding에 어떤 데이터가 들어갔는지를 본다.
제목만?
키워드 포함?
description 포함?
검색하려는 의미가 Document Vector에 충분히 표현되어 있지 않으면 Query 쪽을 계속 수정해도 한계가 있다.
5. libCode 필터를 확인한다
같은 검색어라도 도서관 조건이 다르면 후보 자체가 달라진다.
따라서 Query 비교 실험을 할 때는 반드시 동일한 필터 조건으로 테스트해야 한다.
6. 그 다음 KNN 파라미터를 본다
앞 조건들이 동일한데 결과가 지나치게 불안정하다면 그때:
k
num_candidates
같은 approximate KNN 설정을 확인한다.
앞에서 정리했던 것처럼 후보 탐색 폭이 부족하면 실제 관련 문서가 후보군에 들어오지 못할 수도 있기 때문이다.
검색어 표현의 안정성을 별도의 평가 항목으로 만들 수 있다
자연어 검색 품질을 평가한다면 단순히 검색어 30개를 각각 테스트하는 것보다 같은 의도를 여러 표현으로 바꾼 그룹을 만드는 것도 의미가 있다.
예를 들어:
그룹 1 — Python 입문
파이썬 입문 책
파이썬 처음 공부할 책
코딩 초보자용 Python 책
프로그래밍 처음 배우는데 파이썬 책 추천
그룹 2 — 직장인 번아웃
회사 생활에 지쳤을 때 읽을 책
직장 스트레스 때문에 힘들 때 볼 책
번아웃이 왔을 때 도움이 되는 책
일 때문에 지쳐서 위로받고 싶을 때 읽을 책
그룹 3 — 투자 초보
주식 처음 공부할 책
투자 초보자가 읽기 쉬운 책
경제와 주식을 처음 시작하는 사람 책
재테크를 처음 배우고 싶을 때 볼 책
그리고 그룹별로 측정한다.
Top 5 공통 도서 수
Top 10 공통 도서 수
기대 도서 포함 여부
기대 도서의 평균 순위
이렇게 하면 한 검색어의 성능만 보는 것이 아니라:
같은 의도를 다르게 표현했을 때 검색 시스템이 얼마나 안정적인가
를 확인할 수 있다.
내가 자연어 검색 서비스를 다시 평가한다면 이 지표를 꼭 추가하고 싶다.
결과가 항상 같아야 한다는 목표도 위험하다
그렇다고 모든 paraphrase에서 정확히 같은 결과를 요구하는 것도 좋은 목표는 아닐 수 있다.
예를 들어:
파이썬 입문 책
과:
코딩 자체를 처음 배우는 사람이 볼 파이썬 책
은 매우 비슷하지만 두 번째 문장에는 코딩 자체도 처음이라는 조건이 더 강하다.
따라서 아주 기초적인 책이 두 번째 검색에서 조금 더 올라오는 것은 오히려 자연스러운 변화일 수 있다.
중요한 것은:
모든 표현 → 완전히 동일한 순위
가 아니다.
내가 원하는 것은:
핵심 의도가 유지되면
관련 결과군도 크게 무너지지 않음
+
표현에 추가된 의미는
순위에 적절하게 반영됨
에 가깝다.
이런 관점이 없으면 자연어 검색의 모든 순위 변화를 오류로 착각할 수 있다.
결국 Embedding 테스트도 검색 서비스 전체를 놓고 해야 한다
Embedding 모델의 성능만 따로 측정하는 것과 실제 서비스 검색 품질은 다르다.
내 도서 검색 구조에는 여러 단계가 있다.
사용자 문장
↓
Embedding
↓
Query Vector
↓
Elasticsearch KNN
↓
libCode Filter
↓
Top K 도서
↓
RAG
↓
추천 문장
최종 결과가 이상할 때 어느 단계가 문제인지 분리해야 한다.
예를 들어 검색된 도서는 좋은데 최종 추천 문장이 이상하다면 embedding 문제가 아니다.
검색 결과부터 나쁘다면:
Query 표현
Document Embedding
필터
KNN 설정
데이터 품질
등을 확인해야 한다.
그래서 자연어 검색에서는 단순히:
OpenAI Embedding을 붙였다.
가 끝이 아니다.
OpenAI API는 현재도 별도의 embeddings endpoint와 embedding 모델들을 제공하고 있으며, embedding은 검색·retrieval 같은 시스템에서 사용할 수 있다.
하지만 실제 서비스 품질은 어떤 데이터를 벡터화하고, 어떻게 검색하고, 어떤 조건으로 평가하느냐에서 결정되는 부분이 크다.
지금 내가 다시 테스트한다면
현재 도서 검색을 다시 평가한다면 다음 순서로 실험할 것이다.
먼저 10개의 검색 의도를 정한다.
각 의도마다 표현을 네 개씩 만든다.
10 intents
×
4 paraphrases
= 40 queries
그리고 모든 검색은:
같은 embedding 방식
같은 Elasticsearch index
같은 libCode
같은 k
같은 num_candidates
조건으로 실행한다.
그다음 기록한다.
각 Query의 Top 10
Top 10 공통 결과
기대 도서 포함 여부
기대 도서 순위
검색 결과 score
여기에서 특정 의도 그룹만 결과가 크게 흔들린다면 해당 그룹의 Document Embedding을 살펴본다.
그리고 필요하다면:
title
title + keywords
title + keywords + description
처럼 Document Representation을 변경해 다시 비교한다.
그 후에야 KNN 파라미터를 변경한다.
이 순서가 중요한 이유는 원인을 하나씩 분리하기 위해서다.
Document Embedding과 num_candidates를 동시에 바꾸고 검색 결과가 좋아졌다면 무엇 때문에 좋아졌는지 알 수 없다.
마무리
처음 벡터 검색을 적용할 때는:
의미가 비슷하면 같은 결과가 나오겠지.
라고 단순하게 생각하기 쉽다.
하지만 자연어 검색에서는 같은 의도를 가진 문장도 표현이 달라지면 서로 다른 Query Vector가 만들어질 수 있고, 그 작은 차이가 주변 Document Vector와의 상대적인 유사도에 영향을 줄 수 있다.
특히 상위 도서들의 점수 차이가 작다면 작은 Query 변화만으로도 순위가 바뀔 수 있다.
그렇다고 순위가 바뀌었다는 이유만으로 검색 품질이 나쁘다고 판단해서도 안 된다.
자연어 검색에서는 하나의 정답보다 관련 도서군이 안정적으로 상위권에 유지되는지를 보는 것이 더 중요할 수 있다.
그리고 검색 품질이 좋지 않을 때 Query 표현만 계속 수정하는 것도 부족하다.
Query Embedding
Document Embedding
어떤 메타데이터를 벡터화했는지
libCode 같은 필터
k / num_candidates
검색 대상 데이터 품질
를 함께 봐야 한다.
내가 지금 다시 자연어 도서 검색을 평가한다면 단일 검색어의 결과보다는 같은 의도를 여러 방식으로 표현했을 때 Top K 결과가 얼마나 유지되는지를 별도의 테스트 항목으로 만들 것이다.
그 테스트를 하면 비로소:
우리 벡터 검색이 자연어 표현 변화에도 안정적인가?
라는 질문에 느낌이 아니라 실제 데이터로 답할 수 있기 때문이다.