Downloads · 30 days
1
5% of all-time downloads
russian-oracle/rubert-tiny-vacancy-section-classifier-coreml
rubert-tiny-vacancy-section-classifier-coreml is a text classification model from russian-oracle. Use it when you need a label for a piece of text. It is set up for coreml. The card lists the license as mit.
On-device CoreML (Apple Neural Engine) classifier that labels fragments of Russian-language job postings into 5 structural sections. Built on cointegrated/rubert-tiny (11.9M params), exported to a float16 .mlpackage f…
Downloads · 30 days
1
5% of all-time downloads
All-time downloads
22
Public
Repo size
23.4 MB
Likes
0
Public
Click a slice to open those files.
.bin23.3 MB · 97%
From the Hugging Face model README
On-device CoreML (Apple Neural Engine) classifier that labels fragments of
Russian-language job postings into 5 structural sections. Built on
cointegrated/rubert-tiny
(11.9M params), exported to a float16 .mlpackage for Apple Silicon.
🇬🇧 English card below · 🇷🇺 Русская версия ниже (перейти)
Given one fragment of a Russian vacancy description, the model predicts which of 5 sections it belongs to:
| id | label | meaning |
|---|---|---|
| 0 | responsibilities | what the employee will do (задачи / обязанности) |
| 1 | requirements | what the candidate must have (требования / навыки) |
| 2 | terms | conditions of employment (условия / зарплата / ДМС) |
| 3 | notes | meta / "about the company" / soft boilerplate |
| 4 | junk | non-informative noise (routed out of structured data) |
It is the structured-extraction stage of an HH.ru vacancy-scouting pipeline, where it replaced a heavier Qwen-embedding + cosine + rerank approach at ~1–10 ms per vacancy on Apple Silicon.
This repository ships the CoreML artifact only (no PyTorch weights):
section_classifier.mlpackage — float16, ComputeUnit.ALL (ANE-eligible),
minimum deployment target macOS 13.tokenizer.json, tokenizer_config.json — the matching BERT WordPiece
tokenizer (vocab 29 564). Required — the .mlpackage consumes token ids,
not raw text.| name | dtype | shape | notes |
|---|---|---|---|
input_ids | int32 | [1, 128] | padded to max_length=128 |
attention_mask | int32 | [1, 128] | 1 = real token, 0 = pad |
token_type_ids | int32 | [1, 128] | all zeros (single segment) |
output logits | float32 | [1, 5] | un-normalized; argmax → class |
max_seq_len = 128 and the label names are embedded in the model's
user_defined_metadata.
The numbers below were measured on the source PyTorch model. The CoreML
export was then verified at 100% argmax parity against that source on a
held-out set of probe texts (max absolute logit difference 0.0026, expected
for float16), so they carry over to this artifact.
Headline — golden-281 (human-labeled, held-out):
| metric | value |
|---|---|
| Content accuracy (4 meaningful classes) | 76.5% (176/230) |
| Full 5-class accuracy (incl. junk routing) | 68.7% |
| Junk recall (noise correctly routed out) | 33.3% (17/51) |
This is the metric to trust: 281 fragments labeled by a human, never seen in training.
<details> <summary>In-domain test split (circular — NOT the headline)</summary>Evaluated on the internal test split, which shares the same Claude Opus relabeled distribution as the training data, so it overstates real-world performance. Reported for monitoring only:
| metric | value |
|---|---|
| Accuracy | 89.3% |
| Macro-F1 | 86.9% |
Per-class F1 (in-domain): responsibilities 0.789 · requirements 0.760 · terms 0.795 · notes 0.684 · junk 0.374.
</details>import numpy as np
import coremltools as ct
from transformers import AutoTokenizer
REPO = "russian-oracle/rubert-tiny-vacancy-section-classifier-coreml"
LABELS = ["responsibilities", "requirements", "terms", "notes", "junk"]
tok = AutoTokenizer.from_pretrained(REPO) # tokenizer.json shipped here
mlmodel = ct.models.MLModel("section_classifier.mlpackage") # hf download ... locally
text = "Опыт работы с Python от 3 лет, знание Django и PostgreSQL."
enc = tok(text, return_tensors="np", padding="max_length",
truncation=True, max_length=128)
ids = enc["input_ids"].astype(np.int32)
out = mlmodel.predict({
"input_ids": ids,
"attention_mask": enc["attention_mask"].astype(np.int32),
"token_type_ids": enc.get("token_type_ids", np.zeros_like(ids)).astype(np.int32),
})
logits = np.asarray(out["logits"]).reshape(-1)
print(LABELS[int(logits.argmax())]) # → requirements
# probabilities: softmax(logits)
coremltoolsneeds its native bindings, which ship only with certain CPython builds (a 3.12 wheel works reliably). Run prediction under such an interpreter.
Recommended aggregation (how it is used in production): split a full
description into sentence-level chunks (e.g. razdel + newline), classify each,
take the majority label per chunk; junk fragments are routed to an "orphans"
bucket instead of the structured output.
import CoreML
let model = try MLModel(contentsOf: url) // section_classifier.mlpackage (compiled)
// Provide three [1,128] MLMultiArray(.int32) inputs: input_ids, attention_mask,
// token_type_ids — produced by a BERT WordPiece tokenizer over the input text.
// Output "logits" is [1,5]; argmax over the last axis gives the class id.
cointegrated/rubert-tiny (BERT, 312 hidden, 3 layers, vocab 29 564).razdel sentence splitter +
newline boundaries.max_length 128, trained on Apple MPS in fp32.compute_precision=FLOAT16,
compute_units=ALL, position_ids baked as a constant buffer to work around a
const-fold limitation; verified at 100% argmax parity with the PyTorch source.notes ↔ junk is the hardest boundary (junk F1 0.374). Add a
downstream filter if clean routing matters.MIT — same as the base model cointegrated/rubert-tiny.
@misc{rubert_tiny_vacancy_section_classifier_coreml,
title = {rubert-tiny Vacancy Section Classifier (CoreML)},
author = {russian-oracle},
year = {2026},
note = {Fine-tuned from cointegrated/rubert-tiny; CoreML/ANE export},
url = {https://huggingface.co/russian-oracle/rubert-tiny-vacancy-section-classifier-coreml}
}
Base model:
@misc{dale2021rubert_tiny,
title = {rubert-tiny},
author = {Dale, David (cointegrated)},
url = {https://huggingface.co/cointegrated/rubert-tiny}
}
По одному фрагменту русскоязычного описания вакансии модель предсказывает, к какой из 5 структурных секций он относится:
| id | метка | смысл |
|---|---|---|
| 0 | responsibilities | что сотрудник будет делать (задачи / обязанности) |
| 1 | requirements | что требуется от кандидата (требования / навыки) |
| 2 | terms | условия работы (зарплата / ДМС / график) |
| 3 | notes | мета / «о компании» / мягкий boilerplate |
| 4 | junk | неинформативный шум (выводится из структуры) |
Это этап структурной разметки в пайплайне скаутинга вакансий HH.ru, где модель заменила более тяжёлую связку Qwen-эмбеддинги + косинус + reranking — при ~1–10 мс на вакансию на Apple Silicon.
В репозитории — только CoreML-артефакт (без PyTorch-весов):
section_classifier.mlpackage — float16, ComputeUnit.ALL (с поддержкой
ANE), минимальная цель развёртывания macOS 13.tokenizer.json, tokenizer_config.json — соответствующий BERT WordPiece
токенайзер (словарь 29 564). Обязателен — .mlpackage принимает id
токенов, а не сырой текст.| имя | тип | форма | примечание |
|---|---|---|---|
input_ids | int32 | [1, 128] | паддинг до max_length=128 |
attention_mask | int32 | [1, 128] | 1 — реальный токен, 0 — паддинг |
token_type_ids | int32 | [1, 128] | все нули (один сегмент) |
выход logits | float32 | [1, 5] | без нормализации; argmax → класс |
max_seq_len = 128 и имена классов зашиты в user_defined_metadata модели.
Цифры ниже измерены на исходной PyTorch-модели. CoreML-экспорт затем
проверен на 100% совпадение argmax с этим источником на отложенном наборе
проб (макс. абс. разница логитов 0.0026, что нормально для float16), поэтому
они переносятся на этот артефакт.
Headline — golden-281 (ручная разметка, held-out):
| метрика | значение |
|---|---|
| Content-accuracy (4 содержательных класса) | 76.5% (176/230) |
| Полная 5-class accuracy (включая роутинг junk) | 68.7% |
| Junk recall (корректно отсеянный шум) | 33.3% (17/51) |
Это и есть метрика, которой стоит доверять: 281 фрагмент, размеченный человеком и не виденный при обучении.
<details> <summary>Внутренний test-split (циркулярный — НЕ headline)</summary>Оценка на внутреннем тестовом сплите, у которого то же Claude Opus-распределение разметки, что и у обучающих данных, поэтому он завышает реальное качество. Приведён только для мониторинга:
| метрика | значение |
|---|---|
| Accuracy | 89.3% |
| Macro-F1 | 86.9% |
Per-class F1 (in-domain): responsibilities 0.789 · requirements 0.760 · terms 0.795 · notes 0.684 · junk 0.374.
</details>import numpy as np
import coremltools as ct
from transformers import AutoTokenizer
REPO = "russian-oracle/rubert-tiny-vacancy-section-classifier-coreml"
LABELS = ["responsibilities", "requirements", "terms", "notes", "junk"]
tok = AutoTokenizer.from_pretrained(REPO) # tokenizer.json в этом репо
mlmodel = ct.models.MLModel("section_classifier.mlpackage") # hf download ... локально
text = "Опыт работы с Python от 3 лет, знание Django и PostgreSQL."
enc = tok(text, return_tensors="np", padding="max_length",
truncation=True, max_length=128)
ids = enc["input_ids"].astype(np.int32)
out = mlmodel.predict({
"input_ids": ids,
"attention_mask": enc["attention_mask"].astype(np.int32),
"token_type_ids": enc.get("token_type_ids", np.zeros_like(ids)).astype(np.int32),
})
logits = np.asarray(out["logits"]).reshape(-1)
print(LABELS[int(logits.argmax())]) # → requirements
# вероятности: softmax(logits)
coremltoolsтребует нативных биндингов, которые есть только в части сборок CPython (надёжно работает wheel под 3.12). Запускайте предсказание под таким интерпретатором.
Рекомендуемая агрегация (как используется в продакшене): разбейте полное
описание на фрагменты по предложениям (например, razdel + переводы строк),
классифицируйте каждый, возьмите мажоритарную метку на чанк; фрагменты junk
отправляются в корзину «orphans», а не в структурированный вывод.
cointegrated/rubert-tiny (BERT, 312 hidden, 3 слоя, словарь 29 564).razdel по
предложениям + границам строк.max_length 128, обучение на Apple MPS в fp32.compute_precision=FLOAT16,
compute_units=ALL, position_ids зашит как константный буфер (обход
ограничения const-fold); проверено на 100% argmax-parity с PyTorch-источником.notes ↔ junk — самая сложная (junk F1 0.374). Если важен чистый
роутинг — добавьте downstream-фильтр.MIT — как и у базовой модели cointegrated/rubert-tiny.
См. BibTeX в английской секции выше.