Использование конвейера для предварительной обработки ваших данных дает некоторые существенные преимущества. Конвейер гарантирует, что никакая информация из набора тестов не используется при предварительной обработке или обучении модели. Конвейеры часто сочетаются с перекрестной проверкой, чтобы найти лучшую комбинацию параметров алгоритма машинного обучения. Однако реализованные шаги предварительной обработки, например, масштабировать ли данные, или реализованный алгоритм машинного обучения также можно рассматривать как гиперпараметр; не одной модели, а всего тренировочного процесса. Таким образом, мы можем настроить их как таковые для дальнейшего повышения производительности нашей модели. В этом посте я покажу вам, как это сделать с помощью sci-kit learn!

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

import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV, RepeatedStratifiedKFold
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.metrics import f1_score, classification_report
from sklearn.impute import SimpleImputer
from sklearn.model_selection import train_test_split

Мы снова работаем с набором данных «Титаник».

titanic = pd.read_csv('./titanic.csv')
titanic.head()

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

X = titanic.drop('survived', axis = 1)
y = titanic.survived
X_train, X_holdout, y_train, y_holdout = train_test_split(X, y, stratify = y, test_size = 0.2, random_state = 42)

Следуя последнему посту, мы создаем конвейер, включающий ColumnTransformer (препроцессор), который вычисляет пропущенные значения, создает фиктивные переменные для категориальных функций и масштабирует числовые функции.

categorical_features = ['pclass', 'sex', 'embarked']
categorical_transformer = Pipeline(
    [
        ('imputer_cat', SimpleImputer(strategy = 'constant', fill_value = 'missing')),
        ('onehot', OneHotEncoder(handle_unknown = 'ignore'))
    ]
)

numeric_features = ['age', 'sibsp', 'parch', 'fare']
numeric_transformer = Pipeline(
    [
        ('imputer_num', SimpleImputer()),
        ('scaler', StandardScaler())
    ]
)

preprocessor = ColumnTransformer(
    [
        ('categoricals', categorical_transformer, categorical_features),
        ('numericals', numeric_transformer, numeric_features)
    ],
    remainder = 'drop'
)

В конце концов, мы включаем этот препроцессор в наш конвейер.

pipeline = Pipeline(
    [
        ('preprocessing', preprocessor),
        ('clf', LogisticRegression())
    ]
)

Настройка алгоритма машинного обучения

Точно так же, как мы предоставляем список гиперпараметров алгоритма машинного обучения в сетке параметров, чтобы найти лучшую комбинацию параметров, мы также можем заполнить сам алгоритм машинного обучения как «гиперпараметр». ('clf', LogisticRegression())above - это просто заполнитель, в который можно вставить другие алгоритмы машинного обучения. В приведенной ниже таблице я сначала пробую логистическую регрессию, а во-вторых, классификатор случайного леса. Обратите внимание, что параметры должны быть списком словарей, потому что обе модели имеют разные значения параметров для настройки.

params = [
    {
        'clf': [LogisticRegression()],   
        'clf__solver': ['liblinear'],
        'clf__penalty': ['l1', 'l2'],
        'clf__C': [0.01, 0.1, 1, 10, 100],
        'clf__random_state': [42],
    },
    {
        'clf': [RandomForestClassifier()],
        'clf__n_estimators': [5, 50, 100, 250],
        'clf__max_depth': [5, 8, 10],
        'clf__random_state': [42],
    }
]

Настройка шагов предварительной обработки

Далее мы позаботимся о настройке шагов предварительной обработки. Мы добавляем их как параметры в сетку параметров, вставляя их имена, указанные в конвейере выше: StandardScaler() для предварительной обработки числовых значений может быть адресован 'preprocessing__numericals__scaler'. 'preprocessing' обращается к этапу конвейера, которым является наш ColumnTransformer, '__numericals' обращается к конвейеру для числовых значений внутри этого ColumnTransformer, а '__scaler' обращается к StandardScaler в этом конкретном конвейере. Мы могли бы изменить StandardScaler здесь, например, указав 'preprocessing__scaler__with_std': ['False'], но мы также можем указать, будет ли стандартизация выполняться вообще. Передав список [StandardScaler(), 'passthrough'] на шаг 'scaler', мы либо используем StandardScaler() на этом шаге, либо вообще не используем трансформатор (с 'passthrough'). Таким образом, мы можем оценить, как меняется производительность нашей модели, если мы вообще не стандартизируем ее! То же верно и для импутера: мы можем проверить, дает ли среднее или медианное значение лучшую производительность в данном конкретном процессе перекрестной проверки.

Ниже вы найдете полную сетку параметров со всеми упомянутыми параметрами:

params = [
    {
        'clf': [LogisticRegression()],   
        'clf__solver': ['liblinear'],
        'clf__penalty': ['l1', 'l2'],
        'clf__C': [0.01, 0.1, 1, 10, 100],
        'clf__random_state': [42],
        'preprocessing__numericals__scaler': [StandardScaler(), 'passthrough'],
        'preprocessing__numericals__imputer_num__strategy': ['mean', 'median']
    },
    {
        'clf': [RandomForestClassifier()],
        'clf__n_estimators': [5, 50, 100, 250],
        'clf__max_depth': [5, 8, 10],
        'clf__random_state': [42],
        'preprocessing__numericals__scaler': [StandardScaler(), 'passthrough'],
        'preprocessing__numericals__imputer_num__strategy': ['mean', 'median']
    }
]

И последнее: если вы хотите изменить StandardScaler(), e. г. установив with_mean, вам нужно будет сделать это в последней точке, где вы объявляете, что нужно заполнить в шаге 'scaler'. Здесь это будет 'preprocessing__numericals__scaler': [StandardScaler(with_mean = False), 'passthrough'].

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

rskf = RepeatedStratifiedKFold(n_splits = 5, n_repeats = 2, random_state = 42)

cv = GridSearchCV(pipeline, params, cv = rskf, scoring = ['f1', 'accuracy'], refit = 'f1', n_jobs = -1)

cv.fit(X_train, y_train)

print(f'Best F1-score: {cv.best_score_:.3f}\n')
print(f'Best parameter set: {cv.best_params_}\n')
print(f'Scores: {classification_report(y_train, cv.predict(X_train))}')

Результаты:

Best F1-score: 0.722

Best parameter set: {'clf': RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini',
                       max_depth=8, max_features='auto', max_leaf_nodes=None,
                       min_impurity_decrease=0.0, min_impurity_split=None,
                       min_samples_leaf=1, min_samples_split=2,
                       min_weight_fraction_leaf=0.0, n_estimators=50,
                       n_jobs=None, oob_score=False, random_state=42, verbose=0,
                       warm_start=False), 'clf__max_depth': 8, 'clf__n_estimators': 50, 'clf__random_state': 42, 'preprocessing__numericals__imputer_num__strategy': 'median', 'preprocessing__numericals__scaler': StandardScaler(copy=True, with_mean=True, with_std=True)}

Scores:               precision    recall  f1-score   support

           0       0.87      0.95      0.91       647
           1       0.91      0.77      0.83       400

    accuracy                           0.88      1047
   macro avg       0.89      0.86      0.87      1047
weighted avg       0.88      0.88      0.88      1047

Наша лучшая оценка - это случайный лес с max_depth = 8, n_estimators = 50, условным расчетом с помощью медианы и стандартизованных числовых значений.

Как мы поступаем с совершенно новыми, но невидимыми данными?

preds = cv.predict(X_holdout)
print(f'Scores: {classification_report(y_holdout, preds)}\n')
print(f'F1-score: {f1_score(y_holdout, preds):.3f}')

Результаты:

Scores:               precision    recall  f1-score   support

           0       0.83      0.88      0.86       162
           1       0.79      0.71      0.75       100

    accuracy                           0.82       262
   macro avg       0.81      0.80      0.80       262
weighted avg       0.82      0.82      0.81       262


F1-score: 0.747

Кажется, есть возможности для улучшения!

Этот пост также был опубликован в моем блоге.

Полный код можно найти в одном файле здесь: