Как вы очищаете текст от шума (HTML-теги, спецсимволы, стоп-слова) в production RAG-пайплайне?
Краткий тезис
Очистка текста — критический этап предобработки в RAG-пайплайне, определяющий качество индексации и релевантность ответа. В production-среде требуется модульная архитектура очистки: парсинг HTML через BeautifulSoup или trafilatura, удаление спецсимволов регулярными выражениями, фильтрация стоп-слов с учётом языка (NLTK, spaCy, Stanza). Каждый шаг имеет trade-off: чрезмерное удаление (например, знаков препинания в коде C++) разрушает смысл, поэтому необходима настройка под домен.
2. Регулярные выражения для спецсимволов
После извлечения HTML остаются следы экранирования (&, <), лишние пробелы, управляющие символы (нулевой байт, табуляция), эмодзи, которые увеличивают размер индекса и зашумляют embeddings.
Типичные шаги
- Декодирование HTML-сущностей:
html.unescape(text). - Удаление управляющих символов (кроме
\n,\r,\tдля сохранения разметки):import re clean = re.sub(r'[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]', '', text) - Удаление лишних пробелов:
re.sub(r' {2,}', ' ', text). - Удаление символов, не несущих смысла (специфичных для домена): например, замена множественных дефисов и тире на один пробел.
- Работа с эмодзи: в RAG эмодзи обычно удаляются, если только контекст (например, чаты) не требует их сохранения. Используют
re.compile("[^\w\s]")с исключением букв, цифр, пробелов.
Production-рекомендации
- Регулярные выражения компилируют один раз и применяют как sequence.
- Для русского языка дополнительно нормализуют букву «ё» (замена на «е») и удаляют нестандартные кавычки (лапки, уголки) — оставляют только прямые или «ёлочки» в зависимости от задачи.
- Важно: не удалять знаки препинания, нужные для структуры предложений (точка, вопросительный, восклицательный) — они влияют на chunking и смысловое деление.
3. Стоп-слова: список + учёт языка
Стоп-слова — частотные лексемы (предлоги, союзы, междометия), не несущие значимой информации для поиска. В information retrieval их удаление уменьшает размер индекса и ускоряет поиск. В современных RAG с dense retrieval роль стоп-слов снижена, поскольку трансформеры и так их учитывают, но для гибридного поиска (BM25 + embeddings) фильтрация всё ещё полезна.
Инструменты
- NLTK:
from nltk.corpus import stopwords; stopwords.words('russian')— базовый список (~150 слов). - spaCy: модель
ru_core_news_smвключает стоп-слова как свойство токеновtoken.is_stop. Удобно для интеграции. - Stanza: более точный морфологический анализ, позволяет отсеивать стоп-слова с учётом части речи.
Учёт языка
- Для русского языка важно не удалять местоимения в вопросительных конструкциях (что, какой, сколько) — они значимы для QA.
- Для мультиязычных пайплайнов используют словари для каждого языка; библиотека
langdetectопределяет язык и выбирает соответствующий список. - Стоп-слова ≠ низкочастотные токены. Удаляют только общеупотребительные, оставляя термины домена (например, «контроллер» не удалять).
Пример кода
import spacy
nlp = spacy.load("ru_core_news_sm")
doc = nlp("Применение этого метода позволяет улучшить качество.")
filtered = [token.text for token in doc if not token.is_stop]
4. Trade-off: удаление значимых символов (C++)
Классический пример — исходный код на C++, где знаки препинания (скобки, точки с запятой, операторы) несут семантику. Удаление их регулярным выражением [^\w\s] разрушает синтаксис и делает текст бесполезным для индексации.
Проблема
- В RAG-пайплайне, извлекающем код из документации, нужно сохранить символы
{},(),;,#include,->. - Решение: определять блоки кода (по маркдаун-разметке или через тег
<code>) и применять к ним другую стратегию — оставлять все символы, или заменять на понятные слова (например,{→открывающая скобка). - Trade-off: увеличение размера чанка против сохранения смысла. Для поиска по коду лучше использовать специализированные модели (CodeBERT, GraphCodeBERT) и не удалять знаки препинания.
Методика
- Детекция контента с кодом (по регулярному выражению или по наличию ключевых слов
#include,class,def). - Если контент — код, пропустить шаг удаления спецсимволов; применить только нормализацию пробелов.
- Если контент — текст с примерами кода, отдельно обрабатывать блоки через
markdownилиBeautifulSoup(тег<pre><code>).
Практический паттерн
def is_code_block(text):
# Простая эвристика
return bool(re.search(r'^(#include|import |def |class |using namespace)', text, re.MULTILINE))
if is_code_block(chunk):
# не удаляем знаки препинания
clean = chunk.strip()
else:
clean = remove_punctuation(chunk)
Пет-проект для закрепления
Задача: Разработать модуль очистки для корпуса из 1000 разнородных документов (HTML-статьи, Markdown, техническая документация с кодом).
Инструменты: Python, BeautifulSoup, trafilatura, re, spaCy, langdetect.
Шаги:
- Собрать выборку из 3 типов документов: новости (HTML), туториалы (Markdown с блоками кода), документация (HTML с тегами
<code>). - Реализовать функцию
clean_html(html)с использованием trafilatura для извлечения текста и BeautifulSoup для fallback. - Написать класс
TextCleanerс методами:remove_control_chars(text)— регулярное выражение.normalize_whitespace(text)— удаление лишних пробелов, нормализация переносов.remove_stopwords(text, lang)— на основе spaCy (сохранять части речи, удалять стоп-слова не-существительные).handle_code_blocks(text)— детекция кода через эвристику, отключение удаления спецсимволов для таких блоков.
- Применить пайплайн ко всем документам, замерить время выполнения (цель < 50 мс на документ).
- Оценить качество на ручной разметке: соответствие чистого текста исходнику по 10 документам.
Ожидаемый результат: Воспроизводимый модуль очистки с документацией и тестами. Показать, что удаление стоп-слов снизило размер индекса на 15%, но не повлияло на точность ответов RAG (метрика recall@5).
Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 3 | Предобработка текста (токенизация, лемматизация) |
| ... | ... |
Навигация
- Предыдущий: 920
- Следующий: 922
- Индекс: 00. Индекс разборов