Предобработка текста
🎯 Цель
После прочтения этой главы:
- Знаете очистку реальных «грязных» текстовых данных
- Можете находить сложные паттерны через Regex
- Можете работать с HuggingFace tokenizer
- Можете писать production текстовый pipeline
Что нужно изучить
- Text cleaning — HTML, URL, emoji, punctuation
- Unicode normalization — NFC, NFD, NFKC
- Encoding issues — UTF-8, Windows-1251, latin1
- Regex — pattern matching, capture groups
- Subword tokenization — BPE, WordPiece, SentencePiece
- **HuggingFace
tokenizers**library - Стратегии Truncation и padding
- Multi-language handling
Библиотеки
pip install nltk spacy transformers tokenizers ftfy unidecode emoji
pip install beautifulsoup4 lxml # HTML parsing
Важные темы
Text cleaning pipeline
Реальный текст приходит примерно в таком виде:
"<p>Salom!!! 😊 Mening email: ali@gmail.com, telefon: +99890-123-45-67. Marketing manager 🚀</p>"
Наша задача — «очистить» для ML:
"salom mening email telefon marketing manager"
Subword Tokenization — что и зачем?
Проблема классической word-level tokenization:
- Vocabulary очень большой (миллионы слов)
- “running”, “runs”, “runner” — обрабатываются раздельно
- Unknown words (OOV) — превращаются в
[UNK]
Решение Subword tokenization:
| Algorithm | Where used |
|---|---|
| BPE (Byte-Pair Encoding) | GPT, RoBERTa, Llama |
| WordPiece | BERT, DistilBERT |
| SentencePiece (BPE/Unigram) | T5, Llama, ALBERT, multilingual models |
Пример (BPE):
"unfortunately" → ["un", "for", "tun", "ate", "ly"]
Новое слово тоже разделяется на части, проблемы OOV нет.
Примеры кода
Основная text cleaning
import re
from bs4 import BeautifulSoup
import emoji
import unicodedata
def clean_text(text: str) -> str:
# 1. Удаление HTML
text = BeautifulSoup(text, "lxml").get_text()
# 2. URL
text = re.sub(r"https?://\S+|www\.\S+", "", text)
# 3. Email
text = re.sub(r"\S+@\S+", "", text)
# 4. Телефонные номера (простой)
text = re.sub(r"\+?\d[\d\-\s\(\)]{7,}\d", "", text)
# 5. Преобразование emoji в text или удаление
text = emoji.demojize(text, delimiters=("", "")) # 😊 → smiling_face
# или: text = emoji.replace_emoji(text, "")
# 6. Unicode normalize
text = unicodedata.normalize("NFKC", text)
# 7. Special chars — только alphanumeric + space
text = re.sub(r"[^\w\s]", " ", text, flags=re.UNICODE)
# 8. Множественные пробелы
text = re.sub(r"\s+", " ", text).strip()
# 9. Lowercase
text = text.lower()
return text
# Test
dirty = "<p>Salom!!! 😊 Mening email: ali@gmail.com.</p>"
print(clean_text(dirty))
# "salom mening email"
Encoding fix (ftfy)
from ftfy import fix_text
broken = "“Helloâ€\x9d" # неверный encoded
print(fix_text(broken))
# "Hello"
Regex-паттерны (полезные)
import re
# Hashtag (#ai #machinelearning)
hashtags = re.findall(r"#(\w+)", text)
# Mention (@username)
mentions = re.findall(r"@(\w+)", text)
# Даты (2024-05-28, 28/05/2024)
dates = re.findall(r"\b\d{4}[-/]\d{2}[-/]\d{2}\b|\b\d{2}[-/]\d{2}[-/]\d{4}\b", text)
# Телефонные номера (UZ)
phones = re.findall(r"\+998[\s\-]?\d{2}[\s\-]?\d{3}[\s\-]?\d{2}[\s\-]?\d{2}", text)
# IP addresses
ips = re.findall(r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b", text)
# URL
urls = re.findall(r"https?://[^\s<>\"'{}|\\^`\[\]]+", text)
HuggingFace Tokenizer
from transformers import AutoTokenizer
# BERT
tokenizer = AutoTokenizer.from_pretrained("bert-base-multilingual-cased")
text = "Salom dunyo! Bu mashina o'rganish."
tokens = tokenizer.tokenize(text)
# ['sal', '##om', 'duny', '##o', '!', 'bu', 'mash', '##ina', "'", 'ran', '##ish', '.']
# Token IDs
ids = tokenizer.encode(text, add_special_tokens=True)
# [101, ..., 102] (добавлены [CLS] и [SEP])
# Decode (обратно)
decoded = tokenizer.decode(ids)
# Batch processing (padding + truncation)
batch = ["Salom!", "Bu uzunroq matn. Bir necha gap bor."]
encoded = tokenizer(
batch,
padding=True,
truncation=True,
max_length=128,
return_tensors="pt",
)
# {'input_ids': tensor(...), 'attention_mask': tensor(...), 'token_type_ids': tensor(...)}
Custom BPE tokenizer (HuggingFace tokenizers)
from tokenizers import Tokenizer
from tokenizers.models import BPE
from tokenizers.trainers import BpeTrainer
from tokenizers.pre_tokenizers import Whitespace
# 1. Train custom tokenizer
tokenizer = Tokenizer(BPE(unk_token="[UNK]"))
tokenizer.pre_tokenizer = Whitespace()
trainer = BpeTrainer(
vocab_size=30000,
special_tokens=["[UNK]", "[CLS]", "[SEP]", "[PAD]", "[MASK]"],
)
files = ["data/uzbek_corpus.txt"]
tokenizer.train(files, trainer)
# 2. Save / load
tokenizer.save("uzbek_bpe.json")
tokenizer = Tokenizer.from_file("uzbek_bpe.json")
# 3. Encode
encoded = tokenizer.encode("Salom dunyo")
print(encoded.tokens)
print(encoded.ids)
Стратегии Truncation и padding
texts = [
"Short text",
"Medium length text with some more words",
"Very long text " * 100,
]
# Truncation: укоротить до max_length
encoded = tokenizer(
texts,
truncation=True, # обрезать то, что больше max_length
max_length=128,
padding="max_length", # заполнить [PAD] до 128
return_tensors="pt",
)
# Другие стратегии padding:
# padding="longest" — подгонка под самый длинный текст (экономит memory)
# padding=False — без padding (для single sample)
# Dynamic padding (самый длинный в batch):
encoded = tokenizer(texts, padding=True, truncation=True, max_length=512)
Sliding window — для длинных текстов
def chunk_text(text: str, tokenizer, max_length: int = 512, stride: int = 50):
"""Разделение длинного текста на overlapping chunks."""
tokens = tokenizer.encode(text, add_special_tokens=False)
chunks = []
for i in range(0, len(tokens), max_length - stride):
chunk_tokens = tokens[i:i + max_length]
chunk_text = tokenizer.decode(chunk_tokens)
chunks.append(chunk_text)
return chunks
# Пример: текст в 10000 token → 20 чанков по 512 token
long_text = "..." * 5000
chunks = chunk_text(long_text, tokenizer, max_length=512, stride=50)
Multi-language handling
from langdetect import detect
def preprocess_multilingual(text: str) -> dict:
lang = detect(text)
if lang == "en":
cleaned = clean_text_english(text)
elif lang == "uz":
cleaned = clean_text_uzbek(text)
elif lang == "ru":
cleaned = clean_text_russian(text)
else:
cleaned = clean_text(text)
return {"language": lang, "cleaned_text": cleaned}
Интеграция с backend
Text preprocessing service
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class TextInput(BaseModel):
text: str
operations: list[str] = ["clean", "tokenize"]
class TextOutput(BaseModel):
original: str
cleaned: str
tokens: list[str]
language: str
stats: dict
@app.post("/preprocess", response_model=TextOutput)
def preprocess(data: TextInput):
original = data.text
cleaned = clean_text(original) if "clean" in data.operations else original
tokens = tokenizer.tokenize(cleaned) if "tokenize" in data.operations else []
return TextOutput(
original=original,
cleaned=cleaned,
tokens=tokens,
language=detect(original) if original.strip() else "unknown",
stats={
"original_length": len(original),
"cleaned_length": len(cleaned),
"token_count": len(tokens),
},
)
Bulk processing (Celery)
@celery_app.task
def preprocess_dataset(csv_path: str, text_column: str):
df = pd.read_csv(csv_path)
df["cleaned"] = df[text_column].apply(clean_text)
output_path = csv_path.replace(".csv", "_cleaned.csv")
df.to_csv(output_path, index=False)
return {"output": output_path, "n_rows": len(df)}
Ресурсы
- HuggingFace Tokenizers docs — huggingface.co/docs/tokenizers
- “Natural Language Processing with Transformers” — Lewis Tunstall (O’Reilly)
- Regex101 — regex101.com (interactive regex tester)
- Unicode normalization — Unicode.org docs
- ftfy library — github.com/rspeer/python-ftfy
🏋️ Упражнения
🟢 Easy
- Попробуйте функцию
clean_textвыше на «грязном» узбекском тексте. - Найдите телефонные номера в тексте через Regex.
- Узбекский текст через BERT tokenizer — сколько token получается?
🟡 Medium
- Custom BPE: обучите BPE tokenizer на 100MB узбекского текста, сравните vocabulary с default
bert-multilingual. - Sliding window: разделите книгу в 50,000 слов на чанки по 512 token.
- Multi-language preprocessor: class, применяющий разный preprocessing pipeline в зависимости от языка.
🔴 Hard
- Production text pipeline: FastAPI сервис, делающий real-time clean/tokenize/embed для текста из Kafka stream.
- Custom tokenizer service: training и inference custom tokenizer через REST API.
- NER + Anonymization: поиск PII (personal info) в тексте и замена на placeholders
[NAME],[EMAIL],[PHONE](для GDPR).
Capstone
notebooks/month-04/05_text_preprocessing.ipynb:
- Соберите 10,000 сообщений из узбекских Telegram-каналов
- Построение полного cleaning pipeline
- Custom BPE tokenizer
- Сравнение с pretrained BERT tokenizer (vocab coverage, OOV rate)
✅ Чек-лист
- Знаю удаление HTML, URL, email, телефонов
- Что такое Unicode normalization (NFKC)
- Умею работать с Regex
- Понимаю subword tokenization BPE/WordPiece
- Работа с HuggingFace tokenizer
- Стратегии Truncation и padding
- Могу обучить custom BPE tokenizer
- Multi-language preprocessing pipeline
Переходим к Введение в Transformers.