File size: 6,413 Bytes
aa453bc
 
a7102c6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
aa31414
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
---
license: mit
language:
- en
base_model:
- JobSelect/JobAnalyze_6k
new_version: JobSelect/JobAnalyze_6k
pipeline_tag: text-classification
tags:
- NLP
- TF-IDF
- MLP
- MCP
- API
- CLI
- JobSelect
- JobAnalyze
---

# JobAnalyze 6k v1.0 Skill Classifier

A lightweight **multi-label** PyTorch model that predicts a fixed set of **skills/keywords** from a job description plus a provided **role** and **job type**.

This README documents the exact artifacts and behavior implemented in:

- `model/model.py` (training definition)
- `model/pred.py` (inference wrapper)
- `model/prep/data_prep.py` (feature creation)

Sample runs and evaluation numbers referenced from:

- `data/sample_data/test.txt`
- `data/sample_data/eval.txt`
- `data/sample_data/cli.txt`

---

## Model summary

### Task type

- **Multi-label classification** (each skill is predicted independently)

### Inputs

- `job_desc` (job description text)
- `role` (free text, appended)
- `job_type` / `type` (free text, appended)

These are concatenated during prediction as:

```text
{job_desc} {role} {job_type}
```

### Features

- TF-IDF features created by `model/prep/data_prep.py` using:
  - `TfidfVectorizer(max_features=150, stop_words='english', ngram_range=(1, 2), min_df=2)`
- Artifacts:
  - `model/prep/vectorizer.pkl`
  - `model/prep/label_vocab.json`
  - `model/prep/prepared_data.npz`

### Labels

- A fixed vocabulary of skills/keywords stored in `model/prep/label_vocab.json`
- In training artifacts:
  - `NUM_LABELS = len(VOCAB)`
  - In sample data: **48 Keywords/labels**

### Network architecture (the “6k / 6000 parameter” model)

`model/model.py` defines a small feed-forward network:

- Linear(input_dim → hidden_dim=32)
- ReLU
- Dropout(p=0.3)
- Linear(hidden_dim=32 → num_labels)

The file name and training printout refer to the total parameter count computed at runtime.

---

## Training (model/model.py)

**Do not run `model/model.py` directly for day-to-day use.** It is designed to be executed via `pipeline.py` (and/or the notebooks).

Training uses:

- Loss: `torch.nn.BCEWithLogitsLoss(pos_weight=pos_weight)`
  - `pos_weight` is computed per-label from the training set (class imbalance handling)
  - Clamped with `max=10.0`
- Optimizer: `Adam(lr=1e-3, weight_decay=1e-4)`
- Epochs: `300`

### Outputs (saved artifacts)

At the end of training, the following are written to `model_out/`:

- `model_out/skill_classifier.pt`
- `model_out/training_history.json`

The test suite asserts these exist (see `test/test_model.py`).

---

## Inference (model/pred.py)

`model/pred.py` exposes a prediction helper:

```python
JobAnalyze_6k(job_desc, role="", job_type="", top_k=50) -> List[(str, float)]
```

Key behavior:

- Loads artifacts from repo-relative paths:
  - `model/prep/label_vocab.json`
  - `model/prep/vectorizer.pkl`
  - `model_out/skill_classifier.pt`
- Vectorizes the concatenated text with TF-IDF and produces logits through the trained network.
- Converts logits to probabilities with `sigmoid`.
- Ranks all labels by probability descending and returns the top results.

> Note: `top_k` is capped by the number of labels effectively returned from the ranked list (in the code, slicing is applied directly).

---

## Evaluation snapshot (from data/sample_data)

### Micro/Macro F1

From `data/sample_data/eval.txt`:

- **Micro-F1: 0.624**
- **Macro-F1: 0.420**

### Baseline comparison

Also from `data/sample_data/eval.txt`, baseline always predicts a fixed set of frequent labels:

- Baseline Micro-F1: **0.538**
- Baseline Macro-F1: **0.152**

The evaluation script prints:

> “Model meaningfully beats the naive baseline.”

### Per-label and “trap” analysis

The evaluation output includes per-label precision/recall/F1 and an additional heuristic:

- “trap?” flags labels where the model does not perform better than a trivial always-zero expectation (within a small tolerance).

From `data/sample_data/eval.txt`:

- Right: **15**
- Wrong: **33**
- Total labels: **48**
- Keyword Accuracy: **31.25%**

---

## Example: single inference test (data/sample_data/test.txt)

`data/sample_data/test.txt` contains a job description plus:

- Role: **AI Engineer**
- Type: **Junior**

It lists **15 keys to be predicted**, including:

- Python, LLMs, LangGraph, MCP, GenAI, VectorDB, SQL, APIs, Docker, Agents, Github, CI/CD, Git, AWS/Azure, Prompt Engineering

Reported performance:

- Accuracy (recall): **93.34%** (14/15)

---

## Example: CLI output format (data/sample_data/cli.txt)

`cli/jobauto.py` uses `JobAnalyze_6k` from `model.pred` and prints:

- The job description provided
- Role and Type
- A ranked “TOP Skills” list as `{label} {probability}` plus a text bar

From `data/sample_data/cli.txt`, the top-ranked skills include (with example probabilities):

- apis ~0.78
- langgraph ~0.78
- vectordb ~0.76
- mcp ~0.75
- langchain ~0.74
- rag ~0.72

---

## Data prep artifacts (model/prep/data_prep.py)

`model/prep/data_prep.py` creates all required inputs for training and inference.

It:

1. Loads cleaned job description dataset
2. Normalizes and fixes known skill spelling issues (`SKILLS_FIX`)
3. Applies synonym replacement using `model/prep/sym_map.py`
4. Builds a multi-hot label vector for the vocabulary
5. Vectorizes job text with TF-IDF
6. Splits into train/test and saves:
   - `prepared_data.npz`
   - `label_vocab.json`
   - `vectorizer.pkl`

---

## Reproducibility / how to use

### Requirements (high level)

See `requirements.txt` and `pyproject.toml`.

### Minimum artifacts required for prediction

For `model/pred.py` to work, these must exist:

- `model/prep/label_vocab.json`
- `model/prep/vectorizer.pkl`
- `model_out/skill_classifier.pt`

If any are missing, `model/pred.py` raises a `FileNotFoundError` with guidance.

### Recommended workflow

- Run the full pipeline via `pipeline.py` (which coordinates data prep + training).
- Use `cli/jobauto.py` for interactive predictions.

---

## Notes on “JobAnalyze 6k”

Despite the “6k / 6000 parameter” naming, the true parameter count is computed dynamically in `model/model.py`.

The implementation is intentionally small:

- TF-IDF input features
- 1 hidden layer with 32 units
- Multi-label BCE loss with class imbalance reweighting

This design keeps inference fast and model size small while still providing meaningful gains over the naive baseline (see evaluation snapshot above).