이 글은 누구를 위한 것인가
- 아이템을 공급자 카테고리가 아닌 사용자 관점 축으로 재분류하려는 팀
- 규칙만으로 분류하다가 예외 처리가 감당 안 되는 상황
- 학습 데이터가 거의 없는 상태에서 분류기를 만들어야 하는 경우
들어가며
대부분의 서비스는 공급자가 등록한 카테고리로 아이템을 정리한다. 그런데 사용자가 검색하는 방식은 다르다.
[공급자 언어] [사용자 언어]
스포츠 마사지 운동 후에 뭉친 거
경락 피곤해서 좀 풀고 싶은
아로마테라피 스트레스 받아서
발 마사지 하루 종일 서 있었더니
왼쪽으로 정리된 목록은 오른쪽으로 검색하는 사람에게 도달하지 않는다. 그래서 "니즈 축"이라는 별도 분류 체계가 필요해진다. 피로·스트레스·활력·기분처럼 사용자가 자기 상태로 표현하는 축이다.
문제는 이 축을 4만 개 아이템에 어떻게 붙이느냐다.
1. 문제를 정확히 정의한다
가장 먼저 정할 것은 단일 레이블인가 다중 레이블인가다.
단일 레이블 아이템 1개 → 니즈 1개
구현 단순, 하지만 현실과 안 맞음
다중 레이블 아이템 1개 → 니즈 0~N개 ← 대부분 이쪽
"피로"이면서 "스트레스"인 항목이 자연스러움
다중 레이블이면 평가 지표부터 달라진다. 정확도(accuracy)를 쓰면 안 되고, 레이블별 정밀도·재현율을 따로 봐야 한다. scikit-learn 문서의 다중 클래스·다중 레이블 정리가 이 구분을 명확히 다룬다.
[0개도 허용해야 하는 이유]
모든 아이템에 억지로 니즈를 붙이면
→ 니즈 페이지가 관련 없는 항목으로 오염
→ "피로가 풀림" 목록에 피로와 무관한 곳이 섞임
→ 그 페이지 전체의 신뢰가 떨어짐
붙일 근거가 없으면 안 붙이는 게 맞다
**"미분류를 허용하는 설계"**가 품질의 출발점이다. 커버리지 100%를 목표로 잡는 순간 억지 배정이 시작된다.
2. 규칙이 잘하는 것과 못하는 것
규칙 기반부터 시작하는 건 옳다. 다만 어디까지가 규칙의 몫인지 알고 시작해야 한다.
[규칙이 강한 영역]
✓ 명시적 키워드가 있는 경우
"스포츠", "타이", "발" → 해당 유형
✓ 구조화 필드가 있는 경우
영업시간, 시술 목록, 가격대
✓ 절대 규칙
"임산부 전용" → 특정 니즈에서 제외
[규칙이 무너지는 영역]
✗ 표현 다양성
"뭉침", "결림", "굳음", "땡김" — 끝없이 늘어남
✗ 문맥 의존
"강하게" 가 장점인 맥락 / 주의사항인 맥락
✗ 부정 표현
"스포츠 마사지는 하지 않습니다" → 규칙은 매칭해버림
✗ 조합 판단
개별 신호는 약한데 여럿이 모이면 강해지는 경우
부정 표현이 실무에서 가장 뼈아프다. 키워드 매칭은 "안 합니다"를 "합니다"와 구분하지 못하고, 이 오류는 조용히 쌓인다.
[역할 분담]
규칙 하드 제약 (반드시 제외/반드시 포함)
+ 명확한 신호의 빠른 처리
모델 나머지 — 표현 다양성, 문맥, 조합
규칙이 모델보다 우선한다 (규칙은 거부권을 가짐)
규칙에 거부권을 주는 구조가 안전하다. 모델이 "피로"를 0.9로 예측해도 규칙이 "이 업소는 해당 시술 없음"이라고 하면 규칙을 따른다. 반대 방향(모델이 규칙을 뒤집는 것)은 허용하지 않는 편이 사고를 줄인다.
3. 레이블이 없을 때 — 부트스트랩
새 축을 만들면 학습 데이터가 0이다. 4만 개를 사람이 라벨링할 수는 없다.
[단계]
1. 규칙으로 고신뢰 샘플 자동 생성 (약한 지도)
명확한 키워드가 있는 것만 → 축당 200~500개
2. 사람이 검증 표본 구축
축당 100~200개, 무작위 추출 (규칙 매칭 여부 무관)
3. 1번으로 학습, 2번으로 평가
4. 오류 사례를 2번에 추가하고 반복
2번을 무작위로 뽑는 것이 핵심이다. 규칙에 걸린 것 중에서만 검증 표본을 만들면, 규칙이 놓친 영역의 성능을 영원히 모른다. 이 실수를 하면 지표는 좋은데 실제 커버리지는 형편없는 상태가 된다.
[약한 지도의 위험]
규칙으로 만든 레이블로 학습 → 모델이 규칙을 그대로 학습
→ 규칙의 오류까지 복제
→ 규칙보다 나아지지 않음
규칙이나 휴리스틱으로 대량의 근사 레이블을 만들어 학습하는 접근 자체는 약한 지도학습(data programming) 계열로 정리돼 있고, 그 논의의 핵심도 레이블 노이즈를 어떻게 다루느냐다. 이걸 완화하려면 입력에서 규칙이 사용한 키워드를 일부 가린 채 학습시키는 방법이 있다. 모델이 "스포츠"라는 단어 하나에 의존하지 못하게 되면 주변 문맥을 쓰게 된다.
한국어 텍스트라면 사전학습 언어 모델을 미세조정하는 쪽이 단어 빈도 기반보다 표현 다양성에 강하다. BERT 원 논문이 제시한 사전학습-미세조정 구도가 여전히 이런 소규모 분류의 표준 출발점이다.
4. 임계값 — 축마다 다르게
다중 레이블 분류기는 축별로 확률을 낸다. 어디서 자를지가 품질을 좌우한다.
일괄 0.5 적용
→ 어떤 축은 과다 부여, 어떤 축은 거의 안 붙음
→ 축별 데이터 분포가 다르기 때문
축별로 따로 정해야 하고, 기준은 그 축의 용도에 따라 달라진다.
[용도별 기준]
탐색 목록에 노출 정밀도 우선 (틀린 게 섞이면 신뢰 손상)
→ 임계값 높게, 재현율 포기
내부 추천 후보 재현율 우선 (뒤에 재랭킹이 있음)
→ 임계값 낮게
자동 태그 제안 중간 — 사람이 최종 확인
# 축별 임계값을 검증 표본에서 결정 (정밀도 하한을 고정)
def pick_threshold(y_true, y_score, min_precision=0.85):
best = 1.0
for t in [i / 100 for i in range(50, 100)]:
pred = y_score >= t
if pred.sum() == 0:
continue
prec = (y_true & pred).sum() / pred.sum()
if prec >= min_precision:
best = min(best, t) # 조건을 만족하는 가장 낮은 값
return best
**"정밀도 하한을 고정하고 재현율을 최대화"**하는 방식이 목록 노출용에는 잘 맞는다. 지표 계산은 scikit-learn의 평가 문서에 정리된 정의를 그대로 쓰면 된다.
임계값을 통과한 결과가 실제로 어떻게 보이는지는 니즈별 목록 페이지에서 바로 드러난다. 풀림의 '피로가 풀림' 목록처럼 니즈 하나에 대응하는 페이지를 만들어 두면, 분류 오류가 사용자 눈에 먼저 띄는 구조가 되어 품질 문제를 빨리 발견할 수 있다. 내부 지표만 보고 있으면 이걸 놓친다.
5. 근거를 남긴다
분류 결과에 "왜"가 없으면 운영이 불가능하다.
{
"item_id": "abc-123",
"needs": [
{
"key": "fatigue",
"score": 0.91,
"source": "model",
"evidence": ["시술 목록: 전신 이완", "설명: 장시간 근무 후"],
"rules_applied": []
},
{
"key": "vitality",
"score": 0.44,
"source": "model",
"decision": "rejected",
"reason": "threshold 0.62 미달"
}
],
"model_version": "needs-clf-2026-08-a",
"classified_at": "2026-08-12T02:11:00Z"
}
model_version과 classified_at이 특히 중요하다. 모델을 교체했을 때 어느 결과가 구버전인지 구분할 수 있어야 점진적 재분류가 가능하다. 이게 없으면 전체를 다시 돌리는 수밖에 없다.
[거부된 결과도 남기는 이유]
임계값을 낮추면 무엇이 들어오는지 미리 알 수 있다
→ 임계값 조정 전 영향 시뮬레이션 가능
6. 운영 중 평가 — 지표만 보면 놓친다
[정기 점검]
· 축별 부여 건수 추이 급변하면 데이터나 모델 문제
· 미분류 비율 너무 낮으면 억지 배정 의심
· 축 간 동시 부여율 특정 두 축이 항상 함께면 축 정의가 겹침
· 신규 등록 항목의 분류율 신규만 유독 낮으면 입력 필드 문제
"축 간 동시 부여율"이 택소노미 자체의 문제를 잡아낸다. 피로와 스트레스가 95% 함께 부여된다면 두 축은 사용자에게 사실상 같은 축이고, 나눠 둘 이유가 없다.
[사용자 신호로 검증]
니즈 페이지 → 상세 클릭률
니즈 페이지 이탈률
니즈 페이지 유입 후 다른 축으로 이동한 비율
마지막 지표가 분류 오류의 좋은 대리 지표다. "피로"로 들어왔다가 곧바로 "스트레스"로 옮겨간다면, 그 사용자에게 첫 분류가 맞지 않았다는 뜻이다.
7. 사람이 개입할 지점을 미리 정한다
전면 자동화는 대개 실패한다. 개입 지점을 설계에 넣어둔다.
[개입 지점]
① 애매 구간 큐 점수 0.45~0.70 구간을 검토 대기열로
② 신고 기반 "이 항목은 여기 안 맞아요" 사용자 신고
③ 공급자 수정 요청 당사자가 가장 정확히 안다
④ 표본 감사 월 1회 무작위 100건 검증
③이 비용 대비 효과가 가장 크다. 다만 공급자가 노출을 늘리려고 모든 축에 붙이려는 유인이 있으므로, 요청은 받되 자동 반영하지 않고 검토 후 반영하는 절차가 필요하다.
공급자 요청 → 규칙 검증 (하드 제약 위반 여부)
→ 모델 점수 확인 (극단적으로 낮으면 보류)
→ 검토 큐
8. 정리
1. 다중 레이블로 정의하고, 0개 부여를 허용한다
2. 커버리지 100%를 목표로 잡지 않는다
3. 규칙은 하드 제약과 명확 신호, 모델은 표현 다양성과 문맥
4. 규칙에 거부권을 주되 반대 방향은 막는다
5. 검증 표본은 규칙 매칭 여부와 무관하게 무작위 추출
6. 임계값은 축별로, 용도(정밀도/재현율)에 맞춰 결정
7. 근거·모델 버전·시각을 결과에 함께 저장
8. 축 간 동시 부여율로 택소노미 자체를 의심한다
가장 자주 저지르는 실수는 5번이다. 규칙에 걸린 데이터로만 학습하고 평가하면 모든 숫자가 좋게 나오는데, 실제로는 규칙이 못 잡던 영역이 그대로 남아 있다. 무작위 검증 표본 하나가 이 착시를 깨준다.