AI로 장소 검색 서비스 만드는 중입니다 (기술 8) — 기각의 박물관: 좋아 보였지만 데이터가 버리게 한 것들

t8-rejected

이 연재의 숨은 주제는 "직관은 자주 틀린다" 였습니다. 기술 7편의 평가 인프라가 걸러낸 실패들을 한자리에 모았습니다. 그냥 나열하지 않고 왜 그렇게 믿었는지까지 적었습니다 — 실패의 값어치는 결과가 아니라 어디서 논리가 꺾였는지에 있으니까요.

들어가며 — 기각을 기록해야 하는 이유

성공한 개선은 코드에 남습니다. 코드를 보면 뭘 했는지 알 수 있죠. 그런데 기각된 시도는 아무 데도 안 남습니다. 코드에도 없고, 기억에서도 반년이면 사라집니다.

그러면 어떻게 되냐면 — 같은 아이디어를 다시 냅니다. "리랭커 점수로 순위를 매기면 좋지 않을까?" 이 생각은 정말 그럴듯해서, 잊어버리면 반드시 다시 떠오릅니다. 그리고 다시 이틀을 씁니다.

그래서 기각을 남깁니다. 각 전시물마다 ① 왜 그럴듯했나 ② 실제 결과 ③ 왜 틀렸나 ④ 무엇을 배웠나를 적었습니다.

박물관을 돌다 보면 실패들이 네 개의 방으로 묶인다는 걸 알게 됐습니다.


제1전시실 — "정보를 더 넣으면 좋아진다"

가장 많이, 가장 크게 틀린 가정입니다. 신호를 하나 더 넣으면 검색이 똑똑해질 것 같지만, 실제로는 기존 신호를 망가뜨리는 경우가 훨씬 많았습니다.

1. 피처 가중치 상향 (기각)

왜 그럴듯했나 — 유추카는 장소마다 구조화된 피처(뷰·시설·분위기)를 갖고 있습니다. 임베딩 코사인만 쓰지 말고 이 피처 점수를 섞으면, "정보가 더 많으니" 더 정밀해질 것 같았습니다.

결과 — 순수 코사인이 모든 혼합 조합을 이겼습니다.

방식 성능
순수 cosine 0.464
피처 혼합 (여러 비율) 0.365 → 0.296

왜 틀렸나 — 이유가 예상 밖이었습니다. 피처를 섞자 코사인 점수의 간격이 0.040에서 0.008로 압축됐습니다. 원래 1등과 10등 사이에 0.04의 차이가 있었는데, 다른 신호가 섞이면서 그 차이가 5분의 1로 눌린 겁니다.

순위를 매기려면 점수가 벌어져야 합니다. 모두가 비슷한 점수를 받으면 순서는 사실상 무작위가 됩니다. 정보를 더한 게 아니라 해상도를 지운 셈이었습니다.

배운 것 — 신호를 섞을 땐 "정보량이 느는가"가 아니라 "변별력이 유지되는가" 를 봐야 합니다. 피처는 정렬 점수에 안 섞고 하드게이트(있다/없다) 로만 쓰기로 했습니다.

2. 리랭커에게 근거 문장을 함께 주기 (기각)

왜 그럴듯했나 — 리랭커가 "이 장소가 이 검색어에 맞나"를 판정할 때, 관련 리뷰 문장을 뽑아서 같이 보여주면 판단이 좋아질 것 같았습니다. 근거를 주는데 나빠질 리가 없어 보였죠.

결과두 지표가 동시에 나빠졌습니다. 이건 드문 일입니다.

capped Recall@10 82.7% 78.4%
오탐 위반 42 49

보통은 하나가 오르고 하나가 내립니다(정밀도-재현율 교환). 둘 다 나빠졌다는 건 개입 자체가 해로웠다는 뜻입니다.

왜 틀렸나 — 두 가지가 겹쳤습니다.

첫째, 오답에게도 근거가 공급됐습니다. 발췌는 키워드로 뽑았기 때문에, 정답이 아닌 장소에서도 검색어와 글자가 겹치는 문장이 뽑혀 나왔습니다. "여긴 스페셜티는 아니지만"이 근거로 제출되는 거죠. 리랭커는 근거가 있으니 적합 쪽으로 기울었고, 오탐이 늘었습니다.

둘째, 발췌가 프롬프트를 통째로 교란했습니다. 문장이 추가되면서 프롬프트 구조가 바뀌었고, 발췌와 무관한 다른 쿼리들의 판정까지 흔들렸습니다.

배운 것근거는 중립적이지 않습니다. 정답과 오답 모두에게 같은 방식으로 근거를 주면, 판정을 돕는 게 아니라 오답을 변호하게 됩니다. 재시도한다면 키워드가 아니라 의미(벡터) 기반 선별과, 적합 판정 방향으로만 비대칭 설계가 필요합니다.

3. 사진으로 판정하기 (데이터는 남기고, 검색 소비는 전량 기각)

왜 그럴듯했나 — 이건 꽤 큰 투자였습니다. 4,817곳의 사진을 비전 모델로 태깅했습니다. 야간 사진 692곳, 한옥 건물 272곳 같은 식으로요. 리뷰 텍스트가 놓치는 걸 사진은 잡을 테니, "야경 좋은 곳" 검색의 재현율을 크게 올릴 거라 기대했습니다.

결과 — 검색에 쓰는 모든 방법이 실패했습니다.

  • 게이트에 OR로 추가 → 구제된 정답 0건, 되살아난 오답 +1~5건
  • 리랭커 프롬프트에 병기 → 효과 입증 불가 (측정 요동보다 작음)

왜 틀렸나 — 한 문장으로 요약됩니다. "밤에 찍은 외관 사진"과 "야경 명소"는 다릅니다.

비전 모델은 "이 사진이 밤에 찍혔다"를 정확히 맞혔습니다. 문제는 그게 우리가 알고 싶은 것이 아니었다는 겁니다. 야경 명소는 "그 장소에서 야경이 보이는 곳"이지 "밤에 사진이 찍힌 곳"이 아닙니다. 모델은 정확했고, 우리가 잘못된 질문을 데이터로 만든 겁니다.

배운 것 — 새 데이터 축을 만들 때는 "이 데이터가 정확한가" 보다 "이 데이터가 내 질문에 답하는가" 를 먼저 확인해야 합니다. 정확한 오답은 부정확한 정답보다 위험합니다 — 데이터가 깨끗해 보이니 의심을 안 하게 되거든요.

한편 태깅한 사진 데이터는 지우지 않고 자산으로 남겼습니다. 나중에 다른 질문(예: 실내/실외 판별)엔 맞을 수 있으니까요. 기각은 데이터가 아니라 용법에 대한 판정입니다.


제2전시실 — "더 똑똑한 모델이면 이긴다"

두 번째로 자주 틀린 가정입니다. LLM은 강력하지만, 강력한 것과 이 일에 맞는 것은 다릅니다.

4. 로컬 LLM으로 순위 재정렬 (기각)

왜 그럴듯했나 — 가장 크게 기대했던 시도입니다. 코사인 유사도는 결국 벡터 산수인데, 문장을 읽는 LLM이 다시 매기면 당연히 좋아질 것 같았습니다.

결과 — 온라인에 쓸 수 있는 로컬 모델 중 코사인을 이긴 게 하나도 없었습니다.

리랭커 nDCG@10 vs cosine
cosine 순위만 (기준) 0.552
qwen3:14b 0.541 −0.011
qwen3:8b / gemma3:12b 0.540 −0.012
exaone3.5:7.8b 0.462 −0.090
qwen3:30b 0.594 +0.042 (20초·18GB — 실격)

왜 틀렸나 — LLM은 "이게 적합한가"라는 이분 판정은 잘합니다. 그런데 "3등과 7등 중 뭐가 더 적합한가"라는 미세한 서열은 못 매깁니다. 순위표를 통째로 주고 재배열시키면, 잘 매기던 부분까지 흔들어 놓습니다.

유일하게 이긴 30B는 20초에 18GB라 온라인 검색엔 못 씁니다. "이길 수 있다"와 "쓸 수 있다"는 다른 문제였습니다.

배운 것 — 리랭커의 역할을 재정렬에서 "부적합 컷 + 이유 생성"으로 축소했습니다. LLM에게 잘하는 일만 시키는 원칙이 여기서 나왔습니다. (상세: 기술 1편)

5. 리랭커가 매긴 점수로 순위 재계산 (기각)

왜 그럴듯했나 — 4번을 반성한 뒤의 재시도입니다. "순위표를 통째로 재배열하는 게 문제라면, 리랭커가 장소마다 매긴 점수만 받아서 그걸로 정렬하면 되지 않을까?"

결과

방식 capped Recall@10
기준선 28.5%
LLM 점수 단독 정렬 25.3% (−3.2p)
기존 점수 × LLM 점수 결합 23.3% (−5.2p)

섞었을 때 더 나빴다는 게 인상적입니다. 좋은 신호에 나쁜 신호를 섞으면 중간이 되는 게 아니라, 나쁜 쪽으로 끌려갑니다.

왜 틀렸나 — 4번과 같은 이유입니다. LLM이 뱉는 점수는 판정의 부산물이지 서열이 아닙니다. 0.8과 0.85 사이에 의미 있는 차이가 없습니다. 그 차이로 정렬하면 노이즈로 정렬하는 것과 같습니다.

배운 것LLM은 컷과 이유 생성에만, 순위는 결정적 점수로. 두 번 시도해서 두 번 다 같은 자리에서 꺾였다는 건, 이게 우연이 아니라 성질이라는 뜻입니다.

6. 상위 모델로 리랭크 (기각)

왜 그럴듯했나gemini-2.5-flash. 이름에 flash-lite보다 상위 티어가 붙어 있으니 더 나을 것 같았습니다.

결과약 9.8초로 4~6배 느렸고, 품질은 동등했습니다. 게다가 thinking 모드가 출력 토큰을 잠식해서 검색 이유가 빈 값으로 돌아오는 사고까지 났습니다. 비싸고(출력 $2.50) 느린데 불안정.

왜 틀렸나 — 모델 티어는 추론 난도에 대한 것이지 이 작업에 대한 적합도가 아닙니다. 리랭크는 짧은 판정을 대량으로 하는 일이라 지연이 곧 품질인데, 상위 모델은 정확히 그 축에서 나빴습니다.

배운 것기능마다 모델을 따로 고른다. 이름값이 아니라 그 기능의 지배 요인(여기선 지연)으로. (상세: 기술 2편)

7. 컨셉 점수로 임베딩 대체 (기각)

왜 그럴듯했나 — LLM이 매긴 188개 컨셉 점수가 하드게이트로 P@8을 29% → 100%로 올렸습니다(기술 4편). 그렇게 좋다면 임베딩 없이 컨셉만으로 1차 검색을 해도 되지 않을까?

게다가 초기 측정에서 컨셉이 임베딩을 이겼습니다 — 컨셉 0.965 vs 임베딩 0.881.

결과 — 새 정답셋에서 완전히 뒤집혔습니다.

방식 nDCG (신규 gold)
컨셉 점수 단독 0.11 (생성 쿼리) / 0.43 (합성 쿼리)
임베딩 단독 0.46 / 0.70

왜 틀렸나 — 초기의 0.965는 정답셋 과적합 착시였습니다. 그 정답셋이 컨셉 점수를 참고해 만들어진 부분이 있었거든요. 기술 7편의 "정답셋이 자기 자신을 정답으로 삼는" 문제와 같은 뿌리입니다.

근본적으로는 역할이 다릅니다. 컨셉은 닫힌 목록이라 목록에 없는 개념을 못 잡습니다. 1차 검색기는 어떤 검색어가 와도 뭐라도 불러와야 하는데, 컨셉은 자기가 아는 축이 아니면 아무것도 못 합니다. 필터로는 훌륭하고 검색기로는 무능합니다.

배운 것임베딩은 소환, 컨셉은 게이트. 각 도구에 제자리가 있습니다. 그리고 "측정에서 이겼다"를 볼 땐 그 측정이 어떻게 만들어졌는지부터 봐야 합니다.


제3전시실 — "더 정확한 신호로 좁히면 좋아진다"

세 번째 함정입니다. 신호를 정밀하게 다듬으면 정밀도가 오를 것 같은데, 좁힌 만큼 정답도 같이 잘려나갑니다.

8. 청크 임베딩으로 정렬 (기각)

왜 그럴듯했나 — 청크가 후보 소환에서 대성공을 거뒀습니다(후보 재현율 44.6% → 93.8%). 정렬에도 쓰면 더 좋을 것 같았습니다. 마침 "썸 타는 사람과" 같은 검색은 근거가 리뷰 한 문장에 국소적으로 있으니, 청크가 유리해 보였고요.

결과

R@10 78.8% → 71.0% 붕괴 — 디저트 100 → 20, 수다 100 → 30, 혼밥 100 → 50

왜 틀렸나 — 리뷰가 24개나 있으면 어떤 검색어를 넣어도 그중 한 문장쯤은 어느 정도 비슷합니다. 그러니 청크 최고 점수는 거의 모든 장소에서 고만고만하게 높고, 변별력이 없습니다. 1번(피처 혼합)과 같은 붕괴입니다.

배운 것"근거가 있느냐"와 "얼마나 좋으냐"는 다른 질문입니다. 청크는 앞의 질문에만 답합니다. (상세: 기술 5편)

9. 키즈 신호를 더 정확한 것 하나로 좁히기 (롤백)

왜 그럴듯했나 — "아이들과 가기 좋은 곳" 검색에 키즈존이 아닌 곳이 섞였습니다. 원인을 보니 게이트에 kids_zone(키즈존)과 kids_friendly(아이 배려) 두 신호가 OR로 걸려 있었고, 배려 수준의 카페들이 상위를 차지하고 있었습니다. 그럼 진짜 신호인 kids_zone 하나로 좁히면 되겠죠.

결과

"아이들과 카페" R@10 100% → 0%

전멸했습니다.

왜 틀렸나태깅 편차 때문이었습니다. 진짜 키즈카페인데 kids_zone이 안 붙고 kids_friendly만 붙은 곳이 상당수 있었습니다. 그 장소들에게는 kids_friendly유일한 구조 신호였고, 그걸 지우니 통째로 사라졌습니다.

배운 것라벨이 완벽하다는 전제 위에서만 신호를 좁힐 수 있습니다. 라벨링이 자동화(기술 6편)라 편차가 있는 상황에서는, 부정확한 신호도 누락 방지 그물 역할을 합니다. 두 신호 OR을 유지하고, 대신 순서로 해결했습니다.

10. 반려동물 조건을 필수로 (기각)

왜 그럴듯했나 — 강아지 동반 검색에 반려동물 불가 장소가 하나 섞였습니다. pet_friendly를 필수 조건으로 걸면 막히겠죠.

결과표적 오탐은 안 막혔고(그 장소는 다른 경로로 통과), 정답만 강등됐습니다(강아지 쿼리 90 → 80).

왜 틀렸나 — 오탐 하나를 보고 규칙을 만들면, 그 규칙은 대개 오탐은 못 잡고 정답만 잡습니다. 오탐이 어떤 경로로 들어왔는지 확인하지 않고 "관련 있어 보이는 조건"을 필수로 걸었기 때문입니다.

배운 것 — 규칙을 추가하기 전에 표적이 정확히 어느 경로로 들어왔는지 트레이스로 확인합니다. 기술 7편의 귀인 진단이 필요한 이유이기도 합니다.

11. 승격 기준 조이기 (롤백)

왜 그럴듯했나 — 약한 신호로도 상위 등급으로 승격되는 문제가 있어서, 승격 최소 가중치를 0.15에서 0.25로 올렸습니다. 기준이 엄격해지니 정밀해질 것 같았습니다.

결과전체 −6.8%p. 표적 하나를 고치려고 전체를 깎았습니다.

왜 틀렸나 — 임계값 하나가 모든 쿼리에 동시에 작용한다는 걸 간과했습니다. 표적 쿼리에서 문제였던 그 값이, 다른 수십 개 쿼리에서는 정상 동작의 근거였습니다.

배운 것전역 임계값은 국소 문제의 해법이 아닙니다. 특정 쿼리 유형의 문제는 그 유형에만 작용하는 규칙으로 풀어야 합니다.


제4전시실 — 측정이 만든 유령

마지막 방입니다. 여기 있는 것들은 기법이 아니라 잘못 읽은 숫자입니다. 어떤 의미에선 가장 위험한 실패입니다 — 나머지는 데이터가 막아줬지만, 이건 데이터를 잘못 읽어서 생긴 거니까요.

12. "1초로 8배 빨라졌다" (측정 착오)

리랭크 설정을 바꿨더니 응답이 1초로 떨어졌습니다. 8배 개선. 신나서 기록했습니다.

알고 보니 폐기된 모델명을 부르고 있었고, API가 404를 뱉어 LLM을 아예 안 타고 있었습니다. 빨라진 게 아니라 일을 안 한 거였죠. 정직한 개선은 "2배쯤"이었습니다.

배운 것결과가 너무 좋으면 성공이 아니라 버그를 의심합니다. 개선폭이 예상 범위를 크게 넘으면, 축하하기 전에 "정말 그 일을 하고 있나"부터 확인합니다.

13. 평균은 올랐는데 오탐이 늘었다 (기각)

코사인 유사도를 1차 정렬 기준으로 올리는 실험을 했습니다.

결과
capped R@10 +0.7p
오탐 위반 39 → 49

평균 지표만 봤으면 채택했을 겁니다. 실제로는 코사인이 높은 오답들이 정답과 함께 올라온 것이었습니다. 코사인이 높다는 건 "관련은 있어 보인다"는 뜻이지 "맞다"는 뜻이 아니니까요 — 기술 4편의 감자탕/한옥이 딱 그 경우입니다.

배운 것단일 지표로 채택 결정을 하지 않습니다. 재현율과 오탐은 항상 같이 봅니다.

14. 한 쿼리를 살리고 다른 쿼리를 죽였다 (기각)

특정 후보군을 무조건 상위 등급으로 올리는 실험이었습니다.

쿼리 전 → 후
호수뷰 33 → 67
부모님과 100 → 20

표적 쿼리는 두 배가 됐는데, 다른 쿼리가 5분의 1이 됐습니다. 원인은 승격 조건이 상황형 의도(부모님과, 데이트 등)에서는 너무 광범위하게 참이 돼서, 진짜 정답을 밀어냈기 때문입니다.

배운 것 — 이래서 채택 게이트에 "급락하는 쿼리가 0건인가" 를 넣었습니다(기술 7편). 전체 평균은 이 경우 거의 안 움직였을 겁니다. 평균은 학살을 숨깁니다.


이 박물관이 말하는 것

전시물을 다 보고 나면 패턴이 몇 개 보입니다.

① "더하기"는 공짜가 아니다. 피처 혼합은 순위 해상도를 지웠고, 근거 발췌는 오답을 변호했고, 청크 정렬은 변별력을 덮었습니다. 신호를 추가할 땐 기존 신호에 무슨 일이 일어나는지를 봐야 합니다.

② 각 도구에는 제자리가 있다. 임베딩은 소환, 컨셉·품질은 게이트, LLM은 컷과 이유. 하나로 다 하려고 할 때마다 무너졌습니다. 좋은 도구를 잘못된 자리에 놓는 것이 가장 흔한 실패였습니다.

③ 좁히면 정답도 잘린다. 라벨이 완벽하지 않은 세계에서, 부정확한 신호는 그물 역할을 합니다. 정밀도를 위해 신호를 줄일 땐 그 신호가 유일한 근거인 정답이 있는지 확인해야 합니다.

④ 숫자는 읽는 법을 안 알려준다. 8배는 버그였고, +0.7p는 오탐 +10을 숨겼고, 평균은 학살을 숨겼습니다. 지표는 여러 개를 같이 봐야 하고, 이상하면 원본을 열어봐야 합니다.

그리고 마지막으로 — 기각은 실패가 아닙니다. 이 목록의 절반은 "안 되는 걸 알아낸" 성과입니다. VLM 사진 데이터는 검색에는 못 썼지만 자산으로 남았고, 30B 실험은 "로컬로는 이만큼이 상한"이라는 좌표를 줬습니다. 그리고 리랭커를 두 번 기각한 끝에 "LLM은 컷과 이유에만" 이라는 원칙이 나왔습니다.

유추카가 지금의 품질에 도달한 건 좋은 아이디어를 많이 넣어서가 아니라, 좋아 보이는 나쁜 아이디어를 데이터로 계속 걷어냈기 때문입니다. 화려한 성공 목록보다 이 기각의 목록이, 서비스를 만든 진짜 기록에 더 가깝습니다.

다음 글(기술 9)에서는 이 모든 걸 얹고도 검색을 1020초에서 25초로 줄인 속도 최적화를 봅니다.


Popit은 페이스북 댓글만 사용하고 있습니다. 페이스북 로그인 후 글을 보시면 댓글이 나타납니다.