JobSelect 0.10.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cli/jobselect.py +114 -0
- jobselect-0.10.2.dist-info/METADATA +247 -0
- jobselect-0.10.2.dist-info/RECORD +20 -0
- jobselect-0.10.2.dist-info/WHEEL +5 -0
- jobselect-0.10.2.dist-info/entry_points.txt +2 -0
- jobselect-0.10.2.dist-info/licenses/LICENSE +21 -0
- jobselect-0.10.2.dist-info/top_level.txt +3 -0
- model/Model.py +123 -0
- model/__init__.py +10 -0
- model/eval.py +106 -0
- model/pred.py +81 -0
- model/prep/__init__.py +0 -0
- model/prep/data_prep.py +95 -0
- model/prep/label_vocab.json +50 -0
- model/prep/prepared_data.npz +0 -0
- model/prep/sym_map.py +106 -0
- model/prep/test.py +5 -0
- model/prep/vectorizer.pkl +0 -0
- model_out/skill_classifier.pt +0 -0
- model_out/training_history.json +1 -0
cli/jobselect.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import requests
|
|
6
|
+
from dotenv import load_dotenv
|
|
7
|
+
from rich import print
|
|
8
|
+
from pyfiglet import Figlet
|
|
9
|
+
|
|
10
|
+
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
|
11
|
+
if str(PROJECT_ROOT) not in sys.path:
|
|
12
|
+
sys.path.insert(0, str(PROJECT_ROOT))
|
|
13
|
+
|
|
14
|
+
_env_path = Path(__file__).resolve().parent / ".env"
|
|
15
|
+
if not _env_path.exists():
|
|
16
|
+
_env_path = PROJECT_ROOT / ".env"
|
|
17
|
+
load_dotenv(dotenv_path=_env_path)
|
|
18
|
+
|
|
19
|
+
API_URL = os.getenv("JOBAUTO_API_URL", "").rstrip("/")
|
|
20
|
+
API_KEY = os.getenv("JOBAUTO_API_KEY", "")
|
|
21
|
+
|
|
22
|
+
from model.pred import JobAnalyze_6k as _local_predict
|
|
23
|
+
|
|
24
|
+
def _call_api(jd: str, role: str, job_type: str) -> list[tuple[str, float]]:
|
|
25
|
+
endpoint = f"{API_URL}/JobAnalyze_6k"
|
|
26
|
+
headers = {"JobAnalyze_6k_Key": API_KEY}
|
|
27
|
+
payload = {"Job_Desc": jd, "Role": role, "Type": job_type}
|
|
28
|
+
|
|
29
|
+
response = requests.post(endpoint, json=payload, headers=headers, timeout=10)
|
|
30
|
+
response.raise_for_status()
|
|
31
|
+
|
|
32
|
+
data = response.json()
|
|
33
|
+
|
|
34
|
+
return [(skill, float(score)) for skill, score in data["answer"]]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def predict(jd: str, role: str, job_type: str) -> tuple[list[tuple[str, float]], str]:
|
|
38
|
+
if API_URL and API_KEY:
|
|
39
|
+
try:
|
|
40
|
+
results = _call_api(jd, role, job_type)
|
|
41
|
+
return results, "API"
|
|
42
|
+
except requests.exceptions.ConnectionError:
|
|
43
|
+
pass
|
|
44
|
+
except requests.exceptions.Timeout:
|
|
45
|
+
pass
|
|
46
|
+
except requests.exceptions.HTTPError as e:
|
|
47
|
+
if e.response is not None and e.response.status_code in (401, 403):
|
|
48
|
+
print(f"[red][JobAuto] API key issue ({e.response.status_code}) - running in LOCAL mode.[/red]")
|
|
49
|
+
except Exception:
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
results = _local_predict(jd, role=role, job_type=job_type)
|
|
53
|
+
return results, "LOCAL"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def clear_console() -> None:
|
|
57
|
+
if os.name == "nt":
|
|
58
|
+
subprocess.run("cls", shell=True)
|
|
59
|
+
else:
|
|
60
|
+
subprocess.run(["clear"])
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def title() -> None:
|
|
64
|
+
f = Figlet(font="slant")
|
|
65
|
+
print("——————————————————————————————————————————————————————————————————————")
|
|
66
|
+
print(f.renderText("JobSelect CLI"))
|
|
67
|
+
print("[blue] Running Model : JobAnalyze 6k v1.0")
|
|
68
|
+
print("Copyright Akshay Babu, All rights reserved")
|
|
69
|
+
print("——————————————————————————————————————————————————————————————————————")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def query(message: str) -> None:
|
|
73
|
+
print("\n[yellow][JobAuto]")
|
|
74
|
+
print(f"[yellow] >> {message} : ")
|
|
75
|
+
print("[You]")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def cli() -> None:
|
|
79
|
+
clear_console()
|
|
80
|
+
title()
|
|
81
|
+
|
|
82
|
+
print("[yellow][JobAuto] ")
|
|
83
|
+
print("[yellow]>> Welcome to JobAuto!")
|
|
84
|
+
|
|
85
|
+
query("Enter Job Description")
|
|
86
|
+
jd = input(" >> ")
|
|
87
|
+
|
|
88
|
+
query("Enter Role (AI Engineer / AI Developer)")
|
|
89
|
+
role = input(" >> ")
|
|
90
|
+
|
|
91
|
+
query("Enter Type (Internship / Junior / Senior)")
|
|
92
|
+
job_type = input(" >> ")
|
|
93
|
+
|
|
94
|
+
results, mode = predict(jd, role=role, job_type=job_type)
|
|
95
|
+
|
|
96
|
+
clear_console()
|
|
97
|
+
title()
|
|
98
|
+
|
|
99
|
+
mode_colour = "green" if mode == "API" else "cyan"
|
|
100
|
+
print(f"\n [{mode_colour}][Mode: {mode}][/{mode_colour}]")
|
|
101
|
+
|
|
102
|
+
print("\n [yellow]Job Description Provided : \n", jd.strip())
|
|
103
|
+
print("\n [yellow]Job Role : \n", role)
|
|
104
|
+
print("\n [yellow]Type : \n", job_type)
|
|
105
|
+
print()
|
|
106
|
+
|
|
107
|
+
print("\n [yellow]TOP Skills \n")
|
|
108
|
+
for label, prob in results:
|
|
109
|
+
bar = "█" * int(prob * 30)
|
|
110
|
+
print(f" {label:25s} {prob:.2f} {bar}\n")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
if __name__ == "__main__":
|
|
114
|
+
cli()
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: JobSelect
|
|
3
|
+
Version: 0.10.2
|
|
4
|
+
Author-email: Akshay Babu <akshaysureshbabu100@gmail.com>
|
|
5
|
+
Description-Content-Type: text/markdown
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Dist: rich==15.0.0
|
|
8
|
+
Requires-Dist: pyfiglet==1.0.4
|
|
9
|
+
Requires-Dist: scikit-learn==1.9.0
|
|
10
|
+
Requires-Dist: uvicorn==0.50.2
|
|
11
|
+
Requires-Dist: pydantic==2.13.4
|
|
12
|
+
Requires-Dist: requests==2.34.2
|
|
13
|
+
Requires-Dist: torch==2.12.1
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# Job Description Skill Classifier [JobSeclect v0.10.2 & JobAnalyze 6k v1.0] (Multi-Label)
|
|
17
|
+
|
|
18
|
+
[](https://www.python.org/)
|
|
19
|
+
[](https://pytorch.org/)
|
|
20
|
+
[](https://scikit-learn.org/)
|
|
21
|
+
[](https://numpy.org/)
|
|
22
|
+
[](https://pandas.pydata.org/)
|
|
23
|
+
|
|
24
|
+
This project builds a lightweight text classification pipeline that predicts **multiple technical skills** from a job posting. Given a job description (optionally augmented with role and job type), the model outputs a ranked list of likely skills.
|
|
25
|
+
|
|
26
|
+
Installation:
|
|
27
|
+
|
|
28
|
+
- `pip install jobselect`
|
|
29
|
+
|
|
30
|
+
It uses:
|
|
31
|
+
|
|
32
|
+
- **TF-IDF** features over the combined text (job description + role + type)
|
|
33
|
+
- A **PyTorch feed-forward neural network** trained as a **multi-label** classifier
|
|
34
|
+
- **Per-skill thresholding** for evaluation and **top-k ranked probabilities** for inference
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## What it does
|
|
39
|
+
|
|
40
|
+
1. **Data preparation** (`model/prep/data_prep.py`)
|
|
41
|
+
- Reads cleaned job description data.
|
|
42
|
+
- Normalizes/repairs common skill typos (e.g., `tesnorflow/pytorch` → `tensorflow/pytorch`).
|
|
43
|
+
- Builds a **multi-hot** target vector of skills.
|
|
44
|
+
- Fits a **TF-IDF** vectorizer (with n-grams) and splits into train/test.
|
|
45
|
+
- Saves:
|
|
46
|
+
- `model/prep/prepared_data.npz` (TF-IDF arrays + labels + indexes)
|
|
47
|
+
- `model/prep/vectorizer.pkl` (fitted TF-IDF vectorizer)
|
|
48
|
+
- `model/prep/label_vocab.json` (skill label vocabulary)
|
|
49
|
+
|
|
50
|
+
2. **Model training** (`model/model.py`)
|
|
51
|
+
- Loads prepared TF-IDF arrays.
|
|
52
|
+
- Defines a simple **MLP**:
|
|
53
|
+
- Linear → ReLU → Dropout → Linear (one logit per skill)
|
|
54
|
+
- Trains with `BCEWithLogitsLoss` (multi-label setting).
|
|
55
|
+
- Saves:
|
|
56
|
+
- `model_out/skill_classifier.pt` (model weights)
|
|
57
|
+
- `model_out/training_history.json` (train/test loss curves)
|
|
58
|
+
|
|
59
|
+
3. **Evaluation** (`model/eval.py`)
|
|
60
|
+
- Loads the trained model.
|
|
61
|
+
- Applies a fixed sigmoid + threshold (**0.5**) to obtain binary skill predictions.
|
|
62
|
+
- Reports:
|
|
63
|
+
- Per-skill precision/recall/F1
|
|
64
|
+
- Micro-F1 and Macro-F1
|
|
65
|
+
- Compares against a simple baseline (frequency-driven / always-predict-most-frequent labels).
|
|
66
|
+
|
|
67
|
+
4. **Prediction / Inference** (`model/pred.py`)
|
|
68
|
+
- Loads the TF-IDF vectorizer and trained model.
|
|
69
|
+
- Creates TF-IDF features for the input text.
|
|
70
|
+
- Outputs the **top-k** skills by probability.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Use cases
|
|
75
|
+
|
|
76
|
+
- **Resume/job-post matching** (first-pass filtering of relevant skills)
|
|
77
|
+
- **Job taxonomy building** (discover recurring skills from postings)
|
|
78
|
+
- **Recruiting analytics** (aggregate predicted skill demand by seniority/role/type)
|
|
79
|
+
- **Prototyping multi-label NLP classifiers** (TF-IDF + MLP baseline)
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Project structure
|
|
84
|
+
|
|
85
|
+
```text
|
|
86
|
+
.
|
|
87
|
+
├─ data/
|
|
88
|
+
│ ├─ raw/
|
|
89
|
+
│ │ └─ Job_descriptions.csv # Raw input dataset
|
|
90
|
+
│ ├─ sample_data/
|
|
91
|
+
│ │ └─ test.txt # Example JD, Role and Type for testing
|
|
92
|
+
│ └─ clean/
|
|
93
|
+
│ ├─ cleaned_job_descriptions.csv # Cleaned master CSV
|
|
94
|
+
│ ├─ cleaned_job_descriptions_internships.csv
|
|
95
|
+
│ ├─ cleaned_job_descriptions_junior.csv
|
|
96
|
+
│ └─ cleaned_job_descriptions_senior.csv
|
|
97
|
+
│
|
|
98
|
+
├─ model/
|
|
99
|
+
│ ├─ prep/
|
|
100
|
+
│ │ ├─ data_prep.py # TF-IDF + multi-hot label creation + train/test split
|
|
101
|
+
│ │ ├─ sym_map.py # Synonym/phrase normalization map used during prep
|
|
102
|
+
│ │ ├─ vectorizer.pkl # Saved TF-IDF vectorizer (generated by data_prep)
|
|
103
|
+
│ │ ├─ prepared_data.npz # Saved arrays (generated by data_prep)
|
|
104
|
+
│ │ └─ label_vocab.json # Skill label vocabulary (generated by data_prep)
|
|
105
|
+
│ │
|
|
106
|
+
│ ├─ model.py # PyTorch multi-label classifier training
|
|
107
|
+
│ ├─ eval.py # Thresholded evaluation + F1 metrics + baseline comparison
|
|
108
|
+
│ └─ pred.py # Predict top-k skills for new text
|
|
109
|
+
│
|
|
110
|
+
├─ model_out/
|
|
111
|
+
│ ├─ skill_classifier.pt # Trained model weights (generated by model.py)
|
|
112
|
+
│ └─ training_history.json # Training loss history (generated by model.py)
|
|
113
|
+
│
|
|
114
|
+
├─ cli/
|
|
115
|
+
│ └─ jobselect.py # Rich terminal CLI for interactive prediction (uses model.pred.predict)
|
|
116
|
+
│
|
|
117
|
+
├─ test/
|
|
118
|
+
│ └─ test_model.py # Pytest checks expected artifacts exist in model_out/ and model/prep/
|
|
119
|
+
│
|
|
120
|
+
├─ notebooks/
|
|
121
|
+
│ ├─ 01_EDA.ipynb # Exploratory Data Analysis
|
|
122
|
+
│ └─ 02_Data_Engineering.ipynb # Data engineering / cleaning notes
|
|
123
|
+
│
|
|
124
|
+
├─ pipeline.py # Executes notebooks + training/eval steps in order
|
|
125
|
+
├─ pyproject.toml # Installs as a cli tool
|
|
126
|
+
├─ requirements.txt
|
|
127
|
+
└─ README.md
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Requirements
|
|
133
|
+
|
|
134
|
+
See `requirements.txt` for the exact dependencies.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Getting started
|
|
139
|
+
|
|
140
|
+
### 1) Clone and Install dependencies
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
git clone https://github.com/Ak47xdd/Job-Description-Analysis.git
|
|
144
|
+
pip install -r requirements.txt
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 2) Run data preparation (optional)
|
|
148
|
+
|
|
149
|
+
This builds the TF-IDF features and label vocabulary from the cleaned CSV.
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
python model/prep/data_prep.py
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Expected outputs:
|
|
156
|
+
|
|
157
|
+
- `model/prep/prepared_data.npz`
|
|
158
|
+
- `model/prep/vectorizer.pkl`
|
|
159
|
+
- `model/prep/label_vocab.json`
|
|
160
|
+
|
|
161
|
+
### 3) Train the model (optional)
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
python model/model.py
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Expected outputs:
|
|
168
|
+
|
|
169
|
+
- `model_out/skill_classifier.pt`
|
|
170
|
+
- `model_out/training_history.json`
|
|
171
|
+
|
|
172
|
+
### 4) Evaluate performance (optional)
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
python model/eval.py
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Outputs include:
|
|
179
|
+
|
|
180
|
+
- Per-skill metrics (precision/recall/F1)
|
|
181
|
+
- Micro-F1 and Macro-F1
|
|
182
|
+
- Baseline comparison
|
|
183
|
+
|
|
184
|
+
### 5) Predict skills for a new job description
|
|
185
|
+
|
|
186
|
+
#### Option A: Use Python function
|
|
187
|
+
|
|
188
|
+
`from model.pred import predict`
|
|
189
|
+
|
|
190
|
+
`data/sample_data/test.txt` contains an example job description inside. You can also call the `predict(job_desc, role=..., job_type=..., top_k=...)` function from Python.
|
|
191
|
+
|
|
192
|
+
#### Option B: Use the interactive CLI
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
python cli/jobselect.py
|
|
196
|
+
|
|
197
|
+
or
|
|
198
|
+
|
|
199
|
+
pip install jobselect
|
|
200
|
+
jobselect
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
The CLI prompts for job description, role, and job type, then prints the top skills with probabilities.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## How predictions work
|
|
208
|
+
|
|
209
|
+
- Text is concatenated as:
|
|
210
|
+
`"{job_desc} {role} {job_type}"`
|
|
211
|
+
- TF-IDF transforms text into a fixed-size vector
|
|
212
|
+
- The network outputs one logit per skill
|
|
213
|
+
- Sigmoid converts logits → probabilities
|
|
214
|
+
- Skills are ranked by probability and the top-k are returned
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Important implementation notes
|
|
219
|
+
|
|
220
|
+
- **Multi-label learning:** Each skill is treated independently (binary relevance via sigmoid + `BCEWithLogitsLoss`).
|
|
221
|
+
- **Evaluation threshold:** `model/eval.py` uses a fixed threshold of **0.5**. For production use, you may want per-label thresholds tuned on a validation set.
|
|
222
|
+
- **Dataset size:** The included notebooks and evaluation code suggest the dataset may be small; results can be limited by label frequency and data coverage.
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## New features / capabilities
|
|
227
|
+
|
|
228
|
+
- **Rich terminal CLI** (`cli/jobauto.py`) using `rich` + `pyfiglet` for interactive top-skill display.
|
|
229
|
+
- **Synonym/phrase normalization hook** (`model/prep/sym_map.py`) applied during data preparation.
|
|
230
|
+
- **Pipeline runner** (`pipeline.py`) to execute notebooks and training steps in sequence.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Customization ideas
|
|
235
|
+
|
|
236
|
+
- Improve text cleaning and skill normalization in `data_prep.py`
|
|
237
|
+
- Tune TF-IDF parameters (`max_features`, `ngram_range`, `min_df`)
|
|
238
|
+
- Replace the simple MLP with a stronger baseline (e.g., logistic regression on TF-IDF)
|
|
239
|
+
- Calibrate thresholds per label using validation data
|
|
240
|
+
- Add a CLI or web service endpoint for prediction
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## References / Inspiration
|
|
245
|
+
|
|
246
|
+
This repository follows a common pattern for multi-label NLP baselines:
|
|
247
|
+
TF-IDF features + a simple neural network + sigmoid-based multi-label outputs.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
cli/jobselect.py,sha256=zTCUz4h_z2TC43rFXeZhzQDlKnxqTj_3lxKj4bDVcYE,3767
|
|
2
|
+
jobselect-0.10.2.dist-info/licenses/LICENSE,sha256=GJCmsVy7uDWkdHQkyHmRY0-poSNCBYKMVkxsWu6qs7M,1089
|
|
3
|
+
model/Model.py,sha256=Q8sZ-zWd_DPiEtz4inD2pixU1FGu5dFDTIGEEfuNkdY,4017
|
|
4
|
+
model/__init__.py,sha256=FqtAGX76Pn5t68GeapJMUuKKg5lAan4ln8qdYHP7Joc,251
|
|
5
|
+
model/eval.py,sha256=K_79cWtKihtCoisiei60SitzxUvh-FClWIfYyvZzFDQ,3180
|
|
6
|
+
model/pred.py,sha256=xGIbg_5OPOEYXIGgTjJRYrJDg6MKp3WSdRBis2L5EUo,2403
|
|
7
|
+
model/prep/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
model/prep/data_prep.py,sha256=8pjiAeqPuUWWeHqsxY8jO5nFo7kuc4hrk1IeTM3fW-I,2748
|
|
9
|
+
model/prep/label_vocab.json,sha256=8BPIqMeYhM0qXQf4vt0DI3Z0cwfmSGEveJ9qOle_AwM,694
|
|
10
|
+
model/prep/prepared_data.npz,sha256=QFEpBmYszlijDf5wL-YhHtJjLsUHTtCaX0a-7U3qOfw,169500
|
|
11
|
+
model/prep/sym_map.py,sha256=1ToC-zE8s2jx_Zw8MxPYTzutam9pvcAgHfQpNmb1BF4,3645
|
|
12
|
+
model/prep/test.py,sha256=Fc4kaEPXFTkZNRwKCVZL9Azph0E1OIQVql3KqUTFERc,185
|
|
13
|
+
model/prep/vectorizer.pkl,sha256=ckdOX6V76Z3Y8RH2Uh_3M6r8nEebRGWfnSrnydf2tNQ,6583
|
|
14
|
+
model_out/skill_classifier.pt,sha256=Z4OvWe3pgJBSE5fkUTGsE29l4DXXxCUCfPkwfl5M160,28279
|
|
15
|
+
model_out/training_history.json,sha256=yCG5Kh36qUqEBDEU4dp2iK05VDQt57PqwGq4_q5D-Ws,11982
|
|
16
|
+
jobselect-0.10.2.dist-info/METADATA,sha256=LrY8WvtGvj3a4dAgA7BNC0XIDRmKUZEXNtzwekl4mk8,8958
|
|
17
|
+
jobselect-0.10.2.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
18
|
+
jobselect-0.10.2.dist-info/entry_points.txt,sha256=VFcqEBmL3e4hu6N0fi545Qc47m388RVywebfXXzVQOg,48
|
|
19
|
+
jobselect-0.10.2.dist-info/top_level.txt,sha256=SckdL5L6cqX90MVRKonPOb125RLJ-gkPgFgD2_5JQjk,20
|
|
20
|
+
jobselect-0.10.2.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Akshay_Babu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
model/Model.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""
|
|
2
|
+
model.py - DO NOT RUN THIS SCRIPT!
|
|
3
|
+
|
|
4
|
+
Run this script, prep/data_prep.py and the notebooks through pipeline.py
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import torch
|
|
9
|
+
import numpy as np
|
|
10
|
+
import torch.nn as nn
|
|
11
|
+
from torch.utils.data import Dataset, DataLoader
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
15
|
+
|
|
16
|
+
torch.manual_seed(42)
|
|
17
|
+
|
|
18
|
+
DATA_DIR = Path(__file__).resolve().parent / 'prep'
|
|
19
|
+
|
|
20
|
+
# Ensure required artifacts exist even when run from different CWDs
|
|
21
|
+
assert (DATA_DIR / 'prepared_data.npz').exists(), f"Missing {DATA_DIR / 'prepared_data.npz'}"
|
|
22
|
+
assert (DATA_DIR / 'label_vocab.json').exists(), f"Missing {DATA_DIR / 'label_vocab.json'}"
|
|
23
|
+
|
|
24
|
+
data = np.load(DATA_DIR / 'prepared_data.npz')
|
|
25
|
+
|
|
26
|
+
X_train = data['X_train']
|
|
27
|
+
X_test = data['X_test']
|
|
28
|
+
y_train = data['y_train']
|
|
29
|
+
y_test = data['y_test']
|
|
30
|
+
|
|
31
|
+
with open(DATA_DIR / 'label_vocab.json', encoding='utf-8') as f:
|
|
32
|
+
VOCAB = json.load(f)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
DIM = X_train.shape[1]
|
|
36
|
+
NUM_LABELS = len(VOCAB)
|
|
37
|
+
|
|
38
|
+
class SkillData(Dataset):
|
|
39
|
+
def __init__(self, X, y) -> torch.tensor:
|
|
40
|
+
self.X = torch.tensor(X, dtype=torch.float32)
|
|
41
|
+
self.y = torch.tensor(y, dtype=torch.float32)
|
|
42
|
+
|
|
43
|
+
def __len__(self) -> len:
|
|
44
|
+
return len(self.X)
|
|
45
|
+
|
|
46
|
+
def __getitem__(self, idx) -> int:
|
|
47
|
+
return self.X[idx], self.y[idx]
|
|
48
|
+
|
|
49
|
+
train_ds = SkillData(X_train, y_train)
|
|
50
|
+
test_ds = SkillData(X_test, y_test)
|
|
51
|
+
|
|
52
|
+
train_loader = DataLoader(train_ds, batch_size=64, shuffle=True)
|
|
53
|
+
test_loader = DataLoader(test_ds, batch_size=64, shuffle=False)
|
|
54
|
+
|
|
55
|
+
class SkillClassifier(nn.Module):
|
|
56
|
+
def __init__(self, input_dim, num_labels, hidden_dim=32, dropout=0.3) -> None:
|
|
57
|
+
super().__init__()
|
|
58
|
+
self.net = nn.Sequential(
|
|
59
|
+
nn.Linear(input_dim, hidden_dim),
|
|
60
|
+
nn.ReLU(),
|
|
61
|
+
nn.Dropout(dropout),
|
|
62
|
+
nn.Linear(hidden_dim, num_labels)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def forward(self, x) -> float:
|
|
66
|
+
return self.net(x)
|
|
67
|
+
|
|
68
|
+
jobanalyze_6k = SkillClassifier(DIM, NUM_LABELS)
|
|
69
|
+
|
|
70
|
+
total_params = sum(p.numel() for p in jobanalyze_6k.parameters())
|
|
71
|
+
trainable_params = sum(p.numel() for p in jobanalyze_6k.parameters() if p.requires_grad)
|
|
72
|
+
|
|
73
|
+
print(f"Total Parameters: {total_params:,}")
|
|
74
|
+
print(f"Trainable Parameters: {trainable_params:,}")
|
|
75
|
+
|
|
76
|
+
pos_counts = y_train.sum(axis=0)
|
|
77
|
+
neg_counts = len(y_train) - pos_counts
|
|
78
|
+
pos_weight = torch.tensor(neg_counts / (pos_counts + 1e-6), dtype=torch.float32)
|
|
79
|
+
pos_weight = torch.clamp(pos_weight, max=10.0)
|
|
80
|
+
|
|
81
|
+
criterion = nn.BCEWithLogitsLoss(pos_weight=pos_weight)
|
|
82
|
+
optimizer = torch.optim.Adam(jobanalyze_6k.parameters(), lr=1e-3, weight_decay=1e-4)
|
|
83
|
+
|
|
84
|
+
EPOCHS = 300
|
|
85
|
+
history = {'train_loss' : [], 'test_loss' : []}
|
|
86
|
+
|
|
87
|
+
for epoch in range(1, EPOCHS + 1):
|
|
88
|
+
jobanalyze_6k.train()
|
|
89
|
+
train_losses = []
|
|
90
|
+
for xb, yb in train_loader:
|
|
91
|
+
optimizer.zero_grad()
|
|
92
|
+
logits = jobanalyze_6k(xb)
|
|
93
|
+
loss = criterion(logits, yb)
|
|
94
|
+
loss.backward()
|
|
95
|
+
optimizer.step()
|
|
96
|
+
train_losses.append(loss.item())
|
|
97
|
+
|
|
98
|
+
jobanalyze_6k.eval()
|
|
99
|
+
test_losses = []
|
|
100
|
+
with torch.no_grad():
|
|
101
|
+
for xb, yb in test_loader:
|
|
102
|
+
logits = jobanalyze_6k(xb)
|
|
103
|
+
loss = criterion(logits, yb)
|
|
104
|
+
test_losses.append(loss.item())
|
|
105
|
+
|
|
106
|
+
train_loss = sum(train_losses) / len(train_losses)
|
|
107
|
+
test_loss = sum(test_losses) / len(test_losses)
|
|
108
|
+
history['train_loss'].append(train_loss)
|
|
109
|
+
history['test_loss'].append(test_loss)
|
|
110
|
+
|
|
111
|
+
if epoch % 10 == 0 or epoch == 1:
|
|
112
|
+
print(f"Epoch {epoch:3d} | train_loss {train_loss:.4f} | test_loss {test_loss:.4f}")
|
|
113
|
+
|
|
114
|
+
out_dir = REPO_ROOT / 'model_out'
|
|
115
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
116
|
+
|
|
117
|
+
torch.save(jobanalyze_6k.state_dict(), out_dir / 'skill_classifier.pt')
|
|
118
|
+
with open(out_dir / 'training_history.json', 'w') as f:
|
|
119
|
+
json.dump(history, f)
|
|
120
|
+
|
|
121
|
+
print("\n Saved Model")
|
|
122
|
+
print(f"Final train_loss : {history['train_loss'][-1]:.4f} | "
|
|
123
|
+
f"Final test_loss : {history['test_loss'][-1]:.4f}")
|
model/__init__.py
ADDED
model/eval.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""
|
|
2
|
+
eval.py - RUN after pipeline.py
|
|
3
|
+
|
|
4
|
+
evaluation metrics for the model
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import numpy as np
|
|
9
|
+
import torch
|
|
10
|
+
from sklearn.metrics import precision_recall_fscore_support, f1_score, accuracy_score
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
15
|
+
|
|
16
|
+
from Model import SkillClassifier
|
|
17
|
+
|
|
18
|
+
sys.path.insert(0, '.')
|
|
19
|
+
|
|
20
|
+
data = np.load('prep/prepared_data.npz')
|
|
21
|
+
|
|
22
|
+
X_train = data['X_train']
|
|
23
|
+
y_train = data['y_train']
|
|
24
|
+
X_test = data['X_test']
|
|
25
|
+
y_test = data['y_test']
|
|
26
|
+
|
|
27
|
+
with open('prep/label_vocab.json') as f:
|
|
28
|
+
VOCAB = json.load(f)
|
|
29
|
+
|
|
30
|
+
model = SkillClassifier(X_train.shape[1], len(VOCAB))
|
|
31
|
+
model.load_state_dict(torch.load(ROOT / 'model_out' / 'skill_classifier.pt', map_location='cpu'))
|
|
32
|
+
|
|
33
|
+
model.eval()
|
|
34
|
+
|
|
35
|
+
with torch.no_grad():
|
|
36
|
+
logits = model(torch.tensor(X_test, dtype=torch.float32))
|
|
37
|
+
probs = torch.sigmoid(logits).numpy()
|
|
38
|
+
|
|
39
|
+
THRESHOLD = 0.5
|
|
40
|
+
preds = (probs >= THRESHOLD).astype(int)
|
|
41
|
+
|
|
42
|
+
precision, recall, f1, support = precision_recall_fscore_support(
|
|
43
|
+
y_test,
|
|
44
|
+
preds,
|
|
45
|
+
average=None,
|
|
46
|
+
zero_division=0
|
|
47
|
+
)
|
|
48
|
+
micro_f1 = f1_score(y_test, preds, average='micro', zero_division=0)
|
|
49
|
+
macro_f1 = f1_score(y_test, preds, average='macro', zero_division=0)
|
|
50
|
+
|
|
51
|
+
print("\n Model Evaluation Metrics\n")
|
|
52
|
+
print(f"{'label':25s}{'support':10s}{'precision':12s}{'recall':10s}{'f1':6s}")
|
|
53
|
+
for lbl, p, r, f, s in zip(VOCAB, precision, recall, f1, support):
|
|
54
|
+
if s > 0:
|
|
55
|
+
print(f"{lbl:25s}{int(s):<10d}{p:<12.2f}{r:<10.2f}{f:.2f}")
|
|
56
|
+
|
|
57
|
+
print(f"\nMicro-F1: {micro_f1:.3f} | Macro-F1: {macro_f1:.3f}")
|
|
58
|
+
|
|
59
|
+
print("\n=== PER-LABEL ACCURACY ===")
|
|
60
|
+
print(f"{'label':25s}{'accuracy%':12s}{'support':10s}{'trap?':6s}")
|
|
61
|
+
|
|
62
|
+
is_right = 0
|
|
63
|
+
is_wrong = 0
|
|
64
|
+
|
|
65
|
+
for i, lbl in enumerate(VOCAB):
|
|
66
|
+
label_acc = accuracy_score(y_test[:, i], preds[:, i])
|
|
67
|
+
s = int(support[i])
|
|
68
|
+
|
|
69
|
+
always_zero_acc = 1.0 - (y_test[:, i].sum() / len(y_test))
|
|
70
|
+
is_trap = always_zero_acc >= label_acc - 0.01
|
|
71
|
+
|
|
72
|
+
trap_flag = "Wrong" if is_trap else "Right"
|
|
73
|
+
print(f"{lbl:25s}{label_acc*100:<12.1f}{s:<10d}{trap_flag}")
|
|
74
|
+
|
|
75
|
+
if trap_flag == 'Right':
|
|
76
|
+
is_right += 1
|
|
77
|
+
else:
|
|
78
|
+
is_wrong += 1
|
|
79
|
+
|
|
80
|
+
total_labels = is_right + is_wrong
|
|
81
|
+
|
|
82
|
+
print("Right : \n", is_right)
|
|
83
|
+
print("Wrong : \n", is_wrong)
|
|
84
|
+
|
|
85
|
+
print("Total Labels : \n", total_labels)
|
|
86
|
+
|
|
87
|
+
key_acc = (is_right / total_labels) * 100
|
|
88
|
+
|
|
89
|
+
print(f"Keyword Accuracy : {round(key_acc, 2)}%\n")
|
|
90
|
+
|
|
91
|
+
train_freq = y_train.mean(axis=0)
|
|
92
|
+
baseline_preds = np.tile((train_freq >= 0.3).astype(int), (len(y_test), 1))
|
|
93
|
+
|
|
94
|
+
baseline_micro_f1 = f1_score(y_test, baseline_preds, average='micro', zero_division=0)
|
|
95
|
+
baseline_macro_f1 = f1_score(y_test, baseline_preds, average='macro', zero_division=0)
|
|
96
|
+
|
|
97
|
+
print(f"\nBASELINE: \n")
|
|
98
|
+
baseline_labels = [lbl for lbl, f in zip(VOCAB, train_freq) if f >= 0.3]
|
|
99
|
+
print(f"Baseline always predicts: {baseline_labels}")
|
|
100
|
+
print(f"Baseline Micro-F1: {baseline_micro_f1:.3f} | Macro-F1: {baseline_macro_f1:.3f}")
|
|
101
|
+
|
|
102
|
+
print("\nVERDICT \n")
|
|
103
|
+
if micro_f1 > baseline_micro_f1 + 0.05:
|
|
104
|
+
print("Model meaningfully beats the naive baseline.")
|
|
105
|
+
else:
|
|
106
|
+
print("Model is roughly tied with (or worse than) just guessing the most")
|
model/pred.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pred Module
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import pickle
|
|
8
|
+
from typing import List, Tuple
|
|
9
|
+
import numpy as np
|
|
10
|
+
import torch
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
14
|
+
|
|
15
|
+
from torch import nn
|
|
16
|
+
|
|
17
|
+
class SkillClassifier(nn.Module):
|
|
18
|
+
def __init__(self, input_dim, num_labels, hidden_dim=32, dropout=0.3):
|
|
19
|
+
super().__init__()
|
|
20
|
+
self.net = nn.Sequential(
|
|
21
|
+
nn.Linear(input_dim, hidden_dim),
|
|
22
|
+
nn.ReLU(),
|
|
23
|
+
nn.Dropout(dropout),
|
|
24
|
+
nn.Linear(hidden_dim, num_labels),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
def forward(self, x):
|
|
28
|
+
return self.net(x)
|
|
29
|
+
|
|
30
|
+
def JobAnalyze_6k(job_desc: str = "", role: str = "", job_type: str = "", top_k: int = 50) -> List[Tuple[str, float]]:
|
|
31
|
+
"""
|
|
32
|
+
Predict top-k skills.
|
|
33
|
+
|
|
34
|
+
Current top-k = 48
|
|
35
|
+
"""
|
|
36
|
+
prep_dir = ROOT / "model" / "prep"
|
|
37
|
+
if not prep_dir.exists():
|
|
38
|
+
alt = ROOT / "prep"
|
|
39
|
+
if alt.exists():
|
|
40
|
+
prep_dir = alt
|
|
41
|
+
|
|
42
|
+
label_path = prep_dir / "label_vocab.json"
|
|
43
|
+
vector_path = prep_dir / "vectorizer.pkl"
|
|
44
|
+
weights_path = ROOT / "model_out" / "skill_classifier.pt"
|
|
45
|
+
|
|
46
|
+
if not label_path.exists():
|
|
47
|
+
raise FileNotFoundError(
|
|
48
|
+
f"Missing {label_path}. Make sure you ran data_prep and model training."
|
|
49
|
+
)
|
|
50
|
+
if not vector_path.exists():
|
|
51
|
+
raise FileNotFoundError(
|
|
52
|
+
f"Missing {vector_path}. Make sure you ran data_prep."
|
|
53
|
+
)
|
|
54
|
+
if not weights_path.exists():
|
|
55
|
+
raise FileNotFoundError(
|
|
56
|
+
f"Missing {weights_path}. Make sure you ran model/model.py."
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
with open(label_path, encoding="utf-8") as f:
|
|
60
|
+
label_vocab = json.load(f)
|
|
61
|
+
with open(vector_path, "rb") as f:
|
|
62
|
+
vectorizer = pickle.load(f)
|
|
63
|
+
|
|
64
|
+
input_dim = int(getattr(vectorizer, "vocabulary_", {}).__len__()) or vectorizer.transform([""]).shape[1]
|
|
65
|
+
|
|
66
|
+
model = SkillClassifier(input_dim, len(label_vocab))
|
|
67
|
+
model.load_state_dict(torch.load(weights_path, map_location="cpu"))
|
|
68
|
+
|
|
69
|
+
model.eval()
|
|
70
|
+
|
|
71
|
+
combined_text = f"{job_desc} {role} {job_type}"
|
|
72
|
+
X = vectorizer.transform([combined_text]).toarray().astype(np.float32)
|
|
73
|
+
|
|
74
|
+
with torch.no_grad():
|
|
75
|
+
logits = model(torch.tensor(X))
|
|
76
|
+
probs = torch.sigmoid(logits).numpy()[0]
|
|
77
|
+
|
|
78
|
+
ranked = sorted(zip(label_vocab, probs), key=lambda x: -x[1])
|
|
79
|
+
return ranked[:top_k]
|
|
80
|
+
|
|
81
|
+
|
model/prep/__init__.py
ADDED
|
File without changes
|
model/prep/data_prep.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""
|
|
2
|
+
data_prep.py - DO NOT RUN THIS SCRIPT!
|
|
3
|
+
|
|
4
|
+
Run this script, model/model.py and the notebooks through pipeline.py
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
import pandas as pd
|
|
12
|
+
import pickle
|
|
13
|
+
from collections import Counter
|
|
14
|
+
from sklearn.feature_extraction.text import TfidfVectorizer
|
|
15
|
+
from sklearn.model_selection import train_test_split
|
|
16
|
+
|
|
17
|
+
from sym_map import SYNONYM_MAP
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
df = pd.read_csv(r'C:\Portfolio-Projects\Job-Description-Analysis\data\clean\cleaned_job_descriptions.csv')
|
|
21
|
+
|
|
22
|
+
SKILLS_FIX = {
|
|
23
|
+
'tesnorflow/pytorch': 'tensorflow/pytorch',
|
|
24
|
+
'numpyhugging face': 'numpy',
|
|
25
|
+
'sytem design': 'system design',
|
|
26
|
+
'python. ml': 'ml',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
def normalizer(skills) -> list:
|
|
30
|
+
if pd.isna(skills):
|
|
31
|
+
return []
|
|
32
|
+
skill = [s.strip().lower() for s in skills.split(',') if s.strip()]
|
|
33
|
+
fixed = [SKILLS_FIX.get(s, s) for s in skill]
|
|
34
|
+
return list(dict.fromkeys(fixed))
|
|
35
|
+
|
|
36
|
+
df['skill_list'] = df['tech_skills'].apply(normalizer)
|
|
37
|
+
|
|
38
|
+
freq = Counter(s for lst in df['skill_list'] for s in lst)
|
|
39
|
+
VOCAB = sorted([s for s, c in freq.items() if c >= 2])
|
|
40
|
+
|
|
41
|
+
def encoder(skill_list) -> list:
|
|
42
|
+
return [1 if lbl in skill_list else 0 for lbl in VOCAB]
|
|
43
|
+
|
|
44
|
+
y = np.array([encoder(lst) for lst in df['skill_list']], dtype=np.float32)
|
|
45
|
+
|
|
46
|
+
def apply_synonyms(text: str) -> str:
|
|
47
|
+
text = text.lower()
|
|
48
|
+
for phrase, canonical in sorted(SYNONYM_MAP.items(), key=lambda x: -len(x[0])):
|
|
49
|
+
text = text.replace(phrase, canonical)
|
|
50
|
+
return text
|
|
51
|
+
|
|
52
|
+
jd_input = (
|
|
53
|
+
df['job_desc'].fillna('').apply(apply_synonyms) + ' ' +
|
|
54
|
+
df['role'].fillna('').str.lower() + ' ' +
|
|
55
|
+
df['type'].fillna('').str.lower()
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
vectorizer = TfidfVectorizer(
|
|
59
|
+
max_features=150,
|
|
60
|
+
stop_words='english',
|
|
61
|
+
ngram_range=(1, 2),
|
|
62
|
+
min_df=2,
|
|
63
|
+
)
|
|
64
|
+
X = vectorizer.fit_transform(jd_input).toarray().astype(np.float32)
|
|
65
|
+
|
|
66
|
+
X_train, X_test, y_train, y_test, idx_train, idx_test = train_test_split(
|
|
67
|
+
X, y, np.arange(len(df)),
|
|
68
|
+
test_size=0.2,
|
|
69
|
+
random_state=42
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
print(f"Dataset: {len(df)} rows | Vocab: {len(VOCAB)} labels | "
|
|
73
|
+
f"TF-IDF features: {X.shape[1]}")
|
|
74
|
+
print(f"Train: {len(X_train)} | Test: {len(X_test)}")
|
|
75
|
+
|
|
76
|
+
# Save artifacts relative to this script location so pipeline.py can be run from repo root
|
|
77
|
+
OUT_DIR = Path(__file__).resolve().parent
|
|
78
|
+
|
|
79
|
+
np.savez(
|
|
80
|
+
OUT_DIR / 'prepared_data.npz',
|
|
81
|
+
X_train=X_train,
|
|
82
|
+
X_test=X_test,
|
|
83
|
+
y_train=y_train,
|
|
84
|
+
y_test=y_test,
|
|
85
|
+
idx_train=idx_train,
|
|
86
|
+
idx_test=idx_test,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
with open(OUT_DIR / 'label_vocab.json', 'w') as f:
|
|
90
|
+
json.dump(VOCAB, f, indent=2)
|
|
91
|
+
|
|
92
|
+
with open(OUT_DIR / 'vectorizer.pkl', 'wb') as f:
|
|
93
|
+
pickle.dump(vectorizer, f)
|
|
94
|
+
|
|
95
|
+
print("Successfully Vectorized and Pickled Data")
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[
|
|
2
|
+
".net",
|
|
3
|
+
"agents",
|
|
4
|
+
"anthropic /openai sdks",
|
|
5
|
+
"apis",
|
|
6
|
+
"autogen",
|
|
7
|
+
"aws/azure",
|
|
8
|
+
"c",
|
|
9
|
+
"c#",
|
|
10
|
+
"c++",
|
|
11
|
+
"ci/cd",
|
|
12
|
+
"crewai",
|
|
13
|
+
"django",
|
|
14
|
+
"docker",
|
|
15
|
+
"feature engineering",
|
|
16
|
+
"full stack",
|
|
17
|
+
"genai",
|
|
18
|
+
"git",
|
|
19
|
+
"github",
|
|
20
|
+
"hugging face",
|
|
21
|
+
"java",
|
|
22
|
+
"javascript",
|
|
23
|
+
"kubernetes",
|
|
24
|
+
"langchain",
|
|
25
|
+
"langgraph",
|
|
26
|
+
"llamaindex",
|
|
27
|
+
"llms",
|
|
28
|
+
"mcp",
|
|
29
|
+
"ml",
|
|
30
|
+
"mlflow",
|
|
31
|
+
"mlops",
|
|
32
|
+
"model evaluation",
|
|
33
|
+
"model training",
|
|
34
|
+
"n8n",
|
|
35
|
+
"nlp",
|
|
36
|
+
"numpy",
|
|
37
|
+
"openai",
|
|
38
|
+
"pandas",
|
|
39
|
+
"powerbi",
|
|
40
|
+
"prompt engineering",
|
|
41
|
+
"python",
|
|
42
|
+
"r",
|
|
43
|
+
"rag",
|
|
44
|
+
"react",
|
|
45
|
+
"scikit-learn",
|
|
46
|
+
"sql",
|
|
47
|
+
"system design",
|
|
48
|
+
"tensorflow/pytorch",
|
|
49
|
+
"vectordb"
|
|
50
|
+
]
|
|
Binary file
|
model/prep/sym_map.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
SYNONYM_MAP = {
|
|
2
|
+
# docker
|
|
3
|
+
'containeriz': 'docker',
|
|
4
|
+
'containerize': 'docker',
|
|
5
|
+
|
|
6
|
+
# git
|
|
7
|
+
'version control': 'git',
|
|
8
|
+
'source control': 'git',
|
|
9
|
+
|
|
10
|
+
# github
|
|
11
|
+
'gitlab': 'github',
|
|
12
|
+
'bitbucket': 'github',
|
|
13
|
+
'pull request': 'github',
|
|
14
|
+
|
|
15
|
+
# ci/cd
|
|
16
|
+
'infrastructure-as-code': 'ci/cd',
|
|
17
|
+
'continuous integration': 'ci/cd',
|
|
18
|
+
'continuous deployment': 'ci/cd',
|
|
19
|
+
'continuous delivery': 'ci/cd',
|
|
20
|
+
'github actions': 'ci/cd',
|
|
21
|
+
'gitlab ci': 'ci/cd',
|
|
22
|
+
'jenkins': 'ci/cd',
|
|
23
|
+
'devops': 'ci/cd',
|
|
24
|
+
'cicd': 'ci/cd',
|
|
25
|
+
|
|
26
|
+
# genai
|
|
27
|
+
'generative ai': 'genai',
|
|
28
|
+
'frontier model': 'genai',
|
|
29
|
+
'foundation model': 'genai',
|
|
30
|
+
'gen ai': 'genai',
|
|
31
|
+
'gemini': 'genai',
|
|
32
|
+
'claude': 'genai',
|
|
33
|
+
'gpt': 'genai',
|
|
34
|
+
|
|
35
|
+
# agents
|
|
36
|
+
'multi-agent': 'agents',
|
|
37
|
+
'orchestrat': 'agents',
|
|
38
|
+
'agentic': 'agents',
|
|
39
|
+
'copilot': 'agents',
|
|
40
|
+
|
|
41
|
+
# mcp
|
|
42
|
+
'model context protocol': 'mcp',
|
|
43
|
+
'protocol wrapper': 'mcp',
|
|
44
|
+
'function calling': 'mcp',
|
|
45
|
+
'tool call': 'mcp',
|
|
46
|
+
|
|
47
|
+
# prompt engineering
|
|
48
|
+
'prompt engineer': 'prompt engineering',
|
|
49
|
+
'chain-of-thought': 'prompt engineering',
|
|
50
|
+
'system prompt': 'prompt engineering',
|
|
51
|
+
'few-shot': 'prompt engineering',
|
|
52
|
+
'zero-shot': 'prompt engineering',
|
|
53
|
+
'prompt design': 'prompt engineering',
|
|
54
|
+
|
|
55
|
+
# mlops
|
|
56
|
+
'experiment track': 'mlops',
|
|
57
|
+
'model monitor': 'mlops',
|
|
58
|
+
'model deploy': 'mlops',
|
|
59
|
+
'model registry': 'mlops',
|
|
60
|
+
'ml ops': 'mlops',
|
|
61
|
+
|
|
62
|
+
# kubernetes
|
|
63
|
+
'k8s': 'kubernetes',
|
|
64
|
+
'helm': 'kubernetes',
|
|
65
|
+
'eks': 'kubernetes',
|
|
66
|
+
'aks': 'kubernetes',
|
|
67
|
+
'gke': 'kubernetes',
|
|
68
|
+
|
|
69
|
+
# sql
|
|
70
|
+
'postgresql': 'sql',
|
|
71
|
+
'postgres': 'sql',
|
|
72
|
+
'pgvector': 'sql',
|
|
73
|
+
'snowflake': 'sql',
|
|
74
|
+
'databricks': 'sql',
|
|
75
|
+
'mysql': 'sql',
|
|
76
|
+
'nosql': 'sql',
|
|
77
|
+
|
|
78
|
+
# vectordb
|
|
79
|
+
'azure ai search': 'vectordb',
|
|
80
|
+
'vector database': 'vectordb',
|
|
81
|
+
'vector store': 'vectordb',
|
|
82
|
+
'embedding store': 'vectordb',
|
|
83
|
+
'pinecone': 'vectordb',
|
|
84
|
+
'chromadb': 'vectordb',
|
|
85
|
+
'weaviate': 'vectordb',
|
|
86
|
+
'qdrant': 'vectordb',
|
|
87
|
+
'faiss': 'vectordb',
|
|
88
|
+
'milvus': 'vectordb',
|
|
89
|
+
|
|
90
|
+
# nlp
|
|
91
|
+
'natural language': 'nlp',
|
|
92
|
+
'named entity': 'nlp',
|
|
93
|
+
'text classification': 'nlp',
|
|
94
|
+
|
|
95
|
+
# aws/azure
|
|
96
|
+
'amazon web services': 'aws/azure',
|
|
97
|
+
'azure openai': 'aws/azure',
|
|
98
|
+
'google cloud': 'aws/azure',
|
|
99
|
+
'ai foundry': 'aws/azure',
|
|
100
|
+
'sagemaker': 'aws/azure',
|
|
101
|
+
'bedrock': 'aws/azure',
|
|
102
|
+
'gcp': 'aws/azure',
|
|
103
|
+
'lambda': 'aws/azure',
|
|
104
|
+
'ec2': 'aws/azure',
|
|
105
|
+
's3': 'aws/azure',
|
|
106
|
+
}
|
model/prep/test.py
ADDED
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"train_loss": [0.9581862886746725, 0.9577147165934244, 0.9461615284283956, 0.9476661086082458, 0.9445720911026001, 0.9469820857048035, 0.9392802516619364, 0.9505228598912557, 0.950762927532196, 0.936366856098175, 0.9329907099405924, 0.9334798057874044, 0.9212138652801514, 0.916998823483785, 0.9186211029688517, 0.9199942350387573, 0.917285700639089, 0.9141116539637247, 0.9081712365150452, 0.9007992744445801, 0.8928503592809042, 0.8881315986315409, 0.8868852655092875, 0.8855883677800497, 0.8726210395495096, 0.8689012924830118, 0.8664668202400208, 0.8664715886116028, 0.858808716138204, 0.8502107262611389, 0.8514769673347473, 0.8443883856137594, 0.8354653914769491, 0.8419389526049296, 0.8347751895586649, 0.8293438951174418, 0.8314108848571777, 0.8242264787356058, 0.8150125741958618, 0.8148065209388733, 0.8208412528038025, 0.8038379549980164, 0.8083155353864034, 0.7979742487271627, 0.8024805585543314, 0.8121252655982971, 0.8099909623463949, 0.7862127621968588, 0.7826682726542155, 0.7992724378903707, 0.7828892866770426, 0.7740360895792643, 0.7762934565544128, 0.7719021240870158, 0.7708912889162699, 0.7710039615631104, 0.7656899094581604, 0.7630510330200195, 0.7667286992073059, 0.7553671598434448, 0.7476920684178671, 0.7605920235315958, 0.7457097371419271, 0.7439080874125162, 0.7375965317090353, 0.7373116413752238, 0.740321417649587, 0.7358438372612, 0.7284310857454935, 0.7350131670633951, 0.7183190186818441, 0.7204438249270121, 0.7150562802950541, 0.7068103353182474, 0.7275595664978027, 0.7266760071118673, 0.7168972492218018, 0.7118367950121561, 0.7136472860972086, 0.7103650569915771, 0.6976963678995768, 0.7045106093088785, 0.6940693457921346, 0.687540332476298, 0.698395570119222, 0.6792084574699402, 0.6907051205635071, 0.6839434107144674, 0.6806370615959167, 0.684864858786265, 0.6747912764549255, 0.6723761359850565, 0.6862576802571615, 0.6673139532407125, 0.6679207682609558, 0.6639275352160136, 0.6590360601743063, 0.6575275858243307, 0.6648958325386047, 0.6717456380526224, 0.6632175445556641, 0.6605779131253561, 0.6675432920455933, 0.6592809955279032, 0.6564900676409403, 0.6533124645551046, 0.6523601611455282, 0.6453654567400614, 0.6494061549504598, 0.6490419705708822, 0.6392372449239095, 0.6579065521558126, 0.6391883293787638, 0.6371342341105143, 0.6379399100939432, 0.6424667636553446, 0.6355656782786051, 0.6250395576159159, 0.6172210375467936, 0.6375317970911661, 0.6317365964253744, 0.6250279347101847, 0.6232229272524515, 0.6375943819681803, 0.6228700478871664, 0.6193240483601888, 0.6193757057189941, 0.6122732361157736, 0.6391211549441019, 0.6141518751780192, 0.6103077928225199, 0.6171488563219706, 0.6174906293551127, 0.6065569917360941, 0.6059826016426086, 0.6166791915893555, 0.5973984400431315, 0.6088824073473612, 0.6073964834213257, 0.6005466183026632, 0.6013992230097452, 0.6125759681065878, 0.6079150239626566, 0.5891634225845337, 0.6008321046829224, 0.5972617467244467, 0.5964677532513937, 0.5844391783078512, 0.5972137252489725, 0.5894015232721964, 0.6028069257736206, 0.592693050702413, 0.5975406964619955, 0.5897817015647888, 0.58717147509257, 0.5766614874204, 0.5890741149584452, 0.5749748945236206, 0.5733896891276041, 0.5756001273790995, 0.5863603154818217, 0.5884929100672404, 0.578599731127421, 0.5891632636388143, 0.5758164326349894, 0.5684518814086914, 0.5712788701057434, 0.5735013484954834, 0.5750335256258646, 0.5649053851763407, 0.5641095836957296, 0.5632531642913818, 0.5593478083610535, 0.5687779784202576, 0.565412183602651, 0.566339115301768, 0.5505285859107971, 0.5565174619356791, 0.556056539217631, 0.5571393171946207, 0.5519521633783976, 0.5626179178555807, 0.5540618896484375, 0.5583128929138184, 0.5578981041908264, 0.5443036357561747, 0.5576624075571696, 0.5645721753438314, 0.5607338547706604, 0.5481768647829691, 0.534951368967692, 0.5381232698758444, 0.5366830428441366, 0.5464838743209839, 0.5413755575815836, 0.5512260794639587, 0.543988823890686, 0.5405274629592896, 0.5418429176012675, 0.5398401021957397, 0.5361861785252889, 0.5481286843617758, 0.5376873215039571, 0.5290207862854004, 0.5361277262369791, 0.5333669185638428, 0.5322141846021017, 0.5257742206255595, 0.5330172181129456, 0.5436073342959086, 0.5269099473953247, 0.5185351669788361, 0.5202850103378296, 0.5281335214773814, 0.5258443653583527, 0.5325279434521993, 0.5321405132611593, 0.5215916931629181, 0.5315477848052979, 0.5168627699216207, 0.5142837762832642, 0.51143017411232, 0.5149370034535726, 0.5177168250083923, 0.5222222010294596, 0.5185661117235819, 0.5199925303459167, 0.5164631207784017, 0.522129108508428, 0.5196901758511862, 0.5083784560362498, 0.5147261520226797, 0.5110023319721222, 0.5160118043422699, 0.5126267770926157, 0.5193240543206533, 0.5121001203854879, 0.5038588245709738, 0.4998914698759715, 0.5149586896101633, 0.5078332523504893, 0.513956199089686, 0.49984930952390033, 0.4997527798016866, 0.5031786362330118, 0.4964219530423482, 0.4983686606089274, 0.49576592445373535, 0.49676430225372314, 0.5016859471797943, 0.5083380540211996, 0.49582229057947796, 0.5047439734141032, 0.5027091801166534, 0.49981353680292767, 0.49778684973716736, 0.4949717919031779, 0.49530373016993207, 0.48861031730969745, 0.49632762869199115, 0.4943715234597524, 0.48982996741930646, 0.4938281178474426, 0.48919827739397687, 0.486867219209671, 0.4842636088530223, 0.49763620893160504, 0.49755287170410156, 0.49821798006693524, 0.5028306345144907, 0.4809292455514272, 0.4883957803249359, 0.46985554695129395, 0.47858189543088275, 0.4825609028339386, 0.4949019451936086, 0.47141284743944806, 0.482805867989858, 0.4782057503859202, 0.4866550862789154, 0.4738066792488098, 0.4699362615744273, 0.47626463572184247, 0.47252171238263446, 0.48921040693918866, 0.4608401656150818, 0.4610830048720042, 0.4707030753294627, 0.4640289843082428, 0.4737945298353831, 0.4661054213841756, 0.47239769498507184, 0.47523924708366394, 0.47379793723424274, 0.4777321020762126, 0.4689226845900218, 0.4861786663532257, 0.4632717768351237, 0.4659041265646617, 0.46325353781382245], "test_loss": [0.9298608899116516, 0.9286201000213623, 0.927367091178894, 0.9260708093643188, 0.9246230125427246, 0.9229950904846191, 0.9211652278900146, 0.9191482067108154, 0.9168845415115356, 0.9144287705421448, 0.9117457270622253, 0.9087604284286499, 0.9055784940719604, 0.9021115899085999, 0.8984742164611816, 0.8946555256843567, 0.8906906247138977, 0.8866006731987, 0.8824410438537598, 0.8782087564468384, 0.8738958239555359, 0.8695005774497986, 0.8651221394538879, 0.8607474565505981, 0.8565287590026855, 0.8524606227874756, 0.84834885597229, 0.8445054888725281, 0.8408474922180176, 0.8373579978942871, 0.8339387774467468, 0.8307586312294006, 0.8276570439338684, 0.8246919512748718, 0.8218910694122314, 0.8191938400268555, 0.8164622783660889, 0.814067006111145, 0.8115493655204773, 0.8089558482170105, 0.8065310716629028, 0.8042981624603271, 0.8021987676620483, 0.8000945448875427, 0.7980659008026123, 0.795944094657898, 0.7939944863319397, 0.7918947339057922, 0.7897019386291504, 0.787562370300293, 0.7854297161102295, 0.7832334637641907, 0.7810196876525879, 0.7788534760475159, 0.7766239643096924, 0.7744990587234497, 0.7721480131149292, 0.769902229309082, 0.7678605914115906, 0.7658149003982544, 0.7637181282043457, 0.7617011070251465, 0.7596434950828552, 0.7575833797454834, 0.7554796934127808, 0.7535147070884705, 0.7515978217124939, 0.7497515082359314, 0.7479531764984131, 0.7462489604949951, 0.744451105594635, 0.7426475882530212, 0.7408341765403748, 0.7389852404594421, 0.7371066808700562, 0.735320508480072, 0.7336044907569885, 0.7319872379302979, 0.7304288148880005, 0.728927731513977, 0.7274287343025208, 0.7258942723274231, 0.7242944836616516, 0.7227673530578613, 0.7212544083595276, 0.719752311706543, 0.718218982219696, 0.7167436480522156, 0.7152625322341919, 0.7137777805328369, 0.712334394454956, 0.7109388113021851, 0.7095797061920166, 0.7082014083862305, 0.7068546414375305, 0.7055162191390991, 0.7040970325469971, 0.7026676535606384, 0.7014964818954468, 0.700282633304596, 0.6990247964859009, 0.697831928730011, 0.6965664029121399, 0.6952717304229736, 0.694158136844635, 0.692780613899231, 0.6914969682693481, 0.6900702714920044, 0.6887779235839844, 0.6874729990959167, 0.6863412857055664, 0.6850231885910034, 0.6836751699447632, 0.6825655698776245, 0.6815938353538513, 0.6807154417037964, 0.6797965168952942, 0.678778886795044, 0.6780586242675781, 0.6773285269737244, 0.6765359044075012, 0.6756646037101746, 0.6747848987579346, 0.6738333106040955, 0.6727617383003235, 0.6718459725379944, 0.6710072159767151, 0.6701698303222656, 0.669294536113739, 0.668034017086029, 0.6670897603034973, 0.6662132740020752, 0.6653864979743958, 0.6644272208213806, 0.6636298894882202, 0.6626255512237549, 0.6615933775901794, 0.6606618762016296, 0.6598576307296753, 0.6592516303062439, 0.6586002707481384, 0.6577969193458557, 0.6573529243469238, 0.6567758917808533, 0.6562798619270325, 0.6556363701820374, 0.6548494100570679, 0.6541464924812317, 0.6534116268157959, 0.6525095701217651, 0.6518228054046631, 0.6511184573173523, 0.6504809856414795, 0.6499512791633606, 0.6494218111038208, 0.6489140391349792, 0.6484061479568481, 0.647731363773346, 0.6472024917602539, 0.6465904712677002, 0.6458643674850464, 0.6451480388641357, 0.6448190808296204, 0.6442693471908569, 0.6437927484512329, 0.6435742974281311, 0.6432029008865356, 0.6428396701812744, 0.6423819065093994, 0.6419949531555176, 0.641595721244812, 0.6412363648414612, 0.6409658193588257, 0.640618085861206, 0.640298068523407, 0.6398538947105408, 0.6394216418266296, 0.6389840841293335, 0.638550877571106, 0.6381618976593018, 0.6377515196800232, 0.6371808052062988, 0.6365807056427002, 0.6360141038894653, 0.6352731585502625, 0.6347209215164185, 0.634213387966156, 0.6339948177337646, 0.6337099671363831, 0.6333750486373901, 0.6329785585403442, 0.6326598525047302, 0.6324653625488281, 0.6320960521697998, 0.6318709254264832, 0.6315382719039917, 0.6311991810798645, 0.6310821771621704, 0.6310263276100159, 0.6309400200843811, 0.6306219100952148, 0.6301679611206055, 0.6298125386238098, 0.629535436630249, 0.6295410990715027, 0.6294113397598267, 0.6291530132293701, 0.6289764046669006, 0.6289737224578857, 0.6288029551506042, 0.6284852623939514, 0.628138542175293, 0.6278849840164185, 0.627629280090332, 0.627454936504364, 0.6273459196090698, 0.6272515058517456, 0.6273605823516846, 0.6275609731674194, 0.627708375453949, 0.6276419758796692, 0.6275895833969116, 0.6272011399269104, 0.6267870664596558, 0.6264842748641968, 0.6262391209602356, 0.6260331273078918, 0.6258324980735779, 0.6254996061325073, 0.6251218914985657, 0.6251339316368103, 0.6250628232955933, 0.625011146068573, 0.6249058842658997, 0.6247864961624146, 0.6243884563446045, 0.6242798566818237, 0.6242759823799133, 0.6246921420097351, 0.6247364282608032, 0.6242496371269226, 0.6238921284675598, 0.623569667339325, 0.6234216094017029, 0.6233934164047241, 0.6234481334686279, 0.62331622838974, 0.6231942772865295, 0.6232541799545288, 0.6235364079475403, 0.6235981583595276, 0.6234050989151001, 0.6228485703468323, 0.6221699714660645, 0.6217104196548462, 0.6215617656707764, 0.6215261816978455, 0.6216001510620117, 0.6215519905090332, 0.6216685771942139, 0.6218727827072144, 0.6219251155853271, 0.6217672824859619, 0.6215541362762451, 0.6214060187339783, 0.6216864585876465, 0.621939480304718, 0.621992290019989, 0.6219083666801453, 0.6218449473381042, 0.6216311454772949, 0.6216148138046265, 0.6216328144073486, 0.6220079064369202, 0.6222974061965942, 0.6223437190055847, 0.6223273873329163, 0.6220245361328125, 0.6218318343162537, 0.6212388873100281, 0.6207136511802673, 0.6204036474227905, 0.6206014752388, 0.6209258437156677, 0.6211738586425781, 0.6210983395576477, 0.6211971044540405, 0.6213352084159851, 0.6217947602272034, 0.6220757365226746, 0.6221537590026855, 0.6221935153007507, 0.6221125721931458, 0.6220228672027588, 0.6217535138130188, 0.6214527487754822, 0.6209364533424377, 0.6206910610198975, 0.6206433773040771, 0.6208334565162659]}
|