← 프로젝트

음식점 추천여행 · 로컬 발견

쩝쩝LAB 리서치

리뷰 문장을 '의미 단위'로 묶어 만든 음식점 추천 — 유저→식당 · 유사 식당 · 유사 유저

기간
2024.10 – 진행 중
역할
모임장 · 기술 리드 (파이프라인·추천 아키텍처 단독 설계)
팀
모두의연구소 추천시스템 연구 모임 12명
상태
리서치 진행 중
2,110만
정제한 리뷰 mention
96% → 48%
군집 노이즈 (UMAP 도입 후)
3종
semantic_id 기반 추천 구현
0.950
유사 식당 1위 score (같은 브랜드 다른 지점)

한 줄로

“맛있어요” 같은 평균적인 평가가 아니라, 리뷰어가 실제로 하려던 말을 의미 단위로 모아 취향 벡터를 만들고, 그 벡터로 유저→식당 · 유사 식당 · 유사 유저 추천을 구현했습니다. 대상은 서울·수도권, 동해권(고성·속초·양양·강릉), 제주, 오사카·도쿄입니다.

데이터 파이프라인

  1. 수집 — 지도 서비스의 공개 리뷰 데이터를 비상업적 연구 목적으로 수집
  2. mention 단위 정규화 — 사전 추출 → ko-sroberta + HDBSCAN → Qwen3-8B 분류기
  3. 카테고리 / 서브카테고리 매핑 — 분류 모델 + 저신뢰 구간은 에이전트가 재분류
  4. 개인화 평가 체계 + Semantic ID 기반 추천 아키텍처 설계

문제 — 짧은 표현으로는 취향이 잘린다

처음에는 리뷰에서 짧은 표현(mention span, 문장당 평균 6.6개)을 뽑아 카테고리를 붙이는 방식으로 데이터 적재까지 끝냈습니다. 그런데 두 가지 벽에 부딪혔습니다.

  • 표현이 너무 짧다 — “친절해요”, “웨이팅 김” 같은 어구로는 리뷰어의 의사 전체가 담기지 않습니다.
  • 미분류 잔여가 크다 — 개념 클러스터 245,889건 중 56.8%가 미분류(ETC), mention의 44.3%(511만 건)가 군집 노이즈로 남았습니다.

그리고 2,110만 건을 직접 조회하던 중, subcategory 361개 중 42개(11.6%, 약 30만 건)가 집계 파이프라인에서 통째로 빠져 있는 것을 발견했습니다. 명명 규칙을 flat → 계층형으로 바꾸면서 재집계가 누락된 것이 원인이었습니다.

접근 — 문장 전체를 의미 단위로

리뷰 길이 중앙값이 1문장이라는 실측을 근거로, 문단이 아니라 문장을 의미 단위로 택했습니다.

단계 내용 판단 근거
문장 분할 kss 정규식 분할기가 “1. 친절함 2. 맛있음” 같은 번호 리스트를 문장 경계로 오인식해 교체
임베딩 Qwen3-Embedding-4B (768d) mention 파이프라인과 같은 벡터 공간을 공유해야 교차검증이 가능
군집화 UMAP 768→15d 후 HDBSCAN 원본 차원에서는 파라미터와 무관하게 노이즈 94.5~96.9% — 원인은 파라미터가 아니라 차원의 저주. 축소 후 약 48%
극성 분리 같은 토픽도 긍·부정이 다르면 분리 기존 mention 극성 라벨(99.9997% 채워짐)을 다수결로 재사용
라벨링 · 병합 LLM 쌍 단위 판단 + Connected Components 아래 참고

병합은 LLM에게 한 가지만 묻습니다. “이 두 클러스터가 같은 의미인가?” 전이적 묶음(A~B, B~C ⇒ A~B~C)은 LLM이 아니라 그래프 알고리즘이 처리합니다. 후보는 같은 극성 그룹 안에서만 centroid 코사인 0.85 이상으로 제한했습니다. cluster_id와 semantic_id를 별도 ID 공간으로 둬서, 대표 클러스터가 나중에 재라벨링돼도 깨지지 않게 했습니다.

추천 — semantic_id를 피처로

리뷰어×음식점 sparse 행렬의 피처 키를 (COALESCE(semantic_id, cluster_id), polarity)로 구성하고, 행 단위 L2 정규화 후 코사인 유사도로 세 갈래를 계산합니다.

  • 유저 → 식당: reviewer_vec · diner_matrix.T
  • 유사 식당: diner_matrix · diner_vec.T
  • 유사 유저: reviewer_matrix · reviewer_vec.T

COALESCE 폴백 덕분에 병합이 일부에만 적용된 지금도 전체가 동작하고, 병합 범위가 넓어질수록 벡터가 덜 희소해지는 방향으로만 개선됩니다. split_date 이전 리뷰의 문장만 벡터에 넣어 시점 리키지를 차단했습니다.

다음 단계 — 실측 분포로 나눈 Phase A / B / C

식당 벡터를 TIGER/GRID 계열 Residual K-Means로 (32, 61, 55) 같은 이산 토큰으로 바꾸는 모듈이 있었는데, 토큰만 만들고 추천으로 이어지지 않는 gap을 발견해 연결 구조를 설계했습니다.

  • 리뷰어 554,884명 중 63.3%가 리뷰 1건(콜드스타트), 반면 n≥3 리뷰어는 22.6%지만 리뷰량의 73%를 차지
  • 그래서 Phase A 학습 없는 Prefix 근사검색(전원 커버) → Phase B 방문 시퀀스 기반 생성형 추천(반복 방문자 전용, constrained beam search) → Phase C RRF 결합 + Bayesian 점수 보정으로 나눴습니다.

검증과 한계

  • 기준 식당의 유사 식당 1·2위가 같은 브랜드의 다른 지점(score 0.950, 0.936)으로 나와 방향성을 확인했습니다.
  • 11,561개 클러스터 dry-run에서 한 컴포넌트가 600개 이상을 흡수하는 체이닝 현상을 발견해, 전체 규모 DB 반영은 보류했습니다(50개 파일럿에서 결정론적·멱등 동작은 검증 완료).
  • 정답 라벨이 없어 NDCG/Recall 같은 정량 지표는 아직 없습니다. 리뷰 시점 이전 리뷰어별 시간가중 평균을 baseline으로 만족/불만족 라벨을 만드는 오프라인 평가 프레임워크는 설계해 두었고, 미방문 식당은 관측되지 않는(MNAR) 한계도 함께 적어 두었습니다.
  • 유사 유저 추천은 리뷰 1건 리뷰어가 63%인 구조적 희소성 때문에 신호가 약합니다(score 0.3~0.5).

이 프로젝트에서 보여주고 싶은 것

  • 이전 방법론을 버리지 않고 다음 세대의 입력 신호로 재배치하는 설계 (mention 극성 → 문장 파이프라인)
  • 실데이터에서 결함을 스스로 발견하고 원인까지 규명하는 습관
  • 12명 모임을 운영하며 논문 리뷰 → 구현 → 발표(우아콘·모두콘)로 이어지는 사이클을 1년 6개월간 유지한 경험