6.3. Предварительная обработка данных
Пакет sklearn.preprocessing предоставляет несколько общих служебных функций и классов-трансформеров для преобразования исходных векторов признаков в представление, более подходящее для последующих алгоритмов.
Во многих алгоритмах обучения, таких как линейные модели, выгодно использовать стандартизацию набора данных (см. Значимость масштабирования признаков). Если в наборе присутствуют выбросы, более подходящими могут быть робастные масштабировщики или другие трансформеры. Поведение различных масштабировщиков, трансформеров и нормализаторов на наборе данных с маргинальными выбросами показано в Сравните влияние различных масштабировщиков на данные с выбросами.
6.3.1. Стандартизация, или удаление среднего и масштабирование дисперсии
Стандартизация наборов данных является общим требованием для многих алгоритмов машинного обучения, реализованных в scikit-learn; они могут плохо работать, если отдельные признаки не напоминают стандартные данные с нормальным распределением: Гауссовское распределение с нулевым средним значением и единичной дисперсией.
На практике мы часто игнорируем форму распределения и просто преобразуем данные, чтобы центрировать их, удаляя среднее значение каждого признака, а затем масштабируем его, деля неконстантные признаки на их стандартное отклонение.
Например, многие элементы, используемые в целевой функции алгоритма обучения (например, ядро RBF опорных векторных машин или регуляризаторы l1 и l2 линейных моделей) предполагают, что все признаки центрированы около нуля или имеют дисперсию одного порядка. Если признак имеет дисперсию на порядки больше, чем другие, он может доминировать в целевой функции и сделать алгоритм обучения неспособным правильно изучить другие признаки, как ожидается.
Модуль preprocessing предоставляет утилитарный класс StandardScaler, который является быстрым и удобным способом выполнения следующей операции над набором данных в формате массива:
>>> from sklearn import preprocessing
>>> import numpy as np
>>> X_train = np.array([[ 1., -1., 2.],
... [ 2., 0., 0.],
... [ 0., 1., -1.]])
>>> scaler = preprocessing.StandardScaler().fit(X_train)
>>> scaler
StandardScaler()
>>> scaler.mean_
array([1. ..., 0. ..., 0.33...])
>>> scaler.scale_
array([0.81..., 0.81..., 1.24...])
>>> X_scaled = scaler.transform(X_train)
>>> X_scaled
array([[ 0. ..., -1.22..., 1.33...],
[ 1.22..., 0. ..., -0.26...],
[-1.22..., 1.22..., -1.06...]])
Масштабированные данные имеют нулевое среднее значение и единичную дисперсию:
>>> X_scaled.mean(axis=0) array([0., 0., 0.]) >>> X_scaled.std(axis=0) array([1., 1., 1.])
Этот класс реализует Transformer API для вычисления среднего значения и стандартного отклонения на обучающем наборе, чтобы иметь возможность позже повторно применить то же преобразование к тестовому набору. Таким образом, этот класс подходит для использования на ранних этапах Pipeline:
>>> from sklearn.datasets import make_classification
>>> from sklearn.linear_model import LogisticRegression
>>> from sklearn.model_selection import train_test_split
>>> from sklearn.pipeline import make_pipeline
>>> from sklearn.preprocessing import StandardScaler
>>> X, y = make_classification(random_state=42)
>>> X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
>>> pipe = make_pipeline(StandardScaler(), LogisticRegression())
>>> pipe.fit(X_train, y_train) # apply scaling on training data
Pipeline(steps=[('standardscaler', StandardScaler()),
('logisticregression', LogisticRegression())])
>>> pipe.score(X_test, y_test) # apply scaling on testing data, without leaking training data.
0.96
Можно отключить центрирование или масштабирование, передав with_mean=False или with_std=False в конструктор StandardScaler.
6.3.1.1. Масштабирование признаков в диапазон
Альтернативной стандартизацией является масштабирование признаков до заданного минимального и максимального значения, часто до нуля и единицы или таким образом, чтобы максимальное абсолютное значение каждого признака масштабировалось до единицы. Это можно достичь, используя MinMaxScaler или MaxAbsScaler соответственно.
Мотивацией для использования такого масштабирования является устойчивость к очень малым стандартным отклонениям признаков и сохранение нулевых элементов в разреженных данных.
Вот пример масштабирования матрицы данных для диапазона [0, 1]:
>>> X_train = np.array([[ 1., -1., 2.],
... [ 2., 0., 0.],
... [ 0., 1., -1.]])
...
>>> min_max_scaler = preprocessing.MinMaxScaler()
>>> X_train_minmax = min_max_scaler.fit_transform(X_train)
>>> X_train_minmax
array([[0.5 , 0. , 1. ],
[1. , 0.5 , 0.33333333],
[0. , 1. , 0. ]])
Затем тот же экземпляр трансформера может быть применён к новым тестовым данным, невиденным во время вызова fit: те же операции масштабирования и сдвига будут применены, чтобы быть согласованными с преобразованием, выполненным на данных обучения:
>>> X_test = np.array([[-3., -1., 4.]]) >>> X_test_minmax = min_max_scaler.transform(X_test) >>> X_test_minmax array([[-1.5 , 0. , 1.66666667]])
Можно инспектировать атрибуты масштабировщика, чтобы узнать точный характер преобразования, выученного на обучающих данных:
>>> min_max_scaler.scale_ array([0.5 , 0.5 , 0.33...]) >>> min_max_scaler.min_ array([0. , 0.5 , 0.33...])
Если MinMaxScaler получает явное feature_range=(min, max), полная формула будет:
X_std = (X - X.min(axis=0)) / (X.max(axis=0) - X.min(axis=0)) X_scaled = X_std * (max - min) + min
MaxAbsScaler работает очень похожим образом, но масштабирует данные так, что обучающие данные находятся в диапазоне [-1, 1], деля на наибольшее максимальное значение в каждом признаке. Он предназначен для данных, которые уже центрированы в нуле или разреженных данных.
Вот как использовать данные из предыдущего примера с этим масштабировщиком:
>>> X_train = np.array([[ 1., -1., 2.],
... [ 2., 0., 0.],
... [ 0., 1., -1.]])
...
>>> max_abs_scaler = preprocessing.MaxAbsScaler()
>>> X_train_maxabs = max_abs_scaler.fit_transform(X_train)
>>> X_train_maxabs
array([[ 0.5, -1. , 1. ],
[ 1. , 0. , 0. ],
[ 0. , 1. , -0.5]])
>>> X_test = np.array([[ -3., -1., 4.]])
>>> X_test_maxabs = max_abs_scaler.transform(X_test)
>>> X_test_maxabs
array([[-1.5, -1. , 2. ]])
>>> max_abs_scaler.scale_
array([2., 1., 2.])
6.3.1.2. Масштабирование разреженных данных
Центрирование разреженных данных разрушит структуру разреженности в данных, поэтому это редко является разумным действием. Однако может иметь смысл масштабировать разреженные входные данные, особенно если признаки находятся в разных масштабах.
MaxAbsScaler был специально разработан для масштабирования разреженных данных и является рекомендуемым способом сделать это. Однако, StandardScaler может принимать scipy.sparse матрицы в качестве входных данных, если with_mean=False явно передаётся в конструктор. В противном случае будет поднято ValueError, так как безмолвное центрирование нарушит разреженность и часто приведёт к аварийному завершению выполнения из-за нежелательного выделения избыточного объёма памяти. RobustScaler не может быть обучен на разреженных входных данных, но вы можете использовать метод transform для разреженных входных данных.
Обратите внимание, что масштабировщики принимают как формат Compressed Sparse Rows, так и Compressed Sparse Columns (см. scipy.sparse.csr_matrix и scipy.sparse.csc_matrix). Любой другой разреженный вход будет преобразован в представление Compressed Sparse Rows. Для избежания ненужных копий памяти рекомендуется использовать представление CSR или CSC на этапе обработки данных.
Наконец, если ожидается, что центрированные данные будут достаточно малыми, явное преобразование входных данных в массив с помощью метода toarray разреженных матриц является ещё одним вариантом.
6.3.1.3. Масштабирование данных с выбросами
Если ваши данные содержат много выбросов, масштабирование с использованием среднего и дисперсии данных, вероятно, не будет работать хорошо. В таких случаях можно использовать RobustScaler в качестве прямого заменителя. Он использует более устойчивые оценки центра и диапазона ваших данных.
Ссылки
Более подробное обсуждение важности центрирования и масштабирования данных доступно в этом FAQ: Нужно ли мне нормализовать/стандартизировать/масштабировать данные?
Масштабирование против осветления
Иногда недостаточно центрировать и масштабировать признаки независимо, так как последующая модель может дополнительно сделать предположение о линейной независимости признаков.
Для решения этой проблемы можно использовать PCA с whiten=True для дальнейшего удаления линейной корреляции между признаками.
6.3.1.4. Центрирование матриц ядер
Если у вас есть матрица ядер ядра \(K\), которое вычисляет скалярное произведение в пространстве признаков (возможно, неявно) определённом функцией \(\phi(\cdot)\), KernelCenterer может преобразовать матрицу ядер так, чтобы она содержала скалярные произведения в пространстве признаков, определённом \(\phi\), после удаления среднего значения в этом пространстве. Другими словами, KernelCenterer вычисляет центрированную матрицу Грама, связанную с положительно полуопределённым ядром \(K\).
Математическая формулировка
Теперь, когда мы имеем интуицию, давайте посмотрим на математическую формулировку. Пусть \(K\) — матрица ядер формы (n_samples, n_samples), вычисленная из \(X\), матрицы данных формы (n_samples, n_features), во время этапа fit. \(K\) определяется как
\(\phi(X)\) — это функция отображения \(X\) в гильбертово пространство. Центрированное ядро \(\tilde{K}\) определяется как:
где \(\tilde{\phi}(X)\) получается из центрирования \(\phi(X)\) в гильбертовом пространстве.
Таким образом, можно вычислить \(\tilde{K}\), отобразив \(X\) с помощью функции \(\phi(\cdot)\) и центрировав данные в этом новом пространстве. Однако ядра часто используются, потому что они позволяют проводить некоторые алгебраические вычисления, которые избегают явного вычисления этого отображения с помощью \(\phi(\cdot)\). Действительно, можно неявно центрировать, как показано в Приложении B в [Scholkopf1998]:
\(1_{\text{n}_{samples}}\) — это матрица (n_samples, n_samples), где все элементы равны \(\frac{1}{\text{n}_{samples}}\). На этапе transform, ядро становится \(K_{test}(X, Y)\), определённым как:
\(Y\) — тестовый набор данных формы (n_samples_test, n_features), и, следовательно, \(K_{test}\) имеет форму (n_samples_test, n_samples). В этом случае центрирование \(K_{test}\) выполняется следующим образом:
\(1'_{\text{n}_{samples}}\) — это матрица формы (n_samples_test, n_samples), где все элементы равны \(\frac{1}{\text{n}_{samples}}\).
Литература
B. Schölkopf, A. Smola, and K.R. Müller, “Нелинейный компонентный анализ как задача собственных значений ядра.” Neural computation 10.5 (1998): 1299-1319.
6.3.2. Нелинейное преобразование
Доступны два типа преобразований: преобразования квантилей и степенные преобразования. Преобразования квантилей и степенные преобразования основаны на монотонных преобразованиях признаков и сохраняют порядок значений по каждому признаку.
Преобразования квантилей помещают все признаки в одно и то же желаемое распределение на основе формулы \(G^{-1}(F(X))\), где \(F\) — функция распределения кумулятивного признака, а \(G^{-1}\) — функция квантиля желаемого выходного распределения \(G\). Эта формула использует два следующих факта: (i) если \(X\) — случайная величина с непрерывной функцией кумулятивного распределения \(F\), то \(F(X)\) равномерно распределена на \([0,1]\); (ii) если \(U\) — случайная величина с равномерным распределением на \([0,1]\), то \(G^{-1}(U)\) имеет распределение \(G\). Выполняя преобразование рангов, преобразование квантилей сглаживает необычные распределения и меньше подвержено влиянию выбросов, чем методы масштабирования. Однако оно искажает корреляции и расстояния внутри и между признаками.
Степенные преобразования представляют собой семейство параметрических преобразований, которые стремятся отобразить данные из любого распределения как можно ближе к нормальному распределению.
6.3.2.1. Отображение на равномерное распределение
QuantileTransformer предоставляет непараметрическое преобразование для отображения данных на равномерное распределение со значениями от 0 до 1:
>>> from sklearn.datasets import load_iris >>> from sklearn.model_selection import train_test_split >>> X, y = load_iris(return_X_y=True) >>> X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0) >>> quantile_transformer = preprocessing.QuantileTransformer(random_state=0) >>> X_train_trans = quantile_transformer.fit_transform(X_train) >>> X_test_trans = quantile_transformer.transform(X_test) >>> np.percentile(X_train[:, 0], [0, 25, 50, 75, 100]) array([ 4.3, 5.1, 5.8, 6.5, 7.9])
Этот признак соответствует длине чашелистика в см. После применения преобразования квантилей эти вехи приближаются к предварительно определённым процентилям:
>>> np.percentile(X_train_trans[:, 0], [0, 25, 50, 75, 100]) ... array([ 0.00... , 0.24..., 0.49..., 0.73..., 0.99... ])
Это можно подтвердить на независимом тестовом наборе с аналогичными замечаниями:
>>> np.percentile(X_test[:, 0], [0, 25, 50, 75, 100]) ... array([ 4.4 , 5.125, 5.75 , 6.175, 7.3 ]) >>> np.percentile(X_test_trans[:, 0], [0, 25, 50, 75, 100]) ... array([ 0.01..., 0.25..., 0.46..., 0.60... , 0.94...])
6.3.2.2. Отображение на нормальное распределение
Во многих моделях желательно, чтобы признаки в наборе данных имели нормальное распределение. Степенные преобразования представляют собой семейство параметрических, монотонных преобразований, которые стремятся отобразить данные из любого распределения как можно ближе к нормальному распределению, чтобы стабилизировать дисперсию и минимизировать асимметрию.
PowerTransformer в настоящее время предоставляет два таких степенных преобразования: преобразование Йео-Джонсона и преобразование Бокса-Кокса.
Преобразование Йео-Джонсона
Преобразование Бокса-Кокса
Преобразование Бокса-Кокса может быть применено только к строго положительным данным. В обоих методах преобразование параметризуется \(\lambda\), которое определяется с помощью оценки максимального правдоподобия. Вот пример использования Бокса-Кокса для отображения выборок, взятых из логнормального распределения, на нормальное распределение:
>>> pt = preprocessing.PowerTransformer(method='box-cox', standardize=False)
>>> X_lognormal = np.random.RandomState(616).lognormal(size=(3, 3))
>>> X_lognormal
array([[1.28..., 1.18..., 0.84...],
[0.94..., 1.60..., 0.38...],
[1.35..., 0.21..., 1.09...]])
>>> pt.fit_transform(X_lognormal)
array([[ 0.49..., 0.17..., -0.15...],
[-0.05..., 0.58..., -0.57...],
[ 0.69..., -0.84..., 0.10...]])
Хотя в приведенном выше примере параметр standardize установлен в значение False, PowerTransformer по умолчанию применит нулевую среднюю и единичную дисперсию к преобразованному выводу.
Ниже приведены примеры преобразований Бокса-Кокса и Йео-Джонсона, применённых к различным распределениям вероятностей. Обратите внимание, что при применении к определённым распределениям степенные преобразования дают очень близкие к нормальным результаты, но в других случаях они неэффективны. Это подчеркивает важность визуализации данных до и после преобразования.
Также можно отобразить данные на нормальное распределение с помощью QuantileTransformer, установив output_distribution='normal'. Используя предыдущий пример с набором данных ирисов:
>>> quantile_transformer = preprocessing.QuantileTransformer(
... output_distribution='normal', random_state=0)
>>> X_trans = quantile_transformer.fit_transform(X)
>>> quantile_transformer.quantiles_
array([[4.3, 2. , 1. , 0.1],
[4.4, 2.2, 1.1, 0.1],
[4.4, 2.2, 1.2, 0.1],
...,
[7.7, 4.1, 6.7, 2.5],
[7.7, 4.2, 6.7, 2.5],
[7.9, 4.4, 6.9, 2.5]])
Таким образом, медиана входных данных становится средним значением выходных данных, центрированным в 0. Выходной нормальный набор данных ограничен таким образом, чтобы минимальное и максимальное значение входных данных — соответствующие процентилям 1e-7 и 1 - 1e-7 соответственно — не становились бесконечными при преобразовании.
6.3.3. Нормализация
Нормализация — это процесс масштабирования отдельных выборок, чтобы они имели единичную норму. Этот процесс может быть полезен, если вы планируете использовать квадратичную форму, такую как скалярное произведение или любой другой ядро, для количественной оценки сходства любой пары выборок.
Это предположение является основой модели векторного пространства, часто используемой в контексте классификации и кластеризации текста.
Функция normalize предоставляет быстрый и простой способ выполнения этой операции на одном массивоподобном наборе данных, используя нормы l1, l2, или max:
>>> X = [[ 1., -1., 2.],
... [ 2., 0., 0.],
... [ 0., 1., -1.]]
>>> X_normalized = preprocessing.normalize(X, norm='l2')
>>> X_normalized
array([[ 0.40..., -0.40..., 0.81...],
[ 1. ..., 0. ..., 0. ...],
[ 0. ..., 0.70..., -0.70...]])
Модуль preprocessing дополнительно предоставляет утилитарный класс Normalizer, который реализует ту же операцию с использованием API Transformer, (хотя метод fit бесполезен в этом случае: класс не имеет состояния, так как эта операция обрабатывает выборки независимо).
Поэтому этот класс подходит для использования на ранних этапах Pipeline:
>>> normalizer = preprocessing.Normalizer().fit(X) # fit does nothing >>> normalizer Normalizer()
Затем экземпляр нормализатора можно использовать для векторов выборок как любой препроцессор:
>>> normalizer.transform(X)
array([[ 0.40..., -0.40..., 0.81...],
[ 1. ..., 0. ..., 0. ...],
[ 0. ..., 0.70..., -0.70...]])
>>> normalizer.transform([[-1., 1., 0.]])
array([[-0.70..., 0.70..., 0. ...]])
Примечание: нормализация L2 также известна как пространственная предобработка знака.
Разреженные входные данные
normalize и Normalizer принимают на вход как плотные массивоподобные данные, так и разреженные матрицы из scipy.sparse.
Для разреженных входных данных данные преобразуются в представление Compressed Sparse Rows (см. scipy.sparse.csr_matrix) перед подачей в эффективные процедуры Cython. Для избежания ненужных копий памяти рекомендуется выбрать представление CSR предварительно.
6.3.4. Кодирование категориальных признаков
Часто признаки задаются не как непрерывные значения, а как категориальные. Например, человек может иметь признаки ["male", "female"], ["from Europe", "from US", "from Asia"], ["uses Firefox", "uses Chrome", "uses Safari", "uses Internet Explorer"]. Такие признаки могут быть эффективно закодированы как целые числа, например, ["male", "from US", "uses Internet Explorer"] может быть выражено как [0, 1, 3] , а ["female", "from Asia", "uses Chrome"] будет [1, 2, 1].
Для преобразования категориальных признаков в такие целочисленные коды мы можем использовать OrdinalEncoder. Этот эстиматор преобразует каждый категориальный признак в один новый признак целых чисел (от 0 до n_categories - 1):
>>> enc = preprocessing.OrdinalEncoder() >>> X = [['male', 'from US', 'uses Safari'], ['female', 'from Europe', 'uses Firefox']] >>> enc.fit(X) OrdinalEncoder() >>> enc.transform([['female', 'from US', 'uses Safari']]) array([[0., 1., 1.]])
Однако такое целочисленное представление не может быть использовано напрямую со всеми эстиматорами scikit-learn, так как они ожидают непрерывный ввод и будут интерпретировать категории как упорядоченные, что часто нежелательно (например, набор браузеров был упорядочен произвольно).
По умолчанию OrdinalEncoder также будет пропускать пропущенные значения, которые обозначаются np.nan.
>>> enc = preprocessing.OrdinalEncoder()
>>> X = [['male'], ['female'], [np.nan], ['female']]
>>> enc.fit_transform(X)
array([[ 1.],
[ 0.],
[nan],
[ 0.]])
OrdinalEncoder предоставляет параметр encoded_missing_value для кодирования пропущенных значений без необходимости создания конвейера и использования SimpleImputer.
>>> enc = preprocessing.OrdinalEncoder(encoded_missing_value=-1)
>>> X = [['male'], ['female'], [np.nan], ['female']]
>>> enc.fit_transform(X)
array([[ 1.],
[ 0.],
[-1.],
[ 0.]])
Вышеуказанная обработка эквивалентна следующему конвейеру:
>>> from sklearn.pipeline import Pipeline
>>> from sklearn.impute import SimpleImputer
>>> enc = Pipeline(steps=[
... ("encoder", preprocessing.OrdinalEncoder()),
... ("imputer", SimpleImputer(strategy="constant", fill_value=-1)),
... ])
>>> enc.fit_transform(X)
array([[ 1.],
[ 0.],
[-1.],
[ 0.]])
Другой способ преобразования категориальных признаков в признаки, которые могут быть использованы с эстиматорами scikit-learn, — это использование кодирования one-of-K, также известного как one-hot или dummy кодирование. Этот тип кодирования можно получить с помощью OneHotEncoder, который преобразует каждый категориальный признак с n_categories возможными значениями в n_categories бинарных признаков, один из которых 1, а все остальные 0.
Продолжая пример выше:
>>> enc = preprocessing.OneHotEncoder()
>>> X = [['male', 'from US', 'uses Safari'], ['female', 'from Europe', 'uses Firefox']]
>>> enc.fit(X)
OneHotEncoder()
>>> enc.transform([['female', 'from US', 'uses Safari'],
... ['male', 'from Europe', 'uses Safari']]).toarray()
array([[1., 0., 0., 1., 0., 1.],
[0., 1., 1., 0., 0., 1.]])
По умолчанию значения, которые может принимать каждый признак, автоматически выводятся из набора данных и могут быть найдены в атрибуте categories_:
>>> enc.categories_ [array(['female', 'male'], dtype=object), array(['from Europe', 'from US'], dtype=object), array(['uses Firefox', 'uses Safari'], dtype=object)]
Можно явно указать это, используя параметр categories. В нашем наборе данных есть два пола, четыре возможных континента и четыре веб-браузера:
>>> genders = ['female', 'male']
>>> locations = ['from Africa', 'from Asia', 'from Europe', 'from US']
>>> browsers = ['uses Chrome', 'uses Firefox', 'uses IE', 'uses Safari']
>>> enc = preprocessing.OneHotEncoder(categories=[genders, locations, browsers])
>>> # Note that for there are missing categorical values for the 2nd and 3rd
>>> # feature
>>> X = [['male', 'from US', 'uses Safari'], ['female', 'from Europe', 'uses Firefox']]
>>> enc.fit(X)
OneHotEncoder(categories=[['female', 'male'],
['from Africa', 'from Asia', 'from Europe',
'from US'],
['uses Chrome', 'uses Firefox', 'uses IE',
'uses Safari']])
>>> enc.transform([['female', 'from Asia', 'uses Chrome']]).toarray()
array([[1., 0., 0., 1., 0., 0., 1., 0., 0., 0.]])
Если есть вероятность, что обучающие данные могут содержать пропущенные категориальные признаки, часто лучше указать handle_unknown='infrequent_if_exist' вместо ручного задания categories как выше. Когда handle_unknown='infrequent_if_exist' указан, и при встрече неизвестных категорий во время преобразования, ошибка не будет поднята, а полученные закодированные столбцы one-hot для этого признака будут все нулями или рассматриваться как редкая категория, если это включено. (handle_unknown='infrequent_if_exist' поддерживается только для кодирования one-hot):
>>> enc = preprocessing.OneHotEncoder(handle_unknown='infrequent_if_exist') >>> X = [['male', 'from US', 'uses Safari'], ['female', 'from Europe', 'uses Firefox']] >>> enc.fit(X) OneHotEncoder(handle_unknown='infrequent_if_exist') >>> enc.transform([['female', 'from Asia', 'uses Chrome']]).toarray() array([[1., 0., 0., 0., 0., 0.]])
Также можно закодировать каждый столбец в n_categories - 1 столбца вместо n_categories столбцов, используя параметр drop. Этот параметр позволяет пользователю указать категорию для каждого признака, который нужно исключить. Это полезно для предотвращения коллинеарности в матрице входных данных в некоторых классификаторах. Такая функциональность полезна, например, при использовании нерегулярной регрессии (LinearRegression), так как коллинеарность приведет к тому, что матрица ковариации станет необратимой:
>>> X = [['male', 'from US', 'uses Safari'],
... ['female', 'from Europe', 'uses Firefox']]
>>> drop_enc = preprocessing.OneHotEncoder(drop='first').fit(X)
>>> drop_enc.categories_
[array(['female', 'male'], dtype=object), array(['from Europe', 'from US'], dtype=object),
array(['uses Firefox', 'uses Safari'], dtype=object)]
>>> drop_enc.transform(X).toarray()
array([[1., 1., 1.],
[0., 0., 0.]])
Можно исключить один из двух столбцов только для признаков с 2 категориями. В этом случае вы можете установить параметр drop='if_binary'.
>>> X = [['male', 'US', 'Safari'],
... ['female', 'Europe', 'Firefox'],
... ['female', 'Asia', 'Chrome']]
>>> drop_enc = preprocessing.OneHotEncoder(drop='if_binary').fit(X)
>>> drop_enc.categories_
[array(['female', 'male'], dtype=object), array(['Asia', 'Europe', 'US'], dtype=object),
array(['Chrome', 'Firefox', 'Safari'], dtype=object)]
>>> drop_enc.transform(X).toarray()
array([[1., 0., 0., 1., 0., 0., 1.],
[0., 0., 1., 0., 0., 1., 0.],
[0., 1., 0., 0., 1., 0., 0.]])
В преобразованном X, первый столбец — кодирование признака с категориями “мужской”/”женский”, а остальные 6 столбцов — кодирование 2 признаков с соответственно 3 категориями каждый.
Когда handle_unknown='ignore' и drop не равны None, неизвестные категории будут закодированы как нули:
>>> drop_enc = preprocessing.OneHotEncoder(drop='first', ... handle_unknown='ignore').fit(X) >>> X_test = [['unknown', 'America', 'IE']] >>> drop_enc.transform(X_test).toarray() array([[0., 0., 0., 0., 0.]])
Все категории в X_test неизвестны во время преобразования и будут отображаться как нули. Это означает, что неизвестные категории будут иметь такое же отображение, как и исключенная категория. OneHotEncoder.inverse_transform будет отображать нули на исключенную категорию, если категория исключена, и None если категория не исключена:
>>> drop_enc = preprocessing.OneHotEncoder(drop='if_binary', sparse_output=False, ... handle_unknown='ignore').fit(X) >>> X_test = [['unknown', 'America', 'IE']] >>> X_trans = drop_enc.transform(X_test) >>> X_trans array([[0., 0., 0., 0., 0., 0., 0.]]) >>> drop_enc.inverse_transform(X_trans) array([['female', None, None]], dtype=object)
Поддержка категориальных признаков с пропущенными значениями
OneHotEncoder поддерживает категориальные признаки с пропущенными значениями, рассматривая пропущенные значения как дополнительную категорию:
>>> X = [['male', 'Safari'],
... ['female', None],
... [np.nan, 'Firefox']]
>>> enc = preprocessing.OneHotEncoder(handle_unknown='error').fit(X)
>>> enc.categories_
[array(['female', 'male', nan], dtype=object),
array(['Firefox', 'Safari', None], dtype=object)]
>>> enc.transform(X).toarray()
array([[0., 1., 0., 0., 1., 0.],
[1., 0., 0., 0., 0., 1.],
[0., 0., 1., 1., 0., 0.]])
Если признак содержит как np.nan , так и None, они будут рассматриваться как отдельные категории:
>>> X = [['Safari'], [None], [np.nan], ['Firefox']]
>>> enc = preprocessing.OneHotEncoder(handle_unknown='error').fit(X)
>>> enc.categories_
[array(['Firefox', 'Safari', None, nan], dtype=object)]
>>> enc.transform(X).toarray()
array([[0., 1., 0., 0.],
[0., 0., 1., 0.],
[0., 0., 0., 1.],
[1., 0., 0., 0.]])
См. Загрузка признаков из словарей для категориальных признаков, которые представлены как словарь, а не как скаляры.
6.3.4.1. Часто встречающиеся категории
OneHotEncoder и OrdinalEncoder поддерживают агрегирование редких категорий в один выход для каждого признака. Параметры для включения сбора редких категорий — min_frequency и max_categories.
-
min_frequency— это целое число, больше или равно 1, или число с плавающей запятой в интервале(0.0, 1.0). Еслиmin_frequency— целое число, категории с кратностью меньшеmin_frequencyбудут считаться редкими. Еслиmin_frequency— число с плавающей запятой, категории с кратностью меньше этой доли от общего количества образцов будут считаться редкими. Значение по умолчанию — 1, что означает, что каждая категория кодируется отдельно. -
max_categories— этоNoneили любое целое число больше 1. Этот параметр устанавливает верхний предел количества выходных признаков для каждого входного признака.max_categoriesвключает признак, объединяющий редкие категории.
В следующем примере с OrdinalEncoder категории 'dog' and
'snake' считаются редкими:
>>> X = np.array([['dog'] * 5 + ['cat'] * 20 + ['rabbit'] * 10 +
... ['snake'] * 3], dtype=object).T
>>> enc = preprocessing.OrdinalEncoder(min_frequency=6).fit(X)
>>> enc.infrequent_categories_
[array(['dog', 'snake'], dtype=object)]
>>> enc.transform(np.array([['dog'], ['cat'], ['rabbit'], ['snake']]))
array([[2.],
[0.],
[1.],
[2.]])
OrdinalEncoder’s max_categories не учитывают пропущенные или неизвестные категории. Установка unknown_value или encoded_missing_value в целое число увеличит количество уникальных целочисленных кодов на единицу. Это может привести к максимальному количеству целочисленных кодов max_categories + 2. В следующем примере «a» и «d» считаются редкими и объединяются в одну категорию «b» и «c» — в свои собственные категории, неизвестные значения кодируются как 3, а пропущенные значения — как 4.
>>> X_train = np.array(
... [["a"] * 5 + ["b"] * 20 + ["c"] * 10 + ["d"] * 3 + [np.nan]],
... dtype=object).T
>>> enc = preprocessing.OrdinalEncoder(
... handle_unknown="use_encoded_value", unknown_value=3,
... max_categories=3, encoded_missing_value=4)
>>> _ = enc.fit(X_train)
>>> X_test = np.array([["a"], ["b"], ["c"], ["d"], ["e"], [np.nan]], dtype=object)
>>> enc.transform(X_test)
array([[2.],
[0.],
[1.],
[2.],
[3.],
[4.]])
Аналогично, OneHotEncoder можно настроить для объединения редких категорий:
>>> enc = preprocessing.OneHotEncoder(min_frequency=6, sparse_output=False).fit(X)
>>> enc.infrequent_categories_
[array(['dog', 'snake'], dtype=object)]
>>> enc.transform(np.array([['dog'], ['cat'], ['rabbit'], ['snake']]))
array([[0., 0., 1.],
[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])
Установив handle_unknown на 'infrequent_if_exist', неизвестные категории будут считаться редкими:
>>> enc = preprocessing.OneHotEncoder( ... handle_unknown='infrequent_if_exist', sparse_output=False, min_frequency=6) >>> enc = enc.fit(X) >>> enc.transform(np.array([['dragon']])) array([[0., 0., 1.]])
OneHotEncoder.get_feature_names_out использует ‘infrequent’ в качестве имени признака редких категорий:
>>> enc.get_feature_names_out() array(['x0_cat', 'x0_rabbit', 'x0_infrequent_sklearn'], dtype=object)
Когда 'handle_unknown' установлено на 'infrequent_if_exist', при встрече неизвестной категории в transform:
- Если поддержка редких категорий не настроена или редких категорий не было во время обучения, результирующие закодированные в один горячий столбец для данного признака будут все нули. При обратном преобразовании неизвестная категория будет обозначена как
None. - Если во время обучения есть редкие категории, неизвестная категория будет считаться редкой. При обратном преобразовании для обозначения редкой категории будет использоваться «infrequent_sklearn».
Редкие категории также можно настроить, используя max_categories. В следующем примере мы устанавливаем max_categories=2 для ограничения количества признаков в выходе. Это приведет к тому, что все, кроме категории 'cat', будут считаться редкими, что приведет к двум признакам: один для 'cat' и один для редких категорий — все остальные.
>>> enc = preprocessing.OneHotEncoder(max_categories=2, sparse_output=False)
>>> enc = enc.fit(X)
>>> enc.transform([['dog'], ['cat'], ['rabbit'], ['snake']])
array([[0., 1.],
[1., 0.],
[0., 1.],
[0., 1.]])
Если и max_categories, и min_frequency имеют значения, отличные от значений по умолчанию, то категории выбираются на основе min_frequency и max_categories категорий сохраняются. В следующем примере min_frequency=4 учитывает только snake как редкую, но max_categories=3, заставляет dog также быть редкой.
>>> enc = preprocessing.OneHotEncoder(min_frequency=4, max_categories=3, sparse_output=False)
>>> enc = enc.fit(X)
>>> enc.transform([['dog'], ['cat'], ['rabbit'], ['snake']])
array([[0., 0., 1.],
[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])
Если есть редкие категории с одинаковой кратностью на отсечении max_categories, то первые max_categories берутся в соответствии с лексикографическим порядком. В следующем примере «b», «c» и «d» имеют одинаковую кратность и с max_categories=2, «b» и «c» являются редкими, так как у них больший лексикографический порядок.
>>> X = np.asarray([["a"] * 20 + ["b"] * 10 + ["c"] * 10 + ["d"] * 10], dtype=object).T >>> enc = preprocessing.OneHotEncoder(max_categories=3).fit(X) >>> enc.infrequent_categories_ [array(['b', 'c'], dtype=object)]
6.3.4.2. Кодировщик целей
Класс TargetEncoder использует среднее значение целевой переменной, обусловленное категориальным признаком, для кодирования неупорядоченных категорий, то есть номинальных категорий [PAR] [MIC]. Эта схема кодирования полезна для категориальных признаков с высокой размерностью, где однозначное кодирование увеличило бы размерность признакового пространства, что делает обработку моделью дальнейшего уровня более сложной. Классическим примером категорий высокой размерности являются местоположения, такие как почтовый индекс или регион.
Бинарные целевые переменные классификации
Для бинарной целевой переменной классификации кодирование целевой переменной задаётся следующим образом:
где \(S_i\) — кодирование для категории \(i\), \(n_{iY}\) — количество наблюдений с \(Y=1\) и категорией \(i\), \(n_i\) — количество наблюдений с категорией \(i\), \(n_Y\) — количество наблюдений с \(Y=1\), \(n\) — общее количество наблюдений, а \(\lambda_i\) — фактор сжатия для категории \(i\). Фактор сжатия определяется следующим образом:
где \(m\) — фактор сглаживания, который управляется параметром smooth в TargetEncoder. Большие факторы сглаживания придадут больше веса глобальному среднему значению. Когда smooth="auto", фактор сглаживания вычисляется как оценка эмпирического Байеса: \(m=\sigma_i^2/\tau^2\), где \(\sigma_i^2\) — дисперсия y с категорией \(i\) и \(\tau^2\) — глобальная дисперсия y.
Многоклассовые целевые переменные классификации
Для многоклассовых целевых переменных формулировка аналогична бинарной классификации:
где \(S_{ij}\) — кодирование для категории \(i\) и класса \(j\), \(n_{iY_j}\) — количество наблюдений с \(Y=j\) и категорией \(i\), \(n_i\) — количество наблюдений с категорией \(i\), \(n_{Y_j}\) — количество наблюдений с \(Y=j\), \(n\) — общее количество наблюдений, а \(\lambda_i\) — фактор сжатия для категории \(i\).
Непрерывные целевые переменные
Для непрерывных целевых переменных формулировка аналогична бинарной классификации:
где \(L_i\) — множество наблюдений с категорией \(i\) и \(n_i\) — количество наблюдений с категорией \(i\).
fit_transform внутренне использует схему кросс-валидации для предотвращения утечки информации о целевой переменной в представление во время обучения, особенно для неинформативных категориальных переменных с высокой размерностью, и помогает предотвратить переобучение модели на ложных корреляциях. Обратите внимание, что в результате fit(X, y).transform(X) не равно fit_transform(X, y). В fit_transform данные обучения разбиваются на k фолдов (определяемых параметром cv) и каждый фолд кодируется с использованием кодирований, полученных из других k-1 фолдов. На следующем рисунке показана схема кросс-валидации в fit_transform с параметром по умолчанию cv=5.
fit_transform также обучает кодирование с использованием всех данных, используя весь набор данных обучения. Это никогда не используется в fit_transform, но сохраняется в атрибуте encodings_, для использования при вызове transform. Обратите внимание, что кодирования, обученные для каждого фолда во время схемы кросс-валидации, не сохраняются в атрибут.
Метод fit не использует схемы кросс-валидации и обучает одно кодирование на всем наборе данных обучения, которое используется для кодирования категорий в transform. Это кодирование такое же, как кодирование «полных данных», обученное в fit_transform.
Примечание
TargetEncoder рассматривает пропущенные значения, такие как np.nan или None, как другую категорию и кодирует их так же, как и другие категории. Категории, которые не встречаются во время fit, кодируются средним значением целевой переменной, то есть target_mean_.
Примеры
- Сравнение кодировщика целей с другими кодировщиками
- Внутренняя схема кросс-валидации кодировщика целей
Ссылки
6.3.5. Дискретизация
Дискретизация (также известная как квантование или бинингование) предоставляет способ разбиения непрерывных признаков на дискретные значения. Некоторые наборы данных с непрерывными признаками могут извлечь выгоду из дискретизации, так как дискретизация может преобразовать набор данных с непрерывными атрибутами в набор данных только с номинальными атрибутами.
Дискретизированные признаки, закодированные с помощью one-hot, могут сделать модель более выразительной, сохраняя при этом интерпретируемость. Например, предобработка с помощью дискретизатора может ввести нелинейность в линейные модели. Для более продвинутых возможностей, в частности, плавных, см. Генерация полиномиальных признаков ниже.
6.3.5.1. Дискретизация с K ячейками
KBinsDiscretizer дискретизирует признаки на k ячейки:
>>> X = np.array([[ -3., 5., 15 ], ... [ 0., 6., 14 ], ... [ 6., 3., 11 ]]) >>> est = preprocessing.KBinsDiscretizer(n_bins=[3, 2, 2], encode='ordinal').fit(X)
По умолчанию результат кодируется с помощью one-hot в разреженную матрицу (см. Кодирование категориальных признаков), и это можно настроить с помощью параметра encode. Для каждого признака границы ячеек вычисляются во время fit, и вместе с числом ячеек они определят интервалы. Таким образом, для текущего примера эти интервалы определяются как:
- признак 1: \({[-\infty, -1), [-1, 2), [2, \infty)}\)
- признак 2: \({[-\infty, 5), [5, \infty)}\)
- признак 3: \({[-\infty, 14), [14, \infty)}\)
На основе этих интервалов ячеек X преобразуется следующим образом:
>>> est.transform(X)
array([[ 0., 1., 1.],
[ 1., 1., 1.],
[ 2., 0., 0.]])
Полученный набор данных содержит порядковые атрибуты, которые могут быть далее использованы в Pipeline.
Дискретизация аналогична построению гистограмм для непрерывных данных. Однако гистограммы сосредоточены на подсчете признаков, попадающих в определенные ячейки, тогда как дискретизация фокусируется на присвоении значений признаков этим ячейкам.
KBinsDiscretizer реализует различные стратегии бинингования, которые можно выбрать с помощью параметра strategy. Стратегия «uniform» использует ячейки с постоянной шириной. Стратегия «quantile» использует значения квантилей для равномерного заполнения ячеек для каждого признака. Стратегия «kmeans» определяет ячейки на основе процедуры кластеризации k-means, выполняемой для каждого признака независимо.
Обратите внимание, что можно указать пользовательские ячейки, передав вызываемую функцию, определяющую стратегию дискретизации, в FunctionTransformer. Например, мы можем использовать функцию Pandas pandas.cut:
>>> import pandas as pd
>>> import numpy as np
>>> from sklearn import preprocessing
>>>
>>> bins = [0, 1, 13, 20, 60, np.inf]
>>> labels = ['infant', 'kid', 'teen', 'adult', 'senior citizen']
>>> transformer = preprocessing.FunctionTransformer(
... pd.cut, kw_args={'bins': bins, 'labels': labels, 'retbins': False}
... )
>>> X = np.array([0.2, 2, 15, 25, 97])
>>> transformer.fit_transform(X)
['infant', 'kid', 'teen', 'adult', 'senior citizen']
Categories (5, object): ['infant' < 'kid' < 'teen' < 'adult' < 'senior citizen']
Примеры
6.3.5.2. Бинаризация признаков
Бинаризация признаков — это процесс порогового значения числовых признаков для получения булевых значений. Это может быть полезно для последующих вероятностных оценок, которые предполагают, что входные данные распределены в соответствии с многомерным распределением Бернулли. Например, это относится к BernoulliRBM.
Среди текстовых процессоров также часто используются бинарные значения признаков (вероятно, для упрощения вероятностного рассуждения), даже если нормализованные подсчеты (также известные как частоты терминов) или признаки TF-IDF часто демонстрируют немного лучшие результаты на практике.
Что касается класса Normalizer, утилитарный класс Binarizer предназначен для использования на ранних этапах Pipeline. Метод fit ничего не делает, так как каждый образец обрабатывается независимо от других:
>>> X = [[ 1., -1., 2.],
... [ 2., 0., 0.],
... [ 0., 1., -1.]]
>>> binarizer = preprocessing.Binarizer().fit(X) # fit does nothing
>>> binarizer
Binarizer()
>>> binarizer.transform(X)
array([[1., 0., 1.],
[1., 0., 0.],
[0., 1., 0.]])
Можно настроить порог бинаризатора:
>>> binarizer = preprocessing.Binarizer(threshold=1.1)
>>> binarizer.transform(X)
array([[0., 0., 1.],
[1., 0., 0.],
[0., 0., 0.]])
Что касается класса Normalizer, модуль предобработки предоставляет вспомогательную функцию binarize, предназначенную для использования, когда API трансформатора не нужен.
Обратите внимание, что Binarizer похож на KBinsDiscretizer, когда k = 2, и когда граница ячейки находится в значении threshold.
6.3.6. Заполнение пропущенных значений
Инструменты для заполнения пропущенных значений обсуждаются в Заполнении пропущенных значений.
6.3.7. Генерация полиномиальных признаков
Часто полезно повысить сложность модели, рассмотрев нелинейные признаки входных данных. Мы покажем два варианта, основанные на полиномах: первый использует чистые полиномы, второй — сплайны, т. е. кусковые полиномы.
6.3.7.1. Полиномиальные признаки
Простой и распространённый метод — полиномиальные признаки, которые могут получать признаки высших порядков и взаимодействия. Он реализован в PolynomialFeatures:
>>> import numpy as np
>>> from sklearn.preprocessing import PolynomialFeatures
>>> X = np.arange(6).reshape(3, 2)
>>> X
array([[0, 1],
[2, 3],
[4, 5]])
>>> poly = PolynomialFeatures(2)
>>> poly.fit_transform(X)
array([[ 1., 0., 1., 0., 0., 1.],
[ 1., 2., 3., 4., 6., 9.],
[ 1., 4., 5., 16., 20., 25.]])
Признаки X были преобразованы от \((X_1, X_2)\) к \((1, X_1, X_2, X_1^2, X_1X_2, X_2^2)\).
В некоторых случаях требуются только члены взаимодействия между признаками, и их можно получить с настройкой interaction_only=True:
>>> X = np.arange(9).reshape(3, 3)
>>> X
array([[0, 1, 2],
[3, 4, 5],
[6, 7, 8]])
>>> poly = PolynomialFeatures(degree=3, interaction_only=True)
>>> poly.fit_transform(X)
array([[ 1., 0., 1., 2., 0., 0., 2., 0.],
[ 1., 3., 4., 5., 12., 15., 20., 60.],
[ 1., 6., 7., 8., 42., 48., 56., 336.]])
Признаки X были преобразованы от \((X_1, X_2, X_3)\) к \((1, X_1, X_2, X_3, X_1X_2, X_1X_3, X_2X_3, X_1X_2X_3)\).
Обратите внимание, что полиномиальные признаки используются неявно в методах ядер (например, SVC, KernelPCA) при использовании полиномиальных функций ядра.
См. Интерполяция полиномами и сплайнами для регрессии Риджа с созданными полиномиальными признаками.
6.3.7.2. Преобразователь сплайнов
Другой способ добавить нелинейные члены вместо чистых полиномов признаков — сгенерировать функции базиса сплайна для каждого признака с помощью SplineTransformer. Сплайны — это кусковые полиномы, параметризованные их степенью полинома и положениями узлов. SplineTransformer реализует базис B-сплайна, см. ссылки ниже.
Примечание
SplineTransformer обрабатывает каждый признак отдельно, т. е. не даст вам члены взаимодействия.
Некоторые преимущества сплайнов по сравнению с полиномами:
- B-сплайны очень гибкие и надёжные, если вы сохраните фиксированную низкую степень, обычно 3, и экономно адаптируете количество узлов. Полиномы потребовали бы большей степени, что приводит к следующему пункту.
- B-сплайны не имеют колебательного поведения на границах, как полиномы (чем выше степень, тем хуже). Это известно как явление Рунге.
- B-сплайны обеспечивают хорошие варианты экстраполяции за пределы границ, т. е. за пределы диапазона аппроксимируемых значений. Обратите внимание на параметр
extrapolation. - B-сплайны генерируют матрицу признаков с полосатой структурой. Для одного признака каждая строка содержит только
degree + 1ненулевых элементов, которые встречаются последовательно и даже положительны. Это приводит к матрице с хорошими численными свойствами, например, малой числовой обусловленностью, в резком контрасте с матрицей полиномов, которая называется матрицей Вандермонда. Малая числовая обусловленность важна для устойчивых алгоритмов линейных моделей.
Следующий фрагмент кода демонстрирует сплайны в действии:
>>> import numpy as np
>>> from sklearn.preprocessing import SplineTransformer
>>> X = np.arange(5).reshape(5, 1)
>>> X
array([[0],
[1],
[2],
[3],
[4]])
>>> spline = SplineTransformer(degree=2, n_knots=3)
>>> spline.fit_transform(X)
array([[0.5 , 0.5 , 0. , 0. ],
[0.125, 0.75 , 0.125, 0. ],
[0. , 0.5 , 0.5 , 0. ],
[0. , 0.125, 0.75 , 0.125],
[0. , 0. , 0.5 , 0.5 ]])
Поскольку X отсортированы, легко увидеть выходной полосатый матрицы. Только три средние диагонали ненулевые для degree=2. Чем выше степень, тем больше перекрытие сплайнов.
Интересно, что SplineTransformer от degree=0 эквивалентен KBinsDiscretizer с encode='onehot-dense' и n_bins = n_knots - 1 если knots = strategy.
Примеры
Ссылки
- Eilers, P., & Marx, B. (1996). Гибкое сглаживание с B-сплайнами и штрафами. Statist. Sci. 11 (1996), no. 2, 89–121.
- Perperoglou, A., Sauerbrei, W., Abrahamowicz, M. et al. Обзор процедур сплайновых функций в R. BMC Med Res Methodol 19, 46 (2019).
6.3.8. Пользовательские преобразователи
Зачастую вам потребуется преобразовать существующую Python-функцию в преобразователь для помощи в очистке или обработке данных. Вы можете реализовать преобразователь из произвольной функции с помощью FunctionTransformer. Например, чтобы создать преобразователь, применяющий логарифмическое преобразование в конвейере, сделайте так:
>>> import numpy as np
>>> from sklearn.preprocessing import FunctionTransformer
>>> transformer = FunctionTransformer(np.log1p, validate=True)
>>> X = np.array([[0, 1], [2, 3]])
>>> # Since FunctionTransformer is no-op during fit, we can call transform directly
>>> transformer.transform(X)
array([[0. , 0.69314718],
[1.09861229, 1.38629436]])
Вы можете убедиться, что func и inverse_func являются обратными друг другу, установив check_inverse=True и вызвав fit до transform. Обратите внимание, что генерируется предупреждение, которое можно преобразовать в ошибку с filterwarnings:
>>> import warnings
>>> warnings.filterwarnings("error", message=".*check_inverse*.",
... category=UserWarning, append=False)
Для примера полного кода, демонстрирующего использование FunctionTransformer для извлечения признаков из текстовых данных, см. Преобразователь столбцов с разнородными источниками данных и Инженерия временных признаков.
© 2007–2025 The scikit-learn developers
Licensed under the 3-clause BSD License.
https://scikit-learn.org/1.6/modules/preprocessing.html