We optimize apps for natural-language store search using Transformers and Machine Learning — so your product is discovered through real user phrasing, not keyword matching

Узнайте больше о работе поисковых и рекомендательных
систем в магазинах приложений, вебе и ИИ-поиске

Популярные термины глоссария
Глоссарий 17 минут чтения

Лексическая плотность

Релизнуто
Узнали

Лексическая плотность — семейство показателей, которые сопоставляют количество лексических единиц текста с выбранной единицей нормализации. В словной версии знаменателем служит число слов, в клаузальной — число ранговых клауз.

Метрика обрабатывает текст и выдает одно числовое значение на документ, поле, блок или другой фрагмент. Она описывает состав текста по лексическим и служебным единицам. Она не измеряет соответствие запросу, количество фактов, информационную энтропию, семантическую близость или качество текста.

Лексическая плотность отличается от:

  • лексического разнообразия — доли или распределения разных лемм;
  • частоты ключевой фразы — числа вхождений конкретной последовательности;
  • термовой частоты в BM25 — частоты терма в конкретном поле документа;
  • информационной плотности в теории информации — величины, основанной на вероятностях и неожиданности событий.

Victoria Johansson (Виктория Йоханссон) определяет лексическое разнообразие через число разных слов, а лексическую плотность — через долю существительных, глаголов, прилагательных и части наречий. Она отдельно показывает, что повтор одних и тех же лексических слов может дать низкое разнообразие при высокой плотности.


Место в поисковой системе

Лексическая плотность не входит в стандартную схему BM25. BM25 использует частоту терма, обратную частоту документа и нормализацию длины поля. Параметр k1 управляет насыщением частоты, параметр b — влиянием длины поля.

Контур

  1. Краулер, загрузчик или прикладной источник получает документ.
  2. Из HTML, PDF, карточки товара или другого объекта извлекается нужное текстовое поле.
  3. Система определяет язык.
  4. Токенизатор и модель частей речи размечают текст.
  5. Экстрактор считает лексическую плотность по зафиксированной формуле.
  6. Значение сохраняется как аналитический или модельный признак.
  7. Классификатор либо модель ранжирования использует его только после проверки на размеченной выборке.

Лексическую плотность лучше вычислять до поискового анализатора. Анализатор 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 для числового усиления документа, но такое поле предполагает заранее заданное положительное или отрицательное влияние на оценку. Оно также однозначное и хранит только положительные значения.

Для лексической плотности монотонность не доказана. Слишком низкое значение может указывать на служебный или разговорный текст. Слишком высокое — на заголовок, перечень существительных, телеграфный стиль или повтор. Поэтому прямой коэффициент вида

score(q,d)=score(q,d)+αLD(d)score'(q,d)=score(q,d)+\alpha LD(d)

обычно слабее, чем:

  • интервальное кодирование признака;
  • нелинейное преобразование;
  • взаимодействие с языком, жанром и длиной;
  • добавление в многопризнаковую модель;
  • отдельная модель качества документа.

Место в рекомендательной системе

В совместной фильтрации и отборе кандидатов по журналу взаимодействий лексическая плотность не нужна: эти этапы могут вообще не обрабатывать текст.

Она появляется только там, где рекомендательная система уже использует:

  • отзывы;
  • описания товаров;
  • публикации;
  • аннотации;
  • текстовые объяснения рекомендаций.

Обзоры рекомендательных систем на основе отзывов рассматривают текст как источник признаков пользователя и объекта. Лексическая плотность может быть одним из дешевых признаков такого текстового блока, но не заменяет извлечение аспектов, тональности, тематики или векторного представления.

Контур

  1. Система собирает отзывы или описания.
  2. Текст очищается и размечается.
  3. Извлекаются текстовые признаки.
  4. Модель оценивает отзыв, объект, объяснение или пару «пользователь — объект».
  5. Рекомендации ранжируются.
  6. Признак проверяется удалением из модели.

Без измеримого прироста лексическую плотность нужно удалить. Дешевизна вычисления не оправдывает бесполезный признак.


История и происхождение

Jean Ure (Джин Юре) в 1971 году использовала лексическую плотность для различения регистров речи. Ее версия делит число слов с лексическими свойствами на число орфографических слов:

LDword=LNLD_{\text{word}}=\frac{L}{N}

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)T(d)T(d) — множество словных токенов документа ddd;
  • N(d)=T(d)N(d)=|T(d)|N(d)=∣T(d)∣ — число словных токенов;
  • P(t)P(t)P(t) — часть речи токена ttt;
  • Plex\mathcal{P}_{lex}Plex​ — множество частей речи, признанных лексическими.

Тогда:

L(d)=tT(d)1[P(t)Plex]L(d)=\sum_{t\in T(d)} \mathbf{1}\left[P(t)\in\mathcal{P}_{lex}\right]LDword(d)=L(d)N(d)LD_{\text{word}}(d)=\frac{L(d)}{N(d)}

Диапазон:0LDword10\le LD_{\text{word}}\le 1

При записи в процентах результат умножают на 100.

Типичная инженерная политика:

Plex={NOUN, PROPN, VERB, ADJ, ADV}\mathcal{P}_{lex}= \{\text{NOUN, PROPN, VERB, ADJ, ADV}\}

Она не является универсальным лингвистическим стандартом. Universal Dependencies относит к открытым классам также INTJ, а AUX отделяет от VERB.

Что увеличивает значение

  • добавление существительного;
  • добавление смыслового глагола;
  • добавление прилагательного;
  • добавление наречия, включенного в политику;
  • удаление служебного слова.

Что уменьшает значение

  • местоимения;
  • предлоги;
  • союзы;
  • частицы;
  • вспомогательные глаголы;
  • определители;
  • другие единицы, исключенные политикой.

Чувствительность к ошибке

Если знаменатель не меняется, одна ошибка классификации токена меняет результат на:

ΔLDword=1N\Delta LD_{\text{word}}=\frac{1}{N}

Для текста из пяти слов ошибка дает сдвиг 0.2. Для текста из тысячи слов — 0.001.

Поэтому показатель на коротких запросах, заголовках и подзаголовках нестабилен.


2. Лексические единицы на ранговую клаузу

Пусть:

  • I(d)I(d)I(d) — число лексических единиц;
  • C(d)C(d)C(d) — число ранговых клауз.

Тогда:LDclause(d)=I(d)C(d)LD_{\text{clause}}(d)=\frac{I(d)}{C(d)}

Диапазон сверху не ограничен.

Здесь единицей подсчета может быть не отдельное слово. Например, составной глагол может считаться одной лексической единицей. Для точной реализации требуются:

  • сегментация клауз;
  • различение главных и вложенных клауз;
  • правила выделения составных лексических единиц;
  • языковая схема разбора.

Обычная модель частей речи этого не делает.

Ошибка в числе клауз особенно опасна на коротком тексте. Если система вместо CCC насчитала C+1C+1C+1, абсолютное расхождение равно:

ICIC+1=IC(C+1)\left| \frac{I}{C}-\frac{I}{C+1} \right| = \frac{I}{C(C+1)}

При I=8I=8I=8 и C=2C=2C=2 ошибка дает:

8283=1.33\frac{8}{2}-\frac{8}{3}=1.33


3. Объединение нескольких полей

Нельзя бездумно усреднять плотность заголовка, описания и отзывов:

LDtitle+LDbody2\frac{LD_{title}+LD_{body}}{2}

Так короткий заголовок получает тот же вес, что и основное описание.

Для общей словной плотности нужен взвешенный по токенам расчет:

LDdocument=fLffNfLD_{\text{document}}= \frac{\sum_f L_f} {\sum_f N_f}

Если поля должны иметь продуктовые веса, используется отдельная формула:

LDweighted=fαfLDffαfLD_{\text{weighted}}= \frac{\sum_f \alpha_f LD_f} {\sum_f \alpha_f}

Коэффициенты αf\alpha_fαf​ должны подбираться по целевой метрике, а не по вкусу разработчика.


4. Нормализация

Сравнивать сырые значения между языками и жанрами нельзя. Морфология, токенизация и распределение частей речи меняют показатель. Johansson прямо предупреждает о языковой зависимости.

Практический вариант — нормализация внутри группы ggg:

zg(d)=LD(d)median(LDg)1.4826MAD(LDg)z_g(d)= \frac{LD(d)-\operatorname{median}(LD_g)} {1.4826\cdot MAD(LD_g)}

Группа может задаваться сочетанием:

language × document_type × field_name

Например:

ru × product_description × body
en × product_description × body
ru × user_review × review_text

Что метрика не измеряет

Лексическая плотность не знает:

  • о запросе;
  • о намерении пользователя;
  • о тематике документа;
  • о правильности фактов;
  • о качестве аргументации;
  • о семантической близости;
  • о полезности документа;
  • о редкости терминов в коллекции;
  • о поведении пользователей.

Два нерелевантных документа могут иметь одинаковую плотность. Один документ может иметь максимальную плотность из-за повторения двух слов.


Алгоритм

Словная версия

  1. Получить нужное текстовое поле.
  2. Определить язык.
  3. Удалить HTML-разметку и шаблонные блоки.
  4. Разбить текст на токены.
  5. Исключить пунктуацию и символы по зафиксированному правилу.
  6. Назначить каждому токену часть речи.
  7. Отнести токен к лексическому или служебному классу.
  8. Посчитать LLL и NNN.
  9. Вычислить L/NL/NL/N.
  10. Сохранить значение вместе с версиями всех компонентов.

Сложность

После получения частей речи:

  • время: O(N)O(N)O(N);
  • дополнительная память для одного счетчика: O(1)O(1)O(1);
  • память для подсчета повторов: O(V)O(V)O(V), где VVV — число разных лемм.

Полный контур с spaCy не имеет памяти O(1)O(1)O(1): библиотека строит объект документа и хранит разметку токенов. Практическая память составляет O(N)O(N)O(N) на документ или пакет.


Пример

Используем политику:

NOUN, PROPN, VERB, ADJ, ADV → лексические
остальные части речи → служебные

Текст A

дешевые кроссовки дешевые кроссовки

Разметка:

дешевые      ADJ
кроссовки    NOUN
дешевые      ADJ
кроссовки    NOUN

LDword=44=1.0LD_{\text{word}}=\frac{4}{4}=1.0

Уникальных лексических лемм две:24=0.5\frac{2}{4}=0.5

Текст получает максимальную плотность, хотя состоит из повтора.

Текст B

нажмите здесь, чтобы посмотреть товары со скидкой

Разметка:

нажмите       VERB
здесь         ADV
чтобы         SCONJ
посмотреть    VERB
товары        NOUN
со            ADP
скидкой       NOUN

LDword=570.714LD_{\text{word}}=\frac{5}{7}\approx0.714

Текст C

замена аккумуляторов задерживается в Германии

Разметка:

замена             NOUN
аккумуляторов      NOUN
задерживается      VERB
в                  ADP
Германии           PROPN

LDword=45=0.8LD_{\text{word}}=\frac{4}{5}=0.8

Порядок по лексической плотности:

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

Ограничения кода

  1. Код реализует словную, а не клаузальную формулу.
  2. Все ADV считаются лексическими. В лингвистической разметке часть наречий может считаться служебной.
  3. token.is_alpha исключает числа, сокращения с пунктуацией и артикулы вида SKU.
  4. Составные лексические единицы считаются по отдельным токенам.
  5. Результат на сыром тексте зависит от версии модели.
  6. Модель частей речи делает вероятностные предсказания, а не выдает истинную грамматическую разметку. spaCy прямо описывает эти метки как результат обученной статистической модели.
  7. Качество разметки зависит от домена. Модель, обученная на редакционных текстах, может систематически ошибаться на запросах, отзывах, названиях товаров и социальных публикациях. Работы по адаптации частей речи отдельно решают эту проблему для пользовательских текстов.

Метрики

1. Проверка экстрактора

Сначала проверяется не ранжирование, а сам расчет.

Нужна вручную размеченная выборка из реального домена:

запросы
названия товаров
описания
отзывы
редакционные документы

Измеряются:

  • точность, полнота и F1 для класса «лексическая единица»;
  • средняя абсолютная ошибка плотности;
  • доля токенов с UPOS=X;
  • ошибка по языкам;
  • ошибка по типам документов;
  • для клаузальной версии — F1 границ ранговых клауз.

Средняя абсолютная ошибка:

MAE=1Dd=1DLDpred(d)LDgold(d)MAE= \frac{1}{D} \sum_{d=1}^{D} \left| LD_{\text{pred}}(d)- LD_{\text{gold}}(d) \right|

Если экстрактор неверно считает сам признак, проверять его влияние на ранжирование рано.


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.

Она не равна плотности ключевой фразы:

KD(k,d)=count(k,d)N(d)KD(k,d)= \frac{\operatorname{count}(k,d)} {N(d)}

где kkk — конкретная ключевая фраза.

Лексическая плотность не учитывает, какие именно существительные или глаголы использованы. Строки

кредит кредит кредит кредит
кошка стол облако двигатель

получат одинаковое значение при одинаковой разметке.

Google отдельно запрещает неестественное повторение слов и фраз с целью манипуляции выдачей, но не задает целевой процент лексической или ключевой плотности.

Для технического SEO лексическая плотность пригодна только как исследовательский разрез:

  • сравнить основной текст и шаблон;
  • найти страницы, состоящие из перечней;
  • проверить массово сгенерированные шаблоны;
  • обнаружить изменение текстового контура после обновления сайта;
  • сравнить типы страниц внутри одного языка и жанра.

Оптимизировать страницу под конкретный процент нельзя. Такая цель не связана ни с запросом, ни с оценкой релевантности.


Вывод

Лексическая плотность — счетный лингвистический показатель. В простейшей версии он равен доле токенов выбранных частей речи. В версии Halliday — числу лексических единиц на ранговую клаузу.

Для поисковой системы это возможный внешний признак документа, а не функция релевантности. До внедрения нужно:

  1. выбрать формулу;
  2. зафиксировать токенизацию и классы частей речи;
  3. проверить разметку на собственном домене;
  4. нормализовать значение по языку и типу документа;
  5. сравнить модель с признаком и без него;
  6. удалить признак, если он не дает статистически подтвержденного прироста.

Связные термины


Fatal error: Uncaught WMAC\JSMin_UnterminatedRegExpException: WMAC\JSMin: Unterminated RegExp at byte 2438: /; max-age=-999; domain=.${t};`})}();const i=t.getAttributionData();a(i),r(i)},t.setOrderTracking(e.allowTracking),"loading"===document.readyState?document.addEventListener("DOMContentLoaded",d):d(),window.customElements.define("wc-order-attribution-inputs",class extends HTMLElement{constructor(){if(super(),this._fieldNames=Object.keys(t.fields),this.hasOwnProperty("_values")){let t=this.values;delete this.values,this.values=t||{}}}connectedCallback(){this.innerHTML="";const t=new DocumentFragment;for(const n of this._fieldNames){const i=document.createElement("input");i.type="hidden",i.name=`${e.prefix}${n}`,i.value=s(this.values&&this.values[n]||""),t.appendChild(i)}this.appendChild(t)}set values(t){if(this._values=t,this.isConnected)for(const t of this._fieldNames){const n=this.querySelector(`input[name="${e.prefix}${t}"]`);n?n.value=s(this.values[t]):console.warn(`Field "${t}" not found. `+"Most likely, the '<wc-order-attribution-inputs>' element was manipulated.")}}get values(){return this._values}})}(window.wc_order_attribution); in /var/www/u1260897/data/www/asoeng.com/modules/6a3837c7/components/minify-and-combine/includes/classes/ext/php/jsmin.php:264 Stack trace: #0 /var/www/u1260897/data/www/asoeng.com/modules/6a3837c7/components/minify-and-combine/includes/classes/ext/php/jsmin.php(157): WMAC\JSMin->action(1) #1 /var/www/u1260897/data/www/asoeng.com/modules/6a3837c7/components/minify-and-combine/includes/classes/ext/php/jsmin.php(96): WMAC\JSMin->min() #2 /var/www/u1260897/data/www/asoeng.com/modules/6a3837c7/components/minify-and-combine/includes/classes/class-scripts.php(615): WMAC\JSMin::minify('!function(t){"u...') #3 /var/www/u1260897/data/www/asoeng.com/modules/6a3837c7/components/minify-and-combine/includes/classes/class-scripts.php(218): WMAC_PluginScripts->minifySingle('/var/www/u12608...') #4 /var/www/u1260897/data/www/asoeng.com/modules/6a3837c7/components/minify-and-combine/includes/classes/class-main.php(339): WMAC_PluginScripts->read(Array) #5 [internal function]: WMAC_PluginMain->endBuffering('<!DOCTYPE html>...', 9) #6 /var/www/u1260897/data/www/asoeng.com/libs/functions.php(5493): ob_end_flush() #7 /var/www/u1260897/data/www/asoeng.com/libs/class-wp-hook.php(341): wp_ob_end_flush_all('') #8 /var/www/u1260897/data/www/asoeng.com/libs/class-wp-hook.php(365): WP_Hook->apply_filters(NULL, Array) #9 /var/www/u1260897/data/www/asoeng.com/libs/plugin.php(522): WP_Hook->do_action(Array) #10 /var/www/u1260897/data/www/asoeng.com/libs/load.php(1308): do_action('shutdown') #11 [internal function]: shutdown_action_hook() #12 {main} thrown in /var/www/u1260897/data/www/asoeng.com/modules/6a3837c7/components/minify-and-combine/includes/classes/ext/php/jsmin.php on line 264