Skip to content

Latest commit

 

History

History
206 lines (153 loc) · 13.1 KB

File metadata and controls

206 lines (153 loc) · 13.1 KB

BM25-Ru: предобученная BM25-модель для русского языка

Предобученная 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 ядра)

Сравнение с встроенными моделями dashtext

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

Распределение DF (document frequency)

Доля документов Количество термов
>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 языков. Использован для валидации покрытия

Процесс создания

Этап 1: Исследование (6 июня 2026)

  1. Изучены исходники dashtext: SparseVectorEncoder, BM25, токенизатор
  2. Деконструирован формат bm25_en_default.json: 5 полей (_b, _k1, _doc_count, _doc_length_average, _doc_token_frequency), mmh3-хеши термов, IDF-статистика
  3. Поиск готовой модели: не существует — ни на GitHub, HuggingFace, ни в Milvus/Qdrant
  4. Бенчмарк UDPipe vs Snowball на Intel Core i5-750: Snowball в 19.3x быстрее (30,508 vs 1,581 слов/сек)

Этап 2: Variant B — прототип (6 июня)

Быстрая валидация формата до начала длительного обучения:

  1. Скачать FrequencyWords ru_50k.txt (50k слов из OpenSubtitles)
  2. Создать tokenizer_ru.py — Snowball + стоп-слова + нормализация ё→е
  3. Создать generate_bm25_from_freq.py — конверсия TF→DF по формуле DF ≈ N * (1 - (1 - TF/N)^avg_doc_len)
  4. Сгенерировать прототип bm25_ru_default.json (0.4 МБ, 19,359 термов)
  5. Валидация: формат JSON, mmh3-хеши, round-trip пайплайн — всё совпадает с en-моделью

Этап 3: Variant A — обучение на Wikipedia (6–7 июня)

  1. Скачать дамп ruwiki-latest-pages-articles.xml.bz2 (5,591 МБ) с Wikimedia dumps
  2. Создать train_bm25_wiki.py — потоковый тренер:
    • Чтение bz2 без распаковки на диск (через bz2.open)
    • Инкрементальный XML-парсинг (xml.etree.ElementTree.iterparse)
    • Очистка wiki-разметки (регулярные выражения)
    • Токенизация через tokenize_ru
    • Подсчёт DF (document frequency) через mmh3-хеш
    • Чекпоинты каждые 100k документов
  3. Первый запуск (100k документов) → анализ топ-термов → обнаружены мусорные однобуквенные стеммы (е, м, с, в)
  4. Добавлен фильтр _MIN_STEM_LEN = 2 (после стемминга) — отсекает однобуквенный мусор, пропускает реальные короткие слова (Го, Ге, Уз)
  5. Перезапуск → обучение завершено за 7.6 часов

Этап 4: Доработка стоп-слов (во время обучения)

  1. Добавлены стоп-слова из Qdrant/bm25 (150 RU, 841 ZH)
  2. Покрытие Qdrant RU: 100% (150/150)
  3. Покрытие stopwords-iso RU: 62.7% (остаток — содержательные слова)
  4. Валидация: содержательные слова (вода, город, человек, москва, россия) не отфильтрованы

Этап 5: Верификация (7 июня)

Проверка Результат
JSON валидный, 5 полей Да
_doc_count > 1M 2,094,388
_doc_length_average в пределах 50–500 331.2
Ключи — числовые строки (mmh3 хеши) Да
Стоп-слова отфильтрованы Да
Round-trip: tokenize_rummh3.hash → lookup Все термы найдены
Типы совпадают с bm25_en_default.json Да (float, int, dict[str, int])

Использование

С dashtext (на машине с AVX2)

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("поиск документов")

С zvec (на машине с AVX2)

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")

Только токенизация (без dashtext/zvec)

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. Исходный код — на усмотрение автора.