Skip to content

Latest commit

 

History

History
354 lines (249 loc) · 12 KB

File metadata and controls

354 lines (249 loc) · 12 KB

🧠 Продвинутый NLP-алгоритм для разноски транзакций

Обзор

Система автоматической категоризации транзакций использует продвинутый NLP-алгоритм, который комбинирует несколько техник для максимальной точности определения категории расходов.

Архитектура алгоритма

1️⃣ Multi-Query (Разбиение на предложения)

Цель: Обработать сложные назначения платежей, содержащие несколько смысловых единиц.

Пример:

Входной текст: "Оплата за цветы. Доставка завтра утром"
Результат: ["Оплата за цветы", "Доставка завтра утром"]

Реализация:

  • Разбивка по знакам препинания (., !, ?, ;, ,)
  • Фильтрация пустых и слишком коротких фрагментов (< 3 символов)
  • Каждое предложение обрабатывается независимо

2️⃣ Лемматизация (pymorphy2)

Цель: Привести слова к начальной форме для лучшего сопоставления.

Пример:

"цветочки" → "цветок"
"купили" → "купить"
"красивые" → "красивый"

Реализация:

  • Используется библиотека pymorphy2 для морфологического анализа
  • Кэширование результатов (LRU cache на 1000 записей)
  • Извлечение только существительных и глаголов как ключевых слов

3️⃣ Синонимы (RuWordNet)

Цель: Расширить поиск через семантически близкие слова.

Пример:

"цветок" → {"тюльпан", "роза", "букет", "растение", ...}
"оплата" → {"платёж", "расчёт", "выплата", ...}

Реализация:

  • Используется RuWordNet - русская версия WordNet
  • Берутся первые 3 синсета, до 10 синонимов на слово
  • Кэширование результатов (LRU cache на 500 записей)

4️⃣ Vector Search (Поиск по эмбеддингам)

Цель: Найти семантически близкие категории через векторное представление.

Пример:

calculate_similarity("оплата за доставку цветов", "логистика") → 0.72
calculate_similarity("оплата за доставку цветов", "реклама") → 0.15

Реализация:

  • Используется OpenAI Embeddings API (text-embedding-3-small)
  • Вычисление косинусного расстояния между векторами
  • Кэширование эмбеддингов для часто встречающихся текстов

5️⃣ Keyword Boost (Бонус за совпадение лемм)

Цель: Увеличить вес совпадений на уровне лемм.

Бонус: +0.15 к базовому скору

Пример:

Транзакция: "Купили красивые цветочки"
Ключевое слово: "цветок"

Леммы транзакции: {"купить", "красивый", "цветок"}
Лемма ключевого слова: "цветок"

Совпадение! → Бонус +0.15

6️⃣ Exact Match (Бонус за точное совпадение)

Цель: Максимально увеличить вес при точном совпадении слова.

Бонус: +0.25 к базовому скору

Пример:

Транзакция: "Оплата за цветы и доставку"
Ключевое слово: "цветы"

"цветы" in "оплата за цветы и доставку" → True
Совпадение! → Бонус +0.25

7️⃣ Synonym Match (Бонус за совпадение синонимов)

Цель: Учесть семантическую близость через синонимы.

Бонус: +0.10 к базовому скору

Пример:

Транзакция: "Покупка букета роз"
Ключевое слово: "цветок"

Синонимы "цветок": {"роза", "тюльпан", "букет", ...}
Леммы транзакции: {"покупка", "букет", "роза"}

Пересечение: {"букет", "роза"} → Бонус +0.10

Итоговая формула скора

final_score = min(
    base_embedding_similarity 
    + (0.25 if exact_match else 0)
    + (0.15 if lemma_match else 0)
    + (0.10 if synonym_match else 0),
    1.0  # Максимум
)

Пример работы алгоритма

Входные данные

Назначение платежа:

"Оплата за доставку цветов клиенту. Срочная курьерская служба"

Категории и ключевые слова:

{
    "Логистика": ["доставка", "курьер", "транспорт"],
    "Маркетинг": ["реклама", "продвижение"],
    "Офисные расходы": ["канцелярия", "офис"]
}

Процесс обработки

Шаг 1: Multi-Query

Предложения:
1. "Оплата за доставку цветов клиенту"
2. "Срочная курьерская служба"

Шаг 2: Лемматизация

Ключевые слова: ["оплата", "доставка", "цветок", "клиент", "срочный", "курьерский", "служба"]

Шаг 3: Проверка категории "Логистика", ключевое слово "доставка"

Vector Search:

similarity("Оплата за доставку цветов клиенту", "доставка") = 0.68

Exact Match:

"доставка" in "оплата за доставку цветов клиенту" → True
Бонус: +0.25

Lemma Match:

"доставка" in {"оплата", "доставка", "цветок", ...} → True
Бонус: +0.15

Synonym Match:

Синонимы "доставка": {"курьер", "транспортировка", ...}
"курьерский" → "курьер" (лемма)
Пересечение найдено!
Бонус: +0.10

Итоговый скор:

0.68 + 0.25 + 0.15 + 0.10 = 1.18 → 1.0 (ограничено максимумом)

Результат

✅ Категория: "Логистика"
✅ Скор: 1.000
✅ Примененные бусты: exact, lemma, synonym

Логирование

Система логирует детальную информацию о процессе категоризации:

[CATEGORY-NLP][batch-uuid] Категория определена: Логистика (score=1.000) [boosts: exact, lemma, synonym]

Настройки

Порог совпадения (Threshold)

Минимальный скор для принятия категории (по умолчанию: 0.7)

# В .env
EMBEDDING_SIMILARITY_THRESHOLD=0.7

Fallback стратегия

Если NLP не дал результата (скор < threshold):

  1. Проверка маппинга по ИНН - если есть ИНН контрагента, проверяется ContractorExpenseMapping
  2. Оставить пустым - если маппинга нет, категория остается None

Производительность

Оптимизации

  1. LRU кэширование:

    • Лемматизация: 1000 записей
    • Синонимы: 500 записей
    • Эмбеддинги: неограниченно (в embedding_cache.py)
  2. Lazy loading:

    • pymorphy2 и RuWordNet загружаются только при первом использовании
    • Singleton паттерн для переиспользования экземпляров
  3. Graceful degradation:

    • Если pymorphy2 не установлен → простая нормализация .lower()
    • Если RuWordNet не установлен → работа без синонимов

Типичное время обработки

  • Одна транзакция: 50-150ms (с холодным кэшем)
  • Одна транзакция: 5-15ms (с прогретым кэшем)
  • Batch из 100 транзакций: ~2-5 секунд

Установка зависимостей

pip install pymorphy2 pymorphy2-dicts-ru ruwordnet

Или через requirements.txt:

pip install -r requirements.txt

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

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

from coin_desk.utils.nlp_processor import get_category_matcher

matcher = get_category_matcher()

category, score, boosts = matcher.find_best_category(
    transaction_text="Оплата за доставку цветов",
    category_keywords={
        "Логистика": ["доставка", "курьер"],
        "Маркетинг": ["реклама", "продвижение"]
    },
    threshold=0.7
)

print(f"Категория: {category}, Скор: {score:.3f}")
# Вывод: Категория: Логистика, Скор: 1.000

Расширенное использование

from coin_desk.utils.nlp_processor import get_nlp_processor

nlp = get_nlp_processor()

# Лемматизация
lemma = nlp.lemmatize("цветочки")  # → "цветок"

# Синонимы
synonyms = nlp.get_synonyms("цветок")  # → {"роза", "тюльпан", ...}

# Ключевые слова
keywords = nlp.extract_keywords("Купили красивые цветы")  # → ["купить", "цветок"]

# Расширение запроса
expanded = nlp.expand_query_with_synonyms("доставка цветов")
# → {"доставка", "цветок", "курьер", "роза", "тюльпан", ...}

Мониторинг и отладка

Проверка работы алгоритма

Логи сохраняются в debug.log с префиксом [CATEGORY-NLP]:

tail -f debug.log | grep "CATEGORY-NLP"

Анализ качества категоризации

from coin_desk.models import Transaction

# Транзакции, категоризованные через NLP
nlp_transactions = Transaction.objects.filter(category_source='nlp')

print(f"Всего NLP-категоризаций: {nlp_transactions.count()}")

Дальнейшие улучшения

Возможные направления развития

  1. Machine Learning:

    • Обучение классификатора на исторических данных
    • Fine-tuning эмбеддинг-модели на доменных данных
  2. Контекстный анализ:

    • Учет истории транзакций контрагента
    • Анализ сезонности и трендов
  3. Feedback loop:

    • Обучение на ручных корректировках пользователя
    • A/B тестирование разных порогов и весов
  4. Многоязычность:

    • Поддержка английского языка
    • Автоопределение языка транзакции

Авторы

Разработано для проекта CoinCounter.

Лицензия

MIT License