Add CoreML vacancy section classifier (mlpackage + tokenizer + bilingual card)
Browse files
README.md
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
language:
|
| 3 |
+
- ru
|
| 4 |
+
license: mit
|
| 5 |
+
library_name: coreml
|
| 6 |
+
pipeline_tag: text-classification
|
| 7 |
+
base_model: cointegrated/rubert-tiny
|
| 8 |
+
base_model_relation: finetune
|
| 9 |
+
inference: false
|
| 10 |
+
tags:
|
| 11 |
+
- coreml
|
| 12 |
+
- core-ml
|
| 13 |
+
- text-classification
|
| 14 |
+
- russian
|
| 15 |
+
- rubert
|
| 16 |
+
- rubert-tiny
|
| 17 |
+
- bert
|
| 18 |
+
- ane
|
| 19 |
+
- apple-neural-engine
|
| 20 |
+
- apple-silicon
|
| 21 |
+
- on-device
|
| 22 |
+
- vacancy
|
| 23 |
+
- hr
|
| 24 |
+
- job-postings
|
| 25 |
+
- sequence-classification
|
| 26 |
+
---
|
| 27 |
+
|
| 28 |
+
# rubert-tiny · Vacancy Section Classifier · CoreML
|
| 29 |
+
|
| 30 |
+
On-device CoreML (Apple Neural Engine) classifier that labels fragments of
|
| 31 |
+
Russian-language job postings into 5 structural sections. Built on
|
| 32 |
+
[`cointegrated/rubert-tiny`](https://huggingface.co/cointegrated/rubert-tiny)
|
| 33 |
+
(11.9M params), exported to a `float16` `.mlpackage` for Apple Silicon.
|
| 34 |
+
|
| 35 |
+
> 🇬🇧 English card below · 🇷🇺 Русская версия ниже ([перейти](#-русская-версия))
|
| 36 |
+
|
| 37 |
+
---
|
| 38 |
+
|
| 39 |
+
## 🇬🇧 English
|
| 40 |
+
|
| 41 |
+
### What it does
|
| 42 |
+
|
| 43 |
+
Given one fragment of a Russian vacancy description, the model predicts which of
|
| 44 |
+
5 sections it belongs to:
|
| 45 |
+
|
| 46 |
+
| id | label | meaning |
|
| 47 |
+
|----|----------------------|------------------------------------------------------|
|
| 48 |
+
| 0 | `responsibilities` | what the employee will do (задачи / обязанности) |
|
| 49 |
+
| 1 | `requirements` | what the candidate must have (требования / навыки) |
|
| 50 |
+
| 2 | `terms` | conditions of employment (условия / зарплата / ДМС) |
|
| 51 |
+
| 3 | `notes` | meta / "about the company" / soft boilerplate |
|
| 52 |
+
| 4 | `junk` | non-informative noise (routed out of structured data) |
|
| 53 |
+
|
| 54 |
+
It is the structured-extraction stage of an HH.ru vacancy-scouting pipeline,
|
| 55 |
+
where it replaced a heavier Qwen-embedding + cosine + rerank approach at
|
| 56 |
+
~1–10 ms per vacancy on Apple Silicon.
|
| 57 |
+
|
| 58 |
+
### Artifact
|
| 59 |
+
|
| 60 |
+
This repository ships the **CoreML artifact only** (no PyTorch weights):
|
| 61 |
+
|
| 62 |
+
- `section_classifier.mlpackage` — `float16`, `ComputeUnit.ALL` (ANE-eligible),
|
| 63 |
+
minimum deployment target macOS 13.
|
| 64 |
+
- `tokenizer.json`, `tokenizer_config.json` — the matching BERT WordPiece
|
| 65 |
+
tokenizer (vocab 29 564). **Required** — the `.mlpackage` consumes token ids,
|
| 66 |
+
not raw text.
|
| 67 |
+
|
| 68 |
+
#### CoreML I/O signature
|
| 69 |
+
|
| 70 |
+
| name | dtype | shape | notes |
|
| 71 |
+
|------------------|--------|---------|-----------------------------------|
|
| 72 |
+
| `input_ids` | int32 | [1, 128] | padded to `max_length=128` |
|
| 73 |
+
| `attention_mask` | int32 | [1, 128] | 1 = real token, 0 = pad |
|
| 74 |
+
| `token_type_ids` | int32 | [1, 128] | all zeros (single segment) |
|
| 75 |
+
| **output** `logits` | float32 | [1, 5] | un-normalized; `argmax` → class |
|
| 76 |
+
|
| 77 |
+
`max_seq_len = 128` and the label names are embedded in the model's
|
| 78 |
+
`user_defined_metadata`.
|
| 79 |
+
|
| 80 |
+
### Metrics
|
| 81 |
+
|
| 82 |
+
The numbers below were measured on the **source PyTorch model**. The CoreML
|
| 83 |
+
export was then verified at **100% argmax parity** against that source on a
|
| 84 |
+
held-out set of probe texts (max absolute logit difference `0.0026`, expected
|
| 85 |
+
for `float16`), so they carry over to this artifact.
|
| 86 |
+
|
| 87 |
+
**Headline — golden-281 (human-labeled, held-out):**
|
| 88 |
+
|
| 89 |
+
| metric | value |
|
| 90 |
+
|----------------------------------------------|---------------------|
|
| 91 |
+
| Content accuracy (4 meaningful classes) | **76.5%** (176/230) |
|
| 92 |
+
| Full 5-class accuracy (incl. junk routing) | **68.7%** |
|
| 93 |
+
| Junk recall (noise correctly routed out) | **33.3%** (17/51) |
|
| 94 |
+
|
| 95 |
+
This is the metric to trust: 281 fragments labeled by a human, never seen in
|
| 96 |
+
training.
|
| 97 |
+
|
| 98 |
+
<details>
|
| 99 |
+
<summary>In-domain test split (circular — NOT the headline)</summary>
|
| 100 |
+
|
| 101 |
+
Evaluated on the internal test split, which shares the same Claude Opus
|
| 102 |
+
relabeled distribution as the training data, so it overstates real-world
|
| 103 |
+
performance. Reported for monitoring only:
|
| 104 |
+
|
| 105 |
+
| metric | value |
|
| 106 |
+
|------------------|--------|
|
| 107 |
+
| Accuracy | 89.3% |
|
| 108 |
+
| Macro-F1 | 86.9% |
|
| 109 |
+
|
| 110 |
+
Per-class F1 (in-domain): responsibilities 0.789 · requirements 0.760 ·
|
| 111 |
+
terms 0.795 · notes 0.684 · **junk 0.374**.
|
| 112 |
+
</details>
|
| 113 |
+
|
| 114 |
+
### Usage (Python · coremltools)
|
| 115 |
+
|
| 116 |
+
```python
|
| 117 |
+
import numpy as np
|
| 118 |
+
import coremltools as ct
|
| 119 |
+
from transformers import AutoTokenizer
|
| 120 |
+
|
| 121 |
+
REPO = "russian-oracle/rubert-tiny-vacancy-section-classifier-coreml"
|
| 122 |
+
LABELS = ["responsibilities", "requirements", "terms", "notes", "junk"]
|
| 123 |
+
|
| 124 |
+
tok = AutoTokenizer.from_pretrained(REPO) # tokenizer.json shipped here
|
| 125 |
+
mlmodel = ct.models.MLModel("section_classifier.mlpackage") # hf download ... locally
|
| 126 |
+
|
| 127 |
+
text = "Опыт работы с Python от 3 лет, знание Django и PostgreSQL."
|
| 128 |
+
enc = tok(text, return_tensors="np", padding="max_length",
|
| 129 |
+
truncation=True, max_length=128)
|
| 130 |
+
ids = enc["input_ids"].astype(np.int32)
|
| 131 |
+
out = mlmodel.predict({
|
| 132 |
+
"input_ids": ids,
|
| 133 |
+
"attention_mask": enc["attention_mask"].astype(np.int32),
|
| 134 |
+
"token_type_ids": enc.get("token_type_ids", np.zeros_like(ids)).astype(np.int32),
|
| 135 |
+
})
|
| 136 |
+
logits = np.asarray(out["logits"]).reshape(-1)
|
| 137 |
+
print(LABELS[int(logits.argmax())]) # → requirements
|
| 138 |
+
# probabilities: softmax(logits)
|
| 139 |
+
```
|
| 140 |
+
|
| 141 |
+
> `coremltools` needs its native bindings, which ship only with certain CPython
|
| 142 |
+
> builds (a 3.12 wheel works reliably). Run prediction under such an interpreter.
|
| 143 |
+
|
| 144 |
+
**Recommended aggregation (how it is used in production):** split a full
|
| 145 |
+
description into sentence-level chunks (e.g. `razdel` + newline), classify each,
|
| 146 |
+
take the majority label per chunk; `junk` fragments are routed to an "orphans"
|
| 147 |
+
bucket instead of the structured output.
|
| 148 |
+
|
| 149 |
+
### Usage (Swift · sketch)
|
| 150 |
+
|
| 151 |
+
```swift
|
| 152 |
+
import CoreML
|
| 153 |
+
|
| 154 |
+
let model = try MLModel(contentsOf: url) // section_classifier.mlpackage (compiled)
|
| 155 |
+
// Provide three [1,128] MLMultiArray(.int32) inputs: input_ids, attention_mask,
|
| 156 |
+
// token_type_ids — produced by a BERT WordPiece tokenizer over the input text.
|
| 157 |
+
// Output "logits" is [1,5]; argmax over the last axis gives the class id.
|
| 158 |
+
```
|
| 159 |
+
|
| 160 |
+
### Training
|
| 161 |
+
|
| 162 |
+
- **Base:** `cointegrated/rubert-tiny` (BERT, 312 hidden, 3 layers, vocab 29 564).
|
| 163 |
+
- **Lineage:** multi-stage fine-tune — rubert-tiny → intermediate extractor →
|
| 164 |
+
4-class → 5-class → **5-class "rechunked"** (this model). Warm-started from the
|
| 165 |
+
previous 5-class checkpoint.
|
| 166 |
+
- **Data:** ~12–13k fragments of Russian IT vacancies, **relabeled by Claude
|
| 167 |
+
Opus** (silver → distilled), re-chunked with a `razdel` sentence splitter +
|
| 168 |
+
newline boundaries.
|
| 169 |
+
- **Objective:** class-weighted cross-entropy (balanced inverse-frequency) to
|
| 170 |
+
counter section imbalance.
|
| 171 |
+
- **Schedule:** 8 epochs with early stopping (patience 3, best ≈ epoch 3),
|
| 172 |
+
batch 32, lr 3e-5, weight decay 0.01, warmup ratio 0.1, linear decay, seed 42,
|
| 173 |
+
`max_length` 128, trained on Apple MPS in fp32.
|
| 174 |
+
- **Export:** coremltools 9.0, `compute_precision=FLOAT16`,
|
| 175 |
+
`compute_units=ALL`, `position_ids` baked as a constant buffer to work around a
|
| 176 |
+
const-fold limitation; verified at 100% argmax parity with the PyTorch source.
|
| 177 |
+
|
| 178 |
+
### Limitations & bias
|
| 179 |
+
|
| 180 |
+
- **Junk recall is low (33.3%).** The model often keeps noise rather than
|
| 181 |
+
dropping it; `notes` ↔ `junk` is the hardest boundary (junk F1 0.374). Add a
|
| 182 |
+
downstream filter if clean routing matters.
|
| 183 |
+
- **Domain:** trained on Russian **IT** vacancies. Other industries, other
|
| 184 |
+
languages, or non-vacancy text are out of distribution.
|
| 185 |
+
- **Granularity:** classifies a *single fragment*, not a whole posting. Use the
|
| 186 |
+
chunk-then-vote pattern above for full descriptions.
|
| 187 |
+
- **Sequence length:** fixed at 128 tokens; longer fragments are truncated.
|
| 188 |
+
- Labels are distilled from an LLM (Claude Opus), so they inherit its biases.
|
| 189 |
+
|
| 190 |
+
### License
|
| 191 |
+
|
| 192 |
+
**MIT** — same as the base model `cointegrated/rubert-tiny`.
|
| 193 |
+
|
| 194 |
+
### Citation
|
| 195 |
+
|
| 196 |
+
```bibtex
|
| 197 |
+
@misc{rubert_tiny_vacancy_section_classifier_coreml,
|
| 198 |
+
title = {rubert-tiny Vacancy Section Classifier (CoreML)},
|
| 199 |
+
author = {russian-oracle},
|
| 200 |
+
year = {2026},
|
| 201 |
+
note = {Fine-tuned from cointegrated/rubert-tiny; CoreML/ANE export},
|
| 202 |
+
url = {https://huggingface.co/russian-oracle/rubert-tiny-vacancy-section-classifier-coreml}
|
| 203 |
+
}
|
| 204 |
+
```
|
| 205 |
+
|
| 206 |
+
Base model:
|
| 207 |
+
|
| 208 |
+
```bibtex
|
| 209 |
+
@misc{dale2021rubert_tiny,
|
| 210 |
+
title = {rubert-tiny},
|
| 211 |
+
author = {Dale, David (cointegrated)},
|
| 212 |
+
url = {https://huggingface.co/cointegrated/rubert-tiny}
|
| 213 |
+
}
|
| 214 |
+
```
|
| 215 |
+
|
| 216 |
+
---
|
| 217 |
+
|
| 218 |
+
## 🇷🇺 Русская версия
|
| 219 |
+
|
| 220 |
+
### Что делает
|
| 221 |
+
|
| 222 |
+
По одному фрагменту русскоязычного описания вакансии модель предсказывает, к
|
| 223 |
+
какой из 5 структурных секций он относится:
|
| 224 |
+
|
| 225 |
+
| id | метка | смысл |
|
| 226 |
+
|----|----------------------|------------------------------------------------------|
|
| 227 |
+
| 0 | `responsibilities` | что сотрудник будет делать (задачи / обязанности) |
|
| 228 |
+
| 1 | `requirements` | что требуется от кандидата (требования / навыки) |
|
| 229 |
+
| 2 | `terms` | условия работы (зарплата / ДМС / график) |
|
| 230 |
+
| 3 | `notes` | мета / «о компании» / мягкий boilerplate |
|
| 231 |
+
| 4 | `junk` | неинформативный шум (выводится из структуры) |
|
| 232 |
+
|
| 233 |
+
Это этап структурной разметки в пайплайне скаутинга вакансий HH.ru, где модель
|
| 234 |
+
заменила более тяжёлую связку Qwen-эмбеддинги + косинус + reranking — при
|
| 235 |
+
~1–10 мс на вакансию на Apple Silicon.
|
| 236 |
+
|
| 237 |
+
### Артефакт
|
| 238 |
+
|
| 239 |
+
В репозитории — **только CoreML-артефакт** (без PyTorch-весов):
|
| 240 |
+
|
| 241 |
+
- `section_classifier.mlpackage` — `float16`, `ComputeUnit.ALL` (с поддержкой
|
| 242 |
+
ANE), минимальная цель развёртывания macOS 13.
|
| 243 |
+
- `tokenizer.json`, `tokenizer_config.json` — соответствующий BERT WordPiece
|
| 244 |
+
токенайзер (словарь 29 564). **Обязателен** — `.mlpackage` принимает id
|
| 245 |
+
токенов, а не сырой текст.
|
| 246 |
+
|
| 247 |
+
#### Сигнатура входов/выходов CoreML
|
| 248 |
+
|
| 249 |
+
| имя | тип | форма | примечание |
|
| 250 |
+
|------------------|--------|---------|-----------------------------------|
|
| 251 |
+
| `input_ids` | int32 | [1, 128] | паддинг до `max_length=128` |
|
| 252 |
+
| `attention_mask` | int32 | [1, 128] | 1 — реальный токен, 0 — паддинг |
|
| 253 |
+
| `token_type_ids` | int32 | [1, 128] | все нули (один сегмент) |
|
| 254 |
+
| **выход** `logits` | float32 | [1, 5] | без нормализации; `argmax` → класс |
|
| 255 |
+
|
| 256 |
+
`max_seq_len = 128` и имена классов зашиты в `user_defined_metadata` модели.
|
| 257 |
+
|
| 258 |
+
### Метрики
|
| 259 |
+
|
| 260 |
+
Цифры ниже измерены на **исходной PyTorch-модели**. CoreML-экспорт затем
|
| 261 |
+
проверен на **100% совпадение argmax** с этим источником на отложенном наборе
|
| 262 |
+
проб (макс. абс. разница логитов `0.0026`, что нормально для `float16`), поэтому
|
| 263 |
+
они переносятся на этот артефакт.
|
| 264 |
+
|
| 265 |
+
**Headline — golden-281 (ручная разметка, held-out):**
|
| 266 |
+
|
| 267 |
+
| метрика | значение |
|
| 268 |
+
|--------------------------------------------------|---------------------|
|
| 269 |
+
| Content-accuracy (4 содержательных класса) | **76.5%** (176/230) |
|
| 270 |
+
| Полная 5-class accuracy (включая роутинг junk) | **68.7%** |
|
| 271 |
+
| Junk recall (корректно отсеянный шум) | **33.3%** (17/51) |
|
| 272 |
+
|
| 273 |
+
Это и есть метрика, которой стоит доверять: 281 фрагмент, размеченный человеком
|
| 274 |
+
и не виденный при обучении.
|
| 275 |
+
|
| 276 |
+
<details>
|
| 277 |
+
<summary>Внутренний test-split (циркулярный — НЕ headline)</summary>
|
| 278 |
+
|
| 279 |
+
Оценка на внутреннем тестовом сплите, у которого то же Claude Opus-распределение
|
| 280 |
+
разметки, что и у обучающих данных, поэтому он завышает реальное качество.
|
| 281 |
+
Приведён только для мониторинга:
|
| 282 |
+
|
| 283 |
+
| метрика | значение |
|
| 284 |
+
|------------------|--------|
|
| 285 |
+
| Accuracy | 89.3% |
|
| 286 |
+
| Macro-F1 | 86.9% |
|
| 287 |
+
|
| 288 |
+
Per-class F1 (in-domain): responsibilities 0.789 · requirements 0.760 ·
|
| 289 |
+
terms 0.795 · notes 0.684 · **junk 0.374**.
|
| 290 |
+
</details>
|
| 291 |
+
|
| 292 |
+
### Использование (Python · coremltools)
|
| 293 |
+
|
| 294 |
+
```python
|
| 295 |
+
import numpy as np
|
| 296 |
+
import coremltools as ct
|
| 297 |
+
from transformers import AutoTokenizer
|
| 298 |
+
|
| 299 |
+
REPO = "russian-oracle/rubert-tiny-vacancy-section-classifier-coreml"
|
| 300 |
+
LABELS = ["responsibilities", "requirements", "terms", "notes", "junk"]
|
| 301 |
+
|
| 302 |
+
tok = AutoTokenizer.from_pretrained(REPO) # tokenizer.json в этом репо
|
| 303 |
+
mlmodel = ct.models.MLModel("section_classifier.mlpackage") # hf download ... локально
|
| 304 |
+
|
| 305 |
+
text = "Опыт работы с Python от 3 лет, знание Django и PostgreSQL."
|
| 306 |
+
enc = tok(text, return_tensors="np", padding="max_length",
|
| 307 |
+
truncation=True, max_length=128)
|
| 308 |
+
ids = enc["input_ids"].astype(np.int32)
|
| 309 |
+
out = mlmodel.predict({
|
| 310 |
+
"input_ids": ids,
|
| 311 |
+
"attention_mask": enc["attention_mask"].astype(np.int32),
|
| 312 |
+
"token_type_ids": enc.get("token_type_ids", np.zeros_like(ids)).astype(np.int32),
|
| 313 |
+
})
|
| 314 |
+
logits = np.asarray(out["logits"]).reshape(-1)
|
| 315 |
+
print(LABELS[int(logits.argmax())]) # → requirements
|
| 316 |
+
# вероятности: softmax(logits)
|
| 317 |
+
```
|
| 318 |
+
|
| 319 |
+
> `coremltools` требует нативных биндингов, которые есть только в части сборок
|
| 320 |
+
> CPython (надёжно работает wheel под 3.12). Запускайте предсказание под таким
|
| 321 |
+
> интерпретатором.
|
| 322 |
+
|
| 323 |
+
**Рекомендуемая агрегация (как используется в продакшене):** разбейте полное
|
| 324 |
+
описание на фрагменты по предложениям (например, `razdel` + переводы строк),
|
| 325 |
+
классифицируйте каждый, возьмите мажоритарную метку на чанк; фрагменты `junk`
|
| 326 |
+
отправляются в корзину «orphans», а не в структурированный вывод.
|
| 327 |
+
|
| 328 |
+
### Обучение
|
| 329 |
+
|
| 330 |
+
- **База:** `cointegrated/rubert-tiny` (BERT, 312 hidden, 3 слоя, словарь 29 564).
|
| 331 |
+
- **Происхождение:** многоступенчатый файн-тюн — rubert-tiny → промежуточный
|
| 332 |
+
extractor → 4-class → 5-class → **5-class «rechunked»** (эта модель).
|
| 333 |
+
Warm-start с предыдущего 5-class чекпойнта.
|
| 334 |
+
- **Данные:** ~12–13 тыс. фрагментов русских IT-вакансий, **переразмечены
|
| 335 |
+
Claude Opus** (silver → дистилляция), перечанкованы сплиттером `razdel` по
|
| 336 |
+
предложениям + границам строк.
|
| 337 |
+
- **Лосс:** взвешенная по классам кросс-энтропия (balanced inverse-frequency)
|
| 338 |
+
против дисбаланса секций.
|
| 339 |
+
- **Расписание:** 8 эпох с ранней ос��ановкой (patience 3, лучшая ≈ эпоха 3),
|
| 340 |
+
batch 32, lr 3e-5, weight decay 0.01, warmup ratio 0.1, линейный спад, seed 42,
|
| 341 |
+
`max_length` 128, обучение на Apple MPS в fp32.
|
| 342 |
+
- **Экспорт:** coremltools 9.0, `compute_precision=FLOAT16`,
|
| 343 |
+
`compute_units=ALL`, `position_ids` зашит как константный буфер (обход
|
| 344 |
+
ограничения const-fold); проверено на 100% argmax-parity с PyTorch-источником.
|
| 345 |
+
|
| 346 |
+
### Ограничения и смещения
|
| 347 |
+
|
| 348 |
+
- **Низкий junk recall (33.3%).** Модель чаще оставляет шум, чем отсеивает его;
|
| 349 |
+
граница `notes` ↔ `junk` — самая сложная (junk F1 0.374). Если важен чистый
|
| 350 |
+
роутинг — добавьте downstream-фильтр.
|
| 351 |
+
- **Домен:** обучена на русских **IT**-вакансиях. Другие отрасли, языки или
|
| 352 |
+
не-вакансионный текст — вне распределения.
|
| 353 |
+
- **Гранулярность:** классифицирует *отдельный фрагмент*, а не вакансию целиком.
|
| 354 |
+
Для полных описаний используйте схему chunk-then-vote выше.
|
| 355 |
+
- **Длина последовательности:** фиксированные 128 токенов; длиннее — обрезается.
|
| 356 |
+
- Метки дистиллированы из LLM (Claude Opus) и наследуют её смещения.
|
| 357 |
+
|
| 358 |
+
### Лицензия
|
| 359 |
+
|
| 360 |
+
**MIT** — как и у базовой модели `cointegrated/rubert-tiny`.
|
| 361 |
+
|
| 362 |
+
### Цитирование
|
| 363 |
+
|
| 364 |
+
См. BibTeX в английской секции выше.
|
section_classifier.mlpackage/Data/com.apple.CoreML/model.mlmodel
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e150127ec4c530d1453f50c6b609e7f58d1143632a54dbb3f307918f1b9436ce
|
| 3 |
+
size 40872
|
section_classifier.mlpackage/Data/com.apple.CoreML/weights/weight.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d3b134748113e168e281da6ea9784184e9f55d174061570d2365798a05190f41
|
| 3 |
+
size 23336048
|
section_classifier.mlpackage/Manifest.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"fileFormatVersion": "1.0.0",
|
| 3 |
+
"itemInfoEntries": {
|
| 4 |
+
"059072FA-F4AC-4B94-A284-8A828ED6C0D2": {
|
| 5 |
+
"author": "com.apple.CoreML",
|
| 6 |
+
"description": "CoreML Model Specification",
|
| 7 |
+
"name": "model.mlmodel",
|
| 8 |
+
"path": "com.apple.CoreML/model.mlmodel"
|
| 9 |
+
},
|
| 10 |
+
"E6CAA176-ED54-4F45-B849-90F22B9627EB": {
|
| 11 |
+
"author": "com.apple.CoreML",
|
| 12 |
+
"description": "CoreML Model Weights",
|
| 13 |
+
"name": "weights",
|
| 14 |
+
"path": "com.apple.CoreML/weights"
|
| 15 |
+
}
|
| 16 |
+
},
|
| 17 |
+
"rootModelIdentifier": "059072FA-F4AC-4B94-A284-8A828ED6C0D2"
|
| 18 |
+
}
|
tokenizer.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
tokenizer_config.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"backend": "tokenizers",
|
| 3 |
+
"cls_token": "[CLS]",
|
| 4 |
+
"do_basic_tokenize": true,
|
| 5 |
+
"do_lower_case": false,
|
| 6 |
+
"is_local": true,
|
| 7 |
+
"local_files_only": false,
|
| 8 |
+
"mask_token": "[MASK]",
|
| 9 |
+
"max_length": 512,
|
| 10 |
+
"model_max_length": 512,
|
| 11 |
+
"never_split": null,
|
| 12 |
+
"pad_to_multiple_of": null,
|
| 13 |
+
"pad_token": "[PAD]",
|
| 14 |
+
"pad_token_type_id": 0,
|
| 15 |
+
"padding_side": "right",
|
| 16 |
+
"sep_token": "[SEP]",
|
| 17 |
+
"stride": 0,
|
| 18 |
+
"strip_accents": null,
|
| 19 |
+
"tokenize_chinese_chars": true,
|
| 20 |
+
"tokenizer_class": "BertTokenizer",
|
| 21 |
+
"truncation_side": "right",
|
| 22 |
+
"truncation_strategy": "longest_first",
|
| 23 |
+
"unk_token": "[UNK]"
|
| 24 |
+
}
|