Спам — это раковая опухоль мессенджеров. Телеграм с его открытыми API и группами — идеальная среда для рассадника ботов, рекламирующих криптоскам, порно или «заработок в интернете». Стандартные фильтры Telegram работают так себе: регулярки и черные списки — моветон. Хотите реальную защиту? Нужна нейросеть. Своя. Обученная на своих данных. Именно это мы и сделаем.
В статье Как построить антиспам-бота для Telegram на Gemini Flash мы уже касались темы антиспама, но там использовалась LLM, которая требует запросов к API и денег. Наш подход — офлайн, дешево и сердито. Запускаете на своем сервере — никаких лимитов и подписок.
Почему LSTM, а не трансформеры или регулярки?
Регулярки ломаются на первой же обфускации. Трансформеры (BERT, GPT) — это удар по памяти и времени. LSTM — золотая середина. Она видит последовательность слов, улавливает паттерны вроде «заработай 100500$ за час» и при этом весит 50 МБ. На CPU инференс занимает 10–30 мс. Для бота с нагрузкой в тысячу сообщений в минуту — идеально.
Что нам понадобится?
- Python 3.12+ (на момент 28.04.2026 актуальна версия 3.12, но 3.13 уже в релиз-кандидате)
- PyTorch 2.6 или TensorFlow 2.17 — выберу PyTorch, он дает больше контроля
- python-telegram-bot v21.5 — асинхронная обвязка
- Datasets (Hugging Face) для загрузки готовых датасетов
- scikit-learn, nltk, emoji для предобработки
- Сервер (VPS с 2 ГБ RAM и 2 vCPU — за глаза)
Если вы никогда не писали ботов под Telegram, рекомендую сначала пройти курс Создание Telegram-бота — там разжёвана база.
Пошаговый план (чтобы не заблудиться)
1 Сбор датасета: как раздобыть чистые данные для обучения
В открытом доступе есть несколько размеченных коллекций SMS-спама. Но для Telegram они подходят плохо — там короткие сообщения, много эмодзи, URL и мусора. Лучше собрать свой датасет:
- SMS Spam Collection (5572 сообщения) — как основа.
- NUS SMS Corpus (около 1000 сообщений с метками).
- Собственный сбор — выгрузите историю чатов через Telegram API (с согласия участников), разметьте вручную несколько тысяч сообщений.
Ошибка №1: не проверять дубликаты. Если одно и то же сообщение встретится и в тренировочной, и в тестовой выборке, точность будет липовой. Удалите дубликаты сразу.
import pandas as pd
from datasets import load_dataset
# Загружаем SMS Spam Collection
sms = load_dataset("sms_spam", split="train").to_pandas()
sms.columns = ["label", "text"]
sms["label"] = sms["label"].map({"ham": 0, "spam": 1})
# Добавляем свои собранные данные (колонки: text, label)
my_data = pd.read_csv("my_telegram_spam.csv")
dataset = pd.concat([sms, my_data], ignore_index=True)
dataset.drop_duplicates(subset="text", inplace=True)
print(dataset.shape) # (6500, 2) - примерно
2 Предобработка: как не утопить модель в мусоре
Сырой текст нельзя скормить нейросети. Нужно:
- Привести к нижнему регистру.
- Заменить URL на токен
<URL>— иначе модель выучит конкретные ссылки. - Удалить или заменить эмодзи на текстовое описание (библиотека
emoji). - Лемматизировать (pymorphy2 для русского, nltk для английского).
- Удалить стоп-слова, но не все — иногда спам использует «неестественные» стоп-слова для обхода фильтров.
Ошибка №2: полная очистка от пунктуации. В спаме часто используют дефисы, звёздочки, многоточия — они могут быть сигнатурой. Удаляйте только лишнее, оставляйте подозрительные символы.
import re
import emoji
import nltk
from nltk.corpus import stopwords
nltk.download('stopwords')
def clean_text(text: str, lang='english') -> str:
text = text.lower()
text = re.sub(r'https?://\S+', '', text)
text = emoji.demojize(text) # :joy: -> joy
words = nltk.word_tokenize(text)
stop = set(stopwords.words(lang))
# оставляем короткие слова (они могут быть спам-ключевые)
words = [w for w in words if w not in stop or len(w) <= 2]
return ' '.join(words)
dataset['clean'] = dataset['text'].apply(clean_text)
3 Токенизация и создание эмбеддингов
Каждое слово превращаем в числовой индекс. Используем Tokenizer из Keras (или torchtext). Ограничиваем словарь 10000 самых частых токенов.
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences
MAX_WORDS = 10000
MAX_LEN = 100 # макс. длина сообщения
tokenizer = Tokenizer(num_words=MAX_WORDS)
tokenizer.fit_on_texts(dataset['clean'])
sequences = tokenizer.texts_to_sequences(dataset['clean'])
X = pad_sequences(sequences, maxlen=MAX_LEN, padding='post')
y = dataset['label'].values
4 Архитектура LSTM: почему без dropout — катастрофа
Берём Embedding слой, затем LSTM (лучше два слоя) и Dense с сигмоидой. Переобучение — бич маленьких датасетов. Dropout 0.5 между слоями спасает.
import torch
import torch.nn as nn
class SpamLSTM(nn.Module):
def __init__(self, vocab_size, embedding_dim=128, hidden_dim=64):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embedding_dim)
self.lstm1 = nn.LSTM(embedding_dim, hidden_dim, batch_first=True, bidirectional=True)
self.dropout = nn.Dropout(0.5)
self.lstm2 = nn.LSTM(hidden_dim * 2, hidden_dim, batch_first=True, bidirectional=True)
self.fc = nn.Linear(hidden_dim * 2, 1)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
x = self.embedding(x)
x, _ = self.lstm1(x)
x = self.dropout(x)
x, _ = self.lstm2(x)
x = x[:, -1, :] # только последний выход
out = self.sigmoid(self.fc(x))
return out.squeeze()
model = SpamLSTM(vocab_size=MAX_WORDS)
Если вам интересна тема семантической обработки текста в Telegram, рекомендую прочитать Семантический поиск в Telegram: разбираем кейс с Habr и строим свой аналог — там показано, как использовать эмбеддинги для поиска похожих сообщений.
5 Обучение: бинарная кросс-энтропия и дисбаланс классов
В обучающей выборке обычно 85–90% «не спам» и 10–15% «спам». Просто Accuracy не показатель. Используем weighted loss или Focal Loss. Я предпочитаю взвешенный BCE с весом обратно пропорционально частоте класса.
from sklearn.utils.class_weight import compute_class_weight
import numpy as np
class_weights = compute_class_weight('balanced', classes=np.unique(y), y=y)
weight_tensor = torch.tensor([class_weights[1]], dtype=torch.float32)
criterion = nn.BCEWithLogitsLoss(pos_weight=weight_tensor)
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# Обучайте 10 эпох с early stopping
После обучения сохраняем модель и токенизатор — они понадобятся боту.
6 Интеграция с Telegram-ботом
Пишем асинхронного бота на python-telegram-bot. При получении сообщения прогоняем текст через предобработку и модель. Если вероятность спама > 0.7 — удаляем или отправляем на премодерацию.
from telegram.ext import Application, MessageHandler, filters
import joblib
tokenizer = joblib.load('tokenizer.pkl')
model = torch.jit.load('spam_model.pt', map_location='cpu')
model.eval()
def predict(text: str) -> bool:
clean = clean_text(text)
seq = tokenizer.texts_to_sequences([clean])
padded = pad_sequences(seq, maxlen=MAX_LEN, padding='post')
with torch.no_grad():
prob = model(torch.tensor(padded)).item()
return prob > 0.7
async def handle_message(update, context):
if predict(update.message.text):
await update.message.delete()
# можно еще забанить отправителя на время
else:
pass
app = Application.builder().token("YOUR_TOKEN").build()
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
app.run_polling()
Не используйте блокирующий код (time.sleep) в асинхронном обработчике — похороните пропускную способность. Всё предсказание делайте в отдельном потоке через run_in_executor или используйте Torch в инференсе асинхронно.
7 Деплой и мониторинг
Запускаем бота на VPS под systemd. Добавляем метрики: количество удалённых спам-сообщений, ложные срабатывания (если есть кнопка «Это не спам»). Регулярно дообучайте модель на новых данных — боты адаптируются.
Возможные ошибки и как их избежать
- Переобучение на коротких сообщениях. Одно слово типа «квратира» может оказаться спамом, а «квартира» — нет. Добавьте аугментацию: заменяйте случайные символы.
- Игнорирование эмодзи. Спамеры любят эмодзи для привлечения внимания. Демоджизируйте, а не удаляйте — иначе потеряете сигнал.
- Пренебрежение кешированием токенизатора. Не грузите токенизатор заново для каждого сообщения — сохраните в глобальной переменной.
- Нет гибридной защиты. LSTM не панацея. Добавьте белые списки доверенных пользователей и капчу для новых.
Кстати, если вы захотите пойти дальше и автоматизировать не только фильтрацию, но и общение с пользователями, взгляните на статью Генеративный ИИ-бот для заказов: как сделать, чтобы он не разговаривал, а работал — там про создание продуктивного бота.
Если вы чувствуете, что одного обучения мало, пройдите курс по продвижению ботов — Создание Telegram-бота и продвижение в мессенджерах — там рассказывают, как привлечь аудиторию и монетизировать.
А напоследок: LSTM — это динозавр, но динозавр, который до сих пор способен уделать новомодные модели на узкой задаче классификации, если подойти с умом. Соберите датасет, подберите гиперпараметры, и ваш чат будет очищен от спама на 95%+. Остальные 5% — всегда оставляйте для пользовательских жалоб, чтобы учить модель дальше.