Проект ЦИТадель
Большие языковые модели не отменили классическое машинное обучение. Для кредитного скоринга, обнаружения мошенничества, прогноза оттока и спроса, ранжирования заявок и других задач со структурированными данными линейная модель или ансамбль деревьев часто остаются разумной отправной точкой. Они сравнительно дёшевы в обучении и эксплуатации, а их качество можно проверять привычными статистическими методами.
Эта статья показывает полный рабочий цикл: как перевести прикладную проблему в задачу обучения, подготовить данные без утечки, выбрать схему проверки и метрику, сравнить несколько моделей и сопровождать результат после внедрения. Основные термины собраны в глоссарии, а место классических методов на общей карте ИИ показано в обзорной статье.
Обычная программа следует правилам, которые задал разработчик. Модель машинного обучения оценивает закономерность по примерам. Такой подход полезен, когда правило зависит от множества взаимодействующих факторов, содержит шум или слишком часто меняется, чтобы поддерживать его вручную. Но сложность сама по себе не доказывает, что нужна модель.
До выбора алгоритма следует ответить на несколько вопросов:
Для обучения с учителем нужны репрезентативные примеры с целевой переменной. Для кластеризации и обнаружения аномалий готовых ответов может не быть, но всё равно нужны данные, соответствующие будущему потоку, и способ проверить полезность результата. Если задачу удовлетворительно решает проверяемое правило или SQL-запрос, это хороший базовый ориентир и нередко готовое решение. Правила тоже приходится сопровождать, поэтому сравнивать следует не модность подходов, а качество, стоимость и риск всего процесса.
Классификация определяет один или несколько классов и часто оценивает их вероятности: спам или не спам, категория обращения, уход или сохранение клиента. Регрессия оценивает число: сумму, срок или объём спроса. Ранжирование упорядочивает документы, рекомендации или заявки. Прогноз временного ряда может быть регрессией или классификацией, но требует особой проверки на последовательных временных интервалах.
В обучении без учителя кластеризация ищет группы похожих объектов, а методы обнаружения аномалий — нетипичные наблюдения. Найденная структура ещё не является прикладным результатом: сегменты нужно проверить на устойчивость и полезность, а аномалии — на приемлемую долю ложных срабатываний.
Предсказание не следует смешивать с решением и тем более с причинным эффектом. Модель оттока может найти клиентов с высоким риском ухода, но из этого не следует, что скидка удержит именно их. Вопрос «кто уйдёт?» относится к прогнозированию, а «для кого мера изменит исход?» — к оценке эффекта воздействия. Для второго обычно нужны данные контролируемого эксперимента или явно обоснованный причинный дизайн. История прежних решений также может искажать выборку: например, метка возврата долга наблюдается только для выданных кредитов.
Полезно записать постановку как небольшой контракт: объект прогноза, целевая переменная и горизонт, момент расчёта, доступные признаки, действие по результату, цена ошибок и способ получить фактический исход. Такая запись часто обнаруживает проблему раньше, чем обучение первой модели.
В табличной задаче строки обычно соответствуют объектам или событиям, столбцы — признакам, а отдельный столбец содержит целевую переменную. Признаками могут быть число заказов за 30 дней, срок с последнего визита, тариф, регион или агрегат по истории операций. Полезность таких признаков зависит не только от формулы, но и от происхождения данных, момента их появления, правил заполнения пропусков и определения целевой переменной.
Конструирование признаков особенно важно, когда предметная область подсказывает подходящие интервалы, отношения и агрегаты. Однако сначала нужно построить простой базовый вариант: улучшение может дать и новая модель, и исправление меток, и более подходящее разбиение, и дополнительный источник данных. Какой из путей полезнее, устанавливает эксперимент, а не общее правило.
Утечка данных возникает, когда при обучении или проверке модель получает информацию, которой не будет в реальном прогнозе. Основные варианты таковы:
Поэтому сначала фиксируют временной срез и отделяют проверочную часть, а всю обучаемую предобработку включают в единый pipeline с моделью. Для каждого признака полезно хранить не только тип, но и источник, время доступности и версию расчёта. Неожиданно высокая метрика — повод отдельно проверить дубликаты, временные границы и происхождение полей.
Линейная и логистическая регрессия быстры, хорошо изучены и дают сильную исходную точку. Их коэффициенты описывают условную связь признака с прогнозом при фиксированных остальных признаках. Величина коэффициента зависит от масштаба и кодирования, а коррелированные признаки делают интерпретацию неустойчивой. Коэффициент предсказательной модели сам по себе не доказывает причинного влияния.
Дерево решений делит пространство признаков последовательностью условий. Одно дерево наглядно, но легко переобучается и может заметно измениться при небольшом изменении данных. Случайный лес усредняет много деревьев, уменьшая эту нестабильность. Это удобный базовый вариант для нелинейных зависимостей, но он не освобождает от настройки, проверки вероятностей и контроля размера модели.
Градиентный бустинг по деревьям последовательно добавляет деревья, каждое из которых уменьшает ошибки текущего ансамбля. Реализации XGBoost, LightGBM и CatBoost часто дают высокое качество на табличных данных среднего размера, поэтому бустинг стоит включать в сравнение, но он не является автоматическим победителем. Подробности разобраны в отдельной статье.
Метод ближайших соседей полезен там, где осмысленно задана мера близости, но плохо масштабируется по числу объектов и признаков. SVM бывает силён на небольших и средних выборках, особенно с разреженными признаками. Наивный Байес остаётся быстрым базовым вариантом, в частности для текстовой классификации. Нейронные сети незаменимы для многих задач с изображениями, звуком и текстом; на типичных табличных наборах ансамбли деревьев нередко конкурентоспособнее, но результат зависит от размера данных, режима предобучения и устройства конкретной задачи. Выбор алгоритма должен подтверждаться одной схемой проверки и учитывать не только метрику, но и задержку, память и стоимость сопровождения.
Модель проверяют на данных, не участвовавших в её настройке. Обычно исходные данные делят на обучающую часть и закрытую тестовую выборку, а варианты модели сравнивают кросс-валидацией внутри обучающей части. После выбора признаков, алгоритма, гиперпараметров и порога тест используют один раз для итоговой оценки. Если по результату теста снова менять решение, тест фактически превращается в ещё одну валидационную выборку.
Способ разбиения должен воспроизводить будущую эксплуатацию:
Случайная k-кратная кросс-валидация не исправляет неверную постановку и не показывает качество после сдвига распределения. Для временных, групповых и пространственно связанных данных нужны соответствующие схемы разбиения.
Метрика должна соответствовать последующему решению. Accuracy бесполезна как единственная оценка при сильном дисбалансе: если мошеннических операций 0,3%, постоянный ответ «обычная операция» даёт 99,7% accuracy и не находит ни одного случая. Precision показывает долю верных срабатываний, recall — долю найденных положительных случаев. Изменение порога обычно меняет обе величины, но их связь не обязана быть строго монотонной.
ROC-AUC оценивает ранжирование положительных и отрицательных примеров по всем порогам. Для редкого положительного класса полезно дополнительно смотреть кривую precision–recall и average precision (AP): они лучше показывают, сколько ложных срабатываний сопровождает найденные случаи. Ни ROC-AUC, ни AP не выбирают рабочий порог. Его задают на валидационных данных с учётом цены ошибок, пропускной способности операционного процесса и требуемой полноты. Если число 0,8 должно означать примерно 80% вероятности события, отдельно проверяют калибровку вероятностей.
Для регрессии MAE выражает среднюю абсолютную ошибку в единицах цели, а RMSE сильнее реагирует на крупные промахи. При асимметричной цене ошибок может понадобиться квантильная функция потерь. В любом случае следует показывать разброс оценки между разбиениями, сравнение с простым базовым вариантом и качество на значимых подгруппах, а не только одно среднее число.
Ниже — компактный шаблон бинарной классификации оттока. Он
предполагает, что churned содержит 0 или 1, а
customer_id не используется как признак. В демонстрации типы
столбцов определяются автоматически; в рабочей системе схему и смысл типов
лучше зафиксировать явно.
import pandas as pd
from sklearn.base import clone
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import RandomForestClassifier
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import (
average_precision_score,
classification_report,
roc_auc_score,
)
from sklearn.model_selection import (
StratifiedKFold,
cross_validate,
train_test_split,
)
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler
df = pd.read_csv("churn.csv")
y = df["churned"]
X = df.drop(columns=["churned", "customer_id"])
num_cols = X.select_dtypes(include="number").columns
cat_cols = X.select_dtypes(exclude="number").columns
num_pipe = Pipeline([
("imputer", SimpleImputer(strategy="median")),
("scaler", StandardScaler()),
])
cat_pipe = Pipeline([
("imputer", SimpleImputer(strategy="most_frequent")),
("onehot", OneHotEncoder(handle_unknown="ignore")),
])
prep = ColumnTransformer([
("num", num_pipe, num_cols),
("cat", cat_pipe, cat_cols),
])
# Тест отделяется до настройки предобработки и моделей.
X_train, X_test, y_train, y_test = train_test_split(
X,
y,
test_size=0.2,
stratify=y,
random_state=42,
)
models = {
"logreg": LogisticRegression(max_iter=1000),
"forest": RandomForestClassifier(
n_estimators=300,
min_samples_leaf=2,
n_jobs=-1,
random_state=42,
),
}
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
results = {}
for name, model in models.items():
candidate = Pipeline([
("prep", prep),
("model", model),
])
scores = cross_validate(
candidate,
X_train,
y_train,
cv=cv,
scoring={"roc_auc": "roc_auc", "ap": "average_precision"},
)
results[name] = {
"model": candidate,
"ap": scores["test_ap"].mean(),
}
print(
f"{name}: "
f"ROC-AUC = {scores['test_roc_auc'].mean():.3f} "
f"+/- {scores['test_roc_auc'].std():.3f}; "
f"AP = {scores['test_ap'].mean():.3f} "
f"+/- {scores['test_ap'].std():.3f}"
)
# Здесь редкий положительный класс, поэтому выбираем модель по AP.
best_name = max(results, key=lambda name: results[name]["ap"])
best = clone(results[best_name]["model"])
best.fit(X_train, y_train)
# Закрытая тестовая выборка используется только после выбора модели.
proba = best.predict_proba(X_test)[:, 1]
print("selected:", best_name)
print("test ROC-AUC:", round(roc_auc_score(y_test, proba), 3))
print("test AP:", round(average_precision_score(y_test, proba), 3))
# 0.5 -- лишь демонстрационный порог, а не универсальное значение.
prediction = proba >= 0.5
print(classification_report(y_test, prediction, zero_division=0))
Pipeline гарантирует, что заполнение пропусков,
масштабирование и словарь категорий на каждом разбиении настраиваются только
по обучающей части. Код
действительно выбирает кандидата с лучшим средним AP, затем заново обучает
его и лишь после этого обращается к тесту. Разброс метрик между разбиениями
показывает, насколько устойчиво сравнение.
Пример всё ещё намеренно упрощён. Для повторных записей одного клиента нужно групповое разбиение, для прогноза будущего — временное. Порог 0,5 оставлен только для показа отчёта: в проекте его выбирают на отдельной валидационной выборке или по прогнозам вне обучающих фолдов, а не на тесте. Если оптимизировать модели, признаки и порог много раз, полезно вести журнал экспериментов и заранее определить главную метрику.
Качество может измениться после внедрения: меняются пользователи, процессы, датчики, правила заполнения данных и сама политика, зависящая от предсказаний. Поэтому вместе с моделью версионируют код подготовки данных, схему признаков, интервал обучающей выборки, параметры и программное окружение. Для отката нужно уметь связать каждое предсказание с версией этого комплекта.
Мониторинг охватывает несколько разных сигналов:
Дрейф входов не равен падению качества: он может оказаться безвредным, а ошибка может вырасти и без заметного дрейфа отдельных столбцов. Поэтому триггер мониторинга должен вести к расследованию, а не автоматически публиковать переобученную модель. Новую версию снова проверяют на свежем временном интервале и вводят с возможностью отката; при высоком риске используют теневой или ограниченный запуск.
Логи признаков, прогнозов и исходов могут содержать персональные или чувствительные данные. Состав полей, сроки хранения и доступ к ним следует определить до запуска. Помимо качества самой модели, эксплуатационная система создаёт зависимости и обратные связи, которые тоже требуют тестов и документирования.
LLM удобна, когда входом служит свободный текст, разметки мало, формат быстро меняется или нужно быстро проверить идею. На устойчивой массовой задаче с фиксированной таблицей специализированная модель часто требует меньше вычислений и даёт более стабильную задержку. Но заранее объявлять победителя нельзя: варианты следует сравнить на одной репрезентативной выборке по качеству конечного решения, цене, задержке, приватности и требованиям к воспроизводимости.
Полезен гибридный процесс. Языковая модель извлекает из обращения тему, объекты или другие структурированные признаки, а классическая модель объединяет их с историей клиента и выполняет массовый прогноз. LLM также может помочь подготовить черновую разметку, но такую разметку нужно проверять на отдельной человеческой выборке: систематические ошибки учителя перейдут в следующую модель. Версии модели и запроса фиксируют так же, как версию обычного преобразования данных. Практическая работа с LLM разобрана в отдельном учебнике.
Pipeline.Алгоритм занимает лишь одну строку этого порядка. Надёжность результата чаще определяется постановкой, происхождением данных, схемой проверки и тем, что происходит с прогнозом после выпуска. Следующий материал подробно разбирает один из сильных кандидатов для табличных задач — градиентный бустинг.