| --- |
| language: |
| - ru |
| tags: |
| - text-classification |
| - moderation |
| - toxicity |
| - spam-detection |
| - toxic-detection |
| - cyberbullying-detection |
| - russian |
| - cyberbullying |
| - bert |
| - NLP |
| - rubert |
| license: mit |
| base_model: DeepPavlov/rubert-base-cased |
| --- |
| |
| # AIMS — AI Moderation System |
|
|
| **AIMS** — многоклассовая модель классификации текста для автоматической модерации русскоязычных чатов. Определяет токсичный контент, спам, коммерческие объявления и другие нарушения. |
|
|
| Модель построена на базе [`DeepPavlov/rubert-base-cased`](https://huggingface.co/DeepPavlov/rubert-base-cased) с дополнительной головой классификатора (multi-head pooling: CLS + mean + max). |
| Модель может использоваться для автоматической модерации русскоязычных онлайн-сообществ. |
|
|
| *Мой TON (Gram) Крипто-кошелек. Если вы хотите поддержать проект: UQDB_yClLEoQMzfxFz1XoTa-bH0mPEiDfGhLhC3igJV4HrSL* |
|
|
| --- |
|
|
| ## Классы |
|
|
| | Класс | Описание | |
| |---|---| |
| | `SAFE` | Безопасное сообщение, нарушений не обнаружено | |
| | `OSK` | Оскорбления — кибербуллинг, агрессия, унижение | |
| | `RUDELY_OSK` | Грубые оскорбления — мат в адрес родственников, несколько оскорблений в одном сообщении | |
| | `SPAM` | Спам — реклама, предложения работы, навязчивые рассылки | |
| | `SELL` | Коммерция — объявления о покупке/продаже, обмене | |
| | `ADULT` | Сексуальный контент — текстовые предложения интима, различные грубые выражения | |
| | `PLS` | Попрошайничество - навязчивые просьбы о денежном вознаграждении *(бета-тест)* | |
|
|
| > ⚠️ Модель работает **исключительно с русскоязычным текстом**. |
|
|
| --- |
|
|
| ## Характеристики |
|
|
| | Параметр | Значение | |
| |---|---| |
| | Базовая модель | `DeepPavlov/rubert-base-cased` | |
| | Архитектура | BERT + multi-head pooling + Linear classifier | |
| | Макс. длина токенов | 40 | |
| | Количество классов | 7 | |
| | Язык | Русский | |
| | Источник данных | Активные Telegram-чаты | |
|
|
| --- |
|
|
| ## Датасет |
|
|
| Модель обучена на собственном датасете из **27 000 примеров**, собранных в русскоязычных Telegram-чатах летом и осенью 2025 года. |
|
|
| - Разметка: частично выполнена вручную человеком, частично с помощью **DeepSeek** |
| - Источник: живые сообщения из публичных Telegram-чатов |
| - Баланс классов: компенсируется оверсэмплингом редких классов и взвешенным `CrossEntropyLoss` |
|
|
| --- |
|
|
| ## Метрики |
|
|
| Тестирование проводилось на внешних датасетах с HuggingFace с бинарной разметкой (плохое/хорошее). Оценивалась способность модели детектировать целевой класс среди немеченых плохих сообщений. |
|
|
| Все метрики приведены для порога уверенности **≥ 90%** (рекомендуемый порог уверенности модели). |
|
|
| ### Токсичность (OSK + RUDELY_OSK) |
| |
| Для оценки эффективности модели использовались открытые наборы данных, охватывающие различные задачи автоматической модерации русскоязычного контента. |
| |
| Тестирование проводилось на следующих датасетах: |
| |
| - **AlexSham/Toxic_Russian_Comments** — набор данных, содержащий русскоязычные комментарии с разметкой токсичности. Использовался для оценки качества обнаружения токсичных высказываний. |
| |
| Тест на датасете с оскорбительными сообщениями (Взятый обьем: 22 000 примеров, ~18 000 SAFE / ~4 000 BAD). **AlexSham/Toxic_Russian_Comments** |
| |
| | Метрика | Значение | |
| |---|---| |
| | **Precision** | 91.3% | |
| | **Recall** | 95.7% | |
| | **F1-score** | 93.5% | |
| |
| |
| |
| ### Спам (SPAM) |
| |
| - **alt-gnome/telegram-spam** — набор сообщений из Telegram с разметкой на спам и легитимные сообщения. Использовался для оценки качества обнаружения спам-контента в условиях, приближенных к реальной эксплуатации. |
| |
| |
| Тест на датасете спам сообщений (Взятый обьем: 15 000 примеров, ~10 000 SAFE / ~5 000 BAD). |
| |
| | Метрика | Значение | |
| |---|---| |
| | **Precision** | 93.6% | |
| | **Recall** | 96.8% | |
| | **F1-score** | 95.2% | |
| |
| |
| |
| |
| --- |
| |
| ## Использование |
| |
| ### Установка зависимостей |
| |
| ```bash |
| pip install torch transformers |
| ``` |
| |
| ### Быстрый старт |
| |
| ```python |
| import torch |
| import torch.nn as nn |
| import pickle |
| import json |
| import numpy as np |
| from transformers import AutoTokenizer, AutoModel |
| |
| MODEL_DIR = "path/to/aims-model" |
| DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") |
| |
| # Загрузка конфига |
| with open(f"{MODEL_DIR}/config.json") as f: |
| config = json.load(f) |
| |
| with open(f"{MODEL_DIR}/label_encoder.pkl", "rb") as f: |
| label_encoder = pickle.load(f) |
| |
| # Определение архитектуры |
| class AIMSModel(nn.Module): |
| def __init__(self, model_name, num_classes): |
| super().__init__() |
| self.encoder = AutoModel.from_pretrained(model_name, low_cpu_mem_usage=True) |
| hidden = self.encoder.config.hidden_size |
| self.classifier = nn.Sequential( |
| nn.Dropout(0.2), |
| nn.Linear(hidden * 3, 512), |
| nn.GELU(), |
| nn.Dropout(0.1), |
| nn.Linear(512, num_classes) |
| ) |
| |
| def forward(self, input_ids, attention_mask): |
| hs = self.encoder(input_ids=input_ids, attention_mask=attention_mask).last_hidden_state |
| cls_p = hs[:, 0] |
| mask = attention_mask.unsqueeze(-1).float() |
| mean_p = (hs * mask).sum(1) / mask.sum(1).clamp(min=1e-9) |
| max_p = hs.masked_fill(attention_mask.unsqueeze(-1) == 0, float('-inf')).max(dim=1).values |
| return self.classifier(torch.cat([cls_p, mean_p, max_p], dim=-1)) |
| |
| # Загрузка модели |
| tokenizer = AutoTokenizer.from_pretrained(MODEL_DIR) |
| model = AIMSModel(config["model"], config["num_classes"]).to(DEVICE) |
| model.load_state_dict(torch.load(f"{MODEL_DIR}/pytorch_model.bin", map_location=DEVICE, weights_only=True)) |
| model.eval() |
| |
| # Инференс |
| def predict(text: str) -> dict: |
| enc = tokenizer(text, truncation=True, padding="max_length", |
| max_length=config["max_length"], return_tensors="pt") |
| with torch.no_grad(): |
| logits = model(enc["input_ids"].to(DEVICE), enc["attention_mask"].to(DEVICE)) |
| probs = torch.softmax(logits, dim=-1).squeeze().cpu().numpy() |
| idx = int(np.argmax(probs)) |
| return { |
| "label": label_encoder.classes_[idx], |
| "confidence": float(probs[idx]), |
| "probs": dict(zip(label_encoder.classes_, probs.tolist())) |
| } |
| |
| # Пример |
| result = predict("продам аккаунт за 500 звёзд, пишите в лс") |
| print(result) |
| # {'label': 'SELL', 'confidence': 0.99, 'probs': {...}} |
| ``` |
| |
| ### Рекомендуемая логика принятия решения |
| |
| ```python |
| FINAL_THRESHOLD = 0.90 # окончательное решение |
| WARNING_THRESHOLD = 0.80 # предупреждение, требуется проверка |
|
|
| def moderate(text: str) -> dict: |
| result = predict(text) |
| conf = result["confidence"] |
| label = result["label"] |
| |
| if label == "SAFE": |
| verdict = "safe" |
| elif conf >= FINAL_THRESHOLD: |
| verdict = "block" # высокая уверенность — блокировать |
| elif conf >= WARNING_THRESHOLD: |
| verdict = "review" # средняя уверенность — на проверку модератору |
| else: |
| verdict = "safe" # низкая уверенность — пропустить |
| |
| return {"verdict": verdict, "label": label, "confidence": conf} |
| ``` |
| |
| > **Рекомендация:** используйте порог **90-95%**- как границу для автоматической блокировки. При уверенности 80–90% — передавайте сообщение на ручную проверку. Ниже 80% — считайте сообщение безопасным. |
|
|
| --- |
|
|
| ## Ограничения |
|
|
| - Модель оптимизирована для **коротких сообщений** (до 40 токенов). Длинные тексты обрезаются. |
| - Класс `PLS` (попрошайничество) находится в **бета-тестировании** — точность ниже остальных классов из-за малого количества обучающих примеров. |
| - Модель не адаптирована для официальных текстов или новостных материалов. |
| - Возможны ложные срабатывания на текстах с нецензурной лексикой в нейтральном контексте (цитаты, художественные тексты). |
|
|
| --- |
|
|
| ## Лицензия |
|
|
| MIT |
|
|
| --- |
|
|
| ## Цитирование |
|
|
| ```bibtex |
| @misc{aims2025, |
| author = {JustDev23}, |
| title = {AIMS: AI Moderation System}, |
| year = {2025}, |
| publisher = {Hugging Face}, |
| note = {Trained on proprietary Telegram dataset collected during summer--autumn 2025} |
| } |
| ``` |