NLP_Homework_1 / src /model_evaluation.py
Kolesnikov Dmitry
feat: Попытка навайбкодить 3 и 4 лабораторные
68545bc
"""
Модуль для оценки качества моделей классификации и настройки гиперпараметров.
Включает кросс-валидацию, подбор гиперпараметров и комплексные метрики.
"""
from __future__ import annotations
import time
from typing import List, Dict, Any, Optional, Tuple, Union
from dataclasses import dataclass
import numpy as np
import pandas as pd
from sklearn.model_selection import (
GridSearchCV, RandomizedSearchCV, StratifiedKFold,
cross_val_score, train_test_split
)
from sklearn.metrics import (
accuracy_score, precision_score, recall_score, f1_score,
roc_auc_score, classification_report, confusion_matrix,
precision_recall_curve, roc_curve, average_precision_score
)
try:
import optuna
OPTUNA_AVAILABLE = True
except ImportError:
OPTUNA_AVAILABLE = False
print("⚠️ Optuna не установлен. Bayesian optimization недоступен.")
try:
from hyperopt import fmin, tpe, hp, Trials, STATUS_OK
HYPEROPT_AVAILABLE = True
except ImportError:
HYPEROPT_AVAILABLE = False
print("⚠️ Hyperopt не установлен. Bayesian optimization недоступен.")
@dataclass
class EvaluationMetrics:
"""Контейнер для метрик оценки."""
accuracy: float
precision_macro: float
recall_macro: float
f1_macro: float
precision_micro: float
recall_micro: float
f1_micro: float
roc_auc: Optional[float] = None
pr_auc: Optional[float] = None
train_time: float = 0.0
predict_time: float = 0.0
def evaluate_classifier(y_true: np.ndarray,
y_pred: np.ndarray,
y_proba: Optional[np.ndarray] = None,
task_type: str = "multiclass") -> EvaluationMetrics:
"""
Комплексная оценка классификатора.
Args:
y_true: Истинные метки
y_pred: Предсказанные метки
y_proba: Вероятности классов
task_type: Тип задачи (binary, multiclass, multilabel)
Returns:
Объект EvaluationMetrics
"""
metrics = EvaluationMetrics(
accuracy=accuracy_score(y_true, y_pred),
precision_macro=precision_score(y_true, y_pred, average='macro', zero_division=0),
recall_macro=recall_score(y_true, y_pred, average='macro', zero_division=0),
f1_macro=f1_score(y_true, y_pred, average='macro', zero_division=0),
precision_micro=precision_score(y_true, y_pred, average='micro', zero_division=0),
recall_micro=recall_score(y_true, y_pred, average='micro', zero_division=0),
f1_micro=f1_score(y_true, y_pred, average='micro', zero_division=0),
)
# ROC-AUC для бинарной классификации
if task_type == "binary" and y_proba is not None:
if y_proba.shape[1] == 2:
try:
metrics.roc_auc = roc_auc_score(y_true, y_proba[:, 1])
metrics.pr_auc = average_precision_score(y_true, y_proba[:, 1])
except:
pass
elif y_proba.shape[1] == 1:
try:
metrics.roc_auc = roc_auc_score(y_true, y_proba.flatten())
metrics.pr_auc = average_precision_score(y_true, y_proba.flatten())
except:
pass
# ROC-AUC для многоклассовой (macro)
elif task_type == "multiclass" and y_proba is not None:
try:
metrics.roc_auc = roc_auc_score(y_true, y_proba, average='macro', multi_class='ovr')
except:
pass
return metrics
def cross_validate(model, X: np.ndarray, y: np.ndarray,
cv: int = 5,
scoring: str = 'f1_macro',
return_train_score: bool = False) -> Dict[str, Any]:
"""
Кросс-валидация модели.
Args:
model: Модель с интерфейсом sklearn
X: Признаки
y: Метки
cv: Количество фолдов
scoring: Метрика для оценки
return_train_score: Возвращать ли оценки на обучении
Returns:
Словарь с результатами кросс-валидации
"""
cv_scores = cross_val_score(
model, X, y,
cv=StratifiedKFold(n_splits=cv, shuffle=True, random_state=42),
scoring=scoring,
return_train_score=return_train_score
)
result = {
"mean": float(cv_scores.mean()),
"std": float(cv_scores.std()),
"scores": cv_scores.tolist()
}
if return_train_score and hasattr(cv_scores, 'train_scores'):
result["train_mean"] = float(cv_scores.train_scores.mean())
result["train_std"] = float(cv_scores.train_scores.std())
return result
def grid_search(model, X: np.ndarray, y: np.ndarray,
param_grid: Dict[str, List[Any]],
cv: int = 5,
scoring: str = 'f1_macro',
n_jobs: int = -1) -> Dict[str, Any]:
"""
Подбор гиперпараметров методом Grid Search.
Args:
model: Модель с интерфейсом sklearn
X: Признаки
y: Метки
param_grid: Сетка параметров
cv: Количество фолдов
scoring: Метрика для оценки
n_jobs: Количество параллельных задач
Returns:
Словарь с лучшими параметрами и результатами
"""
grid_search = GridSearchCV(
model,
param_grid,
cv=StratifiedKFold(n_splits=cv, shuffle=True, random_state=42),
scoring=scoring,
n_jobs=n_jobs,
verbose=1
)
start = time.time()
grid_search.fit(X, y)
search_time = time.time() - start
return {
"best_params": grid_search.best_params_,
"best_score": float(grid_search.best_score_),
"best_model": grid_search.best_estimator_,
"search_time": search_time,
"cv_results": grid_search.cv_results_
}
def random_search(model, X: np.ndarray, y: np.ndarray,
param_distributions: Dict[str, List[Any]],
n_iter: int = 50,
cv: int = 5,
scoring: str = 'f1_macro',
n_jobs: int = -1) -> Dict[str, Any]:
"""
Подбор гиперпараметров методом Random Search.
Args:
model: Модель с интерфейсом sklearn
X: Признаки
y: Метки
param_distributions: Распределения параметров
n_iter: Количество итераций
cv: Количество фолдов
scoring: Метрика для оценки
n_jobs: Количество параллельных задач
Returns:
Словарь с лучшими параметрами и результатами
"""
random_search = RandomizedSearchCV(
model,
param_distributions,
n_iter=n_iter,
cv=StratifiedKFold(n_splits=cv, shuffle=True, random_state=42),
scoring=scoring,
n_jobs=n_jobs,
random_state=42,
verbose=1
)
start = time.time()
random_search.fit(X, y)
search_time = time.time() - start
return {
"best_params": random_search.best_params_,
"best_score": float(random_search.best_score_),
"best_model": random_search.best_estimator_,
"search_time": search_time,
"cv_results": random_search.cv_results_
}
def optuna_optimize(model_class, X: np.ndarray, y: np.ndarray,
param_space: Dict[str, Any],
n_trials: int = 50,
cv: int = 5,
scoring: str = 'f1_macro') -> Dict[str, Any]:
"""
Подбор гиперпараметров методом Bayesian Optimization (Optuna).
Args:
model_class: Класс модели
X: Признаки
y: Метки
param_space: Пространство параметров (функции для Optuna)
n_trials: Количество испытаний
cv: Количество фолдов
scoring: Метрика для оценки
Returns:
Словарь с лучшими параметрами и результатами
"""
if not OPTUNA_AVAILABLE:
raise ImportError("Optuna не установлен. Установите: pip install optuna")
def objective(trial):
params = {}
for param_name, param_func in param_space.items():
params[param_name] = param_func(trial)
model = model_class(**params)
scores = cross_val_score(
model, X, y,
cv=StratifiedKFold(n_splits=cv, shuffle=True, random_state=42),
scoring=scoring
)
return scores.mean()
study = optuna.create_study(direction='maximize', study_name='classifier_optimization')
start = time.time()
study.optimize(objective, n_trials=n_trials, show_progress_bar=True)
search_time = time.time() - start
# Обучаем лучшую модель
best_model = model_class(**study.best_params)
best_model.fit(X, y)
return {
"best_params": study.best_params,
"best_score": float(study.best_value),
"best_model": best_model,
"search_time": search_time,
"study": study
}
def create_confusion_matrix_plot(y_true: np.ndarray, y_pred: np.ndarray,
class_names: Optional[List[str]] = None) -> pd.DataFrame:
"""
Создает матрицу ошибок.
Args:
y_true: Истинные метки
y_pred: Предсказанные метки
class_names: Названия классов
Returns:
DataFrame с матрицей ошибок
"""
cm = confusion_matrix(y_true, y_pred)
if class_names is None:
class_names = [f"Класс {i}" for i in range(len(cm))]
df = pd.DataFrame(cm, index=class_names, columns=class_names)
return df
def create_classification_report_df(y_true: np.ndarray, y_pred: np.ndarray,
class_names: Optional[List[str]] = None) -> pd.DataFrame:
"""
Создает отчет о классификации.
Args:
y_true: Истинные метки
y_pred: Предсказанные метки
class_names: Названия классов
Returns:
DataFrame с отчетом
"""
report = classification_report(y_true, y_pred, target_names=class_names, output_dict=True)
df = pd.DataFrame(report).transpose()
return df
if __name__ == "__main__":
# Тестирование
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
X, y = make_classification(n_samples=1000, n_features=20, n_classes=3, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Обучение модели
model = LogisticRegression(max_iter=1000, random_state=42)
model.fit(X_train, y_train)
# Оценка
y_pred = model.predict(X_test)
y_proba = model.predict_proba(X_test)
metrics = evaluate_classifier(y_test, y_pred, y_proba, task_type="multiclass")
print("Метрики:")
print(f"Accuracy: {metrics.accuracy:.4f}")
print(f"F1 (macro): {metrics.f1_macro:.4f}")
print(f"ROC-AUC: {metrics.roc_auc:.4f if metrics.roc_auc else 'N/A'}")
# Кросс-валидация
cv_results = cross_validate(model, X_train, y_train, cv=5)
print(f"\nКросс-валидация F1: {cv_results['mean']:.4f} ± {cv_results['std']:.4f}")
# Grid Search
param_grid = {
'C': [0.1, 1, 10],
'penalty': ['l1', 'l2']
}
# grid_results = grid_search(model, X_train, y_train, param_grid, cv=3)
# print(f"\nЛучшие параметры (Grid Search): {grid_results['best_params']}")