--- 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)