oc-p07-sentiment-api/notebooks/p07_04-notebook-modele-bert.ipynb

1 line
No EOL
77 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{"cells":[{"cell_type":"markdown","metadata":{"id":"y3x3_GfST76Z"},"source":["# Partie 4 : Analyse de Sentiment de Tweets avec BERT\n","\n","Dans ce notebook, nous explorons l'analyse de sentiment de tweets en utilisant **BERT (Bidirectional Encoder Representations from Transformers)**, un modèle de deep learning à la pointe de la technologie pour le traitement du langage naturel. Le dataset **Sentiment140** contient 1,6 million de tweets étiquetés (positifs ou négatifs), ce qui en fait une excellente ressource pour entraîner notre modèle. Contrairement aux approches traditionnelles qui nécessitent une ingénierie de caractéristiques complexe, **BERT comprend le contexte bidirectionnel des mots**, ce qui est particulièrement utile pour capturer les subtilités linguistiques des tweets comme l'ironie, les abréviations et les expressions idiomatiques propres aux réseaux sociaux.\n","\n","Concernant l'entrainement, le choix a été fait d'entrainer par optimisation de la variable de perte de validation `val_loss` avec en particulier un critère d'`EarlyStopping` au bout d'un certain nombre d'époques lancées (7, en l'ocurrence) afin de garantir une bonne généralisation ainsi qu'une limitation de l'overfitting.\n","\n","Cependant, le critère qui sera retenu comme le plus important en raison de notre problématique métier est la `precision` car nous souhaitons plus que tout éviter les faux positifs (tweet prédit comme positif, alors qu'il sera mal reçu)."]},{"cell_type":"markdown","metadata":{"id":"kW-8vumuT76b"},"source":["## Importation des bibliothèques"]},{"cell_type":"code","execution_count":1,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"Xplx_nQRg0rF","executionInfo":{"status":"ok","timestamp":1751448435967,"user_tz":-120,"elapsed":1150,"user":{"displayName":"Grégoire","userId":"01249298206117462766"}},"outputId":"c4ea9a18-24dd-48b3-cd5a-75aed9500009"},"outputs":[{"output_type":"stream","name":"stdout","text":["Drive already mounted at /content/drive; to attempt to forcibly remount, call drive.mount(\"/content/drive\", force_remount=True).\n"]}],"source":["from google.colab import drive\n","drive.mount('/content/drive')\n","import os\n","drive_folder= \"/content/drive/MyDrive/Colab_Notebooks/Project_7/notebooks\"\n","os.chdir(drive_folder)\n","# os.listdir()"]},{"cell_type":"code","execution_count":2,"metadata":{"id":"tXTBgiq7U8gK","executionInfo":{"status":"ok","timestamp":1751448462025,"user_tz":-120,"elapsed":26060,"user":{"displayName":"Grégoire","userId":"01249298206117462766"}}},"outputs":[],"source":["!pip install mlflow boto3 dotenv --quiet"]},{"cell_type":"code","execution_count":3,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"collapsed":true,"id":"W50jr7rUT76d","executionInfo":{"status":"ok","timestamp":1751448466953,"user_tz":-120,"elapsed":4927,"user":{"displayName":"Grégoire","userId":"01249298206117462766"}},"outputId":"322f93c0-6933-44b6-a9d0-745fd5db8e24"},"outputs":[{"output_type":"stream","name":"stdout","text":["PyTorch version: 2.6.0+cu124\n","CUDA disponible: False\n"]}],"source":["import torch\n","print(f\"PyTorch version: {torch.__version__}\")\n","print(f\"CUDA disponible: {torch.cuda.is_available()}\")\n","if torch.cuda.is_available():\n"," print(f\"Appareil CUDA: {torch.cuda.get_device_name(0)}\")"]},{"cell_type":"code","execution_count":4,"metadata":{"id":"MMB0gwbBT76d","executionInfo":{"status":"ok","timestamp":1751448487987,"user_tz":-120,"elapsed":21027,"user":{"displayName":"Grégoire","userId":"01249298206117462766"}}},"outputs":[],"source":["import os\n","import pickle\n","import subprocess\n","import re\n","import pandas as pd\n","import numpy as np\n","import torch\n","from torch.utils.data import Dataset, DataLoader\n","from torch.amp import GradScaler\n","from torch.optim import AdamW\n","from transformers import DistilBertTokenizer, DistilBertForSequenceClassification, get_scheduler\n","from sklearn.model_selection import train_test_split\n","from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score\n","from sklearn.metrics import confusion_matrix, classification_report, roc_curve, auc\n","import matplotlib.pyplot as plt\n","import seaborn as sns\n","from tqdm import tqdm\n","import html\n","import pickle\n","import zipfile\n","import requests\n","import json"]},{"cell_type":"code","execution_count":5,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"OhgBsQDCUq4A","executionInfo":{"status":"ok","timestamp":1751448489639,"user_tz":-120,"elapsed":1657,"user":{"displayName":"Grégoire","userId":"01249298206117462766"}},"outputId":"44ba2bc6-79ce-4428-9664-c22cd7505837"},"outputs":[{"output_type":"stream","name":"stdout","text":["MLflow Tracking URI: https://mlflow.greg-madman-nas.duckdns.org/\n","Identifiants AWS configurés\n"]}],"source":["import mlflow\n","from mlflow.models.signature import infer_signature\n","from mlflow import MlflowClient\n","from mlflow.models.signature import ModelSignature\n","from mlflow.types import Schema, ColSpec, TensorSpec\n","from dotenv import load_dotenv\n","from google.colab import userdata\n","import mlflow.pytorch\n","\n","\n","# Charger les variables d'environnement depuis le fichier .env\n","load_dotenv()\n","\n","# Configuration de MLflow avec les variables d'environnement\n","mlflow_tracking_uri = os.getenv(\"MLFLOW_TRACKING_URI\")\n","mlflow_s3_endpoint_url = os.getenv(\"MLFLOW_S3_ENDPOINT_URL\")\n","aws_access_key_id = os.getenv(\"AWS_ACCESS_KEY_ID\")\n","aws_secret_access_key = os.getenv(\"AWS_SECRET_ACCESS_KEY\")\n","\n","# Configuration explicite de MLflow\n","mlflow.set_tracking_uri(mlflow_tracking_uri)\n","print(f\"MLflow Tracking URI: {mlflow_tracking_uri}\")\n","\n","# Configuration explicite des identifiants AWS\n","os.environ[\"MLFLOW_S3_ENDPOINT_URL\"] = mlflow_s3_endpoint_url\n","os.environ[\"AWS_ACCESS_KEY_ID\"] = aws_access_key_id\n","os.environ[\"AWS_SECRET_ACCESS_KEY\"] = aws_secret_access_key\n","print(\"Identifiants AWS configurés\")\n","\n","# Créer l'expérience MLflow\n","mlflow.set_experiment(\"OC Projet 7\")\n","\n","#Création du dossier de sauvegarde du modèle\n","os.makedirs('content/bert-model/model', exist_ok=True)"]},{"cell_type":"markdown","metadata":{"id":"SuNqSc3UT76d"},"source":["## 💾 Jeu de données : Sentiment140\n","\n","Le jeu de données [Sentiment140 dataset with 1.6 million tweets](https://www.kaggle.com/datasets/kazanova/sentiment140) est une ressource majeure pour l'analyse de sentiment sur Twitter, comprenant **1,6 million de tweets** extraits via l'API Twitter. Ces tweets ont été automatiquement annotés selon leur polarité sentimentale, offrant une base solide pour développer des modèles de classification de sentiment.\n","\n","Le jeu de données est organisé en 6 colonnes distinctes :\n","\n","1. **target** : La polarité du sentiment exprimé dans le tweet.\n"," - 0 = sentiment négatif\n"," - 2 = sentiment neutre\n"," - 4 = sentiment positif\n","2. **ids** : L'identifiant unique du tweet (exemple : *2087*)\n","3. **date** : La date et l'heure de publication du tweet.\n","4. **flag** : La requête utilisée pour obtenir le tweet.\n"," - Exemple : *lyx*\n"," - Si aucune requête n'a été utilisée : *NO_QUERY*\n","5. **user** : Le nom d'utilisateur de l'auteur du tweet.\n","6. **text** : Le contenu textuel du tweet."]},{"cell_type":"code","execution_count":6,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"Q5ACc3OUT76d","executionInfo":{"status":"ok","timestamp":1751448489656,"user_tz":-120,"elapsed":13,"user":{"displayName":"Grégoire","userId":"01249298206117462766"}},"outputId":"1a842048-f745-4037-80c1-e42c24efee08"},"outputs":[{"output_type":"stream","name":"stdout","text":["CPU times: user 861 µs, sys: 400 µs, total: 1.26 ms\n","Wall time: 3.65 ms\n"]}],"source":["%%time\n","\n","# Define the URL and the local file path\n","url = \"https://s3-eu-west-1.amazonaws.com/static.oc-static.com/prod/courses/files/AI+Engineer/Project+7%C2%A0-+D%C3%A9tectez+les+Bad+Buzz+gr%C3%A2ce+au+Deep+Learning/sentiment140.zip\"\n","local_zip_path = \"./content/data/sentiment140.zip\"\n","extract_path = \"./content/data\"\n","\n","if not os.path.exists(extract_path):\n","\n"," # Create the directory if it doesn't exist\n"," os.makedirs(extract_path, exist_ok=True)\n","\n"," # Download the zip file\n"," response = requests.get(url)\n"," with open(local_zip_path, 'wb') as file:\n"," file.write(response.content)\n","\n"," # Extract the contents of the zip file\n"," with zipfile.ZipFile(local_zip_path, 'r') as zip_ref:\n"," zip_ref.extractall(extract_path)\n","\n"," # Delete the zip file\n"," os.remove(local_zip_path)"]},{"cell_type":"code","execution_count":7,"metadata":{"id":"_D2V2EgHT76e","executionInfo":{"status":"ok","timestamp":1751448489667,"user_tz":-120,"elapsed":9,"user":{"displayName":"Grégoire","userId":"01249298206117462766"}}},"outputs":[],"source":["# Définir le chemin vers le fichier CSV\n","csv_file_path = './content/data/training.1600000.processed.noemoticon.csv'"]},{"cell_type":"markdown","source":["## 🧠📚 Entraînement spécifique de BERT\n","Notre démarche pour la classification de sentiment avec des approches avancées comprend:\n","\n","1. **Prétraitement des tweets**\n"," - Nettoyage\n"," - Tokenisation et lemmatisation\n"," - Remplacement des URLs et mentions par des tokens spéciaux\n","\n","3. **Modèles testés**\n"," - BERT\n","\n","\n","3. **Évaluation et suivi**\n"," - Métriques: précision, accuracy, recall, F1-score, ROC-AUC\n"," - Tracking avec MLflow pour la reproductibilité et la comparaison"],"metadata":{"id":"Sv9nlaIq4BZo"}},{"cell_type":"markdown","metadata":{"id":"AFdxLghUT76e"},"source":["## Prétraitement des tweets"]},{"cell_type":"code","execution_count":8,"metadata":{"id":"NmOIhsWET76e","executionInfo":{"status":"ok","timestamp":1751448489681,"user_tz":-120,"elapsed":10,"user":{"displayName":"Grégoire","userId":"01249298206117462766"}}},"outputs":[],"source":["# PRÉTRAITEMENT DES TWEETS\n","def preprocess_tweet_for_bert(tweet):\n"," \"\"\"\n"," Prétraite un tweet pour l'entraînement BERT en conservant la structure naturelle\n"," du langage mais en normalisant certains éléments spécifiques aux réseaux sociaux.\n"," \"\"\"\n"," # Vérifier si le tweet est une chaîne de caractères\n"," if not isinstance(tweet, str):\n"," return \"\"\n","\n"," # Remplacer les URLs par un token spécial\n"," tweet = re.sub(r'https?://\\S+|www\\.\\S+', '[URL]', tweet)\n","\n"," # Remplacer les mentions par un token spécial\n"," tweet = re.sub(r'@\\w+', '[USER]', tweet)\n","\n"," # Normaliser les hashtags (conserver le hashtag comme entité)\n"," tweet = re.sub(r'#(\\w+)', r'#\\1', tweet)\n","\n"," # Traiter le langage html\n"," tweet = html.unescape(tweet)\n","\n"," # Normaliser les espaces multiples\n"," tweet = re.sub(r'\\s+', ' ', tweet)\n","\n"," # Supprimer les caractères non imprimables et certains caractères spéciaux inutiles\n"," tweet = re.sub(r'[^\\x20-\\x7E]', '', tweet)\n","\n"," # Nettoyer les espaces en début et fin\n"," tweet = tweet.strip()\n","\n"," return tweet"]},{"cell_type":"markdown","metadata":{"id":"M6ViLmc7T76e"},"source":["## Préparations des données"]},{"cell_type":"code","execution_count":9,"metadata":{"id":"ap7LohJzT76e","executionInfo":{"status":"ok","timestamp":1751448489709,"user_tz":-120,"elapsed":25,"user":{"displayName":"Grégoire","userId":"01249298206117462766"}}},"outputs":[],"source":["# PRÉPARATION DES DONNÉES\n","def prepare_data(df, sample_size=50000, random_state=42):\n"," \"\"\"\n"," Préparation des données pour l'entraînement BERT\n"," \"\"\"\n"," # Remappage des labels (0=négatif, 4=positif) vers (0=négatif, 1=positif)\n"," df['target'] = df['target'].replace({0: 0, 4: 1})\n","\n"," # Prétraitement des tweets\n"," print(\"Prétraitement des tweets...\")\n"," df['processed_text'] = df['text'].apply(preprocess_tweet_for_bert)\n","\n"," # Sélection d'un échantillon pour l'entraînement.\n"," # La distribution uniforme (50%/50%) des modalités ne posera pas problème pour le sous-échantillonnage.\n"," sample_data = df.sample(n=sample_size, random_state=random_state)\n","\n"," # Division train/val/test\n"," train_val_texts, test_texts, train_val_labels, test_labels = train_test_split(\n"," sample_data['processed_text'].values,\n"," sample_data['target'].values,\n"," test_size=0.2,\n"," random_state=random_state\n"," )\n","\n"," train_texts, val_texts, train_labels, val_labels = train_test_split(\n"," train_val_texts,\n"," train_val_labels,\n"," test_size=0.2,\n"," random_state=random_state\n"," )\n","\n"," return {\n"," 'train': {'texts': train_texts, 'labels': train_labels},\n"," 'val': {'texts': val_texts, 'labels': val_labels},\n"," 'test': {'texts': test_texts, 'labels': test_labels}\n"," }"]},{"cell_type":"code","execution_count":10,"metadata":{"id":"lgWXn2SKT76e","executionInfo":{"status":"ok","timestamp":1751448489730,"user_tz":-120,"elapsed":18,"user":{"displayName":"Grégoire","userId":"01249298206117462766"}}},"outputs":[],"source":["# CRÉATION D'UN DATASET PYTORCH\n","class TweetDataset(Dataset):\n"," def __init__(self, texts, labels, tokenizer, max_length=128):\n"," self.texts = texts\n"," self.labels = labels\n"," self.tokenizer = tokenizer\n"," self.max_length = max_length\n","\n"," def __len__(self):\n"," return len(self.texts)\n","\n"," def __getitem__(self, idx):\n"," text = str(self.texts[idx])\n"," label = self.labels[idx]\n","\n"," # Tokenisation du texte\n"," encoding = self.tokenizer(\n"," text,\n"," add_special_tokens=True,\n"," max_length=self.max_length,\n"," truncation=True,\n"," padding='max_length',\n"," return_tensors='pt'\n"," )\n","\n"," return {\n"," 'input_ids': encoding['input_ids'].flatten(),\n"," 'attention_mask': encoding['attention_mask'].flatten(),\n"," 'label': torch.tensor(label, dtype=torch.long)\n"," }"]},{"cell_type":"markdown","metadata":{"id":"P_S6MpCjT76f"},"source":["## Fonctions d'entraînement"]},{"cell_type":"code","source":["# import torch\n","# from torch.cuda.amp import GradScaler\n","# from sklearn.metrics import accuracy_score, precision_score\n","# from tqdm import tqdm\n","\n","# def train_model(\n","# model,\n","# train_loader,\n","# val_loader,\n","# test_loader,\n","# device,\n","# epochs=20,\n","# gradient_accumulation_steps=4,\n","# patience=7,\n","# lr=2e-5,\n","# lr_factor=0.2,\n","# min_lr=1e-6,\n","# ):\n","# \"\"\"\n","# Train a BERT model with gradient accumulation,\n","# EarlyStopping on val_loss, and best model selection on val_precision.\n","\n","# Args:\n","# model: The BERT model to train.\n","# train_loader: DataLoader for training data.\n","# val_loader: DataLoader for validation data.\n","# test_loader: DataLoader for test data.\n","# device: 'cuda' or 'cpu' for training.\n","# epochs: Maximum number of epochs to train.\n","# gradient_accumulation_steps: Number of steps for gradient accumulation.\n","# patience: Patience for EarlyStopping based on val_loss.\n","# lr: Initial learning rate.\n","# lr_factor: Factor to reduce learning rate in scheduler.\n","# min_lr: Minimum learning rate allowed.\n","\n","# Returns:\n","# history: Training and validation history.\n","# test_metrics: Metrics on the test set.\n","# \"\"\"\n","\n","# best_val_precision = 0.0\n","# best_val_loss = float('inf')\n","# no_val_loss_improvement_count = 0\n","\n","# optimizer = torch.optim.AdamW(model.parameters(), lr=lr, eps=1e-8)\n","# scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(\n","# optimizer, mode=\"min\", factor=lr_factor, patience=2, min_lr=min_lr, verbose=True\n","# )\n","# scaler = GradScaler()\n","\n","# history = {\n","# \"train_loss\": [],\n","# \"val_loss\": [],\n","# \"train_accuracy\": [],\n","# \"val_accuracy\": [],\n","# \"train_precision\": [],\n","# \"val_precision\": []\n","# }\n","\n","# for epoch in range(epochs):\n","# print(f\"\\n=== Epoch {epoch + 1}/{epochs} ===\")\n","\n","# # Training phase\n","# model.train()\n","# train_loss, train_true, train_preds = 0.0, [], []\n","\n","# for batch_idx, batch in enumerate(tqdm(train_loader, desc=\"Training\")):\n","# input_ids = batch[\"input_ids\"].to(device)\n","# attention_mask = batch[\"attention_mask\"].to(device)\n","# labels = batch[\"label\"].to(device)\n","\n","# optimizer.zero_grad()\n","\n","# # with autocast(enabled=torch.cuda.is_available()):\n","# with torch.amp.autocast(device_type='cuda' if torch.cuda.is_available() else 'cpu'):\n","\n","# outputs = model(input_ids=input_ids, attention_mask=attention_mask, labels=labels)\n","# loss = outputs.loss / gradient_accumulation_steps\n","\n","# scaler.scale(loss).backward()\n","\n","# if (batch_idx + 1) % gradient_accumulation_steps == 0 or (batch_idx + 1) == len(train_loader):\n","# scaler.unscale_(optimizer)\n","# torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)\n","# scaler.step(optimizer)\n","# scaler.update()\n","\n","# train_loss += loss.item() * gradient_accumulation_steps\n","# train_preds.extend(torch.argmax(outputs.logits, dim=1).cpu().numpy())\n","# train_true.extend(labels.cpu().numpy())\n","\n","# avg_train_loss = train_loss / len(train_loader)\n","# train_accuracy = accuracy_score(train_true, train_preds)\n","# train_precision = precision_score(train_true, train_preds, zero_division=0)\n","# history[\"train_loss\"].append(avg_train_loss)\n","# history[\"train_accuracy\"].append(train_accuracy)\n","# history[\"train_precision\"].append(train_precision)\n","\n","# print(f\"Train Loss: {avg_train_loss:.4f}, Train Accuracy: {train_accuracy:.4f}, Train Precision: {train_precision:.4f}\")\n","\n","# # Validation phase\n","# model.eval()\n","# val_loss, val_true, val_preds = 0.0, [], []\n","\n","# with torch.no_grad():\n","# for batch in tqdm(val_loader, desc=\"Validation\"):\n","# input_ids = batch[\"input_ids\"].to(device)\n","# attention_mask = batch[\"attention_mask\"].to(device)\n","# labels = batch[\"label\"].to(device)\n","\n","# outputs = model(input_ids=input_ids, attention_mask=attention_mask, labels=labels)\n","# loss = outputs.loss\n","\n","# val_loss += loss.item()\n","# val_preds.extend(torch.argmax(outputs.logits, dim=1).cpu().numpy())\n","# val_true.extend(labels.cpu().numpy())\n","\n","# avg_val_loss = val_loss / len(val_loader)\n","# val_accuracy = accuracy_score(val_true, val_preds)\n","# val_precision = precision_score(val_true, val_preds, zero_division=0)\n","# history[\"val_loss\"].append(avg_val_loss)\n","# history[\"val_accuracy\"].append(val_accuracy)\n","# history[\"val_precision\"].append(val_precision)\n","\n","# print(f\"Val Loss: {avg_val_loss:.4f}, Val Accuracy: {val_accuracy:.4f}, Val Precision: {val_precision:.4f}\")\n","\n","# # Scheduler step on val_loss\n","# scheduler.step(avg_val_loss)\n","\n","# # Save best model based on val_precision\n","# if val_precision > best_val_precision:\n","# best_val_precision = val_precision\n","# torch.save(model.state_dict(), \"best_model.pth\")\n","# print(\"New best model saved based on val_precision.\")\n","\n","# # EarlyStopping on val_loss\n","# if avg_val_loss < best_val_loss:\n","# best_val_loss = avg_val_loss\n","# no_val_loss_improvement_count = 0\n","# else:\n","# no_val_loss_improvement_count += 1\n","# print(f\"No val_loss improvement. Patience: {no_val_loss_improvement_count}/{patience}\")\n","\n","# if no_val_loss_improvement_count >= patience:\n","# print(\"EarlyStopping activated.\")\n","# break\n","\n","# # Load best model\n","# model.load_state_dict(torch.load(\"best_model.pth\"))\n","# print(\"Best model loaded for evaluation.\")\n","\n","# # Evaluate on test set\n","# test_metrics = evaluate_model(model, test_loader, device)\n","\n","# return history, test_metrics"],"metadata":{"id":"B-lFUgM1GV4O","executionInfo":{"status":"ok","timestamp":1751448489767,"user_tz":-120,"elapsed":35,"user":{"displayName":"Grégoire","userId":"01249298206117462766"}}},"execution_count":11,"outputs":[]},{"cell_type":"code","source":["import torch\n","from torch.cuda.amp import GradScaler\n","from sklearn.metrics import accuracy_score, precision_score, recall_score\n","from tqdm import tqdm\n","\n","def train_model(\n"," model,\n"," train_loader,\n"," val_loader,\n"," test_loader,\n"," device,\n"," epochs=20,\n"," gradient_accumulation_steps=4,\n"," patience=7,\n"," lr=2e-5,\n"," lr_factor=0.2,\n"," min_lr=1e-6,\n","):\n"," \"\"\"\n"," Train a BERT model with gradient accumulation,\n"," EarlyStopping on val_loss, and best model selection on val_precision.\n","\n"," Args:\n"," model: The BERT model to train.\n"," train_loader: DataLoader for training data.\n"," val_loader: DataLoader for validation data.\n"," test_loader: DataLoader for test data.\n"," device: 'cuda' or 'cpu' for training.\n"," epochs: Maximum number of epochs to train.\n"," gradient_accumulation_steps: Number of steps for gradient accumulation.\n"," patience: Patience for EarlyStopping based on val_loss.\n"," lr: Initial learning rate.\n"," lr_factor: Factor to reduce learning rate in scheduler.\n"," min_lr: Minimum learning rate allowed.\n","\n"," Returns:\n"," history: Training and validation history.\n"," test_metrics: Metrics on the test set.\n"," \"\"\"\n","\n"," best_val_precision = 0.0\n"," best_val_loss = float('inf')\n"," no_val_loss_improvement_count = 0\n","\n"," optimizer = torch.optim.AdamW(model.parameters(), lr=lr, eps=1e-8)\n"," scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(\n"," optimizer, mode=\"min\", factor=lr_factor, patience=2, min_lr=min_lr, verbose=True\n"," )\n"," scaler = GradScaler()\n","\n"," history = {\n"," \"train_loss\": [],\n"," \"val_loss\": [],\n"," \"train_accuracy\": [],\n"," \"val_accuracy\": [],\n"," \"train_precision\": [],\n"," \"val_precision\": [],\n"," \"val_recall\": []\n"," }\n","\n"," for epoch in range(epochs):\n"," print(f\"\\n=== Epoch {epoch + 1}/{epochs} ===\")\n","\n"," # Training phase\n"," model.train()\n"," train_loss, train_true, train_preds = 0.0, [], []\n","\n"," for batch_idx, batch in enumerate(tqdm(train_loader, desc=\"Training\")):\n"," input_ids = batch[\"input_ids\"].to(device)\n"," attention_mask = batch[\"attention_mask\"].to(device)\n"," labels = batch[\"label\"].to(device)\n","\n"," optimizer.zero_grad()\n","\n"," # with autocast(enabled=torch.cuda.is_available()):\n"," with torch.amp.autocast(device_type='cuda' if torch.cuda.is_available() else 'cpu'):\n","\n"," outputs = model(input_ids=input_ids, attention_mask=attention_mask, labels=labels)\n"," loss = outputs.loss / gradient_accumulation_steps\n","\n"," scaler.scale(loss).backward()\n","\n"," if (batch_idx + 1) % gradient_accumulation_steps == 0 or (batch_idx + 1) == len(train_loader):\n"," scaler.unscale_(optimizer)\n"," torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)\n"," scaler.step(optimizer)\n"," scaler.update()\n","\n"," train_loss += loss.item() * gradient_accumulation_steps\n"," train_preds.extend(torch.argmax(outputs.logits, dim=1).cpu().numpy())\n"," train_true.extend(labels.cpu().numpy())\n","\n"," avg_train_loss = train_loss / len(train_loader)\n"," train_accuracy = accuracy_score(train_true, train_preds)\n"," train_precision = precision_score(train_true, train_preds, zero_division=0)\n"," history[\"train_loss\"].append(avg_train_loss)\n"," history[\"train_accuracy\"].append(train_accuracy)\n"," history[\"train_precision\"].append(train_precision)\n","\n"," print(f\"Train Loss: {avg_train_loss:.4f}, Train Accuracy: {train_accuracy:.4f}, Train Precision: {train_precision:.4f}\")\n","\n"," # Validation phase\n"," model.eval()\n"," val_loss, val_true, val_preds = 0.0, [], []\n","\n"," with torch.no_grad():\n"," for batch in tqdm(val_loader, desc=\"Validation\"):\n"," input_ids = batch[\"input_ids\"].to(device)\n"," attention_mask = batch[\"attention_mask\"].to(device)\n"," labels = batch[\"label\"].to(device)\n","\n"," outputs = model(input_ids=input_ids, attention_mask=attention_mask, labels=labels)\n"," loss = outputs.loss\n","\n"," val_loss += loss.item()\n"," val_preds.extend(torch.argmax(outputs.logits, dim=1).cpu().numpy())\n"," val_true.extend(labels.cpu().numpy())\n","\n"," avg_val_loss = val_loss / len(val_loader)\n"," val_accuracy = accuracy_score(val_true, val_preds)\n"," val_precision = precision_score(val_true, val_preds, zero_division=0)\n"," val_recall = recall_score(val_true, val_preds, zero_division=0) # calcul recall ajouté\n","\n"," history[\"val_loss\"].append(avg_val_loss)\n"," history[\"val_accuracy\"].append(val_accuracy)\n"," history[\"val_precision\"].append(val_precision)\n"," history[\"val_recall\"].append(val_recall) # ajout dans l'historique\n","\n"," print(f\"Val Loss: {avg_val_loss:.4f}, Val Accuracy: {val_accuracy:.4f}, Val Precision: {val_precision:.4f}, Val Recall: {val_recall:.4f}\")\n","\n"," # Scheduler step on val_loss\n"," scheduler.step(avg_val_loss)\n","\n"," # Save best model based on val_precision\n"," if val_precision > best_val_precision:\n"," best_val_precision = val_precision\n"," torch.save(model.state_dict(), \"./content/bert-model/model/best_model.pth\")\n"," print(\"New best model saved based on val_precision.\")\n","\n"," # EarlyStopping on val_loss\n"," if avg_val_loss < best_val_loss:\n"," best_val_loss = avg_val_loss\n"," no_val_loss_improvement_count = 0\n"," else:\n"," no_val_loss_improvement_count += 1\n"," print(f\"No val_loss improvement. Patience: {no_val_loss_improvement_count}/{patience}\")\n","\n"," if no_val_loss_improvement_count >= patience:\n"," print(\"EarlyStopping activated.\")\n"," break\n","\n"," # Load best model\n"," model.load_state_dict(torch.load(\"./content/bert-model/model/best_model.pth\"))\n"," print(\"Best model loaded for evaluation.\")\n","\n"," # Evaluate on test set\n"," test_metrics = evaluate_model(model, test_loader, device)\n","\n"," return history, test_metrics"],"metadata":{"id":"EsK_IQRlbu3C","executionInfo":{"status":"ok","timestamp":1751448489844,"user_tz":-120,"elapsed":76,"user":{"displayName":"Grégoire","userId":"01249298206117462766"}}},"execution_count":12,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"3gDy-63XT76f"},"source":["## Fonction d'évaluation"]},{"cell_type":"code","execution_count":13,"metadata":{"id":"g2DZWDoNT76f","executionInfo":{"status":"ok","timestamp":1751448489864,"user_tz":-120,"elapsed":22,"user":{"displayName":"Grégoire","userId":"01249298206117462766"}}},"outputs":[],"source":["# FONCTION D'ÉVALUATION\n","def evaluate_model(model, test_loader, device):\n"," \"\"\"\n"," Évalue le modèle sur le jeu de test et retourne les métriques et prédictions\n"," \"\"\"\n"," model.eval()\n"," test_preds, test_true = [], []\n"," test_probs = []\n","\n"," with torch.no_grad():\n"," for batch in tqdm(test_loader, desc=\"Évaluation sur le jeu de test\"):\n"," input_ids = batch['input_ids'].to(device)\n"," attention_mask = batch['attention_mask'].to(device)\n"," labels = batch['label'].to(device)\n","\n"," outputs = model(\n"," input_ids=input_ids,\n"," attention_mask=attention_mask\n"," )\n","\n"," # Récupération des prédictions et probabilités\n"," logits = outputs.logits\n"," probs = torch.nn.functional.softmax(logits, dim=1)\n"," preds = torch.argmax(logits, dim=1).cpu().numpy()\n"," true = labels.cpu().numpy()\n","\n"," test_preds.extend(preds)\n"," test_true.extend(true)\n"," test_probs.extend(probs[:, 1].cpu().numpy()) # Probabilité de la classe positive\n","\n"," # Calcul des métriques\n"," accuracy = accuracy_score(test_true, test_preds)\n"," precision = precision_score(test_true, test_preds)\n"," recall = recall_score(test_true, test_preds)\n"," f1 = f1_score(test_true, test_preds)\n","\n"," # Matrice de confusion\n"," cm = confusion_matrix(test_true, test_preds)\n","\n"," # Classification report\n"," report = classification_report(test_true, test_preds, target_names=['Négatif', 'Positif'])\n","\n"," # Courbe ROC et AUC\n"," fpr, tpr, _ = roc_curve(test_true, test_probs)\n"," roc_auc = auc(fpr, tpr)\n","\n"," # Regrouper toutes les métriques\n"," metrics = {\n"," 'accuracy': accuracy,\n"," 'precision': precision,\n"," 'recall': recall,\n"," 'f1': f1,\n"," 'roc_auc': roc_auc,\n"," 'fpr': fpr,\n"," 'tpr': tpr,\n"," 'confusion_matrix': cm,\n"," 'classification_report': report,\n"," 'predictions': test_preds,\n"," 'true_labels': test_true,\n"," 'probabilities': test_probs\n"," }\n","\n"," return metrics"]},{"cell_type":"markdown","metadata":{"id":"o0S7DzDJdvbV"},"source":["## Afficher la matrice de confusion"]},{"cell_type":"code","execution_count":14,"metadata":{"id":"nsUgEh2pT76f","executionInfo":{"status":"ok","timestamp":1751448489903,"user_tz":-120,"elapsed":39,"user":{"displayName":"Grégoire","userId":"01249298206117462766"}}},"outputs":[],"source":["# FONCTION POUR TRACER LA MATRICE DE CONFUSION\n","def plot_confusion_matrix(cm, class_names):\n"," \"\"\"\n"," Tracer la matrice de confusion\n"," \"\"\"\n"," plt.figure(figsize=(8, 6))\n"," sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=class_names, yticklabels=class_names)\n"," plt.xlabel('Prédictions')\n"," plt.ylabel('Valeurs réelles')\n"," plt.title('Matrice de confusion')\n","\n"," # Save confusion matrix\n"," filename = f'content/bert-model/confusion_matrix.png'\n"," plt.savefig(filename)\n","\n"," return plt.gcf()"]},{"cell_type":"markdown","metadata":{"id":"ORnBybYLT76g"},"source":["## Fonction pour tracer la courbe ROC AUC"]},{"cell_type":"code","execution_count":15,"metadata":{"id":"Pf-tDY3OT76g","executionInfo":{"status":"ok","timestamp":1751448489942,"user_tz":-120,"elapsed":40,"user":{"displayName":"Grégoire","userId":"01249298206117462766"}}},"outputs":[],"source":["# FONCTION POUR TRACER LA COURBE ROC\n","def plot_roc_curve(fpr, tpr, roc_auc, model_name, run_id=None):\n"," \"\"\"\n"," Trace la courbe ROC et l'enregistre dans MLflow\n"," \"\"\"\n"," plt.figure(figsize=(8, 6))\n"," plt.plot(fpr, tpr, label=f'ROC curve (area = {roc_auc:.3f})')\n"," plt.plot([0, 1], [0, 1], 'k--')\n"," plt.xlim([0.0, 1.0])\n"," plt.ylim([0.0, 1.05])\n"," plt.xlabel('Taux de faux positifs')\n"," plt.ylabel('Taux de vrais positifs')\n"," plt.title(f'Courbe ROC - {model_name}')\n"," plt.legend(loc=\"lower right\")\n","\n"," # Sauvegarder la figure localement\n"," filename = f'content/bert-model/roc_curve_{model_name}.png'\n"," plt.savefig(filename)\n","\n"," # Enregistrer dans MLflow en utilisant l'exécution existante\n"," # if run_id:\n"," # with mlflow.start_run(run_id=run_id):\n"," # mlflow.log_figure(plt.gcf(), f\"roc_curve_{model_name}.png\")\n","\n"," return plt.gcf()"]},{"cell_type":"markdown","metadata":{"id":"WD_nm9eDT76f"},"source":["## Fonction pour tracer l'historique d'entraînement"]},{"cell_type":"code","execution_count":16,"metadata":{"id":"UKzSUOlqT76g","executionInfo":{"status":"ok","timestamp":1751448489959,"user_tz":-120,"elapsed":18,"user":{"displayName":"Grégoire","userId":"01249298206117462766"}}},"outputs":[],"source":["# FONCTION POUR TRACER L'HISTORIQUE D'ENTRAÎNEMENT\n","def plot_training_history(history, model_name, run_id=None):\n"," \"\"\"\n"," Trace l'historique d'entraînement du modèle et l'enregistre dans MLflow\n"," \"\"\"\n"," fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))\n","\n"," # Tracer l'accuracy\n"," ax1.plot(history['train_precision'], label='train')\n"," ax1.plot(history['val_precision'], label='validation')\n"," ax1.set_title(f'Precision - {model_name}')\n"," ax1.set_xlabel('Epoch')\n"," ax1.set_ylabel('Precision')\n"," ax1.legend()\n","\n"," # Tracer la loss\n"," ax2.plot(history['train_loss'], label='train')\n"," ax2.plot(history['val_loss'], label='validation')\n"," ax2.set_title(f'Loss - {model_name}')\n"," ax2.set_xlabel('Epoch')\n"," ax2.set_ylabel('Loss')\n"," ax2.legend()\n","\n"," plt.tight_layout()\n","\n"," # Sauvegarder la figure localement\n"," os.makedirs('content/bert-model', exist_ok=True)\n"," filename = f'content/bert-model/training_history_{model_name}.png'\n"," plt.savefig(filename)\n","\n"," # Enregistrer dans MLflow en utilisant l'exécution existante\n"," if run_id:\n"," with mlflow.start_run(run_id=run_id):\n"," mlflow.log_figure(fig, f\"training_history_{model_name}.png\")\n","\n"," return fig"]},{"cell_type":"markdown","metadata":{"id":"aXZRXEMnT76g"},"source":["## Enregistrement du modèle dans MLFlow\n","\n","1. Définition de la signature du modèle pour MLflow\n","2. Démarrage d'une exécution MLflow avec un nom spécifique\n","3. Enregistrement des paramètres du modèle\n","4. Enregistrement des métriques de performance (accuracy, precision, recall, f1, roc_auc)\n","5. Création et enregistrement de visualisations (matrice de confusion, courbe ROC)\n","6. Enregistrement du rapport de classification\n","7. Sauvegarde locale du modèle BERT\n","8. Enregistrement du modèle PyTorch dans MLflow avec sa signature\n","9. Sauvegarde locale du tokenizer et enregistrement dans MLflow\n","10. Sauvegarde locale de la fonction de prétraitement (preprocessor) via pickle et enregistrement dans MLflow\n","11. Génération et enregistrement du fichier requirements.txt\n","10. Retourne l'**identifiant unique (Run ID)** de l'exécution MLflow qui vient d'être complétée, permettant de la référencer ultérieurement.\n","\n","## Tableau des chemins des artefacts\n","\n","| Artefact | Chemin local | Chemin MLflow |\n","|----------|--------------|---------------|\n","| Matrice de confusion | `content/bert-model/confusion_matrix.png` | `confusion_matrix_{model_name}.png` |\n","| Courbe ROC | `content/bert-model/roc_curve_{model_name}.png` | `roc_curve_{model_name}.png` |\n","| Historique d'entraînement | `content/bert-model/training_history_{model_name}.png` | `training_history_{model_name}.png` (si run_id fourni) |\n","| Rapport de classification | `content/bert-model/classification_report.txt` | Racine des artefacts |\n","| Modèle BERT | `content/bert-model/model/` | `model/` |\n","| Tokenizer | `content/bert-model/tokenizer/` | `tokenizer/` |\n","| Preprocessor | `content/bert-model/preprocessor/preprocessor.pkl` | `preprocessor/preprocessor.pkl` |\n","| Requirements | `content/bert-model/requirements.txt` | (Utilisé par MLflow comme dépendances du modèle) |\n"]},{"cell_type":"code","execution_count":17,"metadata":{"id":"3OppER9A_iPX","executionInfo":{"status":"ok","timestamp":1751448490073,"user_tz":-120,"elapsed":114,"user":{"displayName":"Grégoire","userId":"01249298206117462766"}}},"outputs":[],"source":["# FONCTION POUR ENREGISTRER LE MODÈLE DANS MLFLOW\n","def log_model_to_mlflow(model, tokenizer, model_name, metrics, params=None):\n"," \"\"\"\n"," Enregistre le modèle et ses performances dans MLflow, y compris la signature.\n"," Gère correctement les devices et la sérialisation JSON des métriques NumPy.\n"," Supprime input_example pour éviter l'avertissement de validation avec le flavor pytorch.\n","\n"," Args:\n"," model (torch.nn.Module): Le modèle PyTorch (Hugging Face) entraîné.\n"," tokenizer: Le tokenizer Hugging Face associé au modèle.\n"," model_name (str): Un nom pour identifier ce modèle/cette exécution.\n"," metrics (dict): Un dictionnaire contenant les métriques de performance.\n"," params (dict, optional): Un dictionnaire des hyperparamètres utilisés. Defaults to None.\n","\n"," Returns:\n"," str: L'ID de l'exécution MLflow.\n"," \"\"\"\n"," class_names = ['Négatif', 'Positif']\n","\n"," # --- Définir la signature du modèle ---\n"," sample_input_text = [\"Ceci est un exemple de texte pour inférer la signature.\"]\n"," inputs = tokenizer(\n"," sample_input_text, return_tensors=\"pt\", padding=\"max_length\",\n"," truncation=True, max_length=128\n"," )\n"," try:\n"," model_device = next(model.parameters()).device\n"," except StopIteration:\n"," model_device = torch.device(\"cpu\")\n"," except Exception:\n"," model_device = torch.device(\"cpu\")\n","\n"," inputs_on_device = None\n"," try:\n"," inputs_on_device = {k: v.to(model_device) for k, v in inputs.items()}\n"," except Exception as e:\n"," print(f\"Error moving input tensors to device {model_device}: {e}. Skipping signature inference.\")\n","\n"," input_data_for_signature = {k: v.cpu().numpy() for k, v in inputs.items()} # For potential future use, still on CPU\n","\n"," predictions = None\n"," signature = None\n"," if inputs_on_device:\n"," model.eval()\n"," with torch.no_grad():\n"," try:\n"," outputs = model(**inputs_on_device)\n"," if hasattr(outputs, 'logits'):\n"," predictions = outputs.logits.cpu().numpy()\n"," elif isinstance(outputs, torch.Tensor):\n"," predictions = outputs.cpu().numpy()\n"," else:\n"," # Fallback attempt\n"," for val in getattr(outputs, 'values', lambda: [])(): # Handle dicts or sequences\n"," if isinstance(val, torch.Tensor):\n"," predictions = val.cpu().numpy()\n"," break\n","\n"," if predictions is not None:\n"," # Infer signature using the CPU numpy inputs and CPU numpy predictions\n"," signature = infer_signature(input_data_for_signature, predictions)\n"," print(\"Model signature inferred successfully.\")\n"," else:\n"," print(\"Warning: Could not extract prediction tensor for signature.\")\n","\n"," except Exception as e:\n"," print(f\"Error during model prediction or signature inference: {e}\")\n"," else:\n"," print(\"Skipping signature inference because input tensors could not be moved to model device.\")\n"," # --- Fin de la définition de la signature ---\n","\n"," # Démarrer une exécution MLflow\n"," with mlflow.start_run(run_name=f\"Model_Bert-{model_name}\") as run:\n","\n"," # Enregistrer les paramètres du modèle\n"," if params:\n"," for key, value in params.items():\n"," mlflow.log_param(key, value)\n","\n"," # Enregistrer les métriques de performance\n"," mlflow.log_metric(\"accuracy\", metrics['accuracy'])\n"," mlflow.log_metric(\"precision\", metrics['precision'])\n"," mlflow.log_metric(\"recall\", metrics['recall'])\n"," mlflow.log_metric(\"f1\", metrics['f1'])\n"," mlflow.log_metric(\"roc_auc\", metrics['roc_auc'])\n","\n"," mlflow.log_param(\"model_base\", model_name)\n"," mlflow.set_tag(\"model_base\", model_name)\n","\n"," # Tracer et enregistrer la matrice de confusion\n"," fig_cm = plot_confusion_matrix(metrics['confusion_matrix'], class_names)\n"," mlflow.log_figure(fig_cm, f\"confusion_matrix_{model_name}.png\")\n","\n"," # Tracer et enregistrer la courbe ROC\n"," fig_roc = plot_roc_curve(metrics['fpr'], metrics['tpr'], metrics['roc_auc'], model_name)\n"," mlflow.log_figure(fig_roc, f\"roc_curve_{model_name}.png\")\n","\n"," # Enregistrer le rapport de classification\n"," report_path = f\"content/bert-model/classification_report.txt\"\n"," os.makedirs(os.path.dirname(report_path), exist_ok=True)\n"," with open(report_path, \"w\") as f:\n"," f.write(metrics['classification_report'])\n"," mlflow.log_artifact(report_path)\n","\n","\n"," # Sauvegarder le modèle localement\n"," model_path = f\"content/bert-model/model\"\n"," os.makedirs(model_path, exist_ok=True)\n"," try:\n"," model.save_pretrained(model_path)\n"," print(\"Model logged to MLflow artifact path 'model'.\")\n"," except Exception as e:\n"," print(f\"Warning: Failed to save or log model: {e}\")\n","\n","\n"," # Enregistrer le tokenizer localement et sur MLFlow\n"," tokenizer_path = f\"content/bert-model/tokenizer\"\n"," os.makedirs(tokenizer_path, exist_ok=True)\n"," try:\n"," tokenizer.save_pretrained(tokenizer_path)\n"," mlflow.log_artifact(tokenizer_path)\n"," print(\"Tokenizer logged to MLflow artifact path 'tokenizer'.\")\n"," except Exception as e:\n"," print(f\"Warning: Failed to save or log tokenizer: {e}\")\n","\n","\n"," # --- Sauvegarder la fonction preprocessor via pickle ---\n"," local_save_directory = \"content/bert-model/preprocessor\" # Attention au double \"content\" si c'est bien voulu\n"," local_pickle_filename = \"preprocessor.pkl\"\n"," local_pickle_filepath = os.path.join(local_save_directory, local_pickle_filename)\n"," # Sous-dossier cible dans les artefacts MLflow\n"," mlflow_artifact_subdir = \"preprocessor\"\n"," try:\n"," os.makedirs(local_save_directory, exist_ok=True)\n"," with open(local_pickle_filepath, \"wb\") as f_out:\n"," pickle.dump(preprocess_tweet_for_bert, f_out)\n"," mlflow.log_artifact(local_pickle_filepath, artifact_path=mlflow_artifact_subdir)\n"," except NameError:\n"," print(f\"\\nWarning: La fonction 'preprocess_tweet_for_bert' n'est pas définie. Impossible de la sauvegarder.\\n\")\n"," except Exception as e:\n"," print(f\"\\nWarning: Impossible de sauvegarder (pickle) ou logger la fonction de prétraitement : {e}\\n\")\n","\n","\n"," # Enregistrer le fichier requirements.txt\n"," requirements_path = \"content/bert-model/requirements.txt\"\n"," try:\n"," with open(requirements_path, 'w') as f:\n"," import sys\n"," subprocess.check_call([sys.executable, '-m', 'pip', 'freeze'], stdout=f)\n"," except subprocess.CalledProcessError as e:\n"," print(f\"Warning: Failed to generate requirements.txt: {e}. Logging without pip_requirements.\")\n"," requirements_path = None\n"," except Exception as e:\n"," print(f\"An unexpected error occurred during requirements generation: {e}\")\n"," requirements_path = None\n","\n"," # --- Enregistrer le modèle PyTorch sans input_example ---\n"," try:\n"," mlflow.pytorch.log_model(\n"," pytorch_model=model,\n"," artifact_path=\"model\", # Simplement \"model\" pour être cohérent\n"," signature=signature,\n"," pip_requirements=requirements_path if requirements_path else None\n"," )\n"," print(f\"Model '{model_name}' logged to MLflow artifact path 'model_{model_name}'.\")\n"," except Exception as e:\n"," print(f\"FATAL: Error logging PyTorch model to MLflow: {e}\")\n"," return None\n","\n"," print(f\"MLflow logging attempt finished for run '{run.info.run_id}'.\")\n","\n"," return run.info.run_id"]},{"cell_type":"code","source":["# FONCTION POUR ENREGISTRER LE MODÈLE DANS MLFLOW\n","def log_model_to_mlflow(model, tokenizer, model_name, metrics, history=None, params=None):\n"," \"\"\"\n"," Enregistre le modèle et ses performances dans MLflow, y compris la signature.\n"," Gère correctement les devices et la sérialisation JSON des métriques NumPy.\n"," Supprime input_example pour éviter l'avertissement de validation avec le flavor pytorch.\n","\n"," Args:\n"," model (torch.nn.Module): Le modèle PyTorch (Hugging Face) entraîné.\n"," tokenizer: Le tokenizer Hugging Face associé au modèle.\n"," model_name (str): Un nom pour identifier ce modèle/cette exécution.\n"," metrics (dict): Un dictionnaire contenant les métriques de performance.\n"," params (dict, optional): Un dictionnaire des hyperparamètres utilisés. Defaults to None.\n","\n"," Returns:\n"," str: L'ID de l'exécution MLflow.\n"," \"\"\"\n"," class_names = ['Négatif', 'Positif']\n","\n"," # --- Définir la signature du modèle ---\n"," sample_input_text = [\"Ceci est un exemple de texte pour inférer la signature.\"]\n"," inputs = tokenizer(\n"," sample_input_text, return_tensors=\"pt\", padding=\"max_length\",\n"," truncation=True, max_length=128\n"," )\n"," try:\n"," model_device = next(model.parameters()).device\n"," except StopIteration:\n"," model_device = torch.device(\"cpu\")\n"," except Exception:\n"," model_device = torch.device(\"cpu\")\n","\n"," inputs_on_device = None\n"," try:\n"," inputs_on_device = {k: v.to(model_device) for k, v in inputs.items()}\n"," except Exception as e:\n"," print(f\"Error moving input tensors to device {model_device}: {e}. Skipping signature inference.\")\n","\n"," input_data_for_signature = {k: v.cpu().numpy() for k, v in inputs.items()} # For potential future use, still on CPU\n","\n"," predictions = None\n"," signature = None\n"," if inputs_on_device:\n"," model.eval()\n"," with torch.no_grad():\n"," try:\n"," outputs = model(**inputs_on_device)\n"," if hasattr(outputs, 'logits'):\n"," predictions = outputs.logits.cpu().numpy()\n"," elif isinstance(outputs, torch.Tensor):\n"," predictions = outputs.cpu().numpy()\n"," else:\n"," # Fallback attempt\n"," for val in getattr(outputs, 'values', lambda: [])(): # Handle dicts or sequences\n"," if isinstance(val, torch.Tensor):\n"," predictions = val.cpu().numpy()\n"," break\n","\n"," if predictions is not None:\n"," # Infer signature using the CPU numpy inputs and CPU numpy predictions\n"," signature = infer_signature(input_data_for_signature, predictions)\n"," print(\"Model signature inferred successfully.\")\n"," else:\n"," print(\"Warning: Could not extract prediction tensor for signature.\")\n","\n"," except Exception as e:\n"," print(f\"Error during model prediction or signature inference: {e}\")\n"," else:\n"," print(\"Skipping signature inference because input tensors could not be moved to model device.\")\n"," # --- Fin de la définition de la signature ---\n","\n"," # Démarrer une exécution MLflow\n"," with mlflow.start_run(run_name=f\"Model_Bert-{model_name}\") as run:\n","\n"," # Enregistrer les paramètres du modèle\n"," if params:\n"," for key, value in params.items():\n"," mlflow.log_param(key, value)\n","\n"," if history is not None:\n"," val_precisions = history.get(\"val_precision\", [])\n"," if val_precisions:\n"," best_epoch_idx = val_precisions.index(max(val_precisions))\n","\n"," val_precision_at_best = val_precisions[best_epoch_idx]\n"," val_loss_at_best = history.get(\"val_loss\", [None])[best_epoch_idx]\n"," val_recall_at_best = history.get(\"val_recall\", [None])[best_epoch_idx]\n","\n"," mlflow.log_metric(\"val_precision\", val_precision_at_best)\n"," mlflow.log_metric(\"val_loss\", val_loss_at_best)\n"," mlflow.log_metric(\"val_recall\", val_recall_at_best)\n","\n","\n"," # Enregistrer les métriques de performance\n"," mlflow.log_metric(\"test_precision\", metrics['precision'])\n"," mlflow.log_metric(\"test_accuracy\", metrics['accuracy'])\n"," mlflow.log_metric(\"test_recall\", metrics['recall'])\n"," mlflow.log_metric(\"test_f1\", metrics['f1'])\n"," mlflow.log_metric(\"test_roc_auc\", metrics['roc_auc'])\n","\n"," mlflow.log_param(\"model_base\", model_name)\n"," mlflow.set_tag(\"model_base\", model_name)\n","\n"," # Tracer et enregistrer la matrice de confusion\n"," fig_cm = plot_confusion_matrix(metrics['confusion_matrix'], class_names)\n"," mlflow.log_figure(fig_cm, f\"confusion_matrix_{model_name}.png\")\n","\n"," # Tracer et enregistrer la courbe ROC\n"," fig_roc = plot_roc_curve(metrics['fpr'], metrics['tpr'], metrics['roc_auc'], model_name)\n"," mlflow.log_figure(fig_roc, f\"roc_curve_{model_name}.png\")\n","\n"," # Enregistrer le rapport de classification\n"," report_path = f\"content/bert-model/classification_report.txt\"\n"," os.makedirs(os.path.dirname(report_path), exist_ok=True)\n"," with open(report_path, \"w\") as f:\n"," f.write(metrics['classification_report'])\n"," mlflow.log_artifact(report_path)\n","\n","\n"," # Sauvegarder le modèle localement\n"," model_path = f\"content/bert-model/model\"\n"," os.makedirs(model_path, exist_ok=True)\n"," try:\n"," model.save_pretrained(model_path)\n"," print(\"Model logged to MLflow artifact path 'model'.\")\n"," except Exception as e:\n"," print(f\"Warning: Failed to save or log model: {e}\")\n","\n","\n"," # Enregistrer le tokenizer localement et sur MLFlow\n"," tokenizer_path = f\"content/bert-model/tokenizer\"\n"," os.makedirs(tokenizer_path, exist_ok=True)\n"," try:\n"," tokenizer.save_pretrained(tokenizer_path)\n"," mlflow.log_artifact(tokenizer_path)\n"," print(\"Tokenizer logged to MLflow artifact path 'tokenizer'.\")\n"," except Exception as e:\n"," print(f\"Warning: Failed to save or log tokenizer: {e}\")\n","\n","\n"," # --- Sauvegarder la fonction preprocessor via pickle ---\n"," local_save_directory = \"content/bert-model/preprocessor\" # Attention au double \"content\" si c'est bien voulu\n"," local_pickle_filename = \"preprocessor.pkl\"\n"," local_pickle_filepath = os.path.join(local_save_directory, local_pickle_filename)\n"," # Sous-dossier cible dans les artefacts MLflow\n"," mlflow_artifact_subdir = \"preprocessor\"\n"," try:\n"," os.makedirs(local_save_directory, exist_ok=True)\n"," with open(local_pickle_filepath, \"wb\") as f_out:\n"," pickle.dump(preprocess_tweet_for_bert, f_out)\n"," mlflow.log_artifact(local_pickle_filepath, artifact_path=mlflow_artifact_subdir)\n"," except NameError:\n"," print(f\"\\nWarning: La fonction 'preprocess_tweet_for_bert' n'est pas définie. Impossible de la sauvegarder.\\n\")\n"," except Exception as e:\n"," print(f\"\\nWarning: Impossible de sauvegarder (pickle) ou logger la fonction de prétraitement : {e}\\n\")\n","\n","\n"," # Enregistrer le fichier requirements.txt\n"," requirements_path = \"content/bert-model/requirements.txt\"\n"," try:\n"," with open(requirements_path, 'w') as f:\n"," import sys\n"," subprocess.check_call([sys.executable, '-m', 'pip', 'freeze'], stdout=f)\n"," except subprocess.CalledProcessError as e:\n"," print(f\"Warning: Failed to generate requirements.txt: {e}. Logging without pip_requirements.\")\n"," requirements_path = None\n"," except Exception as e:\n"," print(f\"An unexpected error occurred during requirements generation: {e}\")\n"," requirements_path = None\n","\n"," # --- Enregistrer le modèle PyTorch sans input_example ---\n"," try:\n"," mlflow.pytorch.log_model(\n"," pytorch_model=model,\n"," artifact_path=\"model\", # Simplement \"model\" pour être cohérent\n"," signature=signature,\n"," pip_requirements=requirements_path if requirements_path else None\n"," )\n"," print(f\"Model '{model_name}' logged to MLflow artifact path 'model_{model_name}'.\")\n"," except Exception as e:\n"," print(f\"FATAL: Error logging PyTorch model to MLflow: {e}\")\n"," return None\n","\n"," print(f\"MLflow logging attempt finished for run '{run.info.run_id}'.\")\n","\n"," return run.info.run_id"],"metadata":{"id":"1PHu82H-YNtN","executionInfo":{"status":"ok","timestamp":1751448490132,"user_tz":-120,"elapsed":34,"user":{"displayName":"Grégoire","userId":"01249298206117462766"}}},"execution_count":18,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"o1uxWQimT76g"},"source":["## Entrainement du modèle : `DistilBertForSequenceClassification`\n","\n","[DistilBertForSequenceClassification](https://huggingface.co/docs/transformers/model_doc/distilbert#transformers.DistilBertForSequenceClassification) est un modèle basé sur **DistilBERT** avec **une couche de classification ou de régression ajoutée à la tête (head)**. Il est conçu pour les tâches de **classification de séquence**.\n","\n","**DistilBertForSequenceClassification** est une version allégée de BERT, idéale pour des tâches de classification de texte. Il est facile à utiliser grâce aux classes `AutoTokenizer` et `AutoModel`, et il prend en charge plusieurs types de classification (mono-étiquette, multi-étiquettes). Il s’intègre naturellement avec **PyTorch** et peut être fine-tuné sur des tâches spécifiques.\n","\n","#### 📌 **Caractéristiques principales :** \n","- **Hérite de `PreTrainedModel`**, ce qui lui permet d’utiliser les fonctionnalités standards des modèles pré-entraînés (téléchargement, sauvegarde, ajustement des embeddings, etc.). \n","- **Sous-classe de `torch.nn.Module`**, donc il fonctionne comme un module PyTorch classique. \n","- Utilisé pour **la classification de texte** (binaire, multi-classes ou multi-étiquettes). \n","\n","### ⚙️ **Méthode `forward` (passage avant du modèle)** \n","\n","**Entrées possibles :** \n","- `input_ids` : Identifiants des tokens dans le vocabulaire (obtenus avec un tokenizer). \n","- `attention_mask` : Masque pour ignorer les tokens de padding (1 = pris en compte, 0 = ignoré). \n","- `head_mask` : Masque pour désactiver certaines têtes d’attention. \n","- `inputs_embeds` : Embeddings d’entrée à utiliser à la place de `input_ids`. \n","- `labels` : Étiquettes pour la classification, utilisées pour calculer la perte (`CrossEntropy` ou `MSE` selon `num_labels`). \n","\n","**Sorties possibles :** \n","- `loss` : Perte (si `labels` est fourni). \n","- `logits` : Scores de classification (avant application de **Softmax**). \n","- `hidden_states` : États cachés de toutes les couches du modèle (si demandé). \n","- `attentions` : Poids des mécanismes d’attention (si demandé). \n","\n","### Analyse de sentiment de tweet\n","\n","Pour une **analyse de sentiment de tweet** avec **deux classes distinctes** (0 = négatif, 1 = positif), il faut utiliser **une classification simple** (**single-label classification**). \n","\n","- Chaque tweet appartient **exclusivement** à une seule catégorie (**négatif ou positif, mais pas les deux**). \n","- La classification **multi-étiquettes** est utilisée lorsque **plusieurs catégories peuvent s’appliquer simultanément** (ex : un tweet peut être à la fois « joyeux » et « informatif »). \n","\n","### 🔹 **Comment configurer le modèle pour une classification binaire ?**\n","\n","1. Charger un modèle pré-entraîné et indiquer `num_labels=2`.\n","2. Le modèle renverra 0 ou 1, indiquant si le tweet est négatif ou positif. \n","\n","Pour entraîner le modèle sur un dataset personnalisé (ex : tweets annotés manuellement), il faudra fine-tuner DistilBERT avec une **Cross-Entropy Loss** adaptée à la classification binaire.\n","\n","### Documentation\n","\n","- `distilbert-base-uncased` : https://huggingface.co/docs/transformers/model_doc/distilbert"]},{"cell_type":"code","execution_count":19,"metadata":{"id":"kVj7LqF-bZGq","executionInfo":{"status":"ok","timestamp":1751448490146,"user_tz":-120,"elapsed":14,"user":{"displayName":"Grégoire","userId":"01249298206117462766"}}},"outputs":[],"source":["# FONCTION PRINCIPALE D'ENTRAÎNEMENT\n","def train_bert_sentiment(data_path, model_name=\"distilbert-base-uncased\", batch_size=4, epochs=3, sample_size=20000):\n"," \"\"\"\n"," Fonction principale pour l'entraînement du modèle DistilBERT sur une tâche d'analyse de sentiments.\n"," \"\"\"\n","\n"," # Définir les paramètres\n"," params = {\n"," 'model_name': model_name,\n"," 'batch_size': batch_size,\n"," 'learning_rate': 2e-5,\n"," 'epochs': epochs,\n"," 'max_length': 128,\n"," 'sample_size': sample_size\n"," }\n","\n"," # Charger les données\n"," print(\"Chargement du dataset...\")\n"," column_names = ['target', 'ids', 'date', 'flag', 'user', 'text']\n","\n"," try:\n"," raw_data = pd.read_csv(data_path, encoding='utf-8', names=column_names)\n"," except UnicodeDecodeError:\n"," print(\"Lecture du fichier en latin-1...\")\n"," raw_data = pd.read_csv(data_path, encoding='latin-1', names=column_names)\n","\n"," # Préparer les données\n"," print(\"Préparation des données...\")\n"," data_splits = prepare_data(raw_data, sample_size=sample_size)\n","\n"," # Initialiser le tokenizer et le modèle\n"," print(\"Initialisation du modèle DistilBERT...\")\n"," tokenizer = DistilBertTokenizer.from_pretrained(model_name)\n"," model = DistilBertForSequenceClassification.from_pretrained(\n"," model_name,\n"," num_labels=2 # Sentiment binaire (0 = négatif, 1 = positif)\n"," )\n","\n"," # Ajustement du batch size selon la mémoire GPU\n"," adjusted_batch_size = min(8, batch_size)\n"," print(f\"Batch size ajusté à {adjusted_batch_size} pour économiser la mémoire GPU.\")\n","\n"," # Création des datasets et des dataloaders\n"," train_dataset = TweetDataset(data_splits['train']['texts'], data_splits['train']['labels'], tokenizer)\n"," val_dataset = TweetDataset(data_splits['val']['texts'], data_splits['val']['labels'], tokenizer)\n"," test_dataset = TweetDataset(data_splits['test']['texts'], data_splits['test']['labels'], tokenizer)\n","\n"," train_loader = DataLoader(train_dataset, batch_size=adjusted_batch_size, shuffle=True)\n"," val_loader = DataLoader(val_dataset, batch_size=adjusted_batch_size * 2)\n"," test_loader = DataLoader(test_dataset, batch_size=adjusted_batch_size * 2)\n","\n"," # Détection du device (GPU/CPU)\n"," device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n"," print(f\"Utilisation de : {device}\")\n","\n"," # Déplacement du modèle sur le device\n"," model.to(device)\n","\n"," # Entraînement du modèle\n"," print(\"Début de l'entraînement...\")\n","\n"," # Ajustement de l'accumulation de gradients\n"," gradient_accumulation_steps = max(1, 16 // adjusted_batch_size) # Éviter des valeurs trop élevées\n"," print(f\"Accumulation de gradient ajustée à {gradient_accumulation_steps} étapes.\")\n","\n"," history, metrics = train_model(\n"," model,\n"," train_loader,\n"," val_loader,\n"," test_loader,\n"," device,\n"," epochs=epochs,\n"," gradient_accumulation_steps=gradient_accumulation_steps\n"," )\n","\n"," # Enregistrement du modèle dans MLflow\n"," print(\"Enregistrement du modèle dans MLflow...\")\n"," run_id = log_model_to_mlflow(model, tokenizer, model_name, metrics, history, params)\n","\n"," # Tracer et enregistrer l'historique d'entraînement\n"," plot_training_history(history, model_name, run_id)\n","\n"," # Retourner le modèle entraîné et le tokenizer\n"," return {\n"," 'model': model,\n"," 'tokenizer': tokenizer,\n"," 'metrics': metrics,\n"," 'run_id': run_id\n"," }\n"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"8Y3ycjLlT76h","outputId":"d44ffc85-6cea-460f-c244-c97cfc3d8906","collapsed":true},"outputs":[{"output_type":"stream","name":"stdout","text":["Chargement du dataset...\n","Lecture du fichier en latin-1...\n","Préparation des données...\n","Prétraitement des tweets...\n","Initialisation du modèle DistilBERT...\n"]},{"output_type":"stream","name":"stderr","text":["/usr/local/lib/python3.11/dist-packages/huggingface_hub/utils/_auth.py:94: UserWarning: \n","The secret `HF_TOKEN` does not exist in your Colab secrets.\n","To authenticate with the Hugging Face Hub, create a token in your settings tab (https://huggingface.co/settings/tokens), set it as secret in your Google Colab and restart your session.\n","You will be able to reuse this secret in all of your notebooks.\n","Please note that authentication is recommended but still optional to access public models or datasets.\n"," warnings.warn(\n","Some weights of DistilBertForSequenceClassification were not initialized from the model checkpoint at distilbert-base-uncased and are newly initialized: ['classifier.bias', 'classifier.weight', 'pre_classifier.bias', 'pre_classifier.weight']\n","You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.\n","/tmp/ipython-input-12-2251732592.py:49: FutureWarning: `torch.cuda.amp.GradScaler(args...)` is deprecated. Please use `torch.amp.GradScaler('cuda', args...)` instead.\n"," scaler = GradScaler()\n","/usr/local/lib/python3.11/dist-packages/torch/amp/grad_scaler.py:132: UserWarning: torch.cuda.amp.GradScaler is enabled, but CUDA is not available. Disabling.\n"," warnings.warn(\n"]},{"output_type":"stream","name":"stdout","text":["Batch size ajusté à 8 pour économiser la mémoire GPU.\n","Utilisation de : cpu\n","Début de l'entraînement...\n","Accumulation de gradient ajustée à 2 étapes.\n","\n","=== Epoch 1/20 ===\n"]},{"output_type":"stream","name":"stderr","text":["Training: 0%| | 1/8000 [03:09<420:21:53, 189.19s/it]"]}],"source":[" Entraîner le modèle\n","bert_pack = train_bert_sentiment(\n"," data_path=csv_file_path,\n"," model_name=\"distilbert-base-uncased\",\n"," batch_size=16,\n"," epochs=20,\n"," sample_size=100000\n",")"]},{"cell_type":"markdown","metadata":{"id":"gYK8Th3rT76h"},"source":["## Chargement du modèle"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"yYPiB5A8N7l_"},"outputs":[],"source":["# FONCTION POUR CHARGER LE MODÈLE ENTRAINÉ\n","def load_bert_model(model_base_dir=\"content/bert-model\"):\n"," \"\"\"\n"," Charge le modèle BERT entraîné, le tokenizer et le preprocessor\n"," à partir de l'arborescence des artefacts sauvegardés.\n","\n"," Args:\n"," model_base_dir (str): Répertoire de base contenant tous les artefacts du modèle\n"," (par défaut: \"content/bert-model\")\n","\n"," Returns:\n"," dict: Un dictionnaire contenant le modèle, le tokenizer et la fonction de prétraitement\n"," \"\"\"\n"," # Définir les chemins des différents artefacts\n"," model_dir = os.path.join(model_base_dir, \"model\")\n"," tokenizer_dir = os.path.join(model_base_dir, \"tokenizer\")\n"," preprocessor_dir = os.path.join(model_base_dir, \"preprocessor\")\n"," preprocessor_file = os.path.join(preprocessor_dir, \"preprocessor.pkl\")\n","\n"," # Charger le modèle\n"," try:\n"," model = DistilBertForSequenceClassification.from_pretrained(model_dir)\n"," print(f\"Modèle chargé avec succès depuis '{model_dir}'\")\n"," except Exception as e:\n"," print(f\"Erreur lors du chargement du modèle : {e}\")\n"," raise\n","\n"," # Charger le tokenizer\n"," try:\n"," tokenizer = DistilBertTokenizer.from_pretrained(tokenizer_dir)\n"," print(f\"Tokenizer chargé avec succès depuis '{tokenizer_dir}'\")\n"," except Exception as e:\n"," print(f\"Erreur lors du chargement du tokenizer : {e}\")\n"," raise\n","\n"," # Charger la fonction de prétraitement\n"," try:\n"," with open(preprocessor_file, \"rb\") as f_in:\n"," preprocess_tweet_for_bert = pickle.load(f_in)\n"," print(f\"Fonction 'preprocess_tweet_for_bert' chargée avec succès depuis '{preprocessor_file}'\")\n"," except FileNotFoundError:\n"," print(f\"Erreur : Fichier preprocessor introuvable à l'emplacement '{preprocessor_file}'.\")\n"," preprocess_tweet_for_bert = None\n"," except Exception as e:\n"," print(f\"Erreur lors du chargement de la fonction de prétraitement : {e}\")\n"," preprocess_tweet_for_bert = None\n","\n"," # Vérifier si on a bien chargé la fonction de prétraitement\n"," if preprocess_tweet_for_bert is None:\n"," print(\"ATTENTION: Fonction de prétraitement non chargée. Les prédictions pourraient être affectées.\")\n","\n"," # Création d'un pack pour faciliter l'utilisation\n"," model_pack = {\n"," 'model': model,\n"," 'tokenizer': tokenizer,\n"," 'preprocess': preprocess_tweet_for_bert\n"," }\n","\n"," return model_pack"]},{"cell_type":"markdown","metadata":{"id":"tB3Uiz5pT76h"},"source":["## Fonction de prédiction"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"RkFU6mWxT76h"},"outputs":[],"source":["# FONCTION DE PRÉDICTION\n","def predict_sentiment(tweet, model_pack):\n"," \"\"\"\n"," Prédit le sentiment d'un tweet à l'aide du modèle BERT\n"," \"\"\"\n"," model = model_pack['model']\n"," tokenizer = model_pack['tokenizer']\n"," preprocess = model_pack['preprocess']\n","\n"," # Prétraitement du tweet\n"," processed_tweet = preprocess(tweet)\n","\n"," # Tokenisation\n"," encoding = tokenizer(\n"," processed_tweet,\n"," return_tensors=\"pt\",\n"," truncation=True,\n"," padding=True,\n"," max_length=128\n"," )\n","\n"," # Détection du device\n"," device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n"," model = model.to(device)\n","\n"," # Passer au mode évaluation\n"," model.eval()\n","\n"," # Déplacer les tenseurs sur le device\n"," input_ids = encoding['input_ids'].to(device)\n"," attention_mask = encoding['attention_mask'].to(device)\n","\n"," # Prédiction\n"," with torch.no_grad():\n"," outputs = model(input_ids=input_ids, attention_mask=attention_mask)\n"," logits = outputs.logits\n"," probabilities = torch.nn.functional.softmax(logits, dim=1)\n","\n"," # Récupérer le sentiment prédit et le score de confiance\n"," predicted_class = torch.argmax(probabilities, dim=1).item()\n"," confidence_score = probabilities[0][predicted_class].item()\n"," raw_score = probabilities[0][1].item() # Score de la classe positive\n","\n"," # Convertir en étiquette lisible\n"," sentiment = \"Positif\" if predicted_class == 1 else \"Négatif\"\n","\n"," # Résultat\n"," result = {\n"," 'sentiment': sentiment,\n"," 'confidence': confidence_score,\n"," 'raw_score': raw_score\n"," }\n","\n"," return result"]},{"cell_type":"markdown","metadata":{"id":"_tNu9-3tT76h"},"source":["## Test sur un ensemble de tweets"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"ZFMlwStUT76h"},"outputs":[],"source":["# FONCTION DE TEST SUR ENSEMBLE DE TWEETS\n","def test_model_on_examples(model_pack, test_tweets):\n"," \"\"\"\n"," Teste le modèle sur une liste de tweets et retourne un DataFrame avec les résultats des prédictions.\n"," \"\"\"\n"," results = {\n"," \"Positif\": 0,\n"," \"Négatif\": 0\n"," }\n","\n"," # Créer un DataFrame pour stocker les résultats\n"," predictions_data = []\n","\n"," for i, tweet in enumerate(test_tweets):\n"," # Utiliser la fonction predict_sentiment avec model_pack\n"," result = predict_sentiment(tweet, model_pack)\n","\n"," # Ajouter l'emoji approprié pour le sentiment\n"," emoji = \"💚\" if result['sentiment'] == \"Positif\" else \"❌\"\n","\n"," # Ajouter les données au DataFrame\n"," predictions_data.append({\n"," \"Tweet\": tweet,\n"," \"Sentiment\": result['sentiment'],\n"," \"Emoji\": emoji,\n"," \"Score de confiance\": result['confidence'],\n"," \"Score brut\": result['raw_score'],\n"," \"Index\": i+1\n"," })\n","\n"," # Comptage des résultats\n"," results[result['sentiment']] += 1\n","\n"," # Créer le DataFrame\n"," df_predictions = pd.DataFrame(predictions_data)\n","\n"," # Afficher un résumé\n"," print(\"\\nTest du modèle sur les exemples:\")\n"," print(\"=\"*50)\n"," print(f\"Résumé: {len(test_tweets)} tweets analysés\")\n"," print(f\"Tweets positifs: {results['Positif']} ({(results['Positif']/len(test_tweets)*100):.1f}%)\")\n"," print(f\"Tweets négatifs: {results['Négatif']} ({(results['Négatif']/len(test_tweets)*100):.1f}%)\")\n","\n"," # Sauvegarder les résultats dans un fichier CSV\n"," os.makedirs('content/bert-model', exist_ok=True)\n"," df_predictions.to_csv(\"content/bert-model/predictions_results.csv\", index=False)\n","\n"," # Retourner le DataFrame\n"," return df_predictions"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"Tkb9WDu-T76h"},"outputs":[],"source":["# Liste améliorée de tweets pour tester le modèle\n","test_tweets_improved = [\n"," # Tweets positifs avec différentes caractéristiques\n"," \"I absolutely love flying with @AirParadis! Their service is exceptional and the staff is always so friendly :) #bestairline #travel\",\n"," \"I just landed after an amazing flight... The crew was truly fantastic and the food was delicious! Can't wait to fly with them again :D\",\n"," \"Wow! You have to try @AirParadis. Best. Flight. Ever. Their new seats are incredibly comfortable. http://airparadis.com/newseats #travel #happy #AirParadis\",\n"," \"It was my first time flying business class, and I’m completely blown away!!! The service is definitely worth it... @AirParadis never lets me down :)\",\n"," \"What an incredible flight experience with @AirParadis! Everything was perfect from takeoff to landing. Would do it again in a heartbeat! #travel #satisfaction\",\n","\n"," # Tweets négatifs avec différentes caractéristiques\n"," \"Honestly, my last flight with @AirParadis was disappointing. The flight was delayed without any notice. #disappointment\",\n"," \"I had high hopes for @AirParadis, but the seat was uncomfortable and the staff seemed overwhelmed during the flight. Not the best experience.\",\n"," \"So frustrated with @AirParadis this time. The staff was unresponsive and it ruined our trip. #nothappy\",\n"," \"The flight I booked with @AirParadis was canceled at the last minute. Very disappointing and no adequate communication!\",\n"," \"The quality of service with @AirParadis has declined. I expected better. It left a sour taste. #disappointed\",\n","\n"," # Tweets mixtes et nuancés\n"," \"To be honest, @AirParadis has significantly improved their service since last year! Not perfect yet, but they are making an effort... #progress\",\n"," \"We were really looking forward to our trip, but our flight was delayed... at least the team was very apologetic and provided us with complimentary drinks! @AirParadis\",\n"," \"The flight with @AirParadis had great moments, but the delay somewhat tarnished the experience. I'll give them another chance!\",\n"," \"Overall, @AirParadis has good service, but I had a few minor issues with the check-in process. It could be better! #mixedfeelings\",\n"," \"While our flight was pleasant, the lack of communication regarding the schedule was a bit frustrating. @AirParadis can do better. #nuanced\"\n","]\n","\n","# Charger le modèle entraîné\n","model_pack = load_bert_model()\n","\n","# Tester le modèle sur les exemples\n","df_results = test_model_on_examples(model_pack, test_tweets_improved)\n","\n","print(\"Analyse de sentiment terminée! Résultats disponibles dans content/bert-model/predictions_results.csv\")"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"XIHAcDH3E1rH"},"outputs":[],"source":["df_results"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"-mJxfVLiGfTL"},"outputs":[],"source":["# Liste améliorée de tweets pour tester le modèle\n","unambiguous_tweets = [\n"," # --- Strongly Positive ---\n"," \"@AirParadis offers absolutely FLAWLESS service! Best airline, period. I recommend it 1000%! 😍 #DreamTrip\",\n"," \"PERFECT flight with @AirParadis today. Amazing staff, clean plane, arrived early. What more could you ask for? ⭐⭐⭐⭐⭐ #HappyCustomer\",\n"," \"I'm totally a fan of @AirParadis! Every flight is a pleasure. Comfort, kindness, punctuality... they have it all! Thank you! 😊✈️\",\n"," \"Extraordinary experience in business class on @AirParadis. Worth every penny. Luxury and attention to detail. WELL DONE! 👏 #LuxuryTravel\",\n"," \"@AirParadis saved my vacation! After a cancellation with another airline, they immediately found me a seat. Top-notch customer service! 🙏 #Saved\",\n","\n"," # --- Strongly Negative ---\n"," \"AVOID @AirParadis! The worst customer service I've ever seen. Rude, incompetent, and they lost my luggage! An absolute disgrace. 😠 #AirlineToAvoid\",\n"," \"DISASTROUS flight with @AirParadis. 5-hour delay, no information, unfriendly staff. NEVER flying with them again. 🤬 #TripRuined\",\n"," \"How can @AirParadis dare to charge for seats that are so dirty and uncomfortable? It's outright theft. Horrible airline. 👎 #Scam\",\n"," \"@AirParadis let me down. Flight canceled for no valid reason, no solution offered. Zero service. I'm disgusted. 😤 #Abandoned\",\n"," \"The food served on @AirParadis was simply inedible. Cold, tasteless... I've never eaten anything so bad on a flight. 🤢 #AirplaneFood\"\n","]\n","\n","# Charger le modèle entraîné\n","# Assurez-vous que la fonction load_bert_model est définie et fonctionnelle\n","model_pack = load_bert_model()\n","\n","# Tester le modèle sur les exemples\n","# Assurez-vous que la fonction test_model_on_examples est définie,\n","# prend le model_pack et la liste de tweets, et renvoie un DataFrame\n","df_results = test_model_on_examples(model_pack, unambiguous_tweets)\n","\n","# Afficher un message indiquant où trouver les résultats (si sauvegardés par la fonction)\n","print(\"Analyse de sentiment terminée! Vérifiez si les résultats sont dans content/bert-model/predictions_results.csv\")\n","\n","# Optionnel : Afficher les premières lignes du DataFrame résultant\n","df_results"]},{"cell_type":"markdown","source":["# Enregistrement de dépendances"],"metadata":{"id":"hkxoyNm_t6YG"}},{"cell_type":"code","source":["!pip freeze > requirements_bert.txt"],"metadata":{"id":"HjaFxsm3t9eP"},"execution_count":null,"outputs":[]}],"metadata":{"accelerator":"GPU","colab":{"gpuType":"T4","provenance":[],"toc_visible":true},"kernelspec":{"display_name":"Python 3","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.10.12"}},"nbformat":4,"nbformat_minor":0}