Downloads · 30 days
0
Kimhakyung/jobfit-embed-ko
jobfit-embed-ko is a sentence similarity model from Kimhakyung. Use it when you need a score for how close two texts are. It is set up for onnx. The card lists the license as apache-2.0.
이력서와 채용공고를 매칭하기 위해 파인튜닝한 문장 임베딩 모델입니다. JobFit AI 프로젝트에서 사용합니다.
Downloads · 30 days
0
Access
Public
Updated Jul 30, 2026
Repo size
488 MB
Likes
0
Public
Click a slice to open those files.
.onnx471 MB · 96%
From the Hugging Face model README
이력서와 채용공고를 매칭하기 위해 파인튜닝한 문장 임베딩 모델입니다. JobFit AI 프로젝트에서 사용합니다.
범용 문장 유사도 모델은 직군 경계를 잘 구분하지 못했습니다. ML 엔지니어 이력서를 넣으면 백엔드 공고가 1위로 올라오는 문제가 있었습니다.
이력서↔공고 쌍으로 대조학습(MultipleNegativesRankingLoss)을 적용하고,
단순히 무관한 공고 대신 인접 직군 공고를 hard negative로 주어
미묘한 경계를 학습시켰습니다.
학습에 사용하지 않은 쿼리 32건, 검색 대상 공고 421개 기준입니다.
| 지표 | 파인튜닝 전 | 파인튜닝 후 | 변화 |
|---|---|---|---|
| Recall@1 | 37.5% | 84.4% | +46.9%p |
| Recall@3 | 62.5% | 90.6% | +28.1%p |
| Recall@5 | 90.6% | 96.9% | +6.3%p |
| MRR | 0.552 | 0.889 | +0.337 |
| 직군 일치율 (1위 공고) | 78.1% | 100% | +21.9%p |
마지막 지표는 이 프로젝트의 목표(직군 경계 구분)를 직접 측정하려고 정의한 것입니다.
배포 편의를 위해 동적 INT8 양자화를 시도했습니다. 용량은 471MB → 118MB로 줄었지만 직군 일치율이 100% → 93.8%로 떨어져 채택하지 않았습니다. 배포 환경에 메모리 여유가 있어 정확도를 포기할 이유가 없었습니다.
ONNX Runtime으로 추론합니다. PyTorch가 필요 없습니다.
import numpy as np
import onnxruntime as ort
from huggingface_hub import snapshot_download
from tokenizers import Tokenizer
path = snapshot_download("Kimhakyung/jobfit-embed-ko")
tokenizer = Tokenizer.from_file(f"{path}/tokenizer.json")
tokenizer.enable_truncation(max_length=128) # 학습 설정과 반드시 일치시킬 것
tokenizer.enable_padding()
session = ort.InferenceSession(f"{path}/model.onnx", providers=["CPUExecutionProvider"])
def embed(texts: list[str]) -> np.ndarray:
enc = tokenizer.encode_batch(texts)
ids = np.array([e.ids for e in enc], dtype=np.int64)
mask = np.array([e.attention_mask for e in enc], dtype=np.int64)
hidden = session.run(None, {
"input_ids": ids,
"attention_mask": mask,
"token_type_ids": np.zeros_like(ids),
})[0]
# 마스크를 반영한 평균 풀링 후 L2 정규화
m = mask[..., None].astype(np.float32)
pooled = (hidden * m).sum(1) / np.clip(m.sum(1), 1e-9, None)
return pooled / np.linalg.norm(pooled, axis=1, keepdims=True)
vectors = embed(["Python과 Airflow로 데이터 파이프라인을 구축한 3년차 엔지니어입니다."])
최대 길이 128을 지켜야 합니다. 학습 시 128토큰에서 잘랐기 때문에, 추론에서 다른 값을 쓰면 긴 문서의 벡터가 달라져 정확도가 조용히 떨어집니다. 오류가 나지 않으므로 알아채기 어렵습니다.
sentence_bert_config.json에 이 값을 함께 담아 두었습니다.
| 항목 | 값 |
|---|---|
| 베이스 모델 | sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 |
| 손실 함수 | MultipleNegativesRankingLoss |
| 학습 예제 | 384쌍 (anchor, positive, hard negative) |
| 에폭 / 배치 | 3 / 16 (72 스텝) |
| 학습률 | 2e-5 |
| 최대 토큰 | 128 |
| 하드웨어 | CPU (약 6분) |
재현 스크립트: scripts/finetune_embeddings.py
실제 이력서는 개인정보이므로 수집하지 않았습니다. 학습에 사용한 이력서 요약과 채용공고는 모두 LLM으로 생성한 합성 데이터이며, 등장하는 회사는 실존하지 않습니다.