Аналитика27 августа 2026 г.

Pipeline в scikit-learn: как собрать одинаковую подготовку данных для обучения и проверки

#COURSE##INNER#

Pipeline в scikit-learn: как собрать одинаковую подготовку данных для обучения и проверки

Если вопрос звучит как «как сделать, чтобы модель обучалась, проверялась и потом принимала новые строки с одной и той же подготовкой данных», то короткий ответ такой: собрать все обучаемые шаги в один объект Pipeline, а для разных типов столбцов добавить ColumnTransformer. Тогда вы не повторяете очистку и кодирование вручную по ячейкам ноутбука, а один раз описываете порядок действий и потом вызываете fit и predict у всего конвейера. Если нужно отдельно посмотреть преобразованные признаки, для transform используют подготовительную часть, например pipe.named_steps['prep'] или срез pipe[:-1].

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

Почему ручная подготовка быстро расползается

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

Такая ошибка не всегда заметна сразу. Код может выполняться без исключений, но модель фактически получает другой набор признаков, чем на обучении. Например, если отдельно делать get_dummies для train и validation, набор столбцов может отличаться. Если отдельно масштабировать данные, легко применить не те параметры или просто забыть этот шаг перед прогнозом. Воспроизводимость ломается не в одном большом месте, а в серии мелких расхождений.

Pipeline нужен именно для того, чтобы убрать эти расхождения. В scikit-learn конвейер хранит шаги в заданном порядке и при обучении последовательно вызывает их, передавая результат дальше. Снаружи вы работаете уже не с набором разрозненных объектов, а с одним конвейером: для модели это обычно fit и predict, а для отдельного просмотра подготовленных данных — transform у подготовительной части.

Какие шаги стоит включать в конвейер

Хорошее практическое правило: все, что чему-то «учится» на данных, лучше держать внутри конвейера. Это заполнение пропусков медианой или модой, масштабирование, one-hot-кодирование, отбор признаков, снижение размерности и сама модель. Тогда все параметры этих шагов будут выучены в одном месте и применены в том же порядке, когда вы перейдете от обучения к проверке или к новым данным.

Если таблица содержит разнородные столбцы, удобнее сначала разделить обработку по типам данных. Для числовых полей обычно нужны свои шаги, для категориальных — свои. В scikit-learn для этого есть ColumnTransformer: он берет указанные группы столбцов, применяет к каждой группе свою цепочку преобразований, а потом объединяет результат в общую матрицу признаков. Поверх него уже ставят общий Pipeline с моделью в последнем шаге.

На практике в конвейер обычно входят такие части:

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

Полезно помнить и техническое ограничение: в обычном Pipeline все шаги, кроме последнего, должны уметь делать transform. Последний шаг может быть моделью, которая уже выдает predict. Поэтому у всего pipeline метод transform есть не всегда, а только если его последний шаг сам поддерживает преобразование.

Что именно обучается во время fit

Когда вы вызываете pipe.fit(X_train, y_train), конвейер не просто «запускает код». Каждый промежуточный шаг сначала обучается на входных данных, а потом отдает преобразованный результат следующему шагу. Поэтому после fit внутри объекта сохраняются вполне конкретные параметры: у импутера — медианы или самые частые значения, у кодировщика — список увиденных категорий, у скейлера — статистики для масштабирования, у модели — коэффициенты или структура дерева.

Из-за этого один и тот же конвейер можно безопасно применять повторно. Для X_valid и для новых строк он уже не будет заново «переизобретать» подготовку. Он использует ровно те параметры, которые выучил на обучающей выборке. Это и есть воспроизводимость в прикладном смысле: одинаковые входные поля проходят через одинаковую последовательность шагов с одинаковыми сохраненными настройками.

Еще один полезный момент — проверяемость. У pipeline есть named_steps, а у подготовительной части можно запросить get_feature_names_out(). Значит, вы можете не только верить, что все собрано правильно, но и проверить, какие признаки реально получились на выходе. Для отладки это намного полезнее, чем ориентироваться только на итоговую метрику.

Учебный пример: один Pipeline для train, validation и новых строк

Учебный пример. Ниже условная задача бинарной классификации в среде Python 3 и scikit-learn. У нас есть два числовых столбца, один категориальный и маленькая обучающая выборка. Цель примера не в качестве модели, а в проверяемом результате: после обучения конвейер должен создавать одинаковые 5 признаков и для train, и для validation, и для новых строк.

import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler

# исходные данные
# строки 0-5: train, строки 6-7: validation

df = pd.DataFrame({
    'age': [25, 37, 29, None, 52, 46, 33, 41],
    'visits': [3, 8, 2, 5, 11, 9, None, 6],
    'plan': ['basic', 'pro', 'basic', 'pro', 'business', 'basic', None, 'vip'],
    'target': [0, 1, 0, 1, 1, 0, 0, 1]
})

X_train = df.loc[:5, ['age', 'visits', 'plan']]
y_train = df.loc[:5, 'target']
X_valid = df.loc[6:, ['age', 'visits', 'plan']]

numeric_features = ['age', 'visits']
categorical_features = ['plan']

numeric_pipe = Pipeline([
    ('imputer', SimpleImputer(strategy='median')),
    ('scaler', StandardScaler())
])

categorical_pipe = Pipeline([
    ('imputer', SimpleImputer(strategy='most_frequent')),
    ('onehot', OneHotEncoder(handle_unknown='ignore', sparse_output=False))
])

prep = ColumnTransformer([
    ('num', numeric_pipe, numeric_features),
    ('cat', categorical_pipe, categorical_features)
])

pipe = Pipeline([
    ('prep', prep),
    ('clf', LogisticRegression(max_iter=1000))
])

pipe.fit(X_train, y_train)

ohe = pipe.named_steps['prep'].named_transformers_['cat'].named_steps['onehot']
print(ohe.categories_[0].tolist())
print(pipe.named_steps['prep'].get_feature_names_out().tolist())
print(pipe.named_steps['prep'].transform(X_train).shape)
print(pipe.named_steps['prep'].transform(X_valid).shape)

X_new = pd.DataFrame({
    'age': [38, None],
    'visits': [4, 10],
    'plan': ['vip', 'business']
})

print(pipe.named_steps['prep'].transform(X_new).shape)
print(pipe.predict(X_new))

Что здесь можно проверить без догадок. На train значения age обучающий импутер заполнит медианой 37, а visits — медианой 6.5. По столбцу plan конвейер увидит только три категории: basic, business, pro. Значит, после подготовки на выходе получится 5 признаков: 2 числовых и 3 one-hot-столбца.

Проверяемый результат такой: форма преобразованных данных будет (6, 5) для train, (2, 5) для validation и (2, 5) для новых строк. При этом категория vip не ломает код: благодаря handle_unknown='ignore' конвейер не создает новый столбец на лету, а использует ту же структуру признаков, что и на обучении. Это и есть практическая цель конвейера: новые строки проходят по старым правилам, а не по заново собранной вручную схеме.

Как проверить воспроизводимость перед реальной работой

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

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

Быстрый чек-лист перед использованием на новых данных выглядит так:

  • все обучаемые преобразования находятся внутри Pipeline или ColumnTransformer;
  • вы можете получить список итоговых признаков через get_feature_names_out();
  • если последний шаг — модель, для просмотра признаков вы используете подготовительную часть, например pipe.named_steps['prep'] или pipe[:-1], а не pipe.transform(...);
  • новые строки содержат те же исходные поля, что и train;
  • неизвестные категории обработаны явно, например через handle_unknown='ignore';
  • сохраняется и загружается весь конвейер, а не только модель.

Если хотите углубить тему на практике, по официальной странице и сохраненной программе курса «Специалист по Data Science» у Яндекс Практикума в учебном плане есть предобработка данных, библиотека scikit-learn, кросс-валидация и отдельный проект, где работу упаковывают в пайплайн. Краткая карточка курса в каталоге: Специалист по Data Science.

Курсы

9540
4,2
14 отзывов
Специалист по Data Science
  • c 15 октября
  • 1 месяц
  • Смешанный
  • Диплом / Сертификат
168 500 ₽
Перейти на сайт