🛡️ Шпаргалка к защите — СонБот (бот для продажи кроватей)

Файл: bed_bot.py. Стек: TF-IDF (символьные 3-граммы) + LinearSVC + расстояние Левенштейна + лемматизация (pymorphy2).


📊 Что есть / чего нет

Метод В проекте Где в коде
Расстояние Левенштейна ✅ есть _phrase_matches (582), generate_answer (760)
Лемматизация (pymorphy2) ✅ есть lemmatize (54), внутри clear_phrase
Стемминг ❌ нет (вместо него лемматизация)
Косинусное сходство ❌ нет (вместо него Левенштейн)
TF-IDF векторизация ✅ есть TfidfVectorizer (563)
LinearSVC (мультиклассовая) ✅ есть clf = LinearSVC() (565)
Токенизация (символьная / по словам) ✅ есть analyzer='char' (563), .split()
Сентимент-анализ ❌ нет
Контекст диалога (память) ✅ есть hist_theme, selected_product

Запомнить намертво: у меня — лемматизация (не стемминг), близость текстов меряю Левенштейном (не косинусом), определяю намерение (не тональность).


📚 Базовые вопросы

1. Расстояние Левенштейна

Минимальное число операций (вставка / удаление / замена символа), чтобы превратить одну строку в другую. «кот»→«код» = 1.

У меня: через nltk.edit_distance, нормированное на длину (distance / len): - _phrase_matches() — сравнение реплики с примерами намерений (порог 0.4–0.5). - generate_answer() — поиск похожего вопроса в датасете (порог < 0.2).


2. Стемминг

Грубое отсечение окончаний по правилам до основы (стема). «бегущий/бегал»→«бег». Стем может быть не реальным словом.

У меня НЕ используется. Вместо него — лемматизация (pymorphy2, строка 54): приведение к словарной начальной форме. «кроватей»→«кровать». Разница: стемминг = обрубок по правилам; лемматизация = словарная форма по морфологии. Для русского лемматизация точнее.


3. Косинусное сходство

Мера близости двух векторов по косинусу угла между ними (от −1 до 1). В NLP тексты → векторы (TF-IDF), косинус показывает смысловую близость независимо от длины.

У меня НЕ используется. Близость текстов меряю расстоянием Левенштейна. Косинус был бы альтернативой — TF-IDF-инфраструктура для него у меня уже есть, можно было бы сравнивать вектор реплики и примеров через cosine_similarity. Выбрал Левенштейн осознанно: фразы короткие, важна устойчивость к опечаткам.


4. Как реализована классификация намерений

Гибрид в два этапа (classify_intentclassify_intent_by_theme, 596–659):

  1. ML: примеры намерений → TfidfVectorizer(analyzer='char', ngram_range=(3,3)) → обучение LinearSVC. Для новой реплики clf.predict().
  2. Проверка Левенштейном: предсказание подтверждается через _phrase_matches; если нет — резервный ручной перебор.
  3. Контекст темы: у намерений есть theme_app/theme_gen, применяются с учётом истории hist_theme (позволяет понимать «да»/«давай»).

Кратко: TF-IDF (char 3-gram) + LinearSVC, подстрахованные Левенштейном и контекстом.


5. Токенизация

Разбиение текста на единицы (токены): слова, символы, подслова, предложения. Первый шаг обработки.

У меня: подключён nltk punkt; основная обработка — в clear_phrase() (нижний регистр, только русские буквы, .split()). А TfidfVectorizer(analyzer='char') делает символьную токенизацию — режет на 3-символьные куски.


6. Мультиклассовая и бинарная классификация

  • Бинарная — 2 класса (спам/не спам).
  • Мультиклассовая — больше двух, объект → один из многих.

У меня — мультиклассовая. Классов ≈ 20 (по числу намерений: hello, buy_bed, ask_price, back_pain…). LinearSVC различает все сразу (под капотом — one-vs-rest).


7. Векторизация

Превращение текста в числовой вектор, потому что ML работает только с числами.

Да, есть — TF-IDF (TfidfVectorizer, 563): - TF — частота элемента в тексте; - IDF — насколько он редкий по всей выборке (редкие = информативнее).

Реплика → вектор весов её символьных 3-грамм. Зачем: чтобы LinearSVC мог обучаться и классифицировать.


8. Сентимент-анализ

Определение тональности текста: позитив / негатив / нейтрально.

У меня НЕТ. Бот определяет намерение (что человек хочет), а не настроение. Для продающего бота ключевое — намерение. Добавить можно отдельным классификатором тональности.


9. Интеллектуальные возможности бота

  1. Классификация намерений (TF-IDF char-n-gram + LinearSVC).
  2. Устойчивость к опечаткам и формам слов (Левенштейн + n-граммы + лемматизация).
  3. Контекст диалога — история тем hist_theme, выбранный товар selected_product, отдельно на каждого пользователя Telegram.
  4. Генеративный поиск ответа в датасете диалогов (generate_answer).
  5. Распознавание товара по названию («хочу уют») и номеру («первый вариант»).
  6. Фразы-заглушки, когда не понял.
  7. Контекстные рекламные триггеры (при боли в спине → ортопедическая модель).
  8. Сбор статистики диалога.

🛡️ Защита: стемминг / косинус / сентимент

Принцип: не «не успел», а «выбрал другое, и вот почему».

Стемминг: > Сознательно не использовал — для русского он слишком грубый, даёт обрубки. Взял лемматизацию через pymorphy2: словарная форма с учётом морфологии, точнее. Задачу нормализации слов решает она. > Если надавят: сработал бы SnowballStemmer, но с бóльшим числом ошибок склейки.

Косинусное сходство: > Близость меряю Левенштейном — запросы короткие, важна устойчивость к опечаткам, а он ловит посимвольные различия. Косинус сильнее на длинных текстах. > Если надавят: легко встроить — TF-IDF у меня уже есть, можно считать cosine_similarity вместо Левенштейна. Выбор осознанный.

Сентимент-анализ: > Моя задача — намерение (что хочет), а не тональность (как относится). Для бота-продавца ключевое — намерение. > Если надавят: добавил бы вторую модель тональности, чтобы подстраивать тон или звать оператора при негативе.


🔥 Провокационные вопросы

TF-IDF / векторизация

Формула TF-IDF? → TF × IDF. TF = частота в документе; IDF = log(всего документов / документов с элементом). Частые везде → низкий вес, редкие информативные → высокий.

Почему символьные n-граммы, а не слова? (самый частый вопрос) → 1) устойчивость к опечаткам («спсибо»≈«спасибо»); 2) формы слова дают общие n-граммы; 3) мало данных (10–13 примеров на класс) — пословный словарь слишком разреженный.

Что такое ngram_range=(3,3)? → куски по 3 символа: «привет»→«при»,«рив»,«иве»,«вет». Тройка — компромисс между шумом (1–2) и редкостью (4+).

Если слово не встречалось при обучении? → для пословной модели — нулевой вектор; у меня char-граммы дают частичное совпадение по кускам.

SVM / LinearSVC

Что такое опорные векторы? → точки выборки ближе всего к разделяющей границе; именно они задают её положение. SVM максимизирует зазор (margin) между классами.

Почему LinearSVC, а не Байес/нейросеть? → хорош на разреженных высокоразмерных данных (TF-IDF), быстрый, не переобучается на малой выборке. Нейросеть избыточна.

SVM же бинарный — как несколько классов? (ловушка) → стратегия one-vs-rest: для каждого намерения свой бинарный классификатор «класс против всех», на выходе — максимум решающей функции.

Что такое гиперплоскость / линейная разделимость? → граница, разделяющая классы в пространстве признаков (в 2D — прямая). Линейный SVM считает, что классы делятся такой плоскостью; в TF-IDF высокой размерности это обычно так.

Левенштейн

Если опечаток слишком много? → расстояние превысит порог, совпадения нет → сработает ML или заглушка. Бот не выдаст неверный ответ, честно скажет «не понял».

Зачем нормировать на длину? → чтобы порог не зависел от длины строки; деление даёт долю несовпадения, один порог работает для любых фраз.

Сложность алгоритма? → O(m×n) через динамическое программирование (матрица). Поэтому датасет сначала фильтрую по словам и длине, чтобы не считать все пары.

Архитектура

Почему гибрид ML + Левенштейн, не избыточно? → подстраховка: ML всегда выдаёт класс даже на мусор, Левенштейн отсекает ложные срабатывания; ручной перебор ловит ошибки ML.

Это ML или правила? (каверзный) → и то, и другое. Ядро — настоящее ML (TF-IDF + LinearSVC на данных), поверх — эвристики ради предсказуемости узкого бота.

Как помнит разговор?hist_theme (стек тем) + selected_product, отдельно на каждого пользователя (context.chat_data), не в глобальной переменной.

Обучается в процессе общения? → нет, офлайн-обучение один раз при старте на фиксированных примерах. Дообучения на лету нет.


✅ Главные правила

  1. На любое «почему не X» → «потому что выбрал Y, и вот trade-off», никогда «не успел / не знал».
  2. Ссылайтесь на конкретное место в коде — снимает 90% доп. вопросов.
  3. Не путайте стемминг и лемматизацию: стемминг = обрубок по правилам, лемматизация = словарная форма. У меня лемматизация.
  4. Любой вопрос про отсутствующий метод заканчивайте упоминанием того, что есть (Левенштейн, TF-IDF, LinearSVC, лемматизация).