Материал учебного циклаОткрытый материал

Как обучается ML-модель: функция потерь, градиент и переобучение

Как модель превращает примеры в параметры: признаки, прогноз, функция потерь, градиент, оптимизация и регуляризация на примере обращений клиентов.

AI и автоматизация #ML
Учебный цикл AI и автоматизация в цифровых продуктах: от данных до работающей системы Материал 3 из 10

Как обучается модель

У нас есть два обращения:

«Оплатил заказ, но платёж не отобразился».

«Оплатил доставку, но курьер не приехал».

В обоих есть слово «оплатил». Первое нужно передать команде по оплате, второе - команде доставки. Если написать правило «встречается слово об оплате -> направить к специалисту по оплате», оно ошибётся на втором обращении. Можно дописывать исключения, но новых сочетаний слов будет всё больше.

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

Теперь разберём, что происходит после команды «обучить модель». Какие числа изменяются? Почему модель начинает выделять полезные сочетания слов? Что означает ошибка во время обучения и почему её снижение ещё не гарантирует хорошую работу на новых заявках?

Для конкретности возьмём понятный текстовый классификатор: TF-IDF + логистическая регрессия. TF-IDF превращает текст в числовые признаки; логистическая регрессия назначает этим признакам веса и вычисляет оценки тем. Название «регрессия» здесь историческое: в нашем примере это модель классификации. Такое применение и устройство модели описаны в документации scikit-learn.

Что именно получает модель во время обучения

Обучающий пример состоит из двух частей:

  • Признаки: сведения, доступные в момент прогноза. Здесь это текст обращения, превращённый в числовой вектор.
  • Правильный ответ: проверенная тема обращения, например payment, delivery или access.

Пусть у нас есть заявка:

«Не прошёл платёж, заказ остался неоплаченным».

После обработки текста часть числового вектора может выглядеть так:

Признак Значение
«платёж» 0,46
«не прошёл» 0,38
«заказ» 0,12
«курьер» 0
«пароль» 0

Значения здесь иллюстративные. В настоящем TF-IDF их рассчитывают по частотам слов в обращении и во всём обучающем корпусе. Слова, которых нет в заявке, получают ноль. Сочетания слов можно выделить в отдельные признаки: «не прошёл» часто полезнее, чем каждое слово по отдельности. Механизм преобразования текста подробно описан в руководстве scikit-learn по текстовым признакам.

На этом этапе полезно различать два процесса, которые часто объединяют словом «обучение»:

  1. Настройка представления текста. Векторизатор определяет словарь, частоты и числовые значения признаков по обучающим обращениям.
  2. Настройка классификатора. Модель использует признаки вместе с правильными темами, чтобы подобрать веса для каждой темы.

Векторизатор не знает, что «платёж» означает именно оплату. Эту связь классификатор обнаруживает, сопоставляя признаки с проверенными ответами. Словарь и частоты также нельзя вычислять по будущему тесту: тогда информация из него попадёт в обучение.

Справка. В этой главе параметры - числа, которые алгоритм подбирает во время обучения: веса признаков и свободные члены модели. Гиперпараметры задают правила самого обучения: например, насколько сильна регуляризация или какие сочетания слов включать в словарь. Гиперпараметры выбирают отдельной проверкой, а не «выучивают» из одного обращения тем же способом, что веса.

Вывод раздела

  • Модель видит не абстрактный «текст», а числовые признаки и проверенные ответы.
  • Векторизатор и классификатор настраиваются по обучающей части данных.
  • Параметры модели и настройки способа обучения выполняют разные роли.

Как из признаков получается прогноз

Сначала упростим задачу до решения «оплата или не оплата». Это позволит проследить расчёт, не отвлекаясь на три темы сразу.

Представим, что у модели только два признака:

  • x₁ - насколько сильно в обращении выражены слова о платеже;
  • x₂ - насколько сильно выражены слова о доставке.

Для каждого признака модель хранит вес: w₁ и w₂. Кроме них есть свободный член b, который помогает установить исходный уровень оценки, даже если значения признаков равны нулю.

Сначала вычисляется число, называемое логитом:

z = w₁ × x₁ + w₂ × x₂ + b.

Например, пусть у обращения x₁ = 0,8 и x₂ = 0,3, а текущие параметры равны w₁ = 2, w₂ = −1, b = −0,5. Тогда:

z = 2 × 0,8 − 1 × 0,3 − 0,5 = 0,8.

Положительный вес признака оплаты увеличил оценку. Отрицательный вес признака доставки уменьшил её. Но логит не является вероятностью: он может принимать значения от очень больших отрицательных до очень больших положительных.

Чтобы получить число от 0 до 1, применяется сигмоидная функция:

p = 1 / [1 + exp(−z)].

Здесь exp - показательная функция. При z = 0,8 получаем p ≈ 0,69. Это оценка класса «оплата» в данной упрощённой модели. Чем больше логит, тем ближе результат к 1; чем меньше, тем ближе к 0.

Почему признаки складываются с весами? Такая конструкция позволяет модели учесть несколько сигналов одновременно. Слово «оплатил» может поддерживать тему оплаты, а слова «курьер не приехал» - тему доставки. Их совместный вклад определяет итоговую оценку. Линейная форма ограничивает модель: она не распознает любое сложное правило сама по себе. Часть сочетаний приходится заранее представить отдельными признаками, например парами слов.

Важно не читать отдельный вес как самостоятельное причинное утверждение. Значение веса зависит от других признаков, их масштаба и регуляризации. Большой вес выражения «не прошёл платёж» показывает, как модель использует этот признак в текущем наборе данных; он не доказывает, что выражение является причиной проблемы клиента.

Вывод раздела

  • Прогноз начинается со взвешенной суммы признаков.
  • Функция преобразует эту сумму в оценку класса.
  • Вес показывает вклад признака в расчёт модели при данном наборе остальных признаков.

Откуда модель узнаёт, что её ответ плохой

На старте веса не знают связи между текстом и темой. Модель может дать платёжному обращению оценку 0,2, хотя правильная метка равна 1. Нужно численно описать, насколько плох этот прогноз. Для этого служит функция потерь.

Для бинарной классификации с правильным ответом y, равным 1 для оплаты и 0 для других тем, используют логарифмическую потерю:

Потеря = −[y × ln(p) + (1 − y) × ln(1 − p)].

Здесь ln - натуральный логарифм, а p - оценка класса «оплата». Формула кажется громоздкой, но для каждой конкретной заявки в ней остаётся только одна часть.

Если обращение действительно об оплате, y = 1:

Потеря = −ln(p).

Если обращение относится к другой теме, y = 0:

Потеря = −ln(1 − p).

Посмотрим на платёжное обращение. Когда модель даёт ему p = 0,9, потеря составляет примерно 0,105. Если она сомневается и даёт p = 0,5, потеря равна примерно 0,693. Если она уверенно ошибается и даёт p = 0,01, потеря возрастает примерно до 4,605.

Оценка оплаты для настоящего платёжного обращения Потеря
0,90 0,105
0,50 0,693
0,01 4,605

Таким образом, функция потерь различает не только правильный и неправильный класс, но и уверенность прогноза. Уверенная ошибка получает большой штраф. Это полезно при обучении: модель должна исправлять не только сам факт ошибки, но и опасную чрезмерную уверенность. Определение log loss приведено в документации scikit-learn.

Для набора обращений вычисляют среднюю потерю:

Средняя потеря = сумма потерь всех обучающих обращений / число обращений.

Именно это число алгоритм стремится уменьшить. Но оно не является готовым ответом на вопрос, можно ли запускать систему. После обучения нам понадобятся отдельная проверка на новых данных и метрики конкретного действия, например доля пропущенных платёжных заявок. Эти вопросы подробно разбираются в главе о проверке качества модели.

Вывод раздела

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

Как ошибка превращается в изменение весов

Теперь главное: каким образом модель понимает, какой вес изменить и в какую сторону?

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

Вернёмся к бинарному случаю. Логит равен z = w × x + b для одного признака x; оценка класса p получается применением сигмоиды к z. Можно показать, что производная потери по логиту имеет простую форму:

Изменение потери при изменении z = p − y.

Это результат последовательного применения производных: сначала смотрим, как потеря меняется при изменении p, затем как p меняется при изменении z. Для каждого веса получается:

Градиент по весу w = (p − y) × x.

А для свободного члена:

Градиент по b = p − y.

Проверим смысл без сложной математики. Если правильная метка y = 1, а модель дала p = 0,2, то p − y = −0,8. Для присутствующего признака с положительным x градиент отрицательный. Чтобы уменьшить потерю, вес этого признака нужно увеличить. Если правильная метка y = 0, а модель ошибочно дала высокую оценку оплаты, градиент будет положительным, и вес нужно уменьшить.

Алгоритм оптимизации делает шаг в сторону уменьшения потери:

Новый вес = старый вес − скорость шага × градиент.

Скорость шага часто обозначают η и называют скоростью обучения. При слишком большом шаге можно перескочить область хороших значений; при слишком маленьком обучение идёт медленно. Это описание относится к методам градиентного спуска. Конкретные библиотеки могут использовать и другие численные оптимизаторы.

Один шаг с числами

Возьмём игрушечную задачу с одним признаком «есть сигнал об оплате»:

  • значение признака x = 1;
  • правильная метка y = 1;
  • начальный вес w = 0;
  • начальный свободный член b = 0.

Сначала z = 0, а значит, p = 0,5. Модель пока не отдаёт предпочтения ни одному ответу.

Градиент по весу равен (0,5 − 1) × 1 = −0,5. Градиент по свободному члену тоже равен −0,5. Пусть скорость шага η = 0,2:

Новый w = 0 − 0,2 × (−0,5) = 0,1.

Новый b = 0 − 0,2 × (−0,5) = 0,1.

При следующем расчёте для того же примера z = 0,1 × 1 + 0,1 = 0,2, а p ≈ 0,55. Оценка оплаты сдвинулась в правильную сторону.

Это не означает, что одно обращение обучило модель. Следующая заявка может подтолкнуть те же параметры иначе. Полезные веса возникают после обработки множества разнообразных случаев, когда алгоритм ищет сочетание, снижающее общую потерю.

В стохастическом градиентном спуске параметры обновляют по отдельным примерам или небольшим группам. Другие оптимизаторы используют информацию о всей обучающей выборке и более сложные правила шага. Например, LogisticRegression в scikit-learn по умолчанию применяет решатель lbfgs, а не буквально повторяет показанный выше шаг для каждой строки. Числовой пример объясняет принцип связи ошибки и параметра, а не внутренний код конкретного решателя. Для нейросетей последовательность «прогноз -> потеря -> градиенты -> шаг оптимизатора» видна также в учебном материале PyTorch.

Вывод раздела

  • Градиент показывает, как изменение параметра повлияет на потерю.
  • Обучение повторяет вычисление прогноза, потери и изменения параметров на множестве примеров.
  • Скорость шага относится к конкретному способу оптимизации; она не равна «силе обучения» модели в целом.

Как тот же механизм работает для трёх тем

До сих пор мы упрощали задачу до «оплата или не оплата». В настоящем цикле модель различает оплату, доставку и доступ.

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

z(оплата) = веса оплаты × признаки + свободный член оплаты.

z(доставка) = веса доставки × признаки + свободный член доставки.

z(доступ) = веса доступа × признаки + свободный член доступа.

Здесь «веса × признаки» означает сумму произведений каждого признака на его вес для данной темы.

Затем функция softmax превращает три логита в доли, сумма которых равна 1:

Оценка темы k = exp[z(k)] / сумма exp[z(всех тем)].

Например, если логиты равны 2 для оплаты, 1 для доставки и 0 для доступа, значения exp приблизительно равны 7,39; 2,72 и 1. После деления на их сумму оценки составят примерно 0,665; 0,245 и 0,090.

Если правильная тема этого обращения - оплата, его потеря равна:

Потеря = −ln(оценка правильной темы) = −ln(0,665) ≈ 0,408.

Когда оценка правильной темы растёт, потеря падает. Когда модель уверенно выбирает неправильную тему, потеря увеличивается. При обучении параметры всех трёх тем меняются так, чтобы уменьшить суммарную потерю на обучающих обращениях.

Это объясняет, почему модель может научиться использовать сочетания сигналов. Признак «платёж» поддерживает оплату, а «курьер» сильнее поддерживает доставку. В заявке «Оплатил доставку, но курьер не приехал» второй сигнал может перевесить первый.

Есть граница применимости: линейная модель складывает вклады уже заданных признаков. Если важное различие зависит от сложного порядка слов, длинного контекста или информации за пределами текста заявки, одних отдельных слов и пар слов может не хватить. Тогда нужно улучшить представление входа или выбрать другую модель. Более сложная модель имеет смысл только после проверки, что проблема действительно в ограничениях текущей, а не в разметке или качестве данных.

Почему модель может запомнить обучение и ошибаться на новых заявках

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

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

Это переобучение: модель слишком хорошо приспосабливается к особенностям обучающей части и хуже переносит найденную закономерность на новые случаи.

Обратная ситуация - недообучение. Если модель слишком ограничена, она не улавливает даже устойчивые признаки. Например, при чрезмерном ограничении весов оценки тем будут слабо меняться от текста к тексту, а модель начнёт почти всегда предпочитать наиболее частую тему.

Диагностика требует смотреть на обучение и валидацию одновременно:

Наблюдение Возможное объяснение Следующий шаг
Потеря высока и на обучении, и на валидации Модель не извлекла полезное правило или обучение не завершилось Проверить признаки, метки, настройку и сходимость
На обучении потеря низкая, на валидации заметно выше Возможное переобучение Проверить редкие признаки, зависимые заявки и силу регуляризации
Обе потери низкие Хороший знак, но не окончательное доказательство Проверить более поздний период и важные виды ошибок
Качество на валидации нестабильно между периодами Изменился поток обращений или проверка слишком мала Разобрать периоды, темы и число примеров

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

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

Вывод раздела

  • Малая потеря на обучении описывает известные модели примеры, а не будущие заявки.
  • Разрыв между обучением и проверкой указывает на проблему переноса, но её причину нужно исследовать.
  • Сложность модели подбирают по качеству на новых данных, а не по способности запомнить обучающие.

Регуляризация: зачем ограничивать большие веса

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

Для простого варианта с L2-регуляризацией цель обучения можно записать так:

Цель = средняя потеря на обучающих заявках + (λ / 2) × сумма квадратов весов.

Здесь λ управляет силой ограничения. Чем оно больше, тем дороже модели давать отдельным признакам очень крупные веса.

Предположим, редкое слово встретилось только в двух платёжных заявках. Без ограничения модель может придать ему огромную важность и уверенно классифицировать по нему будущие обращения. Штраф заставляет сравнить выгоду от идеального распознавания двух известных заявок с ценой слишком сильного веса. Если выгода невелика, вес останется умеренным.

У L2-штрафа есть понятный вклад в градиент:

Градиент штрафа по весу w = λ × w.

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

Но слишком сильная регуляризация тоже вредна. Она может подавить действительно полезные сигналы, например сочетание «не прошёл платёж». Поэтому её силу выбирают по валидации, а не по принципу «чем больше, тем безопаснее».

В LogisticRegression библиотеки scikit-learn параметр C задаёт обратную силу регуляризации: меньший C означает более сильное ограничение весов. Точная нормировка функции потерь в библиотеке сложнее нашей учебной записи с λ, поэтому не следует считать C буквальным значением 1 / λ в каждой формуле. Практическое направление изменения параметра описано в справке scikit-learn.

Справка. L1-регуляризация использует сумму модулей весов и может обнулять часть из них. L2 использует сумму квадратов и обычно уменьшает веса плавнее. Это разные способы ограничить сложность; выбор проверяют на новых данных. Ни один из них не исправит ошибочную разметку или признак, взятый из будущего.

Вывод раздела

  • Регуляризация снижает выгоду от запоминания случайных деталей обучающих заявок.
  • Слабое ограничение может оставить переобучение, чрезмерное - привести к недообучению.
  • Значение C подбирают по отдельной проверке; меньший C в scikit-learn означает более сильную регуляризацию.

Что делать с редкой темой «оплата»

В нашем сквозном примере обращений об оплате может быть только 6%. Если при обучении считать все заявки одинаково, частые темы дадут основной вклад в суммарную потерю. Модель способна улучшать средний результат и при этом недостаточно хорошо находить оплату.

Один вариант - веса классов: ошибка на платёжном обращении получает больший вес в функции потерь. Это меняет то, чему модель отдаёт предпочтение во время обучения. В scikit-learn для этого есть параметр class_weight, включая режим "balanced", который рассчитывает веса по частотам классов в обучающих данных.

Но увеличение веса редкого класса не является автоматическим решением. Оно может увеличить число найденных платёжных заявок и одновременно отправить профильной команде больше чужих обращений. Кроме того, изменятся оценки классов: перед использованием их как вероятностей потребуется отдельно проверить калибровку.

Есть и другой рычаг - порог принятия решения после обучения. Вес класса меняет саму подгонку параметров; порог меняет действие на основе готовых оценок. Это разные вмешательства. Их сравнивают по результату на валидации и по нагрузке на команду, как показано в главе о проверке качества модели.

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

Как выглядит обучение в коде

Ниже - минимальная схема для подготовленных данных. Предположим, таблица train содержит:

  • text_at_creation - текст, доступный при поступлении заявки;
  • reviewed_topic - тему, подтверждённую после её обработки.

Валидационная таблица valid содержит те же поля, но её заявки не используются в fit.

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import log_loss
from sklearn.pipeline import Pipeline

model = Pipeline([
    (
        "text",
        TfidfVectorizer(
            ngram_range=(1, 2),
            min_df=2
        )
    ),
    (
        "classifier",
        LogisticRegression(
            C=1.0,
            max_iter=1000
        )
    )
])

model.fit(
    train["text_at_creation"],
    train["reviewed_topic"]
)

train_probabilities = model.predict_proba(train["text_at_creation"])
valid_probabilities = model.predict_proba(valid["text_at_creation"])

print(
    "Потеря на обучении:",
    log_loss(
        train["reviewed_topic"],
        train_probabilities,
        labels=model.classes_
    )
)

print(
    "Потеря на валидации:",
    log_loss(
        valid["reviewed_topic"],
        valid_probabilities,
        labels=model.classes_
    )
)

Pipeline связывает преобразование текста и классификатор в одну последовательность. При вызове fit словарь и веса TF-IDF строятся на train, затем на полученных числовых признаках обучается классификатор. При вызове predict_proba для valid применяется уже настроенный векторизатор. Это помогает не подмешивать проверочные заявки в подготовку признаков. Такую последовательность работы описывает документация Pipeline.

Для выбора регуляризации можно обучить несколько вариантов с разными значениями C и сравнить их на валидации. Это не просто поиск самой маленькой потери на train: большой C может отлично подогнать обучающие тексты и хуже справиться с новыми. Если обучение не сходится за заданное число итераций, сначала разбирают предупреждение, признаки и решатель, а не объявляют полученные веса окончательными.

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

Как обучение и проверка складываются в одну систему

Теперь можно восстановить процесс целиком:

Подготовленные обращения -> признаки текста -> оценки тем -> потеря относительно проверенной темы -> изменение весов -> повторение на обучающих примерах -> проверка на новых заявках.

Каждая часть решает свою задачу:

  • Признаки переводят обращение в форму, с которой модель может работать.
  • Параметры хранят выученную связь между признаками и темами.
  • Функция потерь показывает, какие прогнозы нужно исправлять сильнее.
  • Оптимизатор определяет, как изменить параметры, чтобы снизить потерю.
  • Регуляризация ограничивает подгонку под случайные особенности обучающих заявок.
  • Валидация и тест показывают, сохранилась ли польза на новых случаях.

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

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

Продолжить

  1. 01 Введение в машинное обучение: что это такое и какие задачи оно решает Понятное введение в машинное обучение: отличие от обычной программы, данные, модели, обучение, классификация, регрессия, кластеризация и связь с data mining.
  2. 02 RAG-системы: как языковая модель отвечает на основе внешних данных Подробно разбираем, что такое RAG-система, из каких компонентов она состоит, как документы превращаются в индекс, как работает поиск и как LLM формирует ответ на основе найденного контекста.
  3. 03 Подготовка данных для ML: разметка, признаки и утечка данных Как собрать данные для обучения модели: определить правильный ответ, подготовить признаки, избежать утечки и проверить выборку.
  4. 04 Как проверить качество ML-модели: выборка, метрики и цена ошибок Разбираем проверку ML-модели на примере обращений клиентов: разделение данных, матрица ошибок, precision и recall, выбор порога, калибровка и контроль после запуска.

Обратная связь

Материал был полезен?

Нашли ошибку или неточность? Сообщить →

Начните вводить — найдём материал по всей библиотеке.