| """ | |
| pred.py - predictor for evaluation | |
| """ | |
| import json | |
| import pickle | |
| from typing import List, Tuple | |
| import numpy as np | |
| import torch | |
| from pathlib import Path | |
| ROOT = Path(__file__).resolve().parent.parent | |
| from torch import nn | |
| class SkillClassifier(nn.Module): | |
| def __init__(self, input_dim, num_labels, hidden_dim=32, dropout=0.3): | |
| super().__init__() | |
| self.net = nn.Sequential( | |
| nn.Linear(input_dim, hidden_dim), | |
| nn.ReLU(), | |
| nn.Dropout(dropout), | |
| nn.Linear(hidden_dim, num_labels), | |
| ) | |
| def forward(self, x): | |
| return self.net(x) | |
| def JobAnalyze_6k(job_desc: str = "", role: str = "", job_type: str = "", top_k: int = 50) -> List[Tuple[str, float]]: | |
| """ | |
| Predict top-k skills. | |
| Current top-k = 48 | |
| """ | |
| prep_dir = ROOT / "model" / "prep" | |
| if not prep_dir.exists(): | |
| alt = ROOT / "prep" | |
| if alt.exists(): | |
| prep_dir = alt | |
| label_path = prep_dir / "label_vocab.json" | |
| vector_path = prep_dir / "vectorizer.pkl" | |
| weights_path = ROOT / "model_out" / "skill_classifier.pt" | |
| if not label_path.exists(): | |
| raise FileNotFoundError( | |
| f"Missing {label_path}. Make sure you ran data_prep and model training." | |
| ) | |
| if not vector_path.exists(): | |
| raise FileNotFoundError( | |
| f"Missing {vector_path}. Make sure you ran data_prep." | |
| ) | |
| if not weights_path.exists(): | |
| raise FileNotFoundError( | |
| f"Missing {weights_path}. Make sure you ran model/model.py." | |
| ) | |
| with open(label_path, encoding="utf-8") as f: | |
| label_vocab = json.load(f) | |
| with open(vector_path, "rb") as f: | |
| vectorizer = pickle.load(f) | |
| input_dim = int(getattr(vectorizer, "vocabulary_", {}).__len__()) or vectorizer.transform([""]).shape[1] | |
| model = SkillClassifier(input_dim, len(label_vocab)) | |
| model.load_state_dict(torch.load(weights_path, map_location="cpu")) | |
| model.eval() | |
| combined_text = f"{job_desc} {role} {job_type}" | |
| X = vectorizer.transform([combined_text]).toarray().astype(np.float32) | |
| with torch.no_grad(): | |
| logits = model(torch.tensor(X)) | |
| probs = torch.sigmoid(logits).numpy()[0] | |
| ranked = sorted(zip(label_vocab, probs), key=lambda x: -x[1]) | |
| return ranked[:top_k] | |