823 KiB
Начинаем работу...¶
Датасет: Продажи домов в округе Кинг
import pandas as pd
from sklearn import set_config
# Установим параметры для вывода
set_config(transform_output="pandas")
random_state = 42
# Подключим датафрейм и выгрузим данные
df = pd.read_csv(".//static//csv//kc_house_data.csv")
print(df.columns)
df.head()
df.describe()
# Процент пропущенных значений признаков
for i in df.columns:
null_rate = df[i].isnull().sum() / len(df) * 100
if null_rate > 0:
print(f'{i} Процент пустых значений: %{null_rate:.2f}')
print(df.isnull().sum())
print(df.isnull().any())
# Проверка типов столбцов
df.dtypes
Выбор бизнес-целей¶
Для датасета недвижимости предлагаются две бизнес-цели:
Задача регрессии – предсказание цены дома (price). Это может помочь риэлторам и аналитикам определить справедливую рыночную стоимость недвижимости.
Задача классификации – определение вероятности того, что цена дома будет выше/ниже медианы рынка. Классифицировать дома по ценовым категориям (например, низкая, средняя, высокая цена). Это может помочь определить, какие дома популярны у покупателей.
Определение достижимого уровня качества модели¶
Для регрессии и классификации мы выберем метрики:
Для регрессии будем использовать метрики MAE (средняя абсолютная ошибка) и R^2 (коэффициент детерминации), стремясь к MAE ниже 10% от средней цены. А классификация будте ориентироваться на метрики accuracy и F1-score при целевом значении accuracy около 80%.
Ориентир для каждой задачи¶
Для регрессии ориентиром будет медианная цена (price.median()), так как это стабильное значение. Для классификации ориентируемся на среднюю вероятность предсказания класса выше медианы.
Анализ алгоритмов машинного обучения¶
Рассмотрим для задачи регрессии:
Линейная регрессия: подходит для простых линейных зависимостей.
Дерево решений: учитывает нелинейные зависимости, может учесть сложные закономерности.
Случайный лес: ансамблевый метод, обобщающий данные и эффективно обрабатывающий выбросы.
Для задачи классификации:
Логистическая регрессия: простая модель, подходящая для бинарной классификации.
Метод опорных векторов (SVM): работает хорошо на данных с четкими разделениями.
Градиентный бустинг: подходит для сложных и высокоразмерных данных, обеспечивает высокую точность.
Выбор моделей¶
Выбираем по три модели для каждой задачи:
Регрессия: Линейная регрессия, Дерево решений, Случайный лес.
Классификация: Логистическая регрессия, Метод опорных векторов (SVM), Градиентный бустинг.
Построение конвейера и визуализации¶
Теперь напишем код для загрузки данных, анализа и подготовки моделей с визуализацией результатов.
Начнём с задачи классификации¶
Целевой признак --> above_median_price
Формируем выборки. Разделяем набор данных на обучающую и тестовые выборки (80/20) для задачи классификации
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from typing import Tuple
import pandas as pd
from pandas import DataFrame
from sklearn.model_selection import train_test_split
# Создание целевого признака
median_price = df['price'].median()
df['above_median_price'] = np.where(df['price'] > median_price, 1, 0)
# Разделение на признаки и целевую переменную
X = df.drop(columns=['id', 'date', 'price', 'above_median_price'])
y = df['above_median_price']
# Примерная категоризация
df['price_category'] = pd.cut(df['price'], bins=[0, 300000, 700000, np.inf], labels=[0, 1, 2])
# Выбор признаков и целевых переменных
X = df.drop(columns=['id', 'date', 'price', 'price_category'])
def split_stratified_into_train_val_test(
df_input,
stratify_colname="y",
frac_train=0.6,
frac_val=0.15,
frac_test=0.25,
random_state=None,
) -> Tuple[DataFrame, DataFrame, DataFrame, DataFrame, DataFrame, DataFrame]:
if frac_train + frac_val + frac_test != 1.0:
raise ValueError(
"fractions %f, %f, %f do not add up to 1.0"
% (frac_train, frac_val, frac_test)
)
if stratify_colname not in df_input.columns:
raise ValueError("%s is not a column in the dataframe" % (stratify_colname))
X = df_input # Contains all columns.
y = df_input[
[stratify_colname]
] # Dataframe of just the column on which to stratify.
# Split original dataframe into train and temp dataframes.
df_train, df_temp, y_train, y_temp = train_test_split(
X, y, stratify=y, test_size=(1.0 - frac_train), random_state=random_state
)
if frac_val <= 0:
assert len(df_input) == len(df_train) + len(df_temp)
return df_train, pd.DataFrame(), df_temp, y_train, pd.DataFrame(), y_temp
# Split the temp dataframe into val and test dataframes.
relative_frac_test = frac_test / (frac_val + frac_test)
df_val, df_test, y_val, y_test = train_test_split(
df_temp,
y_temp,
stratify=y_temp,
test_size=relative_frac_test,
random_state=random_state,
)
assert len(df_input) == len(df_train) + len(df_val) + len(df_test)
return df_train, df_val, df_test, y_train, y_val, y_test
X_train, X_val, X_test, y_train, y_val, y_test = split_stratified_into_train_val_test(
df, stratify_colname="above_median_price", frac_train=0.80, frac_val=0, frac_test=0.20, random_state=42
)
display("X_train", X_train)
display("y_train", y_train)
display("X_test", X_test)
display("y_test", y_test)
# Проверка преобразования
print(df.dtypes)
# Визуализация распределения цен
plt.figure(figsize=(10, 6))
sns.histplot(df['price'], bins=50, kde=True)
plt.title('Распределение цен на недвижимость')
plt.xlabel('Цена')
plt.ylabel('Частота')
plt.show()
# Визуализация зависимости между ценой и количеством спален
plt.figure(figsize=(10, 6))
sns.boxplot(x='bedrooms', y='price', data=df)
plt.title('Зависимость цены от количества спален')
plt.xlabel('Количество спален')
plt.ylabel('Цена')
plt.show()
Построение конвейеров предобработки¶
Создадим пайплайн для числовых и категориальных данных.
preprocessing_num -- конвейер для обработки числовых данных: заполнение пропущенных значений и стандартизация
preprocessing_cat -- конвейер для обработки категориальных данных: заполнение пропущенных данных и унитарное кодирование
features_preprocessing -- трансформер для предобработки признаков
features_engineering -- трансформер для конструирования признаков
drop_columns -- трансформер для удаления колонок
pipeline_end -- основной конвейер предобработки данных и конструирования признаков
import numpy as np
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.compose import ColumnTransformer
from sklearn.discriminant_analysis import StandardScaler
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import OneHotEncoder
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
pipeline_end = StandardScaler()
# Построение конвейеров предобработки
class HouseFeatures(BaseEstimator, TransformerMixin):
def __init__(self):
pass
def fit(self, X, y=None):
return self
def transform(self, X, y=None):
# Создание новых признаков
X = X.copy()
X["Living_area_to_Lot_ratio"] = X["sqft_living"] / X["sqft_lot"]
return X
def get_feature_names_out(self, features_in):
# Добавление имен новых признаков
new_features = ["Living_area_to_Lot_ratio"]
return np.append(features_in, new_features, axis=0)
# Обработка числовых данных. Числовой конвейр: заполнение пропущенных значений медианой и стандартизация
preprocessing_num_class = Pipeline(steps=[
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler())
])
preprocessing_cat_class = Pipeline(steps=[
('imputer', SimpleImputer(strategy='most_frequent')),
('onehot', OneHotEncoder(handle_unknown='ignore'))
])
columns_to_drop = ["date"]
numeric_columns = ["sqft_living", "sqft_lot", "above_median_price"]
cat_columns = []
features_preprocessing = ColumnTransformer(
verbose_feature_names_out=False,
transformers=[
("prepocessing_num", preprocessing_num_class, numeric_columns),
("prepocessing_cat", preprocessing_cat_class, cat_columns),
],
remainder="passthrough"
)
drop_columns = ColumnTransformer(
verbose_feature_names_out=False,
transformers=[
("drop_columns", "drop", columns_to_drop),
],
remainder="passthrough",
)
features_postprocessing = ColumnTransformer(
verbose_feature_names_out=False,
transformers=[
('preprocessing_cat', preprocessing_cat_class, ["price_category"]),
],
remainder="passthrough",
)
pipeline_end = Pipeline(
[
("features_preprocessing", features_preprocessing),
("custom_features", HouseFeatures()),
("drop_columns", drop_columns),
]
)
Демонстрация работы конвейра для предобработки данных при классификации
preprocessing_result = pipeline_end.fit_transform(X_train)
preprocessed_df = pd.DataFrame(
preprocessing_result,
columns=pipeline_end.get_feature_names_out(),
)
preprocessed_df
Формирование набора моделей для классификации¶
logistic -- логистическая регрессия
ridge -- гребневая регрессия
decision_tree -- дерево решений
knn -- k-ближайших соседей
naive_bayes -- наивный Байесовский классификатор
gradient_boosting -- метод градиентного бустинга (набор деревьев решений)
random_forest -- метод случайного леса (набор деревьев решений)
mlp -- многослойный персептрон (нейронная сеть)
from sklearn import ensemble, linear_model, naive_bayes, neighbors, neural_network, tree, svm
class_models = {
"logistic": {"model": linear_model.LogisticRegression(max_iter=150)},
"ridge": {"model": linear_model.RidgeClassifierCV(cv=5, class_weight="balanced")},
"ridge": {"model": linear_model.LogisticRegression(max_iter=150, solver='lbfgs', penalty="l2", class_weight="balanced")},
"decision_tree": {
"model": tree.DecisionTreeClassifier(max_depth=5, min_samples_split=10, random_state=random_state)
},
"knn": {"model": neighbors.KNeighborsClassifier(n_neighbors=7)},
"naive_bayes": {"model": naive_bayes.GaussianNB()},
"gradient_boosting": {
"model": ensemble.GradientBoostingClassifier(n_estimators=210)
},
"random_forest": {
"model": ensemble.RandomForestClassifier(
max_depth=5, class_weight="balanced", random_state=random_state
)
},
"mlp": {
"model": neural_network.MLPClassifier(
hidden_layer_sizes=(7,),
max_iter=200,
early_stopping=True,
random_state=random_state,
)
},
}
Обучение моделей на обучающем наборе данных и оценка на тестовом
import numpy as np
from sklearn import metrics
for model_name in class_models.keys():
print(f"Model: {model_name}")
model = class_models[model_name]["model"]
model_pipeline = Pipeline([("pipeline", pipeline_end), ("model", model)])
model_pipeline = model_pipeline.fit(X_train, y_train.values.ravel())
y_train_predict = model_pipeline.predict(X_train)
y_test_probs = model_pipeline.predict_proba(X_test)[:, 1]
y_test_predict = np.where(y_test_probs > 0.5, 1, 0)
class_models[model_name]["pipeline"] = model_pipeline
class_models[model_name]["probs"] = y_test_probs
class_models[model_name]["preds"] = y_test_predict
class_models[model_name]["Precision_train"] = metrics.precision_score(
y_train, y_train_predict, zero_division=1
)
class_models[model_name]["Precision_test"] = metrics.precision_score(
y_test, y_test_predict, zero_division=1
)
class_models[model_name]["Recall_train"] = metrics.recall_score(
y_train, y_train_predict
)
class_models[model_name]["Recall_test"] = metrics.recall_score(
y_test, y_test_predict
)
class_models[model_name]["Accuracy_train"] = metrics.accuracy_score(
y_train, y_train_predict
)
class_models[model_name]["Accuracy_test"] = metrics.accuracy_score(
y_test, y_test_predict
)
class_models[model_name]["ROC_AUC_test"] = metrics.roc_auc_score(
y_test, y_test_probs
)
class_models[model_name]["F1_train"] = metrics.f1_score(y_train, y_train_predict)
class_models[model_name]["F1_test"] = metrics.f1_score(y_test, y_test_predict)
class_models[model_name]["MCC_test"] = metrics.matthews_corrcoef(
y_test, y_test_predict
)
class_models[model_name]["Cohen_kappa_test"] = metrics.cohen_kappa_score(
y_test, y_test_predict
)
class_models[model_name]["Confusion_matrix"] = metrics.confusion_matrix(
y_test, y_test_predict
)
Сводная таблица оценок качества для использованных моделей классификации¶ Матрица неточностей
from sklearn.metrics import ConfusionMatrixDisplay
import matplotlib.pyplot as plt
_, ax = plt.subplots(int(len(class_models) / 2), 2, figsize=(12, 10), sharex=False, sharey=False)
for index, key in enumerate(class_models.keys()):
c_matrix = class_models[key]["Confusion_matrix"]
disp = ConfusionMatrixDisplay(
confusion_matrix=c_matrix, display_labels=["Less", "More"]
).plot(ax=ax.flat[index])
disp.ax_.set_title(key)
plt.subplots_adjust(top=1, bottom=0, hspace=0.4, wspace=0.1)
plt.show()
Значение 2173 в желтом квадрате представляет собой количество объектов, относимых к классу "Less", которые модель правильно классифицировала. Это свидетельствует о высоком уровне точности в идентификации этого класса. Значение 2150 в жёлтом нижнем правом квадрате указывает на количество правильно классифицированных объектов класса "More". Хотя это также является положительным результатом, мы можем заметить, что он местами ниже, чем для класса "Less", а местами и выше.
Точность, полнота, верность (аккуратность), F-мера
class_metrics = pd.DataFrame.from_dict(class_models, "index")[
[
"Precision_train",
"Precision_test",
"Recall_train",
"Recall_test",
"Accuracy_train",
"Accuracy_test",
"F1_train",
"F1_test",
]
]
class_metrics.sort_values(
by="Accuracy_test", ascending=False
).style.background_gradient(
cmap="plasma",
low=0.3,
high=1,
subset=["Accuracy_train", "Accuracy_test", "F1_train", "F1_test"],
).background_gradient(
cmap="viridis",
low=1,
high=0.3,
subset=[
"Precision_train",
"Precision_test",
"Recall_train",
"Recall_test",
],
)
Действительно, если модели, включая логистическую регрессию (есть исключения), ридж-регрессию (есть исключения), дерево решений, случайный лес и градиентный бустинг, показывают 100% точность на обучающей выборке, это может свидетельствовать о переобучении. Переобучение (overfitting) происходит, когда модель слишком хорошо подстраивается под обучающие данные, включая шум и случайные вариации, и начинает плохо работать на новых данных (например, на тестовой выборке).
ROC-кривая, каппа Коэна, коэффициент корреляции Мэтьюса
class_metrics = pd.DataFrame.from_dict(class_models, "index")[
[
"Accuracy_test",
"F1_test",
"ROC_AUC_test",
"Cohen_kappa_test",
"MCC_test",
]
]
class_metrics.sort_values(by="ROC_AUC_test", ascending=False).style.background_gradient(
cmap="plasma",
low=0.3,
high=1,
subset=[
"ROC_AUC_test",
"MCC_test",
"Cohen_kappa_test",
],
).background_gradient(
cmap="viridis",
low=1,
high=0.3,
subset=[
"Accuracy_test",
"F1_test",
],
)
best_model = str(class_metrics.sort_values(by="MCC_test", ascending=False).iloc[0].name)
display(best_model)
Вывод данных с ошибкой предсказания для оценки
preprocessing_result = pipeline_end.transform(X_test)
preprocessed_df = pd.DataFrame(
preprocessing_result,
columns=pipeline_end.get_feature_names_out(),
)
y_pred = class_models[best_model]["preds"]
error_index = y_test[y_test["above_median_price"] != y_pred].index.tolist()
display(f"Error items count: {len(error_index)}")
error_predicted = pd.Series(y_pred, index=y_test.index).loc[error_index]
error_df = X_test.loc[error_index].copy()
error_df.insert(loc=1, column="Predicted", value=error_predicted)
error_df.sort_index()
model = class_models[best_model]["pipeline"]
example_id = 6863
test = pd.DataFrame(X_test.loc[example_id, :]).T
test_preprocessed = pd.DataFrame(preprocessed_df.loc[example_id, :]).T
display(test)
display(test_preprocessed)
result_proba = model.predict_proba(test)[0]
result = model.predict(test)[0]
real = int(y_test.loc[example_id].values[0])
display(f"predicted: {result} (proba: {result_proba})")
display(f"real: {real}")
Подбор гиперпараметров методом поиска по сетке
from sklearn.model_selection import GridSearchCV
optimized_model_type = "random_forest"
random_forest_model = class_models[optimized_model_type]["pipeline"]
param_grid = {
"model__n_estimators": [10, 50, 100],
"model__max_features": ["sqrt", "log2"],
"model__max_depth": [5, 7, 10],
"model__criterion": ["gini", "entropy"],
}
gs_optomizer = GridSearchCV(
estimator=random_forest_model, param_grid=param_grid, n_jobs=-1
)
gs_optomizer.fit(X_train, y_train.values.ravel())
gs_optomizer.best_params_
Обучение модели с новыми гиперпараметрами¶
optimized_model = ensemble.RandomForestClassifier(
random_state=random_state,
criterion="gini",
max_depth=5,
max_features="log2",
n_estimators=10,
)
result = {}
result["pipeline"] = Pipeline([("pipeline", pipeline_end), ("model", optimized_model)]).fit(X_train, y_train.values.ravel())
result["train_preds"] = result["pipeline"].predict(X_train)
result["probs"] = result["pipeline"].predict_proba(X_test)[:, 1]
result["preds"] = np.where(result["probs"] > 0.5, 1, 0)
result["Precision_train"] = metrics.precision_score(y_train, result["train_preds"])
result["Precision_test"] = metrics.precision_score(y_test, result["preds"])
result["Recall_train"] = metrics.recall_score(y_train, result["train_preds"])
result["Recall_test"] = metrics.recall_score(y_test, result["preds"])
result["Accuracy_train"] = metrics.accuracy_score(y_train, result["train_preds"])
result["Accuracy_test"] = metrics.accuracy_score(y_test, result["preds"])
result["ROC_AUC_test"] = metrics.roc_auc_score(y_test, result["probs"])
result["F1_train"] = metrics.f1_score(y_train, result["train_preds"])
result["F1_test"] = metrics.f1_score(y_test, result["preds"])
result["MCC_test"] = metrics.matthews_corrcoef(y_test, result["preds"])
result["Cohen_kappa_test"] = metrics.cohen_kappa_score(y_test, result["preds"])
result["Confusion_matrix"] = metrics.confusion_matrix(y_test, result["preds"])
Формирование данных для оценки старой и новой версии модели
optimized_metrics = pd.DataFrame(columns=list(result.keys()))
optimized_metrics.loc[len(optimized_metrics)] = pd.Series(
data=class_models[optimized_model_type]
)
optimized_metrics.loc[len(optimized_metrics)] = pd.Series(
data=result
)
optimized_metrics.insert(loc=0, column="Name", value=["Old", "New"])
optimized_metrics = optimized_metrics.set_index("Name")
Оценка параметров старой и новой модели
optimized_metrics[
[
"Precision_train",
"Precision_test",
"Recall_train",
"Recall_test",
"Accuracy_train",
"Accuracy_test",
"F1_train",
"F1_test",
]
].style.background_gradient(
cmap="plasma",
low=0.3,
high=1,
subset=["Accuracy_train", "Accuracy_test", "F1_train", "F1_test"],
).background_gradient(
cmap="viridis",
low=1,
high=0.3,
subset=[
"Precision_train",
"Precision_test",
"Recall_train",
"Recall_test",
],
)
Как для обучающей (Precision_train), так и для тестовой (Precision_test) выборки обе модели достигли идеальных значений 1.000000. Это указывает на то, что модели очень точно классифицируют положительные образцы, не пропуская их.
optimized_metrics[
[
"Accuracy_test",
"F1_test",
"ROC_AUC_test",
"Cohen_kappa_test",
"MCC_test",
]
].style.background_gradient(
cmap="plasma",
low=0.3,
high=1,
subset=[
"ROC_AUC_test",
"MCC_test",
"Cohen_kappa_test",
],
).background_gradient(
cmap="viridis",
low=1,
high=0.3,
subset=[
"Accuracy_test",
"F1_test",
],
)
Оба варианта модели продемонстрировали безупречную точность классификации, достигнув значения 1.000000. Это свидетельствует о том, что модели точно классифицировали все тестовые примеры, не допустив никаких ошибок в предсказаниях.
_, ax = plt.subplots(1, 2, figsize=(10, 4), sharex=False, sharey=False
)
for index in range(0, len(optimized_metrics)):
c_matrix = optimized_metrics.iloc[index]["Confusion_matrix"]
disp = ConfusionMatrixDisplay(
confusion_matrix=c_matrix, display_labels=["Less", "More"]
).plot(ax=ax.flat[index])
plt.subplots_adjust(top=1, bottom=0, hspace=0.4, wspace=0.3)
plt.show()
В желтом квадрате мы видим значение 2173, что обозначает количество правильно классифицированных объектов, отнесенных к классу "Less". Это свидетельствует о том, что модель успешно идентифицирует объекты этого класса, минимизируя количество ложных положительных срабатываний.
В правом нижнем жёлтом квадрате значение 2150 указывает на количество правильно классифицированных объектов, отнесенных к классу "More". Это также является показателем высокой точности модели в определении объектов данного класса.
Задача регресии: предсказание цены дома (price).¶
Описание: Оценить, какая будет цена дома (price) на основе исторических данных о характеристиках домов, таких как площадь. Целевая переменная: Цена дома (price). (среднее значение)
import pandas as pd
from sklearn import set_config
set_config(transform_output="pandas")
# Опция для настройки генерации случайных чисел (если это нужно для других частей кода)
random_state = 42
# Вычисление среднего значения поля "Close"
average_price = df['sqft_living'].mean()
print(f"Среднее значение поля: {average_price}")
# Создание новой колонки, указывающей, выше или ниже среднего значение цена закрытия
df['average_price'] = (df['sqft_living'] > average_price).astype(int)
# Удаление последней строки, где нет значения для следующего дня
df.dropna(inplace=True)
# Вывод DataFrame с новой колонкой
print(df.head())
# Примерный анализ данных
print("Статистическое описание DataFrame:")
print(df.describe())
from typing import Tuple
from pandas import DataFrame
from sklearn.model_selection import train_test_split
def split_into_train_test(
df_input: DataFrame,
target_colname: str = "average_price",
frac_train: float = 0.8,
random_state: int = None,
) -> Tuple[DataFrame, DataFrame, DataFrame, DataFrame]:
if not (0 < frac_train < 1):
raise ValueError("Fraction must be between 0 and 1.")
# Проверка наличия целевого признака
if target_colname not in df_input.columns:
raise ValueError(f"{target_colname} is not a column in the DataFrame.")
# Разделяем данные на признаки и целевую переменную
X = df_input.drop(columns=[target_colname]) # Признаки
y = df_input[[target_colname]] # Целевая переменная
# Разделяем данные на обучающую и тестовую выборки
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=(1.0 - frac_train),
random_state=random_state
)
return X_train, X_test, y_train, y_test
# Применение функции для разделения данных
X_train, X_test, y_train, y_test = split_into_train_test(
df,
target_colname="average_price",
frac_train=0.8,
random_state=42 # Убедитесь, что вы задали нужное значение random_state
)
# Для отображения результатов
display("X_train", X_train)
display("y_train", y_train)
display("X_test", X_test)
display("y_test", y_test)
Формирование конвейера для решения задачи регрессии¶
import numpy as np
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder
from sklearn.ensemble import RandomForestRegressor # Пример регрессионной модели
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
class HouseFeatures(BaseEstimator, TransformerMixin):
def __init__(self):
pass
def fit(self, X, y=None):
return self
def transform(self, X, y=None):
# Создание новых признаков
X = X.copy()
X["Square"] = X["sqft_living"] / X["sqft_lot"]
return X
def get_feature_names_out(self, features_in):
# Добавление имен новых признаков
new_features = ["Square"]
return np.append(features_in, new_features, axis=0)
# Указываем столбцы, которые нужно удалить и обрабатывать
columns_to_drop = ["date"]
num_columns = ["bathrooms", "floors", "waterfront", "view"]
cat_columns = []
# Определяем предобработку для численных данных
num_imputer = SimpleImputer(strategy="median")
num_scaler = StandardScaler()
preprocessing_num = Pipeline(
[
("imputer", num_imputer),
("scaler", num_scaler),
]
)
# Определяем предобработку для категориальных данных
cat_imputer = SimpleImputer(strategy="constant", fill_value="unknown")
cat_encoder = OneHotEncoder(handle_unknown="ignore", sparse_output=False, drop="first")
preprocessing_cat = Pipeline(
[
("imputer", cat_imputer),
("encoder", cat_encoder),
]
)
# Подготовка признаков с использованием ColumnTransformer
features_preprocessing = ColumnTransformer(
verbose_feature_names_out=False,
transformers=[
("preprocessing_num", preprocessing_num, num_columns),
("preprocessing_cat", preprocessing_cat, cat_columns),
],
remainder="passthrough"
)
# Удаление нежелательных столбцов
drop_columns = ColumnTransformer(
verbose_feature_names_out=False,
transformers=[
("drop_columns", "drop", columns_to_drop),
],
remainder="passthrough",
)
# Постобработка признаков
features_postprocessing = ColumnTransformer(
verbose_feature_names_out=False,
transformers=[
("preprocessing_cat", preprocessing_cat, ["price_category"]),
],
remainder="passthrough",
)
# Создание окончательного конвейера
pipeline = Pipeline(
[
("features_preprocessing", features_preprocessing),
("drop_columns", drop_columns),
("custom_features", HouseFeatures()),
("model", RandomForestRegressor()) # Выбор модели для обучения
]
)
# Использование конвейера
def train_pipeline(X, y):
pipeline.fit(X, y)
Формирование набора моделей для регрессии¶
Определение перечня алгоритмов решения задачи аппроксимации (регрессии)
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn import linear_model, tree, neighbors, ensemble, neural_network
random_state = 9
models = {
"linear": {"model": linear_model.LinearRegression(n_jobs=-1)},
"linear_poly": {
"model": make_pipeline(
PolynomialFeatures(degree=2),
linear_model.LinearRegression(fit_intercept=False, n_jobs=-1),
)
},
"linear_interact": {
"model": make_pipeline(
PolynomialFeatures(interaction_only=True),
linear_model.LinearRegression(fit_intercept=False, n_jobs=-1),
)
},
"ridge": {"model": linear_model.RidgeCV()},
"decision_tree": {
"model": tree.DecisionTreeRegressor(max_depth=7, random_state=random_state)
},
"knn": {"model": neighbors.KNeighborsRegressor(n_neighbors=7, n_jobs=-1)},
"random_forest": {
"model": ensemble.RandomForestRegressor(
max_depth=7, random_state=random_state, n_jobs=-1
)
},
"mlp": {
"model": neural_network.MLPRegressor(
activation="tanh",
hidden_layer_sizes=(3,),
max_iter=500,
early_stopping=True,
random_state=random_state,
)
},
}
Формирование набора моделей для регрессии¶
from sklearn.linear_model import LinearRegression
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.svm import SVR
from sklearn.model_selection import cross_val_score
def train_multiple_models(X, y, models):
results = {}
# Преобразуем y в одномерный массив numpy только при необходимости
if hasattr(y, 'values'):
y = y.values.ravel() # Если y - DataFrame, преобразуем в numpy array
else:
y = y.ravel() # Если y - numpy array, просто используем ravel()
for model_name, model in models.items():
# Создаем конвейер для каждой модели
model_pipeline = Pipeline(
[
("features_preprocessing", features_preprocessing),
("drop_columns", drop_columns),
("model", model) # Используем текущую модель
]
)
# Обучаем модель и вычисляем кросс-валидацию
scores = cross_val_score(model_pipeline, X, y, cv=5, error_score='raise') # 5-кратная кросс-валидация
results[model_name] = {
"mean_score": scores.mean(),
"std_dev": scores.std()
}
return results
models = {
"Random Forest": RandomForestRegressor(),
"Linear Regression": LinearRegression(),
"Gradient Boosting": GradientBoostingRegressor(),
"Support Vector Regression": SVR()
}
results = train_multiple_models(X_train, y_train, models)
# Вывод результатов
for model_name, scores in results.items():
print(f"{model_name}: Mean Score = {scores['mean_score']}, Standard Deviation = {scores['std_dev']}")
import numpy as np
from sklearn import metrics
from sklearn.pipeline import Pipeline
# Проверка наличия необходимых переменных
if 'class_models' not in locals():
raise ValueError("class_models is not defined")
if 'X_train' not in locals() or 'X_test' not in locals() or 'y_train' not in locals() or 'y_test' not in locals():
raise ValueError("Train/test data is not defined")
y_train = np.ravel(y_train)
y_test = np.ravel(y_test)
# Инициализация списка для хранения результатов
results = []
# Проход по моделям и оценка их качества
for model_name in class_models.keys():
print(f"Model: {model_name}")
# Извлечение модели из словаря
model = class_models[model_name]["model"]
# Создание пайплайна
model_pipeline = Pipeline([("pipeline", pipeline_end), ("model", model)])
# Обучение модели
model_pipeline.fit(X_train, y_train)
# Предсказание для обучающей и тестовой выборки
y_train_predict = model_pipeline.predict(X_train)
y_test_predict = model_pipeline.predict(X_test)
# Сохранение пайплайна и предсказаний
class_models[model_name]["pipeline"] = model_pipeline
class_models[model_name]["preds"] = y_test_predict
# Вычисление метрик для регрессии
class_models[model_name]["MSE_train"] = metrics.mean_squared_error(y_train, y_train_predict)
class_models[model_name]["MSE_test"] = metrics.mean_squared_error(y_test, y_test_predict)
class_models[model_name]["MAE_train"] = metrics.mean_absolute_error(y_train, y_train_predict)
class_models[model_name]["MAE_test"] = metrics.mean_absolute_error(y_test, y_test_predict)
class_models[model_name]["R2_train"] = metrics.r2_score(y_train, y_train_predict)
class_models[model_name]["R2_test"] = metrics.r2_score(y_test, y_test_predict)
# Дополнительные метрики
class_models[model_name]["STD_train"] = np.std(y_train - y_train_predict)
class_models[model_name]["STD_test"] = np.std(y_test - y_test_predict)
# Вывод результатов для текущей модели
print(f"MSE (train): {class_models[model_name]['MSE_train']}")
print(f"MSE (test): {class_models[model_name]['MSE_test']}")
print(f"MAE (train): {class_models[model_name]['MAE_train']}")
print(f"MAE (test): {class_models[model_name]['MAE_test']}")
print(f"R2 (train): {class_models[model_name]['R2_train']}")
print(f"R2 (test): {class_models[model_name]['R2_test']}")
print(f"STD (train): {class_models[model_name]['STD_train']}")
print(f"STD (test): {class_models[model_name]['STD_test']}")
print("-" * 40) # Разделитель для разных моделей
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn import linear_model, tree, neighbors, ensemble, neural_network
random_state = 9
models = {
"linear": {"model": linear_model.LinearRegression(n_jobs=-1)},
"linear_poly": {
"model": make_pipeline(
PolynomialFeatures(degree=2),
linear_model.LinearRegression(fit_intercept=False, n_jobs=-1),
)
},
"linear_interact": {
"model": make_pipeline(
PolynomialFeatures(interaction_only=True),
linear_model.LinearRegression(fit_intercept=False, n_jobs=-1),
)
},
"ridge": {"model": linear_model.RidgeCV()},
"decision_tree": {
"model": tree.DecisionTreeRegressor(max_depth=7, random_state=random_state)
},
"knn": {"model": neighbors.KNeighborsRegressor(n_neighbors=7, n_jobs=-1)},
"random_forest": {
"model": ensemble.RandomForestRegressor(
max_depth=7, random_state=random_state, n_jobs=-1
)
},
"mlp": {
"model": neural_network.MLPRegressor(
activation="tanh",
hidden_layer_sizes=(3,),
max_iter=500,
early_stopping=True,
random_state=random_state,
)
},
}
Обучение и оценка моделей с помощью различных алгоритмов¶
import numpy as np
from sklearn import metrics
from sklearn.pipeline import Pipeline
# Проверка наличия необходимых переменных
if 'class_models' not in locals():
raise ValueError("class_models is not defined")
if 'X_train' not in locals() or 'X_test' not in locals() or 'y_train' not in locals() or 'y_test' not in locals():
raise ValueError("Train/test data is not defined")
y_train = np.ravel(y_train)
y_test = np.ravel(y_test)
# Инициализация списка для хранения результатов
results = []
# Проход по моделям и оценка их качества
for model_name in class_models.keys():
print(f"Model: {model_name}")
# Извлечение модели из словаря
model = class_models[model_name]["model"]
# Создание пайплайна
model_pipeline = Pipeline([("pipeline", pipeline_end), ("model", model)])
# Обучение модели
model_pipeline.fit(X_train, y_train)
# Предсказание для обучающей и тестовой выборки
y_train_predict = model_pipeline.predict(X_train)
y_test_predict = model_pipeline.predict(X_test)
# Сохранение пайплайна и предсказаний
class_models[model_name]["pipeline"] = model_pipeline
class_models[model_name]["preds"] = y_test_predict
# Вычисление метрик для регрессии
class_models[model_name]["MSE_train"] = metrics.mean_squared_error(y_train, y_train_predict)
class_models[model_name]["MSE_test"] = metrics.mean_squared_error(y_test, y_test_predict)
class_models[model_name]["MAE_train"] = metrics.mean_absolute_error(y_train, y_train_predict)
class_models[model_name]["MAE_test"] = metrics.mean_absolute_error(y_test, y_test_predict)
class_models[model_name]["R2_train"] = metrics.r2_score(y_train, y_train_predict)
class_models[model_name]["R2_test"] = metrics.r2_score(y_test, y_test_predict)
# Дополнительные метрики
class_models[model_name]["STD_train"] = np.std(y_train - y_train_predict)
class_models[model_name]["STD_test"] = np.std(y_test - y_test_predict)
# Вывод результатов для текущей модели
print(f"MSE (train): {class_models[model_name]['MSE_train']}")
print(f"MSE (test): {class_models[model_name]['MSE_test']}")
print(f"MAE (train): {class_models[model_name]['MAE_train']}")
print(f"MAE (test): {class_models[model_name]['MAE_test']}")
print(f"R2 (train): {class_models[model_name]['R2_train']}")
print(f"R2 (test): {class_models[model_name]['R2_test']}")
print(f"STD (train): {class_models[model_name]['STD_train']}")
print(f"STD (test): {class_models[model_name]['STD_test']}")
print("-" * 40) # Разделитель для разных моделей
Пример использования обученной модели (конвейера регрессии) для предсказания
Подбор гиперпараметров методом поиска по сетке
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.ensemble import RandomForestRegressor
from sklearn.preprocessing import StandardScaler
# Convert the date column to a datetime object and extract numeric features
df['date'] = pd.to_datetime(df['date'], errors='coerce') # Coerce invalid dates to NaT
df.dropna(subset=['date'], inplace=True) # Drop rows with invalid dates
df['year'] = df['date'].dt.year
df['month'] = df['date'].dt.month
df['day'] = df['date'].dt.day
# Prepare predictors and target
X = df[['yr_built', 'year', 'month', 'day', 'price', 'price_category']]
y = df['average_price']
# Split data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Define model and parameter grid
model = RandomForestRegressor()
param_grid = {
'n_estimators': [50, 100, 200],
'max_depth': [None, 10, 20, 30],
'min_samples_split': [2, 5, 10]
}
# Hyperparameter tuning with GridSearchCV
grid_search = GridSearchCV(estimator=model, param_grid=param_grid,
scoring='neg_mean_squared_error', cv=5, n_jobs=-1, verbose=2)
# Fit the model
grid_search.fit(X_train, y_train)
# Output the best parameters and score
print("Best parameters:", grid_search.best_params_)
print("Best MSE:", -grid_search.best_score_)
Обучение модели с новыми гиперпараметрами и сравнение новых и старых данных
import pandas as pd
import numpy as np
from sklearn import metrics
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split, GridSearchCV
import matplotlib.pyplot as plt
# 1. Настройка параметров для старых значений
old_param_grid = {
'n_estimators': [50, 100, 200], # Количество деревьев
'max_depth': [None, 10, 20, 30], # Максимальная глубина дерева
'min_samples_split': [2, 10, 15] # Минимальное количество образцов для разбиения узла
}
# Подбор гиперпараметров с помощью Grid Search для старых параметров
old_grid_search = GridSearchCV(estimator=RandomForestRegressor(),
param_grid=old_param_grid, scoring='neg_mean_squared_error', cv=5, n_jobs=-1, verbose=2)
# Обучение модели на тренировочных данных
old_grid_search.fit(X_train, y_train)
# 2. Результаты подбора для старых параметров
old_best_params = old_grid_search.best_params_
old_best_mse = -old_grid_search.best_score_ # Меняем знак, так как берем отрицательное значение MSE
# 3. Настройка параметров для новых значений
new_param_grid = {
'n_estimators': [200],
'max_depth': [10],
'min_samples_split': [10]
}
# Подбор гиперпараметров с помощью Grid Search для новых параметров
new_grid_search = GridSearchCV(estimator=RandomForestRegressor(),
param_grid=new_param_grid, scoring='neg_mean_squared_error', cv=2)
# Обучение модели на тренировочных данных
new_grid_search.fit(X_train, y_train)
# 4. Результаты подбора для новых параметров
new_best_params = new_grid_search.best_params_
new_best_mse = -new_grid_search.best_score_ # Меняем знак, так как берем отрицательное значение MSE
# 5. Обучение модели с лучшими параметрами для новых значений
model_best = RandomForestRegressor(**new_best_params)
model_best.fit(X_train, y_train)
# Прогнозирование на тестовой выборке
y_pred = model_best.predict(X_test)
# Оценка производительности модели
mse = metrics.mean_squared_error(y_test, y_pred)
rmse = np.sqrt(mse)
# Вывод результатов
print("Старые параметры:", old_best_params)
print("Лучший результат (MSE) на старых параметрах:", old_best_mse)
print("\nНовые параметры:", new_best_params)
print("Лучший результат (MSE) на новых параметрах:", new_best_mse)
print("Среднеквадратическая ошибка (MSE) на тестовых данных:", mse)
print("Корень среднеквадратичной ошибки (RMSE) на тестовых данных:", rmse)
# Визуализация ошибок
plt.figure(figsize=(10, 5))
plt.bar(['Старые параметры', 'Новые параметры'], [old_best_mse, new_best_mse], color=['blue', 'orange'])
plt.xlabel('Подбор параметров')
plt.ylabel('Среднеквадратическая ошибка (MSE)')
plt.title('Сравнение MSE для старых и новых параметров')
plt.show()
Сравнивая результаты старых и новых параметров, можно сказать, что старые параметры модели позволили добиться меньшей среднеквадратической ошибки, что указывает на более эффективное предсказание по сравнению с новыми настройками. Скорее всего модель обучена достаточно хорошо, учитывая следующие факторы:
- Показатели MSE на тренировочных (0.159) и тестовых данных (0.1589) очень близки. Это говорит о том, что модель не переобучена и не недообучена — она хорошо обобщает на тестовой выборке, что является желаемым результатом.
- Старые параметры дали наилучший результат, так что модель способна выдать высокую точность при настройке гиперпараметров. Попытка с новыми параметрами позволила оценить, как модель реагирует на изменения параметров, и выяснить, что увеличение max_depth и снижение min_samples_split улучшили результат. Этот процесс настройки параметров — часть процесса улучшения модели.
- Старые параметры дали наилучший результат, так что модель способна выдать высокую точность при настройке гиперпараметров. Попытка с новыми параметрами позволила оценить, как модель реагирует на изменения параметров, и выяснить, что увеличение max_depth и снижение min_samples_split улучшили результат. Этот процесс настройки параметров — часть процесса улучшения модели.
Таким образом, можно сказать, что модель обучена хорошо, но возможны дальнейшие небольшие улучшения за счет оптимизации гиперпараметров.
plt.figure(figsize=(10, 5))
plt.scatter(range(len(y_test)), y_test, label="Актуалочка", color="black", alpha=0.5)
plt.scatter(range(len(y_test)), y_pred, label="Предсказанные(новые параметры)", color="blue", alpha=0.5)
plt.scatter(range(len(y_test)), y_test_predict, label="Предсказанные(старые параметры)", color="red", alpha=0.5)
plt.xlabel("Выборка")
plt.ylabel("Значения")
plt.legend()
plt.title("Актуалочка vs Предсказанных значений (Новые and Старые Параметры)")
plt.show()
Ураааа! Усёёёё, вроде бы всё ^_^