{"id":14293,"url":"\/distributions\/14293\/click?bit=1&hash=05c87a3ce0b7c4063dd46190317b7d4a16bc23b8ced3bfac605d44f253650a0f","hash":"05c87a3ce0b7c4063dd46190317b7d4a16bc23b8ced3bfac605d44f253650a0f","title":"\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u043d\u043e\u0432\u044b\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u043d\u0435 \u043f\u043e\u0442\u0440\u0430\u0442\u0438\u0432 \u043d\u0438 \u043a\u043e\u043f\u0435\u0439\u043a\u0438","buttonText":"","imageUuid":""}

10 функций Sklearn, которые упускают из виду 99% онлайн-курсов

Я написал более двух десятков статей о Sklearn, но эта библиотека не всегда была моей любимой. На самом деле, из-за ужасного онлайн-курса, который я прошел, я сначала ненавидел её.

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

Эта же будет гораздо более практична и применима к широкому спектру рабочих процессов.

Давайте начинать!

1. FunctionTransformer

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

Вот почему вы должны обернуть все свои пользовательские функции предварительной обработки внутрь FunctionTransformer, что может преобразовать их в Sklearn-совместимый преобразователь. Единственное требование состоит в том, чтобы функция принимала массив признаков (X) и необязательный целевой массив и возвращала их после предварительной обработки.

💻Демо

from sklearn.pipeline import make_pipeline from sklearn.preprocessing import FunctionTransformer def reduce_memory(X: pd.DataFrame, y=None): """Simple function to reduce memory usage by casting numeric columns to float32.""" num_cols = X.select_dtypes(incluce=np.number).columns for col in num_cols: X[col] = X.astype("float32") return X, y ReduceMemoryTransformer = FunctionTransformer(reduce_memory) # Plug into a pipeline >>> make_pipeline(SimpleImputer(), ReduceMemoryTransformer) Pipeline(steps=[('simpleimputer', SimpleImputer()), ('functiontransformer', ReduceMemoryTransformer()])

📚Документация: FunctionTransformer — ссылка

2. Пользовательские преобразователи

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

Например, одна из самых распространенных операций при очистке — масштабирование перекошенных объектов, чтобы они были нормально распределены. Обычно люди используют логарифмические преобразователи типа PowerTransformer или np.log, но у них есть подвох. Если функция содержит нули, базовая функция логарифмирования не сможет их обработать и выдаст ошибку.

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

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

💻 Демо

from sklearn.base import BaseEstimator, TransformerMixin from sklearn.preprocessing import PowerTransformer class CustomLogTransformer(BaseEstimator, TransformerMixin): def __init__(self): self._estimator = PowerTransformer() # init a transformer def fit(self, X, y=None): X_copy = np.copy(X) + 1 # add one in case of zeroes self._estimator.fit(X_copy) return self def transform(self, X): X_copy = np.copy(X) + 1 return self._estimator.transform(X_copy) # perform scaling def inverse_transform(self, X): X_reversed = self._estimator.inverse_transform(np.copy(X)) return X_reversed - 1 # return subtracting 1 after inverse transform

Класс должен наследовать от BaseEstimator и TransformerMixin классы, которые позволяют подключить его к конвейеру. Вы выигрываете от того, что пишете меньше кода, и снижается вероятность утечки данных.

📚Документация

BaseEstimator — ссылка .

TransformerMixin — ссылка .

3. TransformedTargetRegressor

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

Разве не было бы здорово, если бы у вас был конвейер, который заботился бы как о целевом массиве y, так и о массиве функций X? Оказывается, он существует! (Только для регрессии).

TransformedTargetRegressor — это класс, который принимает как конвейер регрессора для функций x, так и отдельную функцию предварительной обработки или преобразователь для целевого массива y.

💻 Демо

from sklearn.compose import TransformedTargetRegressor reg_lgbm = lgbm.LGBMRegressor() final_estimator = TransformedTargetRegressor( regressor=reg_lgbm, transformer=CustomLogTransformer() ) final_estimator.fit(X_train, y_train) TransformedTargetRegressor(regressor=LGBMRegressor(), transformer=CustomLogTransformer())

Параметр regressor принимает как регрессоры, так и конечные конвейеры. У него также есть параметр transformer, для которого вы передаете класс преобразователя, который будет применен к цели y. Если преобразователь — это функция, например np.log, вы можете передать ее в аргумент func.

Подробнее об этом в документации.

📚Документация: TransformedTargetregressor — ссылка .

4. HTML-представление

Если ваш конвейер состоит из нескольких шагов или вложенных конвейеров, их отображение IPython убьёт всю эстетику. Вот как это будет выглядеть, если вы случайно отобразите их в Jupyter:

>>> giant_pipeline Pipeline(steps=[('columntransformer', ColumnTransformer(transformers=[('cat_pipe', Pipeline(steps=[('impute', SimpleImputer(strategy='most_frequent')), ('oh', OneHotEncoder())]), <sklearn.compose._column_transformer.make_column_selector object at 0x000001B6D8BD9310>), ('num_pipe', Pipeline(steps=[('impute', SimpleImputer(strategy='median')), ('transform', QuantileTransformer())]), <sklearn.compose._column_transformer.make_column_selector object at 0x000001B6D8BD9160>)])), ('lgbmregressor', LGBMRegressor(device_type='gpu', learning_rate=0.01, n_estimators=10000))])

К счастью, Sklearn предлагает HTML-представление своих оценок, чтобы сделать их более удобными для пользователя и радовать нас:

💻Демо

from sklearn import set_config set_config(display="diagram") >>> giant_pipeline

Установив для параметра display config значение diagram, вы получите интерактивное HTML-представление вашего конвейера в IPython.

📚Документация

sklearn.set_config — ссылка .

sklearn.utils.estimator_html_repr — ссылка

5. Квадратичный дискриминантный анализ

В конкурсе мгновенного вознаграждения Kaggle классификатор квадратичного дискриминантного анализа достиг впечатляющего показателя ROC AUC 0,965 даже без настройки гиперпараметров, превзойдя большинство древовидных моделей, включая XGBoost и LightGBM.

Итак, почему вы никогда раньше не слышали об этом алгоритме, если он может превзойти современные модели? Ну, его вариант использования ограничен. Функции для обучения QDA должны быть строго нормально распределены, чтобы QDA легко вычисляла и подгоняла форму эллипсоида вокруг распределения.

Ещё одним преимуществом QDA является его молниеносная скорость — для его обучения на наборе данных из миллиона строк требуется всего несколько секунд:

💻Демо

В этой записной книжке Крис Деотт показывает, как он достигает показателя ROC AUC 0,965, комбинируя 512 моделей QDA.

В конкурсе TPS (сентябрь 2021 г.) мне также удалось набрать около 0,8 ROC AUC с QDA, в то время как у лучших решений было около 0,81. Разница в 2% — это компромисс, который вы можете рассмотреть между наиболее точными древовидными моделями, которые намного медленнее, и моделью, которая работает молниеносно за счет более низкой точности.

📚Документация: QuadraticDiscriminantAnalysis — ссылка .

6. Voting Classifier/Regressor

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

Причина, по которой этот метод работает так хорошо, заложена в теории вероятностей. Короче говоря, три классификатора с точностью 0,6, 0,7 и 0,8 в конечном итоге превзойдут три классификатора с точностью 0,8 при объединении. Если вы мне не верите, прочитайте эту статью от MLWave.

Sklearn предоставляет два удобных класса, которые реализуют эту операцию. Вам нужно передать список классификаторов или регрессоров, и он позаботится об их объединении.

💻Демо

from sklearn.ensemble import VotingClassifier X, y = make_classification(n_samples=1000) ensemble = VotingClassifier( estimators=[ ("xgb", xgb.XGBClassifier(eval_metric="auc")), ("lgbm", lgbm.LGBMClassifier()), ("cb", cb.CatBoostClassifier(verbose=False)), ], voting="soft", # n_jobs=-1, ) _ = ensemble.fit(X, y)

Я установил voting значение soft, указав, что хочу, чтобы прогнозы были вероятностными. Существует также аргумент weights, который вы можете использовать для назначения различных коэффициентов для более точных моделей. Ознакомьтесь с документами для более подробной информации.

📚Документация

VotingClassifier— ссылка .

VotingRegressor— ссылка .

7. Stacking Classifier/Regressor

Другой метод объединения, более мощный, чем voting, — это stacking.

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

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

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

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

Большинство выигрышных решений в табличных соревнованиях Kaggle — это несколько моделей, сложенных вместе.

💻Демо

from sklearn.ensemble import StackingClassifier, StackingRegressor from sklearn.linear_model import LogisticRegression X, y = make_classification(n_samples=1000) ensemble = StackingClassifier( estimators=[ ("xgb", xgb.XGBClassifier(eval_metric="auc")), ("lgbm", lgbm.LGBMClassifier()), ("cb", cb.CatBoostClassifier(verbose=False)), ], final_estimator=LogisticRegression(), cv=5, passthrough=False # n_jobs=-1, ) _ = ensemble.fit(X, y)

Несмотря на то, что это звучит сложно, вы быстро освоитесь со Sklearn. Реализация такая же интуитивно понятная, как и Voting Classifier/Regressor.

📚Документация

StackingClassifier — ссылка .

StackingRegressor — ссылка .

8. LocalOutlierFactor

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

Поиск выбросов не является проблемой для небольших наборов данных. Настоящая проблема начинается с наборов данных, содержащих более 50–100 признаков. Вам нужен алгоритм, который будет быстрым и точным при обнаружении выбросов высокой размерности. Тем не менее, для наборов данных с сотнями функций и миллионами строк выполнение необработанных алгоритмов может занять несколько часов.

Вот где вам нужно объединить алгоритм уменьшения размерности с надежным детектором выбросов. Комбинация, которая мне недавно понравилась, — это UMAP и LocalOutlierFactor .

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

💻Демо

%%time import umap # pip install umap from sklearn.neighbors import LocalOutlierFactor X, y = make_classification(n_samples=5000, n_classes=2, n_features=10) X_reduced = umap.UMAP(n_components=2).fit_transform(X, y) lof = LocalOutlierFactor() labels = lof.fit_predict(X_reduced, y) Wall time: 17.8 s >>> np.where(labels == -1) (array([ 119, 155, 303, 331, 333, 407, 418, 549, 599, 664, 795, 3092, 3262, 3271, 3280, 3289, 3311, 3477, 3899, 3929, 3975, 4301, 4358, 4442, 4522, 4561, 4621, 4631, 4989], dtype=int64),)

📚Документация

LocalOutlierFactor — ссылка .

9. QuantileTransformer

Иногда вы будете сталкиваться с сумасшедшими распределениями с дикими формами, которые преобразователи логов или масштабаторы не могут преобразовать в нормальное распределение.

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

💻Демо

import pandas as pd from sklearn.preprocessing import QuantileTransformer qt = QuantileTransformer().fit(crazy_distributions) crazy_feature_names = ["f18", "f31", "f61"] crazy_distributions = pd.DataFrame(qt.transform(crazy_distributions), columns=crazy_feature_names) fig, axes = plt.subplots(1, 3, figsize=(20, 6)) for ax, f_name in zip(axes.flatten(), crazy_feature_names): sns.kdeplot(crazy_distributions[f_name], ax=ax, color="#E50914")

📚Документация

QuantileTransformer — ссылка .

10. PCA + tSNE/UMAP

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

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

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

Вот почему документация Sklearn предлагает сочетать алгоритмы уменьшения размерности с PCA (анализ основных компонентов).

PCA работает быстро для любого количества измерений, что делает его идеальным для редукции на первом этапе. Рекомендуется проецировать данные на разумное количество измерений, например, 30–50 с PCA, а затем использовать другие алгоритмы для еще большего уменьшения, например tSNE или UMAP.

Ниже представлена комбинация PCA и tSNE:

💻Демо

from sklearn.decomposition import PCA from sklearn.manifold import TSNE df = dt.fread("data/large.csv").to_pandas() >>> df.shape (1000000, 287) X, y = df.drop("target", axis=1), df[["target"]].values.flatten() %%time manifold_pipe = make_pipeline(QuantileTransformer(), PCA(n_components=30), TSNE()) reduced_X = manifold_pipe.fit_transform(X, y) ------------------------------------------ Wall time: 4h 27min 46s

В синтетическом наборе данных с 1 млн строк и примерно 300 объектов проецирование данных на первые 30 измерений, а затем на два измерения заняло 4,5 часа. К сожалению, результаты не очень порадовали:

>>> plt.scatter(reduced_X[:, 0], reduced_X[:, 1], c=y, s=0.05);

Вот почему я рекомендую использовать UMAP. Это намного быстрее, чем tSNE, и лучше сохраняет локальную структуру данных:

%%time manifold_pipe = make_pipeline(QuantileTransformer(), PCA(n_components=30)) X_pca = manifold_pipe.fit_transform(X, y) embedding = umap.UMAP(n_components=2).fit(X_pca, y) Wall time: 14min 27s >>> plt.scatter(embedding.embedding_[:, 0], embedding.embedding_[:, 1], c=y, s=0.05);

UMAP удалось найти четкое различие между целевыми классами, и он сделал это в 20 раз быстрее, чем tSNE.

📚Документация

ППШ — ссылка .

tSNE — ссылка .

УМАП — ссылка .

Заключение

В эпоху ИИ легко ускользнуть от причудливых моделей, таких как языковые преобразователи. Но они предназначены только для обработки естественного языка — классическое машинное обучение все еще находится в руках таких гигантов, как Scikit-learn.

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

Спасибо за чтение!

0
Комментарии
-3 комментариев
Раскрывать всегда