Лексическая плотность — семейство показателей, которые сопоставляют количество лексических единиц текста с выбранной единицей нормализации. В словной версии знаменателем служит число слов, в клаузальной — число ранговых клауз.
Метрика обрабатывает текст и выдает одно числовое значение на документ, поле, блок или другой фрагмент. Она описывает состав текста по лексическим и служебным единицам. Она не измеряет соответствие запросу, количество фактов, информационную энтропию, семантическую близость или качество текста.
Лексическая плотность отличается от:
- лексического разнообразия — доли или распределения разных лемм;
- частоты ключевой фразы — числа вхождений конкретной последовательности;
- термовой частоты в BM25 — частоты терма в конкретном поле документа;
- информационной плотности в теории информации — величины, основанной на вероятностях и неожиданности событий.
Victoria Johansson (Виктория Йоханссон) определяет лексическое разнообразие через число разных слов, а лексическую плотность — через долю существительных, глаголов, прилагательных и части наречий. Она отдельно показывает, что повтор одних и тех же лексических слов может дать низкое разнообразие при высокой плотности.
Место в поисковой системе
Лексическая плотность не входит в стандартную схему BM25. BM25 использует частоту терма, обратную частоту документа и нормализацию длины поля. Параметр k1 управляет насыщением частоты, параметр b — влиянием длины поля.
Контур
- Краулер, загрузчик или прикладной источник получает документ.
- Из HTML, PDF, карточки товара или другого объекта извлекается нужное текстовое поле.
- Система определяет язык.
- Токенизатор и модель частей речи размечают текст.
- Экстрактор считает лексическую плотность по зафиксированной формуле.
- Значение сохраняется как аналитический или модельный признак.
- Классификатор либо модель ранжирования использует его только после проверки на размеченной выборке.
Лексическую плотность лучше вычислять до поискового анализатора. Анализатор Elasticsearch может удалять стоп-слова, добавлять синонимы, порождать n-граммы и менять токеновый поток. Плотность, рассчитанная после таких преобразований, описывает индексное представление, а не исходный текст.
Вход
- сырой текст или размеченная последовательность токенов;
- язык;
- поле документа;
- версия токенизатора;
- версия модели частей речи;
- политика отнесения частей речи к лексическим единицам.
Выход
Минимальная запись признака:
document_id
field_name
language
method
extractor_version
model_name
model_version
token_count
lexical_count
lexical_density
calculated_atОдного поля lexical_density недостаточно. Без метода, языка и версии модели значение нельзя воспроизвести.
Использование в ранжировании
Числовое значение можно сохранить как обычное числовое поле, признак LTR или rank_feature. Elasticsearch разрешает использовать rank_feature для числового усиления документа, но такое поле предполагает заранее заданное положительное или отрицательное влияние на оценку. Оно также однозначное и хранит только положительные значения.
Для лексической плотности монотонность не доказана. Слишком низкое значение может указывать на служебный или разговорный текст. Слишком высокое — на заголовок, перечень существительных, телеграфный стиль или повтор. Поэтому прямой коэффициент вида
обычно слабее, чем:
- интервальное кодирование признака;
- нелинейное преобразование;
- взаимодействие с языком, жанром и длиной;
- добавление в многопризнаковую модель;
- отдельная модель качества документа.
Место в рекомендательной системе
В совместной фильтрации и отборе кандидатов по журналу взаимодействий лексическая плотность не нужна: эти этапы могут вообще не обрабатывать текст.
Она появляется только там, где рекомендательная система уже использует:
- отзывы;
- описания товаров;
- публикации;
- аннотации;
- текстовые объяснения рекомендаций.
Обзоры рекомендательных систем на основе отзывов рассматривают текст как источник признаков пользователя и объекта. Лексическая плотность может быть одним из дешевых признаков такого текстового блока, но не заменяет извлечение аспектов, тональности, тематики или векторного представления.
Контур
- Система собирает отзывы или описания.
- Текст очищается и размечается.
- Извлекаются текстовые признаки.
- Модель оценивает отзыв, объект, объяснение или пару «пользователь — объект».
- Рекомендации ранжируются.
- Признак проверяется удалением из модели.
Без измеримого прироста лексическую плотность нужно удалить. Дешевизна вычисления не оправдывает бесполезный признак.
История и происхождение
Jean Ure (Джин Юре) в 1971 году использовала лексическую плотность для различения регистров речи. Ее версия делит число слов с лексическими свойствами на число орфографических слов:
M. A. K. Halliday (Майкл Халлидей) использовал лексическую плотность при сравнении устной и письменной речи. Его книга Spoken and Written Language вышла в 1985 году.
В части литературы ему приписывают словную формулу с более строгим определением лексической единицы. В работах системно-функционального направления используется развитая версия — среднее число лексических единиц на ранговую клаузу.
Формулы имеют разные знаменатели, диапазоны и интерпретации. Реализация обязана называть конкретный вариант:
lexical_density_word_ratio_v1
lexical_items_per_ranking_clause_v1Запись lexical_density без метода неполна.
Математическая основа
1. Словная лексическая плотность
Пусть:
- T(d) — множество словных токенов документа d;
- N(d)=∣T(d)∣ — число словных токенов;
- P(t) — часть речи токена t;
- Plex — множество частей речи, признанных лексическими.
Тогда:
Диапазон:
При записи в процентах результат умножают на 100.
Типичная инженерная политика:
Она не является универсальным лингвистическим стандартом. Universal Dependencies относит к открытым классам также INTJ, а AUX отделяет от VERB.
Что увеличивает значение
- добавление существительного;
- добавление смыслового глагола;
- добавление прилагательного;
- добавление наречия, включенного в политику;
- удаление служебного слова.
Что уменьшает значение
- местоимения;
- предлоги;
- союзы;
- частицы;
- вспомогательные глаголы;
- определители;
- другие единицы, исключенные политикой.
Чувствительность к ошибке
Если знаменатель не меняется, одна ошибка классификации токена меняет результат на:
Для текста из пяти слов ошибка дает сдвиг 0.2. Для текста из тысячи слов — 0.001.
Поэтому показатель на коротких запросах, заголовках и подзаголовках нестабилен.
2. Лексические единицы на ранговую клаузу
Пусть:
- I(d) — число лексических единиц;
- C(d) — число ранговых клауз.
Тогда:
Диапазон сверху не ограничен.
Здесь единицей подсчета может быть не отдельное слово. Например, составной глагол может считаться одной лексической единицей. Для точной реализации требуются:
- сегментация клауз;
- различение главных и вложенных клауз;
- правила выделения составных лексических единиц;
- языковая схема разбора.
Обычная модель частей речи этого не делает.
Ошибка в числе клауз особенно опасна на коротком тексте. Если система вместо C насчитала C+1, абсолютное расхождение равно:
При I=8 и C=2 ошибка дает:
3. Объединение нескольких полей
Нельзя бездумно усреднять плотность заголовка, описания и отзывов:
Так короткий заголовок получает тот же вес, что и основное описание.
Для общей словной плотности нужен взвешенный по токенам расчет:
Если поля должны иметь продуктовые веса, используется отдельная формула:
Коэффициенты αf должны подбираться по целевой метрике, а не по вкусу разработчика.
4. Нормализация
Сравнивать сырые значения между языками и жанрами нельзя. Морфология, токенизация и распределение частей речи меняют показатель. Johansson прямо предупреждает о языковой зависимости.
Практический вариант — нормализация внутри группы g:
Группа может задаваться сочетанием:
language × document_type × field_nameНапример:
ru × product_description × body
en × product_description × body
ru × user_review × review_textЧто метрика не измеряет
Лексическая плотность не знает:
- о запросе;
- о намерении пользователя;
- о тематике документа;
- о правильности фактов;
- о качестве аргументации;
- о семантической близости;
- о полезности документа;
- о редкости терминов в коллекции;
- о поведении пользователей.
Два нерелевантных документа могут иметь одинаковую плотность. Один документ может иметь максимальную плотность из-за повторения двух слов.
Алгоритм
Словная версия
- Получить нужное текстовое поле.
- Определить язык.
- Удалить HTML-разметку и шаблонные блоки.
- Разбить текст на токены.
- Исключить пунктуацию и символы по зафиксированному правилу.
- Назначить каждому токену часть речи.
- Отнести токен к лексическому или служебному классу.
- Посчитать L и N.
- Вычислить L/N.
- Сохранить значение вместе с версиями всех компонентов.
Сложность
После получения частей речи:
- время: O(N);
- дополнительная память для одного счетчика: O(1);
- память для подсчета повторов: O(V), где V — число разных лемм.
Полный контур с spaCy не имеет памяти O(1): библиотека строит объект документа и хранит разметку токенов. Практическая память составляет O(N) на документ или пакет.
Пример
Используем политику:
NOUN, PROPN, VERB, ADJ, ADV → лексические
остальные части речи → служебныеТекст A
дешевые кроссовки дешевые кроссовкиРазметка:
дешевые ADJ
кроссовки NOUN
дешевые ADJ
кроссовки NOUNУникальных лексических лемм две:
Текст получает максимальную плотность, хотя состоит из повтора.
Текст B
нажмите здесь, чтобы посмотреть товары со скидкойРазметка:
нажмите VERB
здесь ADV
чтобы SCONJ
посмотреть VERB
товары NOUN
со ADP
скидкой NOUNТекст C
замена аккумуляторов задерживается в ГерманииРазметка:
замена NOUN
аккумуляторов NOUN
задерживается VERB
в ADP
Германии PROPNПорядок по лексической плотности:
A > C > BИз этого нельзя вывести порядок по качеству или релевантности.
Пример на Python
Что проверяем кодом
Код считает словную лексическую плотность по UPOS-разметке. Дополнительно он считает:
- долю уникальных лексических лемм;
- максимальную долю одной леммы;
- долю токенов с неизвестной частью речи.
Первый пример работает на вручную размеченных данных и дает воспроизводимый результат. Для сырого русского текста добавлена отдельная функция spaCy без молчаливого перехода на стоп-слова.
Установка
python -m pip install "spacy>=3.8,<4"
python -m spacy download ru_core_news_smКод
from __future__ import annotations
from collections import Counter
from dataclasses import dataclass
from typing import Iterator, Mapping, Sequence
LEXICAL_UPOS = frozenset({
"NOUN",
"PROPN",
"VERB",
"ADJ",
"ADV",
})
NON_WORD_UPOS = frozenset({
"PUNCT",
"SYM",
})
@dataclass(frozen=True, slots=True)
class DensityResult:
token_count: int
lexical_count: int
lexical_density: float | None
unique_lexeme_ratio: float | None
max_lexeme_share: float | None
unknown_pos_ratio: float
def density_from_upos(
tokens: Sequence[tuple[str, str]],
) -> DensityResult:
"""
Рассчитывает словную лексическую плотность.
Каждый элемент tokens:
(лемма, UPOS)
Пунктуация и символы исключаются из знаменателя.
Токены с UPOS=X остаются в знаменателе и считаются
неизвестными, а не лексическими.
"""
words = [
(lemma.casefold(), upos)
for lemma, upos in tokens
if upos not in NON_WORD_UPOS
]
token_count = len(words)
if token_count == 0:
return DensityResult(
token_count=0,
lexical_count=0,
lexical_density=None,
unique_lexeme_ratio=None,
max_lexeme_share=None,
unknown_pos_ratio=0.0,
)
lexical_lemmas = [
lemma
for lemma, upos in words
if upos in LEXICAL_UPOS
]
lexical_count = len(lexical_lemmas)
lexical_density = lexical_count / token_count
unknown_count = sum(
upos == "X"
for _, upos in words
)
unknown_pos_ratio = unknown_count / token_count
if lexical_count == 0:
unique_lexeme_ratio = None
max_lexeme_share = None
else:
frequencies = Counter(lexical_lemmas)
unique_lexeme_ratio = (
len(frequencies) / lexical_count
)
max_lexeme_share = (
max(frequencies.values()) / lexical_count
)
return DensityResult(
token_count=token_count,
lexical_count=lexical_count,
lexical_density=lexical_density,
unique_lexeme_ratio=unique_lexeme_ratio,
max_lexeme_share=max_lexeme_share,
unknown_pos_ratio=unknown_pos_ratio,
)
def density_from_raw_texts(
texts: Mapping[str, str],
model_name: str = "ru_core_news_sm",
) -> Iterator[tuple[str, DensityResult, str]]:
"""
Размечает сырые тексты через spaCy.
Никакого перехода на списки стоп-слов.
При отсутствии модели выполнение завершается с ошибкой.
"""
try:
import spacy
except ImportError as exc:
raise RuntimeError(
'Установите spaCy: '
'python -m pip install "spacy>=3.8,<4"'
) from exc
try:
nlp = spacy.load(
model_name,
disable=["ner", "parser"],
)
except OSError as exc:
raise RuntimeError(
f"Модель {model_name!r} не установлена. "
f"Выполните: python -m spacy download {model_name}"
) from exc
if not {"tagger", "morphologizer"} & set(nlp.pipe_names):
raise RuntimeError(
f"Модель {model_name!r} не выдает части речи."
)
model_id = ":".join([
str(nlp.meta.get("lang", "unknown")),
str(nlp.meta.get("name", model_name)),
str(nlp.meta.get("version", "unknown")),
])
document_ids = list(texts)
document_texts = (
texts[document_id]
for document_id in document_ids
)
documents = nlp.pipe(
document_texts,
batch_size=128,
)
for document_id, document in zip(
document_ids,
documents,
strict=True,
):
tagged_tokens = [
(
(token.lemma_ or token.text).casefold(),
token.pos_,
)
for token in document
if token.is_alpha
]
yield (
document_id,
density_from_upos(tagged_tokens),
model_id,
)
def format_float(value: float | None) -> str:
return "n/a" if value is None else f"{value:.3f}"
if __name__ == "__main__":
samples = {
"repeat": [
("дешевый", "ADJ"),
("кроссовок", "NOUN"),
("дешевый", "ADJ"),
("кроссовок", "NOUN"),
],
"navigation": [
("нажать", "VERB"),
("здесь", "ADV"),
("чтобы", "SCONJ"),
("посмотреть", "VERB"),
("товар", "NOUN"),
("со", "ADP"),
("скидка", "NOUN"),
],
"fact": [
("замена", "NOUN"),
("аккумулятор", "NOUN"),
("задерживаться", "VERB"),
("в", "ADP"),
("Германия", "PROPN"),
],
}
for sample_id, tagged_tokens in samples.items():
result = density_from_upos(tagged_tokens)
print(sample_id)
print(f" tokens: {result.token_count}")
print(f" lexical: {result.lexical_count}")
print(
" density: "
f"{format_float(result.lexical_density)}"
)
print(
" unique_lexeme_ratio: "
f"{format_float(result.unique_lexeme_ratio)}"
)
print(
" max_lexeme_share: "
f"{format_float(result.max_lexeme_share)}"
)
print()Вывод
repeat
tokens: 4
lexical: 4
density: 1.000
unique_lexeme_ratio: 0.500
max_lexeme_share: 0.500
navigation
tokens: 7
lexical: 5
density: 0.714
unique_lexeme_ratio: 1.000
max_lexeme_share: 0.200
fact
tokens: 5
lexical: 4
density: 0.800
unique_lexeme_ratio: 1.000
max_lexeme_share: 0.250Ограничения кода
- Код реализует словную, а не клаузальную формулу.
- Все
ADVсчитаются лексическими. В лингвистической разметке часть наречий может считаться служебной. token.is_alphaисключает числа, сокращения с пунктуацией и артикулы вида SKU.- Составные лексические единицы считаются по отдельным токенам.
- Результат на сыром тексте зависит от версии модели.
- Модель частей речи делает вероятностные предсказания, а не выдает истинную грамматическую разметку. spaCy прямо описывает эти метки как результат обученной статистической модели.
- Качество разметки зависит от домена. Модель, обученная на редакционных текстах, может систематически ошибаться на запросах, отзывах, названиях товаров и социальных публикациях. Работы по адаптации частей речи отдельно решают эту проблему для пользовательских текстов.
Метрики
1. Проверка экстрактора
Сначала проверяется не ранжирование, а сам расчет.
Нужна вручную размеченная выборка из реального домена:
запросы
названия товаров
описания
отзывы
редакционные документыИзмеряются:
- точность, полнота и F1 для класса «лексическая единица»;
- средняя абсолютная ошибка плотности;
- доля токенов с
UPOS=X; - ошибка по языкам;
- ошибка по типам документов;
- для клаузальной версии — F1 границ ранговых клауз.
Средняя абсолютная ошибка:
Если экстрактор неверно считает сам признак, проверять его влияние на ранжирование рано.
2. Проверка классификатора
Если признак добавлен в классификатор качества, спама или полезности отзывов, сравниваются:
baseline
baseline + lexical_densityНужны:
- PR-AUC для несбалансированных классов;
- точность и полнота на рабочем пороге;
- F1;
- калибровка вероятностей;
- результаты по языкам, жанрам и длинам.
Оценивать модель только по Accuracy нельзя: при редком положительном классе она маскирует провал полноты.
3. Проверка ранжирования
Для поискового ранжирования сравниваются два запуска:
основная модель
основная модель + признакМетрики выбираются по задаче:
- NDCG@K — при градуированной релевантности;
- MRR — когда критичен первый релевантный результат;
- Recall@K — когда проверяется сохранение релевантных документов в верхней части списка;
- MAP — для нескольких бинарно релевантных документов.
trec_eval остается стандартным инструментом оценки запусков TREC. ranx вычисляет NDCG, MAP, MRR и другие метрики, а также поддерживает парные статистические тесты.
Проверка должна включать:
- абсолютную разницу метрик;
- доверительный интервал;
- статистический тест по запросам;
- разрезы по языку;
- разрезы по намерению;
- разрезы по длине запроса;
- разрезы по типу документа.
Средний прирост может скрыть падение на конкретном классе запросов.
4. Онлайн-эксперимент
Если offline-оценка дала прирост, проводится A/B-тест или перемежение результатов.
Целевые показатели зависят от продукта:
- успешное завершение поиска;
- переформулировка запроса;
- возврат к выдаче;
- добавление товара;
- покупка;
- сохранение;
- длительное чтение;
- жалоба на результат.
Ограничители:
- задержка ответа;
- доля пустых или ошибочных документов;
- деградация отдельных языков;
- падение разнообразия;
- рост доли одного источника или типа документа.
Сырой CTR не доказывает рост релевантности: на него влияет позиция.
Отказовые случаи
1. Повтор лексических слов
кредит кредит кредит кредитСловная плотность равна 1.0. Информативность и полезность из этого не следуют.
Исправление: считать вместе с повторяемостью, долей уникальных лемм и максимальной долей одной леммы.
2. Короткие запросы
ипотека
как оформить ипотекуПервый запрос получает 1.0. Второй — меньше из-за служебных слов. Это не делает первый запрос качественнее или сложнее.
Исправление: не использовать плотность отдельно на строках короче заданного порога. Добавлять длину и структуру запроса.
3. Заголовки и перечни
Доставка оплата возврат гарантияПлотность максимальна, потому что строка состоит из существительных.
Исправление: разделять поля и типы документов.
4. Шаблонные блоки
Меню, навигация, подвал и список категорий меняют распределение частей речи.
Исправление: считать основной текст отдельно от оболочки страницы.
5. Стоп-слова поискового анализатора
Если сначала удалить стоп-слова, знаменатель сократится и плотность искусственно вырастет.
Исправление: считать по исходному нормализованному тексту. Индексную плотность хранить отдельно только для диагностики анализатора.
6. Сдвиг домена
Модель частей речи может корректно работать на новостях и ошибаться на:
iphone 17 pro max 256gb
скидкааа топчик беру второй
как токен поменять???Исправление: ручная разметка доменной выборки, измерение ошибки, адаптация модели или правила для специальных токенов.
7. Смешение языков
купить MacBook murah di JakartaОдна языковая модель разметит часть токенов неверно.
Исправление: определять язык на уровне фрагмента либо использовать многоязычную схему с известным качеством.
8. Морфологически богатые языки
Одна словоформа может упаковывать грамматические значения, которые в другом языке выражаются отдельными словами. Поэтому одинаковый текст на двух языках даст разные значения плотности.
Исправление: сравнивать внутри языка либо строить языково-зависимую нормализацию.
9. Ошибка сегментации клауз
Клаузальная формула чувствительна к вложенным конструкциям и ранговому сдвигу. Обычный синтаксический анализатор не выдает готовую разметку Halliday.
Исправление: создать отдельную схему аннотации и проверить согласованность разметчиков.
10. Прямое усиление в ранжировании
Линейное добавление плотности поднимет:
- списки существительных;
- телеграфные заголовки;
- повторяющиеся ключевые слова;
- короткие карточки.
Исправление: проверять немонотонную зависимость, использовать интервалы и взаимодействия с другими признаками.
11. Смена версии модели
После обновления токенизатора или морфологической модели распределение признака меняется без изменения документов.
Исправление:
- версионировать экстрактор;
- хранить старый и новый расчет параллельно;
- сравнивать распределения;
- переобучать модель, использующую признак;
- не смешивать значения разных версий в одной обучающей выборке.
Наблюдаемость
Для каждого пакета документов нужно считать:
доля пустых текстов
среднее и медиана token_count
распределение lexical_density
доля UPOS=X
доля документов с LD=0
доля документов с LD=1
распределение по языкам
распределение по типам полей
время обработки на тысячу токенов
доля ошибок моделиАномальный рост LD=1 часто означает не улучшение текстов, а изменение токенизации, удаление служебных слов или поступление коротких заголовков.
Лексическая плотность и SEO
У лексической плотности нет нормативного диапазона для ранжирования в Google.
Она не равна плотности ключевой фразы:
где k — конкретная ключевая фраза.
Лексическая плотность не учитывает, какие именно существительные или глаголы использованы. Строки
кредит кредит кредит кредит
кошка стол облако двигательполучат одинаковое значение при одинаковой разметке.
Google отдельно запрещает неестественное повторение слов и фраз с целью манипуляции выдачей, но не задает целевой процент лексической или ключевой плотности.
Для технического SEO лексическая плотность пригодна только как исследовательский разрез:
- сравнить основной текст и шаблон;
- найти страницы, состоящие из перечней;
- проверить массово сгенерированные шаблоны;
- обнаружить изменение текстового контура после обновления сайта;
- сравнить типы страниц внутри одного языка и жанра.
Оптимизировать страницу под конкретный процент нельзя. Такая цель не связана ни с запросом, ни с оценкой релевантности.
Вывод
Лексическая плотность — счетный лингвистический показатель. В простейшей версии он равен доле токенов выбранных частей речи. В версии Halliday — числу лексических единиц на ранговую клаузу.
Для поисковой системы это возможный внешний признак документа, а не функция релевантности. До внедрения нужно:
- выбрать формулу;
- зафиксировать токенизацию и классы частей речи;
- проверить разметку на собственном домене;
- нормализовать значение по языку и типу документа;
- сравнить модель с признаком и без него;
- удалить признак, если он не дает статистически подтвержденного прироста.