269 KiB
Лабораторная работа 4 (Diamonds Prices)¶
import pandas as pd
df = pd.read_csv(".//static//csv//Diamonds Prices2022.csv")
print(df.info)
Задача регрессии¶
Бизнес-цель для задачи регрессии¶
Предсказать цену бриллианта на основе его характеристик
Зачем это нужно?
Для покупателей: Упрощение выбора бриллианта, основываясь на соотношении цена-качество.
Для продавцов: Оптимизация ценовой политики и привлечение клиентов за счет точного ценообразования.
Для оценщиков: Ускорение процесса оценки бриллиантов и снижение субъективности
Достижимый уровень качества модели для задачи регрессии:¶
Целевой уровень: MAE < 300, R² > 0.97.
Основание: это позволяет достичь приемлемой точности для предсказания цен бриллиантов с учетом их характеристик.
Линейная регрессия¶
import pandas as pd
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import StandardScaler, MinMaxScaler, MaxAbsScaler
from sklearn.metrics import mean_absolute_error, r2_score
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
import sklearn.preprocessing as preproc
import numpy as np
df = pd.read_csv(".//static//csv//Diamonds Prices2022.csv")
data = df[['carat', 'cut', 'color', 'clarity', 'depth', 'table', 'x', 'y', 'z', 'price']]
# Преобразуем категориальные признаки в числовые через one-hot encoding
data = pd.get_dummies(data, columns=['cut', 'color', 'clarity'], drop_first=True)
# Определение входных и целевых переменных
X = data.drop('price', axis=1) # признаки
y = data['price'] # целевая переменная (цена)
# Разделение данных на обучающую и тестовую выборки
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Подготовка пайплайна для линейной регрессии
pipeline_lin_reg = Pipeline([
('preprocessing', ColumnTransformer([
('num', SimpleImputer(strategy='median'), X.columns)
])),
('model', LinearRegression())
])
# Определение сетки гиперпараметров для поиска лучших значений
param_grid = {
'preprocessing': [StandardScaler(), MinMaxScaler(), MaxAbsScaler(), None] # разные методы масштабирования
}
# Создание объекта GridSearchCV для поиска лучших гиперпараметров
grid_search = GridSearchCV(pipeline_lin_reg, param_grid, cv=5, scoring='neg_root_mean_squared_error', n_jobs=-1)
# Обучение модели с перебором гиперпараметров
grid_search.fit(X_train, y_train)
# Вывод лучших гиперпараметров
print("Лучшие гиперпараметры: ", grid_search.best_params_)
# Лучшая модель линейной регрессии
best_model = grid_search.best_estimator_
# Прогнозирование на тестовых данных
y_pred = best_model.predict(X_test)
# Оценка модели: Средняя абсолютная ошибка (MAE)
mae = mean_absolute_error(y_test, y_pred)
print(f'Cредняя абсолютная ошибка (MAE) = {mae}')
# Оценка качества модели: R^2
r2 = r2_score(y_test, y_pred)
print(f'R^2 = {r2}')
# Оценка дисперсии и смещения
cv_results = grid_search.cv_results_
mean_test_score = np.mean(cv_results['mean_test_score'])
std_test_score = np.std(cv_results['mean_test_score'])
print(f"Смещение: {mean_test_score}")
print(f"Дисперсия: {std_test_score}")
Градиентный бустинг¶
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.model_selection import cross_val_predict
# Преобразование числовых данных (заполнение пустых значений медианой)
num_imputer = SimpleImputer(strategy="median")
preprocessing_num = Pipeline([("imputer", num_imputer)])
# Общая предобработка (только числовые данные)
preprocessing = ColumnTransformer(
[("nums", preprocessing_num, X_train.columns)]
)
# Конвейер
pipeline_grad = Pipeline([
('preprocessing', preprocessing),
('model', GradientBoostingRegressor(random_state=42))
])
# Определение сетки гиперпараметров
param_grid = {
'preprocessing': [StandardScaler(), preproc.MinMaxScaler(), preproc.MaxAbsScaler(), None],
'model__n_estimators': [100, 200, 300],
'model__learning_rate': [0.1, 0.2],
'model__max_depth': [3, 5, 7]
}
# Создание объекта GridSearchCV
grid_search = GridSearchCV(pipeline_grad, param_grid, cv=2, scoring='neg_root_mean_squared_error', n_jobs=-1)
# Обучение модели с перебором гиперпараметров
grid_search.fit(X_train, y_train)
# Вывод лучших гиперпараметров
print("Лучшие гиперпараметры: ", grid_search.best_params_)
# Лучшая модель градиентного бустинга
best_model = grid_search.best_estimator_
# Предсказания на тестовой выборке
y_pred = best_model.predict(X_test)
# Оценка качества
print(f'Cредняя абсолютная ошибка (MAE) = {mean_absolute_error(y_test, y_pred)}')
# Предсказания на кросс-валидации
y_cv_pred = cross_val_predict(best_model, X_train, y_train, cv=3)
# Оценка смещения и дисперсии
cv_results = grid_search.cv_results_
mean_test_score = cv_results['mean_test_score']
std_test_score = cv_results['std_test_score']
print(f"Смещение: {mean_test_score.mean()}")
print(f"Дисперсия: {std_test_score.mean()}")
# Коэффициент детерминации R^2
print(f'R^2 = {r2_score(y_test, y_pred)}')
Случаные леса¶
from sklearn.ensemble import RandomForestRegressor
# Конвейер
pipeline_forest = Pipeline([
('preprocessing', preprocessing),
('model', RandomForestRegressor(random_state=42))
])
# Определение сетки гиперпараметров
param_grid = {
'preprocessing': [StandardScaler(), preproc.MinMaxScaler(), preproc.MaxAbsScaler(), None],
'model__n_estimators': [100, 200, 300], # Количество деревьев
'model__max_depth': [None, 5, 10], # Максимальная глубина деревьев
'model__min_samples_split': [2, 5], # Минимальное число образцов для разделения
}
# Создание объекта GridSearchCV
grid_search = GridSearchCV(pipeline_forest, param_grid, cv=3, scoring='neg_root_mean_squared_error', n_jobs=-1)
# Обучение модели с перебором гиперпараметров
grid_search.fit(X_train, y_train)
print("Лучшие гиперпараметры: ", grid_search.best_params_)
# Лучшая модель случайного леса
best_model = grid_search.best_estimator_
y_pred = best_model.predict(X_test)
print(f'Средняя абсолютная ошибка (MAE) = {mean_absolute_error(y_test, y_pred)}')
# Получение предсказаний на кросс-валидации
y_cv_pred = cross_val_predict(best_model, X_train, y_train, cv=3)
cv_results = grid_search.cv_results_
mean_test_score = cv_results['mean_test_score']
std_test_score = cv_results['std_test_score']
print(f"Смещение: {mean_test_score.mean()}")
print(f"Дисперсия: {std_test_score.mean()}")
print(f'R^2 = {r2_score(y_test, y_pred)}')
Вывод:¶
Линейная регрессия:
MAE: 731.55 — наибольшая ошибка среди моделей.
R²: 0.9222 — показывает, что модель объясняет 92.2% дисперсии, но хуже справляется с нелинейными зависимостями.
Смещение: -1138.41 — модель недооценивает значения (высокое смещение).
Дисперсия: 4.40e-13 — практически нулевая, модель стабильна.
Вывод: Линейная регрессия не справляется с учетом сложных взаимосвязей между признаками.
Градиентный бустинг:
MAE: 282.34 — самая низкая ошибка, лучшая модель.
R²: 0.9791 — объясняет 97.9% дисперсии, хорошо справляется с нелинейными зависимостями.
Смещение: -696.99 — приемлемое смещение, модель лучше оценивает данные.
Дисперсия: 4.39 — модель стабильна.
Вывод: Градиентный бустинг — наиболее точная и сбалансированная модель.
Случайные леса:
MAE: 292.26 — немного хуже, чем у градиентного бустинга.
R²: 0.9743 — объясняет 97.4% дисперсии.
Смещение: -882.53 — модель недооценивает значения сильнее, чем градиентный бустинг.
Дисперсия: 11.13 — несколько выше, чем у других моделей.
Вывод: Случайные леса также подходят для задачи, но уступают градиентному бустингу.
Градиентный бустинг выбран как лучшая модель из-за низкого MAE и сбалансированных смещения и дисперсии. Случайные леса также могут быть использованы, но с дополнительной настройкой гиперпараметров.
Задача классификация¶
Бизнес-цель для задачи регрессии¶
Спрогнозировать категорию огранки бриллианта.
Это поможет:
- Улучшить процессы оценки качества бриллиантов.
- Упростить классификацию для ювелирных компаний.
- Повысить точность ценообразования и автоматизировать принятие решений.
Спрогнозировать категорию категория огранки на основе его характеристик
Метрики в задаче классификации¶
ROC AUC: Измеряет способность модели разделять классы. Чем ближе к 1, тем лучше.
Точность (Accuracy): Доля правильно классифицированных объектов.
Смещение: Показывает, насколько среднее значение предсказаний отличается от истинного класса.
Дисперсия: Описывает изменчивость предсказаний модели при разных обучающих наборах.
Достижимый уровень качества модели для задачи классификации¶
Целевой уровень: ROC AUC > 0.99, Точность > 0.95.
Основание: Эти значения обеспечивают высокую точность и надежность при прогнозировании категории огранки бриллианта, минимизируя количество ошибок и повышая доверие к автоматизированной системе оценки качества.
Логистическая регрессия¶
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import GridSearchCV, train_test_split
from sklearn.metrics import roc_auc_score, confusion_matrix, accuracy_score
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn import metrics
df = pd.read_csv(".//static//csv//Diamonds Prices2022.csv")
df.columns = df.columns.str.strip()
# Создание целевой переменной на основе столбца 'price'
bins = [0, 1000, 5000, float('inf')]
labels = ['Low', 'Medium', 'High']
df['price_category'] = pd.cut(df['price'], bins=bins, labels=labels)
# Преобразуем целевую переменную в категориальный тип
y = pd.Categorical(df['price_category'])
# Подготовка данных
data = df[['carat', 'cut', 'color', 'clarity', 'depth', 'table', 'x', 'y', 'z', 'price_category']] # Целевая переменная - 'price_category'
X = data.drop('price_category', axis=1) # Признаки
y = data['price_category'] # Целевая переменная
# Разделение данных на обучающую и тестовую выборки
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Преобразование числовых данных
num_imputer = SimpleImputer(strategy="median") # Замена пропусков медианой
num_scaler = StandardScaler() # Стандартизация
preprocessing_num = Pipeline(
[
("imputer", num_imputer),
("scaler", num_scaler),
]
)
# Преобразование категориальных данных
cat_imputer = SimpleImputer(strategy="most_frequent") # Заполнение пропусков наиболее частым значением
cat_encoder = OneHotEncoder(handle_unknown='ignore') # Преобразование категориальных признаков в OneHot
preprocessing_cat = Pipeline(
[
("imputer", cat_imputer),
("encoder", cat_encoder),
]
)
# Общая предобработка
preprocessing = ColumnTransformer(
[
("nums", preprocessing_num, X.select_dtypes(include=['float64', 'int64']).columns), # Числовые признаки
("cats", preprocessing_cat, X.select_dtypes(include=['object']).columns), # Категориальные признаки
]
)
# Конвейер для логистической регрессии
pipeline_logreg = Pipeline([
('preprocessing', preprocessing),
('classifier', LogisticRegression(max_iter=1000, multi_class='ovr', solver='liblinear')) # Указание решателя и метода многоклассовой классификации
])
# Определение сетки гиперпараметров
param_grid = {
'classifier__C': [0.1, 0.5, 1],
'classifier__penalty': ['l1', 'l2'],
'classifier__solver': ['liblinear', 'saga']
}
# Создание объекта GridSearchCV для поиска лучших гиперпараметров
grid_search = GridSearchCV(pipeline_logreg, param_grid, cv=5, scoring='accuracy', n_jobs=-1)
# Обучение модели с перебором гиперпараметров
grid_search.fit(X_train, y_train)
print("Лучшие гиперпараметры: ", grid_search.best_params_)
# Лучшая модель логистической регрессии
best_model = grid_search.best_estimator_
# Использование и оценка лучшей логистической модели
y_pred_proba = best_model.predict_proba(X_test) # Получаем вероятности для всех классов
# Для многоклассовой классификации AUC считается для каждого класса
roc_auc = roc_auc_score(y_test, y_pred_proba, multi_class='ovr', average='macro')
print(f'ROC AUC у логистической регрессии = {roc_auc}')
y_pred = best_model.predict(X_test)
print(f'Точность = {accuracy_score(y_test, y_pred)}')
# Построение ROC кривой для каждого класса
# Пример для класса "Medium"
fpr, tpr, _ = metrics.roc_curve(y_test, y_pred_proba[:, 1], pos_label='Medium')
plt.plot(fpr, tpr)
plt.ylabel('True Positive Rate')
plt.xlabel('False Positive Rate')
plt.title('ROC Curve для класса "Medium"')
plt.show()
# Построение матрицы ошибок
conf_matrix = confusion_matrix(y_test, y_pred)
# Визуализация матрицы ошибок
plt.figure(figsize=(8, 6))
sns.heatmap(conf_matrix, annot=True, fmt='d', cmap='Blues',
xticklabels=['Предсказанный "Low"', 'Предсказанный "Medium"', 'Предсказанный "High"'],
yticklabels=['Действительно "Low"', 'Действительно "Medium"', 'Действительно "High"'])
plt.title('Confusion Matrix')
plt.ylabel('Actual')
plt.xlabel('Predicted')
plt.show()
# Оценка смещения и дисперсии
cv_results = grid_search.cv_results_
mean_test_score = cv_results['mean_test_score']
std_test_score = cv_results['std_test_score']
print(f"Смещение: {mean_test_score.mean()}")
print(f"Дисперсия: {std_test_score.mean()}")
Градиентный букинг¶
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import GridSearchCV, train_test_split
from sklearn.metrics import roc_auc_score, confusion_matrix, accuracy_score
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn import metrics
df = pd.read_csv(".//static//csv//Diamonds Prices2022.csv")
df.columns = df.columns.str.strip()
# Создание целевой переменной на основе столбца 'price'
bins = [0, 1000, 5000, float('inf')]
labels = ['Low', 'Medium', 'High']
df['price_category'] = pd.cut(df['price'], bins=bins, labels=labels)
# Преобразуем целевую переменную в категориальный тип
y = pd.Categorical(df['price_category'])
# Подготовка данных
data = df[['carat', 'cut', 'color', 'clarity', 'depth', 'table', 'x', 'y', 'z', 'price_category']] # Целевая переменная - 'price_category'
X = data.drop('price_category', axis=1) # Признаки
y = data['price_category'] # Целевая переменная
# Разделение данных на обучающую и тестовую выборки
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Преобразование числовых данных
num_imputer = SimpleImputer(strategy="median") # Замена пропусков медианой
num_scaler = StandardScaler() # Стандартизация
preprocessing_num = Pipeline(
[
("imputer", num_imputer),
("scaler", num_scaler),
]
)
# Преобразование категориальных данных
cat_imputer = SimpleImputer(strategy="most_frequent")
cat_encoder = OneHotEncoder(handle_unknown='ignore')
preprocessing_cat = Pipeline(
[
("imputer", cat_imputer),
("encoder", cat_encoder),
]
)
# Общая предобработка
preprocessing = ColumnTransformer(
[
("nums", preprocessing_num, X.select_dtypes(include=['float64', 'int64']).columns),
("cats", preprocessing_cat, X.select_dtypes(include=['object']).columns),
]
)
# Конвейер для градиентного бустинга
pipeline_gbc = Pipeline([
('preprocessing', preprocessing),
('classifier', GradientBoostingClassifier(random_state=42)) # Градиентный бустинг
])
# Определение сетки гиперпараметров для градиентного бустинга
param_grid = {
'classifier__n_estimators': [50, 100, 200],
'classifier__learning_rate': [0.01, 0.1, 0.2],
'classifier__max_depth': [3, 5, 7],
'classifier__subsample': [0.8, 1.0]
}
# Создание объекта GridSearchCV для поиска лучших гиперпараметров
grid_search = GridSearchCV(pipeline_gbc, param_grid, cv=5, scoring='accuracy', n_jobs=-1)
# Обучение модели с перебором гиперпараметров
grid_search.fit(X_train, y_train)
print("Лучшие гиперпараметры: ", grid_search.best_params_)
# Лучшая модель градиентного бустинга
best_model = grid_search.best_estimator_
# Использование и оценка лучшей модели
y_pred_proba = best_model.predict_proba(X_test) # Получаем вероятности для всех классов
# Для многоклассовой классификации AUC считается для каждого класса
roc_auc = roc_auc_score(y_test, y_pred_proba, multi_class='ovr', average='macro')
print(f'ROC AUC у градиентного бустинга = {roc_auc}')
y_pred = best_model.predict(X_test)
print(f'Точность = {accuracy_score(y_test, y_pred)}')
# Построение ROC кривой для каждого класса
# Пример для класса "Medium"
fpr, tpr, _ = metrics.roc_curve(y_test, y_pred_proba[:, 1], pos_label='Medium')
plt.plot(fpr, tpr)
plt.ylabel('True Positive Rate')
plt.xlabel('False Positive Rate')
plt.title('ROC Curve для класса "Medium"')
plt.show()
# Построение матрицы ошибок
conf_matrix = confusion_matrix(y_test, y_pred)
# Визуализация матрицы ошибок
plt.figure(figsize=(8, 6))
sns.heatmap(conf_matrix, annot=True, fmt='d', cmap='Blues',
xticklabels=['Предсказанный "Low"', 'Предсказанный "Medium"', 'Предсказанный "High"'],
yticklabels=['Действительно "Low"', 'Действительно "Medium"', 'Действительно "High"'])
plt.title('Confusion Matrix')
plt.ylabel('Actual')
plt.xlabel('Predicted')
plt.show()
# Оценка смещения и дисперсии
cv_results = grid_search.cv_results_
mean_test_score = cv_results['mean_test_score']
std_test_score = cv_results['std_test_score']
print(f"Смещение: {mean_test_score.mean()}")
print(f"Дисперсия: {std_test_score.mean()}")
Случайный лес¶
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import RandomizedSearchCV, train_test_split
from sklearn.metrics import roc_auc_score, confusion_matrix, accuracy_score
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn import metrics
df = pd.read_csv(".//static//csv//Diamonds Prices2022.csv")
# Убираем пробелы в именах столбцов, если есть
df.columns = df.columns.str.strip()
# Создание целевой переменной на основе столбца 'price'
bins = [0, 1000, 5000, float('inf')]
labels = ['Low', 'Medium', 'High']
df['price_category'] = pd.cut(df['price'], bins=bins, labels=labels)
# Преобразуем целевую переменную в категориальный тип
y = pd.Categorical(df['price_category'])
# Подготовка данных
data = df[['carat', 'cut', 'color', 'clarity', 'depth', 'table', 'x', 'y', 'z', 'price_category']] # Целевая переменная - 'price_category'
X = data.drop('price_category', axis=1) # Признаки
y = data['price_category'] # Целевая переменная
# Разделение данных на обучающую и тестовую выборки
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Преобразование числовых данных
num_imputer = SimpleImputer(strategy="median") # Замена пропусков медианой
num_scaler = StandardScaler() # Стандартизация
preprocessing_num = Pipeline(
[
("imputer", num_imputer),
("scaler", num_scaler),
]
)
# Преобразование категориальных данных
cat_imputer = SimpleImputer(strategy="most_frequent") # Заполнение пропусков наиболее частым значением
cat_encoder = OneHotEncoder(handle_unknown='ignore') # Преобразование категориальных признаков в OneHot
preprocessing_cat = Pipeline(
[
("imputer", cat_imputer),
("encoder", cat_encoder),
]
)
# Общая предобработка
preprocessing = ColumnTransformer(
[
("nums", preprocessing_num, X.select_dtypes(include=['float64', 'int64']).columns), # Числовые признаки
("cats", preprocessing_cat, X.select_dtypes(include=['object']).columns), # Категориальные признаки
]
)
# Конвейер для случайного леса
pipeline_rf = Pipeline([
('preprocessing', preprocessing),
('classifier', RandomForestClassifier(random_state=42)) # Модель случайного леса
])
# Определение сетки гиперпараметров
param_grid = {
'classifier__n_estimators': [50, 100, 150], # Количество деревьев
'classifier__max_depth': [10, 20, None], # Глубина деревьев
'classifier__min_samples_split': [2, 5, 10], # Минимальное количество образцов для разделения
'classifier__min_samples_leaf': [1, 2, 4], # Минимальное количество образцов в листьях
'classifier__max_features': ['sqrt', 'log2'] # Количество признаков для каждого дерева
}
# Создание объекта RandomizedSearchCV для поиска лучших гиперпараметров
random_search = RandomizedSearchCV(pipeline_rf, param_grid, n_iter=10, cv=5, scoring='accuracy', n_jobs=-1, random_state=42)
# Обучение модели с перебором гиперпараметров
random_search.fit(X_train, y_train)
print("Лучшие гиперпараметры: ", random_search.best_params_)
# Лучшая модель случайного леса
best_model = random_search.best_estimator_
# Использование и оценка лучшей модели случайного леса
y_pred_proba = best_model.predict_proba(X_test) # Получаем вероятности для всех классов
# Для многоклассовой классификации используем roc_auc_score с multi_class='ovr'
print(f'ROC AUC для случайного леса = {roc_auc_score(y_test, y_pred_proba, multi_class="ovr")}')
y_pred = best_model.predict(X_test)
print(f'Точность = {accuracy_score(y_test, y_pred)}')
# Построение ROC кривой для многоклассовой задачи
fpr, tpr, _ = metrics.roc_curve(y_test, y_pred_proba[:, 2], pos_label='High') # Указываем класс для которого строим ROC
plt.plot(fpr, tpr)
plt.ylabel('True Positive Rate')
plt.xlabel('False Positive Rate')
plt.title('ROC Curve')
plt.show()
# Построение матрицы ошибок
conf_matrix = confusion_matrix(y_test, y_pred)
# Визуализация матрицы ошибок
plt.figure(figsize=(8, 6))
sns.heatmap(conf_matrix, annot=True, fmt='d', cmap='Blues',
xticklabels=['Предсказанный "Low"', 'Предсказанный "Medium"', 'Предсказанный "High"'],
yticklabels=['Действительно "Low"', 'Действительно "Medium"', 'Действительно "High"'])
plt.title('Confusion Matrix')
plt.ylabel('Actual')
plt.xlabel('Predicted')
plt.show()
# Оценка смещения и дисперсии
cv_results = random_search.cv_results_
mean_test_score = cv_results['mean_test_score']
std_test_score = cv_results['std_test_score']
print(f"Смещение: {mean_test_score.mean()}")
print(f"Дисперсия: {std_test_score.mean()}")
Вывод:¶
Градиентный бустинг показал наивысший ROC AUC (0.995) и точность (0.951), но имеет высокую дисперсию (0.0032). Это указывает на более сложные зависимости, которые модель способна уловить.
Случайный лес близок по качеству к градиентному бустингу (ROC AUC = 0.995, точность = 0.947), при этом демонстрирует меньшую дисперсию. Это делает его более стабильным при изменении данных.
Логистическая регрессия уступает обоим ансамблевым методам, но имеет минимальную дисперсию (0.0018), что делает её менее подверженной переобучению.