Как писать преобразователи данных в Sklearn

Сегодня разбираемся, как создавать собственные преобразователи Sklearn, позволяющие интегрировать практически любую функцию или преобразование данных в классы конвейера Sklearn. Подробности под катом к старту флагманского курса по Data Science.
Зачем?
Только один вызов fit , и один — predict — насколько это было бы здорово? Вы получаете данные, обучаете конвейер единожды, и он заботится о предварительной обработке, инжиниринге признаков, моделировании. Всё, что нужно сделать вам, — это вызвать fit .
Какой же конвейер для этого достаточно мощный? В Sklearn много преобразователей, но не для всех ситуаций предварительной обработки. Итак, наш конвейер — несбыточная мечта, или нет? Точно нет.
Что такое конвейеры Sklearn?
Вот простой конвейер, который заполняет пропущенные значения числами, масштабирует их и обучает XGBRegressor на X , y :
from sklearn.pipeline import make_pipeline from sklearn.preprocessing import StandardScaler from sklearn.impute import SimpleImputer import xgboost as xgb xgb_pipe = make_pipeline( SimpleImputer(strategy='mean'), StandardScaler(), xgb.XGBRegressor() ) _ = xgb_pipe.fit(X, y)
В этом посте я в мельчайших подробностях рассказал о конвейерах Sklearn и об их преимуществах.
Самое заметное преимущество конвейеров — способность объединять все этапы предварительной обработки и моделирования в единственный оценщик, предотвращать утечку данных и не вызывать fit на наборах данных для валидации. А ещё конвейер — это бонус в виде краткого, воспроизводимого, модульного кода.
Но вся эта идея атомарных, аккуратных конвейеров ломается, как только нужно выполнять операции, которые не встроены в Sklearn как функции оценки, например:
- Извлечь шаблоны регулярных выражений для очистки текстовых данных нужно.
- Объединить существующие функции в одну, исходя из знаний предметной области.
Чтобы сохранить все преимущества конвейеров, нужен способ интегрировать в Sklearn пользовательскую предварительную обработку и логику инжиниринга признаков. Здесь и вступают в игру пользовательские преобразователи.
Интеграция простых функций через FunctionTransformer
В сентябрьском конкурсе TPS 2021 на Kaggle одна из идей — добавить количества пропущенных [в сроке данных] значений как новый признак — значительно повысила производительность модели. Эта операция не реализована в Sklearn, поэтому напишем функцию, которая отработает после импорта данных:
tps_df = pd.read_csv("data/train.csv") tps_df.head()

>>> tps_df.shape (957919, 120) >>> # Find the number of missing values across rows >>> tps_df.isnull().sum(axis=1) 0 1 1 0 2 5 3 2 4 8 .. 957914 0 957915 4 957916 0 957917 1 957918 4 Length: 957919, dtype: int64
Эта функция принимает DataFrame и реализует операцию:
def num_missing_row(X: pd.DataFrame, y=None): # Calculate some metrics across rows num_missing = X.isnull().sum(axis=1) num_missing_std = X.isnull().std(axis=1) # Add the above series as a new feature to the df X["#missing"] = num_missing X["num_missing_std"] = num_missing_std return X
Добавим в функцию в конвейер — передадим её в FunctionTransformer :
from sklearn.preprocessing import FunctionTransformer num_missing_estimator = FunctionTransformer(num_missing_row)
При передаче пользовательской функции в FunctionTransformer создаётся оценщик с методами fit , transform и fit_transform :
# Check number of columns before print(f"Number of features before preprocessing: ") # Apply the custom estimator tps_df = num_missing_estimator.transform(tps_df) print(f"Number of features after preprocessing: ") ------------------------------------------------------ Number of features before preprocessing: 120 Number of features after preprocessing: 122
Итак, у нас есть простая функция, поэтому нет необходимости вызывать fit : она просто возвращает нетронутую оценку. Единственное требование FunctionTransformer состоит в том, что передаваемая функция должна принимать данные в своём первом аргументе. При желании, если в функции нужен целевой массив, можно передать и его:
# FunctionTransformer signature def custom_function(X, y=None): . estimator = FunctionTransformer(custom_function) # no errors custom_pipeline = make_pipeline(StandardScaler(), estimator, xgb.XGBRegressor()) custom_pipeline.fit(X, y)
FunctionTransformer также принимает инверсию переданной функции на случай, если понадобится отменить изменения:
def custom_function(X, y=None): . def inverse_of_custom(X, y=None): . estimator = FunctionTransformer(func=custom_function, inverse_func=inverse_of_custom)
Подробности о других аргументах смотрите в документации.
Интеграция сложных шагов предварительной обработки
Один из самых распространённых вариантов масштабирования искажённых данных — это логарифмическое преобразование. Но если функция содержит хотя бы один 0, преобразование с помощью np.log или PowerTransformer вернёт ошибку.
Для обхода особенности особенности участники соревнований Kaggle ко всем образцам данных добавляют 1, и лишь затем применяют преобразование. Если преобразование выполняется в целевом массиве, то потребуется обратное преобразование, для которого после прогнозирования нужно использовать экспоненциальную функцию и вычесть 1:
y_transformed = np.log(y + 1) _ = model.fit(X, y_transformed) preds = np.exp(model.predict(X, y_transformed) - 1)
Работает, но осталась та же проблема — мы не можем включить код в конвейер из коробки. Конечно, можно обратиться к новому другу — FunctionTransformer , но он не подходит для сложных этапов предварительной обработки, таких как этот.
Вместо этого напишем собственный класс преобразователя и создадим функции fit , transform вручную. В конце концов у нас снова будет Sklearn-совместимый оценщик. Начнём:
from sklearn.base import BaseEstimator, TransformerMixin class CustomLogTransformer(BaseEstimator, TransformerMixin): pass
Сначала мы создаём класс, который наследуется от BaseEstimator и TransformerMixin из sklearn.base . Наследование этих классов позволяет конвейерам Sklearn распознавать классы как пользовательские оценщики.
Напишем метод __init__ , где инициализируем экземпляр PowerTransformer :
from sklearn.preprocessing import PowerTransformer class CustomLogTransformer(BaseEstimator, TransformerMixin): def __init__(self): self._estimator = PowerTransformer()
Напишем fit , где добавляем 1 ко всем признакам в данных и обучаем PowerTransformer :
class CustomLogTransformer(BaseEstimator, TransformerMixin): def __init__(self): self._estimator = PowerTransformer() def fit(self, X, y=None): X_copy = np.copy(X) + 1 self._estimator.fit(X_copy) return self
Метод fit должен возвращать сам преобразователь, это делается возвратом self . И проверим то, что мы написали:
custom_log = CustomLogTransformer() >>> custom_log.fit(tps_df) CustomLogTransformer()
Пока работает как положено.
У нас есть transform , где после добавления 1 к переданным данным вызывается transform из класса PowerTransformer :
class CustomLogTransformer(BaseEstimator, TransformerMixin): def __init__(self): self._estimator = PowerTransformer() def fit(self, X, y=None): X_copy = np.copy(X) + 1 self._estimator.fit(X_copy) return self def transform(self, X): X_copy = np.copy(X) + 1 return self._estimator.transform(X_copy)
Проверим его по-другому:
custom_log = CustomLogTransformer() custom_log.fit(tps_df) transformed_tps = custom_log.transform(tps_df) >>> transformed_tps[:5, :5] array([[ 0.48908946, -2.17126787, -1.79124946, -0.52828469, nan], [ 0.38660665, -0.29384644, 1.31313666, 0.1901713 , -0.34236997], [-0.04286469, -0.05047097, -1.16463754, 0.95459266, 1.71830766], [-0.584329 , -1.5743182 , -1.02444525, -0.15117546, 0.46952437], [-0.87027925, -0.13045462, -0.10489176, -0.36806683, 1.21317668]])
Работает как надо. Как я уже говорил, нам нужен метод отмены преобразования:
class CustomLogTransformer(BaseEstimator, TransformerMixin): def __init__(self): self._estimator = PowerTransformer() def fit(self, X, y=None): X_copy = np.copy(X) + 1 self._estimator.fit(X_copy) return self def transform(self, X): X_copy = np.copy(X) + 1 return self._estimator.transform(X_copy) def inverse_transform(self, X): X_reversed = self._estimator.inverse_transform(np.copy(X)) return X_reversed - 1
Вместо inverse_transform можно было воспользоваться np.exp . Теперь проведём окончательную проверку:
custom_log = CustomLogTransformer() tps_transformed = custom_log.fit_transform(tps_df) tps_inversed = custom_log.inverse_transform(tps_transformed)
Но подождите! Мы не писали _fit_transform_ — откуда она взялась?
Это просто — когда вы наследуетесь от _BaseEstimator_ и _TransformerMixin_ , то метод _fit_transform_ получаете просто так.
После обратного преобразования можно сравнить его с исходными данными:
>>> tps_df.values[:5, 5] array([0.35275, 0.17725, 0.25997, 0.4293 , 0.34079]) >>> tps_inversed[:5, 5] array([0.35275, 0.17725, 0.25997, 0.4293 , 0.34079])
Теперь у нас есть собственный преобразователь. Давайте соберём весь код воедино:
from sklearn.metrics import roc_auc_score from sklearn.model_selection import train_test_split from sklearn.pipeline import make_pipeline xgb_pipe = make_pipeline( FunctionTransformer(num_missing_row), SimpleImputer(strategy="constant", fill_value=-99999), CustomLogTransformer(), xgb.XGBClassifier( n_estimators=1000, tree_method="gpu_hist", objective="binary:logistic" ), ) X, y = tps_df.drop("claim", axis=1), tps_df[["claim"]].values.flatten() split = train_test_split(X, y, test_size=0.33, random_state=1121218) X_train, X_test, y_train, y_test = split xgb_pipe.fit(X_train, y_train) preds = xgb_pipe.predict_proba(X_test) >>> roc_auc_score(y_test, preds[:, 1]) 0.7986831816726399
Несмотря на то что преобразование нанесло вред модели, мы заставили наш конвейер работать!
Говоря коротко, сигнатура пользовательского класса преобразователя должна быть такой:
class CustomTransformer(BaseEstimator, TransformerMixin): def __init__(self): pass def fit(self): pass def transform(self): pass def inverse_transform(self): pass
Так вы получаете fit_transform без всяких усилий. Если не нужны методы __init__ , fit , transform или inverse_transform , не используйте их, родительские классы Sklearn позаботятся обо всём. Логика этих методов полностью зависит от ваших нужд.
А пока вы осваиваете преобразования в Sklearn, мы поможем вам прокачать навыки или с самого начала освоить профессию, востребованную в любое время:
- Профессия Data Scientist
- Профессия Fullstack-разработчик на Python
Как создать пользовательский трансформер данных с помощью sklearn?
Sklearn — это библиотека машинного обучения для Python, которая в том числе предоставляет широкий функционал по преобразованию данных для разных задач.
В процессе очистки и подготовки данных нам часто приходится делать такие простые операции, как удаление столбцов и пр. Зачем для этого каждый раз писать пользовательский код? sklearn предоставляет механизм стандартизации таких преобразований для любых данных и поможет нам создать унифицированный конвейер из нужных действий.
Кроме того, при оценке моделей с использованием кросс-валидации, преобразования данных в исходном датасете не должны приводить к утечке данных. И эту проблему решит создание пользовательского трансформера, а делать мы это будем при помощи класса Function Transformer . В нём пропишем сразу все необходимые действия над данными, а потом просто будем его использовать, как и любой другой трансформер в sklearn .
Создание пользовательского трансформера
Всё, что нам нужно сделать для разработки собственного трансформера — реализовать несколько шагов:
- инициализировать класс transformer ;
- классы BaseEstimator и TransformerMixin из модулей sklearn.base должны быть дочерними по отношению к нему;
- должны быть объявлены методы класса fit() и transform() . Чтобы они без проблем встраивались в пайплайн, у них обязательно должны быть параметры X и y , а функция transform() в качестве выходных данных должна возвращать pandas.DataFrame или массив Numpy.
from numpy.random import randint from sklearn.base import BaseEstimator, TransformerMixin class BasicTransformer(BaseEstimator, TransformerMixin): def fit(self, X, y=None): return self def transform(self, X, y=None): X["cust_num"] = randint(0, 10, X.shape[0]) return X df_basic = pd.DataFrame() pipe = Pipeline( steps=[ ("use_custom_transformer", basicTransformer()) ] ) transformed_df = pipe.fit_transform(df_basic) df_basic
Вот и всё, в пайплайне будут реализованы все операции, прописанные в этом классе. Полученный датафрейм будет выглядеть следующим образом:
Давайте создадим ещё один пользовательский трансформер, применим его к набору данных и даже спрогнозируем некоторые значения. Начнём с импорта нужных библиотек. Они понадобятся нам для выполнения дальнейших операций.
import numpy as np import pandas as pd from sklearn.metrics import mean_squared_error,r2_score from sklearn.pipeline import FeatureUnion, Pipeline, make_pipeline from sklearn.base import BaseEstimator, TransformerMixin from sklearn.linear_model import LinearRegression from sklearn.model_selection import train_test_split
Чтение, подготовка и анализ данных
Будем работать с датасетом из области страхования. В качестве прогнозируемого показателя взята стоимость страховки в зависимости от различных фич.
df = pd.read_csv("/content/insurance.csv") df_util = pd.get_dummies(data = df, columns=["sex", "smoker", "region"], drop_first = True)
Приведённый выше график отображает распределение величины страховых взносов в зависимости от индекса массы тела (ИМТ) клиентов с классификацией клиентов по возрасту.
Разобьём данные на обучающие и тестовые в соответствии со стандартным соотношением 70:30.
X = df_util.drop(["charges"], axis = 1) y = df_util["charges"] X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.30, random_state = 42)
Теперь создадим непосредственно пользовательский трансформер и используем его для преобразования обучающей выборки.
class CustomTransformer(BaseEstimator, TransformerMixin): def __init__(self, feature_name, additional_param = "SM"): print("\n. intializing\n") self.feature_name = feature_name self.additional_param = additional_param def fit(self, X, y = None): print("\nfiting data. \n") print(f"\n \U0001f600 \n") return self def transform(self, X, y = None): print("\n. transforming data \n") X_ = X.copy() X_[self.feature_name] = np.log(X_[self.feature_name]) return X print("creating second pipeline. ") pipe2 = Pipeline(steps=[ ("experimental_trans", CustomTransformer("bmi")), ("linear_model", LinearRegression()) ]) print("fiting pipeline 2") pipe2.fit(X_train, y_train) preds2 = pipe2.predict(X_test) print(f"RMSE: \n")
Пайплайн используется по причине того, что выполнение всех последовательных преобразований отдельными блоками кода — далеко не самый удобный вариант. Пайплайны сохраняют нужную последовательность операций в едином блоке кода, позволяя одним действием сделать все нужное.
В этом конкретном трансформере пользователь может указывать названия столбцов, над которыми необходимо выполнить преобразования через передачу аргументов.
Далее строим модель линейной регрессии с использованием этого трансформера. Еще он логарифмирует значения, что часто может повысить качество моделей линейной регрессии.
Выше представлен график, на котором отражены фактические и прогнозируемые страховые взносы. Можно заметить, что линия регрессии вполне адекватно объясняет их взаимосвязь.
Вот мы уже можем создавать пользовательские трансформеры и даже использовать их для прогнозирования данных. Но что, если мы захотим настроить трансформеры, предлагаемые sklearn ? Давайте модифицируем OrdinalEncoder:
from sklearn.preprocessing import OrdinalEncoder class CustEncoder(OrdinalEncoder): def __init__(self, **kwargs): super().__init__(**kwargs) def transform(self, X, y=None): transformed_data = super().transform(X) encoded_data = pd.DataFrame(transformed_data, columns=self.feature_names_in_) return encoded_data data = df[["sex","smoker","region"]] enc = CustEncoder(dtype=int) new_data = enc.fit_transform(data) new_data[:8]
С помощью super() мы можем настраивать любые предопределённые трансформеры в соответствии с нашими потребностями.
Заключение
Пользовательские трансформеры позволяют более гибко и удобно реализовывать подготовку данных, в т.ч. инкапсулировать её в отдельную сущность. Такой подход делает код более понятным. А помимо создания новых трансформеров, можно модифицировать уже существующие из библиотеки sklearn . Попробуйте, пригодиться ^_^.
Конвейеры трансформации и кастомные трансформаторы в scikit-learn
В scikit-learn есть специальный класс Pipeline, с помощью которого можно создавать конструктор определяющий последовательность из шагов, трансформирующих данные в нужном порядке.
Выглядит это примерно так:
from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler transform_pipeline = Pipeline([ ('std_scaler', StandardScaler()), ('pd_to_np', FunctionTransformer(PdtoNp, validate=False)), ])
Вызов метода fit() запускает всю цепочку и приводит к последовательному выполнению трансформаций.
В данном случае в конвейер включены два шага трансформации данных. Первый — базовый scikit-learn-овский метод стандартизации данных (подробнее). А второй — это кастомный трансформатор.
Самый простой способ создать собственный трансформатор — это импортировать FunctionTransformer из sklearn.preprocessing. FunctionTransformer отправляет X и опционально y (массив данных и массив меток), а так же пользовательские аргументы в указанную функцию и возвращает результат. В примитивном примере я трансформирую произвольные данные в numpy массив.
from sklearn.preprocessing import FunctionTransformer def PdtoNp(some_data): return some_data.values
Чтобы создать более сложную конструкцию, можно реализовать собственный класс, а в нем три базовых метода scikit-learn — fit() (должен возвращать self), transform() (собственно сам трансформер) и fit_transform(). Для всех трех методов обязательным аргументом является X. Если унаследовать класс от TransformerMixin, то fit_transformer() можно не задавать, а если добавить в качестве базового класса BaseEstimator и не реализовывать *args, **kwargs в конструкторе, то будут доступны методы get_params() и set_params()
from sklearn.base import BaseEstimator, TransformerMixin class That(BaseEstimator, TransformerMixin): def __init__(self, this = True): self.this = this def fit(self, X, y=None): return self def transform(self, X, y=None): that = self.this return that transformer = That(this=False) my_data = transformer.transform(my_data)
Ну и, наконец, можно собирать несколько конвейеров трансформации в один, например, так:
transform_pipeline1 = Pipeline([ ('std_scaler', StandardScaler()), ]) transform_pipeline2 = Pipeline([ ('pd_to_np', FunctionTransformer(PdtoNp, validate=False)), ]) full_transform_pipeline = Pipeline([ ('transform_pipeline1', transform_pipeline1)), ('transform_pipeline2', transform_pipeline1)), ])
Весь этот набор инструментов scikit-learn превращает обработку данных в довольно креативный и удобный для пользователя процесс.
Все статьи с тегом scikit-learn
- Подготовка данных: кодирование категориальных признаков (28 Oct 2019)
- Особенности препроцессинга данных в scikit-learn (08 Oct 2019)
- Зависимость вычислений в scikit-learn от данных и модели (28 Sep 2019)
- Временная сложность алгоритмов машинного обучения (08 Sep 2019)
- API scikit-learn (27 Aug 2019)
- Метрики оценки для отбора моделей в scikit-learn (09 Aug 2019)
- Конвейеры трансформации и кастомные трансформаторы в scikit-learn (02 Aug 2019)
CS231n: Softmax классификатор
Третья задача в Assignment #1: Image Classification, kNN, SVM, Softmax, Neural Network — это построение Softmax-классификатора. В задаче используется тот.
Метрики оценки для отбора моделей в scikit-learn
В scikit-learn можно воспользоваться встроенными метриками для отбора модели. Это можно сделать с помощью аргумента scoring, который доступен в том.
Этот проект поддерживается KonstantinKlepikov
Что такое Scikit Learn — гайд по популярной библиотеке Python для начинающих
Scikit-learn — один из наиболее широко используемых пакетов Python для Data Science и Machine Learning. Он позволяет выполнять множество операций и предоставляет множество алгоритмов. Scikit-learn также предлагает отличную документацию о своих классах, методах и функциях, а также описание используемых алгоритмов.
- предварительную обработку данных;
- уменьшение размерности;
- выбор модели;
- регрессии;
- классификации;
- кластерный анализ.
Он также предоставляет несколько наборов данных, которые вы можете использовать для тестирования ваших моделей.
Scikit-learn не реализует все, что связано с машинным обучением. Например, он не имеет комплексной поддержки для:
- нейронных сетей;
- самоорганизующихся карт (сетей Кохонена);
- обучения ассоциативным правилам;
- обучения с подкреплением (reinforcement learning).
Scikit-learn основан на NumPy и SciPy, поэтому необходимо понять хотя бы азы этих двух библиотек, чтобы эффективно применять Scikit-learn.
Scikit-learn — это пакет с открытым исходным кодом. Как и большинство материалов из экосистемы Python, он бесплатный даже для коммерческого использования. Он лицензирован под лицензией BSD.
В этой статье кратко представим некоторые возможности scikit-learn.
Предварительная обработка данных
Вы можете использовать scikit-learn для подготовки ваших данных к алгоритмам машинного обучения: стандартизации или нормализации данных, кодирования категориальных переменных и многое другое.
Давайте сначала определим массив NumPy, с которым будем работать:
>>> import numpy as np
>>> x = np.array([[0.1, 1.0, 22.8],
. [0.5, 5.0, 41.2],
. [1.2, 12.0, 2.8],
. [0.8, 8.0, 14.0]])
>>> x
array([[ 0.1, 1. , 22.8],
[ 0.5, 5. , 41.2],
[ 1.2, 12. , 2.8],
[ 0.8, 8. , 14. ]])
Вам часто нужно преобразовывать данные таким образом, чтобы среднее значение каждого столбца (элемента) было равно нулю, а стандартное отклонение — единице. В этом случае, можно использовать sklearn.preprocessing.StandardScaler:
>>> from sklearn.preprocessing import StandardScaler
>>> scaler = StandardScaler()
>>> scaled_x = scaler.fit_transform(x)
>>> scaler.scale_
array([ 0.40311289, 4.03112887, 14.04421589])
>>> scaler.mean_
array([ 0.65, 6.5 , 20.2 ])
>>> scaler.var_
array([1.6250e-01, 1.6250e+01, 1.9724e+02])
>>> scaled_x
array([[-1.36438208, -1.36438208, 0.18512959],
[-0.3721042 , -0.3721042 , 1.4952775 ],
[ 1.36438208, 1.36438208, -1.23894421],
[ 0.3721042 , 0.3721042 , -0.44146288]])
>>> scaled_x.mean().round(decimals=4)
0.0
>>> scaled_x.mean(axis=0)
array([ 1.66533454e-16, -1.38777878e-17, 1.52655666e-16])
>>> scaled_x.std(axis=0)
array([1., 1., 1.])
>>> scaler.inverse_transform(scaled_x)
array([[ 0.1, 1. , 22.8],
[ 0.5, 5. , 41.2],
[ 1.2, 12. , 2.8],
[ 0.8, 8. , 14. ]])
Бывает, что у вас есть некоторые категориальные данные, и вам нужно преобразовать их в числа. Один из способов сделать это — использовать класс sklearn.preprocessing.OneHotEncoder. Рассмотрим следующий пример с массивами ролей в компании:
>>> from sklearn.preprocessing import OneHotEncoder
>>> roles = np.array([('Tom', 'manager'),
. ('Mary', 'developer'),
. ('Ann', 'recruiter'),
. ('Jim', 'developer')])
>>> roles
array([['Tom', 'manager'],
['Mary', 'developer'],
['Ann', 'recruiter'],
['Jim', 'developer']], dtype='>> encoder = OneHotEncoder()
>>> encoded_roles = encoder.fit_transform(roles[:, [1]])
>>> encoded_roles.toarray()
array([[0., 1., 0.],
[1., 0., 0.],
[0., 0., 1.],
[1., 0., 0.]])
В приведенном выше примере первый столбец объекта encoded_roles указывает, является ли каждый сотрудник разработчиком. Второй и четвертый сотрудник (Мэри и Джим) — разработчики. Второй столбец связан с должностью менеджера. Только первый сотрудник (Том) имеет эту должность. Наконец, третий столбец соответствует должности рекрутера, и им является третий сотрудник (Энн).
Уменьшение размерности
Уменьшение размерности включает в себя выбор или извлечение наиболее важных компонентов (признаков) многомерного набора данных. Scikit-learn предлагает несколько подходов к уменьшению размерности. Одним из них является анализ основных компонентов (PCA).
Выбор модели
Для обучения и тестирования моделей машинного обучения, вам необходимо случайным образом разбивать данные на подмножества. Это включает как входы, так и их соответствующие выходы. Функция sklearn.model_selection.train_test_split () полезна в таких случаях:
>>> import numpy as np
>>> from sklearn.model_selection import train_test_split
>>> x, y = np.arange(1, 21).reshape(-1, 2), np.arange(3, 40, 4)
>>> x
array([[ 1, 2],
[ 3, 4],
[ 5, 6],
[ 7, 8],
[ 9, 10],
[11, 12],
[13, 14],
[15, 16],
[17, 18],
[19, 20]])
>>> y
array([ 3, 7, 11, 15, 19, 23, 27, 31, 35, 39])
>>> x_train, x_test, y_train, y_test =\
. train_test_split(x, y, test_size=0.4, random_state=0)
>>> x_train
array([[ 3, 4],
[13, 14],
[15, 16],
[ 7, 8],
[ 1, 2],
[11, 12]])
>>> y_train
array([ 7, 27, 31, 15, 3, 23])
>>> x_test
array([[ 5, 6],
[17, 18],
[ 9, 10],
[19, 20]])
>>> y_test
array([11, 35, 19, 39])
В дополнение к обычному разделению наборов данных, scikit-learn предоставляет средства для осуществления перекрестной проверки, настройки гиперпараметров ваших моделей с помощью поиска по сетке, вычисления многих величин, которые показывают производительность модели (например, коэффициент детерминации, среднеквадратичная ошибка, показатель отклонения с пояснением, матрица ошибок, отчет о классификации, f-показатели и многое другое).
Набор данных
Scikit-learn предоставляет несколько наборов данных, подходящих для изучения и тестирования ваших моделей. В основном, это известные наборы данных. Они представляют собой достаточный объем данных для тестирования моделей и в то же время не очень большой, что обеспечивает приемлемую продолжительность обучения.
Например, функция sklearn.datasets.load_boston () отображает данные о ценах на дома в районе Бостона (цены не обновляются!). Есть 506 наблюдений, а входная матрица имеет 13 столбцов (признаков):
>>> from sklearn.datasets import load_boston
>>> x, y = load_boston(return_X_y=True)
>>> x.shape, y.shape
((506, 13), (506,))
Этот набор данных подходит для многовариантной регрессии.
Другой пример — набор данных, связанный с вином. Его можно получить с помощью функции sklearn.datasets.load_wine ():
>>> from sklearn.datasets import load_wine
>>> x, y = load_wine(return_X_y=True)
>>> x.shape, y.shape
((178, 13), (178,))
>>> np.unique(y)
array([0, 1, 2])
Этот набор данных подходит для классификации. Он содержит 13 функций, связанных с тремя различными винодельческими компаниями из Италии, и 178 наблюдений.
Регрессия
Scikit-learn поддерживает различные методы регрессии начиная с линейной регрессии, метода k-ближайших соседей посредством полиномиальной регрессии, регрессии опорных векторов, деревьев принятия решений и т.д., до ансамбля методов, таких как random forest и градиентный бустинг. Он также поддерживает нейронные сети, но не в такой степени, как специализированные библиотеки, например, как TensorFlow.
Ниже покажем регрессию random forest.
Обычно мы начинаем наше регрессионное путешествие с импорта необходимых нам пакетов, классов и функций:
>>> import numpy as np
>>> from sklearn.datasets import load_boston
>>> from sklearn.ensemble import RandomForestRegressor
>>> from sklearn.model_selection import train_test_split
Следующим шагом является получение данных для работы и разделение этих данных на обучающее и тестовое подмножества. Мы будем использовать набор данных Бостона:
>>> x, y = load_boston(return_X_y=True)
>>> x_train, x_test, y_train, y_test =\
. train_test_split(x, y, test_size=0.33, random_state=0)
Некоторые методы требуют масштабирования (стандартизации) данных, в то время как для других методов это необязательно. На этот раз мы продолжим без масштабирования.
Теперь нам нужно создать наш регрессор и обучить его с помощью подмножества данных, выбранного для обучения:
>>> regressor = RandomForestRegressor(n_estimators=10, random_state=0)
>>> regressor.fit(x_train, y_train)
RandomForestRegressor(bootstrap=True, criterion='mse', max_depth=None,
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=10,
n_jobs=None, oob_score=False, random_state=0, verbose=0,
warm_start=False)
После того, как модель обучена, мы проверяем коэффициент детерминации на обучающем подмножестве данных и, что более важно, на тестовом подмножестве данных, который не использовался при обучении моделей.
>>> regressor.score(x_train, y_train)
0.9680930547240916
>>> regressor.score(x_test, y_test)
0.8219576562705848
Модель, обученная достаточно хорошо, может использоваться для прогнозирования выходных данных при других новых входных данных x_new. В данном случае, используется команда .predict(): regressor.predict(x_new).
Классификация
Scikit-learn выполняет классификацию почти так же, как регрессию. Он поддерживает различные методы классификации, такие как логистическая регрессия и k-ближайшие соседи, метод опорных векторов, наивный байесовский классификатор, дерево принятия решений, а также ансамбль методов, такие как random forest, AdaBoost и градиентный бустинг.
В этой статье показано, как использовать метод random forest для классификации. Этот подход весьма аналогичен подходу, применяемому в случае регрессии. Но теперь мы будем использовать набор данных, связанных с вином, определять классификатор и оценивать его с точностью классификации вместо коэффициента детерминации.
>>> import numpy as np
>>> from sklearn.datasets import load_wine
>>> from sklearn.ensemble import RandomForestClassifier
>>> from sklearn.model_selection import train_test_split
>>> x, y = load_wine(return_X_y=True)
>>> x_train, x_test, y_train, y_test =\
. train_test_split(x, y, test_size=0.33, random_state=0)
>>> classifier = RandomForestClassifier(n_estimators=10, random_state=0)
>>> classifier.fit(x_train, y_train)
RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini',
max_depth=None, 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=10,
n_jobs=None, oob_score=False, random_state=0, verbose=0,
warm_start=False)
>>> classifier.score(x_train, y_train)
1.0
>>> classifier.score(x_test, y_test)
1.0
Модель, обученная достаточно хорошо, может использоваться для прогнозирования выходных данных при других новых входных данных. В данном случае, используется команда .predict(): regressor.predict(x_new).
Кластерный анализ
Кластеризация — это ветвь неконтролируемого обучения, широко поддерживаемая в scikit-learn. В дополнение к методу k-средних, есть возможность применять метод распространения близости, спектральную кластеризацию, агломеративную кластеризацию и т.д.
В этой статье мы покажем метод k-средних. При его реализации, будьте внимательны, имеет ли смысл стандартизировать или нормализовать ваши данные и особенно, какая мера расстояния подходит (в большинстве случаев, это евклидово расстояние).
Опять же, мы начинаем с импорта и получения данных. На этот раз мы возьмем NumPy и sklearn.cluster.KMeans:
>>> import numpy as np
>>> from sklearn.cluster import KMeans
>>> x = np.array([(0.0, 0.0),
. (9.9, 8.1),
. (-1.0, 1.0),
. (7.1, 5.6),
. (-5.0, -5.5),
. (8.0, 9.8),
. (0.5, 0.5)])
>>> x
array([[ 0. , 0. ],
[ 9.9, 8.1],
[-1. , 1. ],
[ 7.1, 5.6],
[-5. , -5.5],
[ 8. , 9.8],
[ 0.5, 0.5]])
Следующим шагом является масштабирование данных, но это не всегда обязательно. Однако во многих случаях это действительно хорошая идея. Как только предварительная обработка данных завершена, мы создаем копию KMeans и подгоняем ее под наши данные:
>>> cluster_analyzer = KMeans(n_clusters=3, init='k-means++')
>>> cluster_analyzer.fit()
>>> cluster_analyzer.fit(x)
KMeans(algorithm='auto', copy_x=True, init='k-means++', max_iter=300,
n_clusters=3, n_init=10, n_jobs=None, precompute_distances='auto',
random_state=None, tol=0.0001, verbose=0)
Итак, мы готовы получить результаты, такие как координаты центров кластеров и метки кластеров, которым принадлежит каждое наблюдение:
>>> cluster_analyzer.cluster_centers_
array([[ 8.33333333, 7.83333333],
[-0.16666667, 0.5 ],
[-5. , -5.5 ]])
>>> cluster_analyzer.labels_
array([1, 0, 1, 0, 2, 0, 1], dtype=int32)
Вы можете использовать метод . predict() для получения ближайших кластеров для новых наблюдений.
Заключение
В этой статье представлены только основы Scikit-Learn, очень популярного пакета Python по data science и machine learning. Это одна из основных библиотек Python для этих целей.