File size: 6,980 Bytes
fc53c27 ee3db37 fc53c27 ee3db37 fc53c27 ee3db37 084dc6e 0607622 ee3db37 084dc6e ee3db37 12fa79a ee3db37 | 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 | ---
language:
- ru
license: mit
base_model: DeepPavlov/rubert-base-cased
tags:
- text-classification
- spam-detection
- russian
- telegram-moderation
- bert
pipeline_tag: text-classification
datasets:
- alt-gnome/telegram-spam
metrics:
- f1
- precision
- recall
---
# AIMS-RU-Binary-Spam-Classifier
Бинарный классификатор спама для русскоязычных сообщений, часть проекта **AIMS (AI Moderation System)**.
Модель дообучена поверх [`DeepPavlov/rubert-base-cased`](https://huggingface.co/DeepPavlov/rubert-base-cased) и определяет, является ли сообщение **SPAM** или **SAFE**.
Лёгкая и быстрая модель.
## Обновлено. Фикс некоторых ложных срабатываний
## Обучающие данные
Обучение проводилось на сообщениях, собранных в активных публичных Telegram чатах осенью-весной 2025-2026 года.
## Метрики
Оценка проводилась на внешнем датасете [`alt-gnome/telegram-spam`](https://huggingface.co/datasets/alt-gnome/telegram-spam), не пересекающемся с обучающей выборкой:
| Метрика | Значение |
|-----------|----------|
| F1 | 0.95 |
| Precision | 0.98 |
| Recall | 0.92 |
Высокий precision (0.98) означает низкую долю ложных срабатываний — модель редко помечает безопасные сообщения как спам, что важно для автоматической модерации без риска задеть живых пользователей.
## Архитектура
- Энкодер: `DeepPavlov/rubert-base-cased`
- Голова классификации: `Dropout(0.2) → Linear(hidden_size, 1)`
- Пулинг: эмбеддинг токена `[CLS]`
- Функция потерь при обучении: `BCEWithLogitsLoss` с весом положительного класса (`pos_weight`) для компенсации дисбаланса SAFE/SPAM
- Выход: один логит → `sigmoid` → вероятность класса SPAM
## РЕКОМЕНДАЦИЯ Нормализация текста
Перед подачей в модель рекомендуется нормализовать текст, это использовалось при обучении.
- убирает "разрежённое" написание (`п.р.и.в.е.т.` → `привет`)
- заменяет гомоглифы (похожие символы из других алфавитов — `ᴀ`, `α`, латиница вместо кириллицы и т.д.) на кириллические эквиваленты
- удаляет ссылки и команды ботов (`/cmd@bot`)
- схлопывает повторяющиеся буквы (`приввееееет` → `привет`)
- приводит текст к нижнему регистру
## Быстрый запуск
```bash
pip install torch transformers huggingface_hub
```
```python
import re
import json
import torch
import torch.nn as nn
from transformers import AutoTokenizer, AutoModel
from huggingface_hub import hf_hub_download
REPO = "SafeTechDev/AIMS-RU-Binary-Spam-Classifier"
device = "cuda" if torch.cuda.is_available() else "cpu"
# ── Архитектура ──────────────────────────────────────────────────────────
class BinaryModel(nn.Module):
def __init__(self, model_name):
super().__init__()
self.bert = AutoModel.from_pretrained(model_name, low_cpu_mem_usage=True)
hidden = self.bert.config.hidden_size
self.binary = nn.Sequential(
nn.Dropout(0.2),
nn.Linear(hidden, 1)
)
def forward(self, input_ids, attention_mask):
outputs = self.bert(input_ids=input_ids, attention_mask=attention_mask)
pooled = outputs.last_hidden_state[:, 0]
return self.binary(pooled).squeeze(-1)
# ── Загрузка ─────────────────────────────────────────────────────────────
config_path = hf_hub_download(REPO, "config.json")
weights_path = hf_hub_download(REPO, "pytorch_model.bin")
with open(config_path, encoding="utf-8") as f:
cfg = json.load(f)
MAX_LENGTH = cfg.get("max_length", 40)
tokenizer = AutoTokenizer.from_pretrained(REPO)
model = BinaryModel(cfg["model"]).to(device)
model.load_state_dict(torch.load(weights_path, map_location=device, weights_only=True))
model.eval()
# ── Инференс ─────────────────────────────────────────────────────────────
def classify(text: str) -> dict:
enc = tokenizer(
text, truncation=True, padding="max_length",
max_length=MAX_LENGTH, return_tensors="pt"
)
with torch.no_grad():
logits = model(enc["input_ids"].to(device), enc["attention_mask"].to(device))
prob_spam = float(torch.sigmoid(logits).squeeze().cpu().item())
label = "SPAM" if prob_spam >= 0.7 else "SAFE"
return {"label": label, "prob_spam": prob_spam}
print(classify("Дам денег, работу, пишите в лс"))
# {'label': 'SPAM', 'prob_spam': 0.98...}
print(classify("Привет, как дела?"))
# {'label': 'SAFE', 'prob_spam': 0.02...}
```
### Через pipeline (упрощённо, без кастомной нормализации)
Модель хранит веса в формате `pytorch_model.bin` с кастомной головой, поэтому напрямую через `transformers.pipeline("text-classification", ...)` она не запустится — используйте код выше.
## Ограничения
- Модель обучена и валидирована на **русскоязычном** тексте; на других языках качество не гарантируется.
- Порог 0.7 используется по умолчанию для разделения SAFE/SPAM — в проде рекомендуется подбирать порог под свои данные (например, по максимальному F1), т.к. соотношение precision/recall меняется в зависимости от домена.
- Модель не учитывает контекст переписки — классифицируется только отдельное сообщение.
## Автор
[SafeTechDev](https://huggingface.co/SafeTechDev)
|