jobfit-embed-ko

이λ ₯μ„œμ™€ μ±„μš©κ³΅κ³ λ₯Ό λ§€μΉ­ν•˜κΈ° μœ„ν•΄ νŒŒμΈνŠœλ‹ν•œ λ¬Έμž₯ μž„λ² λ”© λͺ¨λΈμž…λ‹ˆλ‹€. 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 μ–‘μžν™”λ₯Ό μ±„νƒν•˜μ§€ μ•Šμ€ 이유

배포 편의λ₯Ό μœ„ν•΄ 동적 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으둜 μƒμ„±ν•œ ν•©μ„± 데이터이며, λ“±μž₯ν•˜λŠ” νšŒμ‚¬λŠ” μ‹€μ‘΄ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

ν•œκ³„

  • ν•©μ„± λ°μ΄ν„°λ‘œ ν•™μŠ΅Β·ν‰κ°€ν–ˆμœΌλ―€λ‘œ μ‹€μ œ μ±„μš©κ³΅κ³  뢄포와 λ‹€λ₯Ό 수 μžˆμŠ΅λ‹ˆλ‹€.
  • 평가 쿼리가 32건으둜 적어, μ§€ν‘œ 1건의 차이가 3%p μ΄μƒμœΌλ‘œ λ‚˜νƒ€λ‚©λ‹ˆλ‹€.
  • ν•œκ΅­ IT 직ꡰ 10쒅을 λŒ€μƒμœΌλ‘œ ν•™μŠ΅ν–ˆμŠ΅λ‹ˆλ‹€. λ‹€λ₯Έ λ„λ©”μΈμ—λŠ” μ ν•©ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support

Model tree for Kimhakyung/jobfit-embed-ko