Spaces:
Sleeping
Sleeping
Commit
·
89f0e63
1
Parent(s):
3633596
correction bug metrics bien implémentés
Browse files
src/cuml_trainer.py
CHANGED
@@ -8,7 +8,6 @@ import cupy as cp
|
|
8 |
from scipy.sparse import csr_matrix
|
9 |
import cudf
|
10 |
from cuml.model_selection import train_test_split
|
11 |
-
import logging
|
12 |
|
13 |
from config import Config
|
14 |
from base_trainer import BaseTrainer
|
@@ -39,7 +38,6 @@ class CuMLTrainer(BaseTrainer, ABC):
|
|
39 |
super().__init__(config, data_path, target_column)
|
40 |
self.vectorizer: Vectorizer = None
|
41 |
self.classifier: object = None # Déjà dans BaseTrainer, mais redéfini pour clarté
|
42 |
-
self.logger = logging.getLogger(__name__)
|
43 |
|
44 |
# Attributs pour stocker les données splittées (texte brut)
|
45 |
self.X_train_text: Optional[cudf.Series] = None
|
@@ -69,10 +67,8 @@ class CuMLTrainer(BaseTrainer, ABC):
|
|
69 |
Stocke les résultats dans les attributs de l'instance.
|
70 |
"""
|
71 |
if self.X_train_text is not None: # Évite de recharger/resplitter
|
72 |
-
self.logger.info("Données déjà chargées et splittées.")
|
73 |
return
|
74 |
|
75 |
-
self.logger.info(f"Chargement des données depuis {self.data_path}...")
|
76 |
data = cudf.read_csv(self.data_path)
|
77 |
|
78 |
# Identification et concaténation des features
|
@@ -82,7 +78,6 @@ class CuMLTrainer(BaseTrainer, ABC):
|
|
82 |
texts_concatenated = data[feature_columns].astype(str).agg(' '.join, axis=1)
|
83 |
labels = data[self.target_column].astype(self._get_label_dtype()).values
|
84 |
|
85 |
-
self.logger.info("Séparation des données en train/validation/test (80/10/10)...")
|
86 |
# Premier split: 80% train, 20% temp (pour val+test)
|
87 |
X_train, X_temp, y_train, y_temp = train_test_split(
|
88 |
texts_concatenated, labels, test_size=test_size, random_state=random_state, stratify=labels
|
@@ -103,8 +98,6 @@ class CuMLTrainer(BaseTrainer, ABC):
|
|
103 |
self.y_val = y_val
|
104 |
self.y_test = y_test
|
105 |
|
106 |
-
self.logger.info(f"Taille Train: {len(self.X_train_text)}, Val: {len(self.X_val_text)}, Test: {len(self.X_test_text)}")
|
107 |
-
|
108 |
|
109 |
def train(self) -> None:
|
110 |
"""
|
@@ -116,7 +109,6 @@ class CuMLTrainer(BaseTrainer, ABC):
|
|
116 |
if self.vectorizer is None or self.classifier is None:
|
117 |
raise RuntimeError("Les composants (vectorizer, classifier) doivent être construits avant l'entraînement. Appelez build_components().")
|
118 |
|
119 |
-
self.logger.info("Vectorisation des données textuelles...")
|
120 |
# fit_transform sur l'entraînement
|
121 |
self.X_train_vec = self.vectorizer.fit_transform(self.X_train_text)
|
122 |
# transform sur validation et test
|
@@ -126,9 +118,7 @@ class CuMLTrainer(BaseTrainer, ABC):
|
|
126 |
# Préparation pour cuML (conversion en dense si nécessaire)
|
127 |
X_train_prepared = self._prepare_input_for_fit(self.X_train_vec)
|
128 |
|
129 |
-
self.logger.info("Entraînement du modèle...")
|
130 |
self.classifier.fit(X_train_prepared, self.y_train)
|
131 |
-
self.logger.info("Entraînement terminé.")
|
132 |
|
133 |
|
134 |
def evaluate(self, use_validation_set=False) -> dict:
|
@@ -145,12 +135,10 @@ class CuMLTrainer(BaseTrainer, ABC):
|
|
145 |
raise RuntimeError("Le classifieur doit être entraîné avant l'évaluation. Appelez train().")
|
146 |
|
147 |
if use_validation_set:
|
148 |
-
self.logger.info("Évaluation sur l'ensemble de validation...")
|
149 |
X_eval_vec = self.X_val_vec
|
150 |
y_true = self.y_val
|
151 |
dataset_name = "validation"
|
152 |
else:
|
153 |
-
self.logger.info("Évaluation sur l'ensemble de test...")
|
154 |
X_eval_vec = self.X_test_vec
|
155 |
y_true = self.y_test
|
156 |
dataset_name = "test"
|
@@ -159,46 +147,23 @@ class CuMLTrainer(BaseTrainer, ABC):
|
|
159 |
X_eval_prepared = self._prepare_input_for_predict(X_eval_vec)
|
160 |
y_pred = self.classifier.predict(X_eval_prepared)
|
161 |
|
162 |
-
#
|
163 |
-
y_proba =
|
164 |
-
try:
|
165 |
-
# Utilise la méthode _get_positive_probabilities définie dans BaseTrainer
|
166 |
-
# et potentiellement surchargée dans les sous-classes (comme SvmTrainer)
|
167 |
-
y_proba = self._get_positive_probabilities(X_eval_prepared)
|
168 |
-
except NotImplementedError:
|
169 |
-
self.logger.warning("La méthode _get_positive_probabilities n'est pas implémentée pour ce modèle, AUC pourrait être moins précis ou indisponible.")
|
170 |
-
except Exception as e:
|
171 |
-
self.logger.warning(f"Erreur lors de la récupération des probabilités : {e}")
|
172 |
-
|
173 |
|
174 |
-
# Calcul
|
175 |
prefix = f"{self.config.model.type.lower()}_{dataset_name}"
|
176 |
if self.metrics_calculator is None:
|
177 |
# Initialisation par défaut si non fait ailleurs
|
178 |
-
from interfaces.metrics_calculator import DefaultMetricsCalculator
|
179 |
self.metrics_calculator = DefaultMetricsCalculator()
|
180 |
|
181 |
-
#
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
y_pred=y_pred,
|
189 |
-
y_proba=y_proba, # Passer les probas
|
190 |
-
prefix=prefix
|
191 |
-
)
|
192 |
-
else:
|
193 |
-
metrics = self.metrics_calculator.calculate_and_log_multiclass(
|
194 |
-
y_true=y_true,
|
195 |
-
y_pred=y_pred,
|
196 |
-
y_proba=y_proba, # Passer les probas
|
197 |
-
prefix=prefix
|
198 |
-
)
|
199 |
-
|
200 |
-
# Afficher les résultats
|
201 |
-
self.logger.info(f"Métriques d'évaluation ({dataset_name}): {metrics}")
|
202 |
|
203 |
return metrics
|
204 |
|
|
|
8 |
from scipy.sparse import csr_matrix
|
9 |
import cudf
|
10 |
from cuml.model_selection import train_test_split
|
|
|
11 |
|
12 |
from config import Config
|
13 |
from base_trainer import BaseTrainer
|
|
|
38 |
super().__init__(config, data_path, target_column)
|
39 |
self.vectorizer: Vectorizer = None
|
40 |
self.classifier: object = None # Déjà dans BaseTrainer, mais redéfini pour clarté
|
|
|
41 |
|
42 |
# Attributs pour stocker les données splittées (texte brut)
|
43 |
self.X_train_text: Optional[cudf.Series] = None
|
|
|
67 |
Stocke les résultats dans les attributs de l'instance.
|
68 |
"""
|
69 |
if self.X_train_text is not None: # Évite de recharger/resplitter
|
|
|
70 |
return
|
71 |
|
|
|
72 |
data = cudf.read_csv(self.data_path)
|
73 |
|
74 |
# Identification et concaténation des features
|
|
|
78 |
texts_concatenated = data[feature_columns].astype(str).agg(' '.join, axis=1)
|
79 |
labels = data[self.target_column].astype(self._get_label_dtype()).values
|
80 |
|
|
|
81 |
# Premier split: 80% train, 20% temp (pour val+test)
|
82 |
X_train, X_temp, y_train, y_temp = train_test_split(
|
83 |
texts_concatenated, labels, test_size=test_size, random_state=random_state, stratify=labels
|
|
|
98 |
self.y_val = y_val
|
99 |
self.y_test = y_test
|
100 |
|
|
|
|
|
101 |
|
102 |
def train(self) -> None:
|
103 |
"""
|
|
|
109 |
if self.vectorizer is None or self.classifier is None:
|
110 |
raise RuntimeError("Les composants (vectorizer, classifier) doivent être construits avant l'entraînement. Appelez build_components().")
|
111 |
|
|
|
112 |
# fit_transform sur l'entraînement
|
113 |
self.X_train_vec = self.vectorizer.fit_transform(self.X_train_text)
|
114 |
# transform sur validation et test
|
|
|
118 |
# Préparation pour cuML (conversion en dense si nécessaire)
|
119 |
X_train_prepared = self._prepare_input_for_fit(self.X_train_vec)
|
120 |
|
|
|
121 |
self.classifier.fit(X_train_prepared, self.y_train)
|
|
|
122 |
|
123 |
|
124 |
def evaluate(self, use_validation_set=False) -> dict:
|
|
|
135 |
raise RuntimeError("Le classifieur doit être entraîné avant l'évaluation. Appelez train().")
|
136 |
|
137 |
if use_validation_set:
|
|
|
138 |
X_eval_vec = self.X_val_vec
|
139 |
y_true = self.y_val
|
140 |
dataset_name = "validation"
|
141 |
else:
|
|
|
142 |
X_eval_vec = self.X_test_vec
|
143 |
y_true = self.y_test
|
144 |
dataset_name = "test"
|
|
|
147 |
X_eval_prepared = self._prepare_input_for_predict(X_eval_vec)
|
148 |
y_pred = self.classifier.predict(X_eval_prepared)
|
149 |
|
150 |
+
# Récupérer les probabilités pour la classe positive
|
151 |
+
y_proba = self._get_positive_probabilities(X_eval_prepared)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
152 |
|
153 |
+
# Calcul des métriques
|
154 |
prefix = f"{self.config.model.type.lower()}_{dataset_name}"
|
155 |
if self.metrics_calculator is None:
|
156 |
# Initialisation par défaut si non fait ailleurs
|
157 |
+
from interfaces.metrics_calculator import DefaultMetricsCalculator
|
158 |
self.metrics_calculator = DefaultMetricsCalculator()
|
159 |
|
160 |
+
# Utiliser la méthode calculate_and_log pour la classification binaire
|
161 |
+
metrics = self.metrics_calculator.calculate_and_log(
|
162 |
+
y_true=y_true,
|
163 |
+
y_pred=y_pred,
|
164 |
+
y_proba=y_proba,
|
165 |
+
prefix=prefix
|
166 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
167 |
|
168 |
return metrics
|
169 |
|
src/interfaces/metrics_calculator.py
CHANGED
@@ -1,157 +1,134 @@
|
|
1 |
import cupy as cp
|
2 |
-
from typing import Dict, Protocol,
|
3 |
import warnings
|
4 |
-
# Utiliser cuml.metrics
|
5 |
-
from cuml.metrics import accuracy_score,
|
6 |
|
7 |
-
#
|
8 |
-
# Attention: Masquer tous les warnings peut cacher des problèmes potentiels.
|
9 |
warnings.filterwarnings("ignore", category=Warning)
|
10 |
|
11 |
|
12 |
class MetricsCalculator(Protocol):
|
13 |
"""
|
14 |
-
Interface pour les calculateurs de métriques.
|
15 |
"""
|
16 |
def calculate_and_log(
|
17 |
self,
|
18 |
y_true: cp.ndarray,
|
19 |
y_pred: cp.ndarray,
|
20 |
-
y_proba:
|
21 |
prefix: str
|
22 |
) -> Dict[str, float]:
|
23 |
"""
|
24 |
Calcule les métriques pour un problème binaire.
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
def calculate_and_log_multiclass(
|
29 |
-
self,
|
30 |
-
y_true: cp.ndarray,
|
31 |
-
y_pred: cp.ndarray,
|
32 |
-
y_proba: Optional[cp.ndarray], # Probabilités (potentiellement pour futures métriques)
|
33 |
-
prefix: str
|
34 |
-
) -> Dict[str, float]:
|
35 |
-
"""
|
36 |
-
Calcule les métriques pour un problème multiclasses.
|
37 |
"""
|
38 |
pass
|
39 |
|
40 |
|
41 |
class DefaultMetricsCalculator(MetricsCalculator):
|
42 |
"""
|
43 |
-
Implémentation concrète de MetricsCalculator utilisant cuML.
|
44 |
-
Calcule accuracy, et F1
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
"""
|
49 |
|
50 |
def calculate_and_log(
|
51 |
self,
|
52 |
y_true: cp.ndarray,
|
53 |
y_pred: cp.ndarray,
|
54 |
-
y_proba:
|
55 |
prefix: str
|
56 |
) -> Dict[str, float]:
|
57 |
"""
|
58 |
-
Calcule
|
59 |
-
Utilise
|
60 |
-
|
61 |
"""
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
proba_pos_class = y_proba # Assume already positive class proba
|
97 |
-
else:
|
98 |
-
# Forme inattendue, ne peut pas calculer l'AUC
|
99 |
-
raise ValueError("y_proba a une forme inattendue pour le calcul AUC binaire.")
|
100 |
-
|
101 |
-
if proba_pos_class.dtype != cp.float32 and proba_pos_class.dtype != cp.float64:
|
102 |
-
proba_pos_class = proba_pos_class.astype(cp.float32)
|
103 |
-
|
104 |
-
# Check if y_true contains more than one class before calculating AUC
|
105 |
-
unique_labels = cp.unique(y_true)
|
106 |
-
if len(unique_labels) >= 2:
|
107 |
-
auc_score = roc_auc_score(y_true, proba_pos_class)
|
108 |
-
auc = float(auc_score) # Cast to float
|
109 |
-
|
110 |
-
except (ValueError, TypeError, Exception):
|
111 |
-
# Si une erreur se produit (ex: une seule classe, type incorrect, autre), AUC reste NaN
|
112 |
-
pass # auc est déjà float('nan')
|
113 |
-
|
114 |
-
metrics[f"{prefix}_auc_roc"] = auc
|
115 |
-
# Ensure all values in the returned dict are standard floats
|
116 |
-
return {k: float(v) for k, v in metrics.items()}
|
117 |
-
|
118 |
-
|
119 |
-
def calculate_and_log_multiclass(
|
120 |
-
self,
|
121 |
-
y_true: cp.ndarray,
|
122 |
-
y_pred: cp.ndarray,
|
123 |
-
y_proba: Optional[cp.ndarray], # Gardé pour cohérence d'interface
|
124 |
-
prefix: str
|
125 |
-
) -> Dict[str, float]:
|
126 |
"""
|
127 |
-
Calcule
|
128 |
-
|
129 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
130 |
"""
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import cupy as cp
|
2 |
+
from typing import Dict, Protocol, Tuple
|
3 |
import warnings
|
4 |
+
# Utiliser cuml.metrics pour les calculs accélérés par GPU
|
5 |
+
from cuml.metrics import accuracy_score, precision_recall_curve, roc_auc_score
|
6 |
|
7 |
+
# Filtrer les avertissements
|
|
|
8 |
warnings.filterwarnings("ignore", category=Warning)
|
9 |
|
10 |
|
11 |
class MetricsCalculator(Protocol):
|
12 |
"""
|
13 |
+
Interface pour les calculateurs de métriques pour la classification binaire.
|
14 |
"""
|
15 |
def calculate_and_log(
|
16 |
self,
|
17 |
y_true: cp.ndarray,
|
18 |
y_pred: cp.ndarray,
|
19 |
+
y_proba: cp.ndarray, # Probabilités classe positive (1D) - Supposées toujours fournies
|
20 |
prefix: str
|
21 |
) -> Dict[str, float]:
|
22 |
"""
|
23 |
Calcule les métriques pour un problème binaire.
|
24 |
+
Assume que y_proba est toujours fourni et est un tableau 1D
|
25 |
+
contenant les probabilités de la classe positive.
|
26 |
+
Retourne Accuracy, AUC ROC, Precision, Recall et F1 Score.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
"""
|
28 |
pass
|
29 |
|
30 |
|
31 |
class DefaultMetricsCalculator(MetricsCalculator):
|
32 |
"""
|
33 |
+
Implémentation concrète de MetricsCalculator utilisant cuML pour la classification binaire.
|
34 |
+
Calcule l'accuracy, l'AUC-ROC, la précision, le rappel et le F1 score en utilisant les fonctions cuML.
|
35 |
+
Utilise precision_recall_curve pour calculer les métriques de précision, rappel et F1 score optimales.
|
36 |
+
Assume que les données d'entrée sont valides et que y_proba est toujours fourni
|
37 |
+
en tant que tableau 1D des probabilités de la classe positive.
|
38 |
"""
|
39 |
|
40 |
def calculate_and_log(
|
41 |
self,
|
42 |
y_true: cp.ndarray,
|
43 |
y_pred: cp.ndarray,
|
44 |
+
y_proba: cp.ndarray, # Probabilités classe positive (1D) - Supposées toujours fournies
|
45 |
prefix: str
|
46 |
) -> Dict[str, float]:
|
47 |
"""
|
48 |
+
Calcule l'accuracy, l'AUC ROC, la précision, le rappel et le F1 score pour un problème binaire en utilisant cuML.
|
49 |
+
Utilise precision_recall_curve pour calculer les métriques optimales.
|
50 |
+
Assume des entrées valides et que y_proba est un tableau 1D fourni.
|
51 |
"""
|
52 |
+
# 1. Calculer l'accuracy (comme dans l'exemple accuracy_score)
|
53 |
+
acc = accuracy_score(y_true, y_pred)
|
54 |
+
|
55 |
+
# 2. Calculer l'AUC binaire (comme dans l'exemple roc_auc_score)
|
56 |
+
auc = roc_auc_score(y_true.astype(cp.int32), y_proba.astype(cp.float32))
|
57 |
+
|
58 |
+
# 3. Utiliser precision_recall_curve pour obtenir les courbes
|
59 |
+
precision, recall, thresholds = precision_recall_curve(
|
60 |
+
y_true.astype(cp.int32), y_proba.astype(cp.float32)
|
61 |
+
)
|
62 |
+
|
63 |
+
# 4. Calculer la précision, le rappel et le F1 score optimaux
|
64 |
+
optimal_precision, optimal_recall, optimal_f1, optimal_threshold = self._calculate_optimal_f1(
|
65 |
+
precision, recall, thresholds
|
66 |
+
)
|
67 |
+
|
68 |
+
# Construire le dictionnaire des métriques scalaires disponibles
|
69 |
+
metrics = {
|
70 |
+
f"{prefix}_accuracy" : acc,
|
71 |
+
f"{prefix}_precision" : optimal_precision,
|
72 |
+
f"{prefix}_recall" : optimal_recall,
|
73 |
+
f"{prefix}_f1" : optimal_f1,
|
74 |
+
f"{prefix}_optimal_threshold" : optimal_threshold,
|
75 |
+
f"{prefix}_auc_roc" : auc
|
76 |
+
}
|
77 |
+
# Retourner les métriques scalaires calculées
|
78 |
+
return metrics
|
79 |
+
|
80 |
+
def _calculate_optimal_f1(
|
81 |
+
self,
|
82 |
+
precision: cp.ndarray,
|
83 |
+
recall: cp.ndarray,
|
84 |
+
thresholds: cp.ndarray
|
85 |
+
) -> Tuple[float, float, float, float]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
86 |
"""
|
87 |
+
Calcule le F1 score optimal à partir des courbes de précision et de rappel.
|
88 |
+
|
89 |
+
Args:
|
90 |
+
precision: Tableau de précisions pour différents seuils
|
91 |
+
recall: Tableau de rappels pour différents seuils
|
92 |
+
thresholds: Tableau de seuils correspondants
|
93 |
+
|
94 |
+
Returns:
|
95 |
+
Tuple contenant (précision optimale, rappel optimal, F1 score optimal, seuil optimal)
|
96 |
"""
|
97 |
+
# Ajouter le seuil 1.0 à thresholds (qui n'est pas inclus par défaut dans precision_recall_curve)
|
98 |
+
if len(thresholds) > 0:
|
99 |
+
thresholds_with_one = cp.append(thresholds, cp.array([1.0]))
|
100 |
+
else:
|
101 |
+
thresholds_with_one = cp.array([1.0])
|
102 |
+
|
103 |
+
# Calculer le F1 score pour chaque point de la courbe
|
104 |
+
# F1 = 2 * (precision * recall) / (precision + recall)
|
105 |
+
# Éviter la division par zéro
|
106 |
+
denominator = precision + recall
|
107 |
+
# Créer un masque pour éviter la division par zéro
|
108 |
+
mask = denominator > 0
|
109 |
+
|
110 |
+
# Initialiser le F1 score avec des zéros
|
111 |
+
f1_scores = cp.zeros_like(precision)
|
112 |
+
# Calculer le F1 score uniquement où le dénominateur n'est pas zéro
|
113 |
+
f1_scores[mask] = 2 * (precision[mask] * recall[mask]) / denominator[mask]
|
114 |
+
|
115 |
+
# Trouver l'indice du F1 score maximal
|
116 |
+
if len(f1_scores) > 0:
|
117 |
+
best_idx = cp.argmax(f1_scores)
|
118 |
+
best_precision = float(precision[best_idx])
|
119 |
+
best_recall = float(recall[best_idx])
|
120 |
+
best_f1 = float(f1_scores[best_idx])
|
121 |
+
|
122 |
+
# Obtenir le seuil optimal
|
123 |
+
if best_idx < len(thresholds_with_one):
|
124 |
+
best_threshold = float(thresholds_with_one[best_idx])
|
125 |
+
else:
|
126 |
+
best_threshold = 0.5 # Valeur par défaut si l'indice est hors limites
|
127 |
+
else:
|
128 |
+
# Valeurs par défaut si les tableaux sont vides
|
129 |
+
best_precision = 0.0
|
130 |
+
best_recall = 0.0
|
131 |
+
best_f1 = 0.0
|
132 |
+
best_threshold = 0.5
|
133 |
+
|
134 |
+
return best_precision, best_recall, best_f1, best_threshold
|
src/trainers/huggingface/huggingface_transformer_trainer.py
CHANGED
@@ -13,7 +13,7 @@ from cuml.model_selection import train_test_split
|
|
13 |
from transformers import AutoTokenizer, AutoModelForSequenceClassification, Trainer, TrainingArguments, EvalPrediction
|
14 |
from datasets import Dataset as HFDataset # Utiliser le type Dataset de Hugging Face pour plus de clarté
|
15 |
# Utiliser cuml.metrics
|
16 |
-
from cuml.metrics import accuracy_score,
|
17 |
# Importer cupy pour la conversion et argmax
|
18 |
import cupy as cp
|
19 |
# numpy n'est plus nécessaire ici
|
@@ -29,66 +29,93 @@ def compute_metrics(p: EvalPrediction) -> Dict[str, float]:
|
|
29 |
# Convertir les labels numpy en cupy
|
30 |
labels_cp = cp.asarray(p.label_ids)
|
31 |
# Obtenir les prédictions en appliquant argmax aux logits avec cupy
|
32 |
-
preds_cp = cp.argmax(cp.asarray(logits), axis=1)
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
92 |
|
93 |
class HuggingFaceTransformerTrainer(BaseTrainer):
|
94 |
"""
|
@@ -153,7 +180,6 @@ class HuggingFaceTransformerTrainer(BaseTrainer):
|
|
153 |
texts = features_df[features_df.columns[0]]
|
154 |
for col in features_df.columns[1:]:
|
155 |
texts = texts.str.cat(features_df[col], sep=' ')
|
156 |
-
texts_list = texts.to_arrow().to_pylist()
|
157 |
# texts est une cudf.Series, labels est un cp.ndarray
|
158 |
# Utiliser cuml.model_selection.train_test_split directement
|
159 |
# Premier split: 80% train, 20% temp
|
|
|
13 |
from transformers import AutoTokenizer, AutoModelForSequenceClassification, Trainer, TrainingArguments, EvalPrediction
|
14 |
from datasets import Dataset as HFDataset # Utiliser le type Dataset de Hugging Face pour plus de clarté
|
15 |
# Utiliser cuml.metrics
|
16 |
+
from cuml.metrics import accuracy_score, precision_recall_curve, roc_auc_score
|
17 |
# Importer cupy pour la conversion et argmax
|
18 |
import cupy as cp
|
19 |
# numpy n'est plus nécessaire ici
|
|
|
29 |
# Convertir les labels numpy en cupy
|
30 |
labels_cp = cp.asarray(p.label_ids)
|
31 |
# Obtenir les prédictions en appliquant argmax aux logits avec cupy
|
32 |
+
preds_cp = cp.argmax(cp.asarray(logits), axis=1)
|
33 |
+
|
34 |
+
# Obtenir les probabilités (softmax) et convertir en cupy
|
35 |
+
probas_torch = F.softmax(torch.tensor(logits), dim=-1)
|
36 |
+
probas_cp = cp.asarray(probas_torch)
|
37 |
+
|
38 |
+
# Utiliser les probas de la classe positive
|
39 |
+
proba_pos_class = probas_cp[:, 1]
|
40 |
+
|
41 |
+
# 1. Calculer l'accuracy
|
42 |
+
acc = accuracy_score(labels_cp, preds_cp)
|
43 |
+
|
44 |
+
# 2. Calculer l'AUC ROC
|
45 |
+
auc = roc_auc_score(labels_cp.astype(cp.int32), proba_pos_class.astype(cp.float32))
|
46 |
+
|
47 |
+
# 3. Utiliser precision_recall_curve pour obtenir les courbes
|
48 |
+
precision, recall, thresholds = precision_recall_curve(
|
49 |
+
labels_cp.astype(cp.int32), proba_pos_class.astype(cp.float32)
|
50 |
+
)
|
51 |
+
|
52 |
+
# 4. Calculer la précision, le rappel et le F1 score optimaux
|
53 |
+
optimal_precision, optimal_recall, optimal_f1, optimal_threshold = calculate_optimal_f1(
|
54 |
+
precision, recall, thresholds
|
55 |
+
)
|
56 |
+
|
57 |
+
# Construire le dictionnaire des métriques
|
58 |
+
metrics = {
|
59 |
+
"accuracy": float(acc),
|
60 |
+
"precision": float(optimal_precision),
|
61 |
+
"recall": float(optimal_recall),
|
62 |
+
"f1": float(optimal_f1),
|
63 |
+
"optimal_threshold": float(optimal_threshold),
|
64 |
+
"auc_roc": float(auc)
|
65 |
+
}
|
66 |
+
|
67 |
+
return metrics
|
68 |
+
|
69 |
+
def calculate_optimal_f1(precision: cp.ndarray, recall: cp.ndarray, thresholds: cp.ndarray):
|
70 |
+
"""
|
71 |
+
Calcule le F1 score optimal à partir des courbes de précision et de rappel.
|
72 |
+
|
73 |
+
Args:
|
74 |
+
precision: Tableau de précisions pour différents seuils
|
75 |
+
recall: Tableau de rappels pour différents seuils
|
76 |
+
thresholds: Tableau de seuils correspondants
|
77 |
+
|
78 |
+
Returns:
|
79 |
+
Tuple contenant (précision optimale, rappel optimal, F1 score optimal, seuil optimal)
|
80 |
+
"""
|
81 |
+
# Ajouter le seuil 1.0 à thresholds (qui n'est pas inclus par défaut dans precision_recall_curve)
|
82 |
+
if len(thresholds) > 0:
|
83 |
+
thresholds_with_one = cp.append(thresholds, cp.array([1.0]))
|
84 |
+
else:
|
85 |
+
thresholds_with_one = cp.array([1.0])
|
86 |
+
|
87 |
+
# Calculer le F1 score pour chaque point de la courbe
|
88 |
+
# F1 = 2 * (precision * recall) / (precision + recall)
|
89 |
+
# Éviter la division par zéro
|
90 |
+
denominator = precision + recall
|
91 |
+
# Créer un masque pour éviter la division par zéro
|
92 |
+
mask = denominator > 0
|
93 |
+
|
94 |
+
# Initialiser le F1 score avec des zéros
|
95 |
+
f1_scores = cp.zeros_like(precision)
|
96 |
+
# Calculer le F1 score uniquement où le dénominateur n'est pas zéro
|
97 |
+
f1_scores[mask] = 2 * (precision[mask] * recall[mask]) / denominator[mask]
|
98 |
+
|
99 |
+
# Trouver l'indice du F1 score maximal
|
100 |
+
if len(f1_scores) > 0:
|
101 |
+
best_idx = cp.argmax(f1_scores)
|
102 |
+
best_precision = float(precision[best_idx])
|
103 |
+
best_recall = float(recall[best_idx])
|
104 |
+
best_f1 = float(f1_scores[best_idx])
|
105 |
+
|
106 |
+
# Obtenir le seuil optimal
|
107 |
+
if best_idx < len(thresholds_with_one):
|
108 |
+
best_threshold = float(thresholds_with_one[best_idx])
|
109 |
+
else:
|
110 |
+
best_threshold = 0.5 # Valeur par défaut si l'indice est hors limites
|
111 |
+
else:
|
112 |
+
# Valeurs par défaut si les tableaux sont vides
|
113 |
+
best_precision = 0.0
|
114 |
+
best_recall = 0.0
|
115 |
+
best_f1 = 0.0
|
116 |
+
best_threshold = 0.5
|
117 |
+
|
118 |
+
return best_precision, best_recall, best_f1, best_threshold
|
119 |
|
120 |
class HuggingFaceTransformerTrainer(BaseTrainer):
|
121 |
"""
|
|
|
180 |
texts = features_df[features_df.columns[0]]
|
181 |
for col in features_df.columns[1:]:
|
182 |
texts = texts.str.cat(features_df[col], sep=' ')
|
|
|
183 |
# texts est une cudf.Series, labels est un cp.ndarray
|
184 |
# Utiliser cuml.model_selection.train_test_split directement
|
185 |
# Premier split: 80% train, 20% temp
|