AI

BGE-M3 Pairwise Embedding Fine-tuning으로 한국어 업종·가맹점 유사도 모델 개선하기

반응형
공공데이터로 한국어 업종/가맹점 임베딩 모델 추가학습하기

Embedding Fine-tuning Lab · 2026년 6월 3일

공공데이터로 한국어 업종·가맹점 임베딩 모델을 추가학습해본 기록

업체명, 공급업체명, 업종명, 기타 정산 문구가 짧게 섞인 텍스트를 의미 기반으로 비교하기 위해 BGE-M3 계열 임베딩 모델을 도메인 데이터로 튜닝했다. 공개 글에서는 실제 업체명과 내부 문구를 쓰지 않고, 가명과 공개 데이터 기반 예시만 사용했다.

Hugging Face 공개 모델 보기

공공데이터 수집부터 모델 평가까지 이어지는 전체 파이프라인
▲ 학습 파이프라인 GIF. 자체 생성, 기반 데이터: 공공데이터포털·KSIC·로컬 평가 결과

1. 왜 임베딩 모델을 튜닝했나

처음 목표는 단순했다. 카드 사용 내역이나 정산 데이터에 가맹점명 업종명 기타 텍스트가 짧게 들어왔을 때, 두 문장이 같은 성격인지 다른 성격인지 판단하고 싶었다. 예를 들어 일반한식한식음식점은 표현은 다르지만 거의 같은 업종으로 봐야 하고, 일반한식항공사는 같은 내부회계팀 문구가 붙어 있어도 낮게 나와야 한다.

일반 임베딩 모델은 문장 전체 분위기를 잘 잡지만, 업무 데이터에서는 이상한 일이 생긴다. 뒤쪽 문구가 똑같으면 실제로 중요한 업종 차이가 흐려진다. 내부회계관리팀 출근 국내 평일 같은 공통 문맥이 길게 붙으면 한식항공사도 생각보다 높게 묶일 수 있다. 이 문제는 프롬프트로만 해결하기 어렵다. 그래서 모델에게 “이런 조합에서는 앞쪽의 업체명·업종명이 더 중요하다”는 신호를 추가로 줘야 했다.

핵심은 모델에게 업종명을 외우게 하는 것이 아니라, 업종명·상호명·정산 문구가 섞였을 때 어떤 관계를 가깝게 보고 어떤 관계를 멀게 볼지 학습시키는 것이다.

생성형 모델을 바로 파인튜닝하는 선택지도 있었지만, 이 문제에는 맞지 않았다. 우리가 원하는 것은 긴 답변 생성이 아니라 검색, 중복 판단, 후보 정렬, 유사 문의 추천 같은 벡터 검색의 품질이다. 따라서 먼저 임베딩 모델을 도메인 문장에 맞추고, 이후 RAG나 VectorDB 검색에 붙일 수 있는 구조를 잡는 편이 더 실용적이었다.

2. 사용한 공공데이터와 전처리

데이터는 크게 3가지 축으로 모았다. 첫째는 한국표준산업분류 제11차 개정 체계다. 로컬 실험에서는 1,205개의 세세분류 수준 업종 경로를 활용했다. 둘째는 소상공인시장진흥공단의 상가·상권 정보처럼 상호명과 업종명을 함께 제공하는 공개 데이터다. 공공데이터포털 설명에 따르면 해당 API는 상호명, 상권업종명, 표준산업분류명, 위치 정보 등을 주요 항목으로 제공한다. 셋째는 조달/공급업체 계열 공개 데이터에서 공급업체명과 품목명을 뽑아 도메인 표현을 보강했다.

전처리에서는 원문 필드를 그대로 쓰지 않았다. 업체명에는 법인 표기, 괄호, 지점명, 특수문자가 섞이고 업종명에는 표준산업분류와 상권업종분류가 서로 다른 깊이로 들어온다. 따라서 실험에서는 상호명·공급업체명의 공백과 특수문자를 정리하고, 업종명 경로는 보존하되 학습 문장에는 너무 긴 경로를 매번 넣지 않았다. 품목명은 보조 정보로만 사용했다. 실제 카드 데이터에서는 업종명이 더 중요하므로 품목명 중심 학습은 줄였다.

데이터 축사용 목적주의점
한국표준산업분류업종 상하위 관계와 sibling 관계 생성분류체계는 실제 카드 업종명과 1:1로 맞지 않을 수 있음
상가/상권 정보상호명과 업종명의 실제 조합 확보상호명 공개 사용 시 식별 가능성 주의
공급업체/품목 공개 데이터공급업체명·품목명 표현 보강품목명만으로 업종 유사도를 판단하면 오분류 가능
synthetic pair 관계 분포를 보여주는 막대 애니메이션
▲ 학습 pair 분포 GIF. 자체 생성, 기반 파일: compact_card_training_pairs.csv

3. Synthetic pair를 어떻게 만들었나

임베딩 튜닝에서 가장 중요한 것은 문장 하나하나보다 문장 사이의 관계다. 그래서 학습 데이터는 sentences = [...] 목록이 아니라 text_a, text_b, label 형태의 pair로 만들었다. 최종 compact card 학습 데이터는 18,366개 pair였고, 그중 hard tuning 단계에는 3,210개 pair를 사용했다.

라벨은 단순히 0과 1만 두지 않았다. 같은 업체명·같은 canonical 업종이면 0.96처럼 높게, 같은 세세분류의 다른 업체면 0.90처럼 높지만 완전 동일보다는 낮게, 같은 상위 업종의 sibling이면 0.70 전후, 전혀 다른 대분류면 0.04처럼 낮게 주었다. 이렇게 해야 모델이 “비슷함에도 정도가 있다”는 구조를 배운다.

특히 효과가 컸던 것은 hard negative였다. 예를 들어 두 문장의 뒤쪽 문맥이 내부회계관리팀 출근 국내 평일로 같아도 앞쪽 업종이 한식항공사면 낮아야 한다. 일반 모델은 뒤쪽 문맥 때문에 높은 점수를 줄 수 있으므로, 이런 케이스를 일부러 만들어 추가 학습했다.

text_a,text_b,label,relation
A한식 일반한식 내부회계관리팀 출근 국내 평일,A한식 한식음식점 내부회계관리팀 출근 국내 평일,0.96,same_entity_same_industry
A한식 일반한식 내부회계관리팀 출근 국내 평일,B항공 항공사 내부회계관리팀 출근 국내 평일,0.02,hard_different_industry_same_context
C주유소 주유소 출장 차량 유류비,D카페 커피전문점 출장 차량 유류비,0.04,different_industry_same_context

이 방식의 장점은 실제 전체 카드 텍스트가 아직 없어도 준비할 수 있다는 점이다. 공공데이터에서 업종명과 상호명 후보를 만들고, 회사 데이터에서 자주 붙는 문맥을 template으로 붙이면 운영 데이터에 가까운 synthetic pair를 만들 수 있다.

4. 라벨을 덜 붙이고 실제 입력처럼 만들었다

초기에는 공급업체명: 한국식품유통 | 업종명: 식자재 도매업 | 품목명: 급식재료처럼 라벨을 붙인 문장을 많이 생각했다. 설명하기에는 좋지만 실제 운영 입력은 보통 그렇게 깔끔하지 않다. 카드사나 내부 정산 데이터는 한국식품유통 식자재 도매업 급식재료처럼 짧게 붙어 들어올 가능성이 높다.

그래서 최종 학습에서는 앞쪽 일부에만 구조 신호를 주거나, 아예 compact format으로 줄이는 쪽을 택했다. 라벨이 너무 많으면 모델이 업종명: 같은 글자에 익숙해지고, 실제 입력에서 라벨이 사라졌을 때 성능이 흔들릴 수 있다. 반대로 너무 날것으로만 두면 업체명과 업종명의 경계가 흐려진다. 실험에서는 두 포맷을 섞되, 최종 평가는 라벨 없는 compact 문장으로 맞췄다.

라벨형 문장에서 실제 카드 입력형 문장으로 바꾼 과정
▲ 입력 포맷 정리 GIF. 자체 생성

이 글의 예시도 같은 원칙을 따른다. 사람이나 회사가 특정되지 않도록 업체명은 A한식, B항공, C주유소 같은 가명으로 표기했다. 실제 운영 파이프라인에서는 이름 마스킹, 고객사 식별자 분리, 원문 접근 권한 제한을 먼저 설계해야 한다.

튜닝 전후 유사도 변화 비교
▲ 튜닝 전후 평가 GIF. 자체 생성, 기반 파일: compact_card_v2_eval.csv

5. 튜닝 결과와 출력 예시

아래 표는 기존 KSIC 기반 튜닝 모델과 compact card v2 모델을 비교한 결과다. 좋은 튜닝은 모든 점수를 올리는 것이 아니다. 같은 의미의 표현은 높게 유지하고, 문맥은 같지만 업종이 다른 pair는 낮춰야 한다.

케이스이전 모델compact v2변화
한식 alias high93.59%97.64%+4.05
한식 vs 항공 low79.90%-1.10%-81.00
한식 vs 음식점 related85.10%96.26%+11.16
카페 vs 베이커리 related91.43%76.23%-15.20
식자재 유통 alias high92.76%97.48%+4.72
주유소 vs 카페 low81.27%31.20%-50.07
소프트웨어 related high69.88%74.51%+4.63
병원 vs 약국 related78.04%75.38%-2.66
마트 vs 편의점 related70.96%85.01%+14.05
전기공사 alias high95.22%97.09%+1.87

가장 눈에 띈 변화는 한식 vs 항공이다. 이전 모델에서는 같은 내부 문맥 때문에 79.90%까지 올라갔지만, hard negative를 넣은 뒤 -1.10%로 낮아졌다. 주유소 vs 카페도 81.27%에서 31.20%로 내려갔다. 반대로 일반한식 vs 한식음식점은 97.64%로 높게 유지됐다. 이 방향이 업무적으로 중요하다. 유사한 업종은 묶고, 겉문맥만 같은 다른 업종은 분리해야 하기 때문이다.

공개용 가명 문장으로 다시 돌린 출력 예시는 다음과 같다. 이 값들은 로컬의 bge-m3-business-compact-card-context-v2 모델로 계산했다.

판단문장 A문장 B점수
동일/유사 업종A한식 일반한식 내부회계관리팀 출근 국내 평일A한식 한식음식점 내부회계관리팀 출근 국내 평일98.21%
다른 업종A한식 일반한식 내부회계관리팀 출근 국내 평일B항공 항공사 내부회계관리팀 출근 국내 평일0.60%
다른 업종C주유소 주유소 출장 차량 유류비D카페 커피전문점 출장 차량 유류비29.50%
유사 업종E식품유통 식자재 도매업 급식재료 정기구매F급식유통 급식재료 유통 급식재료 정기구매92.23%
유사 업종G전기 전기공사업 시설관리팀 유지보수H전기 전기설비공사 시설관리팀 유지보수96.78%
가명 데이터 5건에 대한 모델 출력 예시
▲ 가명 출력 결과 GIF. 자체 생성, 기반 모델: bge-m3-business-compact-card-context-v2

이 결과만 보고 “완성”이라고 말할 수는 없다. 실제 운영에서는 임계값을 업종군별로 다르게 둘 수 있고, 낮은 확신도의 후보는 담당자 검토로 넘겨야 한다. 다만 이 실험으로 확인한 것은 분명하다. 한국어 업종명과 가맹점명 조합에서는 범용 임베딩을 그대로 쓰기보다, 도메인 pair로 추가학습하면 더 업무적인 점수 분포를 만들 수 있다.

6. 운영에 붙일 때의 현실적인 구조

이 모델은 단독 서비스라기보다 검색 시스템의 한 부품으로 보는 것이 맞다. 예를 들어 신규 문의나 카드 사용 문장이 들어오면 먼저 텍스트를 정제하고, 임베딩으로 후보를 넓게 찾은 뒤, 필요하면 BM25나 reranker를 붙여 최종 후보를 정렬한다. 특히 SAP RFC 문의나 고객지원 데이터처럼 사람들이 대충 적는 텍스트에서는 원문을 그대로 임베딩하는 것보다 query normalization 단계가 중요하다.

원문 정제
질의 보강
임베딩 검색
키워드 검색
Rerank
검토용 초안

운영에서 중요한 평가지표도 모델 점수 하나가 아니다. 검색 상위 5개 안에 정답 케이스가 있는지, 담당자가 초안 작성 시간을 얼마나 줄였는지, 잘못된 자동 응답을 막기 위해 “추가 확인 필요”로 돌린 비율이 적절한지 함께 봐야 한다. 100건 중 1건의 누락만 보고 전체 기능을 폐기하면 AI 도입은 계속 실패한다. 대신 실패 케이스를 모아 hard negative와 평가셋으로 다시 넣어야 한다.

  • 신규 데이터는 매번 전체 재학습하지 않고 증분 임베딩으로 VectorDB에 upsert한다.
  • 모델 재학습은 실패 케이스가 충분히 쌓였을 때만 수행한다.
  • 개인정보, 고객사명, 담당자명, 계약정보는 정제 단계에서 분리한다.
  • 임계값보다 낮거나 근거가 약한 답변은 자동 응답하지 않고 운영자 검토로 넘긴다.

마무리

이번 실험은 “공공데이터를 넣었더니 모델이 똑똑해졌다”로 끝나는 이야기가 아니다. 실제로는 어떤 데이터를 넣을지보다 어떤 관계를 학습시킬지가 더 중요했다. 업종명, 가맹점명, 공급업체명은 짧고 비슷한 표현이 많기 때문에, 단순 문장 임베딩보다 pair 설계가 성능을 좌우했다.

정리하면 다음과 같다. 공공데이터로 업종 체계와 표현 후보를 만들고, 실제 운영 입력에 가까운 compact sentence를 만들고, 동의어·상하위어·hard negative를 synthetic pair로 구성하고, BGE-M3 기반 모델을 추가학습했다. 그 결과 한식 vs 한식음식점은 높게 유지하면서 한식 vs 항공사처럼 문맥만 같은 다른 업종은 낮추는 방향으로 점수 분포가 바뀌었다.

다음 단계는 실제 업무 데이터에서 사람이 검수한 pair를 확보하는 것이다. 공개 데이터는 출발점으로 좋지만, 마지막 20%는 조직마다 다른 표현과 예외 케이스가 만든다. 따라서 운영 시스템에서는 VectorDB/RAG와 함께 쓰고, 실패 케이스를 계속 학습 데이터로 되돌리는 루프를 만드는 것이 가장 현실적이다.

실험하면서 정리한 판단 기준

이번 실험에서 가장 자주 부딪힌 질문은 “점수가 얼마나 변했느냐”였다. 그런데 임베딩 모델 튜닝에서는 평균 점수 상승만 보면 위험하다. 같은 업종끼리는 이미 높았는데 더 높아지는 것은 큰 의미가 없을 수 있다. 오히려 업무적으로 중요한 것은 서로 다른 업종인데도 같은 부서명, 같은 결제 목적, 같은 지역명이 붙어 높게 나오는 케이스를 낮추는 것이다. 그래서 평가는 positive pair와 negative pair를 따로 봐야 한다.

예를 들어 일반한식과 한식음식점이 93%에서 97%로 오른 것은 좋은 신호지만, 그것만으로는 부족하다. 한식과 항공사가 79%에서 -1%로 내려간 변화가 더 중요했다. 이 변화는 모델이 문장 뒤쪽의 공통 문맥보다 앞쪽 업종 차이를 더 크게 보게 되었다는 뜻이다. 실제 업무에서는 이런 hard negative가 많다. 부서명, 출장 문구, 국내/해외, 평일/주말 같은 표현은 문장을 비슷하게 보이게 만들지만, 정산 판단에서는 업종이 더 중요한 경우가 많다.

또 하나의 기준은 “운영 입력과 학습 입력이 얼마나 닮았는가”였다. 학습 데이터에 라벨을 예쁘게 붙이면 사람이 보기에는 명확하지만, 실제 입력이 라벨 없이 들어올 때 성능이 흔들릴 수 있다. 그래서 최종적으로는 라벨형 문장과 compact 문장을 섞되, 테스트는 라벨 없는 문장으로 맞췄다. 이 방식은 모델이 필드명을 외우는 대신, 짧은 토큰 조합 안에서 의미 있는 단서를 찾도록 만드는 데 도움이 됐다.

한계도 분명하다. 공공데이터는 업종 체계와 표현 후보를 만들기에는 좋지만, 회사 내부의 카드 사용 문맥, 공급업체 별칭, 부서별 표현 습관까지 담고 있지는 않다. 따라서 공개 데이터 기반 튜닝은 출발점이고, 실제 품질은 운영 데이터에서 나온 실패 케이스를 얼마나 빨리 다시 학습 데이터로 되돌리느냐에 달려 있다. 특히 “마트와 편의점은 어느 정도 유사하게 볼 것인가”, “카페와 베이커리는 같은 식음료군으로 묶을 것인가” 같은 기준은 기술 문제가 아니라 업무 기준 문제다.

그래서 운영 적용 시에는 모델 점수 하나로 자동 판단하지 않고, 점수 구간별 정책을 두는 편이 좋다. 예를 들어 90% 이상은 강한 유사 후보, 70~90%는 관련 후보, 40% 이하는 비유사 후보로 두되, 업종군별 예외를 따로 둔다. 그리고 사람이 수정한 결과를 다시 저장해 다음 튜닝 데이터로 만드는 루프가 필요하다. 임베딩 튜닝은 한 번 끝내는 프로젝트라기보다, 조직의 표현 습관을 계속 반영하는 검색 품질 개선 작업에 가깝다.

공개 가능한 글로 정리할 때는 이 점도 중요하다. 모델이 실제 데이터를 학습했다는 사실보다, 어떤 원칙으로 데이터를 가명 처리했고 어떤 기준으로 평가했는지를 함께 남겨야 재현성과 보안성을 동시에 확보할 수 있다. 작은 실험이라도 기준을 문서로 남기면 다음 개선이 훨씬 쉬워진다. 기록은 모델보다 오래 남는다.

FAQ

Q1. 왜 생성형 모델을 바로 파인튜닝하지 않았나요?+

목표가 답변 생성이 아니라 짧은 업체명·업종명 조합의 유사도 판단이었기 때문입니다. 지식 암기보다 “어떤 문장끼리 가까워야 하는가”를 임베딩 공간에 반영하는 것이 먼저였습니다.

Q2. 공공데이터만으로 충분했나요?+

완전히 충분하지는 않았습니다. 공공데이터는 업종 체계와 상호명·업종명 후보를 만드는 데 좋지만, 실제 회사 카드/정산 문맥은 별도 synthetic pair로 보강해야 했습니다.

Q3. 점수가 100%에 가까우면 항상 같은 의미인가요?+

아닙니다. 임베딩 점수는 운영 판단의 보조 지표입니다. 임계값, 업종별 예외, 하드 네거티브 평가를 함께 두어야 실제 업무에서 안정적으로 쓸 수 있습니다.

Q4. 업체명은 공개해도 되나요?+

공개 글이나 외부 발표에서는 가명 처리하는 편이 안전합니다. 실제 운영에서는 개인정보·고객사명·담당자명·계약정보를 마스킹하고, 원문 접근 권한도 분리해야 합니다.

Q5. 다음 단계는 무엇인가요?+

실제 업무 데이터에서 사람이 판단한 유사/비유사 pair를 모으고, 실패 케이스를 하드 네거티브로 다시 넣는 것입니다. 이후에는 RAG 검색용 임베딩과 reranker를 함께 평가하는 쪽이 좋습니다.

참고 링크

주의: 본문 수치와 GIF는 로컬 실험 산출물을 공개용으로 정리한 것이며, 실제 상호명·고객사명·개인정보는 사용하지 않았다.

반응형

Categories