failed09 commited on
Commit
b03c74b
·
verified ·
1 Parent(s): dc0ef6f

Initial Bashkir LID release

Browse files
Files changed (6) hide show
  1. META.json +14 -0
  2. README.md +60 -0
  3. __init__.py +5 -0
  4. lid.py +88 -0
  5. model.onnx +3 -0
  6. vectorizer.json +0 -0
META.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "id": "bashkir_lid",
3
+ "version": "v3",
4
+ "type": "language_identification",
5
+ "classes": ["ba", "ru"],
6
+ "artifacts": ["model.onnx", "vectorizer.json"],
7
+ "features": 100000,
8
+ "input_contract": "Unicode char_wb adapter; padded feature_ids and counts tensors",
9
+ "source_texts_distributed": false,
10
+ "sha256": {
11
+ "model.onnx": "9ddfdecf5679393da8943dc7b732ea937b5eaf4bfff2071c431cc7f9d1a0586f",
12
+ "vectorizer.json": "0413814d379b4e3c50fd324e5d87999642a9f267640bec9d5bd651a6f948cc38"
13
+ }
14
+ }
README.md ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ language: [ba, ru]
3
+ license: apache-2.0
4
+ library_name: onnxruntime
5
+ tags:
6
+ - bashkir
7
+ - language-identification
8
+ - text-classification
9
+ - onnx
10
+ ---
11
+
12
+ # Bashkir LID
13
+
14
+ Компактная модель для определения того, является ли короткий текст
15
+ башкирским или русским. Модель ориентирована на фильтрацию башкирских
16
+ корпусов и локальные приложения, где важны скорость и небольшой размер.
17
+
18
+ ## Использование
19
+
20
+ ```python
21
+ from lid import LanguageIdentifier
22
+
23
+ lid = LanguageIdentifier()
24
+ print(lid.predict(["Мин башҡорт телен яратам."]))
25
+ ```
26
+
27
+ Для запуска требуется `onnxruntime` и `numpy`.
28
+
29
+ ## Модель
30
+
31
+ Используются символьные n-граммы TF-IDF (`char_wb`, 2–4) и линейный
32
+ классификатор, экспортированный в ONNX. Веса и словарь признаков поставляются
33
+ в репозитории; исходные тренировочные тексты не распространяются.
34
+
35
+ ## Ограничения
36
+
37
+ Модель бинарная: `ba` против `ru`. Она не предназначена для надёжного
38
+ различения татарского, чувашского и других языков. Для многоязычного шлюза
39
+ используйте отдельную LID-модель. Короткие строки, имена, OCR и смешанный текст
40
+ могут классифицироваться неоднозначно.
41
+
42
+ ## Лицензия и происхождение
43
+
44
+ Код и экспорт модели распространяются под Apache-2.0. Тренировочные тексты
45
+ остаются в исходном проекте и не включены в этот репозиторий. Если отдельный
46
+ источник имеет более строгие условия, применяются условия этого источника.
47
+
48
+ ## Цитирование
49
+
50
+ ```bibtex
51
+ @software{failed09_bashkir_lid,
52
+ title = {Bashkir LID},
53
+ author = {failed09},
54
+ year = {2026},
55
+ publisher = {Hugging Face},
56
+ url = {https://huggingface.co/failed09/bashkir-lid}
57
+ }
58
+ ```
59
+
60
+ Башҡорт теле өсөн эшләнгән асыҡ сығанаҡлы инструмент.
__init__.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ """Bashkir/Russian language identification."""
2
+
3
+ from .lid import LanguageIdentifier
4
+
5
+ __all__ = ["LanguageIdentifier"]
lid.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Portable Unicode char_wb preprocessing and sparse ONNX inference."""
2
+ import hashlib
3
+ import json
4
+ import re
5
+ from collections import Counter
6
+ from pathlib import Path
7
+
8
+ import numpy as np
9
+
10
+ ROOT = Path(__file__).resolve().parent
11
+
12
+
13
+ def sha256(path):
14
+ with open(path, "rb") as stream:
15
+ return hashlib.file_digest(stream, "sha256").hexdigest()
16
+
17
+
18
+ def char_wb(text, ngram_range):
19
+ # Match sklearn's _char_wb_ngrams, including short-word handling.
20
+ text = re.sub(r"\s\s+", " ", text.lower())
21
+ for word in text.split():
22
+ word = " " + word + " "
23
+ for n in range(ngram_range[0], ngram_range[1] + 1):
24
+ offset = 0
25
+ yield word[offset:offset + n]
26
+ while offset + n < len(word):
27
+ offset += 1
28
+ yield word[offset:offset + n]
29
+ if offset == 0:
30
+ break
31
+
32
+
33
+ class LanguageIdentifier:
34
+ def __init__(self, model_dir=None, batch_size=256):
35
+ import onnxruntime as ort
36
+
37
+ self.model_dir = Path(model_dir) if model_dir else ROOT / "model"
38
+ self.meta = json.loads((self.model_dir / "META.json").read_text(encoding="utf-8"))
39
+ for name in ("model.onnx", "vectorizer.json"):
40
+ digest = self.meta["sha256"][name]
41
+ if sha256(self.model_dir / name) != digest:
42
+ raise ValueError(f"LID artifact checksum mismatch: {name}")
43
+ cfg = json.loads((self.model_dir / "vectorizer.json").read_text(encoding="utf-8"))
44
+ self.vocabulary = cfg["vocabulary"]
45
+ self.ngram_range = cfg["ngram_range"]
46
+ self.classes = np.asarray(self.meta["classes"])
47
+ if set(self.classes) != {"ba", "ru"}:
48
+ raise ValueError("Expected BA/RU model")
49
+ if batch_size < 1:
50
+ raise ValueError("batch_size must be positive")
51
+ self.batch_size = batch_size
52
+ options = ort.SessionOptions()
53
+ options.intra_op_num_threads = 1
54
+ self.session = ort.InferenceSession(str(self.model_dir / "model.onnx"), options,
55
+ providers=["CPUExecutionProvider"])
56
+
57
+ def features(self, texts):
58
+ rows = []
59
+ for text in texts:
60
+ if not isinstance(text, str):
61
+ raise TypeError("LID input must contain strings")
62
+ counts = Counter(self.vocabulary[g] for g in char_wb(text, self.ngram_range)
63
+ if g in self.vocabulary)
64
+ rows.append(counts)
65
+ width = max(1, max(map(len, rows), default=0))
66
+ ids = np.zeros((len(rows), width), dtype=np.int64)
67
+ counts = np.zeros((len(rows), width), dtype=np.float32)
68
+ for i, row in enumerate(rows):
69
+ if row:
70
+ ids[i, :len(row)] = list(row)
71
+ counts[i, :len(row)] = list(row.values())
72
+ return {"feature_ids": ids, "counts": counts}
73
+
74
+ def predict_proba(self, texts):
75
+ texts = list(texts)
76
+ results = []
77
+ for start in range(0, len(texts), self.batch_size):
78
+ results.append(self.session.run(["probabilities"],
79
+ self.features(texts[start:start + self.batch_size]))[0])
80
+ return np.concatenate(results) if results else np.empty((0, 2), dtype=np.float32)
81
+
82
+ def predict(self, texts):
83
+ return self.classes[self.predict_proba(texts).argmax(axis=1)]
84
+
85
+ def describe(self):
86
+ return {"name": self.meta["id"], "version": self.meta["version"],
87
+ "sha256": self.meta["sha256"], "backend": "onnxruntime",
88
+ "decision_rule": "argmax probabilities; classes ordered as in model passport"}
model.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9ddfdecf5679393da8943dc7b732ea937b5eaf4bfff2071c431cc7f9d1a0586f
3
+ size 800928
vectorizer.json ADDED
The diff for this file is too large to render. See raw diff