{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import os\n", "import mlflow\n", "\n", "from sklearn.model_selection import train_test_split\n", "import pandas as pd\n", "import numpy\n", "\n", "from sklearn.preprocessing import StandardScaler, OrdinalEncoder, OneHotEncoder\n", "\n", "from sklearn.pipeline import Pipeline\n", "from sklearn.compose import ColumnTransformer\n", "from sklearn.ensemble import RandomForestRegressor\n", "from sklearn.linear_model import LinearRegression\n", "from catboost import CatBoostRegressor\n", "\n", "from sklearn.metrics import mean_absolute_percentage_error, mean_absolute_error, mean_squared_error" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df = pd.read_pickle('data/clean_data.pkl').sample(frac=0.1, random_state = 2) # Уменьшаем размер чтобы модель быстрее обучалась на лекции\n", "df.info()" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "df = df.rename(columns={'price': 'target'})\n", "df = df.drop(columns=['date', 'time'])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "X_train, X_test, y_train, y_test = train_test_split(df.drop('target', axis=1), df['target'], test_size=0.25, random_state=2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "cat_features = X_train.select_dtypes(include=['category','object']).columns.to_list()\n", "cat_features" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "num_features = X_train.select_dtypes(include=['number']).columns.to_list()\n", "num_features" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "https://scikit-learn.org/stable/api/sklearn.preprocessing.html - разные способы кодирования и скалирования" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "s_scaler = StandardScaler()\n", "l_encoder = OrdinalEncoder(handle_unknown='use_encoded_value', unknown_value=99999999) # unknown_value нужно выбирать с умом\n", "regressor = CatBoostRegressor()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Column transformer" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "# Для удобной работы со столбцами\n", "preprocessor = ColumnTransformer(\n", " transformers=[\n", " ('num', s_scaler, num_features), # преобразования для числовых признаков\n", " ('cat', l_encoder, cat_features), # преобразования для категориальных признаков\n", " ],\n", " remainder='drop' ) # Удаляем столбцы, которые не затронуты преобразования" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\n", "pipeline = Pipeline(steps=[('preprocessor', preprocessor), \n", " ('model', regressor)])\n", "\n", "pipeline.fit(X_train, y_train)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "predictions = pipeline.predict(X_test) \n", "\n", "metrics = {}\n", "metrics[\"mae\"] = mean_absolute_error(y_test, predictions) \n", "metrics[\"mape\"] = mean_absolute_percentage_error(y_test, predictions)\n", "metrics[\"mse\"] = mean_squared_error(y_test, predictions)\n", "\n", "metrics" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "\n", "# Работаем с MLflow локально\n", "TRACKING_SERVER_HOST = \"127.0.0.1\"\n", "TRACKING_SERVER_PORT = 5000\n", "\n", "registry_uri = f\"http://{TRACKING_SERVER_HOST}:{TRACKING_SERVER_PORT}\"\n", "tracking_uri = f\"http://{TRACKING_SERVER_HOST}:{TRACKING_SERVER_PORT}\"\n", "\n", "mlflow.set_tracking_uri(tracking_uri) \n", "mlflow.set_registry_uri(registry_uri) \n", "\n" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "# название тестового эксперимента, запуска (run) внутри него, имени, под которым модель будет регистрироваться\n", "EXPERIMENT_NAME = \"estate_project\"\n", "RUN_NAME = \"baseline model\"\n", "REGISTRY_MODEL_NAME = \"estate_model_rf\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Логируем вручную" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Обязательно логируем сигнатуру модели и пример входных данных. Подготовим их\n", "from mlflow.models import infer_signature\n", "\n", "signature = infer_signature(model_input = X_train.head(5))\n", "input_example = X_train.head(5)" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "# Будем логировать requirements и артефакт - текстовый файл\n", "req_file = 'requirements.txt'\n", "art = 'comment.txt'" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "# Параметры, котороые будут залогированы, можем задавать вручную или полностью взять из модели\n", "#params_dict = {'n_estimators': 10, 'max_depth': 10}\n", "params_dict = pipeline.get_params()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Когда создаем новый эксперимент, то: \n", "experiment_id = mlflow.create_experiment(EXPERIMENT_NAME)\n", "\n", "# Впоследствии. чтобы добавлять запуски в этот же эксепримент мы должны получить его id:\n", "#experiment_id = mlflow.get_experiment_by_name(EXPERIMENT_NAME).experiment_id\n", "\n", "with mlflow.start_run(run_name=RUN_NAME, experiment_id=experiment_id) as run:\n", " # получаем уникальный идентификатор запуска эксперимента\n", " run_id = run.info.run_id \n", " mlflow.sklearn.log_model(pipeline, \n", " artifact_path=\"models\",\n", " signature=signature,\n", " input_example=input_example,\n", " pip_requirements=req_file\n", " )\n", " mlflow.log_metrics(metrics)\n", " mlflow.log_artifact(art)\n", " mlflow.log_params(params_dict)\n", "\n", "run = mlflow.get_run(run_id) \n", "assert (run.info.status =='FINISHED')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Удаление runs, experiments\n", "\n", "Использовать осторожно" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "experiment_id = mlflow.get_experiment_by_name(EXPERIMENT_NAME).experiment_id\n", "#mlflow.delete_experiment(experiment_id)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "mlflow.search_runs(\n", " #experiment_ids=[experiment_id],\n", " experiment_names=[EXPERIMENT_NAME],\n", " # filter_string='status = \"FAILED\"'\n", " #filter_string='metrics.mae > 1'\n", " \n", ")" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [], "source": [ "\n", "#mlflow.delete_run('74d2a7a40c07413c9cf65df841164356')" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Автологирование\n", "После включения будет срабатывать на каждом обучении модели (на методе fit()).\n", "\n", "Есть плюсы, есть и минусы. Предлагается сделать прогон и сравнить с результатами вручную " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "mlflow.sklearn.autolog()\n", "\n", "with mlflow.start_run(run_name='auto', experiment_id=experiment_id) as run:\n", " pipeline.fit(X_train, y_train)\n" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "# Отключаем автологирование\n", "mlflow.sklearn.autolog(disable=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Model #2\n", "Обучим вторую \"маленькую\" модель\n" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [], "source": [ "regressor2 = RandomForestRegressor(n_estimators=10, max_depth=6)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pipeline = Pipeline(steps=[('preprocessor', preprocessor), \n", " ('model', regressor2)])\n", "\n", "pipeline.fit(X_train, y_train)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "predictions = pipeline.predict(X_test) \n", "metrics = {}\n", "metrics[\"mae\"] = mean_absolute_error(y_test, predictions) \n", "metrics[\"mape\"] = mean_absolute_percentage_error(y_test, predictions)\n", "metrics[\"mse\"] = mean_squared_error(y_test, predictions)\n", "\n", "metrics" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# !!! Проверить название прогона а также все логируемые параметры и артефакты, что они соответствуют второй \"маленькой\" модели. \n", "\n", "\n", "RUN_NAME = 'smaller_model'\n", "\n", "experiment_id = mlflow.get_experiment_by_name(EXPERIMENT_NAME).experiment_id\n", "\n", "with mlflow.start_run(run_name=RUN_NAME, experiment_id=experiment_id) as run:\n", " # получаем уникальный идентификатор запуска эксперимента\n", " run_id = run.info.run_id \n", " mlflow.sklearn.log_model(pipeline, \n", " artifact_path=\"models\",\n", " signature=signature,\n", " input_example=input_example,\n", " pip_requirements=req_file\n", " )\n", " mlflow.log_metrics(metrics)\n", " mlflow.log_artifact(art)\n", " mlflow.log_params(pipeline.get_params())\n", "\n", "run = mlflow.get_run(run_id) \n", "assert (run.info.status =='FINISHED')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# No model\n", "# Логировать можно только артефакты, без модели. Например, залогироавть графики после этапа EDA\n", "\n", "RUN_NAME = 'no_model'\n", "experiment_id = mlflow.get_experiment_by_name(EXPERIMENT_NAME).experiment_id\n", "\n", "with mlflow.start_run(run_name=RUN_NAME, experiment_id=experiment_id) as run:\n", " run_id = run.info.run_id \n", " mlflow.log_artifact(art)\n", "\n", "\n", "run = mlflow.get_run(run_id) \n", "assert (run.info.status =='FINISHED')\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "run_id = '06fa7ec1f1b74aedb3509c88dc4ee1c0' # Указываем run id\n", "mlflow.register_model(f\"runs:/{run_id}/models\", REGISTRY_MODEL_NAME)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Можно регистрировать сразу при создании прогона\n", "\n", "experiment_id = mlflow.get_experiment_by_name(EXPERIMENT_NAME).experiment_id\n", "\n", "with mlflow.start_run(run_name='register_at_run', experiment_id=experiment_id) as run:\n", " # получаем уникальный идентификатор запуска эксперимента\n", " run_id = run.info.run_id \n", " mlflow.sklearn.log_model(pipeline, \n", " artifact_path=\"models\",\n", " signature=signature,\n", " input_example=input_example,\n", " pip_requirements=req_file,\n", " registered_model_name = REGISTRY_MODEL_NAME # Указываем для какой модели регистрируем\n", " )\n", " mlflow.log_metrics(metrics)\n", " mlflow.log_artifact(art)\n", " mlflow.log_params(pipeline.get_params())\n", "\n", "run = mlflow.get_run(run_id) \n", "assert (run.info.status =='FINISHED')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Можно найти зарегистрированные модели\n", "model_reg = mlflow.search_registered_models()\n", "model_reg[0]" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [], "source": [ "\n", "model_name = REGISTRY_MODEL_NAME\n", "model_version = 1\n", "\n", "model_loaded = mlflow.sklearn.load_model(model_uri=f\"models:/{model_name}/{model_version}\")\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "model_loaded.predict(X_test.iloc[0:1])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "y_test.iloc[0]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Feature engineering" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Sklearn" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [], "source": [ "from sklearn.preprocessing import QuantileTransformer, SplineTransformer, PolynomialFeatures, MinMaxScaler" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [], "source": [ "X_train_sklearn = X_train.copy()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### PolynomialFeatures\n", "Создает полином степени `degree` из указанных признаков\n" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [], "source": [ "pf = PolynomialFeatures(degree=2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X_train_sklearn" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pf.fit_transform(X_train_sklearn[['area','kitchen_area']])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### SplineTransformer\n", "Cоздаёт новую матрицу признаков, состоящую из сплайнов порядка degree. Количество сгенерированных сплайнов равно `n_splines=n_knots + degree - 1` для каждого признака, где\n", "\n", "`n_knots` определяет количество узлов (точек, в которых сопрягаются сплайны) для каждого признака. \n", "\n", "`degree` определяет порядок полинома, используемого для построения сплайнов. " ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [], "source": [ "sp = SplineTransformer(n_knots=3, degree=3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sp.fit_transform(X_train_sklearn[['area']])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### QuantileTransformer\n", "Этот метод преобразует признаки, чтобы они распределялись равномерно или нормально — так данные меньше подвергаются влиянию выбросов. Преобразование применяется к каждому признаку независимо. Идея метода такова: оценить функцию распределения признака, чтобы преобразовать исходные значения в равномерное или нормальное распределение. \n", "\n", "`output_distribution='uniform'` или\n", "`output_distribution='normal'` соответственно\n", "\n", "\n", "Пример использования: если у вас есть данные о доходах с широким диапазоном значений, квантильное преобразование сделает их более сопоставимыми и устойчивыми к выбросам." ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [], "source": [ "qt = QuantileTransformer()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "qt.fit_transform(X_train_sklearn[['area']])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Объединяем в ColumnTransformer и создаем Pipeline " ] }, { "cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [], "source": [ "pf = PolynomialFeatures(degree=2)\n", "qt = QuantileTransformer()\n", "sp = SplineTransformer(n_knots=3, degree=3)" ] }, { "cell_type": "code", "execution_count": 50, "metadata": {}, "outputs": [], "source": [ "# Значения преобразованных признаков нужно отскейлить, поэтому создаем pipeline из двух шагов - преобразование и скейлинг\n", "pf_pipeline = Pipeline(steps=[\n", " ('poly', pf),\n", " ('scale', StandardScaler())\n", "])" ] }, { "cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [], "source": [ "preprocessor_sklearn = ColumnTransformer(\n", " transformers=[\n", " ('num', s_scaler, num_features), # преобразования для числовых признаков\n", " ('cat', l_encoder, cat_features), # преобразования для категориальных признаков\n", " ('quantile', qt,num_features),\n", " ('poly', pf_pipeline, ['area', 'kitchen_area']), # В преобразования добавляем созданный ранее pipeline\n", " ('spline', sp, ['area'])\n", " ],\n", " remainder='drop',\n", " ) # Удаляем столбцы, которые не затронуты преобразования" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Посмотрим что из себя теперь представляет датафрейм" ] }, { "cell_type": "code", "execution_count": 56, "metadata": {}, "outputs": [], "source": [ "## не влезаем в float64 в полиномальном преобразовании. Использовать его нужно с умом!\n", "X_train_sklearn[['area', 'kitchen_area']] = X_train_sklearn[['area', 'kitchen_area']].astype('float128')\n", "X_train_sklearn[['area', 'kitchen_area']] = X_train_sklearn[['area', 'kitchen_area']].astype('float128')" ] }, { "cell_type": "code", "execution_count": 58, "metadata": {}, "outputs": [], "source": [ "X_train_sklearn_raw = preprocessor_sklearn.fit_transform(X_train_sklearn)\n", "X_train_sklearn = pd.DataFrame(X_train_sklearn_raw, columns=preprocessor_sklearn.get_feature_names_out())" ] }, { "cell_type": "code", "execution_count": 80, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
num__geo_latnum__geo_lonnum__levelnum__levelsnum__roomsnum__areanum__kitchen_areacat__regioncat__building_typecat__object_typequantile__geo_latquantile__geo_lonquantile__levelquantile__levelsquantile__roomsquantile__areaquantile__kitchen_areapoly__1poly__areapoly__kitchen_areapoly__area^2poly__area kitchen_areapoly__kitchen_area^2spline__area_sp_0spline__area_sp_1spline__area_sp_2spline__area_sp_3spline__area_sp_4
00.495902-0.4497420.359235-0.2147890.2534130.063735-0.18628520.01.00.00.7662570.5110280.7172170.5365370.6006010.6236240.3748750.00.063735-0.186285-0.010002-0.132188-0.0027920.1558060.6661790.1780130.0000020.0
10.1778061.433673-0.246529-0.3677180.253413-0.114293-0.18628570.01.00.00.2971420.8679990.5220220.3868870.6006010.5415420.3748750.0-0.114293-0.186285-0.017375-0.169370-0.0027920.1569210.6662750.1768030.0000010.0
.......................................................................................
410773-0.748366-0.804077-0.6503710.7027880.2534131.3654411.50183352.03.00.00.1931430.1147530.3098100.7417420.6006010.9613670.9845350.01.3654411.5018330.0684381.5701630.0086160.1478200.6651590.1870110.0000100.0
4107741.257769-1.101815-0.0446080.0910701.1759110.553789-0.14254414.01.00.00.9080360.0757250.6046050.6456460.8673670.8418420.4364360.00.553789-0.1425440.014463-0.002742-0.0026490.1527670.6658600.1813700.0000040.0
\n", "

410775 rows × 28 columns

\n", "
" ], "text/plain": [ " num__geo_lat num__geo_lon num__level num__levels num__rooms \\\n", "0 0.495902 -0.449742 0.359235 -0.214789 0.253413 \n", "1 0.177806 1.433673 -0.246529 -0.367718 0.253413 \n", "... ... ... ... ... ... \n", "410773 -0.748366 -0.804077 -0.650371 0.702788 0.253413 \n", "410774 1.257769 -1.101815 -0.044608 0.091070 1.175911 \n", "\n", " num__area num__kitchen_area cat__region cat__building_type \\\n", "0 0.063735 -0.186285 20.0 1.0 \n", "1 -0.114293 -0.186285 70.0 1.0 \n", "... ... ... ... ... \n", "410773 1.365441 1.501833 52.0 3.0 \n", "410774 0.553789 -0.142544 14.0 1.0 \n", "\n", " cat__object_type quantile__geo_lat quantile__geo_lon \\\n", "0 0.0 0.766257 0.511028 \n", "1 0.0 0.297142 0.867999 \n", "... ... ... ... \n", "410773 0.0 0.193143 0.114753 \n", "410774 0.0 0.908036 0.075725 \n", "\n", " quantile__level quantile__levels quantile__rooms quantile__area \\\n", "0 0.717217 0.536537 0.600601 0.623624 \n", "1 0.522022 0.386887 0.600601 0.541542 \n", "... ... ... ... ... \n", "410773 0.309810 0.741742 0.600601 0.961367 \n", "410774 0.604605 0.645646 0.867367 0.841842 \n", "\n", " quantile__kitchen_area poly__1 poly__area poly__kitchen_area \\\n", "0 0.374875 0.0 0.063735 -0.186285 \n", "1 0.374875 0.0 -0.114293 -0.186285 \n", "... ... ... ... ... \n", "410773 0.984535 0.0 1.365441 1.501833 \n", "410774 0.436436 0.0 0.553789 -0.142544 \n", "\n", " poly__area^2 poly__area kitchen_area poly__kitchen_area^2 \\\n", "0 -0.010002 -0.132188 -0.002792 \n", "1 -0.017375 -0.169370 -0.002792 \n", "... ... ... ... \n", "410773 0.068438 1.570163 0.008616 \n", "410774 0.014463 -0.002742 -0.002649 \n", "\n", " spline__area_sp_0 spline__area_sp_1 spline__area_sp_2 \\\n", "0 0.155806 0.666179 0.178013 \n", "1 0.156921 0.666275 0.176803 \n", "... ... ... ... \n", "410773 0.147820 0.665159 0.187011 \n", "410774 0.152767 0.665860 0.181370 \n", "\n", " spline__area_sp_3 spline__area_sp_4 \n", "0 0.000002 0.0 \n", "1 0.000001 0.0 \n", "... ... ... \n", "410773 0.000010 0.0 \n", "410774 0.000004 0.0 \n", "\n", "[410775 rows x 28 columns]" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Удобно использовать для отображения всех строк\\столбцов в DataFrame\n", "with pd.option_context('display.max_rows', 5, 'display.max_columns', None):\n", " display (X_train_sklearn)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Создаем пайплайн с препроцессингом и моделью" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pipeline_sklearn = Pipeline(steps=[\n", " ('transform', preprocessor_sklearn),\n", " ('model', regressor)\n", "])\n", "\n", "model_sklearn = pipeline_sklearn.fit(X_train, y_train)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "model_sklearn" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "predictions = model_sklearn.predict(X_test) \n", "metrics = {}\n", "metrics[\"mae\"] = mean_absolute_error(y_test, predictions) \n", "metrics[\"mape\"] = mean_absolute_percentage_error(y_test, predictions)\n", "metrics[\"mse\"] = mean_squared_error(y_test, predictions)\n", "\n", "metrics" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "experiment_id = mlflow.get_experiment_by_name(EXPERIMENT_NAME).experiment_id\n", "RUN_NAME = 'fe_sklearn'\n", "\n", "with mlflow.start_run(run_name=RUN_NAME, experiment_id=experiment_id) as run:\n", " # получаем уникальный идентификатор запуска эксперимента\n", " run_id = run.info.run_id \n", " mlflow.sklearn.log_model(model_sklearn, \n", " artifact_path=\"models\",\n", " signature=signature,\n", " input_example=input_example,\n", " pip_requirements=req_file\n", " )\n", " mlflow.log_metrics(metrics)\n", " mlflow.log_artifact(art)\n", " mlflow.log_params(model_sklearn.get_params())\n", "\n", "run = mlflow.get_run(run_id) \n", "assert (run.info.status =='FINISHED')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Autofeat" ] }, { "cell_type": "code", "execution_count": 65, "metadata": {}, "outputs": [], "source": [ "from autofeat import AutoFeatRegressor\n", "transformations = [\"1/\", \"exp\", \"log\", \"abs\", \"sqrt\", \"^2\", \"^3\", \"1+\", \"1-\", \"sin\", \"cos\", \"exp-\", \"2^\"] " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "afreg = AutoFeatRegressor(verbose=1, feateng_steps=2, max_gb=8, transformations=[\"log\", \"sqrt\"],feateng_cols=num_features)\n", "X_train_arf = afreg.fit_transform(X_train,y_train)\n", "X_train_arf" ] }, { "cell_type": "code", "execution_count": 67, "metadata": {}, "outputs": [], "source": [ "# Создаем обертку, в которой добавляем метод get_feature_names_out() для получения названий признаков\n", "import numpy as np\n", "\n", "class AutoFeatWrapper():\n", " def __init__(self, feateng_cols, feateng_steps=1, max_gb=16, transformations=[\"1/\", \"exp\", \"log\"], n_jobs=-1, verbose=1):\n", " self.feateng_cols = feateng_cols\n", " self.feateng_steps = feateng_steps\n", " self.max_gb = max_gb\n", " self.transformations = transformations\n", " self.n_jobs = n_jobs\n", " self.afreg = AutoFeatRegressor(feateng_cols=self.feateng_cols,\n", " feateng_steps=self.feateng_steps,\n", " max_gb=self.max_gb,\n", " transformations=self.transformations,\n", " n_jobs=self.n_jobs)\n", " \n", " def fit(self, X, y=None):\n", " self.afreg.fit(X, y)\n", " return self\n", " \n", " def transform(self, X):\n", " return self.afreg.transform(X)\n", " \n", " def get_feature_names_out(self, input_features=None):\n", " # Преобразуем данные и возвращаем имена фичей из DataFrame\n", " transformed_X = self.afreg.transform(pd.DataFrame(np.zeros((1, len(self.feateng_cols))), columns=self.feateng_cols))\n", " return transformed_X.columns.tolist()" ] }, { "cell_type": "code", "execution_count": 69, "metadata": {}, "outputs": [], "source": [ "afreg_pipeline = Pipeline(steps=[\n", " ('autofeat', AutoFeatWrapper( feateng_steps=2, max_gb=16, transformations=[\"log\", \"sqrt\"],feateng_cols=num_features)),\n", " ('scaler', StandardScaler()),\n", "])" ] }, { "cell_type": "code", "execution_count": 70, "metadata": {}, "outputs": [], "source": [ "preprocessor_afr = ColumnTransformer(\n", " transformers=[\n", " ('num', s_scaler, num_features), # преобразования для числовых признаков\n", " ('cat', l_encoder, cat_features), # преобразования для категориальных признаков\n", " ('afr', afreg_pipeline, num_features), # преобразования autofeat\n", " ],\n", " remainder='drop', # Удаляем столбцы, которые не затронуты преобразованиями\n", " ) " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X_train_afr_raw = preprocessor_afr.fit_transform(X_train,y_train)\n", "X_train_afr = pd.DataFrame(X_train_afr_raw, columns=preprocessor_afr.get_feature_names_out())" ] }, { "cell_type": "code", "execution_count": 81, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
num__geo_latnum__geo_lonnum__levelnum__levelsnum__roomsnum__areanum__kitchen_areacat__regioncat__building_typecat__object_typeafr__geo_latafr__geo_lonafr__levelafr__levelsafr__roomsafr__areaafr__kitchen_areaafr__area*roomsafr__area*geo_lonafr__levels*roomsafr__area*kitchen_areaafr__sqrt(area)*geo_latafr__sqrt(area)*log(level)afr__kitchen_area*log(level)afr__sqrt(area)*kitchen_areaafr__geo_lon*log(kitchen_area)afr__sqrt(area)*sqrt(kitchen_area)afr__sqrt(geo_lon)*sqrt(kitchen_area)afr__log(area)afr__rooms*log(level)afr__kitchen_area*roomsafr__kitchen_area*levelsafr__sqrt(geo_lon)*sqrt(level)afr__area**(3/2)afr__geo_lat*log(kitchen_area)afr__geo_lat*log(geo_lon)
00.495902-0.4497420.359235-0.2147890.2534130.063735-0.18628520.01.00.00.495902-0.4497420.359235-0.2147890.2534130.063735-0.1862850.006208-0.1951290.060916-0.1321880.3731510.6880760.044178-0.211335-0.481294-0.153548-0.4908050.3078350.690329-0.132529-0.3528340.323880-0.008748-0.0315290.068167
10.1778061.433673-0.246529-0.3677180.253413-0.114293-0.18628570.01.00.00.1778061.433673-0.246529-0.3677180.253413-0.114293-0.186285-0.0834020.655053-0.054279-0.1693700.0051140.071369-0.173647-0.2527751.191304-0.2672680.6157980.0319070.282625-0.132529-0.4186430.552794-0.056540-0.1438291.129118
...............................................................................................................
410773-0.748366-0.804077-0.6503710.7027880.2534131.3654411.50183352.03.00.0-0.748366-0.804077-0.6503710.7027880.2534131.3654411.5018330.6614270.3751990.7520881.5701631.274445-0.0025210.7455072.3822580.0715992.8288901.4312721.729715-0.1604911.5814362.432437-0.8431500.4114751.671069-1.052343
4107741.257769-1.101815-0.0446080.0910701.1759110.553789-0.14254414.01.00.01.257769-1.101815-0.0446080.0910701.1759110.553789-0.1425440.807887-0.3300700.982478-0.0027421.3389960.635065-0.040302-0.055435-1.0255880.202136-0.9160540.9406241.2179100.311575-0.174762-0.4153590.1356170.359680-0.246790
\n", "

410775 rows × 36 columns

\n", "
" ], "text/plain": [ " num__geo_lat num__geo_lon num__level num__levels num__rooms \\\n", "0 0.495902 -0.449742 0.359235 -0.214789 0.253413 \n", "1 0.177806 1.433673 -0.246529 -0.367718 0.253413 \n", "... ... ... ... ... ... \n", "410773 -0.748366 -0.804077 -0.650371 0.702788 0.253413 \n", "410774 1.257769 -1.101815 -0.044608 0.091070 1.175911 \n", "\n", " num__area num__kitchen_area cat__region cat__building_type \\\n", "0 0.063735 -0.186285 20.0 1.0 \n", "1 -0.114293 -0.186285 70.0 1.0 \n", "... ... ... ... ... \n", "410773 1.365441 1.501833 52.0 3.0 \n", "410774 0.553789 -0.142544 14.0 1.0 \n", "\n", " cat__object_type afr__geo_lat afr__geo_lon afr__level afr__levels \\\n", "0 0.0 0.495902 -0.449742 0.359235 -0.214789 \n", "1 0.0 0.177806 1.433673 -0.246529 -0.367718 \n", "... ... ... ... ... ... \n", "410773 0.0 -0.748366 -0.804077 -0.650371 0.702788 \n", "410774 0.0 1.257769 -1.101815 -0.044608 0.091070 \n", "\n", " afr__rooms afr__area afr__kitchen_area afr__area*rooms \\\n", "0 0.253413 0.063735 -0.186285 0.006208 \n", "1 0.253413 -0.114293 -0.186285 -0.083402 \n", "... ... ... ... ... \n", "410773 0.253413 1.365441 1.501833 0.661427 \n", "410774 1.175911 0.553789 -0.142544 0.807887 \n", "\n", " afr__area*geo_lon afr__levels*rooms afr__area*kitchen_area \\\n", "0 -0.195129 0.060916 -0.132188 \n", "1 0.655053 -0.054279 -0.169370 \n", "... ... ... ... \n", "410773 0.375199 0.752088 1.570163 \n", "410774 -0.330070 0.982478 -0.002742 \n", "\n", " afr__sqrt(area)*geo_lat afr__sqrt(area)*log(level) \\\n", "0 0.373151 0.688076 \n", "1 0.005114 0.071369 \n", "... ... ... \n", "410773 1.274445 -0.002521 \n", "410774 1.338996 0.635065 \n", "\n", " afr__kitchen_area*log(level) afr__sqrt(area)*kitchen_area \\\n", "0 0.044178 -0.211335 \n", "1 -0.173647 -0.252775 \n", "... ... ... \n", "410773 0.745507 2.382258 \n", "410774 -0.040302 -0.055435 \n", "\n", " afr__geo_lon*log(kitchen_area) afr__sqrt(area)*sqrt(kitchen_area) \\\n", "0 -0.481294 -0.153548 \n", "1 1.191304 -0.267268 \n", "... ... ... \n", "410773 0.071599 2.828890 \n", "410774 -1.025588 0.202136 \n", "\n", " afr__sqrt(geo_lon)*sqrt(kitchen_area) afr__log(area) \\\n", "0 -0.490805 0.307835 \n", "1 0.615798 0.031907 \n", "... ... ... \n", "410773 1.431272 1.729715 \n", "410774 -0.916054 0.940624 \n", "\n", " afr__rooms*log(level) afr__kitchen_area*rooms \\\n", "0 0.690329 -0.132529 \n", "1 0.282625 -0.132529 \n", "... ... ... \n", "410773 -0.160491 1.581436 \n", "410774 1.217910 0.311575 \n", "\n", " afr__kitchen_area*levels afr__sqrt(geo_lon)*sqrt(level) \\\n", "0 -0.352834 0.323880 \n", "1 -0.418643 0.552794 \n", "... ... ... \n", "410773 2.432437 -0.843150 \n", "410774 -0.174762 -0.415359 \n", "\n", " afr__area**(3/2) afr__geo_lat*log(kitchen_area) \\\n", "0 -0.008748 -0.031529 \n", "1 -0.056540 -0.143829 \n", "... ... ... \n", "410773 0.411475 1.671069 \n", "410774 0.135617 0.359680 \n", "\n", " afr__geo_lat*log(geo_lon) \n", "0 0.068167 \n", "1 1.129118 \n", "... ... \n", "410773 -1.052343 \n", "410774 -0.246790 \n", "\n", "[410775 rows x 36 columns]" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "with pd.option_context('display.max_rows', 5, 'display.max_columns', None):\n", " display (X_train_afr)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pipeline_afr = Pipeline(steps=[('preprocessor', preprocessor_afr), \n", " ('model', regressor)])\n", "\n", "pipeline_afr.fit(X_train, y_train)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "predictions = pipeline_afr.predict(X_test) \n", "\n", "metrics = {}\n", "metrics[\"mae\"] = mean_absolute_error(y_test, predictions) \n", "metrics[\"mape\"] = mean_absolute_percentage_error(y_test, predictions)\n", "metrics[\"mse\"] = mean_squared_error(y_test, predictions)\n", "\n", "metrics" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\n", "experiment_id = mlflow.get_experiment_by_name(EXPERIMENT_NAME).experiment_id\n", "\n", "with mlflow.start_run(run_name='autofeat', experiment_id=experiment_id) as run:\n", " # получаем уникальный идентификатор запуска эксперимента\n", " run_id = run.info.run_id \n", " mlflow.sklearn.log_model(pipeline_afr, \n", " artifact_path=\"models\",\n", " signature=signature,\n", " input_example=input_example,\n", " pip_requirements=req_file\n", " )\n", " mlflow.log_metrics(metrics)\n", " mlflow.log_artifact(art)\n", " mlflow.log_params(pipeline_afr.get_params())\n", "\n", "run = mlflow.get_run(run_id) \n", "assert (run.info.status =='FINISHED')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# FEATURE SELECTION\n", "## RFE\n", "### Используем autofeat признаки\n", "Поскольку autofeat дает разные совокупности сгенерированных признаков, мы можем добавить выбор информативных только как шаг пайплайна " ] }, { "cell_type": "code", "execution_count": 294, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
num__geo_latnum__geo_lonnum__levelnum__levelsnum__roomsnum__areanum__kitchen_areacat__regioncat__building_typecat__object_type...afr__sqrt(area)*sqrt(kitchen_area)afr__sqrt(geo_lon)*sqrt(kitchen_area)afr__log(area)afr__rooms*log(level)afr__kitchen_area*roomsafr__kitchen_area*levelsafr__sqrt(geo_lon)*sqrt(level)afr__area**(3/2)afr__geo_lat*log(kitchen_area)afr__geo_lat*log(geo_lon)
00.495902-0.4497420.359235-0.2147890.2534130.063735-0.18628520.01.00.0...-0.153548-0.4908050.3078350.690329-0.132529-0.3528340.323880-0.008748-0.0315290.068167
10.1778061.433673-0.246529-0.3677180.253413-0.114293-0.18628570.01.00.0...-0.2672680.6157980.0319070.282625-0.132529-0.4186430.552794-0.056540-0.1438291.129118
20.4405480.047222-0.448450-0.367718-0.669085-0.456947-0.14254415.03.01.0...-0.454880-0.067183-0.603122-0.512211-0.487813-0.383803-0.243092-0.1408000.0634640.460495
3-1.588818-0.722477-0.246529-0.9794360.253413-0.181292-0.14254418.01.00.0...-0.254514-0.607607-0.0803040.282625-0.088119-0.662523-0.369355-0.073838-0.672113-1.481033
41.4936621.1258190.1573130.5498580.2534130.615045-0.01132210.02.00.0...0.4386000.8913831.0096120.5744970.0451120.2084780.9459810.1549020.7808551.923382
..................................................................
4107700.5920110.3550140.5611561.0086460.253413-0.079836-0.09265354.02.00.0...-0.1200350.2375800.0877250.792500-0.0374630.3227970.974381-0.0474960.2430180.789871
4107710.2404780.392697-0.650371-0.9794360.253413-0.334434-0.40498945.03.00.0...-0.716150-0.510766-0.357277-0.160491-0.354582-0.778657-0.406361-0.111897-0.8081570.574534
410772-1.936771-0.6888300.3592350.855717-0.669085-0.456947-0.14254418.00.01.0...-0.454880-0.581851-0.603122-0.211576-0.4878130.1736380.170166-0.140800-0.798234-1.663294
410773-0.748366-0.804077-0.6503710.7027880.2534131.3654411.50183352.03.00.0...2.8288901.4312721.729715-0.1604911.5814362.432437-0.8431500.4114751.671069-1.052343
4107741.257769-1.101815-0.0446080.0910701.1759110.553789-0.14254414.01.00.0...0.202136-0.9160540.9406241.2179100.311575-0.174762-0.4153590.1356170.359680-0.246790
\n", "

410775 rows × 36 columns

\n", "
" ], "text/plain": [ " num__geo_lat num__geo_lon num__level num__levels num__rooms \\\n", "0 0.495902 -0.449742 0.359235 -0.214789 0.253413 \n", "1 0.177806 1.433673 -0.246529 -0.367718 0.253413 \n", "2 0.440548 0.047222 -0.448450 -0.367718 -0.669085 \n", "3 -1.588818 -0.722477 -0.246529 -0.979436 0.253413 \n", "4 1.493662 1.125819 0.157313 0.549858 0.253413 \n", "... ... ... ... ... ... \n", "410770 0.592011 0.355014 0.561156 1.008646 0.253413 \n", "410771 0.240478 0.392697 -0.650371 -0.979436 0.253413 \n", "410772 -1.936771 -0.688830 0.359235 0.855717 -0.669085 \n", "410773 -0.748366 -0.804077 -0.650371 0.702788 0.253413 \n", "410774 1.257769 -1.101815 -0.044608 0.091070 1.175911 \n", "\n", " num__area num__kitchen_area cat__region cat__building_type \\\n", "0 0.063735 -0.186285 20.0 1.0 \n", "1 -0.114293 -0.186285 70.0 1.0 \n", "2 -0.456947 -0.142544 15.0 3.0 \n", "3 -0.181292 -0.142544 18.0 1.0 \n", "4 0.615045 -0.011322 10.0 2.0 \n", "... ... ... ... ... \n", "410770 -0.079836 -0.092653 54.0 2.0 \n", "410771 -0.334434 -0.404989 45.0 3.0 \n", "410772 -0.456947 -0.142544 18.0 0.0 \n", "410773 1.365441 1.501833 52.0 3.0 \n", "410774 0.553789 -0.142544 14.0 1.0 \n", "\n", " cat__object_type ... afr__sqrt(area)*sqrt(kitchen_area) \\\n", "0 0.0 ... -0.153548 \n", "1 0.0 ... -0.267268 \n", "2 1.0 ... -0.454880 \n", "3 0.0 ... -0.254514 \n", "4 0.0 ... 0.438600 \n", "... ... ... ... \n", "410770 0.0 ... -0.120035 \n", "410771 0.0 ... -0.716150 \n", "410772 1.0 ... -0.454880 \n", "410773 0.0 ... 2.828890 \n", "410774 0.0 ... 0.202136 \n", "\n", " afr__sqrt(geo_lon)*sqrt(kitchen_area) afr__log(area) \\\n", "0 -0.490805 0.307835 \n", "1 0.615798 0.031907 \n", "2 -0.067183 -0.603122 \n", "3 -0.607607 -0.080304 \n", "4 0.891383 1.009612 \n", "... ... ... \n", "410770 0.237580 0.087725 \n", "410771 -0.510766 -0.357277 \n", "410772 -0.581851 -0.603122 \n", "410773 1.431272 1.729715 \n", "410774 -0.916054 0.940624 \n", "\n", " afr__rooms*log(level) afr__kitchen_area*rooms \\\n", "0 0.690329 -0.132529 \n", "1 0.282625 -0.132529 \n", "2 -0.512211 -0.487813 \n", "3 0.282625 -0.088119 \n", "4 0.574497 0.045112 \n", "... ... ... \n", "410770 0.792500 -0.037463 \n", "410771 -0.160491 -0.354582 \n", "410772 -0.211576 -0.487813 \n", "410773 -0.160491 1.581436 \n", "410774 1.217910 0.311575 \n", "\n", " afr__kitchen_area*levels afr__sqrt(geo_lon)*sqrt(level) \\\n", "0 -0.352834 0.323880 \n", "1 -0.418643 0.552794 \n", "2 -0.383803 -0.243092 \n", "3 -0.662523 -0.369355 \n", "4 0.208478 0.945981 \n", "... ... ... \n", "410770 0.322797 0.974381 \n", "410771 -0.778657 -0.406361 \n", "410772 0.173638 0.170166 \n", "410773 2.432437 -0.843150 \n", "410774 -0.174762 -0.415359 \n", "\n", " afr__area**(3/2) afr__geo_lat*log(kitchen_area) \\\n", "0 -0.008748 -0.031529 \n", "1 -0.056540 -0.143829 \n", "2 -0.140800 0.063464 \n", "3 -0.073838 -0.672113 \n", "4 0.154902 0.780855 \n", "... ... ... \n", "410770 -0.047496 0.243018 \n", "410771 -0.111897 -0.808157 \n", "410772 -0.140800 -0.798234 \n", "410773 0.411475 1.671069 \n", "410774 0.135617 0.359680 \n", "\n", " afr__geo_lat*log(geo_lon) \n", "0 0.068167 \n", "1 1.129118 \n", "2 0.460495 \n", "3 -1.481033 \n", "4 1.923382 \n", "... ... \n", "410770 0.789871 \n", "410771 0.574534 \n", "410772 -1.663294 \n", "410773 -1.052343 \n", "410774 -0.246790 \n", "\n", "[410775 rows x 36 columns]" ] }, "execution_count": 294, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from sklearn.feature_selection import RFE\n", "X_train_afr" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\n", "rfe_selector = RFE(estimator=regressor, n_features_to_select=12, step = 0.2) #drop 20% of features each iteration\n", "X_train_rfe = rfe_selector.fit_transform(X_train_afr,y_train)" ] }, { "cell_type": "code", "execution_count": 297, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
num__geo_latnum__geo_lonafr__geo_lonafr__area*kitchen_areaafr__sqrt(area)*geo_latafr__sqrt(area)*log(level)afr__kitchen_area*log(level)afr__sqrt(area)*sqrt(kitchen_area)afr__rooms*log(level)afr__kitchen_area*roomsafr__sqrt(geo_lon)*sqrt(level)afr__geo_lat*log(geo_lon)
00.495902-0.449742-0.449742-0.1321880.3731510.6880760.044178-0.1535480.690329-0.1325290.3238800.068167
10.1778061.4336731.433673-0.1693700.0051140.071369-0.173647-0.2672680.282625-0.1325290.5527941.129118
20.4405480.0472220.047222-0.226261-0.425530-0.335537-0.239271-0.454880-0.512211-0.487813-0.2430920.460495
3-1.588818-0.722477-0.722477-0.165302-0.7232250.034116-0.129771-0.2545140.282625-0.088119-0.369355-1.481033
41.4936621.1258191.1258190.0943421.5222650.8627730.1944900.4386000.5744970.0451120.9459811.923382
.......................................
4107700.5920110.3550140.355014-0.1208410.2069260.7144990.226990-0.1200350.792500-0.0374630.9743810.789871
4107710.2404780.3926970.392697-0.296252-0.297209-0.551021-0.560144-0.716150-0.160491-0.354582-0.4063610.574534
410772-1.936771-0.688830-0.688830-0.226261-1.1927060.3062800.100868-0.454880-0.211576-0.4878130.170166-1.663294
410773-0.748366-0.804077-0.8040771.5701631.274445-0.0025210.7455072.828890-0.1604911.581436-0.843150-1.052343
4107741.257769-1.101815-1.101815-0.0027421.3389960.635065-0.0403020.2021361.2179100.311575-0.415359-0.246790
\n", "

410775 rows × 12 columns

\n", "
" ], "text/plain": [ " num__geo_lat num__geo_lon afr__geo_lon afr__area*kitchen_area \\\n", "0 0.495902 -0.449742 -0.449742 -0.132188 \n", "1 0.177806 1.433673 1.433673 -0.169370 \n", "2 0.440548 0.047222 0.047222 -0.226261 \n", "3 -1.588818 -0.722477 -0.722477 -0.165302 \n", "4 1.493662 1.125819 1.125819 0.094342 \n", "... ... ... ... ... \n", "410770 0.592011 0.355014 0.355014 -0.120841 \n", "410771 0.240478 0.392697 0.392697 -0.296252 \n", "410772 -1.936771 -0.688830 -0.688830 -0.226261 \n", "410773 -0.748366 -0.804077 -0.804077 1.570163 \n", "410774 1.257769 -1.101815 -1.101815 -0.002742 \n", "\n", " afr__sqrt(area)*geo_lat afr__sqrt(area)*log(level) \\\n", "0 0.373151 0.688076 \n", "1 0.005114 0.071369 \n", "2 -0.425530 -0.335537 \n", "3 -0.723225 0.034116 \n", "4 1.522265 0.862773 \n", "... ... ... \n", "410770 0.206926 0.714499 \n", "410771 -0.297209 -0.551021 \n", "410772 -1.192706 0.306280 \n", "410773 1.274445 -0.002521 \n", "410774 1.338996 0.635065 \n", "\n", " afr__kitchen_area*log(level) afr__sqrt(area)*sqrt(kitchen_area) \\\n", "0 0.044178 -0.153548 \n", "1 -0.173647 -0.267268 \n", "2 -0.239271 -0.454880 \n", "3 -0.129771 -0.254514 \n", "4 0.194490 0.438600 \n", "... ... ... \n", "410770 0.226990 -0.120035 \n", "410771 -0.560144 -0.716150 \n", "410772 0.100868 -0.454880 \n", "410773 0.745507 2.828890 \n", "410774 -0.040302 0.202136 \n", "\n", " afr__rooms*log(level) afr__kitchen_area*rooms \\\n", "0 0.690329 -0.132529 \n", "1 0.282625 -0.132529 \n", "2 -0.512211 -0.487813 \n", "3 0.282625 -0.088119 \n", "4 0.574497 0.045112 \n", "... ... ... \n", "410770 0.792500 -0.037463 \n", "410771 -0.160491 -0.354582 \n", "410772 -0.211576 -0.487813 \n", "410773 -0.160491 1.581436 \n", "410774 1.217910 0.311575 \n", "\n", " afr__sqrt(geo_lon)*sqrt(level) afr__geo_lat*log(geo_lon) \n", "0 0.323880 0.068167 \n", "1 0.552794 1.129118 \n", "2 -0.243092 0.460495 \n", "3 -0.369355 -1.481033 \n", "4 0.945981 1.923382 \n", "... ... ... \n", "410770 0.974381 0.789871 \n", "410771 -0.406361 0.574534 \n", "410772 0.170166 -1.663294 \n", "410773 -0.843150 -1.052343 \n", "410774 -0.415359 -0.246790 \n", "\n", "[410775 rows x 12 columns]" ] }, "execution_count": 297, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X_train_afr_rfe = pd.DataFrame(X_train_rfe, columns=rfe_selector.get_feature_names_out())\n", "X_train_afr_rfe" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "rfe_pipeline = Pipeline(steps=[\n", " ('preprocessor', preprocessor_afr), \n", " ('rfe_extractor', RFE(estimator=regressor, n_features_to_select=12, step = 0.2)),\n", " ('model', regressor)\n", "])\n", "\n", "rfe_pipeline.fit(X_train, y_train)" ] }, { "cell_type": "code", "execution_count": 301, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'mae': 1431925.3203264712,\n", " 'mape': 1.239752923791043e+18,\n", " 'mse': 261947924998018.2}" ] }, "execution_count": 301, "metadata": {}, "output_type": "execute_result" } ], "source": [ "predictions_rfe = rfe_pipeline.predict(X_test)\n", "\n", "metrics = {}\n", "metrics[\"mae\"] = mean_absolute_error(y_test, predictions_rfe) \n", "metrics[\"mape\"] = mean_absolute_percentage_error(y_test, predictions_rfe)\n", "metrics[\"mse\"] = mean_squared_error(y_test, predictions_rfe)\n", "\n", "metrics" ] }, { "cell_type": "code", "execution_count": 302, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Downloading artifacts: 100%|██████████| 7/7 [00:00<00:00, 40.15it/s]\n", "2024/10/17 14:26:50 INFO mlflow.tracking._tracking_service.client: 🏃 View run rfe_feature_selection at: http://127.0.0.1:5000/#/experiments/1/runs/96f0bbcd6d88466abcf38f3b53f06ff1.\n", "2024/10/17 14:26:50 INFO mlflow.tracking._tracking_service.client: 🧪 View experiment at: http://127.0.0.1:5000/#/experiments/1.\n" ] } ], "source": [ "experiment_id = mlflow.get_experiment_by_name(EXPERIMENT_NAME).experiment_id\n", "RUN_NAME = 'rfe_feature_selection'\n", "\n", "with mlflow.start_run(run_name=RUN_NAME, experiment_id=experiment_id) as run:\n", " # получаем уникальный идентификатор запуска эксперимента\n", " run_id = run.info.run_id \n", " mlflow.sklearn.log_model(rfe_pipeline, \n", " artifact_path=\"models\",\n", " signature=signature,\n", " input_example=input_example,\n", " pip_requirements=req_file\n", " )\n", " mlflow.log_metrics(metrics)\n", " mlflow.log_artifact(art)\n", " mlflow.log_params(model_sklearn.get_params())\n", "\n", "run = mlflow.get_run(run_id) \n", "assert (run.info.status =='FINISHED')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Используем sklearn признаки\n", "Тут мы можем отобрать признаки один раз на обучении, а далее в качестве шага пайплайна использовать написанный класс ColumnExtractor для выбора нуных столбцов" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\n", "rfe_skl_selector = RFE(estimator=regressor, n_features_to_select=12, step = 0.2) #drop 20% of features each iteration\n", "X_train_skl_rfe = rfe_skl_selector.fit_transform(X_train_sklearn,y_train)" ] }, { "cell_type": "code", "execution_count": 305, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
num__geo_latnum__geo_lonnum__levelnum__roomsnum__kitchen_areacat__regionquantile__geo_latquantile__geo_lonquantile__levelpoly__area kitchen_areaspline__area_sp_0spline__area_sp_2
00.495902-0.4497420.3592350.253413-0.18628520.00.7662570.5110280.717217-0.1321880.1558060.178013
10.1778061.433673-0.2465290.253413-0.18628570.00.2971420.8679990.522022-0.1693700.1569210.176803
20.4405480.047222-0.448450-0.669085-0.14254415.00.7323300.6299840.417417-0.2262610.1590800.174488
3-1.588818-0.722477-0.2465290.253413-0.14254418.00.1487890.2952620.522022-0.1653020.1573410.176349
41.4936621.1258190.1573130.253413-0.01132210.00.9859370.7583630.6626630.0943420.1523900.181792
.......................................
4107700.5920110.3550140.5611560.253413-0.09265354.00.7883930.6867280.771271-0.1208410.1567050.177037
4107710.2404780.392697-0.6503710.253413-0.40498945.00.4940620.7172400.309810-0.2962520.1583060.175314
410772-1.936771-0.6888300.359235-0.669085-0.14254418.00.1313520.3276130.717217-0.2262610.1590800.174488
410773-0.748366-0.804077-0.6503710.2534131.50183352.00.1931430.1147530.3098101.5701630.1478200.187011
4107741.257769-1.101815-0.0446081.175911-0.14254414.00.9080360.0757250.604605-0.0027420.1527670.181370
\n", "

410775 rows × 12 columns

\n", "
" ], "text/plain": [ " num__geo_lat num__geo_lon num__level num__rooms num__kitchen_area \\\n", "0 0.495902 -0.449742 0.359235 0.253413 -0.186285 \n", "1 0.177806 1.433673 -0.246529 0.253413 -0.186285 \n", "2 0.440548 0.047222 -0.448450 -0.669085 -0.142544 \n", "3 -1.588818 -0.722477 -0.246529 0.253413 -0.142544 \n", "4 1.493662 1.125819 0.157313 0.253413 -0.011322 \n", "... ... ... ... ... ... \n", "410770 0.592011 0.355014 0.561156 0.253413 -0.092653 \n", "410771 0.240478 0.392697 -0.650371 0.253413 -0.404989 \n", "410772 -1.936771 -0.688830 0.359235 -0.669085 -0.142544 \n", "410773 -0.748366 -0.804077 -0.650371 0.253413 1.501833 \n", "410774 1.257769 -1.101815 -0.044608 1.175911 -0.142544 \n", "\n", " cat__region quantile__geo_lat quantile__geo_lon quantile__level \\\n", "0 20.0 0.766257 0.511028 0.717217 \n", "1 70.0 0.297142 0.867999 0.522022 \n", "2 15.0 0.732330 0.629984 0.417417 \n", "3 18.0 0.148789 0.295262 0.522022 \n", "4 10.0 0.985937 0.758363 0.662663 \n", "... ... ... ... ... \n", "410770 54.0 0.788393 0.686728 0.771271 \n", "410771 45.0 0.494062 0.717240 0.309810 \n", "410772 18.0 0.131352 0.327613 0.717217 \n", "410773 52.0 0.193143 0.114753 0.309810 \n", "410774 14.0 0.908036 0.075725 0.604605 \n", "\n", " poly__area kitchen_area spline__area_sp_0 spline__area_sp_2 \n", "0 -0.132188 0.155806 0.178013 \n", "1 -0.169370 0.156921 0.176803 \n", "2 -0.226261 0.159080 0.174488 \n", "3 -0.165302 0.157341 0.176349 \n", "4 0.094342 0.152390 0.181792 \n", "... ... ... ... \n", "410770 -0.120841 0.156705 0.177037 \n", "410771 -0.296252 0.158306 0.175314 \n", "410772 -0.226261 0.159080 0.174488 \n", "410773 1.570163 0.147820 0.187011 \n", "410774 -0.002742 0.152767 0.181370 \n", "\n", "[410775 rows x 12 columns]" ] }, "execution_count": 305, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X_train_skl_rfe = pd.DataFrame(X_train_skl_rfe, columns=rfe_skl_selector.get_feature_names_out())\n", "X_train_skl_rfe" ] }, { "cell_type": "code", "execution_count": 306, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['num__geo_lat',\n", " 'num__geo_lon',\n", " 'num__level',\n", " 'num__rooms',\n", " 'num__kitchen_area',\n", " 'cat__region',\n", " 'quantile__geo_lat',\n", " 'quantile__geo_lon',\n", " 'quantile__level',\n", " 'poly__area kitchen_area',\n", " 'spline__area_sp_0',\n", " 'spline__area_sp_2']" ] }, "execution_count": 306, "metadata": {}, "output_type": "execute_result" } ], "source": [ "rfe_cols = X_train_skl_rfe.columns.tolist()\n", "rfe_cols" ] }, { "cell_type": "code", "execution_count": 307, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ True, True, True, False, True, False, True, True, False,\n", " False, True, True, True, False, False, False, False, False,\n", " False, False, False, True, False, True, False, True, False,\n", " False])" ] }, "execution_count": 307, "metadata": {}, "output_type": "execute_result" } ], "source": [ "rfe_idx = rfe_skl_selector.support_\n", "rfe_idx" ] }, { "cell_type": "code", "execution_count": 316, "metadata": {}, "outputs": [], "source": [ "# Отбираемые столбцы нужно залогировать, иначе мы потеряем информацию о том, какие призныки выбраны\n", "with open('rfe_skl_idx.txt', 'w+') as f:\n", " f.write(str(rfe_idx))\n", "with open('rfe_skl_cols.txt', 'w+') as f:\n", " f.write(str(rfe_cols))" ] }, { "cell_type": "code", "execution_count": 309, "metadata": {}, "outputs": [], "source": [ "class ColumnExtractor(object):\n", "\n", " def __init__(self, cols):\n", " self.cols = cols\n", "\n", " def transform(self, X):\n", " return X[:,self.cols]\n", " \n", " def fit(self, X, y=None):\n", " return self\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "rfe_skl_pipeline = Pipeline(steps=[\n", " ('preprocessor', preprocessor_sklearn), \n", " ('rfe_extractor', ColumnExtractor(rfe_idx)),\n", " ('model', regressor)\n", "])\n", "\n", "rfe_skl_pipeline.fit(X_train, y_train)" ] }, { "cell_type": "code", "execution_count": 311, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Downloading artifacts: 100%|██████████| 7/7 [00:00<00:00, 193.34it/s]\n", "2024/10/17 14:32:07 INFO mlflow.tracking._tracking_service.client: 🏃 View run rfe_skl_feature_selection at: http://127.0.0.1:5000/#/experiments/1/runs/e55206caeb1549e4aa0d98343d5c1d4d.\n", "2024/10/17 14:32:07 INFO mlflow.tracking._tracking_service.client: 🧪 View experiment at: http://127.0.0.1:5000/#/experiments/1.\n" ] } ], "source": [ "predictions_rfe_skl = rfe_skl_pipeline.predict(X_test)\n", "\n", "metrics = {}\n", "metrics[\"mae\"] = mean_absolute_error(y_test, predictions_rfe_skl) \n", "metrics[\"mape\"] = mean_absolute_percentage_error(y_test, predictions_rfe_skl)\n", "metrics[\"mse\"] = mean_squared_error(y_test, predictions_rfe_skl)\n", "\n", "metrics\n", "experiment_id = mlflow.get_experiment_by_name(EXPERIMENT_NAME).experiment_id\n", "RUN_NAME = 'rfe_skl_feature_selection'\n", "\n", "with mlflow.start_run(run_name=RUN_NAME, experiment_id=experiment_id) as run:\n", " # получаем уникальный идентификатор запуска эксперимента\n", " run_id = run.info.run_id \n", " mlflow.sklearn.log_model(rfe_pipeline, \n", " artifact_path=\"models\",\n", " signature=signature,\n", " input_example=input_example,\n", " pip_requirements=req_file\n", " )\n", " mlflow.log_metrics(metrics)\n", " mlflow.log_artifact('rfe_skl_cols.txt')\n", " mlflow.log_artifact('rfe_skl_idx.txt')\n", " mlflow.log_params(model_sklearn.get_params())\n", "\n", "run = mlflow.get_run(run_id) \n", "assert (run.info.status =='FINISHED')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## mlextend\n", "https://github.com/rasbt/mlxtend/blob/master/docs/sources/user_guide/feature_selection/SequentialFeatureSelector.ipynb " ] }, { "cell_type": "code", "execution_count": 312, "metadata": {}, "outputs": [], "source": [ "from mlxtend.feature_selection import SequentialFeatureSelector \n", "#from sklearn.feature_selection import SequentialFeatureSelector" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sfs = SequentialFeatureSelector(RandomForestRegressor(n_estimators=3), \n", " k_features=3,\n", " forward=True,\n", " floating=False, # True to drop selected features\n", " scoring='neg_mean_absolute_error',\n", " cv=2)\n", "\n", "sfs.fit(X_train_sklearn,y_train)" ] }, { "cell_type": "code", "execution_count": 314, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
num__geo_lonquantile__geo_latspline__area_sp_3
0-0.4497420.7662571.826008e-06
11.4336730.2971421.310449e-06
20.0472220.7323306.098363e-07
3-0.7224770.1487891.144942e-06
41.1258190.9859374.240047e-06
............
4107700.3550140.7883931.401454e-06
4107710.3926970.4940628.202272e-07
410772-0.6888300.1313526.098363e-07
410773-0.8040770.1931431.004843e-05
410774-1.1018150.9080363.903343e-06
\n", "

410775 rows × 3 columns

\n", "
" ], "text/plain": [ " num__geo_lon quantile__geo_lat spline__area_sp_3\n", "0 -0.449742 0.766257 1.826008e-06\n", "1 1.433673 0.297142 1.310449e-06\n", "2 0.047222 0.732330 6.098363e-07\n", "3 -0.722477 0.148789 1.144942e-06\n", "4 1.125819 0.985937 4.240047e-06\n", "... ... ... ...\n", "410770 0.355014 0.788393 1.401454e-06\n", "410771 0.392697 0.494062 8.202272e-07\n", "410772 -0.688830 0.131352 6.098363e-07\n", "410773 -0.804077 0.193143 1.004843e-05\n", "410774 -1.101815 0.908036 3.903343e-06\n", "\n", "[410775 rows x 3 columns]" ] }, "execution_count": 314, "metadata": {}, "output_type": "execute_result" } ], "source": [ "selected_features_sfs = X_train_sklearn.loc[:, sfs.k_feature_names_]\n", "selected_features_sfs" ] }, { "cell_type": "code", "execution_count": 315, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['num__geo_lon', 'quantile__geo_lat', 'spline__area_sp_3']" ] }, "execution_count": 315, "metadata": {}, "output_type": "execute_result" } ], "source": [ "rfe_sfs_idx = list(sfs.k_feature_idx_)\n", "rfe_sfs_idx\n", "rfe_sfs_col = list(sfs.k_feature_names_)\n", "rfe_sfs_col" ] }, { "cell_type": "code", "execution_count": 317, "metadata": {}, "outputs": [ { "data": { "image/png": "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AHjxxRdhMpnQq1cvvPjii3jxxRdx4403HvX8y5cvh91ux5QpU/D444+jR48emDFjRp1uf8iyjIKCgnL/PB5P5PPjxo3DjBkzcMopp+Cxxx5D7969MWfOHFxzzTUVjrV161Zce+216NevHx5//HGcdNJJ6NWrFzZt2hQJJ0IIrF+/HlqtFuvWrYs8dt26ddBqtZE3sB07duCdd97BwIEDMX/+fNx555347bff0Lt373JzPso88MAD+OCDD3DHHXfgoYcegtFoxCeffIIrrrgCGo0Gc+bMweWXX47Ro0fjxx9/rPb70rp1awDAG2+8US7AVsbn86F379546aWXMGLECDzxxBM4++yzMX369HJv7JXJzc3FmWeeiU8//RS33HILHn/8cXTo0AFjx47FggULIvvJsoyBAwdi1qxZ6NGjBx599FHcdtttKC0txe+//46MjAw888wzAIDBgwdHfp+GDBly1HPX5me7bds2DB06FP369cOjjz6KlJQUjBo1Cn/88UeVX58kSfjhhx8qBOJevXpBURR8/fXXkW1lvwNH/l788ssv8Hg8OPfcc6s8z5HKfnYvvPAChBBH3e/GG29Ev379ACDy/XrxxRcBqL+nl112GV588UVcf/31ePDBB7Fv3z6MHDmyxuMoM3z4cADAJ598EtlWk9eSSy65BHa7Ha+//nqFY7722mvo1q0bunfvHtmWlJSE9u3bN/pJ6o2CIIqDTz75ROh0OqHT6UTPnj3Ff/7zH/Hxxx+LUChUbr9du3YJnU4nZs+eXW77b7/9JvR6fWR7KBQSmZmZ4qSTThLBYDCy33PPPScAiN69e0e2LVu2TAAQO3fuLHfMzz//XAAQn3/+uRBCCEVRRMeOHUX//v2FoiiR/Xw+n2jbtq3o169fZNvMmTMFADFmzJhyxxw8eLBIS0srt81ms4mRI0dW+J5UNi6fz1dhvxtvvFFYrVYRCAQi20aOHClat25dYd9/6927twBQ4V/ZeDZu3CgAiHHjxpV73B133CEAiM8++yyyrXXr1gKAWL16dbl9f/jhBwFAfPjhh0IIIX799VcBQFx55ZXijDPOiOw3aNAgcfLJJ0c+DgQCQpblcsfauXOnMJlM4v77749sK/s5tWvXrsL356STThLZ2dmipKQksu2TTz4RAGr0/RkxYoQAIFJSUsTgwYPFvHnzxObNmyvs98ADDwibzSb++uuvctunTZsmdDqd2LNnT2QbADFz5szIx2PHjhXZ2dmioKCg3GOvueYakZSUFPmali5dKgCI+fPnVzh/2e9jfn5+heOXKfudLBPNz/arr76KbMvLyxMmk0lMnTq1wrmOtG3bNgFAPPnkk+W2y7IsnE6n+M9//hP5GtLS0sSVV14pdDqdcLvdQggh5s+fL7RarSguLq7yPEfy+Xyic+fOkZ/zqFGjxJIlS0Rubm6FfSdOnCgqe2t75513BADxyCOPRLaFw2HRq1cvAUAsW7Yssr3se5ufn1/peIqLiwUAMXjw4MjXWtPXkmuvvVZkZmaKcDgc2Xbw4EGh1WrLPQ/KXHjhheK4446r4rtDscArQxQX/fr1w7fffotBgwZh06ZNeOSRR9C/f3+0aNGi3K2Ut99+G4qi4Kqrrip3JSMrKwsdO3bE559/DgD48ccfkZeXhwkTJsBoNEYeX3bJOxobN27E33//jeuuuw6FhYWRc3u9XlxwwQX46quvKtwSmTBhQrmPe/XqhcLCwshVkto6cv6N2+1GQUEBevXqBZ/PF3X1V5s2bbBmzZpy//7zn/8AUCeXAqhwdWPq1KkAgA8++KDc9rZt26J///7ltp188smw2+346quvAKh//efk5GDEiBH4+eef4fP5IITA119/jV69ekUeZzKZInN+ZFlGYWEh7HY7OnfujJ9//rnC1zFy5Mhy35+DBw9i48aNGDlyZLmfeb9+/dC1a9cafW+WLVuGp556Cm3btsWqVatwxx134LjjjsMFF1yA/fv3R/Z744030KtXL6SkpJT7vezbty9kWY587f8mhMBbb72FSy+9FEKIco/t378/SktLI1/rW2+9hfT09EqLCaIpma/tz7Zr167lfj4ZGRno3LkzduzYUeV5CgsLAQApKSnltmu1Wpx11lmR783mzZtRWFiIadOmQQiBb7/9FoD6+9K9e/dKb6EfjcViwXfffYc777wTgHpFdezYscjOzsakSZNqVN314YcfQq/X46abbops0+l0URVzlN0edLvdAGr3WnL11VcjLy+v3O36N998E4qi4Oqrr65wrrLfQYovhqFa+Oqrr3DppZeiefPm0Gg0UZVPCyEwb948dOrUCSaTCS1atMDs2bNjP9gG4LTTTsPbb7+N4uJifP/995g+fTrcbjeGDh2KP//8EwDw999/QwiBjh07IiMjo9y/zZs3RyZb7969GwDQsWPHcucwGAxRTyz8+++/Aahvuv8+9+LFixEMBlFaWlruMa1atSr3cdkbQnFxcVRj+OOPPzB48GAkJSXB6XQiIyMjMvn53+euKZvNhr59+5b7VxYWdu/eDa1Wiw4dOpR7TFZWFpKTkyPf5zJt27atcHydToeePXtGbn2sW7cOvXr1wjnnnANZlrFhwwb8+eefKCoqKvdmqygKHnvsMXTs2BEmkwnp6enIyMjAr7/+WunX+u9zH+13AAA6d+5ck28NtFotJk6ciJ9++gkFBQX43//+h4suugifffZZuVtJf//9N1avXl3h96JsDsfRigDy8/NRUlKC5557rsJjR48eXe6x27dvR+fOnWM2Cbq2P9t//y4D6u9zTX+XRSW3q3r16oWffvoJfr8f69atQ3Z2Nk455RSceOKJkd+Xf4fkmkpKSsIjjzyCXbt2YdeuXViyZAk6d+6Mp556Cg888EC1j9+9ezeys7PLzXMCav67c6Sy284OhwNA7V5LyuYUvfbaa5HjvfbaazjppJPQqVOnCucSQiS0n1RTwWqyWvB6vTjxxBMxZsyYKu/bV+W2227DJ598gnnz5uH4449HUVERioqKYjzShsVoNOK0007Daaedhk6dOmH06NF44403MHPmTCiKEplkW1ml1b9fuGriaC8c/56UXPaX2v/93//hpJNOqvQx/z7/0arBKntjqE5JSQl69+4Np9OJ+++/H+3bt4fZbMbPP/+Mu+66q0YTdaNV0xfXo1WOnXPOOZg9ezYCgQDWrVuHe+65B8nJyejevTvWrVuHZs2aAUC5N72HHnoI9957L8aMGYMHHngAqamp0Gq1mDx5cqVfa7yr1tLS0jBo0CAMGjQIffr0wZdffondu3ejdevWUBQF/fr1i1xR+7fK3rSAf36nrr/++qPORTnhhBNi8wUcRU1/ttH+LqelpQGo/A+Ac845B5Ik4dtvv42EZED9PVi3bh22bNmC/Pz8qMLQkVq3bo0xY8Zg8ODBaNeuHVauXIkHH3ywTsesjbK5kGXBszavJSaTCZdffjlWrVqFp59+Grm5uVi/fj0eeuihSh9XXFxcrqKN4oNhqBYuuugiXHTRRUf9fDAYxD333INXXnkFJSUl6N69O+bOnRupdNq8eTOeeeYZ/P7775G/Rir7y/tYduqppwJQb3kAQPv27SGEQNu2bY/6BgP8M4Hy77//xvnnnx/ZLkkSdu7ciRNPPDGyrexqzb+rRP79l3H79u0BAE6nM/IXfyzU9M3oiy++QGFhId5+++1yk0l37twZs7H8W9kb/d9//43jjjsusj03NxclJSWR73N1evXqhVAohFdeeQX79++PvLmde+65kTDUqVOnSCgC1FsB5513HpYsWVLuWCUlJTV6sT/yd+Dftm7dWqNxH82pp56KL7/8EgcPHkTr1q3Rvn17eDyeWv9eZGRkwOFwQJblah/bvn17fPfdd5AkCQaDodJ9anNFIFY/2+q0atUKFoul0t/T008/HUajEevWrcO6desit7XOPfdcPP/881i7dm3k41hISUlB+/btyxVqHO171rp1a6xduxYej6fcHznR/O6UTcouu4Vc29eSq6++GitWrMDatWuxefNmCCEqvUUGoMLrG8UHb5PF0C233IJvv/0Wr776Kn799VdceeWVGDBgQOTF+7333kO7du3w/vvvo23btmjTpg3GjRt3TF4Z+vzzzyv9C7NsXkNZGBwyZAh0Oh1mzZpVYX8hRGR+wqmnnoqMjAw8++yz5XqKLF++vELoKXthOnJehyzLFXrL9OjRA+3bt8e8efPKVVuVibbvjM1mq1G5btlf5kd+3aFQCE8//XRU562Jiy++GADKVTUBwPz58wGo1S41ccYZZ8BgMGDu3LlITU1Ft27dAKghacOGDfjyyy8r/PWv0+kq/IzfeOONcnN1qpKdnY2TTjoJK1asKHdbbc2aNZHbrlU5dOhQpfuFQiGsXbu23C2mq666Ct9++y0+/vjjCvuXlJQgHA5Xeg6dTocrrrgCb731VqWVlEf+Tl1xxRUoKCjAU089VWG/su+T1WqNnLM6sfrZVsdgMODUU0+ttILPbDbjtNNOwyuvvII9e/aUuzLk9/vxxBNPoH379sjOzgag3gresmVLtbeEN23aVOm8md27d+PPP/8sd6vLZrMBqPg9u/jiixEOhyMVeoD6ulDbjvgvv/wyFi9ejJ49e+KCCy4AUPvXkr59+yI1NRWvvfYaXnvtNZx++umV/mFcWlqK7du346yzzqrVGKn2eGUoRvbs2YNly5Zhz549kU6od9xxB1avXo1ly5bhoYcewo4dO7B792688cYbeOGFFyDLMm6//XYMHToUn332WYK/gtiaNGkSfD4fBg8ejC5duiAUCuGbb77Ba6+9hjZt2kTmT7Rv3x4PPvggpk+fjl27duHyyy+Hw+HAzp07sWrVKtxwww244447YDAY8OCDD+LGG2/E+eefj6uvvho7d+7EsmXLKswZ6tatG84880xMnz4dRUVFSE1NxauvvlrhDUyr1WLx4sW46KKL0K1bN4wePRotWrTA/v378fnnn8PpdOK9996r9dfeo0cPfPrpp5g/fz6aN2+Otm3bVrrG0FlnnYWUlBSMHDkSt956KzQaDV588cWobrnV1IknnoiRI0fiueeei9ym+/7777FixQpcfvnlOO+882p0HKvVih49emDDhg2RHkOA+he/1+uF1+utEIYGDhyI+++/H6NHj8ZZZ52F3377DStXrqzVnK85c+bgkksuwTnnnIMxY8agqKgITz75JLp161bpm9CR9u3bh9NPPx3nn38+LrjgAmRlZSEvLw+vvPIKNm3ahMmTJ0euUN1555149913MXDgQIwaNQo9evSA1+vFb7/9hjfffBO7du066tWshx9+GJ9//jnOOOMMjB8/Hl27dkVRURF+/vlnfPrpp5E/fkaMGIEXXngBU6ZMwffff49evXrB6/Xi008/xc0334zLLrsMFosFXbt2xWuvvYZOnTohNTUV3bt3L1d+XSZWP9uauOyyy3DPPffA5XJFevyU6dWrFx5++GEkJSXh+OOPB6D2FuvcuTO2bt0a6bsFAKtWrcLo0aOxbNmyctv/bc2aNZg5cyYGDRqEM888E3a7HTt27MDSpUsRDAbLdZvu0aMHALXbeP/+/aHT6XDNNdfg0ksvxdlnn41p06Zh165d6Nq1K95+++0qg9ibb74Ju92OUCgU6UC9fv16nHjiiXjjjTci+9X2tcRgMGDIkCF49dVX4fV6MW/evErP/+mnn0ZaAlCc1W/x2rEDgFi1alXk4/fff18AEDabrdw/vV4vrrrqKiGEEOPHjxcAxNatWyOP++mnnwQAsWXLlvr+EuLqo48+EmPGjBFdunQRdrtdGI1G0aFDBzFp0qRKy2Hfeustcc4550S+b126dBETJ04s970SQoinn35atG3bVphMJnHqqaeKr776SvTu3btcab0QQmzfvl307dtXmEwm0axZM3H33XeLNWvWlCutL/PLL7+IIUOGiLS0NGEymUTr1q3FVVddJdauXRvZ52iltpWV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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import matplotlib.pyplot as plt\n", "from mlxtend.plotting import plot_sequential_feature_selection as plot_sfs\n", "\n", "fig = plot_sfs(sfs.get_metric_dict(), kind='std_dev')\n", "\n", "plt.title('Sequential Forward Selection (w. StdDev)')\n", "plt.grid()\n", "plt.show()\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "rfe_sfs_pipeline = Pipeline(steps=[\n", " ('preprocessor', preprocessor_sklearn), \n", " ('rfe_extractor', ColumnExtractor(rfe_sfs_idx)),\n", " ('model', regressor)\n", "])\n", "\n", "rfe_sfs_pipeline.fit(X_train, y_train)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "predictions_sfs = rfe_sfs_pipeline.predict(X_test)\n", "\n", "metrics = {}\n", "metrics[\"mae\"] = mean_absolute_error(y_test, predictions_sfs) \n", "metrics[\"mape\"] = mean_absolute_percentage_error(y_test, predictions_sfs)\n", "metrics[\"mse\"] = mean_squared_error(y_test, predictions_sfs)\n", "\n", "metrics\n", "experiment_id = mlflow.get_experiment_by_name(EXPERIMENT_NAME).experiment_id\n", "RUN_NAME = 'rfe_sfs_feature_selection'\n", "\n", "with mlflow.start_run(run_name=RUN_NAME, experiment_id=experiment_id) as run:\n", " # получаем уникальный идентификатор запуска эксперимента\n", " run_id = run.info.run_id \n", " mlflow.sklearn.log_model(rfe_sfs_pipeline, \n", " artifact_path=\"models\",\n", " signature=signature,\n", " input_example=input_example,\n", " pip_requirements=req_file\n", " )\n", " mlflow.log_metrics(metrics)\n", " mlflow.log_artifact('rfe_skl_cols.txt')\n", " mlflow.log_artifact('rfe_skl_idx.txt')\n", " mlflow.log_params(model_sklearn.get_params())\n", "\n", "run = mlflow.get_run(run_id) \n", "assert (run.info.status =='FINISHED')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "Можно совмещать признаки, выбранные по sfs и sbs: брать их объединение или пересечение. Можно комбинировать с признаками, выделенными разными подходами - целое поле для исследований" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# HYPERPARAMS\n", "## Gridsearch" ] }, { "cell_type": "code", "execution_count": 224, "metadata": {}, "outputs": [], "source": [ "from sklearn.model_selection import GridSearchCV" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "param_grid = {\n", " 'model__depth': [1,3,5]\n", "}" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "gs = GridSearchCV(rfe_sfs_pipeline, param_grid, cv=2, scoring='neg_mean_absolute_error')\n", "gs.fit(X_train, y_train)\n", "print(\"Лучшие гиперпараметры:\", gs.best_params_)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "gs_pipeline = Pipeline(steps=[\n", " ('preprocessor', preprocessor_sklearn), \n", " ('rfe_extractor', ColumnExtractor(rfe_sfs_idx)),\n", " ('model', CatBoostRegressor(depth=5))\n", "])\n", "\n", "# Проведем стандартную проверку на тестовом множестве и залогируем run" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Вместо GridSearch можно использовать RandomSearch" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Optuna" ] }, { "cell_type": "code", "execution_count": 292, "metadata": {}, "outputs": [], "source": [ "import optuna" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def objective(trial):\n", " # предлагаем гиперпараметры\n", " depth = trial.suggest_int('depth', 1, 10)\n", " learning_rate = trial.suggest_float('learning_rate', 0.001, 0.1)\n", "\n", " # создаём и обучаем модель\n", " opt_pipeline = Pipeline(steps=[\n", " ('preprocessor', preprocessor_sklearn), \n", " ('rfe_extractor', ColumnExtractor(rfe_sfs_idx)),\n", " ('model', CatBoostRegressor(depth=depth, learning_rate=learning_rate, verbose=0))\n", " ])\n", "\n", " opt_pipeline.fit(X_train, y_train)\n", "\n", " # предсказываем и вычисляем RMSE\n", " preds = opt_pipeline.predict(X_test)\n", " mae = mean_absolute_error(y_test, preds) \n", "\n", " return mae" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "study = optuna.create_study(direction='minimize')\n", "study.optimize(objective, n_trials=10)\n", "\n", "# выводим результаты\n", "print('Number of finished trials:', len(study.trials))\n", "print('Best trial:', study.best_trial.params) " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "opt_pipeline = Pipeline(steps=[\n", " ('preprocessor', preprocessor_sklearn), \n", " ('rfe_extractor', ColumnExtractor(rfe_sfs_idx)),\n", " ('model', CatBoostRegressor(depth=3, learning_rate=0.02789))\n", "])\n", "\n", "# Проведем стандартную проверку на тестовом множестве и залогируем run" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Выбираем лучшую модель.\n", "Обучаем ее на всей выборке (а не только на train-части). \n", "Далее будем деплоить именно её" ] }, { "cell_type": "markdown", "metadata": {}, "source": [] } ], "metadata": { "kernelspec": { "display_name": ".venv_labs_proj", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.12" } }, "nbformat": 4, "nbformat_minor": 2 }