fioriclass commited on
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
- # Essayer de récupérer les probabilités
163
- y_proba = None
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 et logging des métriques
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 # Utiliser l'implémentation par défaut
179
  self.metrics_calculator = DefaultMetricsCalculator()
180
 
181
- # Déterminer si binaire ou multiclasse
182
- num_classes = len(cp.unique(y_true))
183
- self.logger.info(f"Nombre de classes détectées dans y_true ({dataset_name}): {num_classes}")
184
-
185
- if num_classes <= 2:
186
- metrics = self.metrics_calculator.calculate_and_log(
187
- y_true=y_true,
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, Optional
3
  import warnings
4
- # Utiliser cuml.metrics
5
- from cuml.metrics import accuracy_score, precision_recall_fscore_support, roc_auc_score
6
 
7
- # Ignorer les avertissements (peut nécessiter ajustement si cuML utilise d'autres types)
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: Optional[cp.ndarray], # Probabilités pour AUC
21
  prefix: str
22
  ) -> Dict[str, float]:
23
  """
24
  Calcule les métriques pour un problème binaire.
25
- """
26
- pass
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/precision/recall pondérés.
45
- Calcule AUC-ROC pour les problèmes binaires *uniquement* si les probabilités sont fournies.
46
- Ne calcule pas l'AUC-ROC pour les problèmes multiclasses (non supporté par cuml.metrics.roc_auc_score).
47
- Retourne NaN pour les métriques non calculables.
48
  """
49
 
50
  def calculate_and_log(
51
  self,
52
  y_true: cp.ndarray,
53
  y_pred: cp.ndarray,
54
- y_proba: Optional[cp.ndarray], # Probabilités requises pour AUC
55
  prefix: str
56
  ) -> Dict[str, float]:
57
  """
58
- Calcule les métriques pour un problème binaire.
59
- Utilise y_proba pour AUC si disponible.
60
- Utilise average='weighted' pour precision/recall/f1.
61
  """
62
- metrics: Dict[str, float] = {}
63
-
64
- try:
65
- # Accuracy
66
- acc = accuracy_score(y_true, y_pred)
67
- metrics[f"{prefix}_accuracy"] = float(acc)
68
-
69
- # Precision, Recall, F1 (Weighted)
70
- prec, rec, f1, _ = precision_recall_fscore_support(
71
- y_true, y_pred, average='weighted'
72
- )
73
- metrics[f"{prefix}_precision_weighted"] = float(prec)
74
- metrics[f"{prefix}_recall_weighted"] = float(rec)
75
- metrics[f"{prefix}_f1_weighted"] = float(f1)
76
-
77
- except Exception:
78
- # En cas d'erreur sur les métriques de base, remplir avec NaN
79
- metrics.setdefault(f"{prefix}_accuracy", float('nan'))
80
- metrics.setdefault(f"{prefix}_precision_weighted", float('nan'))
81
- metrics.setdefault(f"{prefix}_recall_weighted", float('nan'))
82
- metrics.setdefault(f"{prefix}_f1_weighted", float('nan'))
83
-
84
- # AUC-ROC (Binary only, requires probabilities)
85
- auc: float = float('nan') # Default to NaN
86
- if y_proba is not None:
87
- try:
88
- # Ensure y_true and y_proba have compatible shapes and types
89
- if y_true.dtype != cp.int32 and y_true.dtype != cp.int64:
90
- y_true = y_true.astype(cp.int32)
91
-
92
- # roc_auc_score expects probabilities of the positive class
93
- if y_proba.ndim == 2 and y_proba.shape[1] == 2:
94
- proba_pos_class = y_proba[:, 1]
95
- elif y_proba.ndim == 1:
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 les métriques pour un problème multiclasses.
128
- AUC-ROC n'est pas calculé (retourne NaN) car non supporté par cuml.metrics.roc_auc_score.
129
- Utilise average='weighted' pour precision/recall/f1.
 
 
 
 
 
 
130
  """
131
- metrics: Dict[str, float] = {}
132
-
133
- try:
134
- # Accuracy
135
- acc = accuracy_score(y_true, y_pred)
136
- metrics[f"{prefix}_accuracy"] = float(acc)
137
-
138
- # Precision, Recall, F1 (Weighted)
139
- prec, rec, f1, _ = precision_recall_fscore_support(
140
- y_true, y_pred, average="weighted"
141
- )
142
- metrics[f"{prefix}_precision_weighted"] = float(prec)
143
- metrics[f"{prefix}_recall_weighted"] = float(rec)
144
- metrics[f"{prefix}_f1_weighted"] = float(f1)
145
-
146
- except Exception:
147
- # En cas d'erreur sur les métriques de base, remplir avec NaN
148
- metrics.setdefault(f"{prefix}_accuracy", float('nan'))
149
- metrics.setdefault(f"{prefix}_precision_weighted", float('nan'))
150
- metrics.setdefault(f"{prefix}_recall_weighted", float('nan'))
151
- metrics.setdefault(f"{prefix}_f1_weighted", float('nan'))
152
-
153
- # AUC Multiclasse non supporté, retourner NaN
154
- metrics[f"{prefix}_auc_roc"] = float('nan')
155
-
156
- # Ensure all values in the returned dict are standard floats
157
- return {k: float(v) for k, v in metrics.items()}
 
 
 
 
 
 
 
 
 
 
 
 
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, precision_recall_fscore_support, 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,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) # Utilisation de cp.argmax
33
-
34
- metrics: Dict[str, float] = {}
35
-
36
- try:
37
- # Accuracy avec cuML
38
- acc = accuracy_score(labels_cp, preds_cp)
39
- metrics["accuracy"] = float(acc)
40
-
41
- # Precision, Recall, F1 (Weighted) avec cuML
42
- prec, rec, f1, _ = precision_recall_fscore_support(
43
- labels_cp, preds_cp, average='weighted'
44
- )
45
- metrics["precision_weighted"] = float(prec)
46
- metrics["recall_weighted"] = float(rec)
47
- metrics["f1_weighted"] = float(f1)
48
-
49
- except Exception:
50
- # Remplir avec NaN si erreur
51
- metrics.setdefault("accuracy", float('nan'))
52
- metrics.setdefault("precision_weighted", float('nan'))
53
- metrics.setdefault("recall_weighted", float('nan'))
54
- metrics.setdefault("f1_weighted", float('nan'))
55
-
56
- # Calcul AUC (binaire seulement avec cuML)
57
- auc: float = float('nan') # Default NaN
58
- num_classes = logits.shape[1]
59
-
60
- if num_classes == 2:
61
- try:
62
- # Obtenir les probabilités (softmax) et convertir en cupy
63
- probas_torch = F.softmax(torch.tensor(logits), dim=-1)
64
- probas_cp = cp.asarray(probas_torch)
65
-
66
- # Utiliser les probas de la classe positive
67
- proba_pos_class = probas_cp[:, 1]
68
-
69
- # S'assurer que les types sont corrects pour cuML roc_auc_score
70
- if labels_cp.dtype != cp.int32 and labels_cp.dtype != cp.int64:
71
- labels_cp = labels_cp.astype(cp.int32)
72
- if proba_pos_class.dtype != cp.float32 and proba_pos_class.dtype != cp.float64:
73
- proba_pos_class = proba_pos_class.astype(cp.float32)
74
-
75
- # Vérifier qu'il y a plus d'une classe dans les labels réels
76
- unique_labels = cp.unique(labels_cp)
77
- if len(unique_labels) >= 2:
78
- auc_score = roc_auc_score(labels_cp, proba_pos_class)
79
- auc = float(auc_score) # Cast to float
80
- # else: # Pas de log
81
-
82
- except (ValueError, TypeError, Exception):
83
- # auc reste NaN en cas d'erreur, pas de log
84
- pass
85
- # else: # Pas de log pour le cas multiclasse
86
- # auc reste NaN
87
-
88
- metrics["auc_roc"] = auc
89
-
90
- # Retourner les métriques avec les noms de base
91
- return {k: float(v) for k, v in metrics.items()} # Assurer float standard
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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