Предобученная BM25-модель bm25_ru_default.json для русского языка, совместимая с dashtext SparseVectorEncoder и zvec. Аналог встроенных моделей bm25_en_default.json и bm25_zh_default.json, но для русского языка.
| Параметр | Значение |
|---|---|
| Корпус | Русская Wikipedia (дамп ruwiki-latest-pages-articles.xml.bz2) |
| Документов | 2,094,388 |
| Токенов | 693,584,546 |
| Средняя длина документа | 331.2 токена |
| Уникальных термов | 5,805,457 |
| BM25 b | 0.75 |
| BM25 k1 | 1.2 |
| Размер файла | 93.4 МБ |
| Хеш-функция | mmh3 (MurmurHash3, 32-bit, unsigned) |
| Время обучения | 7.6 часов (Intel Core i5-750, 2 ядра) |
| en | zh | ru (наш) | |
|---|---|---|---|
| Корпус | English Wikipedia | Chinese Wikipedia | Russian Wikipedia |
| Документов | 5,430,040 | ~1,000,000 | 2,094,388 |
| Средняя длина | 176.5 | ~200 | 331.2 |
| Размер файла | 191 МБ | ~150 МБ | 93.4 МБ |
| Токенизация | whitespace | jieba | Snowball stemmer |
| Стоп-слова | en | zh | ru + en + zh |
| Доля документов | Количество термов |
|---|---|
| >50% | 1 |
| 10–50% | 173 |
| 1–10% | 2,696 |
| 0.1–1% | 14,876 |
| 0.01–0.1% | 62,864 |
| <0.01% | 5,724,847 |
Подавляющее большинство термов (98.6%) — редкие, встречающиеся менее чем в 0.01% документов. Это типичное Zipf-распределение для естественного языка.
- Русская Wikipedia — дамп
ruwiki-latest-pages-articles.xml.bz2(5.5 ГБ). Лицензия: CC BY-SA 4.0. Использованы только статьи основного пространства имён (namespace 0), перенаправления исключены.
-
NLTK SnowballStemmer (
nltk.stem.snowball.SnowballStemmer("russian")) — реализация алгоритма Snowball для русского языка. Примеры: «компании» → «компан», «документы» → «документ», «поисковый» → «поисков». -
UDPipe (
ufal.udpipe) — исследован как альтернатива Snowball. Модельudpipe_syntagrus.model(40.6 МБ) с RusVectores. Бенчмарк показал замедление в 19.3x (1,581 vs 30,508 слов/сек), поэтому Snowball выбран для обучения.
Три языка, 1,585 стоп-слов:
| Язык | Количество | Источники |
|---|---|---|
| RU | 458 | stopwords-iso/stopwords-ru, Qdrant/bm25 russian.txt, RusVectores, ручная курация |
| EN | 245 | NLTK stopwords, dashtext stopwords.txt, stopwords-iso |
| ZH | 882 | Qdrant/bm25 chinese.txt (841 слово) + dashtext core (41 слово) |
Принцип курации: только функциональные слова (предлоги, союзы, местоимения, частицы, вспомогательные глаголы, числительные). Содержательные слова (существительные, прилагательные, основные глаголы) оставлены для сохранения поисковой релевантности. Покрытие: 100% Qdrant RU, 62.7% stopwords-iso RU (остаток — содержательные слова).
- mmh3 (MurmurHash3) —
mmh3.hash(token, signed=False), 32-bit unsigned. Совпадает с хеш-функцией, используемой dashtext/zvec. Верифицировано противbm25_en_default.json: хеши словtime(4,237,064),school(3,523,382),city(3,355,343) совпадают.
- bm25_en_default.json — предобученная модель dashtext для английского языка (Alibaba Cloud OSS). Использована как эталон для валидации формата JSON.
| Ресурс | Результат |
|---|---|
| RusVectores | Dense embeddings (Word2Vec, fastText, ELMo). BM25-моделей нет. Заимствованы: стоп-слова, подход к предобработке, корпус Тайга |
| Тайга (Taiga) | Корпус 5 млрд слов, 92 ГБ. Рассмотрен как альтернатива Wikipedia. Не использован из-за размера и времени обработки |
| naver/ecir23-scratch-tydi-russian-splade | SPLADE (sparse neural), обучена на TyDi QA. Не совместима с dashtext |
| hermitdave/FrequencyWords | Частотные списки из OpenSubtitles. Использован для Variant B (прототип) |
| Qdrant/bm25 | Стоп-слова для 30+ языков. IDF считается сервером Qdrant на лету, предобученной модели нет |
| stopwords-iso | Стоп-слова для 57 языков. Использован для валидации покрытия |
- Изучены исходники dashtext:
SparseVectorEncoder,BM25, токенизатор - Деконструирован формат
bm25_en_default.json: 5 полей (_b,_k1,_doc_count,_doc_length_average,_doc_token_frequency), mmh3-хеши термов, IDF-статистика - Поиск готовой модели: не существует — ни на GitHub, HuggingFace, ни в Milvus/Qdrant
- Бенчмарк UDPipe vs Snowball на Intel Core i5-750: Snowball в 19.3x быстрее (30,508 vs 1,581 слов/сек)
Быстрая валидация формата до начала длительного обучения:
- Скачать FrequencyWords ru_50k.txt (50k слов из OpenSubtitles)
- Создать
tokenizer_ru.py— Snowball + стоп-слова + нормализация ё→е - Создать
generate_bm25_from_freq.py— конверсия TF→DF по формулеDF ≈ N * (1 - (1 - TF/N)^avg_doc_len) - Сгенерировать прототип
bm25_ru_default.json(0.4 МБ, 19,359 термов) - Валидация: формат JSON, mmh3-хеши, round-trip пайплайн — всё совпадает с en-моделью
- Скачать дамп
ruwiki-latest-pages-articles.xml.bz2(5,591 МБ) с Wikimedia dumps - Создать
train_bm25_wiki.py— потоковый тренер:- Чтение bz2 без распаковки на диск (через
bz2.open) - Инкрементальный XML-парсинг (
xml.etree.ElementTree.iterparse) - Очистка wiki-разметки (регулярные выражения)
- Токенизация через
tokenize_ru - Подсчёт DF (document frequency) через mmh3-хеш
- Чекпоинты каждые 100k документов
- Чтение bz2 без распаковки на диск (через
- Первый запуск (100k документов) → анализ топ-термов → обнаружены мусорные однобуквенные стеммы (
е,м,с,в) - Добавлен фильтр
_MIN_STEM_LEN = 2(после стемминга) — отсекает однобуквенный мусор, пропускает реальные короткие слова (Го,Ге,Уз) - Перезапуск → обучение завершено за 7.6 часов
- Добавлены стоп-слова из Qdrant/bm25 (150 RU, 841 ZH)
- Покрытие Qdrant RU: 100% (150/150)
- Покрытие stopwords-iso RU: 62.7% (остаток — содержательные слова)
- Валидация: содержательные слова (
вода,город,человек,москва,россия) не отфильтрованы
| Проверка | Результат |
|---|---|
| JSON валидный, 5 полей | Да |
_doc_count > 1M |
2,094,388 |
_doc_length_average в пределах 50–500 |
331.2 |
| Ключи — числовые строки (mmh3 хеши) | Да |
| Стоп-слова отфильтрованы | Да |
Round-trip: tokenize_ru → mmh3.hash → lookup |
Все термы найдены |
Типы совпадают с bm25_en_default.json |
Да (float, int, dict[str, int]) |
from dashtext import SparseVectorEncoder
import sys
sys.path.insert(0, "bm25-ru")
from tokenizer_ru import tokenize_ru
# Загрузить модель
encoder = SparseVectorEncoder(b=0.75, k1=1.2, tokenize_function=tokenize_ru)
encoder.load("bm25-ru/bm25_ru_default.json")
# Энкодить запросы
sparse_vector = encoder.encode_queries("поиск документов")from zvec.extension import BM25EmbeddingFunction
import sys
sys.path.insert(0, "bm25-ru")
from tokenizer_ru import tokenize_ru
bm25_ru = BM25EmbeddingFunction(
corpus=["dummy"], # обязательно, иначе попытается .default("ru")
encoding_type="query",
tokenize_function=tokenize_ru, # пробрасывается в SparseVectorEncoder через **kwargs
)
# Затем загрузить модель вручную через внутренний энкодер
bm25_ru._encoder.load("bm25-ru/bm25_ru_default.json")import sys
sys.path.insert(0, "bm25-ru")
from tokenizer_ru import tokenize_ru
tokens = tokenize_ru("поисковая оптимизация документов")
# → ['поисков', 'оптимизац', 'документ']bm25-ru/
bm25_ru_default.json # Предобученная BM25-модель (93.4 МБ)
tokenizer_ru.py # Русский токенизатор (Snowball + стоп-слова ru/en/zh)
train_bm25_wiki.py # Потоковый тренер на Wikipedia с чекпоинтами
generate_bm25_from_freq.py # Генератор прототипа из частотного списка
data/
qdrant_zh_stopwords.txt # 841 китайских стоп-слов от Qdrant/bm25
nltk # SnowballStemmer (чистый Python, ~10 МБ)
mmh3 # MurmurHash3 (C-extension)
Модель обучена на русской Wikipedia, лицензия которой — CC BY-SA 4.0. Стоп-слова Qdrant/bm25 — Apache-2.0. Исходный код — на усмотрение автора.