diff --git a/.gitignore b/.gitignore index e1d4cc1..74ef075 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -**/mlruns -**/downloaded_artifacts +**/mlruns +**/downloaded_artifacts diff --git a/README.fr.md b/README.fr.md index 3cb13ee..88345a6 100644 --- a/README.fr.md +++ b/README.fr.md @@ -1,62 +1,62 @@ -# Projet Air Paradis – Analyse de sentiments - -📘 Ce projet est également disponible en [anglais 🇬🇧](./README.md) - -Ce dépôt contient les notebooks et le code source du projet d'analyse de sentiments sur des tweets, réalisé dans le cadre du programme OpenClassrooms **"Air Paradis"**. - ---- - -## Contexte métier - -L'objectif est de prédire si un tweet (ou une courte expression écrite) est perçu comme **positif ou négatif**, dans le contexte d'une compagnie aérienne. -Le projet repose sur le jeu de données public **Sentiment140**. - ---- - -## Description du projet - -Le projet vise à développer un système complet d'analyse de sentiments, comprenant : - -- Une analyse exploratoire des données textuelles, -- Une comparaison rigoureuse de 10+ modèles de classification (classiques ML, LSTM + word embeddings, DistilBERT), -- Le déploiement d'une API REST FastAPI exposant le meilleur modèle avancé (LSTM + Word2Vec), -- La mise en place d'un pipeline MLOps complet : MLFlow, CI/CD (GitHub Actions), monitoring, -- Un hébergement local sur serveur NAS pour garantir la souveraineté des données. - -📖 **Pour des informations détaillées sur les expérimentations menées et les résultats obtenus, consultez [docs/blog.fr.md](./docs/blog.fr.md).** - ---- - -## Structure du dépôt - -- `notebooks/` : notebooks d'analyse exploratoire et de modélisation, -- `deployment/` : code de l'API FastAPI, tests unitaires, Dockerfile et configuration déploiement, -- `docs/` : documentation détaillée (blog.fr.md), diagrammes, captures d'écran, -- `presentation/` : support de présentation du projet (PDF), -- `../.github/workflows/` : pipelines CI/CD (tests automatisés, build Docker, push DockerHub). - ---- - -## Entraînement des modèles - -Les phases d'entraînement ont principalement été réalisées sur **Google Colab**, afin de bénéficier de ressources GPU nécessaires aux modèles de deep learning. - ---- - -## Utilisation - -Les notebooks détaillent les étapes d'analyse et d'entraînement. -L'API est disponible dans le dossier `deployment/api` avec interface Streamlit de test. - -Pour tester l'API en production : https://sentiment-api.greg-madman-nas.duckdns.org - ---- - -## Remarques - -- Les fichiers `requirements.txt` dans `notebooks/` proviennent des environnements Google Colab et peuvent contenir des dépendances peu optimisées pour un usage local. -- Le fichier `.env`, exclu du dépôt, contient les variables d'environnement nécessaires (configuration MLFlow, MinIO, alerting). - ---- - +# Projet Air Paradis – Analyse de sentiments + +📘 Ce projet est également disponible en [anglais 🇬🇧](./README.md) + +Ce dépôt contient les notebooks et le code source du projet d'analyse de sentiments sur des tweets, réalisé dans le cadre du programme OpenClassrooms **"Air Paradis"**. + +--- + +## Contexte métier + +L'objectif est de prédire si un tweet (ou une courte expression écrite) est perçu comme **positif ou négatif**, dans le contexte d'une compagnie aérienne. +Le projet repose sur le jeu de données public **Sentiment140**. + +--- + +## Description du projet + +Le projet vise à développer un système complet d'analyse de sentiments, comprenant : + +- Une analyse exploratoire des données textuelles, +- Une comparaison rigoureuse de 10+ modèles de classification (classiques ML, LSTM + word embeddings, DistilBERT), +- Le déploiement d'une API REST FastAPI exposant le meilleur modèle avancé (LSTM + Word2Vec), +- La mise en place d'un pipeline MLOps complet : MLFlow, CI/CD (GitHub Actions), monitoring, +- Un hébergement local sur serveur NAS pour garantir la souveraineté des données. + +📖 **Pour des informations détaillées sur les expérimentations menées et les résultats obtenus, consultez [docs/blog.fr.md](./docs/blog.fr.md).** + +--- + +## Structure du dépôt + +- `notebooks/` : notebooks d'analyse exploratoire et de modélisation, +- `deployment/` : code de l'API FastAPI, tests unitaires, Dockerfile et configuration déploiement, +- `docs/` : documentation détaillée (blog.fr.md), diagrammes, captures d'écran, +- `presentation/` : support de présentation du projet (PDF), +- `../.github/workflows/` : pipelines CI/CD (tests automatisés, build Docker, push DockerHub). + +--- + +## Entraînement des modèles + +Les phases d'entraînement ont principalement été réalisées sur **Google Colab**, afin de bénéficier de ressources GPU nécessaires aux modèles de deep learning. + +--- + +## Utilisation + +Les notebooks détaillent les étapes d'analyse et d'entraînement. +L'API est disponible dans le dossier `deployment/api` avec interface Streamlit de test. + +Pour tester l'API en production : https://sentiment-api.greg-madman-nas.duckdns.org + +--- + +## Remarques + +- Les fichiers `requirements.txt` dans `notebooks/` proviennent des environnements Google Colab et peuvent contenir des dépendances peu optimisées pour un usage local. +- Le fichier `.env`, exclu du dépôt, contient les variables d'environnement nécessaires (configuration MLFlow, MinIO, alerting). + +--- + **Projet réalisé dans le cadre du parcours "Data Scientist – Projet 7" d'OpenClassrooms** \ No newline at end of file diff --git a/README.md b/README.md index fc89214..7d5bb61 100644 --- a/README.md +++ b/README.md @@ -1,62 +1,62 @@ -# Air Paradis Project – Sentiment Analysis - -📘 This project is also available in [French 🇫🇷](./README.fr.md) - -This repository contains the notebooks and source code for the sentiment analysis project on tweets, conducted as part of the OpenClassrooms **"Air Paradis"** program. - ---- - -## Business Context - -The objective is to predict whether a tweet (or short written expression) is perceived as **positive or negative**, in the context of an airline company. -The project is based on the public **Sentiment140** dataset. - ---- - -## Project Description - -The project aims to develop a complete sentiment analysis system, including: - -- Exploratory analysis of textual data, -- Rigorous comparison of 10+ classification models (classical ML, LSTM + word embeddings, DistilBERT), -- Deployment of a FastAPI REST API exposing the best advanced model (LSTM + Word2Vec), -- Implementation of a complete MLOps pipeline: MLFlow, CI/CD (GitHub Actions), monitoring, -- Local hosting on NAS server to ensure data sovereignty. - -📖 **For detailed information on experiments conducted and results obtained, see [docs/blog.md](./docs/blog.md).** - ---- - -## Repository Structure - -- `notebooks/`: exploratory analysis and modeling notebooks, -- `deployment/`: FastAPI API code, unit tests, Dockerfile and deployment configuration, -- `docs/`: detailed documentation (blog.md), diagrams, screenshots, -- `presentation/`: project presentation slides (PDF), -- `../.github/workflows/`: CI/CD pipelines (automated tests, Docker build, DockerHub push). - ---- - -## Model Training - -Training phases were primarily conducted on **Google Colab** to leverage GPU resources required for deep learning models. - ---- - -## Usage - -Notebooks detail the analysis and training steps. -The API is available in the `deployment/api` folder with Streamlit testing interface. - -To test the production API: https://sentiment-api.greg-madman-nas.duckdns.org - ---- - -## Notes - -- The `requirements.txt` files in `notebooks/` come from Google Colab environments and may contain dependencies not optimized for local use. -- The `.env` file, excluded from the repository, contains necessary environment variables (MLFlow, MinIO, alerting configuration). - ---- - +# Air Paradis Project – Sentiment Analysis + +📘 This project is also available in [French 🇫🇷](./README.fr.md) + +This repository contains the notebooks and source code for the sentiment analysis project on tweets, conducted as part of the OpenClassrooms **"Air Paradis"** program. + +--- + +## Business Context + +The objective is to predict whether a tweet (or short written expression) is perceived as **positive or negative**, in the context of an airline company. +The project is based on the public **Sentiment140** dataset. + +--- + +## Project Description + +The project aims to develop a complete sentiment analysis system, including: + +- Exploratory analysis of textual data, +- Rigorous comparison of 10+ classification models (classical ML, LSTM + word embeddings, DistilBERT), +- Deployment of a FastAPI REST API exposing the best advanced model (LSTM + Word2Vec), +- Implementation of a complete MLOps pipeline: MLFlow, CI/CD (GitHub Actions), monitoring, +- Local hosting on NAS server to ensure data sovereignty. + +📖 **For detailed information on experiments conducted and results obtained, see [docs/blog.md](./docs/blog.md).** + +--- + +## Repository Structure + +- `notebooks/`: exploratory analysis and modeling notebooks, +- `deployment/`: FastAPI API code, unit tests, Dockerfile and deployment configuration, +- `docs/`: detailed documentation (blog.md), diagrams, screenshots, +- `presentation/`: project presentation slides (PDF), +- `../.github/workflows/`: CI/CD pipelines (automated tests, Docker build, DockerHub push). + +--- + +## Model Training + +Training phases were primarily conducted on **Google Colab** to leverage GPU resources required for deep learning models. + +--- + +## Usage + +Notebooks detail the analysis and training steps. +The API is available in the `deployment/api` folder with Streamlit testing interface. + +To test the production API: https://sentiment-api.greg-madman-nas.duckdns.org + +--- + +## Notes + +- The `requirements.txt` files in `notebooks/` come from Google Colab environments and may contain dependencies not optimized for local use. +- The `.env` file, excluded from the repository, contains necessary environment variables (MLFlow, MinIO, alerting configuration). + +--- + **Project completed as part of OpenClassrooms "Data Scientist – Project 7" program** \ No newline at end of file diff --git a/deployment/README.fr.md b/deployment/README.fr.md index c4b67d2..300d770 100644 --- a/deployment/README.fr.md +++ b/deployment/README.fr.md @@ -1,128 +1,128 @@ -# Déploiement de l’API – Projet Air Paradis - -📘 Cette documentation est aussi disponible en [anglais 🇬🇧](./README.md) - -Ce dossier contient le code et la configuration nécessaires au déploiement de l’API de prédiction de sentiment. - ---- - -## 🎯 Objectif - -Déployer une API REST permettant de : - -- Prédire le **sentiment d’un tweet** (positif ou négatif) à partir d’un modèle LSTM entraîné sur des embeddings (Word2Vec ou GloVe), -- Signaler les erreurs de prédiction. - -L’API utilise le **meilleur modèle enregistré** dans MLflow et le sert via **FastAPI**. - ---- - -## 📦 Architecture globale - -Schéma : - -``` - ┌──────────────────────────────┐ - │ Utilisateur final (UI) │ - │ (interface Streamlit) │ - └────────────┬─────────────────┘ - │ - [Appel HTTP vers /predict] - │ - ┌────────────▼─────────────┐ - │ API FastAPI │ - │ (serveur lancé via │ - │ Uvicorn) │ - └────────────┬─────────────┘ - │ - Chargement du modèle (MLflow) - │ - Prétraitement du tweet - │ - Prédiction (inférence) - │ - Réponse JSON avec le sentiment -``` - ---- - -## 🧪 Fonctionnement de l’API - -1. **Chargement du modèle** - - Le meilleur modèle est enregistré dans MLflow sous le nom `SentimentAnalysisLSTM`, avec le stage `Production`. - - L’API le charge au démarrage via l’URI : - - ``` - models:/SentimentAnalysisLSTM/Production - ``` - -2. **Prétraitement du texte** - - Nettoyage, tokenisation, lemmatisation, puis conversion en vecteurs d’embedding. - -3. **Prédiction** - - Le modèle LSTM prédit le sentiment. - - Le résultat (`positive` ou `negative`) est retourné en JSON. - -4. **Interface utilisateur (optionnelle)** - - Une interface Streamlit permet une interaction visuelle avec l’API. - ---- - -## 🐳 Déploiement via Docker - -Un `Dockerfile` est fourni pour simplifier le déploiement sur tout hôte compatible Docker (NAS, cloud, etc.). - -L’image Docker est poussée sur DockerHub à chaque version validée (voir CI/CD plus bas). - -Par souci de **souveraineté des données**, le déploiement est préféré sur un **NAS équipé d’un processeur Intel N100** (peu énergivore et suffisant pour l’inférence LSTM). - -Le redéploiement peut être automatisé via : -- une tâche `cron` simple, ou -- un conteneur **Watchtower** (dans docker-compose ou kubernetes). - -**Commande pour test local :** - -```bash -docker build -t sentiment-api . -docker run --rm --env-file ../../.env -p 8000:8000 -p 8080:8080 sentiment-api -``` - -En production, les variables d’environnement doivent être définies dans le `docker-compose.yml` ou via un gestionnaire de secrets. - ---- - -## 🛠 Problèmes AVX2 / Déploiement cloud - -**Mise à jour :** au départ, le NAS ne supportait pas les instructions AVX2 nécessaires à TensorFlow. -En réalité, les CPU N100 les prennent en charge, mais certains fabricants désactivent cette option dans le BIOS. - -En attendant un accès physique au NAS, nous avons opté pour un déploiement sur **Google Cloud**. -Problème : un seul port peut être exposé publiquement sur ces services. - -Deux solutions possibles : -- Séparer le backend (FastAPI) et le frontend (Streamlit) en deux conteneurs, -- Utiliser un **reverse proxy** (ex. : nginx) pour rediriger les ports. - -➡️ Nous avons choisi la **deuxième solution**, pour garder une **seule image Docker**. - -**Mise à jour 2 :** AVX2 a été activé via le BIOS. Le déploiement est donc opérationnel **à la fois en local (NAS)** et **sur le cloud**, avec déploiement automatique pour le NAS (voir section suivante). - ---- - -## 🔁 CI/CD – GitHub Actions - -Un pipeline est défini dans `.github/workflows/` pour : - -- Lancer les tests unitaires à chaque `push`, -- Vérifier la stabilité de l’API avant déploiement (NAS ou cloud), -- Construire et pousser l’image Docker sur DockerHub. - -Les tests sont situés dans : - -``` -deployment/api/tests/ -``` - -Le redéploiement automatique sur le NAS est géré via **Watchtower**, qui surveille les mises à jour de l’image Docker (fonctionne comme un `cron`, avec logs). - ---- +# Déploiement de l’API – Projet Air Paradis + +📘 Cette documentation est aussi disponible en [anglais 🇬🇧](./README.md) + +Ce dossier contient le code et la configuration nécessaires au déploiement de l’API de prédiction de sentiment. + +--- + +## 🎯 Objectif + +Déployer une API REST permettant de : + +- Prédire le **sentiment d’un tweet** (positif ou négatif) à partir d’un modèle LSTM entraîné sur des embeddings (Word2Vec ou GloVe), +- Signaler les erreurs de prédiction. + +L’API utilise le **meilleur modèle enregistré** dans MLflow et le sert via **FastAPI**. + +--- + +## 📦 Architecture globale + +Schéma : + +``` + ┌──────────────────────────────┐ + │ Utilisateur final (UI) │ + │ (interface Streamlit) │ + └────────────┬─────────────────┘ + │ + [Appel HTTP vers /predict] + │ + ┌────────────▼─────────────┐ + │ API FastAPI │ + │ (serveur lancé via │ + │ Uvicorn) │ + └────────────┬─────────────┘ + │ + Chargement du modèle (MLflow) + │ + Prétraitement du tweet + │ + Prédiction (inférence) + │ + Réponse JSON avec le sentiment +``` + +--- + +## 🧪 Fonctionnement de l’API + +1. **Chargement du modèle** + - Le meilleur modèle est enregistré dans MLflow sous le nom `SentimentAnalysisLSTM`, avec le stage `Production`. + - L’API le charge au démarrage via l’URI : + + ``` + models:/SentimentAnalysisLSTM/Production + ``` + +2. **Prétraitement du texte** + - Nettoyage, tokenisation, lemmatisation, puis conversion en vecteurs d’embedding. + +3. **Prédiction** + - Le modèle LSTM prédit le sentiment. + - Le résultat (`positive` ou `negative`) est retourné en JSON. + +4. **Interface utilisateur (optionnelle)** + - Une interface Streamlit permet une interaction visuelle avec l’API. + +--- + +## 🐳 Déploiement via Docker + +Un `Dockerfile` est fourni pour simplifier le déploiement sur tout hôte compatible Docker (NAS, cloud, etc.). + +L’image Docker est poussée sur DockerHub à chaque version validée (voir CI/CD plus bas). + +Par souci de **souveraineté des données**, le déploiement est préféré sur un **NAS équipé d’un processeur Intel N100** (peu énergivore et suffisant pour l’inférence LSTM). + +Le redéploiement peut être automatisé via : +- une tâche `cron` simple, ou +- un conteneur **Watchtower** (dans docker-compose ou kubernetes). + +**Commande pour test local :** + +```bash +docker build -t sentiment-api . +docker run --rm --env-file ../../.env -p 8000:8000 -p 8080:8080 sentiment-api +``` + +En production, les variables d’environnement doivent être définies dans le `docker-compose.yml` ou via un gestionnaire de secrets. + +--- + +## 🛠 Problèmes AVX2 / Déploiement cloud + +**Mise à jour :** au départ, le NAS ne supportait pas les instructions AVX2 nécessaires à TensorFlow. +En réalité, les CPU N100 les prennent en charge, mais certains fabricants désactivent cette option dans le BIOS. + +En attendant un accès physique au NAS, nous avons opté pour un déploiement sur **Google Cloud**. +Problème : un seul port peut être exposé publiquement sur ces services. + +Deux solutions possibles : +- Séparer le backend (FastAPI) et le frontend (Streamlit) en deux conteneurs, +- Utiliser un **reverse proxy** (ex. : nginx) pour rediriger les ports. + +➡️ Nous avons choisi la **deuxième solution**, pour garder une **seule image Docker**. + +**Mise à jour 2 :** AVX2 a été activé via le BIOS. Le déploiement est donc opérationnel **à la fois en local (NAS)** et **sur le cloud**, avec déploiement automatique pour le NAS (voir section suivante). + +--- + +## 🔁 CI/CD – GitHub Actions + +Un pipeline est défini dans `.github/workflows/` pour : + +- Lancer les tests unitaires à chaque `push`, +- Vérifier la stabilité de l’API avant déploiement (NAS ou cloud), +- Construire et pousser l’image Docker sur DockerHub. + +Les tests sont situés dans : + +``` +deployment/api/tests/ +``` + +Le redéploiement automatique sur le NAS est géré via **Watchtower**, qui surveille les mises à jour de l’image Docker (fonctionne comme un `cron`, avec logs). + +--- diff --git a/deployment/README.md b/deployment/README.md index bde374f..01d8cdd 100644 --- a/deployment/README.md +++ b/deployment/README.md @@ -1,126 +1,126 @@ -# API Deployment – Air Paradis Project - -📘 This documentation is also available in [French 🇫🇷](./README.fr.md) - -This folder contains the code and configuration necessary to deploy the sentiment prediction API. - ---- - -## 🎯 Objective - -Deploy a REST API that allows to: - -- Predict the sentiment of a tweet (positive or negative) using an LSTM model trained on embeddings (Word2Vec or GloVe), -- Report prediction errors. - -The API uses the best-performing model registered on MLflow and serves it via FastAPI. - ---- - -## 📦 Overall Architecture - -Diagram: - -``` - ┌──────────────────────────────┐ - │ End user (UI) │ - │ (Streamlit interface) │ - └────────────┬─────────────────┘ - │ - [HTTP call to /predict] - │ - ┌────────────▼─────────────┐ - │ FastAPI API │ - │ (server launched via │ - │ Uvicorn) │ - └────────────┬─────────────┘ - │ - Model loading from MLflow - │ - Tweet preprocessing - │ - Prediction (inference) - │ - Sentiment response (JSON) -``` - ---- - -## 🧪 API Workflow - -1. **Model loading** - - The best performing model is registered in MLflow under a fixed name (`SentimentAnalysisLSTM`) and a `Production` stage. - - The API loads this model at startup using the URI: - - ``` - models:/SentimentAnalysisLSTM/Production - ``` - -2. **Text preprocessing** - - Text is cleaned, tokenized, lemmatized, and converted into vectors (embeddings). - -3. **Prediction** - - The LSTM model (loaded from MLflow) makes a prediction. - - The result (`positive` or `negative`) is returned as JSON. - -4. **User interface (optional)** - - A Streamlit interface allows visual interaction with the API. - ---- - -## 🐳 Deployment with Docker - -A `Dockerfile` is provided for easy deployment on any Docker-compatible host (NAS, cloud, etc.). - -This Docker image should be pushed to DockerHub with every validated version (see CI/CD below). - -For data sovereignty, deployment is preferably done on a **NAS with Intel N100** CPU (energy-efficient, sufficient for inference). -Docker can be automatically redeployed using: -- `cron` (simple), or -- `Watchtower` in docker-compose/kubernetes setups. - -**Development command (local test):** - -```bash -docker build -t sentiment-api . -docker run --rm --env-file ../../.env -p 8000:8000 -p 8080:8080 sentiment-api -``` - -In production (e.g. via docker-compose or cloud services), environment variables must be defined in the compose file or secret manager. - ---- - -## 🛠 Notes on AVX2 / Cloud Deployment - -**Update:** the NAS initially failed due to lack of AVX2 support. Some N100-based NAS motherboards disable AVX2 in BIOS. - -When not accessible, fallback deployment is done on **Google Cloud**. -Issue: public cloud services do **not allow multiple ports** to be exposed easily. - -Two solutions: -- Split into two containers: FastAPI + Streamlit, -- Use a **reverse proxy** (e.g., Nginx) to map ports. - -➡️ We chose the second option to keep a **single Docker image**. - -**Update 2:** AVX2 was enabled via BIOS. Deployment is now active **both locally (NAS)** and **on cloud**, with continuous deployment on NAS (see section below). - ---- - -## 🔁 CI/CD – GitHub Actions - -A pipeline is defined in `.github/workflows/` to: - -- Run unit tests on every push, -- Check API health before deployment (local/cloud), -- Build and push Docker image to DockerHub. - -Tests are defined in: - -``` -deployment/api/tests/ -``` - -Docker image auto-redeployment on NAS is handled by **Watchtower**, which checks DockerHub updates (like a cron job, with logs). - ---- +# API Deployment – Air Paradis Project + +📘 This documentation is also available in [French 🇫🇷](./README.fr.md) + +This folder contains the code and configuration necessary to deploy the sentiment prediction API. + +--- + +## 🎯 Objective + +Deploy a REST API that allows to: + +- Predict the sentiment of a tweet (positive or negative) using an LSTM model trained on embeddings (Word2Vec or GloVe), +- Report prediction errors. + +The API uses the best-performing model registered on MLflow and serves it via FastAPI. + +--- + +## 📦 Overall Architecture + +Diagram: + +``` + ┌──────────────────────────────┐ + │ End user (UI) │ + │ (Streamlit interface) │ + └────────────┬─────────────────┘ + │ + [HTTP call to /predict] + │ + ┌────────────▼─────────────┐ + │ FastAPI API │ + │ (server launched via │ + │ Uvicorn) │ + └────────────┬─────────────┘ + │ + Model loading from MLflow + │ + Tweet preprocessing + │ + Prediction (inference) + │ + Sentiment response (JSON) +``` + +--- + +## 🧪 API Workflow + +1. **Model loading** + - The best performing model is registered in MLflow under a fixed name (`SentimentAnalysisLSTM`) and a `Production` stage. + - The API loads this model at startup using the URI: + + ``` + models:/SentimentAnalysisLSTM/Production + ``` + +2. **Text preprocessing** + - Text is cleaned, tokenized, lemmatized, and converted into vectors (embeddings). + +3. **Prediction** + - The LSTM model (loaded from MLflow) makes a prediction. + - The result (`positive` or `negative`) is returned as JSON. + +4. **User interface (optional)** + - A Streamlit interface allows visual interaction with the API. + +--- + +## 🐳 Deployment with Docker + +A `Dockerfile` is provided for easy deployment on any Docker-compatible host (NAS, cloud, etc.). + +This Docker image should be pushed to DockerHub with every validated version (see CI/CD below). + +For data sovereignty, deployment is preferably done on a **NAS with Intel N100** CPU (energy-efficient, sufficient for inference). +Docker can be automatically redeployed using: +- `cron` (simple), or +- `Watchtower` in docker-compose/kubernetes setups. + +**Development command (local test):** + +```bash +docker build -t sentiment-api . +docker run --rm --env-file ../../.env -p 8000:8000 -p 8080:8080 sentiment-api +``` + +In production (e.g. via docker-compose or cloud services), environment variables must be defined in the compose file or secret manager. + +--- + +## 🛠 Notes on AVX2 / Cloud Deployment + +**Update:** the NAS initially failed due to lack of AVX2 support. Some N100-based NAS motherboards disable AVX2 in BIOS. + +When not accessible, fallback deployment is done on **Google Cloud**. +Issue: public cloud services do **not allow multiple ports** to be exposed easily. + +Two solutions: +- Split into two containers: FastAPI + Streamlit, +- Use a **reverse proxy** (e.g., Nginx) to map ports. + +➡️ We chose the second option to keep a **single Docker image**. + +**Update 2:** AVX2 was enabled via BIOS. Deployment is now active **both locally (NAS)** and **on cloud**, with continuous deployment on NAS (see section below). + +--- + +## 🔁 CI/CD – GitHub Actions + +A pipeline is defined in `.github/workflows/` to: + +- Run unit tests on every push, +- Check API health before deployment (local/cloud), +- Build and push Docker image to DockerHub. + +Tests are defined in: + +``` +deployment/api/tests/ +``` + +Docker image auto-redeployment on NAS is handled by **Watchtower**, which checks DockerHub updates (like a cron job, with logs). + +--- diff --git a/deployment/api/.dockerignore b/deployment/api/.dockerignore index 3b1b7b4..e35743c 100644 --- a/deployment/api/.dockerignore +++ b/deployment/api/.dockerignore @@ -1,22 +1,22 @@ -# Ignorer les caches Python -__pycache__/ -*.pyc - -# Ignorer les artefacts temporaires -downloaded_artifacts/ -mlruns/ -logs/ - -# Ignorer les tests unitaires -tests/ - -# Ignorer les variables d'environnement. Elles seront plutôt chargées dans la commande docker run `docker run --env-file ./deployment/api/.env -p 8000:8000 sentiment-api` en développement local, et via des variables définies dans le yaml -*.env - -# Ignorer fichier conf amazon web services -*aws* -.elasticbeanstalk - -sentiment-api-*.json - -docker-compose.yml.example +# Ignorer les caches Python +__pycache__/ +*.pyc + +# Ignorer les artefacts temporaires +downloaded_artifacts/ +mlruns/ +logs/ + +# Ignorer les tests unitaires +tests/ + +# Ignorer les variables d'environnement. Elles seront plutôt chargées dans la commande docker run `docker run --env-file ./deployment/api/.env -p 8000:8000 sentiment-api` en développement local, et via des variables définies dans le yaml +*.env + +# Ignorer fichier conf amazon web services +*aws* +.elasticbeanstalk + +sentiment-api-*.json + +docker-compose.yml.example diff --git a/deployment/api/.gitignore b/deployment/api/.gitignore index 168175a..254b45a 100644 --- a/deployment/api/.gitignore +++ b/deployment/api/.gitignore @@ -1,6 +1,6 @@ -# ssh .pem files -*.pem - -*aws* - -sentiment-api-*.json +# ssh .pem files +*.pem + +*aws* + +sentiment-api-*.json diff --git a/deployment/api/Dockerfile b/deployment/api/Dockerfile index 4ecb683..93ff856 100644 --- a/deployment/api/Dockerfile +++ b/deployment/api/Dockerfile @@ -1,42 +1,42 @@ -FROM python:3.12-slim - -WORKDIR /app - -# # Install nginx and system packages -RUN apt-get update && \ - apt-get install -y nginx curl && \ - # Créer l'utilisateur et le groupe www-data si ce n'est pas déjà fait par l'image de base - # Assurer que l'utilisateur nginx (souvent www-data) a un ID consistent - adduser --system --no-create-home --group www-data || true && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* - -# Copy and install Python dependencies -COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt - -# Install nltk resources at build time -RUN python3 -m nltk.downloader punkt \ - && python3 -m nltk.downloader punkt_tab \ - && python3 -m nltk.downloader stopwords \ - && python3 -m nltk.downloader wordnet \ - && python3 -m nltk.downloader omw-1.4 - - -# Copy app code and configs -COPY . . - -# Copy nginx config - l'emplacement par défaut de Nginx est /etc/nginx/nginx.conf -COPY nginx.conf /etc/nginx/nginx.conf - -# Pas besoin de créer les dossiers ici, le start.sh s'en chargera avec les bonnes permissions -RUN mkdir -p /var/log/nginx /var/lib/nginx /run - -# Ensure script is executable -RUN chmod +x /app/start.sh - -# Expose only NGINX port (Cloud Run utilise 8080 par défaut) -EXPOSE 8080 - -# Start everything via script -CMD ["/app/start.sh"] +FROM python:3.12-slim + +WORKDIR /app + +# # Install nginx and system packages +RUN apt-get update && \ + apt-get install -y nginx curl && \ + # Créer l'utilisateur et le groupe www-data si ce n'est pas déjà fait par l'image de base + # Assurer que l'utilisateur nginx (souvent www-data) a un ID consistent + adduser --system --no-create-home --group www-data || true && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Copy and install Python dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Install nltk resources at build time +RUN python3 -m nltk.downloader punkt \ + && python3 -m nltk.downloader punkt_tab \ + && python3 -m nltk.downloader stopwords \ + && python3 -m nltk.downloader wordnet \ + && python3 -m nltk.downloader omw-1.4 + + +# Copy app code and configs +COPY . . + +# Copy nginx config - l'emplacement par défaut de Nginx est /etc/nginx/nginx.conf +COPY nginx.conf /etc/nginx/nginx.conf + +# Pas besoin de créer les dossiers ici, le start.sh s'en chargera avec les bonnes permissions +RUN mkdir -p /var/log/nginx /var/lib/nginx /run + +# Ensure script is executable +RUN chmod +x /app/start.sh + +# Expose only NGINX port (Cloud Run utilise 8080 par défaut) +EXPOSE 8080 + +# Start everything via script +CMD ["/app/start.sh"] diff --git a/deployment/api/app.py b/deployment/api/app.py index e7a85e6..31dbd8a 100644 --- a/deployment/api/app.py +++ b/deployment/api/app.py @@ -1,124 +1,124 @@ -import os -import streamlit as st -import requests - -API_BASE_URL = os.getenv("API_BASE_URL", "http://127.0.0.1:8080") + "/api" - - -st.set_page_config( - page_title="Air Paradis - Tweet Prediction", - page_icon="✈️", # optionnel, tu peux mettre un emoji ou un fichier image -) -st.title("Sentiment Analysis - Air Paradis") - - -tweet = st.text_area("Prompt your tweet here :", height=150) - -# Initialisation des états -if "prediction_result" not in st.session_state: - st.session_state.prediction_result = None - -if "show_report_button" not in st.session_state: - st.session_state.show_report_button = False - -if "report_sent" not in st.session_state: - st.session_state.report_sent = False - -if "last_report_sent" not in st.session_state: - st.session_state.last_report_sent = False - -def reset_state(): - st.session_state.prediction_result = None - st.session_state.show_report_button = False - st.session_state.report_sent = False - st.session_state.last_report_sent = False - -# Bouton d'analyse -if st.button("Analyze the sentiment"): - if not tweet.strip(): - st.warning("Please type a valid tweet.") - reset_state() - else: - try: - response = requests.post( - f"{API_BASE_URL}/predict", - json={"tweet": tweet} - ) - if response.status_code == 200: - data = response.json() - st.session_state.prediction_result = data - st.session_state.show_report_button = True - st.session_state.report_sent = False - st.session_state.last_report_sent = False - else: - st.error(f"Erreur API : {response.status_code} - {response.text}") - reset_state() - except Exception as e: - st.error(f"Error during API request : {e}") - reset_state() - -# Affichage du résultat -if st.session_state.prediction_result: - sentiment = st.session_state.prediction_result["sentiment"].capitalize() - prob = st.session_state.prediction_result["probability"] - st.success(f"Sentiment : {sentiment}") - st.info(f"Positive tweet probability: {prob:.2f}") - -# Gestion du bouton signalement -if st.session_state.show_report_button and not st.session_state.report_sent: - if st.button("Report a wrong prediction"): - try: - response = requests.post( - f"{API_BASE_URL}/report_error", - json={ - "tweet": tweet, - "prediction": sentiment, - "probability": st.session_state.prediction_result["probability"] # Transmettre la probabilité - } - ) - - if response.status_code == 200: - data = response.json() - # Mettre à jour les états - st.session_state.show_report_button = False - st.session_state.report_sent = True - st.session_state.last_report_sent = data.get("report_sent", False) - # Forcer un re-run - st.rerun() - else: - st.error(f"API Error : {response.status_code} - {response.text}") - except Exception as e: - st.error(f"Error during API request : {e}") - -# Message si signalement déjà fait -elif st.session_state.report_sent: - st.success("✅ Thanks for your report.") - - if st.session_state.last_report_sent: - st.info("📩 A complete report has been sent to the website administrator.") - - -st.markdown( - """ - -
- Reported wrong predictions help improve the model. Please avoid including personal information. -
- """, - unsafe_allow_html=True, -) +import os +import streamlit as st +import requests + +API_BASE_URL = os.getenv("API_BASE_URL", "http://127.0.0.1:8080") + "/api" + + +st.set_page_config( + page_title="Air Paradis - Tweet Prediction", + page_icon="✈️", # optionnel, tu peux mettre un emoji ou un fichier image +) +st.title("Sentiment Analysis - Air Paradis") + + +tweet = st.text_area("Prompt your tweet here :", height=150) + +# Initialisation des états +if "prediction_result" not in st.session_state: + st.session_state.prediction_result = None + +if "show_report_button" not in st.session_state: + st.session_state.show_report_button = False + +if "report_sent" not in st.session_state: + st.session_state.report_sent = False + +if "last_report_sent" not in st.session_state: + st.session_state.last_report_sent = False + +def reset_state(): + st.session_state.prediction_result = None + st.session_state.show_report_button = False + st.session_state.report_sent = False + st.session_state.last_report_sent = False + +# Bouton d'analyse +if st.button("Analyze the sentiment"): + if not tweet.strip(): + st.warning("Please type a valid tweet.") + reset_state() + else: + try: + response = requests.post( + f"{API_BASE_URL}/predict", + json={"tweet": tweet} + ) + if response.status_code == 200: + data = response.json() + st.session_state.prediction_result = data + st.session_state.show_report_button = True + st.session_state.report_sent = False + st.session_state.last_report_sent = False + else: + st.error(f"Erreur API : {response.status_code} - {response.text}") + reset_state() + except Exception as e: + st.error(f"Error during API request : {e}") + reset_state() + +# Affichage du résultat +if st.session_state.prediction_result: + sentiment = st.session_state.prediction_result["sentiment"].capitalize() + prob = st.session_state.prediction_result["probability"] + st.success(f"Sentiment : {sentiment}") + st.info(f"Positive tweet probability: {prob:.2f}") + +# Gestion du bouton signalement +if st.session_state.show_report_button and not st.session_state.report_sent: + if st.button("Report a wrong prediction"): + try: + response = requests.post( + f"{API_BASE_URL}/report_error", + json={ + "tweet": tweet, + "prediction": sentiment, + "probability": st.session_state.prediction_result["probability"] # Transmettre la probabilité + } + ) + + if response.status_code == 200: + data = response.json() + # Mettre à jour les états + st.session_state.show_report_button = False + st.session_state.report_sent = True + st.session_state.last_report_sent = data.get("report_sent", False) + # Forcer un re-run + st.rerun() + else: + st.error(f"API Error : {response.status_code} - {response.text}") + except Exception as e: + st.error(f"Error during API request : {e}") + +# Message si signalement déjà fait +elif st.session_state.report_sent: + st.success("✅ Thanks for your report.") + + if st.session_state.last_report_sent: + st.info("📩 A complete report has been sent to the website administrator.") + + +st.markdown( + """ + +
+ Reported wrong predictions help improve the model. Please avoid including personal information. +
+ """, + unsafe_allow_html=True, +) diff --git a/deployment/api/docker-compose.yml.example b/deployment/api/docker-compose.yml.example index 5df8d0b..a56b117 100644 --- a/deployment/api/docker-compose.yml.example +++ b/deployment/api/docker-compose.yml.example @@ -1,32 +1,32 @@ -services: - api: - build: - context: ./deployment/api - dockerfile: Dockerfile - container_name: sentiment_api - ports: - - "8000:8000" # API FastAPI - - "8501:8501" # Streamlit - environment: - MLFLOW_TRACKING_URI: "${MLFLOW_TRACKING_URI}" # ex: http://mlflow:5000 - MLFLOW_S3_ENDPOINT_URL: "${MLFLOW_S3_ENDPOINT_URL}" # ex: url MinIO ou S3 - AWS_ACCESS_KEY_ID: "${AWS_ACCESS_KEY_ID}" # clé ID S3 - AWS_SECRET_ACCESS_KEY: "${AWS_SECRET_ACCESS_KEY}" # clé secrète S3 - - SMTP_SERVER: "${SMTP_SERVER}" # serveur SMTP - SMTP_PORT: "${SMTP_PORT}" # port SMTP (ex: 587) - SMTP_EMAIL: "${SMTP_EMAIL}" # email expéditeur - SMTP_PASSWORD: "${SMTP_PASSWORD}" # mot de passe SMTP - SMTP_FROM_ALIAS: "${SMTP_FROM_ALIAS}" # alias (optionnel) - ADMIN_EMAIL: "${ADMIN_EMAIL}" # email admin destinataire - - SENTIMENT_API_BASE_URL: "${SENTIMENT_API_BASE_URL}" # public api url (used to redirect Streamlit requests to FastAPI) - -# only if you have a reverse proxy server -# networks: -# - swag - restart: always -# only if you have a reverse proxy server -# networks: -# swag: -# external: true +services: + api: + build: + context: ./deployment/api + dockerfile: Dockerfile + container_name: sentiment_api + ports: + - "8000:8000" # API FastAPI + - "8501:8501" # Streamlit + environment: + MLFLOW_TRACKING_URI: "${MLFLOW_TRACKING_URI}" # ex: http://mlflow:5000 + MLFLOW_S3_ENDPOINT_URL: "${MLFLOW_S3_ENDPOINT_URL}" # ex: url MinIO ou S3 + AWS_ACCESS_KEY_ID: "${AWS_ACCESS_KEY_ID}" # clé ID S3 + AWS_SECRET_ACCESS_KEY: "${AWS_SECRET_ACCESS_KEY}" # clé secrète S3 + + SMTP_SERVER: "${SMTP_SERVER}" # serveur SMTP + SMTP_PORT: "${SMTP_PORT}" # port SMTP (ex: 587) + SMTP_EMAIL: "${SMTP_EMAIL}" # email expéditeur + SMTP_PASSWORD: "${SMTP_PASSWORD}" # mot de passe SMTP + SMTP_FROM_ALIAS: "${SMTP_FROM_ALIAS}" # alias (optionnel) + ADMIN_EMAIL: "${ADMIN_EMAIL}" # email admin destinataire + + SENTIMENT_API_BASE_URL: "${SENTIMENT_API_BASE_URL}" # public api url (used to redirect Streamlit requests to FastAPI) + +# only if you have a reverse proxy server +# networks: +# - swag + restart: always +# only if you have a reverse proxy server +# networks: +# swag: +# external: true diff --git a/deployment/api/email_service.py b/deployment/api/email_service.py index 2c2d82c..bd2aebc 100644 --- a/deployment/api/email_service.py +++ b/deployment/api/email_service.py @@ -1,111 +1,111 @@ -import smtplib -import os -import json -from email.mime.text import MIMEText -from email.mime.multipart import MIMEMultipart -from datetime import datetime - -def send_error_report_email(error_reports): - """ - Envoie un email avec le rapport d'erreurs et le JSON intégré - """ - smtp_server = os.getenv("SMTP_SERVER") - smtp_port = int(os.getenv("SMTP_PORT", 587)) - smtp_email = os.getenv("SMTP_EMAIL") # main email address. If smt_from_alias is not, it will also be the mail sender - smtp_password = os.getenv("SMTP_PASSWORD") - smtp_from_alias = os.getenv("SMTP_FROM_ALIAS", smtp_email) # mail sender if not none (alias) - admin_email = os.getenv("ADMIN_EMAIL") # this one will receive the mail - - - if not all([smtp_server, smtp_email, smtp_password, admin_email]): - print("❌ Configuration email manquante dans .env") - return False - - try: - # Create message - msg = MIMEMultipart() - if smtp_from_alias is not None: - smtp_from = smtp_from_alias - else: - smtp_from = smtp_email - msg['From'] = f"Air Paradis Monitor <{smtp_from}>" - msg['To'] = admin_email - msg['Subject'] = f"🚨 Rapport d'erreurs - Prédictions sentiments ({datetime.now().strftime('%d/%m/%Y %H:%M')})" - - # Prepare JSON data - json_data = { - "report_generated_on": datetime.now().isoformat(), - "reports_amount": len(error_reports), - "reports": [ - { - "id": i, - "tweet": tweet, - "incorrect_prediction": prediction, - "tweet_length": len(tweet) - } - for i, (tweet, prediction) in enumerate(error_reports.items(), 1) - ], - "metadata": { - "model": "SentimentAnalysisLSTM", - "api_version": "1.0", - "reporting_threshold": 3 - } - } - - # Message body with embedded JSON - body = f""" -Hello, - -A new error report has been generated for the Air Paradis sentiment prediction API. - -📊 Total number of reports : {len(error_reports)} -📅 Report date : {datetime.now().strftime('%d/%m/%Y à %H:%M')} - -📋 Report summary : - -""" - - for i, (tweet, prediction) in enumerate(error_reports.items(), 1): - body += f"{i}. Tweet: \"{tweet[:100]}{'...' if len(tweet) > 100 else ''}\"\n" - body += f" Prediction reported as incorrect: {prediction.upper()}\n\n" - - body += """ -📈 Recommended actions: -- Analyze reported tweets to identify patterns -- Consider re-training the model if necessary -- Check quality of training data - -📊 COMPLETE DATA (JSON) : -═══════════════════════════════════════════════════════════════════════════════ - -""" - - # ✅ Directly add JSON inside the body - json_string = json.dumps(json_data, ensure_ascii=False, indent=2) - body += f"```json\n{json_string}\n```" - - body += """ - -═══════════════════════════════════════════════════════════════════════════════ - ---- -Sincerely, -🤖 API Air Paradis - Automatic monitoring system -📧 Sent from : """ + smtp_from - - # Add message body - msg.attach(MIMEText(body, 'plain', 'utf-8')) - - # Sending email - with smtplib.SMTP(smtp_server, smtp_port) as server: - server.starttls() - server.login(smtp_email, smtp_password) - server.send_message(msg) - - print(f"✅ Report email successfully sent from {smtp_from}") - print("📊 JSON embedded in message body") - return True - - except Exception as e: - print(f"❌ Error during email sending : {e}") +import smtplib +import os +import json +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart +from datetime import datetime + +def send_error_report_email(error_reports): + """ + Envoie un email avec le rapport d'erreurs et le JSON intégré + """ + smtp_server = os.getenv("SMTP_SERVER") + smtp_port = int(os.getenv("SMTP_PORT", 587)) + smtp_email = os.getenv("SMTP_EMAIL") # main email address. If smt_from_alias is not, it will also be the mail sender + smtp_password = os.getenv("SMTP_PASSWORD") + smtp_from_alias = os.getenv("SMTP_FROM_ALIAS", smtp_email) # mail sender if not none (alias) + admin_email = os.getenv("ADMIN_EMAIL") # this one will receive the mail + + + if not all([smtp_server, smtp_email, smtp_password, admin_email]): + print("❌ Configuration email manquante dans .env") + return False + + try: + # Create message + msg = MIMEMultipart() + if smtp_from_alias is not None: + smtp_from = smtp_from_alias + else: + smtp_from = smtp_email + msg['From'] = f"Air Paradis Monitor <{smtp_from}>" + msg['To'] = admin_email + msg['Subject'] = f"🚨 Rapport d'erreurs - Prédictions sentiments ({datetime.now().strftime('%d/%m/%Y %H:%M')})" + + # Prepare JSON data + json_data = { + "report_generated_on": datetime.now().isoformat(), + "reports_amount": len(error_reports), + "reports": [ + { + "id": i, + "tweet": tweet, + "incorrect_prediction": prediction, + "tweet_length": len(tweet) + } + for i, (tweet, prediction) in enumerate(error_reports.items(), 1) + ], + "metadata": { + "model": "SentimentAnalysisLSTM", + "api_version": "1.0", + "reporting_threshold": 3 + } + } + + # Message body with embedded JSON + body = f""" +Hello, + +A new error report has been generated for the Air Paradis sentiment prediction API. + +📊 Total number of reports : {len(error_reports)} +📅 Report date : {datetime.now().strftime('%d/%m/%Y à %H:%M')} + +📋 Report summary : + +""" + + for i, (tweet, prediction) in enumerate(error_reports.items(), 1): + body += f"{i}. Tweet: \"{tweet[:100]}{'...' if len(tweet) > 100 else ''}\"\n" + body += f" Prediction reported as incorrect: {prediction.upper()}\n\n" + + body += """ +📈 Recommended actions: +- Analyze reported tweets to identify patterns +- Consider re-training the model if necessary +- Check quality of training data + +📊 COMPLETE DATA (JSON) : +═══════════════════════════════════════════════════════════════════════════════ + +""" + + # ✅ Directly add JSON inside the body + json_string = json.dumps(json_data, ensure_ascii=False, indent=2) + body += f"```json\n{json_string}\n```" + + body += """ + +═══════════════════════════════════════════════════════════════════════════════ + +--- +Sincerely, +🤖 API Air Paradis - Automatic monitoring system +📧 Sent from : """ + smtp_from + + # Add message body + msg.attach(MIMEText(body, 'plain', 'utf-8')) + + # Sending email + with smtplib.SMTP(smtp_server, smtp_port) as server: + server.starttls() + server.login(smtp_email, smtp_password) + server.send_message(msg) + + print(f"✅ Report email successfully sent from {smtp_from}") + print("📊 JSON embedded in message body") + return True + + except Exception as e: + print(f"❌ Error during email sending : {e}") return False \ No newline at end of file diff --git a/deployment/api/main.py b/deployment/api/main.py index 6d190e3..767a736 100644 --- a/deployment/api/main.py +++ b/deployment/api/main.py @@ -1,210 +1,210 @@ -import os -import sys -import pickle -from dotenv import load_dotenv -from fastapi import FastAPI, HTTPException -from pydantic import BaseModel -import mlflow -from mlflow.tracking import MlflowClient -from tensorflow.keras.models import load_model -from tensorflow.keras.preprocessing.sequence import pad_sequences -import pandas as pd -from datetime import datetime, timedelta - -# # TO UNCOMMENT FOR DEVELOPMENT. OTHERWISE, LEAVE AS IS -# # Access the .env file -# PARENT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")) -# sys.path.append(PARENT_DIR) -# # 🔐 Load environment variables (.env two levels above) -# load_dotenv() - -# 🔁 Import the preprocessing function (LOCAL) -from preprocessing import preprocess_tweet - -# 📧 Import the email service -from email_service import send_error_report_email - -# 🔗 Configure MLflow -mlflow_tracking_uri = os.getenv("MLFLOW_TRACKING_URI") -if mlflow_tracking_uri: - mlflow.set_tracking_uri(mlflow_tracking_uri) -else: - print("⚠️ MLFLOW_TRACKING_URI not defined in .env") - -# 📌 Model name and stage -MODEL_NAME = "SentimentAnalysisLSTM" -STAGE = "Production" - -# 🧾 Structure of POST requests -class TweetRequest(BaseModel): - tweet: str - -class ReportRequest(BaseModel): - tweet: str - prediction: str - probability: float - -# 🧠 Storage for error reports -error_reports = {} - -# 🔍 Function to load model + tokenizer -def load_model_and_artifacts(model_name=MODEL_NAME, stage=STAGE): - client = MlflowClient() - versions = client.get_latest_versions(model_name, [stage]) - - if not versions: - raise RuntimeError(f"No version found for model '{model_name}' in stage '{stage}'") - - version_info = versions[0] - run_id = version_info.run_id - - if not run_id: - raise RuntimeError("Run ID not found.") - - # Extract embedding tag - model_version_info = client.get_model_version(model_name, version_info.version) - # glove is used as default if embedding_type is not found - embedding_type = model_version_info.tags.get("embedding_type", "glove").lower() - - # Determine paths to artifacts - local_dir = "./downloaded_artifacts" - os.makedirs(local_dir, exist_ok=True) - base_path = "local_artifacts" - tokenizer_file = "tokenizer.pickle" - - if "glove" in embedding_type: - keras_file = "final_model_LSTM_GloVe-300d-Fige.keras" - elif "word2vec" in embedding_type: - keras_file = "final_model_LSTM_Word2Vec-Fige.keras" - else: - raise ValueError(f"Unknown embedding type: {embedding_type}") - - print(f"{embedding_type} model found. Downloading model associated with run_id: {run_id}") - - def dl(path): - return client.download_artifacts(run_id=run_id, path=f"{base_path}/{path}", dst_path=local_dir) - - # 🔽 Download artifacts - model_path = dl(keras_file) - tokenizer_path = dl(tokenizer_file) - print("Contents of downloaded_artifacts:", os.listdir(local_dir)) - - # 📦 Load - model = load_model(model_path) - with open(tokenizer_path, "rb") as f: - tokenizer = pickle.load(f) - - return model, tokenizer, embedding_type - -# 🚀 Launch the API -app = FastAPI(title="Sentiment Prediction API - Air Paradis") -# app = FastAPI(title="Sentiment Prediction API - Air Paradis", -# root_path="/api") - -try: - model, tokenizer, embedding_type = load_model_and_artifacts() -except Exception as e: - print(f"\n❌ Error loading artifacts: {e}") - model, tokenizer = None, None - -@app.get("/") -def root(): - return {"message": "Sentiment API - Air Paradis - online"} - -@app.post("/predict") -def predict_sentiment(request: TweetRequest): - if model is None or tokenizer is None: - raise HTTPException(status_code=503, detail="Model or artifacts unavailable.") - - try: - processed_tweet = preprocess_tweet(request.tweet) - except Exception as e: - raise HTTPException(status_code=500, detail=f"Preprocessing error: {e}") - - if not processed_tweet.strip(): - raise HTTPException(status_code=400, detail="Tweet is empty or invalid after preprocessing.") - - try: - sequence = tokenizer.texts_to_sequences([processed_tweet]) - sequence_padded = pad_sequences(sequence, maxlen=model.input_shape[1]) - prediction_prob = model.predict(sequence_padded)[0][0] - if prediction_prob >= 0.5: - sentiment = "positive" - else: - sentiment = "negative" - # prediction_prob = 1 - prediction_prob - except Exception as e: - raise HTTPException(status_code=500, detail=f"Inference error: {e}") - - return { - "sentiment": sentiment, - "probability": float(prediction_prob) - } - -# Replace the dict with a global empty DataFrame initially -error_reports_df = pd.DataFrame(columns=["tweet", "prediction", "probability", "timestamp"]) - -@app.post("/report_error") -def report_error(request: ReportRequest): - global error_reports_df - - tweet = request.tweet.strip() - prediction = request.prediction.strip().lower() - probability = request.probability - - if not tweet or not prediction: - raise HTTPException(status_code=400, detail="Missing tweet or prediction.") - if probability < 0 or probability > 1: - raise HTTPException(status_code=400, detail="Invalid probability. Must be between 0 and 1.") - - now = datetime.utcnow() - - # Check if tweet already exists to avoid duplicates - existing_idx = error_reports_df.index[error_reports_df["tweet"] == tweet].tolist() - if existing_idx: - # Update existing row - idx = existing_idx[0] - error_reports_df.at[idx, "prediction"] = prediction + f' (p = {probability:.2f} ) ' - error_reports_df.at[idx, "probability"] = probability - error_reports_df.at[idx, "timestamp"] = now - else: - # Add new row - new_row = { - "tweet": tweet, - "prediction": prediction + f' (p = {probability:.2f} ) ', - "probability": probability, - "timestamp": now - } - error_reports_df = pd.concat([error_reports_df, pd.DataFrame([new_row])], ignore_index=True) - - report_sent = False - - # Condition: every 3 reports and max 5 min interval between the last 3 - try: - if len(error_reports_df) % 3 == 0: - last_three = error_reports_df.sort_values("timestamp", ascending=False).head(3) - times = last_three["timestamp"].sort_values() - delta = times.iloc[-1] - times.iloc[0] - if delta <= timedelta(minutes=5): - reports_dict = dict(zip( - last_three["tweet"], - last_three["prediction"] - )) - print(f"📧 Attempting to send email for {len(error_reports_df)} reports...") - email_success = send_error_report_email(reports_dict) - if email_success: - print(f"✅ Email successfully sent for {len(error_reports_df)} reports") - report_sent = True - # DELETE the last 3 reports sent - # Identify indexes to delete - to_remove_idx = last_three.index - error_reports_df = error_reports_df.drop(to_remove_idx).reset_index(drop=True) - else: - print(f"❌ Failed to send email for {len(error_reports_df)} reports") - - except Exception as e: - print(f"❌ Error while sending email: {e}") - # Continue even if it fails - - return {"report_sent": report_sent} +import os +import sys +import pickle +from dotenv import load_dotenv +from fastapi import FastAPI, HTTPException +from pydantic import BaseModel +import mlflow +from mlflow.tracking import MlflowClient +from tensorflow.keras.models import load_model +from tensorflow.keras.preprocessing.sequence import pad_sequences +import pandas as pd +from datetime import datetime, timedelta + +# # TO UNCOMMENT FOR DEVELOPMENT. OTHERWISE, LEAVE AS IS +# # Access the .env file +# PARENT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")) +# sys.path.append(PARENT_DIR) +# # 🔐 Load environment variables (.env two levels above) +# load_dotenv() + +# 🔁 Import the preprocessing function (LOCAL) +from preprocessing import preprocess_tweet + +# 📧 Import the email service +from email_service import send_error_report_email + +# 🔗 Configure MLflow +mlflow_tracking_uri = os.getenv("MLFLOW_TRACKING_URI") +if mlflow_tracking_uri: + mlflow.set_tracking_uri(mlflow_tracking_uri) +else: + print("⚠️ MLFLOW_TRACKING_URI not defined in .env") + +# 📌 Model name and stage +MODEL_NAME = "SentimentAnalysisLSTM" +STAGE = "Production" + +# 🧾 Structure of POST requests +class TweetRequest(BaseModel): + tweet: str + +class ReportRequest(BaseModel): + tweet: str + prediction: str + probability: float + +# 🧠 Storage for error reports +error_reports = {} + +# 🔍 Function to load model + tokenizer +def load_model_and_artifacts(model_name=MODEL_NAME, stage=STAGE): + client = MlflowClient() + versions = client.get_latest_versions(model_name, [stage]) + + if not versions: + raise RuntimeError(f"No version found for model '{model_name}' in stage '{stage}'") + + version_info = versions[0] + run_id = version_info.run_id + + if not run_id: + raise RuntimeError("Run ID not found.") + + # Extract embedding tag + model_version_info = client.get_model_version(model_name, version_info.version) + # glove is used as default if embedding_type is not found + embedding_type = model_version_info.tags.get("embedding_type", "glove").lower() + + # Determine paths to artifacts + local_dir = "./downloaded_artifacts" + os.makedirs(local_dir, exist_ok=True) + base_path = "local_artifacts" + tokenizer_file = "tokenizer.pickle" + + if "glove" in embedding_type: + keras_file = "final_model_LSTM_GloVe-300d-Fige.keras" + elif "word2vec" in embedding_type: + keras_file = "final_model_LSTM_Word2Vec-Fige.keras" + else: + raise ValueError(f"Unknown embedding type: {embedding_type}") + + print(f"{embedding_type} model found. Downloading model associated with run_id: {run_id}") + + def dl(path): + return client.download_artifacts(run_id=run_id, path=f"{base_path}/{path}", dst_path=local_dir) + + # 🔽 Download artifacts + model_path = dl(keras_file) + tokenizer_path = dl(tokenizer_file) + print("Contents of downloaded_artifacts:", os.listdir(local_dir)) + + # 📦 Load + model = load_model(model_path) + with open(tokenizer_path, "rb") as f: + tokenizer = pickle.load(f) + + return model, tokenizer, embedding_type + +# 🚀 Launch the API +app = FastAPI(title="Sentiment Prediction API - Air Paradis") +# app = FastAPI(title="Sentiment Prediction API - Air Paradis", +# root_path="/api") + +try: + model, tokenizer, embedding_type = load_model_and_artifacts() +except Exception as e: + print(f"\n❌ Error loading artifacts: {e}") + model, tokenizer = None, None + +@app.get("/") +def root(): + return {"message": "Sentiment API - Air Paradis - online"} + +@app.post("/predict") +def predict_sentiment(request: TweetRequest): + if model is None or tokenizer is None: + raise HTTPException(status_code=503, detail="Model or artifacts unavailable.") + + try: + processed_tweet = preprocess_tweet(request.tweet) + except Exception as e: + raise HTTPException(status_code=500, detail=f"Preprocessing error: {e}") + + if not processed_tweet.strip(): + raise HTTPException(status_code=400, detail="Tweet is empty or invalid after preprocessing.") + + try: + sequence = tokenizer.texts_to_sequences([processed_tweet]) + sequence_padded = pad_sequences(sequence, maxlen=model.input_shape[1]) + prediction_prob = model.predict(sequence_padded)[0][0] + if prediction_prob >= 0.5: + sentiment = "positive" + else: + sentiment = "negative" + # prediction_prob = 1 - prediction_prob + except Exception as e: + raise HTTPException(status_code=500, detail=f"Inference error: {e}") + + return { + "sentiment": sentiment, + "probability": float(prediction_prob) + } + +# Replace the dict with a global empty DataFrame initially +error_reports_df = pd.DataFrame(columns=["tweet", "prediction", "probability", "timestamp"]) + +@app.post("/report_error") +def report_error(request: ReportRequest): + global error_reports_df + + tweet = request.tweet.strip() + prediction = request.prediction.strip().lower() + probability = request.probability + + if not tweet or not prediction: + raise HTTPException(status_code=400, detail="Missing tweet or prediction.") + if probability < 0 or probability > 1: + raise HTTPException(status_code=400, detail="Invalid probability. Must be between 0 and 1.") + + now = datetime.utcnow() + + # Check if tweet already exists to avoid duplicates + existing_idx = error_reports_df.index[error_reports_df["tweet"] == tweet].tolist() + if existing_idx: + # Update existing row + idx = existing_idx[0] + error_reports_df.at[idx, "prediction"] = prediction + f' (p = {probability:.2f} ) ' + error_reports_df.at[idx, "probability"] = probability + error_reports_df.at[idx, "timestamp"] = now + else: + # Add new row + new_row = { + "tweet": tweet, + "prediction": prediction + f' (p = {probability:.2f} ) ', + "probability": probability, + "timestamp": now + } + error_reports_df = pd.concat([error_reports_df, pd.DataFrame([new_row])], ignore_index=True) + + report_sent = False + + # Condition: every 3 reports and max 5 min interval between the last 3 + try: + if len(error_reports_df) % 3 == 0: + last_three = error_reports_df.sort_values("timestamp", ascending=False).head(3) + times = last_three["timestamp"].sort_values() + delta = times.iloc[-1] - times.iloc[0] + if delta <= timedelta(minutes=5): + reports_dict = dict(zip( + last_three["tweet"], + last_three["prediction"] + )) + print(f"📧 Attempting to send email for {len(error_reports_df)} reports...") + email_success = send_error_report_email(reports_dict) + if email_success: + print(f"✅ Email successfully sent for {len(error_reports_df)} reports") + report_sent = True + # DELETE the last 3 reports sent + # Identify indexes to delete + to_remove_idx = last_three.index + error_reports_df = error_reports_df.drop(to_remove_idx).reset_index(drop=True) + else: + print(f"❌ Failed to send email for {len(error_reports_df)} reports") + + except Exception as e: + print(f"❌ Error while sending email: {e}") + # Continue even if it fails + + return {"report_sent": report_sent} \ No newline at end of file diff --git a/deployment/api/nginx.conf b/deployment/api/nginx.conf index 9069ab0..6d95b0e 100644 --- a/deployment/api/nginx.conf +++ b/deployment/api/nginx.conf @@ -1,38 +1,38 @@ -# This reverse proxy configuration aims to make calls to different services running under different ports (Streamlit :8501 and FastAPI :8000) by calling only one port : the nginx one (:8080) -# The 8080 has to be opened (Dockerfile) and used by app.py (streamlit) in order to make right calls. -worker_processes 1; - -events { worker_connections 1024; } - -http { - server { - listen 8080; - - location /api/ { - proxy_pass http://127.0.0.1:8000/; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location / { - proxy_pass http://127.0.0.1:8501/; - - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location /health { - return 200 'OK'; - add_header Content-Type text/plain; - } - - } -} +# This reverse proxy configuration aims to make calls to different services running under different ports (Streamlit :8501 and FastAPI :8000) by calling only one port : the nginx one (:8080) +# The 8080 has to be opened (Dockerfile) and used by app.py (streamlit) in order to make right calls. +worker_processes 1; + +events { worker_connections 1024; } + +http { + server { + listen 8080; + + location /api/ { + proxy_pass http://127.0.0.1:8000/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location / { + proxy_pass http://127.0.0.1:8501/; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /health { + return 200 'OK'; + add_header Content-Type text/plain; + } + + } +} diff --git a/deployment/api/preprocessing.py b/deployment/api/preprocessing.py index 39031e6..40e6e3a 100644 --- a/deployment/api/preprocessing.py +++ b/deployment/api/preprocessing.py @@ -1,80 +1,80 @@ -import re -import html -import nltk -from nltk.corpus import stopwords -from nltk.tokenize import word_tokenize -from nltk.stem import WordNetLemmatizer - -# Ensure that the necessary resources are available -try: - stopwords.words('english') - word_tokenize("test") - WordNetLemmatizer().lemmatize("testing") -except LookupError: - nltk.download('punkt') - nltk.download('punkt_tab') - nltk.download('stopwords') - nltk.download('wordnet') - nltk.download('omw-1.4') - -# PREPROCESSING FUNCTION FOR CLASSIC AND ADVANCED MODELS -def preprocess_tweet(tweet): - """ - Preprocesses a tweet for classic ML models or word embedding models (e.g., Word2Vec, GloVe). - The text is cleaned, normalized, lemmatized, and stripped of unnecessary noise. - """ - - # Ensure input is a string - if not isinstance(tweet, str): - return "" - - # Decode HTML entities (e.g., & → &) - tweet = html.unescape(tweet) - # print(tweet + '\n') - - # Convert text to lowercase - tweet = tweet.lower() - # print(tweet + '\n') - - # Replace URLs with a generic token - tweet = re.sub(r'https?://\S+|www\.\S+', '', tweet) - # print(tweet + '\n') - - # Replace mentions (@username) with a generic token - tweet = re.sub(r'@\w+', '', tweet) - # print(tweet + '\n') - - # Separate hashtags from the word to preserve semantics during tokenization - tweet = re.sub(r'#(\w+)', r'# \1', tweet) - # print(tweet + '\n') - - # Remove unwanted punctuation and special characters (except useful ones like < > @ # ! ?) - tweet = re.sub(r'[^\w\s<>@#!?]', '', tweet) - # print(tweet + '\n') - - # Remove non-printable/control characters (e.g., \n, \t, emojis, corrupted unicode) - tweet = re.sub(r'[^\x20-\x7E]', '', tweet) - # print(tweet + '\n') - - # Tokenize the tweet into individual words - tokens = word_tokenize(tweet) - # print(tokens) - # print('\n') - - # Lemmatize each token to its base form (e.g., "running" → "run") - lemmatizer = WordNetLemmatizer() - tokens = [lemmatizer.lemmatize(token) for token in tokens] - # print(tokens) - # print('\n') - - # Remove stopwords, but keep negation-related words (important for sentiment) - stop_words = set(stopwords.words('english')) - important_words = {'no', 'not', 'nor', 'neither', 'never', 'nobody', 'none', 'nothing', 'nowhere'} - stop_words = stop_words - important_words - tokens = [token for token in tokens if token not in stop_words] - # print(tokens) - # print('\n') - - - # Reconstruct the cleaned tweet +import re +import html +import nltk +from nltk.corpus import stopwords +from nltk.tokenize import word_tokenize +from nltk.stem import WordNetLemmatizer + +# Ensure that the necessary resources are available +try: + stopwords.words('english') + word_tokenize("test") + WordNetLemmatizer().lemmatize("testing") +except LookupError: + nltk.download('punkt') + nltk.download('punkt_tab') + nltk.download('stopwords') + nltk.download('wordnet') + nltk.download('omw-1.4') + +# PREPROCESSING FUNCTION FOR CLASSIC AND ADVANCED MODELS +def preprocess_tweet(tweet): + """ + Preprocesses a tweet for classic ML models or word embedding models (e.g., Word2Vec, GloVe). + The text is cleaned, normalized, lemmatized, and stripped of unnecessary noise. + """ + + # Ensure input is a string + if not isinstance(tweet, str): + return "" + + # Decode HTML entities (e.g., & → &) + tweet = html.unescape(tweet) + # print(tweet + '\n') + + # Convert text to lowercase + tweet = tweet.lower() + # print(tweet + '\n') + + # Replace URLs with a generic token + tweet = re.sub(r'https?://\S+|www\.\S+', '', tweet) + # print(tweet + '\n') + + # Replace mentions (@username) with a generic token + tweet = re.sub(r'@\w+', '', tweet) + # print(tweet + '\n') + + # Separate hashtags from the word to preserve semantics during tokenization + tweet = re.sub(r'#(\w+)', r'# \1', tweet) + # print(tweet + '\n') + + # Remove unwanted punctuation and special characters (except useful ones like < > @ # ! ?) + tweet = re.sub(r'[^\w\s<>@#!?]', '', tweet) + # print(tweet + '\n') + + # Remove non-printable/control characters (e.g., \n, \t, emojis, corrupted unicode) + tweet = re.sub(r'[^\x20-\x7E]', '', tweet) + # print(tweet + '\n') + + # Tokenize the tweet into individual words + tokens = word_tokenize(tweet) + # print(tokens) + # print('\n') + + # Lemmatize each token to its base form (e.g., "running" → "run") + lemmatizer = WordNetLemmatizer() + tokens = [lemmatizer.lemmatize(token) for token in tokens] + # print(tokens) + # print('\n') + + # Remove stopwords, but keep negation-related words (important for sentiment) + stop_words = set(stopwords.words('english')) + important_words = {'no', 'not', 'nor', 'neither', 'never', 'nobody', 'none', 'nothing', 'nowhere'} + stop_words = stop_words - important_words + tokens = [token for token in tokens if token not in stop_words] + # print(tokens) + # print('\n') + + + # Reconstruct the cleaned tweet return ' '.join(tokens) \ No newline at end of file diff --git a/deployment/api/requirements.txt b/deployment/api/requirements.txt index fba6645..14c5515 100644 --- a/deployment/api/requirements.txt +++ b/deployment/api/requirements.txt @@ -1,21 +1,21 @@ -# === API FastAPI === -fastapi==0.115.14 -uvicorn==0.35.0 -pydantic==2.11.7 -starlette==0.46.2 -# === Interface Streamlit === -streamlit==1.46.1 -requests==2.32.4 -# === Machine Learning === -tensorflow==2.19.0 -mlflow==3.1.1 -boto3==1.39.2 -numpy==2.1.3 -pandas==2.3.0 -# === Traitement de texte === -nltk==3.9.1 -# === Utilitaires === -python-dotenv==1.1.1 -httpx==0.28.1 -# === Dépendances système === -protobuf==3.20.3 +# === API FastAPI === +fastapi==0.115.14 +uvicorn==0.35.0 +pydantic==2.11.7 +starlette==0.46.2 +# === Interface Streamlit === +streamlit==1.46.1 +requests==2.32.4 +# === Machine Learning === +tensorflow==2.19.0 +mlflow==3.1.1 +boto3==1.39.2 +numpy==2.1.3 +pandas==2.3.0 +# === Traitement de texte === +nltk==3.9.1 +# === Utilitaires === +python-dotenv==1.1.1 +httpx==0.28.1 +# === Dépendances système === +protobuf==3.20.3 diff --git a/deployment/api/start.sh b/deployment/api/start.sh index 5231fdd..9bca865 100644 --- a/deployment/api/start.sh +++ b/deployment/api/start.sh @@ -1,35 +1,35 @@ -#!/bin/bash - -set -e - -# S'assurer que les répertoires de cache et de log Nginx sont accessibles en écriture -mkdir -p /var/cache/nginx /var/run/nginx /var/log/nginx -chown -R www-data:www-data /var/cache/nginx /var/run/nginx /var/log/nginx - -echo "Starting Streamlit..." -streamlit run app.py \ - --server.port 8501 \ - --server.address 0.0.0.0 \ - --server.enableXsrfProtection false \ - --server.enableCORS false \ - & # Lance en arrière-plan - -echo "Starting FastAPI..." -uvicorn main:app --host 0.0.0.0 --port 8000 --loop asyncio & # Lance en arrière-plan - -# MODIFICATION IMPORTANTE ICI: Vérifier Streamlit sur le bon chemin -echo "Waiting for Streamlit to be ready..." -until curl -s http://127.0.0.1:8501 > /dev/null; do - sleep 1 -done -echo "Streamlit is ready!" - -# MODIFICATION: Vérifier FastAPI sur un endpoint existant, par exemple /docs ou /health -echo "Waiting for FastAPI to be ready..." -until curl -s http://127.0.0.1:8000/docs > /dev/null; do # Assurez-vous que /docs est un endpoint léger - sleep 1 -done -echo "FastAPI is ready!" - -echo "Launching NGINX..." -nginx -c /etc/nginx/nginx.conf -g 'daemon off;' +#!/bin/bash + +set -e + +# S'assurer que les répertoires de cache et de log Nginx sont accessibles en écriture +mkdir -p /var/cache/nginx /var/run/nginx /var/log/nginx +chown -R www-data:www-data /var/cache/nginx /var/run/nginx /var/log/nginx + +echo "Starting Streamlit..." +streamlit run app.py \ + --server.port 8501 \ + --server.address 0.0.0.0 \ + --server.enableXsrfProtection false \ + --server.enableCORS false \ + & # Lance en arrière-plan + +echo "Starting FastAPI..." +uvicorn main:app --host 0.0.0.0 --port 8000 --loop asyncio & # Lance en arrière-plan + +# MODIFICATION IMPORTANTE ICI: Vérifier Streamlit sur le bon chemin +echo "Waiting for Streamlit to be ready..." +until curl -s http://127.0.0.1:8501 > /dev/null; do + sleep 1 +done +echo "Streamlit is ready!" + +# MODIFICATION: Vérifier FastAPI sur un endpoint existant, par exemple /docs ou /health +echo "Waiting for FastAPI to be ready..." +until curl -s http://127.0.0.1:8000/docs > /dev/null; do # Assurez-vous que /docs est un endpoint léger + sleep 1 +done +echo "FastAPI is ready!" + +echo "Launching NGINX..." +nginx -c /etc/nginx/nginx.conf -g 'daemon off;' diff --git a/deployment/api/tests/test_email_service.py b/deployment/api/tests/test_email_service.py index 226a52c..38c4dfc 100644 --- a/deployment/api/tests/test_email_service.py +++ b/deployment/api/tests/test_email_service.py @@ -1,38 +1,38 @@ -# Utilisation de mock pour simuler l'envoi de mails mais sans spammer une boîte. - -from unittest.mock import patch, MagicMock -import os -import sys -from dotenv import load_dotenv - -# Ajouter le répertoire parent au path pour importer email_service -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../"))) - -from email_service import send_error_report_email - -# Charger les variables d'environnement -dotenv_path = '../../../.env' -load_dotenv(dotenv_path) - -@patch("smtplib.SMTP") -def test_email_service_connection(mock_smtp): - """ - Test SMTP connection without actually sending the email - """ - # 🔧 Configuration du mock - mock_server = MagicMock() - mock_smtp.return_value.__enter__.return_value = mock_server - - test_reports = { - "Flight cancelled without notice": "positif" - } - - # Appel de la fonction - result = send_error_report_email(test_reports) - - # ✅ Assertions - assert result is True - mock_smtp.assert_called_with(os.getenv("SMTP_SERVER"), int(os.getenv("SMTP_PORT", 587))) - mock_server.starttls.assert_called_once() - mock_server.login.assert_called_once_with(os.getenv("SMTP_EMAIL"), os.getenv("SMTP_PASSWORD")) - mock_server.send_message.assert_called_once() +# Utilisation de mock pour simuler l'envoi de mails mais sans spammer une boîte. + +from unittest.mock import patch, MagicMock +import os +import sys +from dotenv import load_dotenv + +# Ajouter le répertoire parent au path pour importer email_service +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../"))) + +from email_service import send_error_report_email + +# Charger les variables d'environnement +dotenv_path = '../../../.env' +load_dotenv(dotenv_path) + +@patch("smtplib.SMTP") +def test_email_service_connection(mock_smtp): + """ + Test SMTP connection without actually sending the email + """ + # 🔧 Configuration du mock + mock_server = MagicMock() + mock_smtp.return_value.__enter__.return_value = mock_server + + test_reports = { + "Flight cancelled without notice": "positif" + } + + # Appel de la fonction + result = send_error_report_email(test_reports) + + # ✅ Assertions + assert result is True + mock_smtp.assert_called_with(os.getenv("SMTP_SERVER"), int(os.getenv("SMTP_PORT", 587))) + mock_server.starttls.assert_called_once() + mock_server.login.assert_called_once_with(os.getenv("SMTP_EMAIL"), os.getenv("SMTP_PASSWORD")) + mock_server.send_message.assert_called_once() diff --git a/deployment/api/tests/test_main.py b/deployment/api/tests/test_main.py index f5a4882..ba4afe5 100644 --- a/deployment/api/tests/test_main.py +++ b/deployment/api/tests/test_main.py @@ -1,32 +1,32 @@ -import os -import sys -from fastapi.testclient import TestClient - -# Add parent directory to path to import email_service -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../"))) - -from main import app - -client = TestClient(app) - -def test_root_endpoint(): - response = client.get("/") - assert response.status_code == 200 - assert response.json() == {"message": "Sentiment API - Air Paradis - online"} - -def test_predict_sentiment(): - response = client.post("/predict", json={"tweet": "I love this airline !"}) - assert response.status_code == 200 - data = response.json() - assert "sentiment" in data - assert "probability" in data - assert data["sentiment"] in ["positive", "negative"] - -def test_missing_tweet(): - response = client.post("/predict", json={}) - assert response.status_code == 422 # Unprocessable Entity (FastAPI) - -def test_empty_tweet(): - response = client.post("/predict", json={"tweet": ""}) - assert response.status_code == 400 +import os +import sys +from fastapi.testclient import TestClient + +# Add parent directory to path to import email_service +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../"))) + +from main import app + +client = TestClient(app) + +def test_root_endpoint(): + response = client.get("/") + assert response.status_code == 200 + assert response.json() == {"message": "Sentiment API - Air Paradis - online"} + +def test_predict_sentiment(): + response = client.post("/predict", json={"tweet": "I love this airline !"}) + assert response.status_code == 200 + data = response.json() + assert "sentiment" in data + assert "probability" in data + assert data["sentiment"] in ["positive", "negative"] + +def test_missing_tweet(): + response = client.post("/predict", json={}) + assert response.status_code == 422 # Unprocessable Entity (FastAPI) + +def test_empty_tweet(): + response = client.post("/predict", json={"tweet": ""}) + assert response.status_code == 400 assert response.json()["detail"] == "Tweet is empty or invalid after preprocessing." \ No newline at end of file diff --git a/deployment/api/tests/test_preprocessing.py b/deployment/api/tests/test_preprocessing.py index 4838ccc..d9bc3c9 100644 --- a/deployment/api/tests/test_preprocessing.py +++ b/deployment/api/tests/test_preprocessing.py @@ -1,28 +1,28 @@ -import os -import sys - -# Ajouter le répertoire parent au path pour importer email_service -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../"))) -from preprocessing import preprocess_tweet - - -def test_preprocess_tweet_removes_hashtags(): - tweet = """ - Just flew w/ @AirParadis 🚀 & I’m in LOVE! Epic service, comfy seats, good prices at https://airparadis.com - #bestflight #AirParadis - """ - assert preprocess_tweet(tweet) == 'flew w < MENTION > im love ! epic service comfy seat good price < URL > # bestflight # airparadis' - -def test_preprocess_tweet_removes_mentions(): - tweet = """ - "Flight of dreams" w/ @AirParadis! Got food, space & smiles 😄 I’ll book again! - #AirParadis - """ - assert preprocess_tweet(tweet) == 'flight dream w < MENTION > ! got food space smile ill book ! # airparadis' - -def test_preprocess_tweet_lowercases_text(): - tweet = """ - My 3rd time w/ @AirParadis & still amazed 😍 Crew = lovely à every step! - #flyhappy #AirParadis - """ - assert preprocess_tweet(tweet) == '3rd time w < MENTION > still amazed crew lovely every step ! # flyhappy # airparadis' +import os +import sys + +# Ajouter le répertoire parent au path pour importer email_service +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../"))) +from preprocessing import preprocess_tweet + + +def test_preprocess_tweet_removes_hashtags(): + tweet = """ + Just flew w/ @AirParadis 🚀 & I’m in LOVE! Epic service, comfy seats, good prices at https://airparadis.com + #bestflight #AirParadis + """ + assert preprocess_tweet(tweet) == 'flew w < MENTION > im love ! epic service comfy seat good price < URL > # bestflight # airparadis' + +def test_preprocess_tweet_removes_mentions(): + tweet = """ + "Flight of dreams" w/ @AirParadis! Got food, space & smiles 😄 I’ll book again! + #AirParadis + """ + assert preprocess_tweet(tweet) == 'flight dream w < MENTION > ! got food space smile ill book ! # airparadis' + +def test_preprocess_tweet_lowercases_text(): + tweet = """ + My 3rd time w/ @AirParadis & still amazed 😍 Crew = lovely à every step! + #flyhappy #AirParadis + """ + assert preprocess_tweet(tweet) == '3rd time w < MENTION > still amazed crew lovely every step ! # flyhappy # airparadis' diff --git a/docs/blog.fr.md b/docs/blog.fr.md index 3c167a7..8b3fb20 100644 --- a/docs/blog.fr.md +++ b/docs/blog.fr.md @@ -1,235 +1,235 @@ -# ✨ Analyse de sentiments et déploiement MLOps : retour d’expérience complet - -Dans le cadre du projet *Air Paradis*, nous avons mis en œuvre une chaîne complète d’analyse de sentiments à partir de tweets, depuis la modélisation jusqu’au déploiement MLOps. Ce billet de blog revient sur les étapes clés du projet : choix méthodologiques, comparaison des modèles, mise en production d’une API robuste, et déploiement sur deux environnements (local + cloud). 🧠🔧 - ---- - -## 🔍 Trois approches de modélisation supervisée - -Nous avons testé trois approches complémentaires pour prédire le sentiment d’un tweet. Chacune repose sur une représentation textuelle différente et un algorithme d’apprentissage adapté : - -### 1. 🧱 Approche simple : Bag-of-Words (BoW) + modèles de machine learning - -On vectorise les tweets via BoW ou BoW + TF-IDF, qui comptent la fréquence des mots. -Contrairement à d'autres projets NLP, nous avons évité LDA (non supervisé, orienté topic modeling). - -➡️ **Modèles testés** : -- Régression Logistique -- Naive Bayes -- SVM -- Random Forest - -Ces modèles sont entraînés avec **GridSearchCV**, en optimisant la **précision (`precision`)**, notre KPI métier. Différents KPI ont été calculés sur l'ensemble de test. - -![Scores modèles simples](./Basic-Models.png) - ---- - -### 2. 🔥 Approche avancée : Word Embeddings + LSTM - -Nous utilisons des vecteurs denses Word2Vec (entraînés) et GloVe 300d (préentraînés par Stanford), qui capturent les similarités sémantiques entre mots. Ces représentations ne prennent toutefois **pas en compte le contexte** exact (ex : "annulé" est probablement un mot très négatif pour un vol mais positif pour un procès contenant des charges contre vous). - -La classification est réalisée par un **LSTM**, capable d'exploiter la séquentialité des mots. -Le **modèle déployé est automatiquement sélectionné** entre Word2Vec + LSTM et GloVe + LSTM, selon la **précision maximale enregistrée dans MLFlow**. - -Modèle `Word2Vec + LSTM` (`test_precision : 0.797 , val_precision : 0.795`): -![Scores entrainement modèle avancé LSTM + Word2Vec](./Word2Vec_LSTM_epoch9.png) -Modèle `GloVe 300d + LSTM` (`test_precision : 0.792 , val_precision : 0.798`): -![Scores entrainement modèles avancés LSTM + GloVe 300d](./GloVe-300d_LSTM_epoch12.png) - -Bien que GloVe 300d + LSTM présente un score de précision légèrement supérieur à Word2Vec + LSTM sur l'ensemble de validation, il reste inférieur pour l'ensemble de test (relativement négligeable dans l'absolu). - ---- - -### 3. 🤖 Approche transformer : DistilBERT, embeddings contextuels allégés - -Nous avons utilisé **DistilBERT**, une version plus légère et plus rapide de BERT (créée par Hugging Face). -Il conserve **95 % de la performance de BERT** tout en étant **40 % plus petit** et **60 % plus rapide**, ce qui le rend particulièrement adapté à un **entraînement sur GPU limité** (Google Colab) et à une **inférence plus rapide**. - -Comme BERT, DistilBERT produit des **embeddings contextuels**, c’est-à-dire que le sens des mots varie selon leur contexte. Par exemple, *"banque"* dans *"je vais à la banque"* et *"banque de données"* n’aura pas le même vecteur. - -Il a été **fine-tuné** ici pour une classification binaire (positif/négatif), avec un tokenizer et un encodage spécifiques à son architecture. - -> **Modèle `DistilBERT`** (`test_precision : 0.861 , val_precision : 0.858`) - -![Courbes d'entraînement DistilBERT](./bert_training_curves_epoch1.png) -![Matrice de Confusion](./bert_confusion_matrix.png) -![ROC Curve](./bert_roc_curve.png) - -⚠️ **Limites** : comme tous les modèles préentraînés, DistilBERT **n’est pas sensible à l’ironie, aux jeux de mots, ni aux références culturelles implicites**. -Or, Twitter est un terrain de jeu idéal pour ce genre de contenus détournés. Ces limites s’appliquent aussi à Word2Vec, GloVe et aux modèles classiques. - ---- - -## 🎯 Pourquoi optimiser la précision (et non l'accuracy) ? - -**Accuracy** = proportion de bonnes prédictions globales. -**Precision** = proportion de tweets *prévus positifs* qui sont *vraiment positifs*. - -> Exemple : sur 100 tweets, si le modèle prédit 30 positifs, mais 15 sont des erreurs, la précision est de 50 %. - -Nous avons préféré **minimiser les faux positifs**, car dans un contexte sensible, **annoncer un tweet positif alors qu’il est négatif peut être problématique**. - -- Modèles classiques : **GridSearchCV optimisé sur la précision** -- Modèles DL : **EarlyStopping sur val_loss**, mais **sélection finale sur la précision** - ---- - -## ⚙️ Environnement MLOps souverain - -### 🖥️ Serveur personnel NAS + OpenMediaVault - -MLFlow est installé sur un **NAS personnel (Intel N100)**, sous **OpenMediaVault (Debian)**. -Nous utilisons **MinIO** pour stocker artefacts et modèles, garantissant une gestion locale, souveraine et compatible S3. - -> Les notebooks, exécutés sur Google Colab pour bénéficier de GPU, loggent dans MLFlow et envoient les modèles vers MinIO via des **variables d’environnement**. - -Dashboard Expériences MLFlow : -![Dashboard MLFLow](./mlflow_experiments_dashboard.png) - -Modèles en production (registre de modèles): -![Models Registry](mlflow_models_registry_2.png) - -Meilleur modèle non BERT retenu : -![Modèle retenu](mlflow_models_registry_best_LSTM_model.png) -![Meilleur modèle non BERT](mlflolw_experiments_dashboard_best_LSTM_model.png) ---- - -## 🧪 CI, tests et versioning - -### ✅ Tests unitaires - -- Vérifient le bon fonctionnement du prétraitement, des prédictions et de la gestion d’erreurs. -- Intégrés à **GitHub Actions**, **ils bloquent le déploiement si les tests échouent.** - -### 🐳 Conteneurisation & CI/CD - -L’API FastAPI est packagée via Docker, puis poussée sur DockerHub. -Un [pipeline CI/CD](https://github.com/GrgMdmn/OpenClassrooms_AI_Projects/blob/main/.github/workflows/p7_ci-cd.yml) complet garantit **un déploiement reproductible**. Il assure: -- La validation de tests unitaires -- La construction et le push de l'api conteneurisée sur [dockerhub](https://hub.docker.com/r/grgmdmn/sentiment_api) - -![github workflow](github_ci-cd.png) ---- - -## 🚀 Déploiement de l’API FastAPI (double) - -### 🔧 Backend : FastAPI - -- Expose un endpoint `/predict` -- Charge dynamiquement le **meilleur modèle Word Embedding + LSTM** via le *Model Registry* MLFlow -- Applique les bons embeddings selon le tag `embedding_type` - -### 💻 Interface Streamlit - -Interface locale pour tester les requêtes, debug, démonstrations. - -![Streamlit Local](./streamlit_interface_local.png) - ---- - -### 🌐 Double déploiement (NAS + Google Cloud) - -#### 🔧 Problème AVX2 - -Initialement, l’API ne fonctionnait pas sur le NAS (processeur Intel N100), car celui-ci ne prenait pas en charge les instructions **AVX2**, requises par TensorFlow. - -> 🛠️ **Solution** : activation manuelle du support AVX2 dans le BIOS. Cette option est souvent désactivée par défaut sur les cartes mères orientées NAS pour des raisons de consommation énergétique ou de stabilité. - -Depuis cette modification, le NAS peut exécuter l’API sans erreurs. - ---- - -#### 🚀 API déployées - -Le projet propose un **double déploiement** de l’API, en local et dans le cloud : - -- 🏠 **NAS local** : [sentiment-api.greg-madman-nas.duckdns.org](https://sentiment-api.greg-madman-nas.duckdns.org)\ -Interface Docker Compose sur le NAS : -![docker compose openmediavault](openmediavault_docker-compose_settings.png) - -- ☁️ **Google Cloud Run** : [sentiment-api-service-7772256003.europe-west1.run.app](https://sentiment-api-service-7772256003.europe-west1.run.app)\ -Interface de déploiement Google Cloud : -![déploiement google cloud](google_cloud_interface.png) - ---- - -#### 🔄 Déploiement continu automatisé (NAS uniquement) - -Pour assurer un **déploiement continu** sur le NAS, il a été choisi d'installer [**Watchtower**](https://containrrr.dev/watchtower/arguments/) pour rester dans un logique d'environnement 100% `docker`. Cet outil surveille toutes les **10 minutes** l’image Docker de l’API (`sentiment_api`) et, en cas de nouvelle version disponible sur DockerHub, redémarre automatiquement le conteneur avec la dernière image. -![Docker Watchtower dans OMV Docker-Compose](watchtower_docker.png) - -Ce mécanisme garantit que la version déployée sur le NAS reste toujours synchronisée avec la dernière version validée par les tests sur GitHub Actions, **sans intervention manuelle**. -![Logs watchtower dans docker (console SSH)](watchtower_logs.png) - -Un **système d’alerte mail** notifie automatiquement toute mise à jour effectuée. -![Rapport mail watchtower update sentiment_api](watchtower_logs_by_email.png) - ---- - -## 📊 Monitoring maison - -Une logique d’alerte légère embarquée dans l’API : - -![1ère erreur](./api_first_error.png) - -![2ème erreur](./api_second_error.png) - -![3ème erreur](./api_third_error.png) - - -Si **3 erreurs de prédiction** consécutives sont détectées sur une **fenêtre de 5 min**, un rapport est généré automatiquement. - -![Rapport d'erreur envoyé par mail](./api_error_report.png) - - - ---- - -## 🧠 Schéma global du pipeline MLOps - -### Schéma synthétique -``` - ┌────────────┐ - │ Données │ - │ (tweets) │ - └─────┬──────┘ - │ - Prétraitement (regex, tokenisation, lemmatisation, stopwords) - │ - ┌─────────▼──────────┐ - │ Entraînement ML │ - └─────────┬──────────┘ - │ - Log via MLFlow + artefacts MinIO - │ - Sélection du meilleur modèle "avancé" (hors BERT) selon précision - │ - ┌─────────▼──────────┐ - │ API FastAPI │ - │ + Streamlit │ - └─────────┬──────────┘ - │ - Déploiement : - ├─ Google Cloud Run (manuel) - └─ NAS local (automatisé via Watchtower) - │ -┌──────────▼─────────────┐ -│ Monitoring léger maison│ -│ (alertes sur mauvaises │ -│ prédictions) │ -└────────────────────────┘ -``` -### Schéma détaillé -![Diagramme détaillé](diagram.drawio.png) - ---- - -## ✨ Conclusion - -Ce projet démontre comment construire une chaîne MLOps complète, autonome et reproductible pour du NLP appliqué à Twitter. -Il souligne les **forces et limites des modèles** (y compris BERT), la nécessité d’une métrique adaptée au contexte (précision vs accuracy), et l’importance d’un **déploiement maîtrisé**, même sur une infrastructure personnelle. - ---- - +# ✨ Analyse de sentiments et déploiement MLOps : retour d’expérience complet + +Dans le cadre du projet *Air Paradis*, nous avons mis en œuvre une chaîne complète d’analyse de sentiments à partir de tweets, depuis la modélisation jusqu’au déploiement MLOps. Ce billet de blog revient sur les étapes clés du projet : choix méthodologiques, comparaison des modèles, mise en production d’une API robuste, et déploiement sur deux environnements (local + cloud). 🧠🔧 + +--- + +## 🔍 Trois approches de modélisation supervisée + +Nous avons testé trois approches complémentaires pour prédire le sentiment d’un tweet. Chacune repose sur une représentation textuelle différente et un algorithme d’apprentissage adapté : + +### 1. 🧱 Approche simple : Bag-of-Words (BoW) + modèles de machine learning + +On vectorise les tweets via BoW ou BoW + TF-IDF, qui comptent la fréquence des mots. +Contrairement à d'autres projets NLP, nous avons évité LDA (non supervisé, orienté topic modeling). + +➡️ **Modèles testés** : +- Régression Logistique +- Naive Bayes +- SVM +- Random Forest + +Ces modèles sont entraînés avec **GridSearchCV**, en optimisant la **précision (`precision`)**, notre KPI métier. Différents KPI ont été calculés sur l'ensemble de test. + +![Scores modèles simples](./Basic-Models.png) + +--- + +### 2. 🔥 Approche avancée : Word Embeddings + LSTM + +Nous utilisons des vecteurs denses Word2Vec (entraînés) et GloVe 300d (préentraînés par Stanford), qui capturent les similarités sémantiques entre mots. Ces représentations ne prennent toutefois **pas en compte le contexte** exact (ex : "annulé" est probablement un mot très négatif pour un vol mais positif pour un procès contenant des charges contre vous). + +La classification est réalisée par un **LSTM**, capable d'exploiter la séquentialité des mots. +Le **modèle déployé est automatiquement sélectionné** entre Word2Vec + LSTM et GloVe + LSTM, selon la **précision maximale enregistrée dans MLFlow**. + +Modèle `Word2Vec + LSTM` (`test_precision : 0.797 , val_precision : 0.795`): +![Scores entrainement modèle avancé LSTM + Word2Vec](./Word2Vec_LSTM_epoch9.png) +Modèle `GloVe 300d + LSTM` (`test_precision : 0.792 , val_precision : 0.798`): +![Scores entrainement modèles avancés LSTM + GloVe 300d](./GloVe-300d_LSTM_epoch12.png) + +Bien que GloVe 300d + LSTM présente un score de précision légèrement supérieur à Word2Vec + LSTM sur l'ensemble de validation, il reste inférieur pour l'ensemble de test (relativement négligeable dans l'absolu). + +--- + +### 3. 🤖 Approche transformer : DistilBERT, embeddings contextuels allégés + +Nous avons utilisé **DistilBERT**, une version plus légère et plus rapide de BERT (créée par Hugging Face). +Il conserve **95 % de la performance de BERT** tout en étant **40 % plus petit** et **60 % plus rapide**, ce qui le rend particulièrement adapté à un **entraînement sur GPU limité** (Google Colab) et à une **inférence plus rapide**. + +Comme BERT, DistilBERT produit des **embeddings contextuels**, c’est-à-dire que le sens des mots varie selon leur contexte. Par exemple, *"banque"* dans *"je vais à la banque"* et *"banque de données"* n’aura pas le même vecteur. + +Il a été **fine-tuné** ici pour une classification binaire (positif/négatif), avec un tokenizer et un encodage spécifiques à son architecture. + +> **Modèle `DistilBERT`** (`test_precision : 0.861 , val_precision : 0.858`) + +![Courbes d'entraînement DistilBERT](./bert_training_curves_epoch1.png) +![Matrice de Confusion](./bert_confusion_matrix.png) +![ROC Curve](./bert_roc_curve.png) + +⚠️ **Limites** : comme tous les modèles préentraînés, DistilBERT **n’est pas sensible à l’ironie, aux jeux de mots, ni aux références culturelles implicites**. +Or, Twitter est un terrain de jeu idéal pour ce genre de contenus détournés. Ces limites s’appliquent aussi à Word2Vec, GloVe et aux modèles classiques. + +--- + +## 🎯 Pourquoi optimiser la précision (et non l'accuracy) ? + +**Accuracy** = proportion de bonnes prédictions globales. +**Precision** = proportion de tweets *prévus positifs* qui sont *vraiment positifs*. + +> Exemple : sur 100 tweets, si le modèle prédit 30 positifs, mais 15 sont des erreurs, la précision est de 50 %. + +Nous avons préféré **minimiser les faux positifs**, car dans un contexte sensible, **annoncer un tweet positif alors qu’il est négatif peut être problématique**. + +- Modèles classiques : **GridSearchCV optimisé sur la précision** +- Modèles DL : **EarlyStopping sur val_loss**, mais **sélection finale sur la précision** + +--- + +## ⚙️ Environnement MLOps souverain + +### 🖥️ Serveur personnel NAS + OpenMediaVault + +MLFlow est installé sur un **NAS personnel (Intel N100)**, sous **OpenMediaVault (Debian)**. +Nous utilisons **MinIO** pour stocker artefacts et modèles, garantissant une gestion locale, souveraine et compatible S3. + +> Les notebooks, exécutés sur Google Colab pour bénéficier de GPU, loggent dans MLFlow et envoient les modèles vers MinIO via des **variables d’environnement**. + +Dashboard Expériences MLFlow : +![Dashboard MLFLow](./mlflow_experiments_dashboard.png) + +Modèles en production (registre de modèles): +![Models Registry](mlflow_models_registry_2.png) + +Meilleur modèle non BERT retenu : +![Modèle retenu](mlflow_models_registry_best_LSTM_model.png) +![Meilleur modèle non BERT](mlflolw_experiments_dashboard_best_LSTM_model.png) +--- + +## 🧪 CI, tests et versioning + +### ✅ Tests unitaires + +- Vérifient le bon fonctionnement du prétraitement, des prédictions et de la gestion d’erreurs. +- Intégrés à **GitHub Actions**, **ils bloquent le déploiement si les tests échouent.** + +### 🐳 Conteneurisation & CI/CD + +L’API FastAPI est packagée via Docker, puis poussée sur DockerHub. +Un [pipeline CI/CD](https://github.com/GrgMdmn/OpenClassrooms_AI_Projects/blob/main/.github/workflows/p7_ci-cd.yml) complet garantit **un déploiement reproductible**. Il assure: +- La validation de tests unitaires +- La construction et le push de l'api conteneurisée sur [dockerhub](https://hub.docker.com/r/grgmdmn/sentiment_api) + +![github workflow](github_ci-cd.png) +--- + +## 🚀 Déploiement de l’API FastAPI (double) + +### 🔧 Backend : FastAPI + +- Expose un endpoint `/predict` +- Charge dynamiquement le **meilleur modèle Word Embedding + LSTM** via le *Model Registry* MLFlow +- Applique les bons embeddings selon le tag `embedding_type` + +### 💻 Interface Streamlit + +Interface locale pour tester les requêtes, debug, démonstrations. + +![Streamlit Local](./streamlit_interface_local.png) + +--- + +### 🌐 Double déploiement (NAS + Google Cloud) + +#### 🔧 Problème AVX2 + +Initialement, l’API ne fonctionnait pas sur le NAS (processeur Intel N100), car celui-ci ne prenait pas en charge les instructions **AVX2**, requises par TensorFlow. + +> 🛠️ **Solution** : activation manuelle du support AVX2 dans le BIOS. Cette option est souvent désactivée par défaut sur les cartes mères orientées NAS pour des raisons de consommation énergétique ou de stabilité. + +Depuis cette modification, le NAS peut exécuter l’API sans erreurs. + +--- + +#### 🚀 API déployées + +Le projet propose un **double déploiement** de l’API, en local et dans le cloud : + +- 🏠 **NAS local** : [sentiment-api.greg-madman-nas.duckdns.org](https://sentiment-api.greg-madman-nas.duckdns.org)\ +Interface Docker Compose sur le NAS : +![docker compose openmediavault](openmediavault_docker-compose_settings.png) + +- ☁️ **Google Cloud Run** : [sentiment-api-service-7772256003.europe-west1.run.app](https://sentiment-api-service-7772256003.europe-west1.run.app)\ +Interface de déploiement Google Cloud : +![déploiement google cloud](google_cloud_interface.png) + +--- + +#### 🔄 Déploiement continu automatisé (NAS uniquement) + +Pour assurer un **déploiement continu** sur le NAS, il a été choisi d'installer [**Watchtower**](https://containrrr.dev/watchtower/arguments/) pour rester dans un logique d'environnement 100% `docker`. Cet outil surveille toutes les **10 minutes** l’image Docker de l’API (`sentiment_api`) et, en cas de nouvelle version disponible sur DockerHub, redémarre automatiquement le conteneur avec la dernière image. +![Docker Watchtower dans OMV Docker-Compose](watchtower_docker.png) + +Ce mécanisme garantit que la version déployée sur le NAS reste toujours synchronisée avec la dernière version validée par les tests sur GitHub Actions, **sans intervention manuelle**. +![Logs watchtower dans docker (console SSH)](watchtower_logs.png) + +Un **système d’alerte mail** notifie automatiquement toute mise à jour effectuée. +![Rapport mail watchtower update sentiment_api](watchtower_logs_by_email.png) + +--- + +## 📊 Monitoring maison + +Une logique d’alerte légère embarquée dans l’API : + +![1ère erreur](./api_first_error.png) + +![2ème erreur](./api_second_error.png) + +![3ème erreur](./api_third_error.png) + + +Si **3 erreurs de prédiction** consécutives sont détectées sur une **fenêtre de 5 min**, un rapport est généré automatiquement. + +![Rapport d'erreur envoyé par mail](./api_error_report.png) + + + +--- + +## 🧠 Schéma global du pipeline MLOps + +### Schéma synthétique +``` + ┌────────────┐ + │ Données │ + │ (tweets) │ + └─────┬──────┘ + │ + Prétraitement (regex, tokenisation, lemmatisation, stopwords) + │ + ┌─────────▼──────────┐ + │ Entraînement ML │ + └─────────┬──────────┘ + │ + Log via MLFlow + artefacts MinIO + │ + Sélection du meilleur modèle "avancé" (hors BERT) selon précision + │ + ┌─────────▼──────────┐ + │ API FastAPI │ + │ + Streamlit │ + └─────────┬──────────┘ + │ + Déploiement : + ├─ Google Cloud Run (manuel) + └─ NAS local (automatisé via Watchtower) + │ +┌──────────▼─────────────┐ +│ Monitoring léger maison│ +│ (alertes sur mauvaises │ +│ prédictions) │ +└────────────────────────┘ +``` +### Schéma détaillé +![Diagramme détaillé](diagram.drawio.png) + +--- + +## ✨ Conclusion + +Ce projet démontre comment construire une chaîne MLOps complète, autonome et reproductible pour du NLP appliqué à Twitter. +Il souligne les **forces et limites des modèles** (y compris BERT), la nécessité d’une métrique adaptée au contexte (précision vs accuracy), et l’importance d’un **déploiement maîtrisé**, même sur une infrastructure personnelle. + +--- + 💬 Merci pour votre lecture ! \ No newline at end of file diff --git a/docs/blog.md b/docs/blog.md index 562e84b..e386f4c 100644 --- a/docs/blog.md +++ b/docs/blog.md @@ -1,245 +1,245 @@ -# ✨ Sentiment Analysis and MLOps Deployment: A Complete Experience Report - -As part of the *Air Paradis* project, we implemented a full sentiment analysis pipeline from tweets, covering everything from modeling to MLOps deployment. This blog post reviews the key project stages: methodological choices, model comparison, deployment of a robust API, and deployment on two environments (local + cloud). 🧠🔧 - ---- - -## 🔍 Three Supervised Modeling Approaches - -We tested three complementary approaches to predict the sentiment of a tweet. Each relies on a different text representation and a suitable learning algorithm: - -### 1. 🧱 Simple Approach: Bag-of-Words (BoW) + Machine Learning Models - -Tweets are vectorized using BoW or BoW + TF-IDF, which count word frequencies. -Unlike other NLP projects, we avoided LDA (unsupervised, topic modeling oriented). - -➡️ **Tested models**: -- Logistic Regression -- Naive Bayes -- SVM -- Random Forest - -These models are trained with **GridSearchCV**, optimizing **precision (`precision`)**, our key business KPI. Various KPIs were computed on the test set. - -![Simple Models Scores](./Basic-Models.png) - ---- - -### 2. 🔥 Advanced Approach: Word Embeddings + LSTM - -We use dense Word2Vec vectors (trained) and GloVe 300d (pretrained by Stanford), which capture semantic similarities between words. However, these representations **do not take into account the exact context** (e.g., "cancelled" is probably very negative for a flight but positive in a legal case with charges against you). - -Classification is done by an **LSTM**, capable of exploiting word sequentiality. -The **deployed model is automatically selected** between Word2Vec + LSTM and GloVe + LSTM, according to the **highest precision recorded in MLFlow**. - -Model `Word2Vec + LSTM` (`test_precision: 0.797, val_precision: 0.795`): -![Advanced LSTM + Word2Vec Training Scores](./Word2Vec_LSTM_epoch9.png) -Model `GloVe 300d + LSTM` (`test_precision: 0.792, val_precision: 0.798`): -![Advanced LSTM + GloVe 300d Training Scores](./GloVe-300d_LSTM_epoch12.png) - -Although GloVe 300d + LSTM shows a slightly better precision score on the validation set than Word2Vec + LSTM, it remains lower on the test set (relatively negligible overall). - ---- - -### 3. 🤖 Transformer Approach: DistilBERT, Lightweight Contextual Embeddings - -We used **DistilBERT**, a lighter and faster version of BERT (created by Hugging Face). -It retains **95% of BERT's performance** while being **40% smaller** and **60% faster**, making it especially suited for **training on limited GPUs** (Google Colab) and **faster inference**. - -Like BERT, DistilBERT produces **contextual embeddings**, meaning word meanings vary depending on context. For example, *"bank"* in *"I go to the bank"* and *"database bank"* will not have the same vector. - -It was **fine-tuned** here for binary classification (positive/negative), with a tokenizer and encoding specific to its architecture. - -> **Model `DistilBERT`** (`test_precision: 0.861, val_precision: 0.858`) - -![DistilBERT Training Curves](./bert_training_curves_epoch1.png) -![Confusion Matrix](./bert_confusion_matrix.png) -![ROC Curve](./bert_roc_curve.png) - -⚠️ **Limits**: Like all pretrained models, DistilBERT **is not sensitive to irony, puns, or implicit cultural references**. -Twitter is an ideal playground for this kind of nuanced content. These limits also apply to Word2Vec, GloVe, and classical models. - ---- - -## 🎯 Why Optimize Precision (Not Accuracy)? - -**Accuracy** = overall proportion of correct predictions. -**Precision** = proportion of *predicted positive* tweets that are *actually positive*. - -> Example: out of 100 tweets, if the model predicts 30 positives but 15 are wrong, precision is 50%. - -We preferred to **minimize false positives**, because in a sensitive context, **announcing a tweet as positive when it is negative can be problematic**. - -- Classical models: **GridSearchCV optimized for precision** -- Deep Learning models: **EarlyStopping on val_loss**, but **final selection based on precision** - ---- - -## ⚙️ Sovereign MLOps Environment - -### 🖥️ Personal NAS Server + OpenMediaVault - -MLFlow is installed on a **personal NAS (Intel N100)** running **OpenMediaVault (Debian)**. -We use **MinIO** to store artifacts and models, ensuring local, sovereign, and S3-compatible management. - -> Notebooks, run on Google Colab to leverage GPUs, log in MLFlow and send models to MinIO via **environment variables**. - -MLFlow Experiments Dashboard: -![MLFlow Dashboard](./mlflow_experiments_dashboard.png) - -Production Models (model registry): -![Models Registry](mlflow_models_registry_2.png) - -Best non-BERT selected model: -![Selected Model](mlflow_models_registry_best_LSTM_model.png) -![Best Non-BERT Model](mlflolw_experiments_dashboard_best_LSTM_model.png) - ---- - -## 🧪 CI, Testing and Versioning - -### ✅ Unit Tests - -- Check correct preprocessing, predictions, and error handling. -- Integrated into **GitHub Actions**, **blocking deployment if tests fail.** - -### 🐳 Containerization & CI/CD - -The FastAPI API is packaged with Docker, then pushed to DockerHub. -A full [CI/CD pipeline](https://github.com/GrgMdmn/OpenClassrooms_AI_Projects/blob/main/.github/workflows/p7_ci-cd.yml) guarantees **reproducible deployment**, ensuring: -- Validation of unit tests -- Build and push of the containerized API to [DockerHub](https://hub.docker.com/r/grgmdmn/sentiment_api) - -![GitHub Workflow](github_ci-cd.png) - ---- - -## 🚀 FastAPI API Deployment (Dual) - -### 🔧 Backend: FastAPI - -- Exposes a `/predict` endpoint -- Dynamically loads the **best Word Embedding + LSTM model** via MLFlow Model Registry -- Applies appropriate embeddings according to the `embedding_type` tag - -### 💻 Streamlit Interface - -Local interface for request testing, debugging, demonstrations. - -![Streamlit Local](./streamlit_interface_local.png) - ---- - -### 🌐 Dual Deployment (NAS + Google Cloud) - -#### 🔧 AVX2 Issue - -Initially, the API did not run on the NAS (Intel N100 CPU) because it did not support **AVX2 instructions**, required by TensorFlow. - -> 🛠️ **Solution**: manual activation of AVX2 support in BIOS. This option is often disabled by default on NAS motherboards for power consumption or stability reasons. - -Since this change, the NAS can run the API without errors. - ---- - -#### 🚀 Deployed APIs - -The project offers a **dual API deployment**, locally and in the cloud: - -- 🏠 **Local NAS**: [sentiment-api.greg-madman-nas.duckdns.org](https://sentiment-api.greg-madman-nas.duckdns.org) -Docker Compose interface on the NAS: -![Docker Compose OpenMediaVault](openmediavault_docker-compose_settings.png) - -- ☁️ **Google Cloud Run**: [sentiment-api-service-7772256003.europe-west1.run.app](https://sentiment-api-service-7772256003.europe-west1.run.app) -Google Cloud deployment interface: -![Google Cloud Deployment](google_cloud_interface.png) - ---- - -#### 🔄 Automated Continuous Deployment (NAS only) - -To ensure **continuous deployment** on the NAS, we chose to install [**Watchtower**](https://containrrr.dev/watchtower/arguments/) to maintain a 100% `docker` environment. This tool checks every **10 minutes** the Docker image of the API (`sentiment_api`) and, if a new version is available on DockerHub, automatically restarts the container with the latest image. -![Docker Watchtower in OMV Docker-Compose](watchtower_docker.png) - -This mechanism ensures that the version deployed on the NAS always stays synchronized with the latest version validated by GitHub Actions tests, **without manual intervention**. -![Logs watchtower in docker (SSH console)](watchtower_logs.png) - -An **email alert system** automatically notifies any update performed. -![Watchtower Logs by Email](watchtower_logs_by_email.png) - - ---- - -## 📊 Home-made Monitoring - -A lightweight alert logic embedded in the API: - -![1st Error](./api_first_error.png) -![2nd Error](./api_second_error.png) -![3rd Error](./api_third_error.png) - -If **3 consecutive prediction errors** are detected within a **5-minute window**, a report is automatically generated. - -![Error Report Sent by Email](./api_error_report.png) - ---- - -## 🧠 Overall MLOps Pipeline Diagram - -### Summary Diagram - -``` - ┌────────────┐ - │ Data │ - │ (tweets) │ - └─────┬──────┘ - │ - Preprocessing (regex, tokenization, lemmatization, stopwords) - │ - ┌─────────▼──────────┐ - │ ML Training │ - └─────────┬──────────┘ - │ - Logging via MLFlow + artifacts MinIO - │ - Select best "advanced" model (excluding BERT) by precision - │ - ┌─────────▼──────────┐ - │ FastAPI │ - │ + Streamlit │ - └─────────┬──────────┘ - │ - Deployment: - ├─ Google Cloud Run (manual) - └─ Local NAS (automated via Watchtower) - │ -┌──────────▼─────────────┐ -│ Lightweight in-house │ -│ Monitoring │ -│ (alerts on bad │ -│ predictions) │ -└────────────────────────┘ - -``` - -### Detailed Diagram -![Detailed Diagram](diagram.drawio.png) - ---- - -## ✨ Conclusion - -This project demonstrates how to build a complete, autonomous, and reproducible MLOps pipeline for NLP applied to Twitter. -It highlights the **strengths and limitations of the models** (including BERT), the need for a **metric adapted to the context** (precision vs accuracy), and the importance of **controlled deployment**, even on personal infrastructure. - ---- - -💬 Thank you for reading! - - - - - - - -Demander à ChatGPT +# ✨ Sentiment Analysis and MLOps Deployment: A Complete Experience Report + +As part of the *Air Paradis* project, we implemented a full sentiment analysis pipeline from tweets, covering everything from modeling to MLOps deployment. This blog post reviews the key project stages: methodological choices, model comparison, deployment of a robust API, and deployment on two environments (local + cloud). 🧠🔧 + +--- + +## 🔍 Three Supervised Modeling Approaches + +We tested three complementary approaches to predict the sentiment of a tweet. Each relies on a different text representation and a suitable learning algorithm: + +### 1. 🧱 Simple Approach: Bag-of-Words (BoW) + Machine Learning Models + +Tweets are vectorized using BoW or BoW + TF-IDF, which count word frequencies. +Unlike other NLP projects, we avoided LDA (unsupervised, topic modeling oriented). + +➡️ **Tested models**: +- Logistic Regression +- Naive Bayes +- SVM +- Random Forest + +These models are trained with **GridSearchCV**, optimizing **precision (`precision`)**, our key business KPI. Various KPIs were computed on the test set. + +![Simple Models Scores](./Basic-Models.png) + +--- + +### 2. 🔥 Advanced Approach: Word Embeddings + LSTM + +We use dense Word2Vec vectors (trained) and GloVe 300d (pretrained by Stanford), which capture semantic similarities between words. However, these representations **do not take into account the exact context** (e.g., "cancelled" is probably very negative for a flight but positive in a legal case with charges against you). + +Classification is done by an **LSTM**, capable of exploiting word sequentiality. +The **deployed model is automatically selected** between Word2Vec + LSTM and GloVe + LSTM, according to the **highest precision recorded in MLFlow**. + +Model `Word2Vec + LSTM` (`test_precision: 0.797, val_precision: 0.795`): +![Advanced LSTM + Word2Vec Training Scores](./Word2Vec_LSTM_epoch9.png) +Model `GloVe 300d + LSTM` (`test_precision: 0.792, val_precision: 0.798`): +![Advanced LSTM + GloVe 300d Training Scores](./GloVe-300d_LSTM_epoch12.png) + +Although GloVe 300d + LSTM shows a slightly better precision score on the validation set than Word2Vec + LSTM, it remains lower on the test set (relatively negligible overall). + +--- + +### 3. 🤖 Transformer Approach: DistilBERT, Lightweight Contextual Embeddings + +We used **DistilBERT**, a lighter and faster version of BERT (created by Hugging Face). +It retains **95% of BERT's performance** while being **40% smaller** and **60% faster**, making it especially suited for **training on limited GPUs** (Google Colab) and **faster inference**. + +Like BERT, DistilBERT produces **contextual embeddings**, meaning word meanings vary depending on context. For example, *"bank"* in *"I go to the bank"* and *"database bank"* will not have the same vector. + +It was **fine-tuned** here for binary classification (positive/negative), with a tokenizer and encoding specific to its architecture. + +> **Model `DistilBERT`** (`test_precision: 0.861, val_precision: 0.858`) + +![DistilBERT Training Curves](./bert_training_curves_epoch1.png) +![Confusion Matrix](./bert_confusion_matrix.png) +![ROC Curve](./bert_roc_curve.png) + +⚠️ **Limits**: Like all pretrained models, DistilBERT **is not sensitive to irony, puns, or implicit cultural references**. +Twitter is an ideal playground for this kind of nuanced content. These limits also apply to Word2Vec, GloVe, and classical models. + +--- + +## 🎯 Why Optimize Precision (Not Accuracy)? + +**Accuracy** = overall proportion of correct predictions. +**Precision** = proportion of *predicted positive* tweets that are *actually positive*. + +> Example: out of 100 tweets, if the model predicts 30 positives but 15 are wrong, precision is 50%. + +We preferred to **minimize false positives**, because in a sensitive context, **announcing a tweet as positive when it is negative can be problematic**. + +- Classical models: **GridSearchCV optimized for precision** +- Deep Learning models: **EarlyStopping on val_loss**, but **final selection based on precision** + +--- + +## ⚙️ Sovereign MLOps Environment + +### 🖥️ Personal NAS Server + OpenMediaVault + +MLFlow is installed on a **personal NAS (Intel N100)** running **OpenMediaVault (Debian)**. +We use **MinIO** to store artifacts and models, ensuring local, sovereign, and S3-compatible management. + +> Notebooks, run on Google Colab to leverage GPUs, log in MLFlow and send models to MinIO via **environment variables**. + +MLFlow Experiments Dashboard: +![MLFlow Dashboard](./mlflow_experiments_dashboard.png) + +Production Models (model registry): +![Models Registry](mlflow_models_registry_2.png) + +Best non-BERT selected model: +![Selected Model](mlflow_models_registry_best_LSTM_model.png) +![Best Non-BERT Model](mlflolw_experiments_dashboard_best_LSTM_model.png) + +--- + +## 🧪 CI, Testing and Versioning + +### ✅ Unit Tests + +- Check correct preprocessing, predictions, and error handling. +- Integrated into **GitHub Actions**, **blocking deployment if tests fail.** + +### 🐳 Containerization & CI/CD + +The FastAPI API is packaged with Docker, then pushed to DockerHub. +A full [CI/CD pipeline](https://github.com/GrgMdmn/OpenClassrooms_AI_Projects/blob/main/.github/workflows/p7_ci-cd.yml) guarantees **reproducible deployment**, ensuring: +- Validation of unit tests +- Build and push of the containerized API to [DockerHub](https://hub.docker.com/r/grgmdmn/sentiment_api) + +![GitHub Workflow](github_ci-cd.png) + +--- + +## 🚀 FastAPI API Deployment (Dual) + +### 🔧 Backend: FastAPI + +- Exposes a `/predict` endpoint +- Dynamically loads the **best Word Embedding + LSTM model** via MLFlow Model Registry +- Applies appropriate embeddings according to the `embedding_type` tag + +### 💻 Streamlit Interface + +Local interface for request testing, debugging, demonstrations. + +![Streamlit Local](./streamlit_interface_local.png) + +--- + +### 🌐 Dual Deployment (NAS + Google Cloud) + +#### 🔧 AVX2 Issue + +Initially, the API did not run on the NAS (Intel N100 CPU) because it did not support **AVX2 instructions**, required by TensorFlow. + +> 🛠️ **Solution**: manual activation of AVX2 support in BIOS. This option is often disabled by default on NAS motherboards for power consumption or stability reasons. + +Since this change, the NAS can run the API without errors. + +--- + +#### 🚀 Deployed APIs + +The project offers a **dual API deployment**, locally and in the cloud: + +- 🏠 **Local NAS**: [sentiment-api.greg-madman-nas.duckdns.org](https://sentiment-api.greg-madman-nas.duckdns.org) +Docker Compose interface on the NAS: +![Docker Compose OpenMediaVault](openmediavault_docker-compose_settings.png) + +- ☁️ **Google Cloud Run**: [sentiment-api-service-7772256003.europe-west1.run.app](https://sentiment-api-service-7772256003.europe-west1.run.app) +Google Cloud deployment interface: +![Google Cloud Deployment](google_cloud_interface.png) + +--- + +#### 🔄 Automated Continuous Deployment (NAS only) + +To ensure **continuous deployment** on the NAS, we chose to install [**Watchtower**](https://containrrr.dev/watchtower/arguments/) to maintain a 100% `docker` environment. This tool checks every **10 minutes** the Docker image of the API (`sentiment_api`) and, if a new version is available on DockerHub, automatically restarts the container with the latest image. +![Docker Watchtower in OMV Docker-Compose](watchtower_docker.png) + +This mechanism ensures that the version deployed on the NAS always stays synchronized with the latest version validated by GitHub Actions tests, **without manual intervention**. +![Logs watchtower in docker (SSH console)](watchtower_logs.png) + +An **email alert system** automatically notifies any update performed. +![Watchtower Logs by Email](watchtower_logs_by_email.png) + + +--- + +## 📊 Home-made Monitoring + +A lightweight alert logic embedded in the API: + +![1st Error](./api_first_error.png) +![2nd Error](./api_second_error.png) +![3rd Error](./api_third_error.png) + +If **3 consecutive prediction errors** are detected within a **5-minute window**, a report is automatically generated. + +![Error Report Sent by Email](./api_error_report.png) + +--- + +## 🧠 Overall MLOps Pipeline Diagram + +### Summary Diagram + +``` + ┌────────────┐ + │ Data │ + │ (tweets) │ + └─────┬──────┘ + │ + Preprocessing (regex, tokenization, lemmatization, stopwords) + │ + ┌─────────▼──────────┐ + │ ML Training │ + └─────────┬──────────┘ + │ + Logging via MLFlow + artifacts MinIO + │ + Select best "advanced" model (excluding BERT) by precision + │ + ┌─────────▼──────────┐ + │ FastAPI │ + │ + Streamlit │ + └─────────┬──────────┘ + │ + Deployment: + ├─ Google Cloud Run (manual) + └─ Local NAS (automated via Watchtower) + │ +┌──────────▼─────────────┐ +│ Lightweight in-house │ +│ Monitoring │ +│ (alerts on bad │ +│ predictions) │ +└────────────────────────┘ + +``` + +### Detailed Diagram +![Detailed Diagram](diagram.drawio.png) + +--- + +## ✨ Conclusion + +This project demonstrates how to build a complete, autonomous, and reproducible MLOps pipeline for NLP applied to Twitter. +It highlights the **strengths and limitations of the models** (including BERT), the need for a **metric adapted to the context** (precision vs accuracy), and the importance of **controlled deployment**, even on personal infrastructure. + +--- + +💬 Thank you for reading! + + + + + + + +Demander à ChatGPT diff --git a/functions.py b/functions.py index dd13e66..97e2f7f 100644 --- a/functions.py +++ b/functions.py @@ -1,125 +1,125 @@ -import re -import html -import numpy as np -import pandas as pd -from multiprocessing import Pool - -# NLTK imports for NLP preprocessing -import nltk -from nltk.corpus import stopwords -from nltk.tokenize import word_tokenize -from nltk.stem import WordNetLemmatizer - -# Ensure necessary NLTK resources are downloaded and available -try: - stopwords.words('english') - word_tokenize("test") - WordNetLemmatizer().lemmatize("testing") -except LookupError: - nltk.download('punkt') # Tokenizer models - nltk.download('punkt_tab') # Additional tokenizer support - nltk.download('stopwords') # Stopwords lists - nltk.download('wordnet') # WordNet lexicon for lemmatization - nltk.download('omw-1.4') # WordNet lemmas data - -# Convert sentiment labels to binary: 0 for negative, 1 for all other classes -def convert_sentiment_label(df): - """ - Convert the 'target' column in the DataFrame to binary sentiment labels: - - 0 for negative sentiment (label 0) - - 1 for any other sentiment label - Returns a new DataFrame with updated labels. - """ - converted_target_data = df.copy() - converted_target_data['target'] = converted_target_data['target'].apply(lambda x: 0 if x == 0 else 1) - return converted_target_data - -# Downsample the dataset to balance classes by limiting samples per class -def downsample_data(df, n_samples=50000, random_state=42): - """ - Perform downsampling to balance dataset classes. - Selects `n_samples` examples from both negative (0) and positive (1) classes. - Returns a balanced DataFrame with equal number of samples per class. - """ - negative_samples = df[df['target'] == 0].sample(n=n_samples, random_state=random_state) - positive_samples = df[df['target'] == 1].sample(n=n_samples, random_state=random_state) - downsampled_data = pd.concat([negative_samples, positive_samples]) - return downsampled_data - -# PREPROCESSING FUNCTION FOR CLASSIC AND ADVANCED MODELS -def preprocess_tweet(tweet): - """ - Preprocess a tweet for classic ML or word embedding models (e.g., Word2Vec, GloVe). - Steps: - - Normalize text (lowercase) - - Replace URLs and mentions with tokens - - Decode HTML entities - - Remove unwanted special characters and non-printable chars - - Tokenize text - - Lemmatize tokens - - Remove stopwords except negation words - Returns cleaned, normalized text as a single string. - """ - - # Ensure input is a string - if not isinstance(tweet, str): - return "" - - # Decode HTML entities (e.g., & to &) - tweet = html.unescape(tweet) - - # Convert to lowercase - tweet = tweet.lower() - - # Replace URLs with placeholder token - tweet = re.sub(r'https?://\S+|www\.\S+', '', tweet) - - # Replace user mentions with placeholder token - tweet = re.sub(r'@\w+', '', tweet) - - # Separate hashtags from words to facilitate tokenization - tweet = re.sub(r'#(\w+)', r'# \1', tweet) - - # Remove unwanted punctuation and special characters except some useful ones - tweet = re.sub(r'[^\w\s<>@#!?]', '', tweet) - - # Remove non-printable and control characters (e.g., tabs, newlines, emojis) - tweet = re.sub(r'[^\x20-\x7E]', '', tweet) - - # Tokenize tweet into words - tokens = word_tokenize(tweet) - - # Lemmatize tokens to their base form - lemmatizer = WordNetLemmatizer() - tokens = [lemmatizer.lemmatize(token) for token in tokens] - - # Remove stopwords but keep negation-related words to preserve sentiment meaning - stop_words = set(stopwords.words('english')) - important_words = {'no', 'not', 'nor', 'neither', 'never', 'nobody', 'none', 'nothing', 'nowhere'} - stop_words = stop_words - important_words - tokens = [token for token in tokens if token not in stop_words] - - # Rebuild cleaned tweet text - return ' '.join(tokens) - -# Parallel processing of a DataFrame using multiple CPU cores -def process_in_parallel(df, func, n_jobs=4): - """ - Split a DataFrame into `n_jobs` parts and apply `func` to each part in parallel, - using multiprocessing Pool for speed-up. - Returns the concatenated processed DataFrame. - """ - df_split = np.array_split(df, n_jobs) - with Pool(n_jobs) as pool: - df = pd.concat(pool.map(func, df_split)) - return df - -# Apply preprocessing function on a DataFrame partition -def apply_preprocessing(df_part): - """ - Apply tweet preprocessing to the 'text' column of a DataFrame partition. - Stores results in a new column 'processed_text'. - Returns the updated DataFrame partition. - """ - df_part['processed_text'] = df_part['text'].apply(preprocess_tweet) - return df_part +import re +import html +import numpy as np +import pandas as pd +from multiprocessing import Pool + +# NLTK imports for NLP preprocessing +import nltk +from nltk.corpus import stopwords +from nltk.tokenize import word_tokenize +from nltk.stem import WordNetLemmatizer + +# Ensure necessary NLTK resources are downloaded and available +try: + stopwords.words('english') + word_tokenize("test") + WordNetLemmatizer().lemmatize("testing") +except LookupError: + nltk.download('punkt') # Tokenizer models + nltk.download('punkt_tab') # Additional tokenizer support + nltk.download('stopwords') # Stopwords lists + nltk.download('wordnet') # WordNet lexicon for lemmatization + nltk.download('omw-1.4') # WordNet lemmas data + +# Convert sentiment labels to binary: 0 for negative, 1 for all other classes +def convert_sentiment_label(df): + """ + Convert the 'target' column in the DataFrame to binary sentiment labels: + - 0 for negative sentiment (label 0) + - 1 for any other sentiment label + Returns a new DataFrame with updated labels. + """ + converted_target_data = df.copy() + converted_target_data['target'] = converted_target_data['target'].apply(lambda x: 0 if x == 0 else 1) + return converted_target_data + +# Downsample the dataset to balance classes by limiting samples per class +def downsample_data(df, n_samples=50000, random_state=42): + """ + Perform downsampling to balance dataset classes. + Selects `n_samples` examples from both negative (0) and positive (1) classes. + Returns a balanced DataFrame with equal number of samples per class. + """ + negative_samples = df[df['target'] == 0].sample(n=n_samples, random_state=random_state) + positive_samples = df[df['target'] == 1].sample(n=n_samples, random_state=random_state) + downsampled_data = pd.concat([negative_samples, positive_samples]) + return downsampled_data + +# PREPROCESSING FUNCTION FOR CLASSIC AND ADVANCED MODELS +def preprocess_tweet(tweet): + """ + Preprocess a tweet for classic ML or word embedding models (e.g., Word2Vec, GloVe). + Steps: + - Normalize text (lowercase) + - Replace URLs and mentions with tokens + - Decode HTML entities + - Remove unwanted special characters and non-printable chars + - Tokenize text + - Lemmatize tokens + - Remove stopwords except negation words + Returns cleaned, normalized text as a single string. + """ + + # Ensure input is a string + if not isinstance(tweet, str): + return "" + + # Decode HTML entities (e.g., & to &) + tweet = html.unescape(tweet) + + # Convert to lowercase + tweet = tweet.lower() + + # Replace URLs with placeholder token + tweet = re.sub(r'https?://\S+|www\.\S+', '', tweet) + + # Replace user mentions with placeholder token + tweet = re.sub(r'@\w+', '', tweet) + + # Separate hashtags from words to facilitate tokenization + tweet = re.sub(r'#(\w+)', r'# \1', tweet) + + # Remove unwanted punctuation and special characters except some useful ones + tweet = re.sub(r'[^\w\s<>@#!?]', '', tweet) + + # Remove non-printable and control characters (e.g., tabs, newlines, emojis) + tweet = re.sub(r'[^\x20-\x7E]', '', tweet) + + # Tokenize tweet into words + tokens = word_tokenize(tweet) + + # Lemmatize tokens to their base form + lemmatizer = WordNetLemmatizer() + tokens = [lemmatizer.lemmatize(token) for token in tokens] + + # Remove stopwords but keep negation-related words to preserve sentiment meaning + stop_words = set(stopwords.words('english')) + important_words = {'no', 'not', 'nor', 'neither', 'never', 'nobody', 'none', 'nothing', 'nowhere'} + stop_words = stop_words - important_words + tokens = [token for token in tokens if token not in stop_words] + + # Rebuild cleaned tweet text + return ' '.join(tokens) + +# Parallel processing of a DataFrame using multiple CPU cores +def process_in_parallel(df, func, n_jobs=4): + """ + Split a DataFrame into `n_jobs` parts and apply `func` to each part in parallel, + using multiprocessing Pool for speed-up. + Returns the concatenated processed DataFrame. + """ + df_split = np.array_split(df, n_jobs) + with Pool(n_jobs) as pool: + df = pd.concat(pool.map(func, df_split)) + return df + +# Apply preprocessing function on a DataFrame partition +def apply_preprocessing(df_part): + """ + Apply tweet preprocessing to the 'text' column of a DataFrame partition. + Stores results in a new column 'processed_text'. + Returns the updated DataFrame partition. + """ + df_part['processed_text'] = df_part['text'].apply(preprocess_tweet) + return df_part diff --git a/notebooks/README.fr.md b/notebooks/README.fr.md index c59b4fc..503b425 100644 --- a/notebooks/README.fr.md +++ b/notebooks/README.fr.md @@ -1,46 +1,46 @@ -# Notebooks - -Ce dossier contient les notebooks Jupyter utilisés pour le développement des modèles et les analyses du projet. - -## Contenu - -- Analyse exploratoire des données -- Modèles classiques (régression logistique, SVM, etc.) -- Modèles avancés (LSTM avec embeddings GloVe) -- Modèle BERT - -## Note concernant les fichiers requirements - -Les fichiers `*requirements.txt` ont été générés depuis un environnement Google Colab. Cet environnement inclut de nombreuses librairies préinstallées, dont toutes ne sont pas nécessaires au projet. - -Un nettoyage manuel ou une génération spécifique des fichiers requirements sera nécessaire pour alléger ces fichiers avant un usage en production ou un déploiement. - ---- - -## Résumé des performances des modèles - -### Modèles classiques (entraînés sur 200 000 tweets) - -| Modèle | Précision | Temps d'entraînement| -|--------------------------------|-----------|---------------------| -| Random Forest + BoW | 0,7798 | ~5 heures | -| Régression logistique + TF-IDF | 0,7796 | <2 minutes | - -### Modèles avancés basés sur des embeddings (entraînés sur 200 000 tweets, accélérés GPU sur Google Colab) - -| Modèle | Précision | Temps d'entraînement| -|------------------------|-----------|---------------------| -| Word2Vec + LSTM | 0,797 | 57 secondes | -| GloVe (300d) + LSTM | 0,792 | 47 secondes | - -### Modèle de pointe (entraîné sur 100 000 tweets en raison des limites RAM) - -| Modèle | Précision | Temps d'entraînement| -|---------|-----------|---------------------| -| BERT | 0,882 | 1,3 minutes | - ---- - -Ce résumé met en lumière les compromis entre précision des modèles et temps d’entraînement. Si les modèles classiques sont plus rapides à entraîner, les modèles avancés utilisant des embeddings et LSTM offrent une meilleure précision, avec BERT qui atteint la meilleure précision malgré un jeu de données plus restreint. - -L’écart de précision entre BERT et les modèles à embeddings peut s’expliquer principalement par la capacité bidirectionnelle de BERT à comprendre le contexte. +# Notebooks + +Ce dossier contient les notebooks Jupyter utilisés pour le développement des modèles et les analyses du projet. + +## Contenu + +- Analyse exploratoire des données +- Modèles classiques (régression logistique, SVM, etc.) +- Modèles avancés (LSTM avec embeddings GloVe) +- Modèle BERT + +## Note concernant les fichiers requirements + +Les fichiers `*requirements.txt` ont été générés depuis un environnement Google Colab. Cet environnement inclut de nombreuses librairies préinstallées, dont toutes ne sont pas nécessaires au projet. + +Un nettoyage manuel ou une génération spécifique des fichiers requirements sera nécessaire pour alléger ces fichiers avant un usage en production ou un déploiement. + +--- + +## Résumé des performances des modèles + +### Modèles classiques (entraînés sur 200 000 tweets) + +| Modèle | Précision | Temps d'entraînement| +|--------------------------------|-----------|---------------------| +| Random Forest + BoW | 0,7798 | ~5 heures | +| Régression logistique + TF-IDF | 0,7796 | <2 minutes | + +### Modèles avancés basés sur des embeddings (entraînés sur 200 000 tweets, accélérés GPU sur Google Colab) + +| Modèle | Précision | Temps d'entraînement| +|------------------------|-----------|---------------------| +| Word2Vec + LSTM | 0,797 | 57 secondes | +| GloVe (300d) + LSTM | 0,792 | 47 secondes | + +### Modèle de pointe (entraîné sur 100 000 tweets en raison des limites RAM) + +| Modèle | Précision | Temps d'entraînement| +|---------|-----------|---------------------| +| BERT | 0,882 | 1,3 minutes | + +--- + +Ce résumé met en lumière les compromis entre précision des modèles et temps d’entraînement. Si les modèles classiques sont plus rapides à entraîner, les modèles avancés utilisant des embeddings et LSTM offrent une meilleure précision, avec BERT qui atteint la meilleure précision malgré un jeu de données plus restreint. + +L’écart de précision entre BERT et les modèles à embeddings peut s’expliquer principalement par la capacité bidirectionnelle de BERT à comprendre le contexte. diff --git a/notebooks/README.md b/notebooks/README.md index b58630e..c3b0f04 100644 --- a/notebooks/README.md +++ b/notebooks/README.md @@ -1,46 +1,46 @@ -# Notebooks - -This folder contains the Jupyter notebooks used for model development and analyses in the project. - -## Contents - -- Exploratory Data Analysis -- Classical Models (logistic regression, SVM, etc.) -- Advanced Models (LSTM with GloVe embeddings) -- BERT Model - -## Note on requirements files - -The `*requirements.txt` files were generated from a Google Colab environment. This environment includes many pre-installed libraries, not all necessary for the project. - -Manual cleanup or specific generation of requirements files will be needed to slim down these files before production use or deployment. - ---- - -## Model Performance Summary - -### Classical Models (trained on 200,000 tweets) - -| Model | Precision | Training Time | -|------------------------------|-----------|-----------------| -| Random Forest + BoW | 0.7798 | ~5 hours | -| Logistic Regression + TF-IDF | 0.7796 | <2 minutes | - -### Advanced Models based on word-embeddings (trained on 200,000 tweets, GPU accelerated on Google Colab) - -| Model | Precision | Training Time | -|------------------------|-----------|---------------| -| Word2Vec + LSTM | 0.797 | 57 seconds | -| GloVe (300d) + LSTM | 0.792 | 47 seconds | - -### State-of-the-art Model (trained on 100,000 tweets due to RAM limits) - -| Model | Precision | Training Time | -|--------|-----------|---------------| -| BERT | 0.882 | 1.3 minutes | - ---- - -This summary highlights the trade-offs between model precision and training time. While the classical models are faster to train, advanced models leveraging embeddings and LSTM yield better precision, with BERT achieving the highest precision despite being trained on a smaller dataset. - -The precision offset between BERT and word embeddings models may mainly be due to the BERT bidirectionnal context abilities. +# Notebooks + +This folder contains the Jupyter notebooks used for model development and analyses in the project. + +## Contents + +- Exploratory Data Analysis +- Classical Models (logistic regression, SVM, etc.) +- Advanced Models (LSTM with GloVe embeddings) +- BERT Model + +## Note on requirements files + +The `*requirements.txt` files were generated from a Google Colab environment. This environment includes many pre-installed libraries, not all necessary for the project. + +Manual cleanup or specific generation of requirements files will be needed to slim down these files before production use or deployment. + +--- + +## Model Performance Summary + +### Classical Models (trained on 200,000 tweets) + +| Model | Precision | Training Time | +|------------------------------|-----------|-----------------| +| Random Forest + BoW | 0.7798 | ~5 hours | +| Logistic Regression + TF-IDF | 0.7796 | <2 minutes | + +### Advanced Models based on word-embeddings (trained on 200,000 tweets, GPU accelerated on Google Colab) + +| Model | Precision | Training Time | +|------------------------|-----------|---------------| +| Word2Vec + LSTM | 0.797 | 57 seconds | +| GloVe (300d) + LSTM | 0.792 | 47 seconds | + +### State-of-the-art Model (trained on 100,000 tweets due to RAM limits) + +| Model | Precision | Training Time | +|--------|-----------|---------------| +| BERT | 0.882 | 1.3 minutes | + +--- + +This summary highlights the trade-offs between model precision and training time. While the classical models are faster to train, advanced models leveraging embeddings and LSTM yield better precision, with BERT achieving the highest precision despite being trained on a smaller dataset. + +The precision offset between BERT and word embeddings models may mainly be due to the BERT bidirectionnal context abilities. diff --git a/notebooks/p07_02-notebook-modele-simple.ipynb b/notebooks/p07_02-notebook-modele-simple.ipynb index 24f4bc5..b0c168f 100644 --- a/notebooks/p07_02-notebook-modele-simple.ipynb +++ b/notebooks/p07_02-notebook-modele-simple.ipynb @@ -1,5026 +1,5026 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "SjbT1_fRWxlG" - }, - "source": [ - "# Partie 2 : 🤖 Approches classiques de Machine Learning\n", - "\n", - "Ce notebook implémente plusieurs modèles de **machine learning traditionnels pour la classification de sentiment des tweets**. Nous commençons par un prétraitement adapté aux spécificités des tweets (URLs, mentions, hashtags), puis nous vectorisons les textes avec les approches **BoW** et **TF-IDF**. Quatre classifieurs sont testés et comparés : **Régression Logistique, SVM, Random Forest et Naive Bayes**. Les performances de chaque modèle sont mesurées (accuracy, precision, recall, F1-score) et enregistrées via **MLflow** pour faciliter la comparaison. Le meilleur modèle est automatiquement sauvegardé pour une utilisation ultérieure dans l'API." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "99IN9CIrWxlH" - }, - "source": [ - "## Importation des bibliothèques" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "executionInfo": { - "elapsed": 16353, - "status": "ok", - "timestamp": 1751704230169, - "user": { - "displayName": "Grégoire", - "userId": "01249298206117462766" - }, - "user_tz": -120 - }, - "id": "nT5hy4pKcc81", - "outputId": "92cbb5ad-11d9-4339-e117-f68959e1c2a4" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Mounted at /content/drive\n" - ] - }, - { - "data": { - "text/plain": [ - "['Copie de notebook-connexion-mlflow (1).ipynb',\n", - " 'Copie de 03-notebook-modele-avance.ipynb',\n", - " 'Copie de 02-notebook-modele-simple.ipynb',\n", - " 'Copie de 01-notebook-analyse-exploratoire.ipynb',\n", - " 'Copie de 04-notebook-modele-bert.ipynb',\n", - " 'content',\n", - " 'Copie de notebook-connexion-mlflow.ipynb',\n", - " 'requirements_analyse_exploratoire.txt',\n", - " 'p07_01-notebook-analyse-exploratoire.ipynb',\n", - " 'notebook-connexion-mlflow.ipynb',\n", - " 'requirements_bert.txt',\n", - " 'p07_04-notebook-modele-bert.ipynb',\n", - " 'requirements_modele_avance.txt',\n", - " 'p07_03-notebook-modele-avance.ipynb',\n", - " 'requirements_modele_simple.txt',\n", - " 'p07_02-notebook-modele-simple.ipynb']" - ] - }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" - } - ], - "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": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "executionInfo": { - "elapsed": 28716, - "status": "ok", - "timestamp": 1751704258896, - "user": { - "displayName": "Grégoire", - "userId": "01249298206117462766" - }, - "user_tz": -120 - }, - "id": "OQzU_EBSFFIE", - "outputId": "08c27435-d6d3-4506-de1a-951c2af4710e" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m24.7/24.7 MB\u001b[0m \u001b[31m76.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.9/1.9 MB\u001b[0m \u001b[31m69.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m139.9/139.9 kB\u001b[0m \u001b[31m10.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m242.7/242.7 kB\u001b[0m \u001b[31m18.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m13.8/13.8 MB\u001b[0m \u001b[31m91.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m147.8/147.8 kB\u001b[0m \u001b[31m8.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m114.9/114.9 kB\u001b[0m \u001b[31m7.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m85.0/85.0 kB\u001b[0m \u001b[31m7.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m85.2/85.2 kB\u001b[0m \u001b[31m7.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m733.8/733.8 kB\u001b[0m \u001b[31m44.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m203.4/203.4 kB\u001b[0m \u001b[31m16.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m65.8/65.8 kB\u001b[0m \u001b[31m4.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m118.5/118.5 kB\u001b[0m \u001b[31m9.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m196.2/196.2 kB\u001b[0m \u001b[31m15.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25h" - ] - } - ], - "source": [ - "!pip install mlflow boto3 dotenv --quiet" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "executionInfo": { - "elapsed": 10853, - "status": "ok", - "timestamp": 1751704269750, - "user": { - "displayName": "Grégoire", - "userId": "01249298206117462766" - }, - "user_tz": -120 - }, - "id": "CINfcJQuWxlH", - "outputId": "8901e3f5-5d6e-42e5-8669-dd6172d63f84" - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "[nltk_data] Downloading package punkt to /root/nltk_data...\n", - "[nltk_data] Unzipping tokenizers/punkt.zip.\n", - "[nltk_data] Downloading package punkt_tab to /root/nltk_data...\n", - "[nltk_data] Unzipping tokenizers/punkt_tab.zip.\n", - "[nltk_data] Downloading package stopwords to /root/nltk_data...\n", - "[nltk_data] Unzipping corpora/stopwords.zip.\n", - "[nltk_data] Downloading package wordnet to /root/nltk_data...\n", - "[nltk_data] Downloading package omw-1.4 to /root/nltk_data...\n" - ] - } - ], - "source": [ - "# Importations nécessaires\n", - "import os\n", - "import sys\n", - "import pandas as pd\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "import seaborn as sns\n", - "# import re\n", - "import time\n", - "import warnings\n", - "from collections import Counter\n", - "import pickle\n", - "from tqdm import tqdm\n", - "# import html\n", - "\n", - "# # Importations NLTK\n", - "# import nltk\n", - "# nltk.download('punkt_tab')\n", - "# nltk.download('stopwords')\n", - "# nltk.download('wordnet')\n", - "# from nltk.corpus import stopwords\n", - "# from nltk.tokenize import word_tokenize\n", - "# from nltk.stem import WordNetLemmatizer\n", - "\n", - "# Importations scikit-learn\n", - "from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer\n", - "from sklearn.model_selection import train_test_split, GridSearchCV\n", - "from sklearn.linear_model import LogisticRegression\n", - "from sklearn.svm import LinearSVC\n", - "from sklearn.ensemble import RandomForestClassifier\n", - "from sklearn.naive_bayes import MultinomialNB\n", - "from sklearn.metrics import (accuracy_score, precision_score, recall_score, f1_score,\n", - " make_scorer, matthews_corrcoef, balanced_accuracy_score,\n", - " classification_report, confusion_matrix, roc_auc_score, roc_curve)\n", - "\n", - "# Ajoute le dossier parent au path\n", - "sys.path.append(os.path.abspath(\"..\"))\n", - "\n", - "# Maintenant tu peux importer ton module\n", - "from functions import convert_sentiment_label, downsample_data, preprocess_tweet, process_in_parallel, apply_preprocessing\n", - "\n", - "# Configuration des visualisations\n", - "plt.style.use('ggplot')\n", - "plt.rcParams['figure.figsize'] = (12, 8)\n", - "plt.rcParams['font.family'] = 'DejaVu Sans'\n", - "warnings.filterwarnings('ignore')" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "A5lhSs7GWxlI" - }, - "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": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "executionInfo": { - "elapsed": 3, - "status": "ok", - "timestamp": 1751704269754, - "user": { - "displayName": "Grégoire", - "userId": "01249298206117462766" - }, - "user_tz": -120 - }, - "id": "DHdBPsYlWxlI", - "outputId": "a59fe70c-baf7-445d-bf10-3112cc1735af" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CPU times: user 214 µs, sys: 0 ns, total: 214 µs\n", - "Wall time: 8.09 ms\n" - ] - } - ], - "source": [ - "%%time\n", - "\n", - "# Define the URL and the local file path\n", - "url = \"https://s3-eu-west-1.amazonminio.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": null, - "metadata": { - "id": "wos8l2t1WxlJ" - }, - "outputs": [], - "source": [ - "pd.set_option('display.max_columns', None)\n", - "pd.set_option('display.expand_frame_repr', True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 206 - }, - "executionInfo": { - "elapsed": 16719, - "status": "ok", - "timestamp": 1751704286474, - "user": { - "displayName": "Grégoire", - "userId": "01249298206117462766" - }, - "user_tz": -120 - }, - "id": "S6zzBsBhWxlJ", - "outputId": "e15fcc77-1542-4b5b-8bf4-e11aaaa0c115" - }, - "outputs": [ - { - "data": { - "application/vnd.google.colaboratory.intrinsic+json": { - "type": "dataframe", - "variable_name": "raw_data" - }, - "text/html": [ - "\n", - "
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
targetidsdateflagusertext
001467810369Mon Apr 06 22:19:45 PDT 2009NO_QUERY_TheSpecialOne_@switchfoot http://twitpic.com/2y1zl - Awww, t...
101467810672Mon Apr 06 22:19:49 PDT 2009NO_QUERYscotthamiltonis upset that he can't update his Facebook by ...
201467810917Mon Apr 06 22:19:53 PDT 2009NO_QUERYmattycus@Kenichan I dived many times for the ball. Man...
301467811184Mon Apr 06 22:19:57 PDT 2009NO_QUERYElleCTFmy whole body feels itchy and like its on fire
401467811193Mon Apr 06 22:19:57 PDT 2009NO_QUERYKaroli@nationwideclass no, it's not behaving at all....
\n", - "
\n", - "
\n", - "\n", - "
\n", - " \n", - "\n", - " \n", - "\n", - " \n", - "
\n", - "\n", - "\n", - "
\n", - " \n", - "\n", - "\n", - "\n", - " \n", - "
\n", - "\n", - "
\n", - "
\n" - ], - "text/plain": [ - " target ids date flag \\\n", - "0 0 1467810369 Mon Apr 06 22:19:45 PDT 2009 NO_QUERY \n", - "1 0 1467810672 Mon Apr 06 22:19:49 PDT 2009 NO_QUERY \n", - "2 0 1467810917 Mon Apr 06 22:19:53 PDT 2009 NO_QUERY \n", - "3 0 1467811184 Mon Apr 06 22:19:57 PDT 2009 NO_QUERY \n", - "4 0 1467811193 Mon Apr 06 22:19:57 PDT 2009 NO_QUERY \n", - "\n", - " user text \n", - "0 _TheSpecialOne_ @switchfoot http://twitpic.com/2y1zl - Awww, t... \n", - "1 scotthamilton is upset that he can't update his Facebook by ... \n", - "2 mattycus @Kenichan I dived many times for the ball. Man... \n", - "3 ElleCTF my whole body feels itchy and like its on fire \n", - "4 Karoli @nationwideclass no, it's not behaving at all.... " - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "\n", - "# Define the path to the CSV file\n", - "csv_file_path = os.path.join(extract_path, 'training.1600000.processed.noemoticon.csv')\n", - "\n", - "# Define the column names\n", - "column_names = ['target', 'ids', 'date', 'flag', 'user', 'text']\n", - "\n", - "# Load the dataset into a pandas DataFrame\n", - "raw_data = pd.read_csv(csv_file_path, encoding='latin-1', names=column_names)\n", - "\n", - "# Display the first few rows of the DataFrame\n", - "raw_data.head()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "executionInfo": { - "elapsed": 6, - "status": "ok", - "timestamp": 1751704286479, - "user": { - "displayName": "Grégoire", - "userId": "01249298206117462766" - }, - "user_tz": -120 - }, - "id": "Mh433N4wWxlJ", - "outputId": "4a4132d3-53f0-4ce4-d59c-a598daedac97" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Ce dataframe contient 1600000 lignes et 6 colonnes.\n" - ] - } - ], - "source": [ - "print(f\"Ce dataframe contient {raw_data.shape[0]} lignes et {raw_data.shape[1]} colonnes.\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "u7_v1qZSWxlJ" - }, - "source": [ - "## 🤖 Approches classiques de Machine Learning\n", - "\n", - "Notre démarche pour la classification de sentiment avec des approches classiques 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", - "2. **Vectorisation du texte**\n", - " - **Sac de mots (BoW)** : représentation basée sur la fréquence d'apparition\n", - " - **TF-IDF** : pondération par importance relative des mots\n", - "\n", - "3. **Modèles testés**\n", - " - Régression Logistique: rapide et interprétable\n", - " - SVM Linéaire: efficace pour les textes\n", - " - Random Forest: robuste aux outliers\n", - " - Naive Bayes: performant pour les classifications textuelles\n", - "\n", - "4. **Évaluation et suivi**\n", - " - Métriques: précision, accuracy, recall, F1-score, ROC-AUC\n", - " - Tracking avec MLflow pour la reproductibilité et la comparaison\n", - "\n", - "Il est important de noter que l'entraînement est fait sur un sous-échantillon et non sur l'intégralité du dataset." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "3bsdJkmgWxlK" - }, - "outputs": [], - "source": [ - "converted_target_data = convert_sentiment_label(raw_data)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 178 - }, - "executionInfo": { - "elapsed": 20, - "status": "ok", - "timestamp": 1751704288202, - "user": { - "displayName": "Grégoire", - "userId": "01249298206117462766" - }, - "user_tz": -120 - }, - "id": "xNda-mfAWxlK", - "outputId": "58ee136e-bcd5-4ad3-8b68-6a0809d5aca2" - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
count
target
0800000
1800000
\n", - "

" - ], - "text/plain": [ - "target\n", - "0 800000\n", - "1 800000\n", - "Name: count, dtype: int64" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "converted_target_data['target'].value_counts()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 178 - }, - "executionInfo": { - "elapsed": 1037, - "status": "ok", - "timestamp": 1751704289237, - "user": { - "displayName": "Grégoire", - "userId": "01249298206117462766" - }, - "user_tz": -120 - }, - "id": "q8z3nv-0WxlK", - "outputId": "845fbde3-5e3e-42bf-a523-2b92a4494eae" - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
count
target
0100000
1100000
\n", - "

" - ], - "text/plain": [ - "target\n", - "0 100000\n", - "1 100000\n", - "Name: count, dtype: int64" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "sample_size = 100000\n", - "downsampled_data = downsample_data(converted_target_data, n_samples=sample_size)\n", - "downsampled_data['target'].value_counts()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "TQAPEWmGWxlK" - }, - "source": [ - "## Prétraitement" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "executionInfo": { - "elapsed": 106518, - "status": "ok", - "timestamp": 1751704395758, - "user": { - "displayName": "Grégoire", - "userId": "01249298206117462766" - }, - "user_tz": -120 - }, - "id": "lrQfmMHMWxlL", - "outputId": "4b28e5a2-c00c-447e-9184-1df0a0eaad12" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Prétraitement des tweets en cours...\n", - "Prétraitement terminé !\n" - ] - } - ], - "source": [ - "# Appliquer le prétraitement à tous les tweets\n", - "print(\"Prétraitement des tweets en cours...\")\n", - "preprocessed_data = process_in_parallel(downsampled_data, apply_preprocessing, n_jobs=8)\n", - "print(\"Prétraitement terminé !\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 206 - }, - "executionInfo": { - "elapsed": 14, - "status": "ok", - "timestamp": 1751704395759, - "user": { - "displayName": "Grégoire", - "userId": "01249298206117462766" - }, - "user_tz": -120 - }, - "id": "NYmqSwSiWxlL", - "outputId": "3ac24116-fea7-4a3a-ddb9-647de880823d" - }, - "outputs": [ - { - "data": { - "application/vnd.google.colaboratory.intrinsic+json": { - "type": "dataframe", - "variable_name": "preprocessed_data" - }, - "text/html": [ - "\n", - "
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
targetidsdateflagusertextprocessed_text
21218801974671194Sat May 30 13:36:31 PDT 2009NO_QUERYsimba98@xnausikaax oh no! where did u order from? tha...< MENTION > oh no ! u order ? thats horrible
29903601997882236Mon Jun 01 17:37:11 PDT 2009NO_QUERYSeve76A great hard training weekend is over. a coup...great hard training weekend couple day rest le...
47597802177756662Mon Jun 15 06:39:05 PDT 2009NO_QUERYx__claireyy__xRight, off to work Only 5 hours to go until I...right work 5 hour go im free xd
58898802216838047Wed Jun 17 20:02:12 PDT 2009NO_QUERYBalasiI am craving for japanese foodcraving japanese food
13885901880666283Fri May 22 02:03:31 PDT 2009NO_QUERYdjrickdawsonJean Michel Jarre concert tomorrow gotta work...jean michel jarre concert tomorrow got ta work...
\n", - "
\n", - "
\n", - "\n", - "
\n", - " \n", - "\n", - " \n", - "\n", - " \n", - "
\n", - "\n", - "\n", - "
\n", - " \n", - "\n", - "\n", - "\n", - " \n", - "
\n", - "\n", - "
\n", - "
\n" - ], - "text/plain": [ - " target ids date flag \\\n", - "212188 0 1974671194 Sat May 30 13:36:31 PDT 2009 NO_QUERY \n", - "299036 0 1997882236 Mon Jun 01 17:37:11 PDT 2009 NO_QUERY \n", - "475978 0 2177756662 Mon Jun 15 06:39:05 PDT 2009 NO_QUERY \n", - "588988 0 2216838047 Wed Jun 17 20:02:12 PDT 2009 NO_QUERY \n", - "138859 0 1880666283 Fri May 22 02:03:31 PDT 2009 NO_QUERY \n", - "\n", - " user text \\\n", - "212188 simba98 @xnausikaax oh no! where did u order from? tha... \n", - "299036 Seve76 A great hard training weekend is over. a coup... \n", - "475978 x__claireyy__x Right, off to work Only 5 hours to go until I... \n", - "588988 Balasi I am craving for japanese food \n", - "138859 djrickdawson Jean Michel Jarre concert tomorrow gotta work... \n", - "\n", - " processed_text \n", - "212188 < MENTION > oh no ! u order ? thats horrible \n", - "299036 great hard training weekend couple day rest le... \n", - "475978 right work 5 hour go im free xd \n", - "588988 craving japanese food \n", - "138859 jean michel jarre concert tomorrow got ta work... " - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "preprocessed_data.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "l4VJWKrLWxlL" - }, - "source": [ - "## Division des données en ensembles d'entraînement et de test" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "executionInfo": { - "elapsed": 144, - "status": "ok", - "timestamp": 1751704395901, - "user": { - "displayName": "Grégoire", - "userId": "01249298206117462766" - }, - "user_tz": -120 - }, - "id": "FqmMGjWlWxlL", - "outputId": "d6665eed-5ed9-45da-8cf4-a525b6ddcd6a" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Taille de l'ensemble d'entraînement: 160000 exemples\n", - "Taille de l'ensemble de test: 40000 exemples\n" - ] - } - ], - "source": [ - "# Diviser les données en ensembles d'entraînement et de test\n", - "from sklearn.model_selection import train_test_split\n", - "\n", - "X = preprocessed_data['processed_text']\n", - "y = preprocessed_data['target']\n", - "\n", - "# Division train/test\n", - "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)\n", - "\n", - "print(f\"Taille de l'ensemble d'entraînement: {X_train.shape[0]} exemples\")\n", - "print(f\"Taille de l'ensemble de test: {X_test.shape[0]} exemples\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "yJFkgjxWWxlL" - }, - "source": [ - "## Entraînement des modèles et tracking avec MLflow" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "wKdwVenuWxlL" - }, - "source": [ - "### Configuration de MLFlow" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "executionInfo": { - "elapsed": 2919, - "status": "ok", - "timestamp": 1751704398811, - "user": { - "displayName": "Grégoire", - "userId": "01249298206117462766" - }, - "user_tz": -120 - }, - "id": "ZbDFrgXCWxlL", - "outputId": "d18cac06-f7b0-431a-f781-530361bbc947" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "MLflow Tracking URI: https://mlflow.greg-madman-nas.duckdns.org/\n", - "Identifiants AWS configurés\n" - ] - } - ], - "source": [ - "import mlflow\n", - "from mlflow.models import infer_signature\n", - "from mlflow import MlflowClient\n", - "from dotenv import load_dotenv\n", - "\n", - "# Charger les variables d'environnement depuis le fichier .env\n", - "load_dotenv(dotenv_path='../.env')\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\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 36 - }, - "executionInfo": { - "elapsed": 4, - "status": "ok", - "timestamp": 1751704398812, - "user": { - "displayName": "Grégoire", - "userId": "01249298206117462766" - }, - "user_tz": -120 - }, - "id": "7sJsURWwthEX", - "outputId": "e33c56ea-8ac5-4402-b36c-a772805c774f" - }, - "outputs": [ - { - "data": { - "application/vnd.google.colaboratory.intrinsic+json": { - "type": "string" - }, - "text/plain": [ - "'https://minio-api.greg-madman-nas.duckdns.org'" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "mlflow_s3_endpoint_url" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "executionInfo": { - "elapsed": 567, - "status": "ok", - "timestamp": 1751704399377, - "user": { - "displayName": "Grégoire", - "userId": "01249298206117462766" - }, - "user_tz": -120 - }, - "id": "vOmnl37nWxlL", - "outputId": "74b71400-c51a-48e2-e66f-04b77ba45410" - }, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Créer l'expérience MLflow\n", - "mlflow.set_experiment(\"OC Projet 7\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "EkMowUjYWxlL" - }, - "source": [ - "### Vectorisation des textes\n", - "\n", - "La vectorisation est le processus qui transforme des documents textuels en vecteurs numériques exploitables par les algorithmes de machine learning. Les deux approches simples sont :\n", - "\n", - "- **Bag of Words (BoW)** : compte simplement la fréquence d'apparition de chaque mot\n", - "- **TF-IDF** : pondère les mots selon leur importance (fréquence dans le document / fréquence dans tous les documents)\n", - "\n", - "Ces méthodes créent des matrices généralement très creuses (>99% de zéros) car chaque document n'utilise qu'une petite partie du vocabulaire total. Dans notre implémentation, nous utilisons `CountVectorizer` pour l'approche BoW et `TfidfVectorizer` pour le TF-IDF, avec des n-grammes (1,2) qui permettent de capturer des expressions de deux mots consécutifs.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "executionInfo": { - "elapsed": 14369, - "status": "ok", - "timestamp": 1751704413755, - "user": { - "displayName": "Grégoire", - "userId": "01249298206117462766" - }, - "user_tz": -120 - }, - "id": "Wn-UOIB_WxlL", - "outputId": "89b2def9-ed05-4173-cf2a-ff5b6a5280cd" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Vectorisation terminée !\n" - ] - } - ], - "source": [ - "from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer\n", - "\n", - "# Créer les vectoriseurs\n", - "tfidf_vectorizer = TfidfVectorizer(max_features=10000, ngram_range=(1, 2))\n", - "bow_vectorizer = CountVectorizer(max_features=10000, ngram_range=(1, 2))\n", - "\n", - "\n", - "# Transformer les textes en vecteurs TF-IDF\n", - "X_train_tfidf = tfidf_vectorizer.fit_transform(X_train)\n", - "X_test_tfidf = tfidf_vectorizer.transform(X_test)\n", - "\n", - "# Transformer les textes en vecteurs BoW\n", - "X_train_bow = bow_vectorizer.fit_transform(X_train)\n", - "X_test_bow = bow_vectorizer.transform(X_test)\n", - "\n", - "print(\"Vectorisation terminée !\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "idwGMj8IWxlM" - }, - "source": [ - "### Fonction d'évaluation des modèles" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "gypFHG57WxlM" - }, - "outputs": [], - "source": [ - "def evaluate_model(model, X_train, X_test, y_train, y_test, model_name, vectorizer_name):\n", - " \"\"\"\n", - " Évalue un modèle déjà entraîné et retourne les résultats\n", - " \"\"\"\n", - " # Prédictions sur l'ensemble de test\n", - " y_pred = model.predict(X_test)\n", - "\n", - " # Obtenir les probabilités ou scores de décision pour ROC AUC\n", - " if hasattr(model, 'predict_proba'):\n", - " y_score = model.predict_proba(X_test)[:, 1]\n", - " elif hasattr(model, 'decision_function'):\n", - " y_score = model.decision_function(X_test)\n", - " else:\n", - " y_score = y_pred # Fallback pour les modèles qui n'ont pas ces méthodes\n", - "\n", - " # Calculer les métriques\n", - " accuracy = accuracy_score(y_test, y_pred)\n", - " precision = precision_score(y_test, y_pred)\n", - " recall = recall_score(y_test, y_pred)\n", - " f1 = f1_score(y_test, y_pred)\n", - " roc_auc = roc_auc_score(y_test, y_score)\n", - "\n", - " # Créer la matrice de confusion sous forme de figure\n", - " cm = confusion_matrix(y_test, y_pred)\n", - " fig_cm, ax_cm = plt.subplots(figsize=(8, 6))\n", - " sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', ax=ax_cm)\n", - " ax_cm.set_xlabel('Prédiction')\n", - " ax_cm.set_ylabel('Valeur réelle')\n", - " ax_cm.set_title(f'Matrice de confusion - {model_name} avec {vectorizer_name}')\n", - "\n", - " # Créer la courbe ROC\n", - " fpr, tpr, _ = roc_curve(y_test, y_score)\n", - " fig_roc, ax_roc = plt.subplots(figsize=(8, 6))\n", - " ax_roc.plot(fpr, tpr, label=f'ROC curve (AUC = {roc_auc:.3f})')\n", - " ax_roc.plot([0, 1], [0, 1], 'k--')\n", - " ax_roc.set_xlim([0.0, 1.0])\n", - " ax_roc.set_ylim([0.0, 1.05])\n", - " ax_roc.set_xlabel('Taux de faux positifs')\n", - " ax_roc.set_ylabel('Taux de vrais positifs')\n", - " ax_roc.set_title(f'Courbe ROC - {model_name} avec {vectorizer_name}')\n", - " ax_roc.legend(loc=\"lower right\")\n", - " ax_roc.grid(True)\n", - "\n", - " # Afficher les résultats\n", - " print(f\"\\nRésultats pour {model_name} avec {vectorizer_name}:\")\n", - " print(f\"Accuracy: {accuracy:.4f}\")\n", - " print(f\"Precision: {precision:.4f}\")\n", - " print(f\"Recall: {recall:.4f}\")\n", - " print(f\"F1 Score: {f1:.4f}\")\n", - " print(f\"ROC AUC: {roc_auc:.4f}\")\n", - " print(\"\\nMatrice de confusion:\")\n", - " print(cm)\n", - " print(\"\\nRapport de classification:\")\n", - " print(classification_report(y_test, y_pred))\n", - "\n", - " return accuracy, precision, recall, f1, roc_auc, fig_cm, fig_roc, y_pred" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "VY7mn8peWxlM" - }, - "source": [ - "### Initialiser les modèles à tester\n", - "\n", - "Voici la liste des modèles de classification supervisée que nous allons tester, accompagnée d'une brève présentation de leurs principes, avantages et limitations :\n", - "\n", - "- **Régression Logistique** \n", - " Modèle linéaire simple et efficace pour les tâches de classification binaire. Il modélise la probabilité d'appartenance à une classe. \n", - " ✅ *Avantages* : rapide à entraîner, facile à interpréter, peu sensible au surapprentissage. \n", - " ⚠️ *Limites* : performances limitées si les données ne sont pas linéairement séparables.\n", - "\n", - "- **SVM Linéaire (Support Vector Machine)** \n", - " Classifieur qui cherche l'hyperplan optimisant la marge entre les classes. Très adapté aux représentations vectorielles comme TF-IDF. \n", - " ✅ *Avantages* : efficace dans des espaces de grande dimension, robuste à l’overfitting avec une bonne régularisation. \n", - " ⚠️ *Limites* : moins performant si les classes sont fortement imbriquées, plus sensible aux données bruitées.\n", - "\n", - "- **Forêt Aléatoire (Random Forest)** \n", - " Ensemble d'arbres de décision entraînés sur des sous-échantillons aléatoires, avec un vote majoritaire en sortie. \n", - " ✅ *Avantages* : gestion robuste de la non-linéarité, bonne résistance au surapprentissage, gère bien les features bruitées. \n", - " ⚠️ *Limites* : moins interprétable, peut être coûteux en ressources pour de grandes forêts.\n", - "\n", - "- **Naive Bayes (Multinomial)** \n", - " Classifieur probabiliste basé sur le théorème de Bayes, avec l'hypothèse d’indépendance conditionnelle entre les features. \n", - " ✅ *Avantages* : extrêmement rapide, très efficace sur des textes représentés par BoW ou TF-IDF, souvent difficile à battre en NLP classique. \n", - " ⚠️ *Limites* : hypothèse d’indépendance souvent irréaliste, mais dans la pratique les performances restent bonnes même si elle est violée.\n", - "\n", - "> 🧠 **Pourquoi ces modèles ?** \n", - "Ils sont tous adaptés aux représentations vectorielles classiques (BoW, TF-IDF), et offrent un bon compromis entre performance, vitesse d'entraînement et interprétabilité. Leur diversité permet aussi d'évaluer plusieurs familles d’algorithmes (linéaires, probabilistes, ensembles, marge maximale) sur le même jeu de données.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "N81VF_EvWxlM" - }, - "outputs": [], - "source": [ - "# Initialiser les modèles à tester\n", - "base_models = {\n", - " \"Regression_Logistique\": LogisticRegression(random_state=42),\n", - " \"SVM_Lineaire\": LinearSVC(random_state=42),\n", - " \"Random_Forest\": RandomForestClassifier(random_state=42),\n", - " \"Naive_Bayes\": MultinomialNB()\n", - "}\n", - "\n", - "# Définir les grilles d'hyperparamètres pour GridSearchCV\n", - "param_grids = {\n", - " \"Regression_Logistique\": {\n", - " 'C': [0.01, 0.1, 1.0, 10.0],\n", - " 'max_iter': [1000],\n", - " 'solver': ['liblinear', 'saga']\n", - " },\n", - " \"SVM_Lineaire\": {\n", - " 'C': [0.01, 0.1, 1.0, 10.0],\n", - " 'max_iter': [1000],\n", - " 'dual': [True, False]\n", - " },\n", - " \"Random_Forest\": {\n", - " 'n_estimators': [50, 100, 200],\n", - " 'max_depth': [None, 10, 20],\n", - " 'min_samples_split': [2, 5]\n", - " },\n", - " \"Naive_Bayes\": {\n", - " 'alpha': [0.1, 0.5, 1.0, 2.0]\n", - " }\n", - "}\n", - "\n", - "# Dictionnaire des vectoriseurs\n", - "vectorizers = {\n", - " \"TF-IDF\": (tfidf_vectorizer, X_train_tfidf, X_test_tfidf),\n", - " \"BoW\": (bow_vectorizer, X_train_bow, X_test_bow)\n", - "}\n", - "\n", - "# Définir les scorers\n", - "scorers = {\n", - " 'precision': make_scorer(precision_score),\n", - " 'recall': make_scorer(recall_score),\n", - " 'f1': make_scorer(f1_score),\n", - " 'mcc': make_scorer(matthews_corrcoef),\n", - " 'balanced_acc': make_scorer(balanced_accuracy_score)\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "nAEoLNGmWxlM" - }, - "source": [ - "## Entrainement des modèles" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "yOKPz7roWxlM" - }, - "outputs": [], - "source": [ - "train_models = True" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 1000 - }, - "executionInfo": { - "elapsed": 35588700, - "status": "ok", - "timestamp": 1751740002532, - "user": { - "displayName": "Grégoire", - "userId": "01249298206117462766" - }, - "user_tz": -120 - }, - "id": "zT0mYkAxWxlM", - "outputId": "32e420f3-7a21-4d06-ad43-7d2b1a3c5c8f" - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\r", - "Progression globale: 0%| | 0/8 [00:00" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "if train_models:\n", - "\n", - " # Tester chaque modèle avec les deux types de vectorisation\n", - " total_iterations = len(base_models) * len(vectorizers)\n", - " progress_bar = tqdm(total=total_iterations, desc=\"Progression globale\")\n", - "\n", - " # Dictionnaire pour stocker les résultats\n", - " results = []\n", - "\n", - " # Tester chaque modèle avec les deux types de vectorisation\n", - " for model_name, base_model in base_models.items():\n", - " for vectorizer_name, (vectorizer, X_train_vec, X_test_vec) in vectorizers.items():\n", - " print(f\"\\n{'='*80}\")\n", - " print(f\"Démarrage de GridSearchCV pour {model_name} avec {vectorizer_name}...\")\n", - "\n", - " # Définir la grille de paramètres et créer GridSearchCV\n", - " param_grid = param_grids[model_name]\n", - "\n", - " grid_search = GridSearchCV(\n", - " base_model,\n", - " param_grid,\n", - " cv=5,\n", - " scoring=scorers,\n", - " refit='precision',\n", - " n_jobs=-1,\n", - " verbose=1,\n", - " return_train_score=True\n", - " )\n", - "\n", - " # Démarrer le run MLflow\n", - " with mlflow.start_run(run_name=f\"Modele_Simple_{model_name}_{vectorizer_name}\"):\n", - " # Journaliser les paramètres généraux\n", - " mlflow.log_param(\"model_type\", model_name)\n", - " mlflow.log_param(\"vectorizer_type\", vectorizer_name)\n", - " mlflow.log_param(\"dataset_size\", X_train.shape[0] + X_test.shape[0])\n", - " mlflow.log_param(\"train_size\", X_train.shape[0])\n", - " mlflow.log_param(\"test_size\", X_test.shape[0])\n", - " mlflow.log_param(\"max_features\", 10000)\n", - " mlflow.log_param(\"ngram_range\", \"(1, 2)\")\n", - " mlflow.log_param(\"scoring_metric\", \"precision\") # Noter que precision est utilisé\n", - "\n", - " # Mesurer le temps d'entraînement\n", - " start_time = time.time()\n", - "\n", - " # Entraîner avec GridSearchCV\n", - " grid_search.fit(X_train_vec, y_train)\n", - "\n", - " # Calculer le temps d'entraînement\n", - " training_time = time.time() - start_time\n", - "\n", - " # Journaliser le temps d'entraînement\n", - " mlflow.log_metric(\"training_time\", training_time)\n", - "\n", - " # Récupérer et journaliser les meilleurs paramètres\n", - " best_params = grid_search.best_params_\n", - " for param, value in best_params.items():\n", - " mlflow.log_param(f\"best_{param}\", value)\n", - "\n", - " mlflow.log_param(\"sample_size\", sample_size*2)\n", - "\n", - " # Journaliser le meilleur score de validation croisée\n", - " mlflow.log_metric(\"val_precision\", grid_search.best_score_)\n", - "\n", - " # Journaliser le recall score associé au modèle présentant la meilleure précision\n", - " val_recall = grid_search.cv_results_['mean_test_recall'][grid_search.best_index_]\n", - " mlflow.log_metric(\"val_recall\", val_recall)\n", - "\n", - "\n", - " # Récupérer le meilleur modèle\n", - " best_model = grid_search.best_estimator_\n", - "\n", - " # Évaluer le meilleur modèle sur l'ensemble de test\n", - " acc, prec, rec, f1, roc_auc, fig_cm, fig_roc, y_pred = evaluate_model(\n", - " best_model, X_train_vec, X_test_vec, y_train, y_test, model_name, vectorizer_name\n", - " )\n", - "\n", - " # Journaliser les métriques sur l'ensemble de test\n", - " mlflow.log_metric(\"test_accuracy\", acc)\n", - " mlflow.log_metric(\"test_precision\", prec)\n", - " mlflow.log_metric(\"test_recall\", rec)\n", - " mlflow.log_metric(\"test_f1\", f1)\n", - " mlflow.log_metric(\"test_roc_auc\", roc_auc)\n", - "\n", - " # Journaliser les figures\n", - " mlflow.log_figure(fig_cm, \"confusion_matrix.png\")\n", - " mlflow.log_figure(fig_roc, \"roc_curve.png\")\n", - " plt.close(fig_cm)\n", - " plt.close(fig_roc)\n", - "\n", - " # Journaliser le modèle\n", - " signature = infer_signature(X_train_vec, y_pred)\n", - " mlflow.sklearn.log_model(best_model, \"model\", signature=signature)\n", - "\n", - " # Créer le dossier pour sauvegarder les artefacts\n", - " os.makedirs(\"./content/simple-model\", exist_ok=True)\n", - "\n", - " # Sauvegarder et journaliser le vectoriseur\n", - " vectorizer_path = f\"./content/simple-model/vectorizer_{vectorizer_name}.pkl\"\n", - " with open(vectorizer_path, \"wb\") as f:\n", - " pickle.dump(vectorizer, f)\n", - " mlflow.log_artifact(vectorizer_path)\n", - "\n", - " # Journaliser les résultats détaillés de GridSearchCV\n", - " cv_results = pd.DataFrame(grid_search.cv_results_)\n", - " cv_results_path = \"./content/simple-model/cv_results.csv\"\n", - " cv_results.to_csv(cv_results_path, index=False)\n", - " mlflow.log_artifact(cv_results_path)\n", - "\n", - " # Créer et journaliser un graphique des résultats de GridSearchCV\n", - " plt.figure(figsize=(12, 8))\n", - " params = [f\"{k}={v}\" for k, v in best_params.items()]\n", - " params_str = \", \".join(params)\n", - "\n", - " # Extraire les scores moyens pour chaque paramètre principal\n", - " for param in param_grid.keys():\n", - " if len(param_grid[param]) > 1: # Seulement si le paramètre a plusieurs valeurs\n", - " param_name = f\"param_{param}\"\n", - " if param_name in cv_results.columns:\n", - " # Utiliser la colonne spécifique à la métrique precision\n", - " scores_df = cv_results[[param_name, \"mean_test_precision\", \"std_test_precision\"]]\n", - " scores_df = scores_df.sort_values(param_name)\n", - "\n", - " plt.figure(figsize=(10, 6))\n", - " plt.errorbar(\n", - " scores_df[param_name].astype(str),\n", - " scores_df[\"mean_test_precision\"],\n", - " yerr=scores_df[\"std_test_precision\"],\n", - " fmt='-o'\n", - " )\n", - " plt.title(f'Scores de validation croisée (Precision) par {param}')\n", - " plt.xlabel(param)\n", - " plt.ylabel('Precision moyenne')\n", - " plt.grid(True)\n", - " mlflow.log_figure(plt.gcf(), f\"cv_results_{param}.png\")\n", - " plt.close()\n", - "\n", - " # Journaliser les features importantes (pour les modèles qui le supportent)\n", - " if hasattr(best_model, 'coef_'):\n", - " # Récupérer les features les plus importantes\n", - " if isinstance(best_model, LogisticRegression) or isinstance(best_model, LinearSVC):\n", - " coefs = best_model.coef_[0]\n", - " if vectorizer_name == \"TF-IDF\":\n", - " feature_names = tfidf_vectorizer.get_feature_names_out()\n", - " else:\n", - " feature_names = bow_vectorizer.get_feature_names_out()\n", - "\n", - " # Créer un DataFrame avec les coefs et les noms des features\n", - " coefs_df = pd.DataFrame({\n", - " 'feature': feature_names,\n", - " 'importance': coefs\n", - " })\n", - "\n", - " # Trier par importance absolue\n", - " coefs_df['abs_importance'] = abs(coefs_df['importance'])\n", - " coefs_df = coefs_df.sort_values('abs_importance', ascending=False).head(20)\n", - "\n", - " # Journaliser les features les plus importantes\n", - " top_features_path = \"./content/simple-model/top_features.csv\"\n", - " coefs_df.to_csv(top_features_path, index=False)\n", - " mlflow.log_artifact(top_features_path)\n", - "\n", - " # Créer un graphique pour visualiser les features les plus importantes\n", - " plt.figure(figsize=(10, 8))\n", - " sns.barplot(x='importance', y='feature', data=coefs_df.sort_values('importance', ascending=False).head(20))\n", - " plt.title(f'Top 20 features positives')\n", - " plt.tight_layout()\n", - " mlflow.log_figure(plt.gcf(), \"top_positive_features.png\")\n", - " plt.close()\n", - "\n", - " plt.figure(figsize=(10, 8))\n", - " sns.barplot(x='importance', y='feature', data=coefs_df.sort_values('importance').head(20))\n", - " plt.title(f'Top 20 features négatives')\n", - " plt.tight_layout()\n", - " mlflow.log_figure(plt.gcf(), \"top_negative_features.png\")\n", - " plt.close()\n", - "\n", - " # Stocker les résultats pour comparaison\n", - " results.append({\n", - " \"Modèle\": model_name.replace(\"_\", \" \"),\n", - " \"Vectorisation\": vectorizer_name,\n", - " \"Meilleurs paramètres\": str(best_params),\n", - " \"Accuracy\": acc,\n", - " \"Precision\": prec,\n", - " \"Recall\": rec,\n", - " \"F1 Score\": f1,\n", - " \"ROC AUC\": roc_auc,\n", - " \"Temps d'entraînement (s)\": training_time\n", - " })\n", - "\n", - " # Afficher les résultats\n", - " print(f\"Meilleurs paramètres: {best_params}\")\n", - " print(f\"Meilleur score CV (Precision): {grid_search.best_score_:.4f}\")\n", - " print(f\"F1 Score sur test: {f1:.4f}\")\n", - " print(f\"ROC AUC sur test: {roc_auc:.4f}\")\n", - " print(f\"Temps d'entraînement: {training_time:.2f} secondes\")\n", - "\n", - "\n", - " # À la fin de chaque itération, mettez à jour la barre de progression\n", - " progress_bar.update(1)\n", - " progress_bar.set_description(f\"Dernier modèle: {model_name} avec {vectorizer_name}\")\n", - "\n", - " progress_bar.close()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "MPL9yrErWxlN" - }, - "source": [ - "## Evaluation des performances" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "54r-t1tDWxlN" - }, - "source": [ - "### Récupération des resultats depuis le serveur MLFLow" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "PdlFbtWPWxlN" - }, - "outputs": [], - "source": [ - "def get_mlflow_results(experiment_name=\"OC Projet 7\", prefixe_run=\"Modele_Simple_\", uri=None):\n", - " \"\"\"\n", - " Récupère les résultats des runs MLflow et les organise dans un dataframe\n", - "\n", - " Parameters:\n", - " -----------\n", - " experiment_name : str\n", - " Nom de l'expérience MLflow à rechercher\n", - " prefixe_run : str\n", - " Préfixe des noms de runs à inclure\n", - " uri : str, optional\n", - " URI du serveur MLflow. Si None, utilise l'URI configuré par défaut\n", - "\n", - " Returns:\n", - " --------\n", - " pandas.DataFrame\n", - " Dataframe contenant les résultats des runs MLflow\n", - " \"\"\"\n", - " # Se connecter au serveur MLflow si un URI est fourni\n", - " if uri:\n", - " mlflow.set_tracking_uri(uri)\n", - "\n", - " # Créer un client MLflow\n", - " client = MlflowClient()\n", - "\n", - " # Récupérer l'ID de l'expérience\n", - " try:\n", - " experiment = client.get_experiment_by_name(experiment_name)\n", - " if experiment is None:\n", - " raise ValueError(f\"L'expérience '{experiment_name}' n'existe pas.\")\n", - " experiment_id = experiment.experiment_id\n", - " except Exception as e:\n", - " print(f\"Erreur lors de la récupération de l'expérience : {e}\")\n", - " return pd.DataFrame()\n", - "\n", - " # Récupérer tous les runs de l'expérience\n", - " runs = client.search_runs(\n", - " experiment_ids=[experiment_id],\n", - " filter_string=f\"tags.mlflow.runName LIKE '{prefixe_run}%'\",\n", - " order_by=[\"metrics.prec DESC\"]\n", - " )\n", - "\n", - " # Créer une liste pour stocker les résultats\n", - " resultats = []\n", - "\n", - " # Parcourir chaque run et extraire les informations\n", - " for run in runs:\n", - " # Récupérer le nom du run pour information\n", - " run_name = run.data.tags.get(\"mlflow.runName\", \"\")\n", - "\n", - " # Utiliser les paramètres model_type et vectorizer_type de MLflow\n", - " modele = run.data.params.get(\"model_type\", \"Inconnu\")\n", - " vectorisation = run.data.params.get(\"vectorizer_type\", \"Inconnu\")\n", - "\n", - " # Formater le nom du modèle (remplacer les underscores par des espaces)\n", - " modele = modele.replace(\"_\", \" \")\n", - "\n", - " # Extraire les métriques\n", - " metrics = run.data.metrics\n", - " params = run.data.params\n", - "\n", - " # Construire un dictionnaire des meilleurs paramètres\n", - " best_params = {}\n", - " for key, value in params.items():\n", - " if key.startswith(\"best_\"):\n", - " param_name = key.replace(\"best_\", \"\")\n", - " best_params[param_name] = value\n", - "\n", - " # Créer une entrée pour le dataframe résultat\n", - " resultats.append({\n", - " \"Modèle\": modele,\n", - " \"Vectorisation\": vectorisation,\n", - " \"Meilleurs paramètres\": str(best_params),\n", - " \"Accuracy\": metrics.get(\"test_accuracy\", None),\n", - " \"Precision\": metrics.get(\"test_precision\", None),\n", - " \"Recall\": metrics.get(\"test_recall\", None),\n", - " \"F1 Score\": metrics.get(\"test_f1\", None),\n", - " \"ROC AUC\": metrics.get(\"test_roc_auc\", None),\n", - " \"Temps d'entraînement (s)\": metrics.get(\"training_time\", None),\n", - " \"Run ID\": run.info.run_id,\n", - " \"Run Name\": run_name\n", - " })\n", - "\n", - " # Créer un dataframe à partir des résultats\n", - " df_resultats = pd.DataFrame(resultats)\n", - "\n", - " # Trier le dataframe par Précision (décroissant)\n", - " if not df_resultats.empty and \"Precision\" in df_resultats.columns:\n", - " df_resultats = df_resultats.sort_values(\"Precision\", ascending=False)\n", - "\n", - " return df_resultats" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "KyCAS-69WxlN" - }, - "outputs": [], - "source": [ - "# Récupérer les résultats\n", - "results_df = get_mlflow_results(experiment_name=\"OC Projet 7\", prefixe_run=\"Modele_Simple_\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "sOFocHNnWxlN" - }, - "source": [ - "### Sauvegarde des résultats\n", - "\n", - "- Au format Markdown (.md)\n", - "- Au format CSV (.csv)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "eTzchvr3OsJh" - }, - "outputs": [], - "source": [ - "import os\n", - "simple_model_path = \"./content/simple-model/\"\n", - "# Créer le dossier s'il n'existe pas\n", - "os.makedirs(simple_model_path, exist_ok=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "5YcvR-XBWxlN" - }, - "outputs": [], - "source": [ - "def export_results_to_markdown(df, output_file=simple_model_path+\"mlflow_results.md\"):\n", - " with open(output_file, \"w\") as f:\n", - " f.write(\"# Résultats des expérimentations MLflow\\n\\n\")\n", - " f.write(df.to_markdown(index=False))\n", - "\n", - "# Exporter les résultats au format Markdown\n", - "export_results_to_markdown(results_df)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "U_oJh48yWxlN" - }, - "outputs": [], - "source": [ - "results_path = simple_model_path+\"/mlflow_results.csv\"\n", - "# Enregistrer le tableau des résultats (optionnel)\n", - "results_df.to_csv(results_path, index=False)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "9-c659sYWxlP" - }, - "source": [ - "### Visualisation des métriques" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 776 - }, - "executionInfo": { - "elapsed": 139, - "status": "ok", - "timestamp": 1751740004089, - "user": { - "displayName": "Grégoire", - "userId": "01249298206117462766" - }, - "user_tz": -120 - }, - "id": "3KiynT2KWxlQ", - "outputId": "aa429d2c-a1fd-462f-d247-570c6cfc6b46" - }, - "outputs": [ - { - "data": { - "application/vnd.google.colaboratory.intrinsic+json": { - "summary": "{\n \"name\": \"results_df\",\n \"rows\": 16,\n \"fields\": [\n {\n \"column\": \"Mod\\u00e8le\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 4,\n \"samples\": [\n \"Regression Logistique\",\n \"SVM Lineaire\",\n \"Random Forest\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Vectorisation\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 2,\n \"samples\": [\n \"TF-IDF\",\n \"BoW\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Meilleurs param\\u00e8tres\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 8,\n \"samples\": [\n \"{'C': '1.0', 'max_iter': '1000', 'solver': 'liblinear'}\",\n \"{'C': '0.1', 'max_iter': '1000', 'solver': 'liblinear'}\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Accuracy\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.007192113012413891,\n \"min\": 0.7648,\n \"max\": 0.7854,\n \"num_unique_values\": 16,\n \"samples\": [\n 0.771925,\n 0.785225\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Precision\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.005252241847122117,\n \"min\": 0.7635874975114474,\n \"max\": 0.7798014096825642,\n \"num_unique_values\": 16,\n \"samples\": [\n 0.7798014096825642,\n 0.7795912365828555\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Recall\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.021208419859024552,\n \"min\": 0.7568,\n \"max\": 0.8074,\n \"num_unique_values\": 15,\n \"samples\": [\n 0.7584,\n 0.7625\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"F1 Score\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.009987107577157433,\n \"min\": 0.7653397186471116,\n \"max\": 0.788185362483344,\n \"num_unique_values\": 16,\n \"samples\": [\n 0.7686690164059132,\n 0.7873672747073237\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"ROC AUC\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.008073892898588831,\n \"min\": 0.8416485,\n \"max\": 0.86671001875,\n \"num_unique_values\": 16,\n \"samples\": [\n 0.85152670125,\n 0.86671001875\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Temps d'entra\\u00eenement (s)\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 5773.421748128435,\n \"min\": 1.4726371765136719,\n \"max\": 17489.656234502792,\n \"num_unique_values\": 16,\n \"samples\": [\n 17489.656234502792,\n 65.19242358207703\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Run ID\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 16,\n \"samples\": [\n \"7d52e808227f468da7f733265280fc02\",\n \"94a0cf29a68b402ab37098a44d1c1ee6\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Run Name\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 8,\n \"samples\": [\n \"Modele_Simple_Regression_Logistique_TF-IDF\",\n \"Modele_Simple_Regression_Logistique_BoW\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}", - "type": "dataframe", - "variable_name": "results_df" - }, - "text/html": [ - "\n", - "
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
ModèleVectorisationMeilleurs paramètresAccuracyPrecisionRecallF1 ScoreROC AUCTemps d'entraînement (s)Run IDRun Name
2Random ForestBoW{'max_depth': 'None', 'min_samples_split': '5'...0.7719250.7798010.757850.7686690.85152717489.6562357d52e808227f468da7f733265280fc02Modele_Simple_Random_Forest_BoW
7Regression LogistiqueTF-IDF{'C': '1.0', 'max_iter': '1000', 'solver': 'li...0.7852250.7795910.795300.7873670.86671065.19242494a0cf29a68b402ab37098a44d1c1ee6Modele_Simple_Regression_Logistique_TF-IDF
0Naive BayesBoW{'alpha': '2.0'}0.7720750.7785230.760500.7694060.8441322.815040a920f241d90d4816b858b16d68a6b38fModele_Simple_Naive_Bayes_BoW
5SVM LineaireTF-IDF{'C': '0.1', 'dual': 'True', 'max_iter': '1000'}0.7854000.7780860.798550.7881850.866477122.321674767fe1513d6445029ca90ca2156ab653Modele_Simple_SVM_Lineaire_TF-IDF
9Naive BayesTF-IDF{'alpha': '2.0'}0.7704000.7776180.757400.7673760.8510674.320478daf72838a04d4f2f962d5b7cf6e41a82Modele_Simple_Naive_Bayes_TF-IDF
1Naive BayesTF-IDF{'alpha': '2.0'}0.7695750.7766430.756800.7665930.8524434.272107f0f4a385cb0b463c97ad736eaa98ce9fModele_Simple_Naive_Bayes_TF-IDF
15Regression LogistiqueTF-IDF{'C': '1.0', 'max_iter': '1000', 'solver': 'sa...0.7831500.7765950.795000.7856900.86109836.014652741178bfcba54908aaa50c4f44d6db71Modele_Simple_Regression_Logistique_TF-IDF
8Naive BayesBoW{'alpha': '2.0'}0.7718000.7765570.763200.7698200.8431681.4726379635b8567b874dd6bc91da23d1cb3b78Modele_Simple_Naive_Bayes_BoW
13SVM LineaireTF-IDF{'C': '0.1', 'dual': 'True', 'max_iter': '1000'}0.7842000.7765130.798100.7871580.86124951.149676bb39934ff22f4021a379983c32d828edModele_Simple_SVM_Lineaire_TF-IDF
10Random ForestBoW{'max_depth': 'None', 'min_samples_split': '5'...0.7688500.7745890.758400.7664090.8439506377.436032623f3412438b4b6a8bb0d112c917dceaModele_Simple_Random_Forest_BoW
6Regression LogistiqueBoW{'C': '0.1', 'max_iter': '1000', 'solver': 'li...0.7829250.7722920.802450.7870820.858402896.35378275396e8531354bd5b168f8dd7e6f4135Modele_Simple_Regression_Logistique_BoW
3Random ForestTF-IDF{'max_depth': 'None', 'min_samples_split': '5'...0.7675000.7702020.762500.7663320.84840416182.0807447dda37625478423abcfd9292dac05580Modele_Simple_Random_Forest_TF-IDF
4SVM LineaireBoW{'C': '0.01', 'dual': 'True', 'max_iter': '1000'}0.7822250.7686960.807400.7875730.858312458.623438eaa8dfac819b44e39fe7b7baaf490de7Modele_Simple_SVM_Lineaire_BoW
14Regression LogistiqueBoW{'C': '0.1', 'max_iter': '1000', 'solver': 'sa...0.7792500.7671990.801800.7841180.851395371.537341b88c00415c1340ec9dd4e6db806f2de3Modele_Simple_Regression_Logistique_BoW
12SVM LineaireBoW{'C': '0.01', 'dual': 'True', 'max_iter': '1000'}0.7800500.7655260.807400.7859050.851585187.8074795c07f55dbafc4c899c5c2a9c3de4a742Modele_Simple_SVM_Lineaire_BoW
11Random ForestTF-IDF{'max_depth': 'None', 'min_samples_split': '5'...0.7648000.7635870.767100.7653400.8416495977.548638cb84d2c0f188472990425e7300f744efModele_Simple_Random_Forest_TF-IDF
\n", - "
\n", - "
\n", - "\n", - "
\n", - " \n", - "\n", - " \n", - "\n", - " \n", - "
\n", - "\n", - "\n", - "
\n", - " \n", - "\n", - "\n", - "\n", - " \n", - "
\n", - "\n", - "
\n", - " \n", - " \n", - " \n", - "
\n", - "\n", - "
\n", - "
\n" - ], - "text/plain": [ - " Modèle Vectorisation \\\n", - "2 Random Forest BoW \n", - "7 Regression Logistique TF-IDF \n", - "0 Naive Bayes BoW \n", - "5 SVM Lineaire TF-IDF \n", - "9 Naive Bayes TF-IDF \n", - "1 Naive Bayes TF-IDF \n", - "15 Regression Logistique TF-IDF \n", - "8 Naive Bayes BoW \n", - "13 SVM Lineaire TF-IDF \n", - "10 Random Forest BoW \n", - "6 Regression Logistique BoW \n", - "3 Random Forest TF-IDF \n", - "4 SVM Lineaire BoW \n", - "14 Regression Logistique BoW \n", - "12 SVM Lineaire BoW \n", - "11 Random Forest TF-IDF \n", - "\n", - " Meilleurs paramètres Accuracy Precision \\\n", - "2 {'max_depth': 'None', 'min_samples_split': '5'... 0.771925 0.779801 \n", - "7 {'C': '1.0', 'max_iter': '1000', 'solver': 'li... 0.785225 0.779591 \n", - "0 {'alpha': '2.0'} 0.772075 0.778523 \n", - "5 {'C': '0.1', 'dual': 'True', 'max_iter': '1000'} 0.785400 0.778086 \n", - "9 {'alpha': '2.0'} 0.770400 0.777618 \n", - "1 {'alpha': '2.0'} 0.769575 0.776643 \n", - "15 {'C': '1.0', 'max_iter': '1000', 'solver': 'sa... 0.783150 0.776595 \n", - "8 {'alpha': '2.0'} 0.771800 0.776557 \n", - "13 {'C': '0.1', 'dual': 'True', 'max_iter': '1000'} 0.784200 0.776513 \n", - "10 {'max_depth': 'None', 'min_samples_split': '5'... 0.768850 0.774589 \n", - "6 {'C': '0.1', 'max_iter': '1000', 'solver': 'li... 0.782925 0.772292 \n", - "3 {'max_depth': 'None', 'min_samples_split': '5'... 0.767500 0.770202 \n", - "4 {'C': '0.01', 'dual': 'True', 'max_iter': '1000'} 0.782225 0.768696 \n", - "14 {'C': '0.1', 'max_iter': '1000', 'solver': 'sa... 0.779250 0.767199 \n", - "12 {'C': '0.01', 'dual': 'True', 'max_iter': '1000'} 0.780050 0.765526 \n", - "11 {'max_depth': 'None', 'min_samples_split': '5'... 0.764800 0.763587 \n", - "\n", - " Recall F1 Score ROC AUC Temps d'entraînement (s) \\\n", - "2 0.75785 0.768669 0.851527 17489.656235 \n", - "7 0.79530 0.787367 0.866710 65.192424 \n", - "0 0.76050 0.769406 0.844132 2.815040 \n", - "5 0.79855 0.788185 0.866477 122.321674 \n", - "9 0.75740 0.767376 0.851067 4.320478 \n", - "1 0.75680 0.766593 0.852443 4.272107 \n", - "15 0.79500 0.785690 0.861098 36.014652 \n", - "8 0.76320 0.769820 0.843168 1.472637 \n", - "13 0.79810 0.787158 0.861249 51.149676 \n", - "10 0.75840 0.766409 0.843950 6377.436032 \n", - "6 0.80245 0.787082 0.858402 896.353782 \n", - "3 0.76250 0.766332 0.848404 16182.080744 \n", - "4 0.80740 0.787573 0.858312 458.623438 \n", - "14 0.80180 0.784118 0.851395 371.537341 \n", - "12 0.80740 0.785905 0.851585 187.807479 \n", - "11 0.76710 0.765340 0.841649 5977.548638 \n", - "\n", - " Run ID \\\n", - "2 7d52e808227f468da7f733265280fc02 \n", - "7 94a0cf29a68b402ab37098a44d1c1ee6 \n", - "0 a920f241d90d4816b858b16d68a6b38f \n", - "5 767fe1513d6445029ca90ca2156ab653 \n", - "9 daf72838a04d4f2f962d5b7cf6e41a82 \n", - "1 f0f4a385cb0b463c97ad736eaa98ce9f \n", - "15 741178bfcba54908aaa50c4f44d6db71 \n", - "8 9635b8567b874dd6bc91da23d1cb3b78 \n", - "13 bb39934ff22f4021a379983c32d828ed \n", - "10 623f3412438b4b6a8bb0d112c917dcea \n", - "6 75396e8531354bd5b168f8dd7e6f4135 \n", - "3 7dda37625478423abcfd9292dac05580 \n", - "4 eaa8dfac819b44e39fe7b7baaf490de7 \n", - "14 b88c00415c1340ec9dd4e6db806f2de3 \n", - "12 5c07f55dbafc4c899c5c2a9c3de4a742 \n", - "11 cb84d2c0f188472990425e7300f744ef \n", - "\n", - " Run Name \n", - "2 Modele_Simple_Random_Forest_BoW \n", - "7 Modele_Simple_Regression_Logistique_TF-IDF \n", - "0 Modele_Simple_Naive_Bayes_BoW \n", - "5 Modele_Simple_SVM_Lineaire_TF-IDF \n", - "9 Modele_Simple_Naive_Bayes_TF-IDF \n", - "1 Modele_Simple_Naive_Bayes_TF-IDF \n", - "15 Modele_Simple_Regression_Logistique_TF-IDF \n", - "8 Modele_Simple_Naive_Bayes_BoW \n", - "13 Modele_Simple_SVM_Lineaire_TF-IDF \n", - "10 Modele_Simple_Random_Forest_BoW \n", - "6 Modele_Simple_Regression_Logistique_BoW \n", - "3 Modele_Simple_Random_Forest_TF-IDF \n", - "4 Modele_Simple_SVM_Lineaire_BoW \n", - "14 Modele_Simple_Regression_Logistique_BoW \n", - "12 Modele_Simple_SVM_Lineaire_BoW \n", - "11 Modele_Simple_Random_Forest_TF-IDF " - ] - }, - "execution_count": 27, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "results_df" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "w0WL6NSxWxlQ" - }, - "outputs": [], - "source": [ - "def afficher_tableau_comparatif(df):\n", - " \"\"\"\n", - " Affiche un tableau comparatif formaté des résultats\n", - "\n", - " Parameters:\n", - " -----------\n", - " df : pandas.DataFrame\n", - " Dataframe contenant les résultats à afficher\n", - " \"\"\"\n", - " if df.empty:\n", - " print(\"Aucun résultat à afficher.\")\n", - " return\n", - "\n", - " # Sélectionner et formater les colonnes pour l'affichage\n", - " df_display = df[[\"Modèle\", \"Vectorisation\", \"Accuracy\", \"Precision\", \"Recall\",\n", - " \"F1 Score\", \"ROC AUC\", \"Temps d'entraînement (s)\"]]\n", - "\n", - " # Formater les valeurs numériques\n", - " for col in [\"Accuracy\", \"Precision\", \"Recall\", \"F1 Score\", \"ROC AUC\"]:\n", - " if col in df_display.columns:\n", - " df_display[col] = df_display[col].apply(lambda x: f\"{x:.4f}\" if x is not None else \"N/A\")\n", - "\n", - " if \"Temps d'entraînement (s)\" in df_display.columns:\n", - " df_display[\"Temps d'entraînement (s)\"] = df_display[\"Temps d'entraînement (s)\"].apply(\n", - " lambda x: f\"{x:.2f}\" if x is not None else \"N/A\")\n", - "\n", - " # Afficher le tableau\n", - " print(\"\\n=== Comparaison des modèles ===\\n\")\n", - " print(df_display.to_string(index=False))\n", - "\n", - " # Identifier le meilleur modèle selon la précision\n", - " if \"Precision\" in df.columns:\n", - " best_idx = df[\"Precision\"].idxmax() if not df.empty else None\n", - " if best_idx is not None:\n", - " best_model = df.loc[best_idx]\n", - " print(\"\\n=== Meilleur modèle ===\")\n", - " print(f\"Modèle: {best_model['Modèle']}\")\n", - " print(f\"Vectorisation: {best_model['Vectorisation']}\")\n", - " print(f\"Precision: {best_model['Precision']:.4f}\")\n", - " print(f\"Meilleurs paramètres: {best_model['Meilleurs paramètres']}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "executionInfo": { - "elapsed": 8, - "status": "ok", - "timestamp": 1751740004112, - "user": { - "displayName": "Grégoire", - "userId": "01249298206117462766" - }, - "user_tz": -120 - }, - "id": "Ou07_WmDWxlQ", - "outputId": "4f3f00f7-3000-47f0-f2e1-ad34d27716d1" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "=== Comparaison des modèles ===\n", - "\n", - " Modèle Vectorisation Accuracy Precision Recall F1 Score ROC AUC Temps d'entraînement (s)\n", - " Random Forest BoW 0.7719 0.7798 0.7579 0.7687 0.8515 17489.66\n", - "Regression Logistique TF-IDF 0.7852 0.7796 0.7953 0.7874 0.8667 65.19\n", - " Naive Bayes BoW 0.7721 0.7785 0.7605 0.7694 0.8441 2.82\n", - " SVM Lineaire TF-IDF 0.7854 0.7781 0.7985 0.7882 0.8665 122.32\n", - " Naive Bayes TF-IDF 0.7704 0.7776 0.7574 0.7674 0.8511 4.32\n", - " Naive Bayes TF-IDF 0.7696 0.7766 0.7568 0.7666 0.8524 4.27\n", - "Regression Logistique TF-IDF 0.7832 0.7766 0.7950 0.7857 0.8611 36.01\n", - " Naive Bayes BoW 0.7718 0.7766 0.7632 0.7698 0.8432 1.47\n", - " SVM Lineaire TF-IDF 0.7842 0.7765 0.7981 0.7872 0.8612 51.15\n", - " Random Forest BoW 0.7689 0.7746 0.7584 0.7664 0.8439 6377.44\n", - "Regression Logistique BoW 0.7829 0.7723 0.8024 0.7871 0.8584 896.35\n", - " Random Forest TF-IDF 0.7675 0.7702 0.7625 0.7663 0.8484 16182.08\n", - " SVM Lineaire BoW 0.7822 0.7687 0.8074 0.7876 0.8583 458.62\n", - "Regression Logistique BoW 0.7792 0.7672 0.8018 0.7841 0.8514 371.54\n", - " SVM Lineaire BoW 0.7801 0.7655 0.8074 0.7859 0.8516 187.81\n", - " Random Forest TF-IDF 0.7648 0.7636 0.7671 0.7653 0.8416 5977.55\n", - "\n", - "=== Meilleur modèle ===\n", - "Modèle: Random Forest\n", - "Vectorisation: BoW\n", - "Precision: 0.7798\n", - "Meilleurs paramètres: {'max_depth': 'None', 'min_samples_split': '5', 'n_estimators': '200'}\n" - ] - } - ], - "source": [ - "# Afficher le tableau comparatif\n", - "afficher_tableau_comparatif(results_df)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "etUvFhueWxlQ" - }, - "outputs": [], - "source": [ - "def display_metrics_subplot(df, figsize=(18, 14)):\n", - " \"\"\"\n", - " Génère des sous-graphiques pour visualiser toutes les métriques avec une échelle fixe (0,1)\n", - "\n", - " Parameters:\n", - " -----------\n", - " df : pandas.DataFrame\n", - " DataFrame contenant les résultats des modèles avec leurs métriques\n", - " figsize : tuple, optional\n", - " Taille de la figure (largeur, hauteur)\n", - " Returns:\n", - " --------\n", - " matplotlib.figure.Figure\n", - " La figure générée\n", - " \"\"\"\n", - " # Définir les métriques à visualiser\n", - " metriques = [\"Accuracy\", \"Precision\", \"Recall\", \"F1 Score\", \"ROC AUC\"]\n", - "\n", - " # Créer une figure avec des sous-graphiques\n", - " fig, axes = plt.subplots(3, 2, figsize=figsize)\n", - " axes = axes.flatten()\n", - "\n", - " # Définir une palette de couleurs cohérente\n", - " palette = sns.color_palette(\"Set2\", len(df[\"Vectorisation\"].unique()))\n", - "\n", - " # Créer un barplot pour chaque métrique\n", - " for i, metrique in enumerate(metriques):\n", - " ax = axes[i]\n", - "\n", - " # Créer le barplot\n", - " sns.barplot(\n", - " x=\"Modèle\",\n", - " y=metrique,\n", - " hue=\"Vectorisation\",\n", - " data=df,\n", - " ax=ax,\n", - " palette=palette\n", - " )\n", - "\n", - " # Configurer l'axe y pour qu'il aille de 0 à 1\n", - " ax.set_ylim(0, 1)\n", - "\n", - " # Ajouter une grille horizontale pour faciliter la lecture\n", - " ax.grid(axis='y', linestyle='--', alpha=0.7)\n", - "\n", - " # Définir le titre du sous-graphique\n", - " ax.set_title(f\"{metrique}\", fontsize=14, fontweight='bold')\n", - "\n", - " # Rotation des étiquettes de l'axe x pour éviter le chevauchement\n", - " ax.set_xticklabels(ax.get_xticklabels(), rotation=45, ha='right')\n", - "\n", - " # Ajouter des valeurs au-dessus des barres\n", - " for p in ax.patches:\n", - " height = p.get_height()\n", - " ax.text(\n", - " p.get_x() + p.get_width()/2.,\n", - " height + 0.01,\n", - " f'{height:.3f}',\n", - " ha=\"center\", fontsize=9\n", - " )\n", - "\n", - " # Ne garder la légende que sur le premier graphique\n", - " # if i != 0:\n", - " # ax.get_legend().remove()\n", - "\n", - " # Ajuster la disposition pour éviter le chevauchement\n", - " plt.tight_layout()\n", - "\n", - " # Ajouter un titre global\n", - " fig.suptitle(\"Comparaison des performances des modèles par métrique\",\n", - " fontsize=18, fontweight='bold', y=1.02)\n", - "\n", - " # Afficher la figure (si dans un notebook)\n", - " plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "lv_FsbT_OlC1" - }, - "outputs": [], - "source": [ - "def display_metrics_subplot(df, figsize=(18, 14)):\n", - " \"\"\"\n", - " Génère des sous-graphiques pour visualiser toutes les métriques.\n", - " Le graphique de Précision occupe 2 colonnes.\n", - "\n", - " Parameters:\n", - " -----------\n", - " df : pandas.DataFrame\n", - " DataFrame contenant les résultats des modèles avec leurs métriques\n", - " figsize : tuple, optional\n", - " Taille de la figure (largeur, hauteur)\n", - " Returns:\n", - " --------\n", - " matplotlib.figure.Figure\n", - " La figure générée\n", - " \"\"\"\n", - " if df.empty:\n", - " print(\"Aucun résultat à afficher.\")\n", - " return None # Retourne None si le dataframe est vide\n", - "\n", - " # Définir les métriques à visualiser (sans la Précision initiale)\n", - " other_metriques = [\"Accuracy\", \"Recall\", \"F1 Score\", \"ROC AUC\"]\n", - "\n", - " # Créer une figure et une grille de sous-graphiques (3 lignes, 2 colonnes)\n", - " fig = plt.figure(figsize=figsize)\n", - " gs = fig.add_gridspec(3, 2)\n", - "\n", - " # Définir les axes pour la Précision (span 2 colonnes)\n", - " ax_precision = fig.add_subplot(gs[0, :]) # La Précision occupe la première ligne, les deux colonnes\n", - "\n", - " # Définir les axes pour les autres métriques (remplir les 2 lignes restantes)\n", - " axes_other = [fig.add_subplot(gs[1, 0]), fig.add_subplot(gs[1, 1]),\n", - " fig.add_subplot(gs[2, 0]), fig.add_subplot(gs[2, 1])]\n", - "\n", - " # Définir une palette de couleurs cohérente\n", - " palette = sns.color_palette(\"Set2\", len(df[\"Vectorisation\"].unique()))\n", - "\n", - " # Fonction utilitaire pour ajouter les valeurs au-dessus des barres\n", - " def add_value_labels(ax):\n", - " for p in ax.patches:\n", - " height = p.get_height()\n", - " if height > 0: # N'affiche le label que si la hauteur est > 0\n", - " ax.text(\n", - " p.get_x() + p.get_width()/2.,\n", - " height + 0.01,\n", - " f'{height:.3f}',\n", - " ha=\"center\", fontsize=9\n", - " )\n", - "\n", - "\n", - " # Tracer le graphique de Précision\n", - " sns.barplot(\n", - " x=\"Modèle\",\n", - " y=\"Precision\",\n", - " hue=\"Vectorisation\",\n", - " data=df,\n", - " ax=ax_precision,\n", - " palette=palette\n", - " )\n", - " ax_precision.set_ylim(0, 1)\n", - " ax_precision.grid(axis='y', linestyle='--', alpha=0.7)\n", - " ax_precision.set_title(\"Precision (Métriques clé)\", fontsize=14, fontweight='bold')\n", - " ax_precision.set_xticklabels(ax_precision.get_xticklabels(), rotation=45, ha='right')\n", - " ax_precision.set_xlabel(\"\") # Masquer l'étiquette x pour la Précision si désiré\n", - " add_value_labels(ax_precision) # Ajouter les valeurs\n", - "\n", - "\n", - " # Tracer les graphiques pour les autres métriques\n", - " for i, metrique in enumerate(other_metriques):\n", - " ax = axes_other[i]\n", - "\n", - " sns.barplot(\n", - " x=\"Modèle\",\n", - " y=metrique,\n", - " hue=\"Vectorisation\",\n", - " data=df,\n", - " ax=ax,\n", - " palette=palette\n", - " )\n", - "\n", - " ax.set_ylim(0, 1)\n", - " ax.grid(axis='y', linestyle='--', alpha=0.7)\n", - " ax.set_title(f\"{metrique}\", fontsize=14, fontweight='bold')\n", - " ax.set_xticklabels(ax.get_xticklabels(), rotation=45, ha='right')\n", - " add_value_labels(ax) # Ajouter les valeurs\n", - "\n", - " # Ne garder la légende que sur l'un des graphiques (par exemple, le premier des autres)\n", - " if i != 0:\n", - " ax.get_legend().remove()\n", - " else:\n", - " # Placer la légende de la précision à un endroit approprié si on ne veut pas de légendes répétées\n", - " # ax_precision.legend(loc='upper right') # Exemple de placement\n", - "\n", - " # Ou retirer la légende de la précision si on la garde sur le premier des autres\n", - " ax_precision.get_legend().remove() # Retirer la légende de la précision\n", - "\n", - " # Ajuster la disposition pour éviter le chevauchement\n", - " plt.tight_layout()\n", - "\n", - " # Ajouter un titre global\n", - " fig.suptitle(\"Comparaison des performances des modèles\",\n", - " fontsize=18, fontweight='bold', y=1.02) # Ajuster y si nécessaire\n", - "\n", - " # Afficher la figure (si dans un notebook)\n", - " plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 1000 - }, - "executionInfo": { - "elapsed": 2184, - "status": "ok", - "timestamp": 1751740006350, - "user": { - "displayName": "Grégoire", - "userId": "01249298206117462766" - }, - "user_tz": -120 - }, - "id": "Noe09nRHWxlQ", - "outputId": "93abb9a5-d96c-424d-f81f-693fde709861" - }, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAABv0AAAWZCAYAAABe3dlUAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3Xd8FNX+//H3ppJCGqEHEpDQexcQQu9IkSKCIggIihRRUbwXvBevIoJUsaAISBUFaQICoaMSeujSpEhPgSSk7u8PftlvNrtpJCFZeT0fjzwemTPnzHzO7uxMMp89ZwxGo9EoAAAAAAAAAAAAADbLLq8DAAAAAAAAAAAAAJA9JP0AAAAAAAAAAAAAG0fSDwAAAAAAAAAAALBxJP0AAAAAAAAAAAAAG0fSDwAAAAAAAAAAALBxJP0AAAAAAAAAAAAAG0fSDwAAAAAAAAAAALBxJP0AAAAAAAAAAAAAG0fSDwAAAAAAAAAAALBxDnkdAAAAAAAAAB6/mzdvau7cuTIajZKkIUOGqESJEnkcFQAAAB4VST8AAAAAAIAnUJEiReTt7a2RI0dKkrZt26Zt27bJwYHbRQAAALaI6T0BAADwRAoKCpLBYDD9DBgwIK9Dyte2b99u9noZDAZdvHgxr8N6Ii1dulRt2rRR0aJF5ejoyHuCPDVgwACzYzAoKCivQ4Kydo174403NH36dEnSrl27NH78+Ezt4+LFixbXhe3bt2c/eAAAADwyvroFAACQyunTp7V06VLt3r1bZ86c0d27dxUXFydPT08FBgaqYcOG6tKli5o1ayaDwZDX4QJ4gowZM0afffZZXocB4B9m5MiRMhqNGj16tKZMmaJnnnlGnTp1yuuwAAAAkEUk/QAAAP6/69eva/jw4Vq9erXp2TYp3b59W7dv39a+ffv02WefqUePHlq5cmUeRArgSfTXX39pxowZeR0GgH+oUaNGSZJGjx6tF198UQcPHlRAQECexgQAAICsIekHAAAgKSQkRB07dtTNmzcz3eb27du5GBFy27Jly/TgwQPTsru7ex5GA2Rs//79SkpKMiubO3euWrZsKUdHR0mSn59fXoQG4B9i1KhRpuQfAAAAbA9JPwAA8MS7dOmS1YRfiRIl9MYbb6hp06by9fVVZGSkjh07pg0bNmj16tV5EyxyTLFixfI6BCBL7t+/b1E2dOhQphkGAAAAAEiS7PI6AAAAgLw2duxYi4Rf06ZNdeLECb3zzjt6+umnFRgYqDp16mjAgAFasWKFzp07p27dulndXnx8vL7//nv16NFD/v7+cnV1lYuLi0qVKqUuXbrom2++UWxsrNW227dvl8FgMPu5ePGiQkND9fzzz6tYsWJydXVVtWrVNG3aNCUkJJjarl+/Xq1atZKPj4/c3NxUp04dff7551anKpVksZ/vvvtODx480EcffaQaNWrI3d1dXl5eatGiRbpJzlWrVum9995Tu3btVKlSJRUtWlROTk5yc3NT6dKl1aFDB33++edWExaSdPHiRYtYtm/fruvXr+uNN95QuXLlVKBAAbPExvHjx/Xpp5/qhRdeUO3atVW6dGm5u7vLyclJhQsXVsOGDTV27FidPHkyzbiDgoLM9jlgwACLOmFhYfrf//6npk2bqmjRonJ2dparq6v8/f1Vv359DRkyRPPmzdOVK1es7sNoNGrt2rXq27evypUrp4IFC8rZ2VnFixdXmzZtNH36dEVGRmbpdYmIiND777+vypUry8XFRV5eXmrZsqU2btyYZl8za9++feratasKFy4sFxcXVaxYUe+//77u3buXpe08ePBA8+bNU5cuXVSqVCm5uLjI3d1d5cuX16BBg/THH3+k237r1q3q37+/KlasqIIFC8rR0VG+vr6qVKmSOnXqpA8++ECbNm3Kcv/Sek3Dw8P17rvvqmLFinJxcZGvr686d+6sHTt2ZLjNvXv3asiQIapSpYo8PT3l5OSk4sWLq3379vrmm28UHx9vtV1an/WzZ89q4MCBKl26tJycnBQQEKCJEyemeYza2dmZ2gcFBVmsP3v2rN566y3Vq1dPhQoVkqOjo3x8fFSrVi2NHDlSx48fT7Nv1j4jSUlJ+vLLL9WoUSN5eXmZzh2S9N1331n0SZL27NmjLl26yNfXV+7u7qpXr57mz59vtq9FixapUaNG8vDwkIeHh5o0aaLly5enGVtunXey8/k6evSoRo0apXr16snX19d0PqpWrZqGDBmiDRs2pNl248aN6t+/vwIDA1WwYEEVKFBApUqVUvfu3fXDDz+keR6XHj6HdtSoUapdu7a8vb3l6OgoT09PlStXTkFBQRo7dqxWrFihqKioDPtgTUJCgmbNmqU6derIzc1N3t7eat68+SNNb33x4kW9++67atCggek1KlSokBo1aqT//ve/unPnTppto6OjNXPmTLVu3VolS5ZUgQIFVKBAAfn5+al27doaMGCA5syZozNnzmQ5ruTPWPJP8pSWa9euVcuWLeXl5SVPT081bdpUP//8s6ldYmKiZs2apVq1aplem9atW2vLli0Z7nPHjh0aNGiQKlWqZDp3FClSRM2aNdOkSZN069atdNvHxMTov//9r6pUqSIXFxcVLlxYHTt21LZt27Lc/9DQUI0cOVK1atWSj4+P6dgNCgrS1KlTs3wNsCYxMVErVqxQr169VKZMGbm5ucnFxUVlypRR37599euvv6bbPiQkREOHDlW1atXk4eEhBwcH+fj4qHz58mrTpo3Gjx+v1atXm/1dBAAA8EQyAgAAPMHOnj1rlGT2U6hQIeOtW7ceaXuhoaHGSpUqWWwz9U+ZMmWM+/fvt2gfHBxsUfezzz4zOjk5Wd1O+/btjYmJicaxY8emua9BgwZZjTV1vY8++shYpUqVNLfzxhtvWN2Op6dnhv2VZPT39zceO3bMov2FCxcs6s6YMcNYqFAhi/JkI0eOzNQ+HRwcjNOmTbMad7NmzczqvvTSS2brT58+bSxevHim9vPRRx9ZbP/y5cvGRo0aZdjW19fXuHHjxky9LtOnTzeWKFHC6nYMBoPxm2++sdrXzJg9e7bRYDBY3XbZsmWNCxYssCi/cOGCxXb27dtn9Pf3z7Dfr776qjEuLs6i/Ztvvpmp19zZ2TnLfbT2mn7xxRfGkiVLpvmafvrpp1a3FRERYezZs2eGcVauXNl4+vRpi/bWPutff/210cXFxeJzM2HChEy9Js2aNTNtPzEx0fj+++8b7ezs0m1jMBiMo0ePNsbHx1vEmPoz0rdvX2OHDh0stjF//nyj0Wg0zp8/32Ld7Nmz04xhyJAhxoSEBGPv3r3TjO+///2v1dc/N847j/r5un//vnHgwIEZxlKjRg2LtteuXTMGBQVl2LZJkybGv//+26L9jz/+mOb1IfXPvn37rMafnvv37xubN2+e5jYHDx5sfPHFF9M8DpMlJSUZJ02aZHRwcEg3Ri8vL+PatWst2t+8eTNT11ZJxqFDh2a5n6k/Y/7+/sa33347zX3873//M0ZFRRmbNm2a5vGyYMECq/u6e/eusUuXLhn2w83NLc1t3Lhxw1i1atU09/3f//43w2uc0Wg0xsbGGl9//fUMYylWrJhx165dFu2tfY6Cg4Mt6p06dcpYvXr1DPfTrVs3Y2RkpEX7mTNnpnl9Sv1j7XMCAADwJCHpBwAAnmjTp0+3uGE0bty4R9rW+fPnjYULF87UTSlJRk9PT+OJEyfMtmEtEZDRja4WLVpkuK9t27ZZxJu6jqOjY4bbmTt3rsV2MnvzXXqYPEqd6LF20zCtG8PJMpv0S/759ddfLeLO6IZop06dMr391Em/O3fuGMuXL5/p9o6OjhY3Sq29LhkdC+7u7sbw8HCrx2d6tm3bluG2XV1dLcpSJ/0OHjxodHNzy3S/Bw4caNZ+//79mW6bU0m/zBz3v/zyi9l24uLiLI6f9H5KlixpvHbtmtk2rH3WrR33j5r0y2zyNPln8ODBFq9X6j6m9blML+mX3fOXvb298ezZsxax5cZ551E+X/Hx8cY2bdpkKo7USb/w8PBMJ7IkGatXr268f/++qX1sbKzRx8cn0+0fJen30ksvZbjd1OcGa0m/d999N9NxOjg4WFyzMpOYSv7JiaRfRseCvb19mgm/5J+CBQsaIyIizPbz4MEDY+PGjTPdF0nG77//3mwbSUlJ6SZi03pfrCX9+vbtm+k43NzcjIcOHTJrn5mk36VLl4zFihXL9H7atGljTEhIMLX/+++/M3WeTv4h6QcAAJ50TO8JAACeaIcOHbIoa9my5SNta8SIERbTcQ0ZMkS7du3Sb7/9plGjRpmti4iI0LBhwzLcrtFo1DvvvKPQ0FCtW7dOvr6+Zuu3bdsmBwcHTZ06VSdOnND8+fPl5ORkVmfx4sUZ7ic+Pl5PP/20Nm7cqEOHDumTTz6x2M748eMVExNjVla6dGm9+uqrWrZsmbZv367jx4/rxIkTCg4O1ujRo2Vn939/cp4/f14//vhjhrEkJCQoMDBQy5Yt06lTp7R371598MEHpvWurq7q2LGj5syZo40bN+rAgQM6e/asQkJC9OWXX8rPz89se59++mmG+0wt9dSO//vf/3To0CGdPXtW+/fv1+LFi/Xaa6/pqaeesmj773//22KKuW7dumnr1q06cOCA/vvf/8rB4f8erx0fH6/BgwdnOC2Z0WhUmzZttGvXLu3fv189e/Y0W3///n2tWbMmq13V2LFjLaYPHD16tP744w8FBwerS5cuio6OzjC2QYMGmU0hWKFCBS1evFihoaEKCQnRu+++azZN67fffms2Fd3OnTvNtlm7dm1t2rRJp0+f1vHjx7V582ZNmTJFHTp0UIECBbLcT2vi4+PVuXNnbdu2TSEhIRo3bpzFM/LefPNNs+U5c+aYHR+Ojo6aMGGC/vjjD504cUKLFy9W6dKlTeuvXr2qd955J8NYEhISVK9ePa1du1anT5/Wjh07NGLECI0aNUoXLlzQlClTLNpcuHDB9LNs2TJJD6fBmzp1qlk9Pz8/LVmyRMeOHdPKlStVtmxZs/Vff/21tm/fnmF8yX09ePCgjh49qoULF6p8+fJptrG3t9enn35qel1Sv2/btm2Tu7u7vv32W504ccLis5qYmGjqV0q5cd55lM/XF198oc2bN5uVFStWTDNnztThw4d1+vRpbdiwQUOGDJGLi4tZvQkTJphNQVywYEFNmzZNBw8eVGhoqL788kt5e3ub1h89elSTJ082LYeGhuru3bumZR8fHy1evFjHjx/X6dOntWvXLn355Zfq16+ffHx8Mux/aseOHdOCBQvMygoXLqyFCxfq6NGjWrJkiYoXL57hueHQoUP6+OOPzcr69u2r4OBgnTp1Sps2bdIzzzxjWpeQkKBXXnnFbGrc1OfjUaNGaf/+/Tp79qwOHjyoH374QW+++aaqVauWI8+4NBqNcnd31zfffKMTJ05o1qxZZusTExO1c+dOFStWTCtXrlRoaKjFeeLevXsWx8usWbO0Z88es7JnnnlGGzdu1JEjRzRr1iy5ubmZrX/99dcVHh5uWl63bp2Cg4PN6pQtW1Y//fSTjh49qi+//FIFCxbM8H1ZvXq1lixZYlbWr18/7dixQydOnNCyZctUpkwZ07qoqCgNHTo03W1a88Ybb+j69eum5eLFi+urr77SkSNHdPjwYU2ZMkXOzs6m9Zs3bzY77vbu3Wt2LAQEBGj16tU6efKkTp48qeDgYM2cOVM9evSQu7t7luMDAAD4x8nLjCMAAEBeszZV3cmTJ7O8nUuXLllsp1+/fhb1hg4dalEv5Wg/a6N/unbtaraNMWPGWNQZNWqUWZ2uXbuara9bt65FLKm3UbRoUWN0dLRZnalTp1rUW7lyZZZem9Qj5l599VWz9dZGCri7u2fr2/orV660GKGQmJhoViejkX4pp1n08PAwxsbGprm/lNORPXjwwGKExTPPPGPR5qOPPrLo94YNG0zrrb0u/v7+ZnHExcUZvby8zOqMHTs2S6/ViRMnLPaTegReUlKSsUaNGhb1Uo7027Vrl9k6R0dH45UrVyz2169fP7N6PXr0MK2bPHmy2bqPP/44zbitTQGXEWuvac2aNY1JSUlm9UaMGGFRLyQkxLT+qaeeMls3ZcoUi31t2bLFrI69vb0xLCzMtN7aZ7106dLGqKioNOO3NpLOmtRTTdrZ2VlMMXrp0iWL0TO9evUyq2NtNOPs2bOzFF/qc1P37t0t6kyfPt2sTs2aNc3WP/fcc2nuMy2Pct55lM9XYGCg2Xpvb2+r094ajUaz99/aeeKHH36waDNv3jyzOoULFzYdr7///rvZunbt2qX5esTFxRljYmIyetnMWJvecufOnWZ1Dhw4YFEn9Ui/QYMGma3v2LGjxb7u379vLFCggFm9lNN8ph4Ref369TTjfpRzg7XRtKmPy9q1a1vUWb16tWl9QkKC0dfXN93jpWzZsmbry5QpYzEKdenSpRb7+fzzz03re/XqZXFuOX/+vNk2fvzxR4ttpL7GtWzZ0mx9t27dLF6XP/74w2I7Bw8eNK3PaKTf5cuXLdb/8ccfFvt5//33zerUqVPHtG758uXpfpZTio6OtjpVMQAAwJOEkX4AAAA5IPUIJenhKL/UrH1L3lrblPr372+2nPKb98lefPFFs+XUI2/CwsLS3YckPf/88xYjUQYNGmRR77fffjNbTh6J06dPH1WuXFmenp5ycHCQwWCQwWDQunXrzOpfuXIlw1gGDx6sYsWKpVvn8OHDGjNmjBo2bKgiRYqoQIECpn0+99xzZnWjoqIy9RqkVKdOHdPvkZGRqlatmoYPH67p06frl19+0dWrV03rCxYsaPp9//79FiMsBg8ebLH9RzkWBg8ebDb60tHR0WKkYVb7+fvvv1uUDRw40GzZYDDo5ZdfTnc7qUfixMfHy8/Pz/SeJP98//33ZvVS9jnlay49HDH57LPP6l//+pcWLVqkkJAQxcXFSTJ/zbNjwIABFiOD0jvur169qnPnzpmte+uttyz62apVK7M6iYmJ2rt3b7qxvPnmm3J1dX2UbphJ/V4EBQVZnBNKly6t9u3bm5VldPwVLVrU6rGcntw6f+XWeScrn6+rV6/q7NmzZuuHDBmigIAAq9v38vIy/R4SEmJxnujZs6fFcfTKK6+Y1bl165ZpdGDlypXNztkbN27UM888o7Fjx+qrr77Sjh07FBERYepLVkfHpj43lCtXzmxEnvRwNG7NmjXT3U7q43H9+vUW/XR3d9eDBw/M6qV3bqhbt65eeeUVTZkyRWvWrNH58+dN63Lq3JDRsevt7a1OnTqZlu3t7S1G0KY8Xq5cuWIWpyS9/PLLcnR0NCvr1auX2QhPyfy1SP2+NG/e3CK2rl27pju6MzExUbt37zYrO3v2rDp16mT2M2HCBIu2qdulJ/V7L0n169e3eP8nTZpkVufQoUO6f/++pIfHWMpz9FdffaW2bdtq3Lhx+vbbb7V3717TZ8nFxcVsFD0AAMCTiL+GAADAE61w4cIWZVeuXFHFihWztJ1r165ZlFmb9jH1DcG02qbXxlpSIPUNv9TJu4ymjbS2DUny9PSUt7e32Y3LlNN03bp1S+3bt9eBAwcy3H6y5Bt56alVq1a66//1r3/pww8/tJiSMqP9FipUKNP1P/74Y7Vu3do0nemZM2cspuwMDAzUoEGDNGrUKNP0ZJk9Fry9veXl5WU2bVtGx4K14/JR3uuUbty4YVFm7ViwVpZSyiRoVty+fVsJCQlycHBQy5Yt9eyzz+rnn3+WJMXFxWnNmjVmU+QVKFBA7dq10/jx41W3bt1H2mdKme1r8nH/qP2UpL///jvd9Rkd95mV+jiydvxJlueWGzduKDExUfb29lbrV6lSxWLK34xkdP5KPseklNExnVvnnax+vqx9XlMnp9KS3eOocuXKcnd314cffqgxY8aY1u3evdssKWNnZ6cGDRpo1KhR6tWrV5b2k/rckNY5oEyZMjp8+HCa23nUvqb8vPz73//Wxo0bdfv2bUkPr9PffPONWf2SJUuqf//+GjdunDw9PR9pn8k8PT0tkmapj93SpUtbfFayerxY+2za2dnJ39/f7Lqbsm1m3pfkbaSc/jWlO3fuKDY21qwsNDRUoaGhVuunZO2akZZHfe+TkpJ048YNubu7q1y5choxYoRmzpxpWrd582azaXUdHR3VrFkzvfPOOxZfuAAAAHjSMNIPAAA80azdZN+6dWseRJK2lKNDJJk9qyqtOo/LyJEjs3TjXVKmEnUlSpRIc93mzZs1adKkLCX8MrvflBo3bqyjR49q+PDh8vf3t1rn7NmzGjdunMWzv3KLtaRlWgmazMrq65LTjEaj2Qifn376SfPnz1fz5s3NnvOU7MGDB1q9erUaN26sffv2Pc5Qsy318zBTS++4zw8eJb6Mzl+Pcu7KrfNObny+ckPK42j06NEKDg7Wc889Z5E8lR4mSPbt26fevXtr2rRpWdpPXp8bUvYzMDBQx44d09tvv53mMySvXr2qjz/+WC1atMjylx9Ss3Zc5sSxmxPy+n1JPSIzt6R8/2fMmKFVq1apQ4cOVp/bFx8fry1btqhNmzaZen4nAADAPxkj/QAAwBOtY8eOGjVqlFnZvHnzNHbs2CyNCrN2M/zcuXMW5amn9pKk4sWLZ3o/uenChQsWZeHh4RZT6yVPuxkXF2dxc6169eqaMGGCKlSoIDc3N0nSiBEjLKbay0h6N9qXLl1qtlygQAF98MEHatmypXx8fGQwGLRjxw4NGDAgS/u0ply5cpozZ47mzJmju3fv6uzZszp79qy2b9+ub7/91nTzde3atTpy5Ihq1KiR5rHQqFEjs7K7d++ajfKT8uZYKFq0qEXZhQsXLPph7fhIKXV9T09PHTx40GqSOrXkY0V6eGN9wIABGjBggBISEnThwgWdO3dOoaGh+uqrr0zTKcbFxemTTz7RqlWrMtx+eqz1y1pZ8nFv7f398ssv1aZNmwz3ldE5JacSTCVKlDCbgjT1dKTJUp+PihQpkm4M+SEBlpvnnayydiwcOHAgU18CsNZ2/fr1qly5coZtU39mg4KCFBQUJOnhCLhz587p1KlTWrFihbZt22aq9+GHH2rUqFGZ+kwm7+f06dOm5bTOAZk5N6Q8Bl9++WX9+9//znD/Kc8L0sPP4OTJkzV58mTdu3dPZ86c0blz57Rv3z7NmTNH8fHxkqSDBw9q/fr1evbZZzPcx+OU1rUhtaSkJF26dMmsLOW1oWjRovrrr79My9Zef2vbSKlQoUJycnIyTZcsSVOmTNHYsWPT70QWpe6zwWDQwYMHM5UwLVmypNly165d1bVrV1Pfzp07p5MnT2rBggWmLwEYjUZNmjRJPXr0yLE+AAAA2BpG+gEAgCdauXLlLJ7/dvv2bfXs2VP37t1Ls93ly5c1Y8YM03LTpk0t6nz55ZeZKrPWNi8sXbrUYiTSt99+a1GvQYMGkh6+TilvGErSxIkT1b17d1WpUkUBAQHy9vbWoUOHcjTO1NOFtWnTRm+//bbq1KmjMmXKKCAgQPv378/2flJPxebj46MGDRqoX79+mjdvnqpXr262Pvk5W/Xq1bOY4u2rr76y2L61srw4FpLfz5RSv+9Go1Hz589PdzvJSYdkERER+v333xUQEJDmz/Xr1xUWFmZ6XlN4eLjZMejg4KDAwEC1a9dOY8eO1SeffGK2j+TXPDvmz59vMXIm9bSB0v+9Tn5+fhZTVq5evVqlS5dOs5+urq46cOBAjj1rLCPNmjUzW96+fbvF1LR//fWXfvnlF7Oy/HIuSk9enXesKVmypAIDA83Kvv76a7OETEopk/z16tWzmC7y559/TvfzYjAYdPLkSdP5JTEx0WKqRT8/PzVr1kxDhw61SI7evXtXN2/ezHT/Up8b/vzzT+3atcus7ODBg+lO7SlZnhs2b94sNze3NPtZrFgxBQcHm02/nfp8XLBgQdWpU0e9evXSZ599pg4dOpitz4lzQ06zdu6YP3++KVmZbMWKFRZftkn52Uz9vgQHB1sk/lavXp3m1J7SwwR+6uczfvvtt+lOg3vr1i3NnTs3zfXWpD4XGY1G/fLLL+ke51FRUfrrr79MzzqMjo42PZtSevjFkDJlyqhVq1YaMWKE5s2bZ7aP/PjeAwAAPE6M9AMAAE+8Tz/9VDt37jS7GRocHKzKlSvrjTfeUNOmTVWoUCFFREQoNDRU69ev1+rVq9WoUSONHDlS0sNn+3Ts2FHr1683bWPx4sVyd3dX//795ejoqGXLllkk/Zo1a5apkR2Pw40bN9SyZUtNmDBBxYoV0+bNm/X++++b1fH29lbHjh1Nvzs4OJhNozZ16lR5e3urWLFiOnHihCZNmpStZ1dZk/o5jNu2bdOCBQvUoEED3blzR4sWLbKaXM2q5FEi7du3V506dRQQECB3d3dFRkZqw4YNFs8+Sp5yzNnZWQMHDtScOXNM63bv3q3u3btrxIgR8vLy0oYNGzRx4kSz9k899VSmRovltMqVK6tWrVpmSZJvv/1WXl5eev755xUVFaVp06bpyJEj6W6ncePGqlGjhlm9gQMH6sCBA+rcubNKlCihmJgYnT9/Xr///rvWrFmjEydOaP78+aZpdnfv3q1+/fqpY8eOCgoKUsWKFVW4cGHZ2dnp3Llz+uijj8z2aW2at6w6cuSInn32WY0ZM0YFCxbUypUrNXv2bLM6FStWNHt+4GuvvaY333zTtPzLL7+odevWeu2111S+fHk5ODjo+vXrOnLkiDZt2qStW7fq6aeffmyjT4YNG2aWuE1KSlLLli31ySefqFq1ajpz5ozeeusti2TD8OHDH0t82ZFX5520jBgxQm+88YZp+e7du2rYsKHGjx+vZ555RgUKFNClS5e0bt06hYSEaM+ePZIenicGDRqkWbNmmdp+9dVXunPnjgYOHKiAgABJD7/kcOjQIa1fv167d+9W//791b59e0kPpz/08/NTq1at1KpVK1WvXl0lSpSQs7Ozbty4oS+++MIi3tSj59LTr18/TZkyxaysR48emjZtmmrUqKHjx4+bPU8wLcnHY3Jy/erVq2rcuLHeeust1a5dWx4eHrp7966OHz+u7du3a+3atQoPD9fLL79s2sYbb7yhEydOqGPHjmrQoIHKli0rDw8PxcTEaOfOndqyZYvZPnPi3JAbhg0bprfeesu0fOHCBbVs2VLjx49X8eLFtWvXLr3zzjtmbby8vNS3b1/Tcr9+/fTDDz+YlhMTE9W6dWtNmTJF5cqV02+//ZapEXvDhw83m8785MmTaty4sd58801Vq1ZNbm5uunnzpg4fPqxNmzZp8+bNKl68uIYNG5bp/pYqVUqdO3fW2rVrTWXvv/++zp8/r969e6tUqVKKj4/XX3/9pZCQEK1du1YhISGaMGGCKdF5/vx51a9fX+3atVOLFi1UpUoVFStWTA4ODrpy5YrFl0Hy63sPAADw2BgBAABg3L9/v7Fw4cJGSZn+adasmdk2zp07Z/T19c10e09PT+Px48fNthEcHGxR78KFC2Z15s+fb1EntQkTJpit9/f3t6iTehuurq4ZxjxnzhyzbXTq1CnDNsWLF0/3dbtw4YJFm+Dg4DTfq5UrV2Z5n9Zex2bNmpmtf+mll8zW16lTJ9PvZcGCBY0RERGmtnfu3DEGBgZmur2jo6NFnzP7umTUj8z49ddfjQaDId0YHRwcMnxNQ0JCjG5ubln6HM2fP9/Ufu3atVlqO2HChCz109prmpnjft26dWbbiY2NNTZt2jRLsaY+7jPzWU8tM5/9ZGPGjMlSfK+88orFNrJ6bOXUuemll15K97V7nOedjF6DuLg4Y8uWLTP1GteoUcOs7d27d40VK1bM0vuUcv/37t3L1jGYGf369ctwu6nPDdb2884772Qp1tTHTo8ePTLdzsHBwXjmzJks9TMnjkujMePj5cGDB8ZGjRpl6XX4/vvvzbaRlJSUqfNP6vfF2ue3T58+WYol9euSmc/RhQsXjEWLFs3SflKe248dO/bInxEAAIAnEdN7AgAASKpbt66OHDmirl27mqYazIivr6/ZctmyZRUcHKyKFStm2DYgIEBbtmzJN6P8pIfPe0o5mim14cOHW3zDf+bMmek+h+5f//pXjo9e6969u/r06ZPm+ho1apiNssttLi4uWrRokTw8PExlPj4+ptFdGfH19dWaNWsspsB7nFq1aqXPPvsszWO/aNGimj59eobbqVOnjrZs2aIyZcpkar/Ozs4WIzczq0WLFhYjYh7F3LlzLabcS2ny5Mmm0a3JnJyctHbtWvXu3TvT+ylVqtQjx/gopkyZovHjx2fq+W0jR47M8rR9eSkvzjtpcXR01OrVq/Xiiy9mua23t7e2bdumFi1aZKq+wWCQn59flvcjSf7+/vr666+z3G7u3LnpTvvatWvXTH0OPvroI02aNEkODpmbbOhR+2lvb6+ZM2daTLuaXzg7O2vdunXq3LlzhnVdXV21YMECvfDCC2blBoNBy5cvT/fvh9dff12NGzfOcB8LFizQiBEjMv13z6OcxwICArRjxw7VrFkzU/Xt7e0f+fm2NWrUsBidCgAA8KRhek8AAID/r3jx4lq1apVOnTqlpUuXavfu3Tp9+rTCwsIUFxcnLy8vlStXTg0bNlSXLl2sJmmqVq2qo0ePavny5frpp58UEhKiW7duyWg0qlChQqpdu7aeffZZ9evXTwUKFHj8nUyHl5eX9uzZoxkzZmjx4sU6e/asHBwcVLt2bY0YMULdu3e3aFOmTBkdOnRIkyZN0tq1a3Xt2jV5enqqdu3aGjlypDp06KABAwbkaJwGg0FLlixRixYt9PXXX+v48eMyGAwqU6aMevfurbFjx+q3337L9n6WL1+unTt3as+ePTp69Khu3LihW7duKSEhQZ6engoMDFSLFi00dOhQqzdCS5Uqpd27d2vt2rVatmyZfv/9d924cUPx8fHy8fFRtWrV1KFDBw0aNMgsYZhXRo4cqbp162ry5Mnau3ev7t+/Lz8/P3Xq1Enjx4/X8ePHM7Wdhg0b6uTJk1q+fLl+/vlnHThwQLdu3VJcXJw8PDxUpkwZ1axZUy1atFDHjh3l6elpatuqVStt2bJFu3fv1r59+/TXX3/p1q1bCgsLk7Ozs0qUKKFatWqpV69e6tGjR6ZvVKendOnSOnz4sCZPnqyVK1fq0qVLcnV1VcOGDfXWW2+lmYz18PDQsmXL9Oabb+q7777Tnj17dOnSJd27d08FChRQ8eLFVblyZT3zzDPq2LGjKlWqlO1Ys8LOzk6TJk3SSy+9pC+//FLbt2/X+fPnde/ePdPz1Jo2baohQ4aoatWqjzW27MqL80563N3dtWDBAo0ePVrz58/X7t27deHCBd27d0+enp4qXry4nn76aXXt2tWibfHixbV161Zt2bJFS5Ys0W+//aarV68qKipKbm5uKlmypKpWraqmTZuqc+fO8vf3N7V1c3PTH3/8oT179mjPnj06c+aMbt26pdu3b8vOzk6+vr6qUqWKOnbsqEGDBmVpas+Ufdu6davmzJmjBQsW6NSpU3J0dFSVKlU0aNAgDRw40GwazrQYDAaNHz9eL774oubNm6dt27bp9OnTCg8Pl729vXx9fVWhQgU1aNBAbdu2tXje3MyZM9W9e3ft2bNHBw8eNJ2PHzx4oIIFC6ps2bJq2rSpBg8e/Ng/a1nl7e2tNWvWKDg4WIsWLdLevXt17do1xcTEyMvLS5UqVVLr1q01dOhQFSlSxOo2ihUrpv3792vKlClavny5zp8/L1dXV9WqVUuvv/66unXrlqkvkjg5OWnmzJkaNmyYvvnmG+3atUt//vmnIiMj5eTkpCJFiqhSpUpq1KiR2rVrl+4Xg9JToUIFhYSEaM2aNVq5cqX++OMPXb9+XTExMSpYsKBKly6t6tWrKygoSJ07dzbrd8WKFbVr1y7t2bNHe/fu1fnz53Xz5k3dvXtXjo6OKlKkiGrUqKGuXbuqX79+pmcBAgAAPKkMRmOqp9YDAADgiZA6YTJ//vzHeqMceNwuXrxoMQoxODg4T0dZAgAAAACQU5jeEwAAAAAAAAAAALBxJP0AAAAAAAAAAAAAG0fSDwAAAAAAAAAAALBxJP0AAAAAAAAAAAAAG2cwGo3GvA4CAAAAAAAAAAAAwKNjpB8AAAAAAAAAAABg40j6AQAAAAAAAAAAADaOpB8AAAAAAAAAAABg40j6AQAAAAAAAAAAADaOpB8AAAAAAAAAAABg40j6AQAAAAAAAAAAADaOpB8AAAAAAAAAAABg40j6AQAAAAAAAAAAADaOpB8AAAAAAAAAAABg40j6AQAAAAAAAAAAADaOpB8AAAAAAAAAAABg40j6AQAAAAAAAAAAADaOpB8AAAAAAAAAAABg40j6AQAAAAAAAAAAADaOpB8AAAAAAAAAAABg40j6AQAAAAAAAAAAADaOpB8AAAAA4LEIDg6Wg4OD7O3t9d133+V1OMgBAwcOlMFgUNmyZXXp0qW8DgcAAAB4opH0AwAAAPBEGjBggAwGgwwGg4KCgh55OxMnTjRtJyAgIMfiyw3Hjx+Xvb29DAaDOnbs+Fj3fe3aNT3//PMyGo367rvvNGDAgFzb18WLF03vicFg0Pbt23NtX/8kWT2Wv/nmG82fP19PPfWUduzYIX9/f7P1P/zwg2l748aNy6WoAQAAACQj6QcAAAAgy7Zv326WVEn54+7ursqVK2vEiBE6f/58XoeKFN577z0lJSVJkt5++22zdUFBQRbv5cGDB61u5+mnn7aoe/HixTT3m5CQoN69e+v27dtatGiR+vfvb7VeyhhyMymI7Dt8+LBef/11lStXTtu3b1epUqUs6nTv3l1PPfWUJGnmzJm6du3a4w4TAAAAeKI45HUAAAAAAP5ZoqKidPLkSZ08eVLffvutfv75Z7Vq1Sqvw7LQp08fVa1aVZKsJiwyq02bNnJ3d5ckeXp65khsueHAgQNas2aNJKlGjRpq1qxZhm1mzpxpMQ3n/v379dtvv2Vp3ydPnlTLli317rvvqkOHDllq+yh8fHw0ZcoU03Jy4gk559ixYxo3bpwGDx6sEiVKWK1jb2+v1157TWPGjFFMTIw+/vhjzZw58zFHCgAAADw5SPoBAAAAyLbevXurbt26iouL0759+7Ru3TpJUnR0tPr376+LFy/K2dk5w+1ERkbKw8Mjt8OVJLVr107t2rXL9nYaNWqkRo0a5UBEuevLL780/d6nT59MtVm2bJmmTJmiwoULm8pmzJiR5X1Xq1ZN1apVy3K7rIqLi5PRaJSHh4fGjh2b6/t7kqU1WjO1Xr166c0335TRaNSiRYs0efJkubi45HJ0AAAAwJOJ6T0BAAAAZFu7du00duxYvffee1q7dq1eeOEF07rr169rz549kiynBf3zzz/16aefqlKlSnJ2dtaLL75oapeUlKRFixapTZs2KlKkiJycnFS4cGF17NhRGzZsSDOWU6dO6bXXXlPlypXl7u4uV1dXlS1bVn369FFISIipXnrP9Dt27Jj69eungIAAOTs7y8XFRaVLl1aLFi307rvv6urVq6a6GT0HLSwsTP/5z39Ut25deXp6ysnJSSVLllT37t3166+/WtT/7rvvzF6j2NhYffjhhypfvrycnZ3l5+ensWPHKjY2NsP3JVlMTIyWLVtmWu7Ro0e69e3sHv6rGBsba5YsvH79ulasWCHp4SiujKxdu1bPPvusihcvLicnJ3l5ealZs2b65ptvTNOMSv/3Gu7YscNUtmDBAqvTh6aeAjQ0NFRdu3ZVoUKF5OzsrJMnT2b4TL/bt29r6NChKlq0qFxcXFS3bl398MMPFsdnyilL05t6NPV7llpsbKxmz56tpk2bysfHR05OTipevLh69uypffv2WX3tvvvuOwUFBcnX11eOjo7y9vZWhQoV1Lt3b33++ecZvvYpXblyRe+8845q1aolDw8PFShQQKVLl1bXrl2tHoNpiYyM1EcffaQGDRqYjuVSpUrphRde0IEDB6y2KVmypJ5++mlJUnh4uH766acsxQ4AAAAg8xjpBwAAACDHPf3001q8eLFp+fr161brDRw4ULt27bIoj4mJUZcuXbRlyxaz8tu3b2vDhg3asGGDxowZo6lTp5qt/+abbzR8+HDFxcWZlV+4cEEXLlxQw4YNVbdu3XRjP3HihBo2bKjo6Giz8suXL+vy5csKDg5Ws2bNVLJkyXS3Iz2c1rJNmza6cuWKWfm1a9e0atUqrVq1SiNHjtT06dPT3EarVq20e/du0/LVq1c1depU3bx5UwsXLswwBkn67bffdO/ePUlS4cKFFRgYmG796tWrKywsTJcuXdIXX3yhcePGycHBQXPnzlV8fLwkqUuXLlq1apXV9klJSRowYIAWLVpkVh4REaGdO3dq586dWr58udauXZupEaBpOXr0qBo2bKioqKhMtwkLC1Pjxo115swZU9mBAwfUq1cvde7c+ZFjScutW7fUpk0bHT582Kz8+vXrWrlypX766SdNmzZNI0eONK2bOHGiPvjgA7P64eHhCg8P15kzZ7Rjxw4NHz48U/vfsGGD+vTpY3r/kyUfzwEBAWrdunWG2zl79qzatGlj8ezGK1euaMmSJVq+fLk+//xzDRkyxKLt008/rb1790qSfv31V7MvBQAAAADIOST9AAAAAOS41KOXihUrZrXerl27VKVKFXXu3FlGo9E0emz06NGmhJ+Tk5P69OmjwMBAHTt2TD/88IOMRqOmTZumOnXqqG/fvpIeJraGDBliGkHm4OCgnj17qmLFirpy5Yo2btyYqdgXLFhgSvj5+fmpX79+cnNz05UrVxQaGprp59klJCSoW7dupoSfvb29+vfvLz8/P61evVqhoaGSHk6XWbt2bbNRjint3r1b3bp1U+XKlbV48WJT0mXx4sX6+OOP03yeWkopE6t16tTJsL69vb1ef/11vfXWW7p69apWrlyp7t27m0b9lS1bVp06dUoz6ffJJ5+YEn4Gg0E9evRQjRo1dOHCBS1atEjx8fH69ddf9d5772nq1Kmm5yLOnTtX58+flyTVrVtXvXv3Nm3Tx8fHYj+HDh2Sg4OD+vfvr8DAQJ06dUoFChRIt2/vv/++WcKvWbNmatasmfbs2aO1a9dm+NpkVf/+/U0Jv4IFC6pv377y8/PTnj17tHHjRiUlJWn06NGqW7euGjduLEmaO3euqX2rVq0UFBSkqKgoXb58Wbt371ZMTEym9n3p0iX17NnTdDwbDAZ16dJFNWvW1K1bt7Rt27ZMbScxMVHdunUzHXuFCxdW37595ePjo02bNmnv3r1KTEzU8OHDVb9+fdWsWdOsfb169Uy/W0vyAwAAAMgZJP0AAAAAZNvGjRt1+/ZtxcXF6bfffjNLnhQtWjTNZ941bNhQwcHBZomau3fv6ptvvjEtf/HFF3r55ZdNy76+vqbpDT/99FNT0m/KlCmmhJ+dnZ22bdumZ555xtQuLi5ON2/ezLAvDx48MP3+2muvady4cWbrw8LCMtyGJK1bt06nT582Lc+aNUvDhg2TJL333nuqVKmSLl26JEmaNm1amkm/UaNG6bPPPpMk9ezZ05RQSUpK0oEDBzKV9Dt37pzp91KlSmUq/ldeeUUTJ05UVFSUZs6cqbi4ON24cUPSw9cleQrQ1JKSksxGYP7rX/8yG7VWsWJFvf3225Ievrf/+9//TM9FXLdunSnpV6VKlUw9l2/lypV69tlnzcpSj0ZLFh8frwULFpiWmzZtqm3btsnOzk5Go1Ht2rXT5s2bM9xnZh09elSbNm0yLf/8889q3ry5aTl5qlqj0aipU6eakn4pj8FFixZZJM2TX6OMzJw502zE6vfff2/6vEgP36u//vorw+2sX79ex48fl/QwIbxnzx7TaNHx48erVq1aOnbsmBITEzV79mzNmzfPrL2fn5/p94sXLyopKSnN4wcAAADAoyPpBwAAACDbli9fruXLl1uUFyhQQAsWLEhz9NXYsWMt1v3+++9KSEgwLQ8cOFADBw602v7w4cOKjo6Wq6ur2RSYbdu2NUv4SQ9HDKZMPqTlmWee0cyZMyU9HBW2Zs0aVaxYURUqVFCDBg30zDPPZOp5dqlHO6ZM6rm4uKhXr16aMmWKpIfJoeR+pJZyGscKFSqYrctsAvLWrVum362NmLPGy8tLL774oubOnat9+/aZkkNubm4aNGhQmqP8Tp8+rdu3b5uW//Of/+g///mP1brR0dE6deqUatSokamYUqtatapFwi89p0+fNpsK9PnnnzclnwwGg1544YUcTfolP8syWYsWLdKsmzz9pfTwGFy/fr2kh31s0KCBAgMDVaVKFTVv3lzlypXL1P5TfiYqVapklvCTHibHrT2HMrWU/UhMTFT58uXTrGvt2X6FChUy/Z6UlKQ7d+6ocOHCGe4XAAAAQNaQ9AMAAACQo1xcXOTv768WLVpo9OjR6SYoKlasaFF29+7dTO/LaDTqzp07cnV1NWtXpkyZrAWdwnPPPaexY8dq1qxZio2N1b59+8wSeP7+/lq/fr2qVKmS7nZSxuPu7i43Nzez9UWLFjXrR3h4uNWkX8qkTOrn3yWPbMwtI0aMME01efXqVUnSSy+9JE9PzzTbZOX9kx4+q+5RWTt+srKvIkWKmC2nfE/SYzQazZZjY2Ot1svKa5EyMTt37lz16tVLv/32m+7cuaMNGzaY1e3Vq5eWLl2a4Wi5nPpMZKUf1t7P1K8XAAAAgNxB0g8AAABAts2fP18DBgzIcrvUiTDJciTa6NGj053CMjkB5ePjY5q+88KFC1mOJaUpU6bo/fff1969e3Xq1CmdOXNGa9as0bVr13Tp0iUNHz5cO3bsSHcbKftx//59RUVFmfU3eapM6eEoMy8vL6vbcXR0NKv3KHx9fU2/Z3Z0oPRwdFibNm1Mo98MBoNGjBiRbpvU79/MmTPVuXPnNOun9bzHzLB2/KQn9WucerrXlO9JaikTbKmfqXf27FmrbVK/Fv/5z3/k4uKSYZylSpXSvn379Oeff+qPP/7Q2bNndezYMf38889KSEjQihUr1K5dO7NpbzPaf3Y+Eym34+LiomPHjqU52tXBwfI2Q8qkoZ2dndnIPwAAAAA5h6QfAAAAgHylQYMGsre3V2JioqSHSS9rz3a7ePGiTp8+LQ8PD0lSkyZN9NNPP0mSNm/erD179piekSZJCQkJunHjhkqWLJnu/i9cuCBvb295eXmpffv2at++vSSpTZs26t69uyTp4MGDGfYj9XMMFy5caHqmX0xMjFasWGFaV6NGDauj/HJK2bJlTb9fvnw5S21HjhxpSvq1bt06w9F1FSpUUKFChXTnzh1J0o8//qjhw4dbJIlOnDihrVu3miURUyY4Uz6LLqdUrFhR7u7uun//viRp6dKlGjJkiOmZfosXL06zbcqE4aFDhxQXFycnJyddvXrV7DmBKaU+Bnx9fU3HQErHjx83S8YeOXJE1apVU7ly5cxGyj777LNas2aNpIfHYEZJvyZNmuiPP/6QJJ08eVLLli1Tnz59TOuNRqMuX76s0qVLp7udlP2IiYlRcHCwXnnlFbM6CQkJ+vnnn+Xr62sxjW7KY87f35/n+QEAAAC5hKQfAAAAgHzFx8dHAwcO1Ndffy1J+uSTTxQSEqJGjRqpQIECunr1qn777TcdOnRIL730ktq2bStJeuutt7R69WolJSUpMTFRzZs3V69evVShQgVdv35dmzZt0uuvv65Ro0alu//ly5drwoQJCgoKUmBgoIoXL66oqCgtXbrUVCetUXkpdezYURUqVNDp06clPZwqc//+/SpZsqRWr16tS5cumeqOHj06i69S1qRMfmYmYZlS+/bt9fPPPyspKUnVqlXLsL6dnZ3GjBmj8ePHS5J27NihqlWrqnPnzvLy8tK1a9e0Z88eHT58WM2aNTNL+qVMyK5fv17jxo2Tr6+vfH19H2kkaWoODg568cUX9fnnn0uSdu7cqRYtWqhZs2bas2ePtm7dmmbbevXqmZ5j+Oeff6p27dqqVKmSgoODTQnO1GrUqKHWrVvr119/lSS9/vrr+uWXX1SnTh3Z2dnp0qVL2rt3r06ePKkJEyaoSZMmkqTevXsrIiJCzZs3V8mSJeXj46Nz586ZTfOZmWPwjTfe0Ny5c00jE/v27avly5erZs2aCgsL0/bt2xUUFKTp06enu52OHTuqUqVKOnnypCRpyJAhWrt2rerUqaOkpCSdPn1aW7du1a1btzR//nw1a9bMrH1ISIjp99TP2gQAAACQc0j6AQAAAMh3pk+frgsXLmjLli2SpG3btmnbtm3ptmnYsKG++uorDR8+XHFxcYqPj0935FZ64uLitHnzZtMIt9TefvvtDLfh4OCgVatWqU2bNrpy5YoSExM1f/58i3pvvPGGXnzxxUeKM7MaNWokV1dXRUdH68aNG7pw4UKmn/FmMBjUpUuXLO1v3LhxOnXqlBYtWiRJOnXqlE6dOpVhu+7du5tGzUVHR2vy5MmSpCpVquRI0k+SJk2apC1btujMmTOSHiYlk6dq7dixo9avX2+13aBBgzR16lRTgu/48eM6fvy47Ozs1LZtW23atMlqu++//15t27bV4cOHlZSUpLVr12rt2rUZxnn9+nWzRHNKPj4+FiPtrPH399fKlSvVp08f3bt3T0ajUatXr9bq1atNdYKCgjLcjoODg1avXq22bdvq4sWLMhqNWrNmjWnUYUZSPhOzdevWmWoDAAAAIOuYUwMAAABAvuPq6qpNmzZpyZIl6tChg4oWLSoHBwe5uLjoqaee0nPPPaevvvpK06ZNM2s3aNAgHT58WMOGDVPFihXl6uoqZ2dnlSpVSs8995xpJFV6unbtqn//+99q1aqVAgIC5OrqKgcHBxUvXlwdO3bUmjVrMnyuXbJKlSrpyJEjmjhxomrXri13d3fTtrp166ZNmzZpxowZj/QaZYWbm5t69+5tWl65cmWu7s/Ozk4LFy7U+vXr1aNHD/n5+cnJyUnOzs7y9/dX586dNX36dIukVpcuXTR79mxVqlRJTk5OuRKbt7e3du/ercGDB6tw4cIqUKCAatWqpcWLF1udRjZZkSJFtGPHDrVv317u7u5yc3NTixYttH37drMpM621+/333zV37ly1aNFCvr6+sre3l5ubmypWrKh+/fpp8eLFeuutt0xtPvroI7366quqU6eOihUrJkdHR7m6uqpixYoaPny4Dhw4IH9//0z1t0OHDjp+/LjeeustVa9eXe7u7nJ0dFSJEiXUsWNHdejQIVPbKV++vI4ePapPPvlEjRo1kre3t+zt7VWwYEFVr15dr7zyilatWqW+ffuatbt69aop6efp6WmaIhcAAABAzjMYjUZjXgcBAAAAAMhd+/fvV/369SVJtWvX1oEDB/I4ovxn+/btat68uWn5woULCggIyLuA/gE+++wzjRkzRtLD6U1nzZqVxxEBAAAA/1yM9AMAAACAJ0C9evXUqVMnSQ+f67d79+48jgj/dImJiZozZ44kycXFRePGjcvjiAAAAIB/NpJ+AAAAAPCE+Oijj2Rn9/DfwI8//jiPo8E/3U8//aRz585JevjsypIlS+ZxRAAAAMA/m0NeBwAAAAAAeDyqVq2qxMTEvA4DT4iePXuKJ4oAAAAAj0++eqbfiRMntGbNGl24cEFhYWEaO3as6ZkTaTl+/LgWLlyoy5cvq1ChQurRo4eCgoIeT8AAAAAAAAAAAABAPpCvpveMjY1VQECABg0alKn6N2/e1Mcff6wqVarok08+UceOHfXFF1/o8OHDuRsoAAAAAAAAAAAAkI/kq+k9a9WqpVq1amW6/ubNm1WkSBG9+OKLkiQ/Pz+dOnVK69evV82aNXMpSgAAAAAAAAAAACB/yVcj/bLq7NmzqlatmllZjRo1dObMmTTbxMfHKzo62uwnPj4+t0MFAAAAAAAAAAAAck2+GumXVeHh4fL09DQr8/T0VExMjOLi4uTk5GTRZtWqVVq5cqVpuXHjxho5cmSuxwoAAAAAAAAAAADkFptO+j2Kbt26qVOnTqZlg8EgSQoLC1NCQkJehQUAAAAAAAAAAABYcHBwkLe3d8b1HkMsucbLy0sRERFmZREREXJxcbE6yk+SHB0d5ejoaFGekJDANJ8AAAAAAAAAAACwSTb9TL/AwEAdO3bMrOzo0aMqX758HkUEAAAAAAAAAAAAPH75Kun34MEDXbx4URcvXpQk3bx5UxcvXtTt27clSUuWLNHs2bNN9du0aaObN2/q+++/19WrV7Vp0ybt27dPHTt2zIvwAQAAAAAAAAAAgDyRr6b3PHfunD744APT8sKFCyVJzZo102uvvaawsDBTAlCSihQponHjxmnBggXasGGDChUqpFdffVU1a9Z83KEDAAAAAAAAAAAAecZgNBqNeR1EfnDr1i2e6QcAAAAAAAAAAIB8xdHRUYULF86wXr6a3hMAAAAAAAAAAABA1pH0AwAAAAAAAAAAAGwcST8AAAAAAAAAAADAxpH0AwAAAAAAAAAAAGwcST8AAAAAAAAAAADAxpH0AwAAAAAAAAAAAGwcST8AAAAAAAAAAADAxpH0AwAAAAAAAAAAAGwcST8AAAAAAAAAAADAxpH0AwAAAAAAAAAAAGwcST8AAAAAAAAAAADAxpH0AwAAAAAAAAAAAGwcST8AAAAAAAAAAADAxpH0AwAAAAAAAAAAAGwcST8AAAAAAAAAAADAxpH0AwAAAAAAAAAAAGwcST8AAAAAAAAAAADAxpH0AwAAAAAAAAAAAGwcST8AAAAAAAAAAADAxpH0AwAAAAAAAAAAAGwcST8AAAAAAAAAAADAxpH0AwAAAAAAAAAAAGwcST8AAAAAAAAAAADAxpH0AwAAAAAAAAAAAGwcST8AAAAAAAAAAADAxpH0AwAAAAAAAAAAAGwcST8AAAAAAAAAAADAxpH0AwAAAAAAAAAAAGwcST8AAAAAAAAAAADAxpH0AwAAAAAAAAAAAGwcST8AAAAAAAAAAADAxpH0AwAAAAAAAAAAAGwcST8AAAAAAAAAAADAxpH0AwAAAAAAAAAAAGwcST8AAAAAAAAAAADAxpH0AwAAAAAAAAAAAGwcST8AAAAAAAAAAADAxpH0AwAAAAAAAAAAAGwcST8AAAAAAAAAAADAxpH0AwAAAAAAAAAAAGwcST8AAAAAAAAAAADAxpH0AwAAAAAAAAAAAGycQ14HAOSE+Ph4TZw4UatWrZLBYFC3bt00ceJEOThYHuKBgYFmy3FxcQoMDNSWLVtyZD0AAMA/weP8++rvv//W+PHj9fvvv8tgMKhx48b63//+p0KFCuVS7wBkhHMAbAn3BAAAtoDrFR6HfJf027hxo9auXavw8HD5+/tr4MCBKleuXJr1169fr82bN+v27dvy8PBQgwYN1LdvXzk5OT3GqJHXZsyYoT/++EPBwcGSpH79+mnWrFkaPXq0Rd2zZ8+aLbdq1UpdunTJsfUAAAD/BI/z76vx48dLkv744w8ZjUa9/vrr+te//qXPP/88x/oDIGs4B8CWcE8AAGALuF7hcchX03vu3btXCxcu1HPPPafJkyfL399fH374oSIiIqzW3717t5YsWaKePXvqs88+06uvvqp9+/Zp6dKljzly5LVly5Zp5MiRKlq0qIoWLao33ngjU8fBoUOHdObMGfXq1StX1gMAkFJ8fLzGjx+vypUrq0qVKnr//feVkJBgtW5gYKDZj7+/v1q1apXp9VnZF2DN4/z76q+//lLnzp3l5uYmd3d3denSRadOncqxvgDIOs4BsCXcEwAA2AKuV3gc8lXSb926dWrZsqWaN28uPz8/DR48WE5OTqbMd2qnT59WhQoV1KRJExUpUkQ1atRQ48aN9eeffz7myJGXwsPD9ffff6tKlSqmsipVqujq1auKjIxMt+3SpUvVvHlzFStWLFfWA6nl5A1/Sdq8ebNat26tcuXKqXbt2lq4cKEk6erVqxbtS5curQEDBuR2FwGkI+W3+rZt26bff/9ds2bNslr37NmzZj+BgYEW39pLb31W9gWk9rj/vho8eLDWrVunyMhIRUREaPXq1WrdunXOdAZAlnEOgC3hngAA7rXAFnC9wuOSb5J+CQkJOn/+vKpVq2Yqs7OzU7Vq1XTmzBmrbSpUqKDz58+bknw3btzQoUOHVKtWrccSM/KHqKgoSZKnp6epLPn3+/fvp9kuOjpaa9asUd++fXNlPWBNTt7wDw4O1rvvvqsPPvhAp0+f1rZt29SoUSNJUsmSJc3aHj9+XB4eHnr22WcfSz8BWPc4v9X3qPsCpMf/91W9evV0+/Zt042aiIgIvf7669ntBoBHxDkAtoR7AgC41wJbwPUKj0u+SfpFRkYqKSlJXl5eZuVeXl4KDw+32qZJkybq1auX/vWvf+n555/XiBEjVLlyZXXv3j3N/cTHxys6Otr0ExMTY1pnMBhkMBjM6ieXZac8ZVlOled0jLbcJzc3N0nSvXv3TOXJ344oWLBgmttZt26dXFxc1LJlS6sxrl27Vi4uLmrVqpXV/Sa3T17/OF73rJbnp/eJPj20bNkyjRo1SsWKFTO7CZ9RjIcPHzbd0E8umzJlisaMGaNGjRrJ3t5e3t7eCgwMtBrLpk2bZDQa1b59+8fSV1t/n+gTfcqNGFN+qy+5rGrVqmbf6ksrlmXLlql58+YqXry41W0nry9WrJgMBoMiIiL0999/q2rVqqb6yfu6d+9ejr/uWS3Pz+8TfXpYnvrvK4PBYPXvq9Sxp/z7yNq2k/++atmypanMaDTq+eefV7169Uw3UOrVq2fxTynvE32iT48vdnd3d0kyuz4lXz+S11mL0dr/UCm3be0ckd45gPeJPmUmRmvXrJTHa1p9snY8ptx+evcMpPSP9+z2KSfKc/p1p0/0KT/3KeUXHosVK6aRI0ea7rWkF3vylyd79+5tKp8yZYpGjx6txo0by8HBQd7e3ipXrpzV7WzcuNF0r4X3iT5lFGN6f18VLFgwzW1ndL3J7vUsO336J75P+b1PmeGQ5Rb5yPHjx7Vq1Sq98sorCgwM1PXr1zV//nytXLlSzz33nNU2q1at0sqVK03LZcqU0eTJk+Xh4SGj0ShJio2NVVRUlNzc3OTs7GyqGxMTo5iYGBUsWFCOjo6m8qioKMXGxsrT01P29vam8nv37ik+Pl5eXl5mb05ERISSkpLk7e1tFltYWJjs7OzMsv1Go1FhYWFydHQ0+/AnJiYqIiJCzs7Opj9wpYdJzXv37snFxUUuLi6m8n9yn0qWLKmSJUvq4sWLqlWrlmJiYnT8+HH5+fnJ398/zT6tWLFC/fv3l4uLi9U+LVu2TD179lThwoWt9im5feHChXmf6FOm+pSUlKS///5bjRo1kre3t+7du2caxp8yVmt9+umnn9S8eXOVLFlSnp6eioqK0tGjR9WhQwc1adJE9+/fV5MmTTR16lQVL17cok8rV65Unz59VLhwYd4n+kSf8qhPt2/flvTw26HJ8ZQuXVrSw2/1lSpVymqfHB0dtWbNGs2bN0/e3t4WfYqKitKaNWs0ffp02dvby9PT0/QtwVKlSkmSHB0dTftKrsP7RJ/S65OXl5fZ31eSdPLkSZUoUcLs76vUfUr++8jX19dqn3744Qf17NlTbm5upj7dvn1bV65c0cCBA+Xj4yMXFxeNHj1a5cuX14MHD1SgQAHeJ/pEnx5zn7y9vVWiRAkdP35cNWrUkL29vYKDg+Xn56dChQql2aelS5ea/key1idr54jY2FhduXJFY8aMUbFixRQREaFXX31Vc+fOVWJionx9fXmf6FO6fbKzs1Px4sXNrlnBwcEqUaKEvL290+xTyv/prfVpxYoVeuGFF+Tg4GC1T0uXLlXfvn3NjnfeJ/pEnx5/n8LCwvT333+rRo0akh4OIGnUqJHpXoukNPu0fPlytW3bVpUqVZL08P+yo0ePqlWrVmratKnu3bunRo0aacqUKXJxcbHo0w8//KBu3brJ29ub94k+Zdgnb29vlSxZUidOnNBTTz0lT09P099XpUuXTrNPS5cuNd3Ts9anlNcza31asWKFnn/+eTk4OPA+2XifkhPHGTEYkzNdeSwhIUH9+vXTmDFjVL9+fVP57NmzFR0drbffftuizb///W8FBgaqf//+prKdO3fqq6++0sKFC00n9pTi4+MVHx9vWjYYDHJxcdGtW7dMcz2nfElSHgCPWp46G5sT5Tkdo633acqUKdqyZYu+//57GY1G9e/fX+3atdOYMWOs1j937pyaNWumXbt2qUyZMhbb/vPPP9WsWTPt3LlTTz31lEXsf/75p4KCgrRr1y6VLVs2V/r0T3yfnvQ+Xb16VfXr19exY8dUqFAhGY1G3blzR9WrV1dISIhKlChhNcbo6GjVqlVLM2bMUNu2bWUwGHTt2jXVrVtXlStX1vz58+Xt7a1x48bp5s2bWrFihVksV69eVcOGDbVp0yZVrlz5sfTVlt8n+kSfcivGsLAwValSRXv27DFdey5cuKDGjRvr5MmT8vDwsLqdFStW6KOPPlJISIgcHBwsYly+fLk++ugj7d+/Xw4ODjIYHo4qrFy5svbu3WtK0Fy8eFGNGzfWqVOnLL5FmNN9teX3iT79X3nKv68kqV+/fhZ/X6Wsn/Lvq7Jly1r0KeXfV2XLljXbZ6NGjdSpUyeNHj1aBoNBU6dO1U8//aSQkJAc7dM/8X2iT/Qpt8o/+eQTbdmyRYsWLZLBYDCdA0aPHm01xrNnzyooyPx/pJTbT+8c0bhxY3Xs2NG07WnTpumnn37SgQMHHktfbfl9ok8Py9O6ZiVfV1LXT/0/fep9pr5mpd5v8vGe+p4B7xN9ok+Pv09Xr15VvXr1dOzYMfn4+MhgMOjOnTuqVq2aQkJCTLOlpI49KipKtWvX1owZM9SuXTtJMt1rqVSpkhYsWCBvb2+98847unHjhlasWGG2nStXrqhhw4bauHGjaTaX3OrrP+F9ok8PpfX31ZgxY6zWz+h6k93rGe+TbfXJ0dHRYoCSNflmpJ+Dg4PKli2r0NBQU9IvKSlJoaGhphNvarGxsRYvurVEX0qOjo5mWdKUUr6o6ZVltTwntpHfyvNTLJI0atQohYWFqVmzZpKk7t27a8SIETIajXrnnXckSZMnTzbVX7p0qRo0aGC66Zp628nrU58sky1btszUPvX6/PTa5KdYcqo8P8WS1fLkb3pERkbKx8fH9HvyurSOpTVr1pgNwzcajXJ1dZUkDRw4UH5+fpKkN998U02aNFFUVJRpvfTweK1atapZwi+3+5qfXvecKs9PseRUeX6KJafK81Msqcu9vLxUvHhxHT9+XAEBAZKk0NBQlShRQh4eHmluZ8mSJerZs6fs7e3N1if/nrzewcHBVO7p6anixYsrNDTUlPRL3lfKhF9u9TWj8vzwfuR0eX6KJafKs/P3lbVjNeXfV6n3OX/+fE2cOFF16tSR0WhUlSpV9N133+V4n/LT65tT5fkplpwqz0+x5FR5fools+XJ54CgoCBJ/3cOkGT1HJDe/0hS+ueIb7/91uo54HG/lvnhdc/p8vwUS06VZ+WaJcn0JfLMHq9Go9HimpV6v8nt07pnkBN9svXy/BRLTpXnp1hyqjw/xfKo5cn3P5LvtRiNRkVEREiS2Yib1NtIOe188rrkbQ0aNEglS5aU9H/3WqKjo+Xq6mqqm3yvpUqVKrne1/z4ume3PD/FklPl2fn7ymi0/j9WRteb7F7PcqJPtlaen2J5lPLMyDcj/SRp7969mjNnjgYPHqxy5cppw4YN2rdvnz777DN5eXlp9uzZ8vHxMT3fY8WKFVq/fr2GDBlimt7z66+/VtmyZU3fEMysW7dumY0AxJPBaDQqOjratOzq6mqRSAZyUt26dfXBBx+oY8eOkh7Ou/3BBx9o//79abbp2rWr6tevr/fee8+svH79+ho9erSef/55SQ9H8TRp0kSnT582/WGblJSkhg0b6vXXX9eLL76YS70CkFnJ30JftGiRJJlGpqf1d0vyt/ZSfisvs+uzui8gp/D3FQAAAB4n7rUAeBLY3Eg/6eG0PpGRkVqxYoXCw8MVEBCg9957T15eXpIePu8j5Q2DHj16yGB4+LDWu3fvysPDQ3Xq1DGdlIGMREdH69VXXzUtf/HFF2bfAgJyWu/evTVz5kzVq1dPkjRr1izTFxms+fPPPxUSEqJp06ZZrHvhhRf07bffKigoSF5eXvrss8/UpEkTs2N4586dunv3rrp27ZrjfQGQdY86asJawi+j9entC8hN/H0FAACAx4l7LQDwf/LVSL+8xEi/J1NUVBQ3pfBYxcfHa8KECVq9erWkhzfhJ06cKAcHB6s3/CdNmqRDhw7pxx9/tNhWYmKiJk2apB9++EHSwy9OTJo0SUWKFDHVGTp0qAoUKKAZM2bkYq8APA6MnoKt4O8rAAAAPE7cawHwJMjsSD+Sfv+frST9ph7Zktch/KMkPIhT6OylpuWqrz8vhwJOeRjRP8ubNVrldQj/GNzsB0AiJfc4/Dw9r0P4R4mKS9Ara46Ylud1qSE3p3w1wYhNS3h2VF6H8I/E/1mwFe9cDM3rEIBM4XoFW8C9ltzF/1mwFbZyzbLJ6T0BANYxVRoAAAAAAEDO4V4LgH8ikn4AACBXMGoiZyU8iDNbnh26g9HpOeSdvA7gH8bV0V7zutQwWwYAAAAAALmPpB+eaPbOjqr6+vNmy8g5DOPPOQ5xCebLG+bKganScoytDOMHAFtgMBiYzhMAACAX8MXKnMUXK3MXX64E8gb/jeOJZjAYuJgDAAAAAAAAAACbR9IPAADABjA6HQAAAAByDv9jAfgnIukHADaA5yMBYHQ6AAAAAOQc/scC8E9E0g8AbADPRwIAAAAAAAAApMcurwMAAAAAAAAAAAAAkD0k/QAAAAAAAAAAAAAbR9IPAAAAAAAAAAAAsHEk/QAAAAAAAAAAAAAbR9IPAAAAAAAAAAAAsHEk/QAAAAAAAAAAAAAbR9IPAAAAAAAAAAAAsHEk/QAAAAAAAAAAAAAbR9IPAAAAAAAAAAAAsHEk/QAAAAAAAAAAAAAbR9IPAAAAAAAAAAAAsHEk/QAAAAAAAAAAAAAbR9IPAAAAAAAAAAAAsHEk/QAAAAAAAAAAAAAbR9IPAAAAAAAAAAAAsHEk/QAAAAAAAAAAAAAbR9IPAAAAAAAAAAAAsHEk/QAAAAAAAAAAAAAbR9IPAAAAAAAAAAAAsHEk/QAAAAAAAAAAAAAbR9IPAAAAAAAAAAAAsHEk/QAAAAAAAAAAAAAbR9IPAAAAAAAAAAAAsHEk/QAAAAAAAAAAAAAbR9IPAAAAAAAAAAAAsHEk/QAAAAAAAAAAAAAbR9IPAAAAAAAAAAAAsHEk/QAAAAAAAAAAAAAbR9IPAAAAAAAAAAAAsHEk/QAAAAAAAAAAAAAbR9IPAAAAAAAAAAAAsHEk/QAAAAAAAAAAAAAbR9IPAAAAAAAAAAAAsHEk/QAAAAAAAAAAAAAbR9IPAAAAAAAAAAAAsHEO2WlsNBq1ZcsWbdu2TTdv3tT9+/ct6hgMBi1btiw7uwEAAAAAAAAAAACQjmwl/b7//nutW7dOAQEBeuaZZ+Tm5pbtgDZu3Ki1a9cqPDxc/v7+GjhwoMqVK5dm/aioKC1dulR//PGH7t+/r8KFC+ull15S7dq1sx0LAAAAAAAAAAAAYAuylfTbsWOHGjRooDFjxuRIMHv37tXChQs1ePBgBQYGav369frwww81ffp0eXp6WtRPSEjQpEmT5OHhoTFjxsjHx0e3b9+Wq6trjsQDAAAAAAAAAAAA2IJsJf3i4uJUvXr1nIpF69atU8uWLdW8eXNJ0uDBg3Xw4EEFBwera9euFvW3bdum+/fv67///a8cHB52pUiRIjkWDwAAAAAAAAAAAGALspX0q1q1qv7880+1atUq24EkJCTo/PnzZsk9Ozs7VatWTWfOnLHa5sCBAwoMDNQ333yjkJAQeXh4qHHjxuratavs7OystomPj1d8fLxp2WAwyMXFxfS79PBZhSnXJ3vU8pRlOVkOAMgZBoMhT87lOX29yWp5bvcJAJDzUv/PwvUp5/oEAMg5XJ9yp08AgNxhK9enzMhW0u+VV17Rhx9+qJ9++kmtW7dWwYIFH3lbkZGRSkpKkpeXl1m5l5eXrl27ZrXNjRs3dOvWLTVp0kTvvvuurl+/rnnz5ikxMVE9e/a02mbVqlVauXKlablMmTKaPHmyPDw8TC9ebGysoqKi5ObmJmdnZ1PdmJgYxcTEqGDBgnJ0dDSVR0VFKTY2Vp6enrK3tzeV37t3T/Hx8fLy8jJ7kyIiIpSUlCRvb2+z2MLCwmRnZ2c2lanRaFRYWJgcHR2z9foCANLm7e2dJ+fyxMRERUREyNnZ2ey5uPHx8bp3755cXFxMX0yRbOP6lLJPAICcl3yO5vqUs30CAOQsrk+50ycAQM4zGAw2cX1yd3fPVH+ylfQbNWqUjEajli9fruXLl8vJycnqCLsFCxZkZzdpMhqN8vDw0NChQ2VnZ6eyZcvq7t27WrNmTZpJv27duqlTp06m5eSLdWRkpBISEkzblR6+mNHR0Wb7kx5e7K1lWiMiIqyWh4eHW8QtPTxwUpcnJiZalEsPDxBr5QCA7AsLC8uTc3nKL5vExcVZlMfExOjBgwcW5fn5+pSyTwCAnJd8zuX6lLN9AgDkLK5PudMnAEDOS07mpS7Lb9en+/fvZ+qLINlK+jVo0CDHhph7eHjIzs7O4gIaHh5uMfovmZeXlxwcHMwSjSVLllR4eLgSEhJMz/lLydHR0SxLmpK1f/jS+icwK+U5sY30ygEA2ZP6/JoX5/K8KOf6BAC2JzPXLFsvz6tYAAA5Jz9dV3KqPD/FAgDIWfntHJ+dc3+2kn6vvfZadpqbcXBwUNmyZRUaGqr69etLkpKSkhQaGqp27dpZbVOhQgXt2bNHSUlJpsTf33//LW9vb6sJPwAAAAAAAAAAAOCfyHIuzjzUqVMnbd26Vdu3b9eVK1c0b948xcbGKigoSJI0e/ZsLVmyxFS/TZs2un//vr777jtdu3ZNBw8e1KpVq9S2bds86gEAAAAAAAAAAADw+GV7OFx0dLTWr1+vgwcP6vbt25IkX19f1alTRx06dJCrq2umt9WoUSNFRkZqxYoVCg8PV0BAgN577z3T9J63b982m07U19dX48eP14IFC/TWW2/Jx8dH7du3V9euXbPbLQAAAAAAAAAAAMBmZCvpd/fuXU2YMEE3b95UiRIlVKFCBUnStWvX9MMPP2jHjh36z3/+I29v70xvs127dmlO5zlx4kSLsvLly+vDDz98pPgBAAAAAAAAAACAf4JsJf0WL16s8PBwvfPOO6pdu7bZukOHDmnatGlavHixXn/99WwFCQAAAAAAAAAAACBt2Xqm3+HDh9WhQweLhJ8k1apVS+3bt9ehQ4eyswsAAAAAAAAAAAAAGchW0i82Nlaenp5prvfy8lJsbGx2dgEAAAAAAAAAAAAgA9lK+vn5+WnPnj1KSEiwWJeQkKA9e/bIz88vO7sAAAAAAAAAAAAAkIFsPdPv2Wef1fTp0/Xuu++qbdu2Kl68uCTp2rVr+vXXX3Xp0iWNHj06RwIFAAAAAAAAAAAAYF22kn5PP/20YmNjtXjxYn399ddm6zw8PDRs2DA1bNgwWwECAAAAAAAAAAAASF+2kn6SFBQUpGeeeUbnzp3T7du3JUm+vr566qmnZG9vn+0AAQAAAAAAAAAAAKQv20k/SbK3t1f58uVVvnz5nNgcAAAAAAAAAAAAgCzIUtLvxIkTkqTKlSubLWckuT4AAAAAAAAAAACAnJelpN8HH3wgSVq8eLEcHBxMyxlZvnx51iMDAAAAAAAAAAAAkClZSvpNmDDhYSMHB7NlAAAAAAAAAAAAAHknS0m/1NN0Mm0nAAAAAAAAAAAAkPfscmOjN27c0JUrV3Jj0wAAAAAAAAAAAABSydJIv9Q2bNigM2fOaNSoUaayzz//XDt27JAklSlTRu+++648PT2zFSQAAAAAAAAAAACAtGVrpN+2bdvMEnqHDx/Wjh071KpVKw0cOFA3btzQDz/8kO0gAQAAAAAAAAAAAKQtWyP9bt26pZIlS5qW9+3bpyJFimjw4MGSpPDwcO3cuTN7EQIAAAAAAAAAAABIV44+0+/o0aOqWbOmablw4cIKDw/PyV0AAAAAAAAAAAAASCVbSb/ixYtr//79kh5O7Xn37l3VqlXLtP7u3btyc3PLXoQAAAAAAAAAAAAA0pWt6T07d+6smTNn6uWXX9aDBw/k5+enGjVqmNaHhoYqICAguzECAAAAAAAAAAAASEe2kn6NGzdWwYIFdfDgQbm5ualt27ayt7eXJN2/f1/u7u5q2rRpjgQKAAAAAAAAAAAAwLpsJf0kqXr16qpevbpFubu7u8aOHZvdzQMAAAAAAAAAAADIQLae6QcAAAAAAAAAAAAg72VppN9rr70mOzs7ffbZZ3JwcNBrr70mg8GQbhuDwaBZs2ZlK0gAAAAAAAAAAAAAactS0q9y5coyGAyys7MzWwYAAAAAAAAAAACQd7I80i+9ZQAAAAAAAAAAAACPH8/0AwAAAAAAAAAAAGxctpJ+u3fv1pw5c9Jc//nnn2vv3r3Z2QUAAAAAAAAAAACADGQr6bd+/Xo5Ojqmud7JyUnr16/Pzi4AAAAAAAAAAAAAZCBbSb9r164pICAgzfX+/v66du1adnYBAAAAAAAAAAAAIAPZfqZfdHR0muuioqKUkJCQ3V0AAAAAAAAAAAAASEe2kn4BAQHas2eP1cRefHy8du/erTJlymRnFwAAAAAAAAAAAAAykK2kX9euXfXXX3/pgw8+UEhIiG7cuKEbN24oJCREEydO1OXLl9W1a9ccChUAAAAAAAAAAACANQ7ZaVyrVi0NGzZM8+fP15QpU8zWFShQQEOHDlXt2rWzFSAAAAAAAAAAAACA9GUr6SdJQUFBql+/vo4ePaobN25IkooWLaoaNWrIxcUl2wECAAAAAAAAAAAASF+2k36S5OrqqoYNG+bEpgAAAAAAAAAAAABkUbaTfklJSdq3b5+OHz+uiIgI9e7dW6VLl1Z0dLSOHTumChUqyMvLKwdCBQAAAAAAAAAAAGBNtpJ+UVFR+t///qc///xTBQoU0IMHD9S+fXtJD5/pN3/+fDVt2lR9+/bNkWABAAAAAAAAAAAAWLLLTuPFixfr8uXLGj9+vGbNmmW+YTs7NWzYUIcOHcpWgAAAAAAAAAAAAADSl62k3/79+9WuXTtVr15dBoPBYn3x4sV169at7OwCAAAAAAAAAAAAQAaylfSLjo5WkSJF0lyfmJioxMTE7OwCAAAAAAAAAAAAQAaylfQrVqyYLly4kOb6I0eOyM/PLzu7AAAAAAAAAAAAAJCBbCX9WrRooeDgYO3du1dGo9FUHh8fr6VLl+rw4cNq3bp1toMEAAAAAAAAAAAAkDaH7DTu0KGDLl++rBkzZsjV1VWSNHPmTN27d09JSUlq1aqVWrRokSOBAgAAAAAAAAAAALAuW0k/g8GgV199VUFBQfrtt9/0999/y2g0qmjRonr66adVuXLlR9ruxo0btXbtWoWHh8vf318DBw5UuXLlMmy3Z88ezZgxQ3Xr1tXbb7/9SPsGAAAAAAAAAAAAbM0jJ/1iY2M1a9YsNWjQQM8884wqVqyYIwHt3btXCxcu1ODBgxUYGKj169frww8/1PTp0+Xp6Zlmu5s3b2rRokWqVKlSjsQBAAAAAAAAAAAA2IpHfqafs7Ozjh07ptjY2JyMR+vWrVPLli3VvHlz+fn5afDgwXJyclJwcHCabZKSkjRr1iz16tVLRYoUydF4AAAAAAAAAAAAgPzukZN+klSxYkWdOXMmp2JRQkKCzp8/r2rVqpnK7OzsVK1atXT3s3LlSnl4ePD8QAAAAAAAAAAAADyRspX0GzhwoE6dOqVly5bpzp072Q4mMjJSSUlJ8vLyMiv38vJSeHi41TanTp3Stm3bNHTo0EztIz4+XtHR0aafmJgY0zqDwSCDwWBWP7ksO+Upy7JbDgDIeY/7XJ5b15uslud2nwAAOY/rU+71CQCQc7g+5X6fAAA5x1auT5nxyM/0k6S33npLiYmJWrVqlVatWiV7e3s5Ojpa1FuwYEF2dpOmmJgYzZo1S0OHDpWHh0em2qxatUorV640LZcpU0aTJ0+Wh4eHjEajpIfPK4yKipKbm5ucnZ3N9hcTE6OCBQua9TMqKkqxsbHy9PSUvb29qfzevXuKj4+Xl5eX2ZsTERGhpKQkeXt7m8UWFhYmOzs7s2cXGo1GhYWFydHRUQULFszkKwMAyApvb+88OZcnJiYqIiJCzs7OcnNzM5XHx8fr3r17cnFxkYuLi6ncFq5PKfsEAMh5yedork852ycAQM7i+pQ7fQIA5DyDwWAT1yd3d/dM9SdbSb+GDRtmp7kFDw8P2dnZWYzqCw8Ptxj9J0k3btzQrVu3NHnyZFNZcuKuT58+mj59uooVK2bWplu3burUqZNpOfliHRkZqYSEBLNtREVFKTo62mLb9+7dM7vIJ5dHRERYLU/dn+TysLAwi/LExESLcunhAWKtHACQfWFhYXlyLk/5ZZO4uDiL8piYGD148MCiPD9fn1L2CQCQ85LPuVyfcrZPAICcxfUpd/oEAMh5ycm81GX57fp0//79TH0R5JGSfnFxcQoJCVGJEiXk7u6uOnXqWGRCH4WDg4PKli2r0NBQ1a9fX5KUlJSk0NBQtWvXzqJ+iRIl9Omnn5qVLVu2TA8ePNCAAQPk6+tr0cbR0dHqaETJ+j98af0TmJXynNhGeuUAgOxJfX7Ni3N5XpRzfQIA25OZa5atl+dVLACAnJOfris5VZ6fYgEA5Kz8do7Pzrk/y0m/iIgIvf/++7p586apbOHChRo7dqyqV6/+yIEk69Spk+bMmaOyZcuqXLly2rBhg2JjYxUUFCRJmj17tnx8fNS3b185OTmpdOnSZu2Th0+mLgcAAAAAAAAAAAD+qbKc9Pvxxx9169YtdezYUVWrVtX169f1448/6uuvv9asWbOyHVCjRo0UGRmpFStWKDw8XAEBAXrvvfdM03vevn2bB9cCAAAAAAAAAAAAKWQ56XfkyBE1bdpUL774oqnMy8tLM2bM0LVr11SiRIlsB9WuXTur03lK0sSJE9Nt+9prr2V7/wAAAAAAAAAAAIAtsctqg9u3b6tixYpmZcnLqR9oCwAAAAAAAAAAACD3ZTnpl5CQICcnJ7MyR0dHSVJSUlLORAUAAAAAAAAAAAAg07I8vack3bx5U+fPnzctR0dHS5L+/vtvubq6WtQvW7bsI4YHAAAAAAAAAAAAICOPlPRbvny5li9fblE+b968NOsDAAAAAAAAAAAAyB1ZTvoNGzYsN+IAAAAAAAAAAAAA8IiynPQLCgrKhTAAAAAAAAAAAAAAPCq7vA4AAAAAAAAAAAAAQPaQ9AMAAAAAAAAAAABsHEk/AAAAAAAAAAAAwMaR9AMAAAAAAAAAAABsHEk/AAAAAAAAAAAAwMaR9AMAAAAAAAAAAABsHEk/AAAAAAAAAAAAwMaR9AMAAAAAAAAAAABsHEk/AAAAAAAAAAAAwMaR9AMAAAAAAAAAAABsHEk/AAAAAAAAAAAAwMaR9AMAAAAAAAAAAABsHEk/AAAAAAAAAAAAwMaR9AMAAAAAAAAAAABsHEk/AAAAAAAAAAAAwMaR9AMAAAAAAAAAAABsHEk/AAAAAAAAAAAAwMaR9AMAAAAAAAAAAABsHEk/AAAAAAAAAAAAwMaR9AMAAAAAAAAAAABsHEk/AAAAAAAAAAAAwMaR9AMAAAAAAAAAAABsHEk/AAAAAAAAAAAAwMaR9AMAAAAAAAAAAABsHEk/AAAAAAAAAAAAwMaR9AMAAAAAAAAAAABsHEk/AAAAAAAAAAAAwMaR9AMAAAAAAAAAAABsHEk/AAAAAAAAAAAAwMaR9AMAAAAAAAAAAABsHEk/AAAAAAAAAAAAwMaR9AMAAAAAAAAAAABsHEk/AAAAAAAAAAAAwMaR9AMAAAAAAAAAAABsHEk/AAAAAAAAAAAAwMaR9AMAAAAAAAAAAABsHEk/AAAAAAAAAAAAwMaR9AMAAAAAAAAAAABsnENeB2DNxo0btXbtWoWHh8vf318DBw5UuXLlrNbdsmWLdu7cqcuXL0uSypYtq+effz7N+gAAAAAAAAAAAMA/Tb4b6bd3714tXLhQzz33nCZPnix/f399+OGHioiIsFr/xIkTaty4sSZMmKBJkyapUKFCmjRpku7evfuYIwcAAAAAAAAAAADyRr5L+q1bt04tW7ZU8+bN5efnp8GDB8vJyUnBwcFW67/xxhtq27atAgICVLJkSb366qsyGo06duzYY44cAAAAAAAAAAAAyBv5anrPhIQEnT9/Xl27djWV2dnZqVq1ajpz5kymthEbG6uEhAS5u7tbXR8fH6/4+HjTssFgkIuLi+l3STIajWbrkz1qecqynCwHAOQMg8GQJ+fynL7eZLU8t/sEAMh5qf9n4fqUc30CAOQcrk+50ycAQO6wletTZuSrpF9kZKSSkpLk5eVlVu7l5aVr165lahuLFy+Wj4+PqlWrZnX9qlWrtHLlStNymTJlNHnyZHl4eJhevNjYWEVFRcnNzU3Ozs6mujExMYqJiVHBggXl6OhoKo+KilJsbKw8PT1lb29vKr93757i4+Pl5eVl9iZFREQoKSlJ3t7eZrGFhYXJzs5Onp6epjKj0aiwsDA5OjqqYMGCmXoNAABZ4+3tnSfn8sTEREVERMjZ2Vlubm6m8vj4eN27d08uLi6mL6ZItnF9StknAEDOSz5Hc33K2T4BAHIW16fc6RMAIOcZDAabuD6lNdAttXyV9Muu1atXa8+ePZo4caKcnJys1unWrZs6depkWk6+WEdGRiohIUHS/2VOo6KiFB0dbaqbXH7v3j2rmdaIiAir5eHh4WYxJJeHhYVZlCcmJlqUSw8PEGvlAIDsCwsLy5Nzecovm8TFxVmUx8TE6MGDBxbl+fn6lLJPAICcl3zO5fqUs30CAOQsrk+50ycAQM5LTualLstv16f79+9n6osg+Srp5+HhITs7O4uLaHh4uMXov9TWrFmj1atX61//+pf8/f3TrOfo6GiWJU3J2j98af0TmJXynNhGeuUAgOxJfX7Ni3N5XpRzfQIA25OZa5atl+dVLACAnJOfris5VZ6fYgEA5Kz8do7Pzrnf7pFb5gIHBweVLVtWoaGhprKkpCSFhoaqfPnyabb7+eef9eOPP+q9997TU0899ThCBQAAAAAAAAAAAPKNfJX0k6ROnTpp69at2r59u65cuaJ58+YpNjZWQUFBkqTZs2dryZIlpvqrV6/W8uXLNWzYMBUpUkTh4eEKDw9n6DsAAAAAAAAAAACeGPlqek9JatSokSIjI7VixQqFh4crICBA7733nml6z9u3b5vNZ/rrr78qISFB06ZNM9vOc889p169ej3O0AEAAAAAAAAAAIA8ke+SfpLUrl07tWvXzuq6iRMnmi3PmTPnMUQEAAAAAAAAAAAA5F/5bnpPAAAAAAAAAAAAAFlD0g8AAAAAAAAAAACwcST9AAAAAAAAAAAAABtH0g8AAAAAAAAAAACwcST9AAAAAAAAAAAAABtH0g8AAAAAAAAAAACwcST9AAAAAAAAAAAAABtH0g8AAAAAAAAAAACwcST9AAAAAAAAAAAAABtH0g8AAAAAAAAAAACwcST9AAAAAAAAAAAAABtH0g8AAAAAAAAAAACwcST9AAAAAAAAAAAAABtH0g8AAAAAAAAAAACwcST9AAAAAAAAAAAAABtH0g8AAAAAAAAAAACwcST9AAAAAAAAAAAAABtH0g8AAAAAAAAAAACwcST9AAAAAAAAAAAAABtH0g8AAAAAAAAAAACwcST9AAAAAAAAAAAAABtH0g8AAAAAAAAAAACwcST9AAAAAAAAAAAAABtH0g8AAAAAAAAAAACwcST9AAAAAAAAAAAAABtH0g8AAAAAAAAAAACwcST9AAAAAAAAAAAAABtH0g8AAAAAAAAAAACwcST9AAAAAAAAAAAAABtH0g8AAAAAAAAAAACwcST9AAAAAAAAAAAAABtH0g8AAAAAAAAAAACwcST9AAAAAAAAAAAAABtH0g8AAAAAAAAAAACwcST9AAAAAAAAAAAAABtH0g8AAAAAAAAAAACwcST9AAAAAAAAAAAAABtH0g8AAAAAAAAAAACwcST9AAAAAAAAAAAAABtH0g8AAAAAAAAAAACwcST9AAAAAAAAAAAAABtH0g8AAAAAAAAAAACwcST9AAAAAAAAAAAAABtH0g8AAAAAAAAAAACwcQ55HYA1Gzdu1Nq1axUeHi5/f38NHDhQ5cqVS7P+vn37tHz5ct26dUvFihXTCy+8oNq1az/GiAEAAAAAAAAAAIC8k+9G+u3du1cLFy7Uc889p8mTJ8vf318ffvihIiIirNY/ffq0ZsyYoRYtWmjy5MmqV6+epkyZor/++usxRw4AAAAAAAAAAADkjXyX9Fu3bp1atmyp5s2by8/PT4MHD5aTk5OCg4Ot1t+wYYNq1qypLl26yM/PT3369FHZsmW1cePGxxw5AAAAAAAAAAAAkDfy1fSeCQkJOn/+vLp27Woqs7OzU7Vq1XTmzBmrbc6cOaNOnTqZldWoUUP79++3Wj8+Pl7x8fGmZYPBIBcXFzk45KuXIk0lPXzyOgQg0xx8S+Z1CECmGBwd8zqEfySuWbAVXK9gS7hm5Q6uWbAVXLNgK7he5Q6uV7AlXLNgK2zlmpXZHFa+ynRFRkYqKSlJXl5eZuVeXl66du2a1Tbh4eHy9PQ0K/P09FR4eLjV+qtWrdLKlStNy40bN9bIkSPl7e2drdgflzGFO2VcCQCAfIBrFgDAVnDNAgDYAq5XAICM5LvpPXNbt27d9N1335l+Bg8ebDbyD0DOiImJ0TvvvKOYmJi8DgUAgDRxvQIA2AquWQAAW8E1C8g7+Wqkn4eHh+zs7CxG6YWHh1uM/kvm5eWliIgIs7KIiIg06zs6OsrRRoZrArbMaDTqwoULMhqNeR0KAABp4noFALAVXLMAALaCaxaQd/LVSD8HBweVLVtWoaGhprKkpCSFhoaqfPnyVtuUL19ex44dMys7evSoAgMDczVWAAAAAAAAAAAAIL/IV0k/SerUqZO2bt2q7du368qVK5o3b55iY2MVFBQkSZo9e7aWLFliqt+hQwcdOXJEa9eu1dWrV7VixQqdO3dO7dq1y6MeAAAAAAAAAAAAAI9XvpreU5IaNWqkyMhIrVixQuHh4QoICNB7771nmq7z9u3bMhgMpvoVKlTQG2+8oWXLlmnp0qUqXry43nrrLZUuXTqPegBAejiV7nPPPcd0ugCAfI3rFQDAVnDNAgDYCq5ZQN4xGJlYFwAAAAAAAAAAALBp+W56TwAAAAAAAAAAAABZQ9IPAAAAAAAAAAAAsHEk/QAAAAAAAAAAAAAbR9IPAAAAAAAAAAAAsHEk/QAAAAAAAAAAAAAbR9IPQIaSkpIsymJiYvIgEgAAAAD450j9v5bRaMyjSAAAAPBPQNIPQIbs7Ox069YtrV+/XpK0b98+zZ49W9HR0XkcGQAA5lLePLX2pRUAAPKLpKQk2dk9vC1z8uRJJSYmymAw5HFUAABYSv5Syq1bt3Tz5k399ddfeRwRgLQ45HUAAPK/xMREbd68WYcPH9aFCxe0a9cuDRs2TK6urnkdGgAAJilvnm7cuFGXLl3SnTt3VLt2bdWvX18+Pj55HCEAAA8ZjUbTNWvZsmXatWuXnn/+eTVq1MhUDgBAfmA0GmUwGPTHH39o+fLlkqTIyEjVr19fXbt2VeHChfM4QgAp8ZckgAzZ29urZ8+e8vX11a5du/T0008rKChIEqMoAAD5R/JN0u+//14rV65UyZIl5efnp02bNmnevHmKi4vL4wgBAHgoeUTfsmXLtHXrVr322muqVq0aCT8AQL5jMBh07NgxzZo1S+3bt9cHH3ygl19+WVu2bNGFCxfyOjwAqfDXJIB0JQ/fd3BwkJubm6pXr647d+5o1apVkh7eYCXxBwDIL06fPq2QkBC988476tSpk6pVq6abN2+qfv36cnJyyuvwAAAwuXXrlg4fPqxhw4apcuXKsrOz019//aWVK1fq5MmTPE4BAJDnku8LHjlyRM2bN1erVq10//59LVu2TC1atFD9+vXzOEIAqTG9J4A0JQ/fP3/+vDw8PPTaa68pOjpay5cvV0hIiCSpW7dupm+jRkZGysPDIy9DBgA84aKjo+Xg4KDAwED99ttvmjt3rl566SUFBQUpNjZWx48fV9WqVUkAAgDyXHx8vP7++2/Z2dnp7Nmz2rZtm/7880/du3dP27Zt0+DBg1WrVi3T/2UAADwuydeeO3fuyNfXVxcuXFC1atUUHx+viRMnqnbt2ho8eLAkacOGDSpevLhq1aqVx1EDkBjpByANKefr/uijj/TLL7/o3r17cnNzU/fu3fXUU08pJCREP/30kyRp+fLlmjdvnuLj4/M4cgDAkyL5W6cpGQwGeXh4mBJ+L7zwgtq0aSNJOnnypEJCQhQeHv6YIwUAPOmszY5SokQJ1a9fX1OnTtV//vMfOTs76/nnn9cXX3whNzc3nTlzRpJI+AEAHrvke4KTJk1SRESEGjRooMOHD2v48OGqW7euXnnlFRkMBiUlJenChQs6cuSIEhIS8jpsAGKkH4A0GAwGHTp0SDNnztTAgQNVt25d0yg+Ly8v9ejRQ2vWrFFwcLB27dqliIgIvfvuu3J0dMzjyAEAT4rkm6A7duyQp6enatasqcqVK2vevHn67LPP9Morr6h169aSpLi4OP3yyy9ycXHhQfMAgMcqKSnJNDvK77//roiICEVGRqpt27YaNmyYmjdvLmdnZz311FOmNu7u7syiAgB4rJKSkmQwGGQwGHT79m1t3bpVnTp1kqenp0qVKqWEhAR5enqqVatWsrOzU1xcnH788UeFhobq3//+txwcSDUA+YHBaO0r0gCeeAkJCfryyy/l6empfv366cGDB7p9+7Z27dqlIkWKqHbt2nJxcdGZM2d07do11axZU8WKFcvrsAEAT5jbt29r7ty5io6O1vPPP6/q1avr/PnzmjJlivz8/NS8eXMlJSVp+/btCgsL0yeffCJ7e3umSgMAPHbff/+99u3bpxIlSujBgwe6cuWKRo4cqZo1a0qS6X+u77//Xnfu3NHHH38se3v7vA0aAPCP9+eff6pcuXJmy9u3b9eNGzc0bNgw+fj4SJJ27typTZs26f79+ypSpIgk6eLFi3rvvfdUpkyZPIkdgCXS7wDSdOvWLcXGxio8PFzLly/X9evXdffuXUVHR+vy5csaMGCAqlevrurVq+d1qACAJ0TqZJ2vr6+6deumzZs3a8WKFbKzs1PVqlX1zjvv6Ouvv9aKFSvk5uamokWLaty4cbK3tzcbcQEAwOOwc+dO7dy5U++9954CAgJ09OhRffjhh6ap0IxGo44ePaoNGzbIYDDoo48+4poFAMh1O3fu1I4dOzR69Gi5ubnJYDDo2LFj+v3335WQkKDo6GhT0q9p06YqUqSI/vrrL507d07+/v4aNGgQgwCAfIaRfgAkWd5ElaSDBw9q1qxZSkxMVPXq1dWwYUM1adJEq1ev1v79+zVhwgQ5OTnlUcQAgCdZRESEPD09TcvHjx/XL7/8ovDwcPXp00dVq1ZVYmKiIiMj5ezsLBcXFxkMBiUmJjJqAgDw2K1atUoREREaMGCA9u7dqy+//NL03NmYmBg5OzsrJiZGp0+fVs2aNWVnZ8c1CwCQ665cuSJnZ2cVLlxYd+/eNSX4Nm/erJ9//lmVK1fWc889p6JFi+ZxpAAyi5F+AEwJv1OnTunUqVOKjIxUtWrVVLt2bU2dOlU3b95UxYoVlfwdgbCwMBUqVCiPowYAPKl2796tLVu26KWXXjJNI1OlShUZjUb98MMPWrJkifr3769KlSrJ29vb1M5oNHLzFACQJ27cuKH4+HiFhoaaJfwkadOmTXrw4IH69Omj2rVrS3r4XCWuWQCA3Obn5yfp/7F33+FNV30fxz9JF22htAXKhrZAWWUP2XspG2UJIqIgCAoOHCCyBAfOGxVvlKksQUCGILJHZQmI7C0yC3RROtPk+YMnv7uFooy2aej7dV1cNOc3ck6T9OSc7xk3l+n85ptv1KRJE7Vs2VKtWrVSYmKitm7dqlWrVumxxx5TQEAA2yQAToA1IgDIZDJpx44dmjRpko4ePaobN27o/fff19y5c5UnTx6VK1dOknT27FnNnTtXmzZt0uOPP84sPwBAlrBarZJkDD5JTk6WJP344486c+aMcV5oaKgeeeQRnTlzRlOmTNGJEyfS3IfGKQAgK61YsUKLFy+WJDVo0EDnzp3ThAkT0gT8EhISdPToUSUkJKS5liU9AQBZyWw2y8/PT9u2bdOGDRskSe3bt1f9+vV1+PBhrV69WpcuXaJNBTgBZvoB0IULFzR79mz17NlTLVq0UEJCgjZv3ixJcnNzk3RzxM+KFSt0+vRpjRkzRiVLlnRklgEAOYTNZjM6Pvfu3avq1auradOmcnd31/r16/XDDz+oa9euxoy//Pnzq0qVKipXrpyCg4MdmXUAQA6WlJSkS5cuKSoqStLNmRQlS5aUxWIx9ki6fPmy5s+fr6ioKL322muS0t92AQCAzFaiRAl17dpVP/30k9avXy9Jatq0qTp06CCTyaTVq1fL1dVV3bt3ZyY6kM2xpx8AnThxQt99953Gjh2rS5cuacyYMapevboGDBggSbp69ary58+vkydPys/Pz1jfGwCAzGS1Wo2A37lz5/Tqq6+qXbt2euqppyTdXOZzw4YNcnV1Vdu2bRUUFKSvv/5apUqVUufOnWUymdLcAwCArLR37159/PHHGjlypMqXL6/w8HAtWrRIR48eVUREhIoUKSIvLy+NHDlSrq6u1FkAgCxhH2By9epV3bhxQ97e3vL19ZWrq6tOnz6t5cuX68qVK2rWrJmaNm0qSfr5559Vs2ZNBQQEODj3AP4NQT8gB7JX7n/88Ye8vb1ltVr1n//8R0OHDtXnn3+uypUr67nnnpPZbNahQ4e0bNkyDRgwgGAfACDLpJ7psHLlSv3999/atWuXYmNj1bp1a/Xr10+StH37dm3btk07d+5U4cKF5eLiog8//FAuLi7MlgAAZIl/qm8mT56sxMREvfDCC/Ly8lJ8fLzi4+N15swZBQQEqEiRIjKbzUpJSWHmBAAg09nrrJ07d+qHH35QTEyMChYsqKCgIPXq1UseHh5G4C8iIkL16tUzlqQG4BxY3hPIgUwmk44cOaKPPvpI/fv3V9WqVVWsWDGNGzdONWvW1IABA4x9k/bt26fExES5uvLnAgCQ+eyNUHvn6cKFC7Vq1SoNGDBA1apV08GDB7VlyxalpKSof//+qlOnjkqXLq1WrVopLi5OtWrVktlsZrYEACDL2OusJUuWKE+ePCpVqpSx7HTVqlX1448/KjY2Vl5eXvLw8JCnp2eaAZVWq5WAHwAgS5hMJu3bt09ffvmlunfvrnr16mnt2rVasWKFoqOjNWjQIAUFBalDhw5asGCBdu/erQYNGsjLy8vRWQdwl+jFB3KgK1euaM+ePercubMaNWokSapZs6bCw8Pl6uqqv//+W0lJSfrtt9+0bt06jR07Vj4+Pg7ONQDgYRcbG6vcuXMbj+Pi4nTo0CF17dpVderUkSRVqFBBRYoU0dy5c+Xh4aE+ffoof/78yp8/v3EdAT8AQFaz2Wy6du2awsLClJSUpBo1aqhZs2Zq2LCh1q5dq4ULF2rw4MHp1k/UWQCArBITE6MVK1bo8ccf12OPPabr169r3bp1CgoK0rlz5zRlyhS98MILCgwMVM+ePZU7d24CfoCTIegH5DDnz5/XlClTFBkZqY4dOxrpLVq0UHx8vPbs2aPXXntNJUqUkNls1ujRo1WiRAkH5hgAkBNMnTpV165d01tvvWWkubq66tq1awoPDzfS8uTJowYNGuj333/XypUrZbVa1bdvX0kylkaj8xQAkNluHWBiMpn03HPP6fz58zp79qx++OEHHT16VD4+PipXrpz279+vixcvqnDhwg7MNQAgp/Px8VH9+vVVsmRJxcTEaPTo0apRo4b69eunmTNnas2aNYqLi9Orr75KfyDgpAj6ATlM0aJFVbp0aW3atEn79u1LM0W/ffv2atGihc6dOyc/Pz95eHgoT548Ds4xACAn6NKli3x9fSVJCQkJypUrl9zd3fXII4/ozJkzOnXqlIKDgyVJuXPnVmBgoNzc3LRjxw5ZLBY999xzcnFxYZYfACDTpa5rjh07pvj4eHl4eKhcuXIqWrSoihYtqipVqujEiRNas2aN0YH6xx9/EPQDADhc06ZNJUmrVq1SQECAunXrJrPZrBIlSqhkyZJyc3NTbGyscuXK5eCcArgfBP2Ah1x6m8r37dtXrq6u2rNnj37++Wc9+uij8vb2liR5enqqTJkyjsgqACAHsy/PuXHjRs2cOVOffvqp/Pz8VKVKFe3Zs0dr1qxRs2bNFBISooSEBJ0/f16VK1dWzZo1tWDBAiUkJGjIkCEE/AAAmcpmsxl1zdy5c7Vz507Fx8erQIECKlSokIYMGSJJ8vLyUuXKlVW5cmUdO3ZM27Zt05o1a1SzZs00S1IDAJBZ7H2CZ86c0aVLl2SxWFSiRAljBl94eLiuXLlibLFw8eJFVa9eXe3bt2dJT8CJEfQDHmL2yv348eM6evSoXF1dFRAQoOrVq6t3796yWq3atWuXJBmBv/SChAAAZJbUsyWSk5NVrlw5FSlSRGPGjNGYMWMUGhqqHj16aPHixfrvf/+rXLlyKTk5WSkpKWrTpo1SUlKUlJSkVatWKSoqypgtCABAZrC3lZYuXaoNGzbo1VdfValSpbRw4UL99NNPiouL0+uvvy7pZr3m5uamkJAQmc1m7du3T1evXiXoBwDIEiaTSTt27NA333yjwMBARUZGysPDQ3Xq1FGHDh0UEhKiI0eO6L333lPevHm1Y8cOffDBBwT8ACdnstlsNkdnAkDGswfvtm/frilTpigwMFA3btzQ+fPn1aZNGz399NOSpJkzZ+r48eOqUKGCOnfuTMUOAMgyBw4c0JUrV9S0aVNNnTpVVqtVAwYMUHh4uL788ktFRETo3XfflZ+fn86cOaOLFy/q8OHD8vPzU/v27eXqenP8WkJCgqxWK3UYACBLXLhwQdOnT1fbtm1VrVo17du3T5988okaNWqk33//XaVKldJrr70m6X/7zUrSyy+/rNatW6tNmzaOzD4AIIc4ffq0Jk6cqK5du6pVq1Y6fPiwxo8fr06dOqlbt26Kj4/X5s2b9eeff8pqtapHjx7s4wc8BJjpBzwk0ttI/tKlS5oxY4Z69eqlVq1aKTY2Vvv379eUKVNkMpnUp08f9e3bV1OnTtWJEydksVgcWAIAQE5hs9mUlJSkFStWKC4uTtu3b9exY8c0duxYmc1mFSpUSIMHD9aXX36pt99+W++++64CAwMVGBiounXrGvexd6Sy1wQAILOlbm8VKVJEDRo0UFBQkI4ePaqvv/5affr0UYsWLWS1WrVu3TqNGjVK48ePNwJ+YWFhioqKUuXKlR1ZDABADvL333+rWLFiatWqlcLDw/XFF1+oSZMm6tatmyQpPj5erVu3VuvWrZWUlCR3d3cH5xhARmDTE+AhYG+Anj17Vvv27TPSr1+/rly5cqlGjRqSpNy5c6tevXp6/vnntWbNGh04cECSNGDAAA0bNkw+Pj6OyD4AIIcxmUzy8PDQsGHDdP36de3bt0/t2rVLM6rUHvjz9/fXmDFjFBERcdt97B2pAABklj179uj777/XZ599plOnThnpTZo0ka+vr/bs2aPKlSurUaNGkm7WXzVr1lSRIkVktVqN8wsUKKD33ntPRYoUyfIyAAByltQL+/n5+SkiIkKjR49WlSpV9Nxzz0m6uerK+vXrFR0dLUlyc3NzSF4BZDyCfoCTswf8/vrrLw0fPlwnTpwwjnl4eOjy5cu6ePGipP9V+hUqVJCfn58iIyONc/PmzZu1GQcA5Ej2DlD7bL+CBQuqYsWKOnjwoDZs2GCcZ7PZjMCfdHM5agAAstLatWv15Zdf6vLly7p27ZpGjx5ttK3sLl68qPPnz8vd3V0Wi0XHjh1TxYoVNWjQIJnNZqWkpEiSypQpo0KFCjmiGACAHMa+/6yfn5/CwsI0bNgw1a5dWwMGDDBmrW/fvl1nz541ZvfZrwHg/FjeE3Bi9oDfmTNn9Pbbb6tz58564oknjONFihRR1apVtXr1anl5eSk4OFiS5OPjo9y5cxsNUAAAskLqpdF27dqlcuXK6c0331RsbKymTJmijRs3ymQyqUmTJkaj09/fX2PHjmU2OgAgS61du1bTpk3Tyy+/rJo1ayo+Pl5jxozRpUuXlD9/fmNGRKNGjTRr1iy98cYbMpvNSkxM1Msvvyzp5gAWZqUDADKbzWaTyWTS6dOndfXqVXl7eyskJESVKlVS9+7dNX/+fJUqVUrR0dFKSUnRzz//rN9++01jx46Vp6eno7MPIIOZbKnn+wJwOhcuXNDw4cP1+OOPq0uXLkb677//rooVK+rPP//UihUr5OXlpRYtWiggIECbNm3Sxo0bNXHiRAUEBDgw9wCAnMLeEJWkuXPnatu2bWrZsqVat24tT09PRUREaNq0aYqLi1P9+vXVuHFjTZgwQcHBwerTp4+k2/evBQAgM+zZs0cffPCBBg8ebCzbKUlDhw5VsWLFdPbsWdWsWVONGjVS8eLFtXfvXv3xxx/y9PRUjx495OLiQp0FAMhS27dv19SpU+Xu7i4PDw8FBQVp8ODBcnNz08yZM/XLL7/I399fuXPnVnx8vF5++WUFBQU5OtsAMgFBP8CJJSUl6auvvtKff/6pl19+WaGhoZKkxYsX69dff9Xbb7+tokWLaufOndq6dat27typwoULy2q1atiwYVTuAIAst2jRIq1atUpvvfWWihUrply5chkBwYiICH333Xc6c+aMkpKS5OXlpffee0+urixOAQDIOkuWLNHmzZtVrVo1Pfnkk3J1ddVHH32kU6dOqU2bNoqJidGqVatUq1YtvfDCC8bSaHYpKSnM8AMAZAp7V77JZDLaUbGxsfryyy9Vt25dVahQQX/88Yd+/fVX+fj4aPjw4XJzc9ORI0cUExMjT09PFStWTH5+fg4uCYDMQtAPcHIHDhzQ6tWrdePGDfXq1UvHjx/XwoUL9dJLL6lq1arGeRaLRVeuXJHValWePHlYJg0AkOlWrlypunXryt/fX5IUFRWlzz//XG3atNEjjzyiiIgIXbp0SRs3blRISIiaNWumGzdu6OTJk4qOjlbDhg2N/ZDoPAUAZJWUlBStWLFCu3btUunSpXXp0iVFRkbq1VdfNVZKWbZsmebMmaNPPvlERYsWdXCOAQA5xdWrV5U/f37j8fHjx7VkyRKZzWY999xz8vX1lcVi0e7du7VkyRLlyZNHr7/++m0DVAA8vBg2DTi50NBQmc1mrVixQpMnT9bVq1c1evRohYSEpBn94+LiosKFCzs4twCAnCIsLEzHjh3To48+aqS5ubnp8uXLOn78uPz8/LRy5UpdvnxZuXLl0qZNm5SUlKTHHnsszaAVq9VKwA8AkGXs9U67du1ktVq1detWXb16VePHj1dAQICSkpLk7u6uQoUKqUSJEtRRAIAss3HjRi1atEiffPKJUf8cP35c58+fV1JSknx9fSVJrq6uqlmzpiRp+fLlGjt2rMaMGWPsRwvg4cYC84ATswf1KlSooA4dOqhw4cIqVqyYEhMTJcnYO+nWnwEAyGz16tXT0KFDZTab9ccff+jChQvy9vZWp06dtGXLFo0fP14FChRQz549NWbMGDVu3Fhnzpy57T7shwQAyEpms9kI/HXs2FGNGzdW0aJFtXbtWsXGxsrd3V1Wq1Xr1q1T/vz5VbBgQUdnGQCQQ5QpU0bvvPOO3N3dlZCQIBcXFzVq1Eht27ZVSkqK/vOf/xjn2gN/jz76qNzd3RUdHe3AnAPISizvCTg5+/rdknT48GEtX75c8fHx6tChg6pVq3bbOQAAZDar1Sqz2Sybzabjx4/r888/V/Xq1dWpUyfly5dPERERio+PN5ZDs1qtGj9+vMqXL69u3bo5OPcAAPyvLktJSdGyZcu0e/dulSpVSj169NCXX36pCxcuaNKkSXJ1dTXOBQAgK5w5c0bjxo3T8OHDVb58ecXFxWnz5s1av369AgMD9cILLxjnWiwWJScny9PT04E5BpCVCPoBD4HUQb1Dhw5pxYoVSkpKUuvWrVWrVi0H5w4AkJOkN9Dkp59+0m+//aayZcvq0UcfVaFChSRJCQkJ+uuvv7R48WJFRETo/fffZ5k0AEC2kTrwt3z5cv3+++/666+/5Ofnp48//liurq7sOwsAyHLnzp3TnDlzdOrUKb366qsKCQnRjRs3jMBf6dKl9fzzzzs6mwAchKFogJNKHa83mUy3LfWZkpKiDRs2KCEhwVFZBADkMKkDflu3btWqVaskSR07dlS9evV05MgRrVq1SuHh4ZJuDlRZvny5rFar3nvvPbm4uMhqtTos/wCAnOOf6hv7sdRLfbZv316hoaGqWrWqPvnkEwJ+AACHKVasmHr16qXy5cvrww8/1LFjx+Tt7a1GjRqpRYsW2r9/v6ZPn+7obAJwEGb6AU7A3okaHh6u2NhYlShRQq6urnc8T5KOHTumfPnyKV++fFmdXQBADpR6abMTJ05o3rx5ioqKUpcuXVS/fn1J0rJly7Rt2zaVL19eHTp0UN68eXX69GkFBwcbMynoPAUAZLbUddbGjRv1119/SZICAwPVuHHjO55vtVplMplkMpmoswAAmcbev5eQkCBXV9c0fYCp+/7OnTunRYsW6cCBA3r99dcVEhKi2NhYbd++XZUqVWLfWSCHIugHOIkdO3bo22+/ldlslpeXl3r27KnKlSsrV65cac5j/z4AgCPNnTtX58+f1/Xr1/XXX38pf/78ateunZo2bSpJWr58ubZt26bixYurV69e8vX1lST2QwIAZLnvv/9emzdvVp06dZScnKwdO3aoSZMm6tOnz23npm5n0eYCAGS2qKgojRw5Uj179lSdOnXSHfwv/S/wd/jwYQ0dOlQVKlSgngJyOHpWgGzOZrMpIiJCixcv1uOPP64RI0aoaNGimjNnjsLCwm5bvpNKHQDgKOvXr9cvv/yijh076s0339To0aNVsGBBrVu3Tps2bZIktW/fXjVq1JDJZJKPj49xLQE/AEBW2r9/v7Zv367XXntN/fr1U6VKlZScnKxixYqlOc8+Tjp1O4s2FwAgs/n6+qpkyZKaMWOGdu/eLYvFkua4vX4qVqyYnnjiCQUFBenrr79WUlKSI7ILIBuhdwXIpuyVt81mU+7cuVW+fHk1adJEJUuW1GuvvaaQkBD99NNP6Qb+AABwhLNnzyokJEQhISHy8vJScHCwunbtKpPJpKVLl2rr1q2SpK5du2rgwIHGUmkAAGS21O0rSbpy5Yry5cunkJAQ7dixQ//973/Vp08fNWvWTAkJCTp48KAkAnwAgKxnbyO9/vrrqlq1qqZMmXJb4C/17HOz2awnn3xSY8aMkbu7O3UXkMMR9AOyKZPJpD179uizzz7TmDFjdPr06TQdo4MHD1aZMmW0cuVKbdy4kcAfAMBh7PWTj4+PEhISFBsbK+lmAzQoKEiPPfaYrly5ojVr1hiBP7PZbDRQAQDIbPYO0OvXr0uScufOrfz58yssLExffvmlevfurZYtW0qSDh8+rF27dikyMtJh+QUA5FypB0e++OKLqlmzphH4S05ONs6zWCz66quv9Mknn6hAgQLy9/d3VJYBZCP0sgDZ1LFjx/Thhx/K29tbZrNZZ8+e1U8//WR0pErSkCFDVLhwYW3atImZEgCALHNrnWMP3AUHB+vUqVPasmWLUlJSjA5WDw8PVa5cWb6+vtq+fbvi4uIkMXsCAJC11q1bp59++kmS5O/vr927d+vzzz9Xr169jIBfUlKSfv75ZyUkJBj7zgIAkBVSt7PuFPjbs2ePLBaLbDabZs+erR07dmjgwIHy9PR0VLYBZDMmm31tCwDZxoULF7Rjxw65ubmpXbt2kqRZs2bpyJEjqlmzph599FF5eXkZ50dERDCaBwCQJaxWqxHk+/333xUTE6OUlBTVrVtX3t7eWrlypWbPnq0ePXooNDRU+fLl09SpUxUSEqIyZcro3Xff1bvvvqsyZco4uCQAgJxm0aJFWr58uSZPniwfHx+FhYXp888/V8eOHVWuXDl5eHho8eLFiomJ0fvvvy8XFxfZbDYGqQAAMoW9bRUXF2f086Vub936ePLkydq9e7eef/55HTp0SBs3btT48eMVFBTkkPwDyJ4I+gHZzOXLlzVlyhRduHBBXbp0UZs2bYxjs2bN0uHDh1W7dm21atVKuXPndmBOAQA52ezZs7Vt2zZ5e3srKSlJCQkJevHFF1WlShWtXr1aP/30kywWi9zd3ZUrVy69//77ioqK0oQJE/TKK6+oRIkSji4CAOAhZe/msAfr7B2mFotFEyZMUGBgoHr16iVXV1dt3LhRS5YsUWxsrAoVKiQfHx+9+uqrcnV1va3jFQCAjHb+/Hm988476tixozp06CDpnwN/X331lTZt2iR3d3eNGzeOgB+A27g6OgMA0sqfP79CQ0N15coV7d69W02aNFGuXLkkSU8//bS+++47bdiwQa6urmrfvj2jTgEAWW7r1q3atGmTRo0apfz588tsNmvq1Kn67LPPNGLECLVp00YVK1ZUXFycEhMTFRoaKrPZrFWrVslkMilv3ryOLgIA4CF2axsp9T6yZcuW1YEDB2SxWOTq6qomTZqoatWqSkxMlJubm/z8/GQymZSSkiIXFxcHlQAAkBNYrVZt2LBBsbGxWrlypSwWi7p06WIs7WkP9KV+/MILLyhv3ryqX7++AgMDHVsAANkSQ9YAB7t1sq2Li4s6d+6s1q1b6/r165ozZ46x95EkPfXUU6pXr57q1KlDwA8A4BDXrl1TqVKlFBgYKC8vL3l5eWnYsGEqW7asvv76a1ksFhUvXlxly5ZV5cqV9ddff+k///mPNm3apKFDhxL0AwBkiu+++05HjhwxHq9fv14TJ07UpUuXlJCQILPZrHbt2unixYtaunSpcZ6vr68KFiwof39/mUwmWa1WAn4AgExnNpsVHBysAgUKqHHjxtq0aZMWL15sHLt1j7+UlBRJUq9evQj4Abgjgn6AA9n3hzh69KgWL16sRYsWaefOnXJxcVG7du1Ut25dnTp1SnPnzk0T+OvZs6cCAgIcmHMAQE5248YNnTt3TtLNxmdycrIkqW3btoqPj1d4eLhxrn2mhLu7u0aPHk3jFACQKc6fP6/Y2Fhjz1j74Mr4+HiNHTtW06dP1549e5Q7d2517dpVp06d0vnz59O9F0t6AgAyg81mM+onewCvXr168vPzU3h4uJo2bapff/1VS5YskXR74I8BKQDuBt9kAQcymUzavn27JkyYoD///FN79+7Vxx9/rG+++UbJycnq0KGDatSoobNnz2r69OmKj493dJYBADlM6kamXYMGDeTm5qbvvvtOkuTm5mb87+bmlmYmuouLi0qUKKHnnnuOffwAAJmmaNGiGjRokFxcXLR161bt379fzZo10/jx4/XEE0/IZDLpww8/1LRp03ThwgWFh4cbA1gAAMhsVqtVJpPJ6NtLHcB79NFH5ebmpurVq6tZs2ZavXq1MSP91sAfAPwb9vQDHCg8PFyzZ89W79691apVK1mtVu3fv18ff/yxTCaTnnvuOXXo0EFJSUk6ceKEEhMT5enp6ehsAwBygGPHjikwMFDu7u63bSQfEBCgRo0aac+ePZo6daq6deummJgYLV26VPny5VPBggVvu5+rK187AQCZy2azKTo6Wj/99JN8fX2VnJysmjVrqnnz5mratKkaNWqkX375RXFxcbp48aI2b96sRx55xNHZBgDkAGazWRcuXNDo0aNVo0YNNWjQQCVKlJCPj4+Cg4M1Z84cVa9eXY8//rhMJpNWr14tk8mkjh07MgMdwD2h9wXIImvXrlWJEiVUpkwZYwZEQkKCXFxcVKFCBeO8qlWr6uWXX9YHH3ygGjVqqFq1aurWrZtu3LihPHnyOCr7AIAcJDw8XFOnTlWBAgX0yiuvyM3NzQj82Ww25cqVS61bt5aHh4fWr1+vF154QQULFpSXl5fGjh1728bzAABkBZPJJF9fXz3//POaN2+efv31V0lSzZo1ZTabFRoaqsDAQMXExGjLli16/PHHHZxjAMDDzt4uslqtWrt2rWJiYrRjxw65urpq2rRp6tWrl0JDQ/Xkk09q7dq1qlKlipo1ayaz2ayFCxfK1dVVbdu2dXQxADgRgn5AFrDZbFq0aJE8PT01ZMgQBQcHy2QyyWQyKTw8XNeuXVOxYsWMcytUqKBixYrp8uXLkm6OBiLgBwDIKn5+fmrdurU2bdqkyZMna8iQIWlm/FmtVuXOnVuPPfaY2rRpo9OnT8vFxUWBgYHGBvPsNwEAyGypB5ik/rl06dLq0aOH5s6dq19//VVms1nVq1eXJHl5eSl37tzq3r27JMlisTAbHQCQacxmsy5fvixPT0+1bdtWNptNGzduVGBgoIKDg7Vo0SKtWrVKrq6uunHjhi5fvqygoCA1adJErq6uRv0FAHeL4ddAJrPZbDKZTPriiy/k5uamr776SidPnlRKSoqKFy+u+vXra9GiRTpx4oTMZrNMJpPc3d3l4eHBDAkAQJaz2Wxyc3NTkyZN1KxZM125ckVffPGFkpKSjICevX6KiYnRL7/8ouLFiys4ONgICBLwAwBkttRBvjVr1ujbb7/V559/rh07dig+Pl5lypTRk08+qaSkJK1Zs0Z79uyRpNvaWAT8AACZKSkpSV999ZWmT5+ufPnyqUWLFnrkkUc0Z84clS5dWu+8844effRRXbt2TadPn9a1a9ckSf7+/mrXrp0KFy7s4BIAcDZEFIBMZjKZlJycLFdXV40bN05JSUmaM2eOTp06JUlq1qyZvL29NW3aNO3atUvHjh3TvHnzdPnyZVWpUsXBuQcA5DQmk8kI/DVs2FAtW7ZME/hzcXEx9kz6+OOPtXXrVrm7uxvXM2AFAJAV7PXNnDlztGDBAnl5eSkxMVFLly7VokWLFBcXlybw9+OPP+rYsWMOzjUAIKdxdXVVYGCgLly4oKSkJBUtWlTt27dXjRo1NHr0aJ06dUo1a9bUuHHj9Mknn6hmzZqy2WySaFsBuD8mm/2vCIBMYZ/pFxYWpoMHD+rChQs6dOiQAgMDNXDgQAUFBenQoUPauHGjtmzZokKFCslkMunFF19UUFCQo7MPAMih7PVXcnKytmzZol9//VUFChTQSy+9JJvNpvHjx+v69euaNGmSXF1d2cMPAJDpbq1rNm7cqB9//FEvv/yygoODtXv3bk2aNEnFihVTxYoV1aNHD3l5eenw4cP67bff1LdvX+oqAECWsbepoqKi9PLLL6tt27Z64oknJEkXL17UkiVLtGvXLg0bNkxVqlQxgn0mk8mR2Qbg5Aj6AVng8OHDmjBhgvr166fixYsrJSVFX3/9tcxmc5rg3uXLl+Xi4iIPDw/28AMAONytgb+1a9fK19dXMTExunHjhhHwYw8/AEBWiIiIkL+/v6xWqyRp/fr1ioyMVNeuXbVz505NmTJFXbt2VUREhNavX68mTZqoS5cuyp07t3EPBqkAALKSvc767rvv9Ndff+mll16Sr6+vpP8F/vbu3avBgweratWqjssogIcG33SBLHDy5EmVLFlSjRo1UpkyZVSuXDlNnDjRCP4dP35cKSkpKliwoPLnz0/ADwCQJewN0FulHmF661Kfp0+fVkJCAgE/AECWOnPmjAYNGqTt27fLbDbLbDarXr16atmypSIiIrRw4UJ16dJFjz32mB577DG5ubnpt99+0/r16yWJpdIAAJnK3rZKSEhQUlKSkWavs2rUqKHDhw8b2/1IUuHChdWlSxeVL19e3377rRITE8X8HAAPih2rgUxknyERFxenuLg4Y5P4pKQkeXl56ZlnntF7772nqVOnatCgQQoODnZwjgEAOUXqmQ5nz56Vm5ub/P395eHhYQT7TCZTmsBfgwYNlDdvXlWtWlVms5mAHwAgy/j6+qp58+b6z3/+IxcXF9WqVUu5cuUylu+Mj49XtWrVJEnR0dEqW7asKleurGbNmkliqTQAQOYym826evWqPvjgAxUvXlzt27dX8eLFjTZXaGio6tatq2XLlikkJMSYhV6oUCH16tVL7u7u8vDwcGQRADwkGOIGZCJ7w7Ju3bqKiIjQkiVLJEnu7u6Sbm7mW6NGDbm6usrb29th+QQA5Dz2xuf333+vjz76SMOHD9e3336rffv2SfrfLL/UP7u5ual69eoym82yWq0E/AAAWcbX11fdunVTy5Yt9dFHH2nXrl1pZu25u7tr9+7dOn/+vBYsWKBcuXKpefPmRp0FAEBmSF3H+Pv7q2LFirp+/bpGjBihr7/+Wps2bTKO16lTR1euXNGVK1ckSRaLRZJUsGBB+fn5ZW3GATy02NMPyED2WRFnzpzR33//raJFi6pAgQLKkyePFi9erA0bNqhp06bq0qWLEhIStGTJEiUmJuqpp56i4xQAkCVSz/DbvXu3Zs2apeeee06RkZHatGmTXF1d1bJlS9WuXVvS/+o2AACy2rVr1+Tu7p5m+4OoqCgtXrxYv/zyi1577TXVqlVL8fHx+u6773TgwAElJiYqX758GjdunFxdXanHAACZxt62Cg8P1969exUaGqqiRYsqJSVFW7Zs0c6dO/Xnn3+qVKlSqlevnpo0aaJ3331Xfn5+evnllx2dfQAPKYJ+QAbbsWOHvvrqK/n4+OjGjRtq0KCB2rZtq7x582r16tVasmSJcufOrVy5cikiIkLvvPOOgoKCHJ1tAEAOs3//fu3evVuFChXSY489JunmHrQLFy6UxWJRq1atCPwBABxm+/bt+u9//ys/Pz81b95cefPmVYMGDSTdnBnx3XffafXq1Xr55ZdVp04dJSQk6NKlS4qPj1fZsmVZhhoAkKnsAb+zZ8/q448/VvHixdWkSRPVrFnTOCcuLk5Xr17V/PnzdfHiRSUlJalQoUI6deqURo4cqdKlSzuwBAAeVgT9gAxg7wy9evWqpk2bpho1aqhhw4bauHGjtmzZooCAAHXr1k2FChXSpUuXtHv3bnl5ealChQoqVKiQo7MPAMhhLl26pA8++EBXr15VmzZt1KtXL+OYPfCXkpKixo0bGx2sAABkFYvFopkzZ2rz5s3y8PBQkSJFdOXKFXl6eqpw4cJq3bq1TCaTDhw4oCVLluitt95S1apV09wj9cx2AAAyw/nz5/X222+rZcuWatOmjfz9/dM9Lzk5WdeuXdOvv/6q3377TYmJiZo0adIdzweAB0HQD8ggJ06c0ObNmxUREaEBAwbIx8dHkrRp0yb9+uuvCggIUMeOHVWyZEkH5xQAkNPYB6eknrG3f/9+zZs3TyaTSb169VLFihWN80+dOqXp06erdOnS6tu3r4NyDQDIyaKiorR06VKFh4erWLFiateunXbu3Kl9+/bpr7/+MmZLHDt2TFarVe+9956Cg4MdnW0AQA6RlJSkL774Qnnz5tWzzz5rpFssFkVHRysxMVFFihSRlHbllHPnzil37tzy9fV1RLYB5ACujs4A8LDYv3+/wsLC5OLiori4OCPo17hxY0nShg0bNH/+fPXq1UvFihVzZFYBADlI6pkOSUlJcnNzk8lkUuXKlWW1WrVw4UL98ssvMpvNKl++vCQpODhYgwYNUuHChR2ZdQBADubr66sOHTpoyZIl+vPPP+Xv7682bdqoRYsWOn/+vKKiorRx40ZZLBZdv36dwZUAgCzl4uKi6OhoVahQwUjbt2+f9u3bpw0bNihPnjwqWLCg3n77bZlMJmPJafoEAWQ2ZvoBGWj16tVauXKlqlSpoo4dO6pAgQLGsbVr12rnzp0aOHAg0/cBAFkidcBv+fLl+vPPP5WSkiI/Pz/169dPXl5e2rdvnxYuXKh8+fLpscceU7ly5e54DwAAslpkZKSWLFmiEydOqGbNmurSpYtx7NaZ7OzhBwDIKnFxcRo5cqTKlSun9u3ba8eOHdq0aZOKFy+u8uXLK1euXFq6dKlq1qypPn36ODq7AHIQgn7AfbA3KhMTE2Wz2ZQrVy7j2NKlS/Xbb7+pQoUKatu2rfLnz28ci4uLk5eXlyOyDADIwebNm6e1a9eqXbt2iomJ0YEDB3Tjxg29/fbbKlKkiPbs2aMlS5bI3d1dTz31lAIDAx2dZQAADFFRUVq8eLFOnjypWrVqqVOnTpKUJsjHIBUAQFY7cOCAJkyYIH9/f8XGxqp3796qVKmSChUqJIvFog8++EC+vr4aPHiwo7MKIAdheU/gHtkDfnv27NG6dev0999/q3bt2qpQoYKqV6+uTp06yWazafv27XJxcVGrVq0UEBAgSQT8AABZ7vLly9qxY4cGDRqkmjVrSpKio6M1efJkvffee/rss89UvXp1paSkaO/evSpRooSDcwwAQFq+vr7q0qWLFi9erN9//10JCQnq0aNHmll9BPwAAFktNDRUkydPVkxMjPLnz29s9SPdrJc8PT2VL18+2efc2Pf1A4DMxLdi4B6ZTCbt3r1bn376qYoXL6727dvr9OnT+vHHH7V161ZJUufOnVWvXj399ttvWr9+vVJSUhycawBATvDOO+9o586dadLi4uIUGRlpDECxWq3KmzevBgwYIElat26dJKlWrVoaMGCAzGazrFZr1mYcAIB/YQ/8FSxYUNHR0WLRIgBAdpA/f34FBwenCfhZLBb98MMPOnr0qBo3biyTyUTAD0CWYaYfcI8uXLigefPmqU+fPmrZsqWSkpK0YMEC5c6dW6tWrZLZbFa9evXUsWNHubi4qHbt2uwrAQDIEg0bNlS1atXSpBUvXlx+fn7atm2bSpQoYcyE8PHxkYeHh+Li4m67D7MlAADZka+vr/r27SsvL680e/kBAJBdbN68WSdPnlRYWJhGjBihwoULOzpLAHIYenSAO7jTyFF3d3dVr15ddevW1bVr1/Tqq6+qTp06eumllxQdHa2ffvpJ69evlyS1a9fOmFkBAEBma9mypdzc3LR48WKtXr3aSK9Zs6YOHz6stWvXGmmurq7y8PCQh4eHI7IKAMB9yZ07tzErnYAfACA7uXDhgjZs2KBr165p9OjRCgoKcnSWAORAzPQD0mHfBP769euKjo6W1Wo19jjy9/dX+/btlTt3bs2dO1dlypRRr1695OnpqTJlyujIkSPavXu36tSpI09PTxqiAIBMZ6+37OLi4rRgwQK5ubmpefPmatu2raKiorRmzRrt2bNHISEh2rt3rxISEtSqVSsH5hwAgPvDrHQAQHZTpEgRDRs2TG5ubvLy8nJ0dgDkUAT9gFvYO07Pnj2rKVOmKCYmRpJUuXJlPf/88zKbzcY63RcuXFBwcLA8PT0lSZ6enmrXrp3q169P5Q4AyBKpA35Xr16Vv7+/evfuLS8vL02dOlVWq1UtW7bUU089pV27diksLEwHDx5UwYIFNWrUKLm4uNwWNAQAAAAA3Lu8efM6OgsAcjiCfkAq9k7PM2fOaNSoUWrZsqVq1Kih7du3a926dQoKClKrVq1ktVqVnJys/Pnz68KFC1q7dq0uXbqk33//XU888YR8fX0dXRQAQA6QOli3aNEiXbhwQc2aNVNoaKi6dOkiq9Wqb7/9VtLNpT9btGihFi1ayGKxyNX15tfAlJQU9p4FAAAAAAB4CBD0A1Ixm826dOmSRo4cqfbt26tHjx6SpAIFCmjdunW6dOmScZ6Hh4caNmyolStX6qeffpK7u7vefPNN+fv7O7IIAIAcxB7w+/7777VhwwY9//zzKlasmHH8iSeekNVq1fTp0+Xi4qKmTZvKZDIZAT+bzUbADwAAAAAA4CFB0A9IxWq1av369fL09FSePHmM9G3btiklJUWXLl3SypUrlTt3btWtW1dVqlRRxYoVFRsbm2bZTwAAssrevXsVFhamUaNGKTAwUFarVdHR0Tp//rxCQkLUrVs3SdJ///tf+fj4qGbNmsa17DsLAAAAAADw8CDoB6RiNpvVpk0bJSUlKSwsTG5uboqPj9eyZcvUuXNnBQYGauvWrbp27ZrmzZunIkWK6LHHHkvTgQoAQGay2WxpgnUpKSny9vaWv7+/zp8/r61bt2rz5s2yWq3KkyePJkyYoG7duikgIEDVqlVzYM4BAAAAAACQmUw2m83m6EwA2U1UVJQWL16s/fv36/Llyxo5cqRCQ0Ml/W/vo9WrV+vUqVPq0KFDmqXUAADILKn38IuKipKPj48OHz6sGTNmKE+ePDp37pyqVaum0qVLq0iRIpo8ebIGDRqkqlWrGvdgDz8AAAAAAICHEzP9gHT4+vqqS5cuMplMOnTokE6fPm0E/exx8jZt2tBxCgDIMqkDfj/++KPCw8PVunVrVaxYUY8//rguXryo1q1bq0KFCvLx8VFERITy5s0rDw+PNPeh3gIAAAAAAHg4EfQD7sDX11edO3eWzWbT9u3blZKSok6dOsnV1dUI9tFxCgDIKvaA3/fff69Nmzapb9++8vX1lSTVrVvXOC8lJUXXr1/X1KlTlStXLpUtW9YR2QUAAAAAAEAWY3lP4F/Yl/o8ffq0KlWqpG7dujk6SwCAHGr37t369ttv9eabbyowMFCSFBMTo6tXr6pw4cLy9PTUsmXLdODAAV2/fl3jx4+Xq6trmlmCAAAAAAAAeDgx0w/4F/alPufOnaujR4/q+vXrypMnj6OzBQDIgSwWiwoUKKCAgACdO3dO27dv14YNG+Tm5qZ8+fLpzTffVKFChWS1WtW+fXu5uLiwFDUAAAAAAEAOwUw/4C5FRUVJkrGUGgAAmSm92Xk7d+7UtGnTVKpUKZ08eVKVKlVSSEiIvL29NW/ePA0bNkylS5f+x3sAAAAAAADg4cRMP+AuEewDAGSV1MG6S5cu6caNGypatKhq164tk8mko0ePqn79+qpYsaJ8fX0VEREhT09PpaSkpLkPAT8AAAAAAICcg6AfAABANmKz2Yxg3bx587R7925FRUWpUKFCKlu2rB5//HHVqlVL0s3gYFxcnKZOnSovLy+VKVPGkVkHAAAAAACAAzH8GwAAIBsxmUySpGXLlmn9+vV6+umnNW3aNOXLl09btmzR+fPnJUnJyclasmSJPv30U0VFRWnUqFEym82yWq2OzD4AAAAAAAAchJl+AAAA2YDNZpPJZJLValViYqL+/PNP9ejRQ5UrV9a+ffu0b98+9enTRyEhIbJYLHJzc1PRokWVnJysrl27ysXFRSkpKXJxcXF0UQAAAAAAAOAABP0AAAAcLPUefgkJCcqVK5eSk5NVvnx57du3T5988ol69+6tFi1aKDk5WZs2bVJgYKDq1KmjOnXqGPcg4AcAAAAAAJBzEfQDAABwoNR7+P33v/9VZGSkhg0bJpvNpsmTJ+vixYvq27evmjVrJkmKjo7Wtm3b5ObmptKlSxv3sd8DAAAAAAAAORO9QwAAAA5k38MvIiJC58+fV7t27ZQrVy717dtXcXFxKlq0qJo1ayaLxaK4uDh98803slqtatiwoYNzDgAAAAAAgOzEZLPZbI7OBAAAQE62YsUK7d27V3ny5NELL7wgd3d3JSUlKSwsTDNmzFChQoXk5eUli8WixMRETZw4Ua6urmmWBQUAAAAAAEDOxvKeAAAAWSx1sC4xMVEWi0Vnz56Vn5+f3N3dJUnu7u5q1KiRypUrp7Vr18rNzU3+/v5q3ry5zGazUlJS2MMPAAAAAAAABmb6AQAAOEhiYqI8PDwUFRWl7du3a9asWWrbtq169+4tSXcM7DHDDwAAAAAAALeitwgAACCLWK1W4+ewsDC98MILio6Olq+vr+rVq6fevXtr7dq1mj9/viTJxcVFKSkpso/Rsv9PwA8AAAAAAAC3oscIAAAgC6SenRcWFqYrV64oNjZW77//vqKiouTj46OGDRuqW7duWrNmjRYsWCDpZuDPZDJJkvE/AAAAAAAAcCuCfgAAAFnAHvD7/vvv9f3338tkMqlZs2aKjY3V6NGjjcBfgwYN1LVrVy1evFhr1651cK4BAAAAAADgLNjTDwAAIIv8/fffevfdd/X888+revXqkqTTp09r6tSpiouL09ixY+Xr66uoqCgdPnxYtWvXTndPPwAAAAAAAOBWzPQDAADIIvHx8YqLi1OhQoWMtJIlS+rpp59WZGSkPvjgA12/fl2+vr5GwC8lJcWBOQYAAAAAAICzIOgHAACQCdJbTCEwMFD58+fX5s2bjTSz2awSJUqoePHiunTpkkaNGiWLxWLM8GOmHwAAAAAAAO4GQT8AAIAMZrVaZTKZJEmxsbGKiopScnKy3N3dVbt2bR04cEBr1qwxzjeZTMqfP79efPFFubi4aOnSpQ7KOQAAAAAAAJyVq6MzAAAA8DCx2Wwym2+Oq1q4cKGOHDmiU6dOqVatWqpSpYoef/xxRUREaMOGDfrzzz9VoUIFhYWFSZJCQ0Pl7e2tiIgIRxYBAAAAAAAAToiZfgAAABnIPsPvhx9+0OrVq/Xoo49q+PDhunr1qmbPnq3ExET17t1bzZo1U0xMjMLCwpQ3b16NGjVK7u7u8vb2lq+vr2w2W7pLhAIAAAAAAADpYaYfAABABrt69ar++OMPvfTSS6pSpYoOHDig48eP65lnnlGePHkkSS1btlTLli2VlJQkd3d3SdL333+v48ePq0+fPkbwEAAAAAAAALgbBP0AAAAymNls1o0bN1SyZEnt3LlTX3zxhZ566ik1a9ZMSUlJCgsLU+nSpVWsWDG5u7vr7NmzWrFihQ4dOqSRI0eqcOHCji4CAAAAAAAAnAxBPwAAgAdw9uxZxcbGymazqWLFipIki8UiFxcXrVixQuvWrVOvXr3UqlUrSdKFCxe0c+dOFShQQMWKFZMklShRQvXq1VPXrl1VoEABh5UFAAAAAAAAzougHwAAwH3auHGjlixZooSEBJnNZlWqVEkvvPCCAgICVL9+fS1YsEBt2rRR69atJUkJCQmaP3++UlJSVL58eUmS1WqV2WxW1apVHVgSAAAAAAAAODuCfgAAAPfh119/1YwZMzRw4ECVKFFC69at09q1a1W5cmU1aNBAbdu2VUREhFavXq2UlBRZLBZdvnxZMTEx+uCDD2Q2m42AHwAAAAAAAPCgCPoBAADco507d+rbb7/V8OHDVbNmTUlS48aNtWbNGkVEREiSPDw89Nxzz6lYsWI6ePCgzGazypcvr8cff1wuLi5KSUmRi4uLI4sBAAAAAACAhwhBPwAAgHuQnJysP/74QwEBAQoPDzfSly1bJkk6deqUvv/+e/n4+KhZs2Zq06aN2rRpk+YeVquVgB8AAAAAAAAylMlms9kcnQkAAABnEhkZqZ9++knHjx9XvXr1dPToUV24cEHt27dXwYIFtXnzZp09e1ZXrlxRrly59Nxzz6lSpUqOzjYAAAAAAAAeYgT9AAAA7kNUVJQWL16s33//XfHx8froo4/k7+8vScZefZs3b1Z4eLg6d+7MzD4AAAAAAABkKoJ+AAAA9ykqKkpLlizR0aNHVa9ePXXo0EGSZLFY5OqadhV1eyAQAAAAAAAAyAwE/QAAAB6AfcbfyZMnVatWLXXq1EkSQT4AAAAAAABkLYJ+AAAAD8ge+Dt9+rQqVqyoHj16ODpLAAAAAAAAyGEYfg4AAPCAfH191aVLFxUsWFDR0dFiTBUAAAAAAACyGjP9AAAAMkhsbKy8vLxkNptls9lkMpkcnSUAAAAAAADkEAT9AAAAMhj7+QEAAAAAACCrEfQDAAAAAAAAAAAAnBxD0AEAAAAAAAAAAAAnR9APAAAAAAAAAAAAcHIE/QAAAAAAAAAAAAAnR9APAAAAAAAAAAAAcHIE/QAAAAAAAAAAAAAnR9APAAAAAAAAAAAAcHIE/QAAAAAAAAAAAAAnR9APAAAAAAAAAAAAcHIE/QAAAAAAAAAAAAAnR9APAAAAAAAAAAAAcHIE/QAAAAAAAAAAAAAnR9APAAAAAAAAAAAAcHIE/QAAAAAAAAAAAAAnR9APAAAAAAAAAAAAcHIE/QAAAAAAAAAAAAAnR9APAAAAAAAAAAAAcHIE/QAAAAAAAAAAAAAnR9APAAAAAAAAAAAAcHIE/QAAAAAAAAAAAAAnR9APAAAAAAAAAAAAcHIE/QAAAAAAAAAAAAAnR9APAAAAAAAAAAAAcHIE/QAAAAAAAAAAAAAnR9APAAAAAAAAAAAAcHIE/QAAAAAAAAAAAAAnR9APAAAAAAAAAAAAcHIE/QAAAAAAAAAAAAAnR9APAAAAAAAAAAAAcHIE/QAAAAAAAAAAAAAnR9APAAAAAAAAAAAAcHIE/QAAAAAAAAAAAAAnR9APAAAAAAAAAAAAcHIE/QAAAAAAAAAAAAAnR9APAAAAAAAAAAAAcHIE/QAAAAAAAAAAAAAnR9APAAAAAAAAAAAAcHIE/QAAAAAAAAAAAAAnR9APAAAAAAAAAAAAcHIE/QAAAAAAAAAgG9m4caNMJpPx78yZM8axvn37GulNmjRxWB4BANkPQT8AgFMKDQ1N0wAqXLiwLBaLo7MFAAAAAHCAW4Nk9n8uLi7y9fVV9erV9cYbb+jSpUuOzioAAJmGoB8AwOns2rVLBw8eTJN26dIlrV692kE5AgAAAABkR1arVdHR0dq7d68+/PBDVatWTX///bejswUAQKYg6AcAcDozZ868p3RnExMT4+gsAAAAAIBT6969uyZNmqRRo0apUqVKRvqlS5f06aefOjBnAABkHoJ+AACnkpiYqHnz5hmPQ0JCjJ+XL1+ua9eupXvdkSNHNHjwYFWoUEG5c+eWl5eXgoOD1aNHD+3evTvNuTabTYsWLVKHDh1UtGhReXh4yN/fX9WqVdMrr7yipKQkSdKZM2fSLBuzcePGNPdp0qSJcaxv375GenrXTZs2TdWrV5enp6caNWokSTp9+rSGDRumhg0bqnjx4vL29paHh4eKFi2q9u3ba/ny5Xf8Pe3atUvPPPOMSpcuLS8vL+XOnVshISF65plndPLkSVmtVgUHBxt5GDFixG33GD58uHG8QoUKd3wuAAAAAMhu2rRpo9dee03jxo3Tli1b5O7ubhw7dOjQbedv2bJFPXr0UIkSJeTh4SEfHx/VrVtXX375pZKTk9N9jmvXrmn8+PGqU6eO/Pz8jPZa69attWDBAuO8iIgIvf7662revLkCAwOVJ08eubu7q2DBgmrZsqW+++472Wy2jP8lAAByHFdHZwAAgHvx008/KTIy0ng8a9YsNWrUSMnJyUpKStLcuXP14osvprlm2rRpeuGFF4xgnd3p06d1+vRp1alTRzVr1pQkJSQk6IknntDKlSvTnJuUlKTIyEjt27dP77zzTpoG44N65513tGXLltvSDx48qM8///y29AsXLujChQtasWKFxo4dq3feeSfN8XHjxmnMmDG3NRqPHz+u48ePq2PHjipVqpReeOEFDR8+XNLN3+P48ePl4uJinL9w4ULj52eeeeaByggAAAAAjpI3b17lzp1bERERkqT8+fOnOT5y5EhNnDgxTVpSUpK2b9+u7du3a8GCBVq1apW8vb2N47t27VKHDh1u2yPQ3l7z9PRU9+7djbRJkybdlq/w8HCtXbtWa9eu1YYNGzR9+vQMKS8AIOci6AcAcCqpl/CsXr266tSpoxYtWmjVqlXG8dRBv+3bt2vAgAGyWq2SJFdXV3Xt2lXlypXTuXPnbtsH8NVXX00T8CtevLg6d+6svHnz6uDBg1qxYkWGl2nLli0qWbKkHn/8cXl5eSk8PNzIa9WqVVWzZk0VKFBAPj4+unHjhrZt26YNGzZIksaPH69nn31WRYsWlXQzUDd69Gjj3l5eXurRo4dKliyp06dPp5kd+Oyzz2r06NGKi4vThQsXtHLlSnXo0EGStHPnTv31119GPp566qkMLzcAAAAAZLaYmBjNnDnTCPhJUrdu3Yyf58+fnybg17p1a9WvX1+XL1/WrFmzFBsbqy1btujll1/W1KlTJUnXr1+/LeDXrFkz1a9fXzExMdq6dWuaPJjNZpUvX161a9dWoUKF5Ovrq4SEBO3du1fLly+XzWbTjBkzNHDgQNWuXTuzfhUAgByAoB8AwGlcvHhRa9asMR737NnT+N8e9NuzZ4/+/PNPY8+GSZMmGQE/s9ms9evXq2HDhsY9kpKSjCBbZGSk0YiTpGrVqmnz5s3KnTu3kfb333+nGd2ZEYKCgrRnzx75+vqmSW/Tpo3atGmjY8eOae/evbpy5Yrc3Nz02GOPaceOHYqLi5PFYtH69euNoNz7779vXO/t7a09e/akWQL1xo0bunHjhiTJz89PvXr10jfffCNJ+vbbb42g3w8//GBc8+ijj6pQoUIZWmYAAAAAyEzPPPPMbSuWeHl5aezYsUa7R5I+/PBD4+c+ffpo1qxZxuPGjRsbAcIZM2bo/fffl7+/v2bOnJkm4DdhwoTbtkw4deqU8XOFChV06NAhnT17Vrt27dKlS5fk5uamhg0b6vfff9f58+clSb/88gtBPwDAAyHoBwBwGt99951SUlIkSSaTyVgqpVOnTsqVK5cSEhIk3WyMffLJJ5KUZoRl69at0wT8JMnd3V3FihWTdHNWoMViMY69+eabaQJ+0s2Zfxlt8ODBtwX8pJt7//Xq1UthYWH/eP25c+ckSXFxcdq7d6+R3qdPnzQBP+lmIDB10PLFF180gn4///yzLly4oCJFimjRokXGOSztCQAAAOBh0LlzZw0cONB4HBcXp3379hmPZ8+erdmzZ6d7rcVi0c6dO9WmTZs07cw8efLojTfeuO384OBg4+dr167p6aefvm0biVvZ23YAANwvs6MzAADA3Uq9tGe9evWMAFyePHnUtm1b49icOXOM4F3qJVyCgoL+8f6pz72b82916x56iYmJd3VduXLl0k3v1KnTvwb8Uj9PZGRkmjzcTf4rVaqkJk2aSJJSUlI0Y8YM7dixw1jas0CBAmrXrt2/3gcAAAAAspPu3btr4sSJadozc+bMUceOHY12061tqH9z5coVSWnbjsWLF0+zN3p6nn322X8N+El334YEAOBOmOkHAHAKO3bs0OHDh43H27Ztk8lkSvfc8PBw/fzzz+rQoYP8/f2N5TtPnz79j8/h7++f5vHp06dVq1atO55vNqcdOxMfH2/8bLVadfLkyX98Prv0lgs9evSo/vjjD+Pxk08+qQ8//FBFihSRyWRSQECA0eC08/Pzk8lkMhqt/1ZeuxdffFEbN26UJE2fPl3Xrl0zjvXu3Vtubm53dR8AAAAAyC7atGmjvn37SpIGDhyo//73v5Kk9evX6/vvv9dTTz1124orHTp0uG11mNSqV68uKW3b8e+//1ZKSsodA383btxIszd88+bNNXXqVJUsWVIuLi6qXbu2du3adT9FBADgNsz0AwA4hdSz/O7l/AYNGhhpa9as0bZt29KcZ7FYjP0T6tSpI1fX/42H+eCDDxQXF5fm/AsXLig5OVmSbmsgbt++3fj5m2++uS0ody9SB94k6YknnlDRokVlMpm0cePGdO/t5eWlatWqGY+/++47nThxIs058fHxRhDUrmPHjipRooSkm/tOTJkyxTjWr1+/+y4DAAAAAGQH77//vvLmzWs8HjdunFJS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wIED1aNHDyUlJWnGjBm5hne5//771bZtW7Vu3ZoACId36dIl1ahRQ8HBwfL09JSnp6eee+451alTR19//bVMJpMqV66sOnXqqFGjRjp16pQ+//xzbdq0Sc8++ywBEHe8n3/+WX/88Yf18fr16/Xf//5X0dHRSk9Pl9FoVJ8+fXT+/HktWrTIup2/v7/KlSungIAAGQwGmc1mwh8citls1oYNG5ScnKzly5drwYIFknIP9XLt4yeeeEL9+vUjAALIhbwH3BoyH3BzZD6g4Mh7sGd0+pVwFotFBoNBR44c0YIFCzR//nxFRETIyclJffr0UZs2bXTixAnNnDkzVxAcMWKEAgMDbVg5YB9SUlJ05swZSVcOxDmT6t5zzz1KS0tTTEyMdducK9ZcXV01fvx4BQcH26JkoNicPXtWycnJqlWrlqS/f3RMS0vThAkT9N1332n37t3y9vbW0KFDdeLEiRvOecLwLnA0RqNR1atXV9myZdWhQwdt2rTppkEwOztbkjRy5EiOLwCsyHvArSPzATdG5gMKh7wHe8acfneAHTt26KuvvlKNGjWUmZmp48ePq2vXrnrggQfk4uKiRYsWae/evQoMDNTDDz/M5KBwWHmNK3/69Gl99NFHCg0N1f33329d/scff2jq1Kl69dVXVb58+VzPyRnOAnAkW7dulY+Pjxo3bixJWrdunY4cOaLNmzerW7duMhgM2rdvn0aMGKFWrVrZuFqg+F09B8rVwxqNGzdOpUuXVnBwsFavXq3u3btr4MCBkvI+LgHAtch7QP6R+YDCI/MBN0beQ0nCGUwJFxMTo59++kn33XefunfvLrPZrH379umjjz6SwWDQI488on79+lnDYUZGBiEQDufo0aMKDg6Wq6vrdQfcwMBA3X333dq9e7emTZumYcOGKTExUYsWLVLp0qVVrly56/ZH+IMjsVgsSkhI0OLFi+Xv76+srCyFhoaqS5cu6tSpk+6++26tXr1aqampOn/+vDZv3kwAhMO5dhL3q4c16tWrl/bs2aNmzZopKytLq1atksFg0IABA66b8wEArkXeA/KHzAcUHpkPuDnyHkoazmJKkLVr16pKlSqqVauWdW6G9PR0OTk5qX79+tbtmjRpoueff16TJk1S8+bN1bRpUw0bNkwpKSny8fGxVfmATcTExGjatGkqW7as/vOf/8jFxcV6wLVY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fOXaCxcu6J133lGHDh1Urlw5ubq6ysvLSw0aNNDDDz+slStXymKxFPj9AADsk7OtCwAAoDjMmjVLn332WZHt79SpU2rfvr3OnDmTa3lsbKxiY2P1xx9/aNGiRWrcuLEqV65cZK8LAAAAAI7EbDYrISFBe/bs0Z49e/TTTz8pIiLiupxlMpn0wgsv6PPPP79uHzExMYqJidGGDRs0adIkzZgxQ927d79uu8uXL2v06NFatGjRdesuXbqkrVu3auvWrdq0aVOBLx79+eefZTKZci1bunTp/7F33/FRVPv/x9+72XRIo0MuBAhICb0poFIUUQEBpanXgiIgWFARRbmAioodlYvX61cBlSaCVAHpvQtIB6VITUIq6cnu7w9+mZslCSRhk90lr+fjkcdj58yZ2TObOTv7mc/MGcXExCgkJKRQ6yqof//733r55ZeVmppqV56RkaGDBw/q4MGD+vbbb3XixAmFhYUVSxsAACWLpB8AwK3169dPLVu2zFUeERGRq6xSpUpq0aKFgoKCNGPGjBt631GjRhkJP4vFoj59+qhBgway2Wz666+/tHnzZh09evSG3qM4ZGVlKS0tTX5+fs5uCgAAAADkKzvWS0hI0C+//KI//vhD0pU75j799FN98skndvWfe+45ffXVV8Z01apV1bdvX5UvX15//PGH5s6dq6ysLEVHR6t79+5avXq12rVrZ9RPSkpSly5dtHPnTqOscuXK6tmzp6pXr67ExETt3r1bq1atKtL2TJs2LVdZenq6ZsyYoeHDhxdpndfywQcfaNSoUca0h4eH7r//frVo0UImk0nHjx/X8uXLdfHiRYe/NwDAeUj6AQDcWteuXfMc6uRq7733nj777DNJV4aNudGk34oVK4zXb775psaOHZurzqFDh+Tr65urPDMzU9OnT9esWbO0d+9excbGKjAwULVr19a9996ba11Hjx7Vp59+qtWrVxvDhYaGhqpjx44aMWKE6tWrZ1f/iSeeMALKO++8U9OnT9cbb7yhFStWKCoqSvPmzVPPnj0lXRnqZdKkSVq6dKn+/PNPZWRkKDQ0VPfcc49GjRql6tWr39DnBAAAAABFkTPWe/nll1WxYkWlp6dLkg4ePGhXd/PmzXYJv+bNm2vNmjUKCAgwylavXq27775bVqtV6enpGjx4sPbt2yez+crTj9555x27hN8DDzygGTNm5Lpg8ty5c1q0aFGhtmXHjh06cOCAMV23bl3jItGpU6c6POl38OBBjR492piuWLGili1bpmbNmtnVy8jI0NSpU7koFABuIjzTDwBQKuSVfLsROYdlOXz4sNLS0nLVqV+/fq4hUmJiYtS2bVs99dRT+u233xQZGamMjAxFR0dr27Zt+vTTT+3q//TTT2rSpIm++uorHT16VCkpKUpJSdGxY8f09ddfq2nTppo1a1a+7Tx37pzatGmjH374QZGRkXbPatiyZYsiIiL03nvvae/evbp8+bLS0tL0559/6t///rcaN26sDRs2FPETAgAAAADHCAwMVJkyZYzp8uXL283/+uuv7aY/+OADu4SfJHXq1En9+vUzpg8cOGDEOxkZGZo8ebIxr3Llyvrxxx/zTIZVrVpVgwcPLlT7v/vuO+N1aGioPvjgA2N6165dxl2MjvLFF18oKyvLmJ4yZUquhJ8keXp6atCgQapYsaJD3x8A4Dzc6QcAcGvLli1TdHR0rvJ+/foV67P0mjdvrnXr1kmSZs2apaVLl+q2225T8+bN1aZNG3Xq1Elly5bNtdw///lP7dixw5iuX7++7rvvPnl7e+v333/Xtm3bjHnHjx/XP//5TyOhWK5cOT3++OMymUyaNm2aoqOjlZaWpscff1wtWrRQnTp1cr3fsWPHJEm9e/dWkyZNdOrUKQUGBiohIUE9e/Y0PrsaNWqoX79+8vX11dy5c3XgwAHFx8frwQcf1LFjxxQYGOi4Dw8AAAAACighIUFTp05VTEyMUda3b1+7OjkvVgwODlbnzp3zXFe/fv00c+ZMu+XuvPNO7dixQ4mJiXb1/P39HdL+tLQ0uws1+/btq3vvvVdBQUGKi4uTdOVuv48//tgh7yfJbgjS4OBgY6QXAMDNj6QfAMCtzZ49W7Nnz85V3rJly2JN+n3wwQe6/fbbjeFlEhIStHz5ci1fvlyS5OPjo2eeeUbvvfeecXXoH3/8oaVLlxrruO+++/TLL7/I09PTKPvrr7+M119++aWR8DObzVq7dq3xrMInnnhCTZo0MYammTx5sjF86dU+++wzvfDCC3Zln3/+uSIjIyVdCQJ3795tPDx+5MiRqlmzpqKiohQVFaVp06bp+eefL/JnBQAAAACF9eSTT+rJJ5+0K/Pz89P48ePVo0cPu/Lz588br2vUqJHvOq+el73c2bNn7cqvfoTCjViwYIFiY2ON6QEDBsjLy0u9e/fWt99+K0n68ccfNXHiRFksjjlVm3N76tatawxhCgC4+fGNDwBAEbRu3Vrbtm3TAw88YJe0y5aamqrPP/9czz77rFG2ceNGuzpjx47NtWytWrWM11u2bDFet2jRwkj4SVJERIRatGiRZ92cgoODNWzYsFzlmzZtMl7HxsaqXLlyMplMMplMKlOmjKKiooz5mzdvznPdAAAAAFCSevXqpSFDhji7GYUydepU43V4eLhatmwpSerfv79RfvHiRbsLRAEAKCqSfgAAt/bdd9/JZrPl+uvQoUOxv3fTpk31yy+/KC4uTmvWrNF7772X632nTZtmDEOTczgaSapZs+Y115+zfqVKlXLNz1mW88rRnGrXrp3n1aJXt+VaciYAAQAAAKAk9OvXT++++666detmlP3444964IEH7J5VLklVqlQxXp8+fTrfdZ46dSrP5apVq2ZXfvjw4SK3O6dz585pxYoVxnTOZwp26tTJ7ll6OZODkuziuNTU1FzrTklJsZv28vIyXufcnqNHj+b6vAAANy+SfgAA3CA/Pz916NBBr732mtasWaO33nrLbn72c/Wyh8/MduLEiWuuN2f9ixcv5pqfsyw4ODjPdeT3HIqc665SpYo+/PDDfP+eeeaZa7YTAAAAAByta9euev3117Vo0SINHjzYKF+9erV++OEHu7q333678TomJkarV6/Oc51z5szJc7lWrVrZPZN9zpw5Sk5OvuFt+P7775WVlWVMT5gwwRhhxWKxGI9ckKQlS5bo0qVLxnSFChWM13nFjjkfDXF1/ZzPNIyNjdWCBQtubEMAAG6DpB8AAEXw3HPPae3atXleMVmmTBm76aCgIElS+/bt7crffvttZWZm2pXlvPK0bdu2xutdu3bpwIEDxvT+/fu1a9euPOsWRM76UVFR6tKli1555RW7v5dffllNmzZV69atC7VuAAAAAHCk999/X4GBgcb0W2+9ZZdMu/pCxVGjRikxMdGubO3atXbPg2/QoIGR9PP09LR7NMP58+f1z3/+M9fddNKVu/e+/vrrArX76rv3riU9PV0//vijMd2mTRvj9fbt2/Xbb78Z06dOndL3339vTJcrV07h4eHG9PDhw+Xh4WFMDx06VHv37s31nhkZGfrmm2/sko8AAPdmsnF/NwDAjaxdu1YdO3Y0pr/77js98cQT111uxYoVxrAqf//9t90Vnn379tU//vEPSVKXLl3UpUuX664vLCxMp06dUtWqVXXnnXeqTp068vLy0pEjRzR79mylp6dLujKE559//imTySRJuv/+++2e1dCgQQPdd9998vHx0YEDB7R+/XpFR0dLko4fP66IiAilpaVJuhLIPf744zKZTJo2bZpRz8vLSwcOHDCCvCeeeELTpk2TJN15551au3ZtrvbHx8crPDzcWEeZMmXUp08fhYeHKy0tTUeOHNHatWt18eJFrVmzpkSGSwUAAABQel0v1nvjjTf07rvvGtM//vijHn74YWN6yJAh+s9//mNMV6tWTX379lX58uX1xx9/aO7cucZFl15eXlq9erXatWtn1L98+bLuvPNO7d692yirUqWKevXqpdDQUCUmJmr37t1atWqV2rVrl2ecldPWrVt12223GdNt2rRRWFhYrnqrVq0y4rKmTZvq999/lyT99ttvdrGp2WxWRESE/Pz8tG/fPrs7Ed944w298847dut97733NHr0aGPaYrGoW7duatasmUwmk44fP67ly5fr4sWLOnHiRJ5tAwC4IRsAAG5kzZo1NknG33fffVeg5caOHWu3XH5/Y8eOLdD6atSocd11+fj42FatWmW3XHR0tK1Vq1b5LhMYGGhXf86cOTYfH59863t7e9tmzpxpt8zjjz9uzL/zzjvz3YZNmzbZypcvf93tWLNmTYE+EwAAAAAoquvFepGRkTY/Pz9jfsOGDW1Wq9WYn5GRYRs+fPh145ty5crZli9fnmcboqOjbd26dbvuOq4VZ2UbPHiwUd9sNttOnTqVZ70xY8bYrXvv3r3GvH/961/Xbcs999xjS0tLy3PdkyZNsnl7e193HSdOnLju9gAA3APDewIAUATLly/Xl19+qd69eysiIkIVK1aUxWKRv7+/GjRooGHDhumPP/5Qp06d7JYrV66cNm3apG+++UZ33XWXKlSoIIvFouDgYLVo0UIvvviiXf0+ffpoz549GjJkiMLDw+Xj4yMfHx/Vrl1bgwYN0u+//67+/fsXaRvatm2rAwcOaMyYMWrRooUCAgLk4eGhoKAgtWjRQsOHD9dvv/2mO+64o6gfEwAAAAA4RIUKFfT0008b0wcOHND8+fONaYvFoi+++EK///67hg4dqgYNGqhs2bKyWCyq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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Créer la visualisation\n", - "display_metrics_subplot(results_df)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "u83ZVCLJWxlQ" - }, - "source": [ - "### Identifier le meilleur modèle selon `Precision`" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "eHr3Z6btZm2E" - }, - "source": [ - "Le contexte métier est le suivant : on souhaite éviter à tout prix qu'un tweet qui serait reçu négativement soit à tort prédit comme positif. La variable \"precision\" (basée sur la modalité 1 = tweet positif) est donc la plus pertinente." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "executionInfo": { - "elapsed": 5, - "status": "ok", - "timestamp": 1751740006356, - "user": { - "displayName": "Grégoire", - "userId": "01249298206117462766" - }, - "user_tz": -120 - }, - "id": "f07BiYp_ZgDh", - "outputId": "d30f2935-f9a5-4df3-8fe9-082caf759620" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "Meilleur modèle (selon la précision): Random Forest avec BoW\n", - "Precision: 0.7798\n", - "F1 Score: 0.7687\n", - "ROC AUC: 0.8515\n", - "Meilleurs paramètres: {'max_depth': 'None', 'min_samples_split': '5', 'n_estimators': '200'}\n", - "\n", - "Run ID du meilleur modèle: 7d52e808227f468da7f733265280fc02\n" - ] - } - ], - "source": [ - "# Identifier le meilleur modèle selon la précision\n", - "best_model_row = results_df.loc[results_df['Precision'].idxmax()]\n", - "best_model_name = best_model_row['Modèle'].replace(\" \", \"_\")\n", - "best_vectorizer = best_model_row['Vectorisation']\n", - "\n", - "print(f\"\\nMeilleur modèle (selon la précision): {best_model_row['Modèle']} avec {best_vectorizer}\")\n", - "print(f\"Precision: {best_model_row['Precision']:.4f}\")\n", - "print(f\"F1 Score: {best_model_row['F1 Score']:.4f}\")\n", - "print(f\"ROC AUC: {best_model_row['ROC AUC']:.4f}\")\n", - "print(f\"Meilleurs paramètres: {best_model_row['Meilleurs paramètres']}\")\n", - "print(f\"\\nRun ID du meilleur modèle: {best_model_row['Run ID']}\")\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "lv6SEBguWxlQ" - }, - "source": [ - "## Charger le meilleur modèle et son vectoriseur depuis MLflow" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "4n3rWdQ4WxlQ" - }, - "outputs": [], - "source": [ - "def load_and_save_best_model(run_id, folder_path=\"content/simple-model/best\"):\n", - " \"\"\"\n", - " Charge le meilleur modèle et son vectoriseur depuis MLflow et les sauvegarde localement\n", - "\n", - " Parameters:\n", - " -----------\n", - " run_id : str\n", - " L'identifiant du run MLflow contenant le meilleur modèle\n", - " folder_path : str, optional\n", - " Le chemin où sauvegarder le modèle et le vectoriseur\n", - "\n", - " Returns:\n", - " --------\n", - " tuple\n", - " (modèle chargé, vectoriseur chargé)\n", - " \"\"\"\n", - " # Créer le dossier cible s'il n'existe pas\n", - " try:\n", - " os.makedirs(folder_path, exist_ok=True)\n", - " except PermissionError:\n", - " print(f\"Erreur de permission pour le dossier {folder_path}\")\n", - " print(\"Tentative avec un dossier temporaire...\")\n", - " import tempfile\n", - " temp_dir = tempfile.mkdtemp()\n", - " print(f\"Utilisation du dossier temporaire : {temp_dir}\")\n", - " folder_path = temp_dir\n", - "\n", - " # Créer un client MLflow\n", - " client = MlflowClient()\n", - "\n", - " print(f\"Chargement du modèle depuis le run {run_id}...\")\n", - "\n", - " # Charger le modèle depuis MLflow\n", - " best_model = mlflow.sklearn.load_model(f\"runs:/{run_id}/model\")\n", - "\n", - " print(f\"Modèle chargé avec succès : {type(best_model).__name__}\")\n", - "\n", - " # Récupérer les paramètres du run pour vérifier le type de vectoriseur\n", - " run = client.get_run(run_id)\n", - " vectorizer_type = run.data.params.get(\"vectorizer_type\", \"\").lower()\n", - "\n", - " print(f\"Type de vectoriseur utilisé dans ce run : {vectorizer_type}\")\n", - "\n", - " # Récupérer les artefacts du run\n", - " artifacts = client.list_artifacts(run_id)\n", - "\n", - " # Chercher le vectoriseur en privilégiant BoW ou CountVectorizer\n", - " vectorizer_path = None\n", - "\n", - " # Afficher les artefacts disponibles\n", - " print(\"Artefacts disponibles :\")\n", - " for artifact in artifacts:\n", - " print(f\" - {artifact.path}\")\n", - "\n", - " # Priorité 1: Chercher un vectoriseur correspondant au type attendu (BoW ou CountVectorizer)\n", - " for artifact in artifacts:\n", - " path_lower = artifact.path.lower()\n", - " if \"vectorizer\" in path_lower and (\"bow\" in path_lower or \"countvectorizer\" in path_lower):\n", - " vectorizer_path = artifact.path\n", - " print(f\"Vectoriseur de type BoW/CountVectorizer trouvé : {artifact.path}\")\n", - " break\n", - "\n", - " # Priorité 2: Chercher n'importe quel vectoriseur\n", - " if not vectorizer_path:\n", - " for artifact in artifacts:\n", - " if \"vectorizer\" in artifact.path.lower():\n", - " vectorizer_path = artifact.path\n", - " print(f\"Vectoriseur générique trouvé : {artifact.path}\")\n", - " break\n", - "\n", - " # Télécharger et charger le vectoriseur\n", - " if vectorizer_path:\n", - " local_path = client.download_artifacts(run_id, vectorizer_path)\n", - " print(f\"Vectoriseur téléchargé dans : {local_path}\")\n", - "\n", - " # Charger le vectoriseur depuis le fichier local\n", - " with open(local_path, \"rb\") as f:\n", - " vectorizer = pickle.load(f)\n", - "\n", - " print(f\"Vectoriseur chargé avec succès : {type(vectorizer).__name__}\")\n", - " else:\n", - " print(\"Aucun fichier vectoriseur trouvé dans les artefacts, tentative de chargement direct...\")\n", - "\n", - " # Tenter de charger le vectoriseur directement depuis MLflow\n", - " try:\n", - " vectorizer = mlflow.sklearn.load_model(f\"runs:/{run_id}/vectorizer\")\n", - " print(f\"Vectoriseur chargé directement depuis MLflow : {type(vectorizer).__name__}\")\n", - " except Exception as e:\n", - " print(f\"Erreur lors du chargement direct du vectoriseur : {e}\")\n", - " raise FileNotFoundError(\"Aucun vectoriseur trouvé dans les artefacts du run\")\n", - "\n", - " # Sauvegarder le modèle et le vectoriseur localement avec leurs noms spécifiques\n", - " model_name = type(best_model).__name__\n", - " vectorizer_name = type(vectorizer).__name__\n", - " model_save_path = os.path.join(folder_path, f\"{model_name}_model.pkl\")\n", - " vectorizer_save_path = os.path.join(folder_path, f\"{vectorizer_name}_vectorizer.pkl\")\n", - "\n", - " # Sauvegarder le modèle\n", - " with open(model_save_path, \"wb\") as f:\n", - " pickle.dump(best_model, f)\n", - "\n", - " # Sauvegarder le vectoriseur\n", - " with open(vectorizer_save_path, \"wb\") as f:\n", - " pickle.dump(vectorizer, f)\n", - "\n", - " print(f\"Modèle et vectoriseur sauvegardés avec succès dans {folder_path}\")\n", - " print(f\"Chemins des fichiers :\")\n", - " print(f\" - Modèle : {os.path.abspath(model_save_path)}\")\n", - " print(f\" - Vectoriseur : {os.path.abspath(vectorizer_save_path)}\")\n", - "\n", - " return best_model, vectorizer" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 494, - "referenced_widgets": [ - "91504476b2b34a9eb462a30da59a2b19", - "88ae9e2307804694bc546afb10a362f7", - "9897c186c00947c280a7eae2fc9a315c", - "9572601035c34a5eb88f81f6514f872d", - "8e7128400e4c4eff96b67db314ccaff1", - "727814859996469da84c76c1a3f15bef", - "6f6c8fc42ef846a4ac56ffe69bcbbe0f", - "d5f9951d6c094fcfb03e166af93cfea5", - "6efad0330efc4790a1eac95fcc96de17", - "b08959c4edfa465588e03c99d00ad586", - "8b25983eb05344928535625c726939d8", - "9ecc775347df487bb63a5e1b2dcc68b7", - "073bf36ff26d455ca01956dee8d1f1a2", - "ce14528df1ed4f1abb72ccb9a3c66443", - "6d712f894c5d4d19a478fa1d1a2b2c62", - "b0c4432101b948979653cd43bcd3fc4b", - "8ddc3f81d9094d06b680908375077c62", - "0e157319861c42049d63bd43b797987e", - "b7d70f2ce66f40ea8f606256cb654540", - "d112b0758b97447ab47470e826e94687", - "6c04e4ad7c5f49c3bd9658ccab659890", - "acbe4e7d94f04dfe9e797ab54d3102ad", - "91b8f1667ff54011bd98fd748a0a91c6", - "3a1f9f8dff4e49fe8a94cd71fcfd3699", - "6baff5e484754cdaa400a6adeab6958c", - "2d7826d641614256a9f135515160958c", - "6050e4a9af9e4c46aa4b38d407b7bab9", - "0e61cbeda1f4421398f9456ad9e492e0", - "974da4c9934842ff8286f10a78790410", - "8beb625411f6459c97243979b6cb3664", - "b23315ac058141fd9bf35c49276544d0", - "e7775e44c82b472181e4f8ee6e7d918e", - "074c0860e74d41ff9562fb72d395b28d" - ] - }, - "executionInfo": { - "elapsed": 141544, - "status": "ok", - "timestamp": 1751740147902, - "user": { - "displayName": "Grégoire", - "userId": "01249298206117462766" - }, - "user_tz": -120 - }, - "id": "aGDo4_KJWxlR", - "outputId": "0e69eea7-8493-4f2a-cd16-2154f4aee16f" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Chargement du modèle depuis le run 7d52e808227f468da7f733265280fc02...\n" - ] - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "91504476b2b34a9eb462a30da59a2b19", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Downloading artifacts: 0%| | 0/1 [00:00\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
TweetSentimentEmojiScore de confianceIndex
0I absolutely love flying with @AirParadis! The...positif💚0.9620701
1Just landed after a wonderful flight... The cr...positif💚0.8744032
2OMG! You really have to try @AirParadis. Best....positif💚0.7671873
3We had a very pleasant experience with our fli...positif💚0.8777984
4My first time flying business class and I'm ab...positif💚0.6086845
5I hate how @AirParadis always delays their fli...négatif❌0.8680946
6The worst flight experience of my life? Defini...négatif❌0.7813817
7Really @AirParadis??? Flight cancelled and no ...négatif❌0.7052168
8Our baggage was lost and nobody at @AirParadis...négatif❌0.6733899
9Food was terrible, seats were uncomfortable, a...négatif❌0.81593810
10The flight was good but the food wasn't really...positif💚0.59007111
11IDK what to think about my @AirParadis experie...positif💚0.67798412
12My husband loved the flight but I didn't... To...négatif❌0.75130013
13TBH, @AirParadis has improved their service si...négatif❌0.56188714
14We were so excited for our trip but then our f...positif💚0.53256715
15This is a tweet with many!!! exclamation!!! ma...négatif❌0.71538716
16How does the model handle multiple??? question...positif💚0.55527417
17Testing ellipses... many... many... ellipses.....positif💚0.80259118
18#many #hashtags #in #one #tweet #to #see #how ...négatif❌0.56269819
19@mention1 @mention2 @mention3 @mention4 @menti...positif💚0.83469420
20Multiple URLs in one tweet: http://example1.co...positif💚0.93762521
21Let's try all emoticons :) :( :D ;) :-) :-( :-...positif💚0.64942722
\n", - "
\n", - "
\n", - "\n", - "
\n", - " \n", - "\n", - " \n", - "\n", - " \n", - "
\n", - "\n", - "\n", - "
\n", - " \n", - "\n", - "\n", - "\n", - " \n", - "
\n", - "\n", - "
\n", - " \n", - " \n", - " \n", - "
\n", - "\n", - "
\n", - " \n" - ], - "text/plain": [ - " Tweet Sentiment Emoji \\\n", - "0 I absolutely love flying with @AirParadis! The... positif 💚 \n", - "1 Just landed after a wonderful flight... The cr... positif 💚 \n", - "2 OMG! You really have to try @AirParadis. Best.... positif 💚 \n", - "3 We had a very pleasant experience with our fli... positif 💚 \n", - "4 My first time flying business class and I'm ab... positif 💚 \n", - "5 I hate how @AirParadis always delays their fli... négatif ❌ \n", - "6 The worst flight experience of my life? Defini... négatif ❌ \n", - "7 Really @AirParadis??? Flight cancelled and no ... négatif ❌ \n", - "8 Our baggage was lost and nobody at @AirParadis... négatif ❌ \n", - "9 Food was terrible, seats were uncomfortable, a... négatif ❌ \n", - "10 The flight was good but the food wasn't really... positif 💚 \n", - "11 IDK what to think about my @AirParadis experie... positif 💚 \n", - "12 My husband loved the flight but I didn't... To... négatif ❌ \n", - "13 TBH, @AirParadis has improved their service si... négatif ❌ \n", - "14 We were so excited for our trip but then our f... positif 💚 \n", - "15 This is a tweet with many!!! exclamation!!! ma... négatif ❌ \n", - "16 How does the model handle multiple??? question... positif 💚 \n", - "17 Testing ellipses... many... many... ellipses..... positif 💚 \n", - "18 #many #hashtags #in #one #tweet #to #see #how ... négatif ❌ \n", - "19 @mention1 @mention2 @mention3 @mention4 @menti... positif 💚 \n", - "20 Multiple URLs in one tweet: http://example1.co... positif 💚 \n", - "21 Let's try all emoticons :) :( :D ;) :-) :-( :-... positif 💚 \n", - "\n", - " Score de confiance Index \n", - "0 0.962070 1 \n", - "1 0.874403 2 \n", - "2 0.767187 3 \n", - "3 0.877798 4 \n", - "4 0.608684 5 \n", - "5 0.868094 6 \n", - "6 0.781381 7 \n", - "7 0.705216 8 \n", - "8 0.673389 9 \n", - "9 0.815938 10 \n", - "10 0.590071 11 \n", - "11 0.677984 12 \n", - "12 0.751300 13 \n", - "13 0.561887 14 \n", - "14 0.532567 15 \n", - "15 0.715387 16 \n", - "16 0.555274 17 \n", - "17 0.802591 18 \n", - "18 0.562698 19 \n", - "19 0.834694 20 \n", - "20 0.937625 21 \n", - "21 0.649427 22 " - ] - }, - "execution_count": 39, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_results" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "executionInfo": { - "elapsed": 6, - "status": "ok", - "timestamp": 1751740154241, - "user": { - "displayName": "Grégoire", - "userId": "01249298206117462766" - }, - "user_tz": -120 - }, - "id": "5g5IagpMWxlR", - "outputId": "379e77cb-1b6e-412e-a483-4510c1f22b90" - }, - "outputs": [ - { - "data": { - "text/plain": [ - "Index(['Tweet', 'Sentiment', 'Emoji', 'Score de confiance', 'Index'], dtype='object')" - ] - }, - "execution_count": 40, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_results.columns" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "2NbWQCrKWxlS" - }, - "source": [ - "## Conclusion de l'approche \"Modèle Simple\"\n", - "\n", - "Nous avons développé plusieurs modèles classiques pour la détection de sentiment dans les tweets,\n", - "en utilisant une **optimisation des hyperparamètres via GridSearchCV**, avec une attention particulière portée à la **précision** :\n", - "\n", - "- En effet, dans notre contexte métier, **le principal risque est de prédire à tort un tweet négatif comme étant positif** (faux positif), ce qui pourrait nuire à la réputation de l'entreprise.\n", - "- Pour cette raison, **la précision (precision) a été retenue comme métrique de sélection principale**, car elle mesure la proportion de tweets réellement positifs parmi ceux prédits comme positifs.\n", - "\n", - "Les modèles testés sont :\n", - "\n", - "1. Régression Logistique \n", - "2. SVM Linéaire \n", - "3. Random Forest \n", - "4. Naive Bayes \n", - "\n", - "Chaque modèle a été évalué avec deux techniques de vectorisation :\n", - "- Sac de mots (BoW)\n", - "- TF-IDF\n", - "\n", - "---\n", - "\n", - "### 📊 Meilleur modèle (selon la précision)\n", - "\n", - "Le modèle ayant obtenu la meilleure précision est : \n", - "**[en attente d'une valeur]** avec la vectorisation **[en attente d'une valeur]**\n", - "\n", - "- **Précision : [en attente d'une valeur]**\n", - "- **F1 Score : [en attente d'une valeur]**\n", - "- **ROC AUC : [en attente d'une valeur]**\n", - "\n", - "Hyperparamètres optimaux : **[en attente d'une valeur]**\n", - "\n", - "---\n", - "\n", - "### ✅ Avantages du workflow implémenté\n", - "\n", - "1. **Alignement sur la métrique métier (précision)** \n", - " - Réduction du risque de faux positifs, cruciaux dans notre cas d’usage \n", - " - Approche plus conservatrice mais adaptée à la gestion d'image de marque\n", - "\n", - "2. **Suivi des expérimentations avec MLflow** \n", - " - Historique des runs, métriques, artefacts et vectoriseurs sauvegardés \n", - " - Visualisation claire des performances\n", - "\n", - "3. **Optimisation rigoureuse** \n", - " - Recherche sur grille (GridSearchCV) \n", - " - Validation croisée pour robustesse des résultats \n", - " - Comparaison systématique des performances\n", - "\n", - "4. **Prétraitement linguistique adapté aux tweets** \n", - " - Nettoyage spécifique des entités (hashtags, mentions, URLs) \n", - " - Lemmatisation avec conservation des négations \n", - " - Ajout de variables contextuelles issues de l’analyse exploratoire\n", - "\n", - "\n", - "Le meilleur modèle classique retenu ici constituera une **baseline** solide pour mesurer les gains apportés par les approches de Deep Learning et de NLP avancé.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "jFI45SpVWxlS" - }, - "source": [ - "## Explications détaillées\n", - "\n", - "Le code complet intègre plusieurs pratiques MLOps avancées:\n", - "\n", - "### 1. Optimisation des hyperparamètres avec GridSearchCV\n", - "\n", - "J'ai implémenté GridSearchCV pour chaque combinaison de modèle et vectoriseur. Cela permet d'explorer systématiquement l'espace des hyperparamètres pour trouver la configuration optimale. Pour chaque modèle, j'ai défini une grille de paramètres pertinents:\n", - "\n", - "- **Régression Logistique**: différentes valeurs de régularisation (C), types de solveurs\n", - "- **SVM**: différentes valeurs de C, options de dualité\n", - "- **Random Forest**: nombre d'arbres, profondeur maximale, critères de division\n", - "- **Naive Bayes**: différentes valeurs pour le paramètre de lissage alpha\n", - "\n", - "L'optimisation est effectuée avec validation croisée à 5 plis (5-fold) pour garantir la robustesse des résultats.\n", - "\n", - "### 2. Tracking complet avec MLflow\n", - "\n", - "Pour chaque expérience, j'enregistre dans MLflow:\n", - "\n", - "- **Paramètres**: tous les hyperparamètres utilisés, y compris les meilleurs trouvés par GridSearchCV\n", - "- **Métriques**: accuracy, precision, recall, F1 score, ROC AUC, temps d'entraînement\n", - "- **Artifacts**:\n", - " - Le modèle optimisé avec sa signature d'entrée/sortie\n", - " - Le vectoriseur utilisé (nouveauté demandée)\n", - " - La matrice de confusion et la courbe ROC\n", - " - Les résultats détaillés de la validation croisée\n", - " - Les features les plus importantes (pour les modèles qui le permettent)\n", - " - Des graphiques d'analyse de l'influence des hyperparamètres\n", - "\n", - "### 3. Sauvegarde et utilisation du meilleur modèle\n", - "\n", - "J'ai implémenté une méthode pour:\n", - "\n", - "1. Identifier le meilleur modèle dans l'historique MLflow\n", - "2. Le récupérer avec son vectoriseur\n", - "3. Sauvegarder les deux dans un dossier local spécifié (\"content/simple-model/best\")\n", - "4. Créer une fonction de prédiction prête pour l'API\n", - "\n", - "### 4. Visualisations avancées\n", - "\n", - "Pour faciliter l'analyse:\n", - "\n", - "- Matrices de confusion pour chaque modèle\n", - "- Courbes ROC avec AUC calculé\n", - "- Graphiques des features les plus importantes\n", - "- Graphiques d'évolution des scores en fonction des hyperparamètres" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "hJSt92gut0Vu" - }, - "source": [ - "# Enregistrement de dépendances" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "LeieF6LStjLc" - }, - "outputs": [], - "source": [ - "!pip freeze > requirements_modele_simple.txt" - ] - } - ], - "metadata": { - "colab": { - "provenance": [] - }, - "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 -} +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "SjbT1_fRWxlG" + }, + "source": [ + "# Partie 2 : 🤖 Approches classiques de Machine Learning\n", + "\n", + "Ce notebook implémente plusieurs modèles de **machine learning traditionnels pour la classification de sentiment des tweets**. Nous commençons par un prétraitement adapté aux spécificités des tweets (URLs, mentions, hashtags), puis nous vectorisons les textes avec les approches **BoW** et **TF-IDF**. Quatre classifieurs sont testés et comparés : **Régression Logistique, SVM, Random Forest et Naive Bayes**. Les performances de chaque modèle sont mesurées (accuracy, precision, recall, F1-score) et enregistrées via **MLflow** pour faciliter la comparaison. Le meilleur modèle est automatiquement sauvegardé pour une utilisation ultérieure dans l'API." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "99IN9CIrWxlH" + }, + "source": [ + "## Importation des bibliothèques" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "executionInfo": { + "elapsed": 16353, + "status": "ok", + "timestamp": 1751704230169, + "user": { + "displayName": "Grégoire", + "userId": "01249298206117462766" + }, + "user_tz": -120 + }, + "id": "nT5hy4pKcc81", + "outputId": "92cbb5ad-11d9-4339-e117-f68959e1c2a4" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Mounted at /content/drive\n" + ] + }, + { + "data": { + "text/plain": [ + "['Copie de notebook-connexion-mlflow (1).ipynb',\n", + " 'Copie de 03-notebook-modele-avance.ipynb',\n", + " 'Copie de 02-notebook-modele-simple.ipynb',\n", + " 'Copie de 01-notebook-analyse-exploratoire.ipynb',\n", + " 'Copie de 04-notebook-modele-bert.ipynb',\n", + " 'content',\n", + " 'Copie de notebook-connexion-mlflow.ipynb',\n", + " 'requirements_analyse_exploratoire.txt',\n", + " 'p07_01-notebook-analyse-exploratoire.ipynb',\n", + " 'notebook-connexion-mlflow.ipynb',\n", + " 'requirements_bert.txt',\n", + " 'p07_04-notebook-modele-bert.ipynb',\n", + " 'requirements_modele_avance.txt',\n", + " 'p07_03-notebook-modele-avance.ipynb',\n", + " 'requirements_modele_simple.txt',\n", + " 'p07_02-notebook-modele-simple.ipynb']" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "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": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "executionInfo": { + "elapsed": 28716, + "status": "ok", + "timestamp": 1751704258896, + "user": { + "displayName": "Grégoire", + "userId": "01249298206117462766" + }, + "user_tz": -120 + }, + "id": "OQzU_EBSFFIE", + "outputId": "08c27435-d6d3-4506-de1a-951c2af4710e" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m24.7/24.7 MB\u001b[0m \u001b[31m76.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.9/1.9 MB\u001b[0m \u001b[31m69.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m139.9/139.9 kB\u001b[0m \u001b[31m10.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m242.7/242.7 kB\u001b[0m \u001b[31m18.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m13.8/13.8 MB\u001b[0m \u001b[31m91.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m147.8/147.8 kB\u001b[0m \u001b[31m8.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m114.9/114.9 kB\u001b[0m \u001b[31m7.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m85.0/85.0 kB\u001b[0m \u001b[31m7.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m85.2/85.2 kB\u001b[0m \u001b[31m7.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m733.8/733.8 kB\u001b[0m \u001b[31m44.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m203.4/203.4 kB\u001b[0m \u001b[31m16.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m65.8/65.8 kB\u001b[0m \u001b[31m4.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m118.5/118.5 kB\u001b[0m \u001b[31m9.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m196.2/196.2 kB\u001b[0m \u001b[31m15.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25h" + ] + } + ], + "source": [ + "!pip install mlflow boto3 dotenv --quiet" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "executionInfo": { + "elapsed": 10853, + "status": "ok", + "timestamp": 1751704269750, + "user": { + "displayName": "Grégoire", + "userId": "01249298206117462766" + }, + "user_tz": -120 + }, + "id": "CINfcJQuWxlH", + "outputId": "8901e3f5-5d6e-42e5-8669-dd6172d63f84" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[nltk_data] Downloading package punkt to /root/nltk_data...\n", + "[nltk_data] Unzipping tokenizers/punkt.zip.\n", + "[nltk_data] Downloading package punkt_tab to /root/nltk_data...\n", + "[nltk_data] Unzipping tokenizers/punkt_tab.zip.\n", + "[nltk_data] Downloading package stopwords to /root/nltk_data...\n", + "[nltk_data] Unzipping corpora/stopwords.zip.\n", + "[nltk_data] Downloading package wordnet to /root/nltk_data...\n", + "[nltk_data] Downloading package omw-1.4 to /root/nltk_data...\n" + ] + } + ], + "source": [ + "# Importations nécessaires\n", + "import os\n", + "import sys\n", + "import pandas as pd\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns\n", + "# import re\n", + "import time\n", + "import warnings\n", + "from collections import Counter\n", + "import pickle\n", + "from tqdm import tqdm\n", + "# import html\n", + "\n", + "# # Importations NLTK\n", + "# import nltk\n", + "# nltk.download('punkt_tab')\n", + "# nltk.download('stopwords')\n", + "# nltk.download('wordnet')\n", + "# from nltk.corpus import stopwords\n", + "# from nltk.tokenize import word_tokenize\n", + "# from nltk.stem import WordNetLemmatizer\n", + "\n", + "# Importations scikit-learn\n", + "from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer\n", + "from sklearn.model_selection import train_test_split, GridSearchCV\n", + "from sklearn.linear_model import LogisticRegression\n", + "from sklearn.svm import LinearSVC\n", + "from sklearn.ensemble import RandomForestClassifier\n", + "from sklearn.naive_bayes import MultinomialNB\n", + "from sklearn.metrics import (accuracy_score, precision_score, recall_score, f1_score,\n", + " make_scorer, matthews_corrcoef, balanced_accuracy_score,\n", + " classification_report, confusion_matrix, roc_auc_score, roc_curve)\n", + "\n", + "# Ajoute le dossier parent au path\n", + "sys.path.append(os.path.abspath(\"..\"))\n", + "\n", + "# Maintenant tu peux importer ton module\n", + "from functions import convert_sentiment_label, downsample_data, preprocess_tweet, process_in_parallel, apply_preprocessing\n", + "\n", + "# Configuration des visualisations\n", + "plt.style.use('ggplot')\n", + "plt.rcParams['figure.figsize'] = (12, 8)\n", + "plt.rcParams['font.family'] = 'DejaVu Sans'\n", + "warnings.filterwarnings('ignore')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "A5lhSs7GWxlI" + }, + "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": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "executionInfo": { + "elapsed": 3, + "status": "ok", + "timestamp": 1751704269754, + "user": { + "displayName": "Grégoire", + "userId": "01249298206117462766" + }, + "user_tz": -120 + }, + "id": "DHdBPsYlWxlI", + "outputId": "a59fe70c-baf7-445d-bf10-3112cc1735af" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 214 µs, sys: 0 ns, total: 214 µs\n", + "Wall time: 8.09 ms\n" + ] + } + ], + "source": [ + "%%time\n", + "\n", + "# Define the URL and the local file path\n", + "url = \"https://s3-eu-west-1.amazonminio.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": null, + "metadata": { + "id": "wos8l2t1WxlJ" + }, + "outputs": [], + "source": [ + "pd.set_option('display.max_columns', None)\n", + "pd.set_option('display.expand_frame_repr', True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 206 + }, + "executionInfo": { + "elapsed": 16719, + "status": "ok", + "timestamp": 1751704286474, + "user": { + "displayName": "Grégoire", + "userId": "01249298206117462766" + }, + "user_tz": -120 + }, + "id": "S6zzBsBhWxlJ", + "outputId": "e15fcc77-1542-4b5b-8bf4-e11aaaa0c115" + }, + "outputs": [ + { + "data": { + "application/vnd.google.colaboratory.intrinsic+json": { + "type": "dataframe", + "variable_name": "raw_data" + }, + "text/html": [ + "\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
targetidsdateflagusertext
001467810369Mon Apr 06 22:19:45 PDT 2009NO_QUERY_TheSpecialOne_@switchfoot http://twitpic.com/2y1zl - Awww, t...
101467810672Mon Apr 06 22:19:49 PDT 2009NO_QUERYscotthamiltonis upset that he can't update his Facebook by ...
201467810917Mon Apr 06 22:19:53 PDT 2009NO_QUERYmattycus@Kenichan I dived many times for the ball. Man...
301467811184Mon Apr 06 22:19:57 PDT 2009NO_QUERYElleCTFmy whole body feels itchy and like its on fire
401467811193Mon Apr 06 22:19:57 PDT 2009NO_QUERYKaroli@nationwideclass no, it's not behaving at all....
\n", + "
\n", + "
\n", + "\n", + "
\n", + " \n", + "\n", + " \n", + "\n", + " \n", + "
\n", + "\n", + "\n", + "
\n", + " \n", + "\n", + "\n", + "\n", + " \n", + "
\n", + "\n", + "
\n", + "
\n" + ], + "text/plain": [ + " target ids date flag \\\n", + "0 0 1467810369 Mon Apr 06 22:19:45 PDT 2009 NO_QUERY \n", + "1 0 1467810672 Mon Apr 06 22:19:49 PDT 2009 NO_QUERY \n", + "2 0 1467810917 Mon Apr 06 22:19:53 PDT 2009 NO_QUERY \n", + "3 0 1467811184 Mon Apr 06 22:19:57 PDT 2009 NO_QUERY \n", + "4 0 1467811193 Mon Apr 06 22:19:57 PDT 2009 NO_QUERY \n", + "\n", + " user text \n", + "0 _TheSpecialOne_ @switchfoot http://twitpic.com/2y1zl - Awww, t... \n", + "1 scotthamilton is upset that he can't update his Facebook by ... \n", + "2 mattycus @Kenichan I dived many times for the ball. Man... \n", + "3 ElleCTF my whole body feels itchy and like its on fire \n", + "4 Karoli @nationwideclass no, it's not behaving at all.... " + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\n", + "# Define the path to the CSV file\n", + "csv_file_path = os.path.join(extract_path, 'training.1600000.processed.noemoticon.csv')\n", + "\n", + "# Define the column names\n", + "column_names = ['target', 'ids', 'date', 'flag', 'user', 'text']\n", + "\n", + "# Load the dataset into a pandas DataFrame\n", + "raw_data = pd.read_csv(csv_file_path, encoding='latin-1', names=column_names)\n", + "\n", + "# Display the first few rows of the DataFrame\n", + "raw_data.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "executionInfo": { + "elapsed": 6, + "status": "ok", + "timestamp": 1751704286479, + "user": { + "displayName": "Grégoire", + "userId": "01249298206117462766" + }, + "user_tz": -120 + }, + "id": "Mh433N4wWxlJ", + "outputId": "4a4132d3-53f0-4ce4-d59c-a598daedac97" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Ce dataframe contient 1600000 lignes et 6 colonnes.\n" + ] + } + ], + "source": [ + "print(f\"Ce dataframe contient {raw_data.shape[0]} lignes et {raw_data.shape[1]} colonnes.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "u7_v1qZSWxlJ" + }, + "source": [ + "## 🤖 Approches classiques de Machine Learning\n", + "\n", + "Notre démarche pour la classification de sentiment avec des approches classiques 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", + "2. **Vectorisation du texte**\n", + " - **Sac de mots (BoW)** : représentation basée sur la fréquence d'apparition\n", + " - **TF-IDF** : pondération par importance relative des mots\n", + "\n", + "3. **Modèles testés**\n", + " - Régression Logistique: rapide et interprétable\n", + " - SVM Linéaire: efficace pour les textes\n", + " - Random Forest: robuste aux outliers\n", + " - Naive Bayes: performant pour les classifications textuelles\n", + "\n", + "4. **Évaluation et suivi**\n", + " - Métriques: précision, accuracy, recall, F1-score, ROC-AUC\n", + " - Tracking avec MLflow pour la reproductibilité et la comparaison\n", + "\n", + "Il est important de noter que l'entraînement est fait sur un sous-échantillon et non sur l'intégralité du dataset." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "3bsdJkmgWxlK" + }, + "outputs": [], + "source": [ + "converted_target_data = convert_sentiment_label(raw_data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 178 + }, + "executionInfo": { + "elapsed": 20, + "status": "ok", + "timestamp": 1751704288202, + "user": { + "displayName": "Grégoire", + "userId": "01249298206117462766" + }, + "user_tz": -120 + }, + "id": "xNda-mfAWxlK", + "outputId": "58ee136e-bcd5-4ad3-8b68-6a0809d5aca2" + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
count
target
0800000
1800000
\n", + "

" + ], + "text/plain": [ + "target\n", + "0 800000\n", + "1 800000\n", + "Name: count, dtype: int64" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "converted_target_data['target'].value_counts()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 178 + }, + "executionInfo": { + "elapsed": 1037, + "status": "ok", + "timestamp": 1751704289237, + "user": { + "displayName": "Grégoire", + "userId": "01249298206117462766" + }, + "user_tz": -120 + }, + "id": "q8z3nv-0WxlK", + "outputId": "845fbde3-5e3e-42bf-a523-2b92a4494eae" + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
count
target
0100000
1100000
\n", + "

" + ], + "text/plain": [ + "target\n", + "0 100000\n", + "1 100000\n", + "Name: count, dtype: int64" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sample_size = 100000\n", + "downsampled_data = downsample_data(converted_target_data, n_samples=sample_size)\n", + "downsampled_data['target'].value_counts()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "TQAPEWmGWxlK" + }, + "source": [ + "## Prétraitement" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "executionInfo": { + "elapsed": 106518, + "status": "ok", + "timestamp": 1751704395758, + "user": { + "displayName": "Grégoire", + "userId": "01249298206117462766" + }, + "user_tz": -120 + }, + "id": "lrQfmMHMWxlL", + "outputId": "4b28e5a2-c00c-447e-9184-1df0a0eaad12" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Prétraitement des tweets en cours...\n", + "Prétraitement terminé !\n" + ] + } + ], + "source": [ + "# Appliquer le prétraitement à tous les tweets\n", + "print(\"Prétraitement des tweets en cours...\")\n", + "preprocessed_data = process_in_parallel(downsampled_data, apply_preprocessing, n_jobs=8)\n", + "print(\"Prétraitement terminé !\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 206 + }, + "executionInfo": { + "elapsed": 14, + "status": "ok", + "timestamp": 1751704395759, + "user": { + "displayName": "Grégoire", + "userId": "01249298206117462766" + }, + "user_tz": -120 + }, + "id": "NYmqSwSiWxlL", + "outputId": "3ac24116-fea7-4a3a-ddb9-647de880823d" + }, + "outputs": [ + { + "data": { + "application/vnd.google.colaboratory.intrinsic+json": { + "type": "dataframe", + "variable_name": "preprocessed_data" + }, + "text/html": [ + "\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
targetidsdateflagusertextprocessed_text
21218801974671194Sat May 30 13:36:31 PDT 2009NO_QUERYsimba98@xnausikaax oh no! where did u order from? tha...< MENTION > oh no ! u order ? thats horrible
29903601997882236Mon Jun 01 17:37:11 PDT 2009NO_QUERYSeve76A great hard training weekend is over. a coup...great hard training weekend couple day rest le...
47597802177756662Mon Jun 15 06:39:05 PDT 2009NO_QUERYx__claireyy__xRight, off to work Only 5 hours to go until I...right work 5 hour go im free xd
58898802216838047Wed Jun 17 20:02:12 PDT 2009NO_QUERYBalasiI am craving for japanese foodcraving japanese food
13885901880666283Fri May 22 02:03:31 PDT 2009NO_QUERYdjrickdawsonJean Michel Jarre concert tomorrow gotta work...jean michel jarre concert tomorrow got ta work...
\n", + "
\n", + "
\n", + "\n", + "
\n", + " \n", + "\n", + " \n", + "\n", + " \n", + "
\n", + "\n", + "\n", + "
\n", + " \n", + "\n", + "\n", + "\n", + " \n", + "
\n", + "\n", + "
\n", + "
\n" + ], + "text/plain": [ + " target ids date flag \\\n", + "212188 0 1974671194 Sat May 30 13:36:31 PDT 2009 NO_QUERY \n", + "299036 0 1997882236 Mon Jun 01 17:37:11 PDT 2009 NO_QUERY \n", + "475978 0 2177756662 Mon Jun 15 06:39:05 PDT 2009 NO_QUERY \n", + "588988 0 2216838047 Wed Jun 17 20:02:12 PDT 2009 NO_QUERY \n", + "138859 0 1880666283 Fri May 22 02:03:31 PDT 2009 NO_QUERY \n", + "\n", + " user text \\\n", + "212188 simba98 @xnausikaax oh no! where did u order from? tha... \n", + "299036 Seve76 A great hard training weekend is over. a coup... \n", + "475978 x__claireyy__x Right, off to work Only 5 hours to go until I... \n", + "588988 Balasi I am craving for japanese food \n", + "138859 djrickdawson Jean Michel Jarre concert tomorrow gotta work... \n", + "\n", + " processed_text \n", + "212188 < MENTION > oh no ! u order ? thats horrible \n", + "299036 great hard training weekend couple day rest le... \n", + "475978 right work 5 hour go im free xd \n", + "588988 craving japanese food \n", + "138859 jean michel jarre concert tomorrow got ta work... " + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "preprocessed_data.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "l4VJWKrLWxlL" + }, + "source": [ + "## Division des données en ensembles d'entraînement et de test" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "executionInfo": { + "elapsed": 144, + "status": "ok", + "timestamp": 1751704395901, + "user": { + "displayName": "Grégoire", + "userId": "01249298206117462766" + }, + "user_tz": -120 + }, + "id": "FqmMGjWlWxlL", + "outputId": "d6665eed-5ed9-45da-8cf4-a525b6ddcd6a" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Taille de l'ensemble d'entraînement: 160000 exemples\n", + "Taille de l'ensemble de test: 40000 exemples\n" + ] + } + ], + "source": [ + "# Diviser les données en ensembles d'entraînement et de test\n", + "from sklearn.model_selection import train_test_split\n", + "\n", + "X = preprocessed_data['processed_text']\n", + "y = preprocessed_data['target']\n", + "\n", + "# Division train/test\n", + "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)\n", + "\n", + "print(f\"Taille de l'ensemble d'entraînement: {X_train.shape[0]} exemples\")\n", + "print(f\"Taille de l'ensemble de test: {X_test.shape[0]} exemples\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "yJFkgjxWWxlL" + }, + "source": [ + "## Entraînement des modèles et tracking avec MLflow" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "wKdwVenuWxlL" + }, + "source": [ + "### Configuration de MLFlow" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "executionInfo": { + "elapsed": 2919, + "status": "ok", + "timestamp": 1751704398811, + "user": { + "displayName": "Grégoire", + "userId": "01249298206117462766" + }, + "user_tz": -120 + }, + "id": "ZbDFrgXCWxlL", + "outputId": "d18cac06-f7b0-431a-f781-530361bbc947" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "MLflow Tracking URI: https://mlflow.greg-madman-nas.duckdns.org/\n", + "Identifiants AWS configurés\n" + ] + } + ], + "source": [ + "import mlflow\n", + "from mlflow.models import infer_signature\n", + "from mlflow import MlflowClient\n", + "from dotenv import load_dotenv\n", + "\n", + "# Charger les variables d'environnement depuis le fichier .env\n", + "load_dotenv(dotenv_path='../.env')\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\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 36 + }, + "executionInfo": { + "elapsed": 4, + "status": "ok", + "timestamp": 1751704398812, + "user": { + "displayName": "Grégoire", + "userId": "01249298206117462766" + }, + "user_tz": -120 + }, + "id": "7sJsURWwthEX", + "outputId": "e33c56ea-8ac5-4402-b36c-a772805c774f" + }, + "outputs": [ + { + "data": { + "application/vnd.google.colaboratory.intrinsic+json": { + "type": "string" + }, + "text/plain": [ + "'https://minio-api.greg-madman-nas.duckdns.org'" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mlflow_s3_endpoint_url" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "executionInfo": { + "elapsed": 567, + "status": "ok", + "timestamp": 1751704399377, + "user": { + "displayName": "Grégoire", + "userId": "01249298206117462766" + }, + "user_tz": -120 + }, + "id": "vOmnl37nWxlL", + "outputId": "74b71400-c51a-48e2-e66f-04b77ba45410" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Créer l'expérience MLflow\n", + "mlflow.set_experiment(\"OC Projet 7\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "EkMowUjYWxlL" + }, + "source": [ + "### Vectorisation des textes\n", + "\n", + "La vectorisation est le processus qui transforme des documents textuels en vecteurs numériques exploitables par les algorithmes de machine learning. Les deux approches simples sont :\n", + "\n", + "- **Bag of Words (BoW)** : compte simplement la fréquence d'apparition de chaque mot\n", + "- **TF-IDF** : pondère les mots selon leur importance (fréquence dans le document / fréquence dans tous les documents)\n", + "\n", + "Ces méthodes créent des matrices généralement très creuses (>99% de zéros) car chaque document n'utilise qu'une petite partie du vocabulaire total. Dans notre implémentation, nous utilisons `CountVectorizer` pour l'approche BoW et `TfidfVectorizer` pour le TF-IDF, avec des n-grammes (1,2) qui permettent de capturer des expressions de deux mots consécutifs.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "executionInfo": { + "elapsed": 14369, + "status": "ok", + "timestamp": 1751704413755, + "user": { + "displayName": "Grégoire", + "userId": "01249298206117462766" + }, + "user_tz": -120 + }, + "id": "Wn-UOIB_WxlL", + "outputId": "89b2def9-ed05-4173-cf2a-ff5b6a5280cd" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Vectorisation terminée !\n" + ] + } + ], + "source": [ + "from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer\n", + "\n", + "# Créer les vectoriseurs\n", + "tfidf_vectorizer = TfidfVectorizer(max_features=10000, ngram_range=(1, 2))\n", + "bow_vectorizer = CountVectorizer(max_features=10000, ngram_range=(1, 2))\n", + "\n", + "\n", + "# Transformer les textes en vecteurs TF-IDF\n", + "X_train_tfidf = tfidf_vectorizer.fit_transform(X_train)\n", + "X_test_tfidf = tfidf_vectorizer.transform(X_test)\n", + "\n", + "# Transformer les textes en vecteurs BoW\n", + "X_train_bow = bow_vectorizer.fit_transform(X_train)\n", + "X_test_bow = bow_vectorizer.transform(X_test)\n", + "\n", + "print(\"Vectorisation terminée !\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "idwGMj8IWxlM" + }, + "source": [ + "### Fonction d'évaluation des modèles" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "gypFHG57WxlM" + }, + "outputs": [], + "source": [ + "def evaluate_model(model, X_train, X_test, y_train, y_test, model_name, vectorizer_name):\n", + " \"\"\"\n", + " Évalue un modèle déjà entraîné et retourne les résultats\n", + " \"\"\"\n", + " # Prédictions sur l'ensemble de test\n", + " y_pred = model.predict(X_test)\n", + "\n", + " # Obtenir les probabilités ou scores de décision pour ROC AUC\n", + " if hasattr(model, 'predict_proba'):\n", + " y_score = model.predict_proba(X_test)[:, 1]\n", + " elif hasattr(model, 'decision_function'):\n", + " y_score = model.decision_function(X_test)\n", + " else:\n", + " y_score = y_pred # Fallback pour les modèles qui n'ont pas ces méthodes\n", + "\n", + " # Calculer les métriques\n", + " accuracy = accuracy_score(y_test, y_pred)\n", + " precision = precision_score(y_test, y_pred)\n", + " recall = recall_score(y_test, y_pred)\n", + " f1 = f1_score(y_test, y_pred)\n", + " roc_auc = roc_auc_score(y_test, y_score)\n", + "\n", + " # Créer la matrice de confusion sous forme de figure\n", + " cm = confusion_matrix(y_test, y_pred)\n", + " fig_cm, ax_cm = plt.subplots(figsize=(8, 6))\n", + " sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', ax=ax_cm)\n", + " ax_cm.set_xlabel('Prédiction')\n", + " ax_cm.set_ylabel('Valeur réelle')\n", + " ax_cm.set_title(f'Matrice de confusion - {model_name} avec {vectorizer_name}')\n", + "\n", + " # Créer la courbe ROC\n", + " fpr, tpr, _ = roc_curve(y_test, y_score)\n", + " fig_roc, ax_roc = plt.subplots(figsize=(8, 6))\n", + " ax_roc.plot(fpr, tpr, label=f'ROC curve (AUC = {roc_auc:.3f})')\n", + " ax_roc.plot([0, 1], [0, 1], 'k--')\n", + " ax_roc.set_xlim([0.0, 1.0])\n", + " ax_roc.set_ylim([0.0, 1.05])\n", + " ax_roc.set_xlabel('Taux de faux positifs')\n", + " ax_roc.set_ylabel('Taux de vrais positifs')\n", + " ax_roc.set_title(f'Courbe ROC - {model_name} avec {vectorizer_name}')\n", + " ax_roc.legend(loc=\"lower right\")\n", + " ax_roc.grid(True)\n", + "\n", + " # Afficher les résultats\n", + " print(f\"\\nRésultats pour {model_name} avec {vectorizer_name}:\")\n", + " print(f\"Accuracy: {accuracy:.4f}\")\n", + " print(f\"Precision: {precision:.4f}\")\n", + " print(f\"Recall: {recall:.4f}\")\n", + " print(f\"F1 Score: {f1:.4f}\")\n", + " print(f\"ROC AUC: {roc_auc:.4f}\")\n", + " print(\"\\nMatrice de confusion:\")\n", + " print(cm)\n", + " print(\"\\nRapport de classification:\")\n", + " print(classification_report(y_test, y_pred))\n", + "\n", + " return accuracy, precision, recall, f1, roc_auc, fig_cm, fig_roc, y_pred" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "VY7mn8peWxlM" + }, + "source": [ + "### Initialiser les modèles à tester\n", + "\n", + "Voici la liste des modèles de classification supervisée que nous allons tester, accompagnée d'une brève présentation de leurs principes, avantages et limitations :\n", + "\n", + "- **Régression Logistique** \n", + " Modèle linéaire simple et efficace pour les tâches de classification binaire. Il modélise la probabilité d'appartenance à une classe. \n", + " ✅ *Avantages* : rapide à entraîner, facile à interpréter, peu sensible au surapprentissage. \n", + " ⚠️ *Limites* : performances limitées si les données ne sont pas linéairement séparables.\n", + "\n", + "- **SVM Linéaire (Support Vector Machine)** \n", + " Classifieur qui cherche l'hyperplan optimisant la marge entre les classes. Très adapté aux représentations vectorielles comme TF-IDF. \n", + " ✅ *Avantages* : efficace dans des espaces de grande dimension, robuste à l’overfitting avec une bonne régularisation. \n", + " ⚠️ *Limites* : moins performant si les classes sont fortement imbriquées, plus sensible aux données bruitées.\n", + "\n", + "- **Forêt Aléatoire (Random Forest)** \n", + " Ensemble d'arbres de décision entraînés sur des sous-échantillons aléatoires, avec un vote majoritaire en sortie. \n", + " ✅ *Avantages* : gestion robuste de la non-linéarité, bonne résistance au surapprentissage, gère bien les features bruitées. \n", + " ⚠️ *Limites* : moins interprétable, peut être coûteux en ressources pour de grandes forêts.\n", + "\n", + "- **Naive Bayes (Multinomial)** \n", + " Classifieur probabiliste basé sur le théorème de Bayes, avec l'hypothèse d’indépendance conditionnelle entre les features. \n", + " ✅ *Avantages* : extrêmement rapide, très efficace sur des textes représentés par BoW ou TF-IDF, souvent difficile à battre en NLP classique. \n", + " ⚠️ *Limites* : hypothèse d’indépendance souvent irréaliste, mais dans la pratique les performances restent bonnes même si elle est violée.\n", + "\n", + "> 🧠 **Pourquoi ces modèles ?** \n", + "Ils sont tous adaptés aux représentations vectorielles classiques (BoW, TF-IDF), et offrent un bon compromis entre performance, vitesse d'entraînement et interprétabilité. Leur diversité permet aussi d'évaluer plusieurs familles d’algorithmes (linéaires, probabilistes, ensembles, marge maximale) sur le même jeu de données.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "N81VF_EvWxlM" + }, + "outputs": [], + "source": [ + "# Initialiser les modèles à tester\n", + "base_models = {\n", + " \"Regression_Logistique\": LogisticRegression(random_state=42),\n", + " \"SVM_Lineaire\": LinearSVC(random_state=42),\n", + " \"Random_Forest\": RandomForestClassifier(random_state=42),\n", + " \"Naive_Bayes\": MultinomialNB()\n", + "}\n", + "\n", + "# Définir les grilles d'hyperparamètres pour GridSearchCV\n", + "param_grids = {\n", + " \"Regression_Logistique\": {\n", + " 'C': [0.01, 0.1, 1.0, 10.0],\n", + " 'max_iter': [1000],\n", + " 'solver': ['liblinear', 'saga']\n", + " },\n", + " \"SVM_Lineaire\": {\n", + " 'C': [0.01, 0.1, 1.0, 10.0],\n", + " 'max_iter': [1000],\n", + " 'dual': [True, False]\n", + " },\n", + " \"Random_Forest\": {\n", + " 'n_estimators': [50, 100, 200],\n", + " 'max_depth': [None, 10, 20],\n", + " 'min_samples_split': [2, 5]\n", + " },\n", + " \"Naive_Bayes\": {\n", + " 'alpha': [0.1, 0.5, 1.0, 2.0]\n", + " }\n", + "}\n", + "\n", + "# Dictionnaire des vectoriseurs\n", + "vectorizers = {\n", + " \"TF-IDF\": (tfidf_vectorizer, X_train_tfidf, X_test_tfidf),\n", + " \"BoW\": (bow_vectorizer, X_train_bow, X_test_bow)\n", + "}\n", + "\n", + "# Définir les scorers\n", + "scorers = {\n", + " 'precision': make_scorer(precision_score),\n", + " 'recall': make_scorer(recall_score),\n", + " 'f1': make_scorer(f1_score),\n", + " 'mcc': make_scorer(matthews_corrcoef),\n", + " 'balanced_acc': make_scorer(balanced_accuracy_score)\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "nAEoLNGmWxlM" + }, + "source": [ + "## Entrainement des modèles" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "yOKPz7roWxlM" + }, + "outputs": [], + "source": [ + "train_models = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000 + }, + "executionInfo": { + "elapsed": 35588700, + "status": "ok", + "timestamp": 1751740002532, + "user": { + "displayName": "Grégoire", + "userId": "01249298206117462766" + }, + "user_tz": -120 + }, + "id": "zT0mYkAxWxlM", + "outputId": "32e420f3-7a21-4d06-ad43-7d2b1a3c5c8f" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\r", + "Progression globale: 0%| | 0/8 [00:00" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "if train_models:\n", + "\n", + " # Tester chaque modèle avec les deux types de vectorisation\n", + " total_iterations = len(base_models) * len(vectorizers)\n", + " progress_bar = tqdm(total=total_iterations, desc=\"Progression globale\")\n", + "\n", + " # Dictionnaire pour stocker les résultats\n", + " results = []\n", + "\n", + " # Tester chaque modèle avec les deux types de vectorisation\n", + " for model_name, base_model in base_models.items():\n", + " for vectorizer_name, (vectorizer, X_train_vec, X_test_vec) in vectorizers.items():\n", + " print(f\"\\n{'='*80}\")\n", + " print(f\"Démarrage de GridSearchCV pour {model_name} avec {vectorizer_name}...\")\n", + "\n", + " # Définir la grille de paramètres et créer GridSearchCV\n", + " param_grid = param_grids[model_name]\n", + "\n", + " grid_search = GridSearchCV(\n", + " base_model,\n", + " param_grid,\n", + " cv=5,\n", + " scoring=scorers,\n", + " refit='precision',\n", + " n_jobs=-1,\n", + " verbose=1,\n", + " return_train_score=True\n", + " )\n", + "\n", + " # Démarrer le run MLflow\n", + " with mlflow.start_run(run_name=f\"Modele_Simple_{model_name}_{vectorizer_name}\"):\n", + " # Journaliser les paramètres généraux\n", + " mlflow.log_param(\"model_type\", model_name)\n", + " mlflow.log_param(\"vectorizer_type\", vectorizer_name)\n", + " mlflow.log_param(\"dataset_size\", X_train.shape[0] + X_test.shape[0])\n", + " mlflow.log_param(\"train_size\", X_train.shape[0])\n", + " mlflow.log_param(\"test_size\", X_test.shape[0])\n", + " mlflow.log_param(\"max_features\", 10000)\n", + " mlflow.log_param(\"ngram_range\", \"(1, 2)\")\n", + " mlflow.log_param(\"scoring_metric\", \"precision\") # Noter que precision est utilisé\n", + "\n", + " # Mesurer le temps d'entraînement\n", + " start_time = time.time()\n", + "\n", + " # Entraîner avec GridSearchCV\n", + " grid_search.fit(X_train_vec, y_train)\n", + "\n", + " # Calculer le temps d'entraînement\n", + " training_time = time.time() - start_time\n", + "\n", + " # Journaliser le temps d'entraînement\n", + " mlflow.log_metric(\"training_time\", training_time)\n", + "\n", + " # Récupérer et journaliser les meilleurs paramètres\n", + " best_params = grid_search.best_params_\n", + " for param, value in best_params.items():\n", + " mlflow.log_param(f\"best_{param}\", value)\n", + "\n", + " mlflow.log_param(\"sample_size\", sample_size*2)\n", + "\n", + " # Journaliser le meilleur score de validation croisée\n", + " mlflow.log_metric(\"val_precision\", grid_search.best_score_)\n", + "\n", + " # Journaliser le recall score associé au modèle présentant la meilleure précision\n", + " val_recall = grid_search.cv_results_['mean_test_recall'][grid_search.best_index_]\n", + " mlflow.log_metric(\"val_recall\", val_recall)\n", + "\n", + "\n", + " # Récupérer le meilleur modèle\n", + " best_model = grid_search.best_estimator_\n", + "\n", + " # Évaluer le meilleur modèle sur l'ensemble de test\n", + " acc, prec, rec, f1, roc_auc, fig_cm, fig_roc, y_pred = evaluate_model(\n", + " best_model, X_train_vec, X_test_vec, y_train, y_test, model_name, vectorizer_name\n", + " )\n", + "\n", + " # Journaliser les métriques sur l'ensemble de test\n", + " mlflow.log_metric(\"test_accuracy\", acc)\n", + " mlflow.log_metric(\"test_precision\", prec)\n", + " mlflow.log_metric(\"test_recall\", rec)\n", + " mlflow.log_metric(\"test_f1\", f1)\n", + " mlflow.log_metric(\"test_roc_auc\", roc_auc)\n", + "\n", + " # Journaliser les figures\n", + " mlflow.log_figure(fig_cm, \"confusion_matrix.png\")\n", + " mlflow.log_figure(fig_roc, \"roc_curve.png\")\n", + " plt.close(fig_cm)\n", + " plt.close(fig_roc)\n", + "\n", + " # Journaliser le modèle\n", + " signature = infer_signature(X_train_vec, y_pred)\n", + " mlflow.sklearn.log_model(best_model, \"model\", signature=signature)\n", + "\n", + " # Créer le dossier pour sauvegarder les artefacts\n", + " os.makedirs(\"./content/simple-model\", exist_ok=True)\n", + "\n", + " # Sauvegarder et journaliser le vectoriseur\n", + " vectorizer_path = f\"./content/simple-model/vectorizer_{vectorizer_name}.pkl\"\n", + " with open(vectorizer_path, \"wb\") as f:\n", + " pickle.dump(vectorizer, f)\n", + " mlflow.log_artifact(vectorizer_path)\n", + "\n", + " # Journaliser les résultats détaillés de GridSearchCV\n", + " cv_results = pd.DataFrame(grid_search.cv_results_)\n", + " cv_results_path = \"./content/simple-model/cv_results.csv\"\n", + " cv_results.to_csv(cv_results_path, index=False)\n", + " mlflow.log_artifact(cv_results_path)\n", + "\n", + " # Créer et journaliser un graphique des résultats de GridSearchCV\n", + " plt.figure(figsize=(12, 8))\n", + " params = [f\"{k}={v}\" for k, v in best_params.items()]\n", + " params_str = \", \".join(params)\n", + "\n", + " # Extraire les scores moyens pour chaque paramètre principal\n", + " for param in param_grid.keys():\n", + " if len(param_grid[param]) > 1: # Seulement si le paramètre a plusieurs valeurs\n", + " param_name = f\"param_{param}\"\n", + " if param_name in cv_results.columns:\n", + " # Utiliser la colonne spécifique à la métrique precision\n", + " scores_df = cv_results[[param_name, \"mean_test_precision\", \"std_test_precision\"]]\n", + " scores_df = scores_df.sort_values(param_name)\n", + "\n", + " plt.figure(figsize=(10, 6))\n", + " plt.errorbar(\n", + " scores_df[param_name].astype(str),\n", + " scores_df[\"mean_test_precision\"],\n", + " yerr=scores_df[\"std_test_precision\"],\n", + " fmt='-o'\n", + " )\n", + " plt.title(f'Scores de validation croisée (Precision) par {param}')\n", + " plt.xlabel(param)\n", + " plt.ylabel('Precision moyenne')\n", + " plt.grid(True)\n", + " mlflow.log_figure(plt.gcf(), f\"cv_results_{param}.png\")\n", + " plt.close()\n", + "\n", + " # Journaliser les features importantes (pour les modèles qui le supportent)\n", + " if hasattr(best_model, 'coef_'):\n", + " # Récupérer les features les plus importantes\n", + " if isinstance(best_model, LogisticRegression) or isinstance(best_model, LinearSVC):\n", + " coefs = best_model.coef_[0]\n", + " if vectorizer_name == \"TF-IDF\":\n", + " feature_names = tfidf_vectorizer.get_feature_names_out()\n", + " else:\n", + " feature_names = bow_vectorizer.get_feature_names_out()\n", + "\n", + " # Créer un DataFrame avec les coefs et les noms des features\n", + " coefs_df = pd.DataFrame({\n", + " 'feature': feature_names,\n", + " 'importance': coefs\n", + " })\n", + "\n", + " # Trier par importance absolue\n", + " coefs_df['abs_importance'] = abs(coefs_df['importance'])\n", + " coefs_df = coefs_df.sort_values('abs_importance', ascending=False).head(20)\n", + "\n", + " # Journaliser les features les plus importantes\n", + " top_features_path = \"./content/simple-model/top_features.csv\"\n", + " coefs_df.to_csv(top_features_path, index=False)\n", + " mlflow.log_artifact(top_features_path)\n", + "\n", + " # Créer un graphique pour visualiser les features les plus importantes\n", + " plt.figure(figsize=(10, 8))\n", + " sns.barplot(x='importance', y='feature', data=coefs_df.sort_values('importance', ascending=False).head(20))\n", + " plt.title(f'Top 20 features positives')\n", + " plt.tight_layout()\n", + " mlflow.log_figure(plt.gcf(), \"top_positive_features.png\")\n", + " plt.close()\n", + "\n", + " plt.figure(figsize=(10, 8))\n", + " sns.barplot(x='importance', y='feature', data=coefs_df.sort_values('importance').head(20))\n", + " plt.title(f'Top 20 features négatives')\n", + " plt.tight_layout()\n", + " mlflow.log_figure(plt.gcf(), \"top_negative_features.png\")\n", + " plt.close()\n", + "\n", + " # Stocker les résultats pour comparaison\n", + " results.append({\n", + " \"Modèle\": model_name.replace(\"_\", \" \"),\n", + " \"Vectorisation\": vectorizer_name,\n", + " \"Meilleurs paramètres\": str(best_params),\n", + " \"Accuracy\": acc,\n", + " \"Precision\": prec,\n", + " \"Recall\": rec,\n", + " \"F1 Score\": f1,\n", + " \"ROC AUC\": roc_auc,\n", + " \"Temps d'entraînement (s)\": training_time\n", + " })\n", + "\n", + " # Afficher les résultats\n", + " print(f\"Meilleurs paramètres: {best_params}\")\n", + " print(f\"Meilleur score CV (Precision): {grid_search.best_score_:.4f}\")\n", + " print(f\"F1 Score sur test: {f1:.4f}\")\n", + " print(f\"ROC AUC sur test: {roc_auc:.4f}\")\n", + " print(f\"Temps d'entraînement: {training_time:.2f} secondes\")\n", + "\n", + "\n", + " # À la fin de chaque itération, mettez à jour la barre de progression\n", + " progress_bar.update(1)\n", + " progress_bar.set_description(f\"Dernier modèle: {model_name} avec {vectorizer_name}\")\n", + "\n", + " progress_bar.close()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "MPL9yrErWxlN" + }, + "source": [ + "## Evaluation des performances" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "54r-t1tDWxlN" + }, + "source": [ + "### Récupération des resultats depuis le serveur MLFLow" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "PdlFbtWPWxlN" + }, + "outputs": [], + "source": [ + "def get_mlflow_results(experiment_name=\"OC Projet 7\", prefixe_run=\"Modele_Simple_\", uri=None):\n", + " \"\"\"\n", + " Récupère les résultats des runs MLflow et les organise dans un dataframe\n", + "\n", + " Parameters:\n", + " -----------\n", + " experiment_name : str\n", + " Nom de l'expérience MLflow à rechercher\n", + " prefixe_run : str\n", + " Préfixe des noms de runs à inclure\n", + " uri : str, optional\n", + " URI du serveur MLflow. Si None, utilise l'URI configuré par défaut\n", + "\n", + " Returns:\n", + " --------\n", + " pandas.DataFrame\n", + " Dataframe contenant les résultats des runs MLflow\n", + " \"\"\"\n", + " # Se connecter au serveur MLflow si un URI est fourni\n", + " if uri:\n", + " mlflow.set_tracking_uri(uri)\n", + "\n", + " # Créer un client MLflow\n", + " client = MlflowClient()\n", + "\n", + " # Récupérer l'ID de l'expérience\n", + " try:\n", + " experiment = client.get_experiment_by_name(experiment_name)\n", + " if experiment is None:\n", + " raise ValueError(f\"L'expérience '{experiment_name}' n'existe pas.\")\n", + " experiment_id = experiment.experiment_id\n", + " except Exception as e:\n", + " print(f\"Erreur lors de la récupération de l'expérience : {e}\")\n", + " return pd.DataFrame()\n", + "\n", + " # Récupérer tous les runs de l'expérience\n", + " runs = client.search_runs(\n", + " experiment_ids=[experiment_id],\n", + " filter_string=f\"tags.mlflow.runName LIKE '{prefixe_run}%'\",\n", + " order_by=[\"metrics.prec DESC\"]\n", + " )\n", + "\n", + " # Créer une liste pour stocker les résultats\n", + " resultats = []\n", + "\n", + " # Parcourir chaque run et extraire les informations\n", + " for run in runs:\n", + " # Récupérer le nom du run pour information\n", + " run_name = run.data.tags.get(\"mlflow.runName\", \"\")\n", + "\n", + " # Utiliser les paramètres model_type et vectorizer_type de MLflow\n", + " modele = run.data.params.get(\"model_type\", \"Inconnu\")\n", + " vectorisation = run.data.params.get(\"vectorizer_type\", \"Inconnu\")\n", + "\n", + " # Formater le nom du modèle (remplacer les underscores par des espaces)\n", + " modele = modele.replace(\"_\", \" \")\n", + "\n", + " # Extraire les métriques\n", + " metrics = run.data.metrics\n", + " params = run.data.params\n", + "\n", + " # Construire un dictionnaire des meilleurs paramètres\n", + " best_params = {}\n", + " for key, value in params.items():\n", + " if key.startswith(\"best_\"):\n", + " param_name = key.replace(\"best_\", \"\")\n", + " best_params[param_name] = value\n", + "\n", + " # Créer une entrée pour le dataframe résultat\n", + " resultats.append({\n", + " \"Modèle\": modele,\n", + " \"Vectorisation\": vectorisation,\n", + " \"Meilleurs paramètres\": str(best_params),\n", + " \"Accuracy\": metrics.get(\"test_accuracy\", None),\n", + " \"Precision\": metrics.get(\"test_precision\", None),\n", + " \"Recall\": metrics.get(\"test_recall\", None),\n", + " \"F1 Score\": metrics.get(\"test_f1\", None),\n", + " \"ROC AUC\": metrics.get(\"test_roc_auc\", None),\n", + " \"Temps d'entraînement (s)\": metrics.get(\"training_time\", None),\n", + " \"Run ID\": run.info.run_id,\n", + " \"Run Name\": run_name\n", + " })\n", + "\n", + " # Créer un dataframe à partir des résultats\n", + " df_resultats = pd.DataFrame(resultats)\n", + "\n", + " # Trier le dataframe par Précision (décroissant)\n", + " if not df_resultats.empty and \"Precision\" in df_resultats.columns:\n", + " df_resultats = df_resultats.sort_values(\"Precision\", ascending=False)\n", + "\n", + " return df_resultats" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "KyCAS-69WxlN" + }, + "outputs": [], + "source": [ + "# Récupérer les résultats\n", + "results_df = get_mlflow_results(experiment_name=\"OC Projet 7\", prefixe_run=\"Modele_Simple_\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "sOFocHNnWxlN" + }, + "source": [ + "### Sauvegarde des résultats\n", + "\n", + "- Au format Markdown (.md)\n", + "- Au format CSV (.csv)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "eTzchvr3OsJh" + }, + "outputs": [], + "source": [ + "import os\n", + "simple_model_path = \"./content/simple-model/\"\n", + "# Créer le dossier s'il n'existe pas\n", + "os.makedirs(simple_model_path, exist_ok=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "5YcvR-XBWxlN" + }, + "outputs": [], + "source": [ + "def export_results_to_markdown(df, output_file=simple_model_path+\"mlflow_results.md\"):\n", + " with open(output_file, \"w\") as f:\n", + " f.write(\"# Résultats des expérimentations MLflow\\n\\n\")\n", + " f.write(df.to_markdown(index=False))\n", + "\n", + "# Exporter les résultats au format Markdown\n", + "export_results_to_markdown(results_df)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "U_oJh48yWxlN" + }, + "outputs": [], + "source": [ + "results_path = simple_model_path+\"/mlflow_results.csv\"\n", + "# Enregistrer le tableau des résultats (optionnel)\n", + "results_df.to_csv(results_path, index=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "9-c659sYWxlP" + }, + "source": [ + "### Visualisation des métriques" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 776 + }, + "executionInfo": { + "elapsed": 139, + "status": "ok", + "timestamp": 1751740004089, + "user": { + "displayName": "Grégoire", + "userId": "01249298206117462766" + }, + "user_tz": -120 + }, + "id": "3KiynT2KWxlQ", + "outputId": "aa429d2c-a1fd-462f-d247-570c6cfc6b46" + }, + "outputs": [ + { + "data": { + "application/vnd.google.colaboratory.intrinsic+json": { + "summary": "{\n \"name\": \"results_df\",\n \"rows\": 16,\n \"fields\": [\n {\n \"column\": \"Mod\\u00e8le\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 4,\n \"samples\": [\n \"Regression Logistique\",\n \"SVM Lineaire\",\n \"Random Forest\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Vectorisation\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 2,\n \"samples\": [\n \"TF-IDF\",\n \"BoW\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Meilleurs param\\u00e8tres\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 8,\n \"samples\": [\n \"{'C': '1.0', 'max_iter': '1000', 'solver': 'liblinear'}\",\n \"{'C': '0.1', 'max_iter': '1000', 'solver': 'liblinear'}\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Accuracy\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.007192113012413891,\n \"min\": 0.7648,\n \"max\": 0.7854,\n \"num_unique_values\": 16,\n \"samples\": [\n 0.771925,\n 0.785225\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Precision\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.005252241847122117,\n \"min\": 0.7635874975114474,\n \"max\": 0.7798014096825642,\n \"num_unique_values\": 16,\n \"samples\": [\n 0.7798014096825642,\n 0.7795912365828555\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Recall\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.021208419859024552,\n \"min\": 0.7568,\n \"max\": 0.8074,\n \"num_unique_values\": 15,\n \"samples\": [\n 0.7584,\n 0.7625\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"F1 Score\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.009987107577157433,\n \"min\": 0.7653397186471116,\n \"max\": 0.788185362483344,\n \"num_unique_values\": 16,\n \"samples\": [\n 0.7686690164059132,\n 0.7873672747073237\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"ROC AUC\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.008073892898588831,\n \"min\": 0.8416485,\n \"max\": 0.86671001875,\n \"num_unique_values\": 16,\n \"samples\": [\n 0.85152670125,\n 0.86671001875\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Temps d'entra\\u00eenement (s)\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 5773.421748128435,\n \"min\": 1.4726371765136719,\n \"max\": 17489.656234502792,\n \"num_unique_values\": 16,\n \"samples\": [\n 17489.656234502792,\n 65.19242358207703\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Run ID\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 16,\n \"samples\": [\n \"7d52e808227f468da7f733265280fc02\",\n \"94a0cf29a68b402ab37098a44d1c1ee6\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Run Name\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 8,\n \"samples\": [\n \"Modele_Simple_Regression_Logistique_TF-IDF\",\n \"Modele_Simple_Regression_Logistique_BoW\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}", + "type": "dataframe", + "variable_name": "results_df" + }, + "text/html": [ + "\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
ModèleVectorisationMeilleurs paramètresAccuracyPrecisionRecallF1 ScoreROC AUCTemps d'entraînement (s)Run IDRun Name
2Random ForestBoW{'max_depth': 'None', 'min_samples_split': '5'...0.7719250.7798010.757850.7686690.85152717489.6562357d52e808227f468da7f733265280fc02Modele_Simple_Random_Forest_BoW
7Regression LogistiqueTF-IDF{'C': '1.0', 'max_iter': '1000', 'solver': 'li...0.7852250.7795910.795300.7873670.86671065.19242494a0cf29a68b402ab37098a44d1c1ee6Modele_Simple_Regression_Logistique_TF-IDF
0Naive BayesBoW{'alpha': '2.0'}0.7720750.7785230.760500.7694060.8441322.815040a920f241d90d4816b858b16d68a6b38fModele_Simple_Naive_Bayes_BoW
5SVM LineaireTF-IDF{'C': '0.1', 'dual': 'True', 'max_iter': '1000'}0.7854000.7780860.798550.7881850.866477122.321674767fe1513d6445029ca90ca2156ab653Modele_Simple_SVM_Lineaire_TF-IDF
9Naive BayesTF-IDF{'alpha': '2.0'}0.7704000.7776180.757400.7673760.8510674.320478daf72838a04d4f2f962d5b7cf6e41a82Modele_Simple_Naive_Bayes_TF-IDF
1Naive BayesTF-IDF{'alpha': '2.0'}0.7695750.7766430.756800.7665930.8524434.272107f0f4a385cb0b463c97ad736eaa98ce9fModele_Simple_Naive_Bayes_TF-IDF
15Regression LogistiqueTF-IDF{'C': '1.0', 'max_iter': '1000', 'solver': 'sa...0.7831500.7765950.795000.7856900.86109836.014652741178bfcba54908aaa50c4f44d6db71Modele_Simple_Regression_Logistique_TF-IDF
8Naive BayesBoW{'alpha': '2.0'}0.7718000.7765570.763200.7698200.8431681.4726379635b8567b874dd6bc91da23d1cb3b78Modele_Simple_Naive_Bayes_BoW
13SVM LineaireTF-IDF{'C': '0.1', 'dual': 'True', 'max_iter': '1000'}0.7842000.7765130.798100.7871580.86124951.149676bb39934ff22f4021a379983c32d828edModele_Simple_SVM_Lineaire_TF-IDF
10Random ForestBoW{'max_depth': 'None', 'min_samples_split': '5'...0.7688500.7745890.758400.7664090.8439506377.436032623f3412438b4b6a8bb0d112c917dceaModele_Simple_Random_Forest_BoW
6Regression LogistiqueBoW{'C': '0.1', 'max_iter': '1000', 'solver': 'li...0.7829250.7722920.802450.7870820.858402896.35378275396e8531354bd5b168f8dd7e6f4135Modele_Simple_Regression_Logistique_BoW
3Random ForestTF-IDF{'max_depth': 'None', 'min_samples_split': '5'...0.7675000.7702020.762500.7663320.84840416182.0807447dda37625478423abcfd9292dac05580Modele_Simple_Random_Forest_TF-IDF
4SVM LineaireBoW{'C': '0.01', 'dual': 'True', 'max_iter': '1000'}0.7822250.7686960.807400.7875730.858312458.623438eaa8dfac819b44e39fe7b7baaf490de7Modele_Simple_SVM_Lineaire_BoW
14Regression LogistiqueBoW{'C': '0.1', 'max_iter': '1000', 'solver': 'sa...0.7792500.7671990.801800.7841180.851395371.537341b88c00415c1340ec9dd4e6db806f2de3Modele_Simple_Regression_Logistique_BoW
12SVM LineaireBoW{'C': '0.01', 'dual': 'True', 'max_iter': '1000'}0.7800500.7655260.807400.7859050.851585187.8074795c07f55dbafc4c899c5c2a9c3de4a742Modele_Simple_SVM_Lineaire_BoW
11Random ForestTF-IDF{'max_depth': 'None', 'min_samples_split': '5'...0.7648000.7635870.767100.7653400.8416495977.548638cb84d2c0f188472990425e7300f744efModele_Simple_Random_Forest_TF-IDF
\n", + "
\n", + "
\n", + "\n", + "
\n", + " \n", + "\n", + " \n", + "\n", + " \n", + "
\n", + "\n", + "\n", + "
\n", + " \n", + "\n", + "\n", + "\n", + " \n", + "
\n", + "\n", + "
\n", + " \n", + " \n", + " \n", + "
\n", + "\n", + "
\n", + "
\n" + ], + "text/plain": [ + " Modèle Vectorisation \\\n", + "2 Random Forest BoW \n", + "7 Regression Logistique TF-IDF \n", + "0 Naive Bayes BoW \n", + "5 SVM Lineaire TF-IDF \n", + "9 Naive Bayes TF-IDF \n", + "1 Naive Bayes TF-IDF \n", + "15 Regression Logistique TF-IDF \n", + "8 Naive Bayes BoW \n", + "13 SVM Lineaire TF-IDF \n", + "10 Random Forest BoW \n", + "6 Regression Logistique BoW \n", + "3 Random Forest TF-IDF \n", + "4 SVM Lineaire BoW \n", + "14 Regression Logistique BoW \n", + "12 SVM Lineaire BoW \n", + "11 Random Forest TF-IDF \n", + "\n", + " Meilleurs paramètres Accuracy Precision \\\n", + "2 {'max_depth': 'None', 'min_samples_split': '5'... 0.771925 0.779801 \n", + "7 {'C': '1.0', 'max_iter': '1000', 'solver': 'li... 0.785225 0.779591 \n", + "0 {'alpha': '2.0'} 0.772075 0.778523 \n", + "5 {'C': '0.1', 'dual': 'True', 'max_iter': '1000'} 0.785400 0.778086 \n", + "9 {'alpha': '2.0'} 0.770400 0.777618 \n", + "1 {'alpha': '2.0'} 0.769575 0.776643 \n", + "15 {'C': '1.0', 'max_iter': '1000', 'solver': 'sa... 0.783150 0.776595 \n", + "8 {'alpha': '2.0'} 0.771800 0.776557 \n", + "13 {'C': '0.1', 'dual': 'True', 'max_iter': '1000'} 0.784200 0.776513 \n", + "10 {'max_depth': 'None', 'min_samples_split': '5'... 0.768850 0.774589 \n", + "6 {'C': '0.1', 'max_iter': '1000', 'solver': 'li... 0.782925 0.772292 \n", + "3 {'max_depth': 'None', 'min_samples_split': '5'... 0.767500 0.770202 \n", + "4 {'C': '0.01', 'dual': 'True', 'max_iter': '1000'} 0.782225 0.768696 \n", + "14 {'C': '0.1', 'max_iter': '1000', 'solver': 'sa... 0.779250 0.767199 \n", + "12 {'C': '0.01', 'dual': 'True', 'max_iter': '1000'} 0.780050 0.765526 \n", + "11 {'max_depth': 'None', 'min_samples_split': '5'... 0.764800 0.763587 \n", + "\n", + " Recall F1 Score ROC AUC Temps d'entraînement (s) \\\n", + "2 0.75785 0.768669 0.851527 17489.656235 \n", + "7 0.79530 0.787367 0.866710 65.192424 \n", + "0 0.76050 0.769406 0.844132 2.815040 \n", + "5 0.79855 0.788185 0.866477 122.321674 \n", + "9 0.75740 0.767376 0.851067 4.320478 \n", + "1 0.75680 0.766593 0.852443 4.272107 \n", + "15 0.79500 0.785690 0.861098 36.014652 \n", + "8 0.76320 0.769820 0.843168 1.472637 \n", + "13 0.79810 0.787158 0.861249 51.149676 \n", + "10 0.75840 0.766409 0.843950 6377.436032 \n", + "6 0.80245 0.787082 0.858402 896.353782 \n", + "3 0.76250 0.766332 0.848404 16182.080744 \n", + "4 0.80740 0.787573 0.858312 458.623438 \n", + "14 0.80180 0.784118 0.851395 371.537341 \n", + "12 0.80740 0.785905 0.851585 187.807479 \n", + "11 0.76710 0.765340 0.841649 5977.548638 \n", + "\n", + " Run ID \\\n", + "2 7d52e808227f468da7f733265280fc02 \n", + "7 94a0cf29a68b402ab37098a44d1c1ee6 \n", + "0 a920f241d90d4816b858b16d68a6b38f \n", + "5 767fe1513d6445029ca90ca2156ab653 \n", + "9 daf72838a04d4f2f962d5b7cf6e41a82 \n", + "1 f0f4a385cb0b463c97ad736eaa98ce9f \n", + "15 741178bfcba54908aaa50c4f44d6db71 \n", + "8 9635b8567b874dd6bc91da23d1cb3b78 \n", + "13 bb39934ff22f4021a379983c32d828ed \n", + "10 623f3412438b4b6a8bb0d112c917dcea \n", + "6 75396e8531354bd5b168f8dd7e6f4135 \n", + "3 7dda37625478423abcfd9292dac05580 \n", + "4 eaa8dfac819b44e39fe7b7baaf490de7 \n", + "14 b88c00415c1340ec9dd4e6db806f2de3 \n", + "12 5c07f55dbafc4c899c5c2a9c3de4a742 \n", + "11 cb84d2c0f188472990425e7300f744ef \n", + "\n", + " Run Name \n", + "2 Modele_Simple_Random_Forest_BoW \n", + "7 Modele_Simple_Regression_Logistique_TF-IDF \n", + "0 Modele_Simple_Naive_Bayes_BoW \n", + "5 Modele_Simple_SVM_Lineaire_TF-IDF \n", + "9 Modele_Simple_Naive_Bayes_TF-IDF \n", + "1 Modele_Simple_Naive_Bayes_TF-IDF \n", + "15 Modele_Simple_Regression_Logistique_TF-IDF \n", + "8 Modele_Simple_Naive_Bayes_BoW \n", + "13 Modele_Simple_SVM_Lineaire_TF-IDF \n", + "10 Modele_Simple_Random_Forest_BoW \n", + "6 Modele_Simple_Regression_Logistique_BoW \n", + "3 Modele_Simple_Random_Forest_TF-IDF \n", + "4 Modele_Simple_SVM_Lineaire_BoW \n", + "14 Modele_Simple_Regression_Logistique_BoW \n", + "12 Modele_Simple_SVM_Lineaire_BoW \n", + "11 Modele_Simple_Random_Forest_TF-IDF " + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "results_df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "w0WL6NSxWxlQ" + }, + "outputs": [], + "source": [ + "def afficher_tableau_comparatif(df):\n", + " \"\"\"\n", + " Affiche un tableau comparatif formaté des résultats\n", + "\n", + " Parameters:\n", + " -----------\n", + " df : pandas.DataFrame\n", + " Dataframe contenant les résultats à afficher\n", + " \"\"\"\n", + " if df.empty:\n", + " print(\"Aucun résultat à afficher.\")\n", + " return\n", + "\n", + " # Sélectionner et formater les colonnes pour l'affichage\n", + " df_display = df[[\"Modèle\", \"Vectorisation\", \"Accuracy\", \"Precision\", \"Recall\",\n", + " \"F1 Score\", \"ROC AUC\", \"Temps d'entraînement (s)\"]]\n", + "\n", + " # Formater les valeurs numériques\n", + " for col in [\"Accuracy\", \"Precision\", \"Recall\", \"F1 Score\", \"ROC AUC\"]:\n", + " if col in df_display.columns:\n", + " df_display[col] = df_display[col].apply(lambda x: f\"{x:.4f}\" if x is not None else \"N/A\")\n", + "\n", + " if \"Temps d'entraînement (s)\" in df_display.columns:\n", + " df_display[\"Temps d'entraînement (s)\"] = df_display[\"Temps d'entraînement (s)\"].apply(\n", + " lambda x: f\"{x:.2f}\" if x is not None else \"N/A\")\n", + "\n", + " # Afficher le tableau\n", + " print(\"\\n=== Comparaison des modèles ===\\n\")\n", + " print(df_display.to_string(index=False))\n", + "\n", + " # Identifier le meilleur modèle selon la précision\n", + " if \"Precision\" in df.columns:\n", + " best_idx = df[\"Precision\"].idxmax() if not df.empty else None\n", + " if best_idx is not None:\n", + " best_model = df.loc[best_idx]\n", + " print(\"\\n=== Meilleur modèle ===\")\n", + " print(f\"Modèle: {best_model['Modèle']}\")\n", + " print(f\"Vectorisation: {best_model['Vectorisation']}\")\n", + " print(f\"Precision: {best_model['Precision']:.4f}\")\n", + " print(f\"Meilleurs paramètres: {best_model['Meilleurs paramètres']}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "executionInfo": { + "elapsed": 8, + "status": "ok", + "timestamp": 1751740004112, + "user": { + "displayName": "Grégoire", + "userId": "01249298206117462766" + }, + "user_tz": -120 + }, + "id": "Ou07_WmDWxlQ", + "outputId": "4f3f00f7-3000-47f0-f2e1-ad34d27716d1" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "=== Comparaison des modèles ===\n", + "\n", + " Modèle Vectorisation Accuracy Precision Recall F1 Score ROC AUC Temps d'entraînement (s)\n", + " Random Forest BoW 0.7719 0.7798 0.7579 0.7687 0.8515 17489.66\n", + "Regression Logistique TF-IDF 0.7852 0.7796 0.7953 0.7874 0.8667 65.19\n", + " Naive Bayes BoW 0.7721 0.7785 0.7605 0.7694 0.8441 2.82\n", + " SVM Lineaire TF-IDF 0.7854 0.7781 0.7985 0.7882 0.8665 122.32\n", + " Naive Bayes TF-IDF 0.7704 0.7776 0.7574 0.7674 0.8511 4.32\n", + " Naive Bayes TF-IDF 0.7696 0.7766 0.7568 0.7666 0.8524 4.27\n", + "Regression Logistique TF-IDF 0.7832 0.7766 0.7950 0.7857 0.8611 36.01\n", + " Naive Bayes BoW 0.7718 0.7766 0.7632 0.7698 0.8432 1.47\n", + " SVM Lineaire TF-IDF 0.7842 0.7765 0.7981 0.7872 0.8612 51.15\n", + " Random Forest BoW 0.7689 0.7746 0.7584 0.7664 0.8439 6377.44\n", + "Regression Logistique BoW 0.7829 0.7723 0.8024 0.7871 0.8584 896.35\n", + " Random Forest TF-IDF 0.7675 0.7702 0.7625 0.7663 0.8484 16182.08\n", + " SVM Lineaire BoW 0.7822 0.7687 0.8074 0.7876 0.8583 458.62\n", + "Regression Logistique BoW 0.7792 0.7672 0.8018 0.7841 0.8514 371.54\n", + " SVM Lineaire BoW 0.7801 0.7655 0.8074 0.7859 0.8516 187.81\n", + " Random Forest TF-IDF 0.7648 0.7636 0.7671 0.7653 0.8416 5977.55\n", + "\n", + "=== Meilleur modèle ===\n", + "Modèle: Random Forest\n", + "Vectorisation: BoW\n", + "Precision: 0.7798\n", + "Meilleurs paramètres: {'max_depth': 'None', 'min_samples_split': '5', 'n_estimators': '200'}\n" + ] + } + ], + "source": [ + "# Afficher le tableau comparatif\n", + "afficher_tableau_comparatif(results_df)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "etUvFhueWxlQ" + }, + "outputs": [], + "source": [ + "def display_metrics_subplot(df, figsize=(18, 14)):\n", + " \"\"\"\n", + " Génère des sous-graphiques pour visualiser toutes les métriques avec une échelle fixe (0,1)\n", + "\n", + " Parameters:\n", + " -----------\n", + " df : pandas.DataFrame\n", + " DataFrame contenant les résultats des modèles avec leurs métriques\n", + " figsize : tuple, optional\n", + " Taille de la figure (largeur, hauteur)\n", + " Returns:\n", + " --------\n", + " matplotlib.figure.Figure\n", + " La figure générée\n", + " \"\"\"\n", + " # Définir les métriques à visualiser\n", + " metriques = [\"Accuracy\", \"Precision\", \"Recall\", \"F1 Score\", \"ROC AUC\"]\n", + "\n", + " # Créer une figure avec des sous-graphiques\n", + " fig, axes = plt.subplots(3, 2, figsize=figsize)\n", + " axes = axes.flatten()\n", + "\n", + " # Définir une palette de couleurs cohérente\n", + " palette = sns.color_palette(\"Set2\", len(df[\"Vectorisation\"].unique()))\n", + "\n", + " # Créer un barplot pour chaque métrique\n", + " for i, metrique in enumerate(metriques):\n", + " ax = axes[i]\n", + "\n", + " # Créer le barplot\n", + " sns.barplot(\n", + " x=\"Modèle\",\n", + " y=metrique,\n", + " hue=\"Vectorisation\",\n", + " data=df,\n", + " ax=ax,\n", + " palette=palette\n", + " )\n", + "\n", + " # Configurer l'axe y pour qu'il aille de 0 à 1\n", + " ax.set_ylim(0, 1)\n", + "\n", + " # Ajouter une grille horizontale pour faciliter la lecture\n", + " ax.grid(axis='y', linestyle='--', alpha=0.7)\n", + "\n", + " # Définir le titre du sous-graphique\n", + " ax.set_title(f\"{metrique}\", fontsize=14, fontweight='bold')\n", + "\n", + " # Rotation des étiquettes de l'axe x pour éviter le chevauchement\n", + " ax.set_xticklabels(ax.get_xticklabels(), rotation=45, ha='right')\n", + "\n", + " # Ajouter des valeurs au-dessus des barres\n", + " for p in ax.patches:\n", + " height = p.get_height()\n", + " ax.text(\n", + " p.get_x() + p.get_width()/2.,\n", + " height + 0.01,\n", + " f'{height:.3f}',\n", + " ha=\"center\", fontsize=9\n", + " )\n", + "\n", + " # Ne garder la légende que sur le premier graphique\n", + " # if i != 0:\n", + " # ax.get_legend().remove()\n", + "\n", + " # Ajuster la disposition pour éviter le chevauchement\n", + " plt.tight_layout()\n", + "\n", + " # Ajouter un titre global\n", + " fig.suptitle(\"Comparaison des performances des modèles par métrique\",\n", + " fontsize=18, fontweight='bold', y=1.02)\n", + "\n", + " # Afficher la figure (si dans un notebook)\n", + " plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "lv_FsbT_OlC1" + }, + "outputs": [], + "source": [ + "def display_metrics_subplot(df, figsize=(18, 14)):\n", + " \"\"\"\n", + " Génère des sous-graphiques pour visualiser toutes les métriques.\n", + " Le graphique de Précision occupe 2 colonnes.\n", + "\n", + " Parameters:\n", + " -----------\n", + " df : pandas.DataFrame\n", + " DataFrame contenant les résultats des modèles avec leurs métriques\n", + " figsize : tuple, optional\n", + " Taille de la figure (largeur, hauteur)\n", + " Returns:\n", + " --------\n", + " matplotlib.figure.Figure\n", + " La figure générée\n", + " \"\"\"\n", + " if df.empty:\n", + " print(\"Aucun résultat à afficher.\")\n", + " return None # Retourne None si le dataframe est vide\n", + "\n", + " # Définir les métriques à visualiser (sans la Précision initiale)\n", + " other_metriques = [\"Accuracy\", \"Recall\", \"F1 Score\", \"ROC AUC\"]\n", + "\n", + " # Créer une figure et une grille de sous-graphiques (3 lignes, 2 colonnes)\n", + " fig = plt.figure(figsize=figsize)\n", + " gs = fig.add_gridspec(3, 2)\n", + "\n", + " # Définir les axes pour la Précision (span 2 colonnes)\n", + " ax_precision = fig.add_subplot(gs[0, :]) # La Précision occupe la première ligne, les deux colonnes\n", + "\n", + " # Définir les axes pour les autres métriques (remplir les 2 lignes restantes)\n", + " axes_other = [fig.add_subplot(gs[1, 0]), fig.add_subplot(gs[1, 1]),\n", + " fig.add_subplot(gs[2, 0]), fig.add_subplot(gs[2, 1])]\n", + "\n", + " # Définir une palette de couleurs cohérente\n", + " palette = sns.color_palette(\"Set2\", len(df[\"Vectorisation\"].unique()))\n", + "\n", + " # Fonction utilitaire pour ajouter les valeurs au-dessus des barres\n", + " def add_value_labels(ax):\n", + " for p in ax.patches:\n", + " height = p.get_height()\n", + " if height > 0: # N'affiche le label que si la hauteur est > 0\n", + " ax.text(\n", + " p.get_x() + p.get_width()/2.,\n", + " height + 0.01,\n", + " f'{height:.3f}',\n", + " ha=\"center\", fontsize=9\n", + " )\n", + "\n", + "\n", + " # Tracer le graphique de Précision\n", + " sns.barplot(\n", + " x=\"Modèle\",\n", + " y=\"Precision\",\n", + " hue=\"Vectorisation\",\n", + " data=df,\n", + " ax=ax_precision,\n", + " palette=palette\n", + " )\n", + " ax_precision.set_ylim(0, 1)\n", + " ax_precision.grid(axis='y', linestyle='--', alpha=0.7)\n", + " ax_precision.set_title(\"Precision (Métriques clé)\", fontsize=14, fontweight='bold')\n", + " ax_precision.set_xticklabels(ax_precision.get_xticklabels(), rotation=45, ha='right')\n", + " ax_precision.set_xlabel(\"\") # Masquer l'étiquette x pour la Précision si désiré\n", + " add_value_labels(ax_precision) # Ajouter les valeurs\n", + "\n", + "\n", + " # Tracer les graphiques pour les autres métriques\n", + " for i, metrique in enumerate(other_metriques):\n", + " ax = axes_other[i]\n", + "\n", + " sns.barplot(\n", + " x=\"Modèle\",\n", + " y=metrique,\n", + " hue=\"Vectorisation\",\n", + " data=df,\n", + " ax=ax,\n", + " palette=palette\n", + " )\n", + "\n", + " ax.set_ylim(0, 1)\n", + " ax.grid(axis='y', linestyle='--', alpha=0.7)\n", + " ax.set_title(f\"{metrique}\", fontsize=14, fontweight='bold')\n", + " ax.set_xticklabels(ax.get_xticklabels(), rotation=45, ha='right')\n", + " add_value_labels(ax) # Ajouter les valeurs\n", + "\n", + " # Ne garder la légende que sur l'un des graphiques (par exemple, le premier des autres)\n", + " if i != 0:\n", + " ax.get_legend().remove()\n", + " else:\n", + " # Placer la légende de la précision à un endroit approprié si on ne veut pas de légendes répétées\n", + " # ax_precision.legend(loc='upper right') # Exemple de placement\n", + "\n", + " # Ou retirer la légende de la précision si on la garde sur le premier des autres\n", + " ax_precision.get_legend().remove() # Retirer la légende de la précision\n", + "\n", + " # Ajuster la disposition pour éviter le chevauchement\n", + " plt.tight_layout()\n", + "\n", + " # Ajouter un titre global\n", + " fig.suptitle(\"Comparaison des performances des modèles\",\n", + " fontsize=18, fontweight='bold', y=1.02) # Ajuster y si nécessaire\n", + "\n", + " # Afficher la figure (si dans un notebook)\n", + " plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000 + }, + "executionInfo": { + "elapsed": 2184, + "status": "ok", + "timestamp": 1751740006350, + "user": { + "displayName": "Grégoire", + "userId": "01249298206117462766" + }, + "user_tz": -120 + }, + "id": "Noe09nRHWxlQ", + "outputId": "93abb9a5-d96c-424d-f81f-693fde709861" + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABv0AAAWZCAYAAABe3dlUAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3Xd8FNX+//H3ppJCGqEHEpDQexcQQu9IkSKCIggIihRRUbwXvBevIoJUsaAISBUFaQICoaMSeujSpEhPgSSk7u8PftlvNrtpJCFZeT0fjzwemTPnzHzO7uxMMp89ZwxGo9EoAAAAAAAAAAAAADbLLq8DAAAAAAAAAAAAAJA9JP0AAAAAAAAAAAAAG0fSDwAAAAAAAAAAALBxJP0AAAAAAAAAAAAAG0fSDwAAAAAAAAAAALBxJP0AAAAAAAAAAAAAG0fSDwAAAAAAAAAAALBxJP0AAAAAAAAAAAAAG0fSDwAAAAAAAAAAALBxDnkdAAAAAAAAAB6/mzdvau7cuTIajZKkIUOGqESJEnkcFQAAAB4VST8AAAAAAIAnUJEiReTt7a2RI0dKkrZt26Zt27bJwYHbRQAAALaI6T0BAADwRAoKCpLBYDD9DBgwIK9Dyte2b99u9noZDAZdvHgxr8N6Ii1dulRt2rRR0aJF5ejoyHuCPDVgwACzYzAoKCivQ4Kydo174403NH36dEnSrl27NH78+Ezt4+LFixbXhe3bt2c/eAAAADwyvroFAACQyunTp7V06VLt3r1bZ86c0d27dxUXFydPT08FBgaqYcOG6tKli5o1ayaDwZDX4QJ4gowZM0afffZZXocB4B9m5MiRMhqNGj16tKZMmaJnnnlGnTp1yuuwAAAAkEUk/QAAAP6/69eva/jw4Vq9erXp2TYp3b59W7dv39a+ffv02WefqUePHlq5cmUeRArgSfTXX39pxowZeR0GgH+oUaNGSZJGjx6tF198UQcPHlRAQECexgQAAICsIekHAAAgKSQkRB07dtTNmzcz3eb27du5GBFy27Jly/TgwQPTsru7ex5GA2Rs//79SkpKMiubO3euWrZsKUdHR0mSn59fXoQG4B9i1KhRpuQfAAAAbA9JPwAA8MS7dOmS1YRfiRIl9MYbb6hp06by9fVVZGSkjh07pg0bNmj16tV5EyxyTLFixfI6BCBL7t+/b1E2dOhQphkGAAAAAEiS7PI6AAAAgLw2duxYi4Rf06ZNdeLECb3zzjt6+umnFRgYqDp16mjAgAFasWKFzp07p27dulndXnx8vL7//nv16NFD/v7+cnV1lYuLi0qVKqUuXbrom2++UWxsrNW227dvl8FgMPu5ePGiQkND9fzzz6tYsWJydXVVtWrVNG3aNCUkJJjarl+/Xq1atZKPj4/c3NxUp04dff7551anKpVksZ/vvvtODx480EcffaQaNWrI3d1dXl5eatGiRbpJzlWrVum9995Tu3btVKlSJRUtWlROTk5yc3NT6dKl1aFDB33++edWExaSdPHiRYtYtm/fruvXr+uNN95QuXLlVKBAAbPExvHjx/Xpp5/qhRdeUO3atVW6dGm5u7vLyclJhQsXVsOGDTV27FidPHkyzbiDgoLM9jlgwACLOmFhYfrf//6npk2bqmjRonJ2dparq6v8/f1Vv359DRkyRPPmzdOVK1es7sNoNGrt2rXq27evypUrp4IFC8rZ2VnFixdXmzZtNH36dEVGRmbpdYmIiND777+vypUry8XFRV5eXmrZsqU2btyYZl8za9++feratasKFy4sFxcXVaxYUe+//77u3buXpe08ePBA8+bNU5cuXVSqVCm5uLjI3d1d5cuX16BBg/THH3+k237r1q3q37+/KlasqIIFC8rR0VG+vr6qVKmSOnXqpA8++ECbNm3Kcv/Sek3Dw8P17rvvqmLFinJxcZGvr686d+6sHTt2ZLjNvXv3asiQIapSpYo8PT3l5OSk4sWLq3379vrmm28UHx9vtV1an/WzZ89q4MCBKl26tJycnBQQEKCJEyemeYza2dmZ2gcFBVmsP3v2rN566y3Vq1dPhQoVkqOjo3x8fFSrVi2NHDlSx48fT7Nv1j4jSUlJ+vLLL9WoUSN5eXmZzh2S9N1331n0SZL27NmjLl26yNfXV+7u7qpXr57mz59vtq9FixapUaNG8vDwkIeHh5o0aaLly5enGVtunXey8/k6evSoRo0apXr16snX19d0PqpWrZqGDBmiDRs2pNl248aN6t+/vwIDA1WwYEEVKFBApUqVUvfu3fXDDz+keR6XHj6HdtSoUapdu7a8vb3l6OgoT09PlStXTkFBQRo7dqxWrFihqKioDPtgTUJCgmbNmqU6derIzc1N3t7eat68+SNNb33x4kW9++67atCggek1KlSokBo1aqT//ve/unPnTppto6OjNXPmTLVu3VolS5ZUgQIFVKBAAfn5+al27doaMGCA5syZozNnzmQ5ruTPWPJP8pSWa9euVcuWLeXl5SVPT081bdpUP//8s6ldYmKiZs2apVq1aplem9atW2vLli0Z7nPHjh0aNGiQKlWqZDp3FClSRM2aNdOkSZN069atdNvHxMTov//9r6pUqSIXFxcVLlxYHTt21LZt27Lc/9DQUI0cOVK1atWSj4+P6dgNCgrS1KlTs3wNsCYxMVErVqxQr169VKZMGbm5ucnFxUVlypRR37599euvv6bbPiQkREOHDlW1atXk4eEhBwcH+fj4qHz58mrTpo3Gjx+v1atXm/1dBAAA8EQyAgAAPMHOnj1rlGT2U6hQIeOtW7ceaXuhoaHGSpUqWWwz9U+ZMmWM+/fvt2gfHBxsUfezzz4zOjk5Wd1O+/btjYmJicaxY8emua9BgwZZjTV1vY8++shYpUqVNLfzxhtvWN2Op6dnhv2VZPT39zceO3bMov2FCxcs6s6YMcNYqFAhi/JkI0eOzNQ+HRwcjNOmTbMad7NmzczqvvTSS2brT58+bSxevHim9vPRRx9ZbP/y5cvGRo0aZdjW19fXuHHjxky9LtOnTzeWKFHC6nYMBoPxm2++sdrXzJg9e7bRYDBY3XbZsmWNCxYssCi/cOGCxXb27dtn9Pf3z7Dfr776qjEuLs6i/Ztvvpmp19zZ2TnLfbT2mn7xxRfGkiVLpvmafvrpp1a3FRERYezZs2eGcVauXNl4+vRpi/bWPutff/210cXFxeJzM2HChEy9Js2aNTNtPzEx0fj+++8b7ezs0m1jMBiMo0ePNsbHx1vEmPoz0rdvX2OHDh0stjF//nyj0Wg0zp8/32Ld7Nmz04xhyJAhxoSEBGPv3r3TjO+///2v1dc/N847j/r5un//vnHgwIEZxlKjRg2LtteuXTMGBQVl2LZJkybGv//+26L9jz/+mOb1IfXPvn37rMafnvv37xubN2+e5jYHDx5sfPHFF9M8DpMlJSUZJ02aZHRwcEg3Ri8vL+PatWst2t+8eTNT11ZJxqFDh2a5n6k/Y/7+/sa33347zX3873//M0ZFRRmbNm2a5vGyYMECq/u6e/eusUuXLhn2w83NLc1t3Lhxw1i1atU09/3f//43w2uc0Wg0xsbGGl9//fUMYylWrJhx165dFu2tfY6Cg4Mt6p06dcpYvXr1DPfTrVs3Y2RkpEX7mTNnpnl9Sv1j7XMCAADwJCHpBwAAnmjTp0+3uGE0bty4R9rW+fPnjYULF87UTSlJRk9PT+OJEyfMtmEtEZDRja4WLVpkuK9t27ZZxJu6jqOjY4bbmTt3rsV2MnvzXXqYPEqd6LF20zCtG8PJMpv0S/759ddfLeLO6IZop06dMr391Em/O3fuGMuXL5/p9o6OjhY3Sq29LhkdC+7u7sbw8HCrx2d6tm3bluG2XV1dLcpSJ/0OHjxodHNzy3S/Bw4caNZ+//79mW6bU0m/zBz3v/zyi9l24uLiLI6f9H5KlixpvHbtmtk2rH3WrR33j5r0y2zyNPln8ODBFq9X6j6m9blML+mX3fOXvb298ezZsxax5cZ551E+X/Hx8cY2bdpkKo7USb/w8PBMJ7IkGatXr268f/++qX1sbKzRx8cn0+0fJen30ksvZbjd1OcGa0m/d999N9NxOjg4WFyzMpOYSv7JiaRfRseCvb19mgm/5J+CBQsaIyIizPbz4MEDY+PGjTPdF0nG77//3mwbSUlJ6SZi03pfrCX9+vbtm+k43NzcjIcOHTJrn5mk36VLl4zFihXL9H7atGljTEhIMLX/+++/M3WeTv4h6QcAAJ50TO8JAACeaIcOHbIoa9my5SNta8SIERbTcQ0ZMkS7du3Sb7/9plGjRpmti4iI0LBhwzLcrtFo1DvvvKPQ0FCtW7dOvr6+Zuu3bdsmBwcHTZ06VSdOnND8+fPl5ORkVmfx4sUZ7ic+Pl5PP/20Nm7cqEOHDumTTz6x2M748eMVExNjVla6dGm9+uqrWrZsmbZv367jx4/rxIkTCg4O1ujRo2Vn939/cp4/f14//vhjhrEkJCQoMDBQy5Yt06lTp7R371598MEHpvWurq7q2LGj5syZo40bN+rAgQM6e/asQkJC9OWXX8rPz89se59++mmG+0wt9dSO//vf/3To0CGdPXtW+/fv1+LFi/Xaa6/pqaeesmj773//22KKuW7dumnr1q06cOCA/vvf/8rB4f8erx0fH6/BgwdnOC2Z0WhUmzZttGvXLu3fv189e/Y0W3///n2tWbMmq13V2LFjLaYPHD16tP744w8FBwerS5cuio6OzjC2QYMGmU0hWKFCBS1evFihoaEKCQnRu+++azZN67fffms2Fd3OnTvNtlm7dm1t2rRJp0+f1vHjx7V582ZNmTJFHTp0UIECBbLcT2vi4+PVuXNnbdu2TSEhIRo3bpzFM/LefPNNs+U5c+aYHR+Ojo6aMGGC/vjjD504cUKLFy9W6dKlTeuvXr2qd955J8NYEhISVK9ePa1du1anT5/Wjh07NGLECI0aNUoXLlzQlClTLNpcuHDB9LNs2TJJD6fBmzp1qlk9Pz8/LVmyRMeOHdPKlStVtmxZs/Vff/21tm/fnmF8yX09ePCgjh49qoULF6p8+fJptrG3t9enn35qel1Sv2/btm2Tu7u7vv32W504ccLis5qYmGjqV0q5cd55lM/XF198oc2bN5uVFStWTDNnztThw4d1+vRpbdiwQUOGDJGLi4tZvQkTJphNQVywYEFNmzZNBw8eVGhoqL788kt5e3ub1h89elSTJ082LYeGhuru3bumZR8fHy1evFjHjx/X6dOntWvXLn355Zfq16+ffHx8Mux/aseOHdOCBQvMygoXLqyFCxfq6NGjWrJkiYoXL57hueHQoUP6+OOPzcr69u2r4OBgnTp1Sps2bdIzzzxjWpeQkKBXXnnFbGrc1OfjUaNGaf/+/Tp79qwOHjyoH374QW+++aaqVauWI8+4NBqNcnd31zfffKMTJ05o1qxZZusTExO1c+dOFStWTCtXrlRoaKjFeeLevXsWx8usWbO0Z88es7JnnnlGGzdu1JEjRzRr1iy5ubmZrX/99dcVHh5uWl63bp2Cg4PN6pQtW1Y//fSTjh49qi+//FIFCxbM8H1ZvXq1lixZYlbWr18/7dixQydOnNCyZctUpkwZ07qoqCgNHTo03W1a88Ybb+j69eum5eLFi+urr77SkSNHdPjwYU2ZMkXOzs6m9Zs3bzY77vbu3Wt2LAQEBGj16tU6efKkTp48qeDgYM2cOVM9evSQu7t7luMDAAD4x8nLjCMAAEBeszZV3cmTJ7O8nUuXLllsp1+/fhb1hg4dalEv5Wg/a6N/unbtaraNMWPGWNQZNWqUWZ2uXbuara9bt65FLKm3UbRoUWN0dLRZnalTp1rUW7lyZZZem9Qj5l599VWz9dZGCri7u2fr2/orV660GKGQmJhoViejkX4pp1n08PAwxsbGprm/lNORPXjwwGKExTPPPGPR5qOPPrLo94YNG0zrrb0u/v7+ZnHExcUZvby8zOqMHTs2S6/ViRMnLPaTegReUlKSsUaNGhb1Uo7027Vrl9k6R0dH45UrVyz2169fP7N6PXr0MK2bPHmy2bqPP/44zbitTQGXEWuvac2aNY1JSUlm9UaMGGFRLyQkxLT+qaeeMls3ZcoUi31t2bLFrI69vb0xLCzMtN7aZ7106dLGqKioNOO3NpLOmtRTTdrZ2VlMMXrp0iWL0TO9evUyq2NtNOPs2bOzFF/qc1P37t0t6kyfPt2sTs2aNc3WP/fcc2nuMy2Pct55lM9XYGCg2Xpvb2+r094ajUaz99/aeeKHH36waDNv3jyzOoULFzYdr7///rvZunbt2qX5esTFxRljYmIyetnMWJvecufOnWZ1Dhw4YFEn9Ui/QYMGma3v2LGjxb7u379vLFCggFm9lNN8ph4Ref369TTjfpRzg7XRtKmPy9q1a1vUWb16tWl9QkKC0dfXN93jpWzZsmbry5QpYzEKdenSpRb7+fzzz03re/XqZXFuOX/+vNk2fvzxR4ttpL7GtWzZ0mx9t27dLF6XP/74w2I7Bw8eNK3PaKTf5cuXLdb/8ccfFvt5//33zerUqVPHtG758uXpfpZTio6OtjpVMQAAwJOEkX4AAAA5IPUIJenhKL/UrH1L3lrblPr372+2nPKb98lefPFFs+XUI2/CwsLS3YckPf/88xYjUQYNGmRR77fffjNbTh6J06dPH1WuXFmenp5ycHCQwWCQwWDQunXrzOpfuXIlw1gGDx6sYsWKpVvn8OHDGjNmjBo2bKgiRYqoQIECpn0+99xzZnWjoqIy9RqkVKdOHdPvkZGRqlatmoYPH67p06frl19+0dWrV03rCxYsaPp9//79FiMsBg8ebLH9RzkWBg8ebDb60tHR0WKkYVb7+fvvv1uUDRw40GzZYDDo5ZdfTnc7qUfixMfHy8/Pz/SeJP98//33ZvVS9jnlay49HDH57LPP6l//+pcWLVqkkJAQxcXFSTJ/zbNjwIABFiOD0jvur169qnPnzpmte+uttyz62apVK7M6iYmJ2rt3b7qxvPnmm3J1dX2UbphJ/V4EBQVZnBNKly6t9u3bm5VldPwVLVrU6rGcntw6f+XWeScrn6+rV6/q7NmzZuuHDBmigIAAq9v38vIy/R4SEmJxnujZs6fFcfTKK6+Y1bl165ZpdGDlypXNztkbN27UM888o7Fjx+qrr77Sjh07FBERYepLVkfHpj43lCtXzmxEnvRwNG7NmjXT3U7q43H9+vUW/XR3d9eDBw/M6qV3bqhbt65eeeUVTZkyRWvWrNH58+dN63Lq3JDRsevt7a1OnTqZlu3t7S1G0KY8Xq5cuWIWpyS9/PLLcnR0NCvr1auX2QhPyfy1SP2+NG/e3CK2rl27pju6MzExUbt37zYrO3v2rDp16mT2M2HCBIu2qdulJ/V7L0n169e3eP8nTZpkVufQoUO6f/++pIfHWMpz9FdffaW2bdtq3Lhx+vbbb7V3717TZ8nFxcVsFD0AAMCTiL+GAADAE61w4cIWZVeuXFHFihWztJ1r165ZlFmb9jH1DcG02qbXxlpSIPUNv9TJu4ymjbS2DUny9PSUt7e32Y3LlNN03bp1S+3bt9eBAwcy3H6y5Bt56alVq1a66//1r3/pww8/tJiSMqP9FipUKNP1P/74Y7Vu3do0nemZM2cspuwMDAzUoEGDNGrUKNP0ZJk9Fry9veXl5WU2bVtGx4K14/JR3uuUbty4YVFm7ViwVpZSyiRoVty+fVsJCQlycHBQy5Yt9eyzz+rnn3+WJMXFxWnNmjVmU+QVKFBA7dq10/jx41W3bt1H2mdKme1r8nH/qP2UpL///jvd9Rkd95mV+jiydvxJlueWGzduKDExUfb29lbrV6lSxWLK34xkdP5KPseklNExnVvnnax+vqx9XlMnp9KS3eOocuXKcnd314cffqgxY8aY1u3evdssKWNnZ6cGDRpo1KhR6tWrV5b2k/rckNY5oEyZMjp8+HCa23nUvqb8vPz73//Wxo0bdfv2bUkPr9PffPONWf2SJUuqf//+GjdunDw9PR9pn8k8PT0tkmapj93SpUtbfFayerxY+2za2dnJ39/f7Lqbsm1m3pfkbaSc/jWlO3fuKDY21qwsNDRUoaGhVuunZO2akZZHfe+TkpJ048YNubu7q1y5choxYoRmzpxpWrd582azaXUdHR3VrFkzvfPOOxZfuAAAAHjSMNIPAAA80azdZN+6dWseRJK2lKNDJJk9qyqtOo/LyJEjs3TjXVKmEnUlSpRIc93mzZs1adKkLCX8MrvflBo3bqyjR49q+PDh8vf3t1rn7NmzGjdunMWzv3KLtaRlWgmazMrq65LTjEaj2Qifn376SfPnz1fz5s3NnvOU7MGDB1q9erUaN26sffv2Pc5Qsy318zBTS++4zw8eJb6Mzl+Pcu7KrfNObny+ckPK42j06NEKDg7Wc889Z5E8lR4mSPbt26fevXtr2rRpWdpPXp8bUvYzMDBQx44d09tvv53mMySvXr2qjz/+WC1atMjylx9Ss3Zc5sSxmxPy+n1JPSIzt6R8/2fMmKFVq1apQ4cOVp/bFx8fry1btqhNmzaZen4nAADAPxkj/QAAwBOtY8eOGjVqlFnZvHnzNHbs2CyNCrN2M/zcuXMW5amn9pKk4sWLZ3o/uenChQsWZeHh4RZT6yVPuxkXF2dxc6169eqaMGGCKlSoIDc3N0nSiBEjLKbay0h6N9qXLl1qtlygQAF98MEHatmypXx8fGQwGLRjxw4NGDAgS/u0ply5cpozZ47mzJmju3fv6uzZszp79qy2b9+ub7/91nTzde3atTpy5Ihq1KiR5rHQqFEjs7K7d++ajfKT8uZYKFq0qEXZhQsXLPph7fhIKXV9T09PHTx40GqSOrXkY0V6eGN9wIABGjBggBISEnThwgWdO3dOoaGh+uqrr0zTKcbFxemTTz7RqlWrMtx+eqz1y1pZ8nFv7f398ssv1aZNmwz3ldE5JacSTCVKlDCbgjT1dKTJUp+PihQpkm4M+SEBlpvnnayydiwcOHAgU18CsNZ2/fr1qly5coZtU39mg4KCFBQUJOnhCLhz587p1KlTWrFihbZt22aq9+GHH2rUqFGZ+kwm7+f06dOm5bTOAZk5N6Q8Bl9++WX9+9//znD/Kc8L0sPP4OTJkzV58mTdu3dPZ86c0blz57Rv3z7NmTNH8fHxkqSDBw9q/fr1evbZZzPcx+OU1rUhtaSkJF26dMmsLOW1oWjRovrrr79My9Zef2vbSKlQoUJycnIyTZcsSVOmTNHYsWPT70QWpe6zwWDQwYMHM5UwLVmypNly165d1bVrV1Pfzp07p5MnT2rBggWmLwEYjUZNmjRJPXr0yLE+AAAA2BpG+gEAgCdauXLlLJ7/dvv2bfXs2VP37t1Ls93ly5c1Y8YM03LTpk0t6nz55ZeZKrPWNi8sXbrUYiTSt99+a1GvQYMGkh6+TilvGErSxIkT1b17d1WpUkUBAQHy9vbWoUOHcjTO1NOFtWnTRm+//bbq1KmjMmXKKCAgQPv378/2flJPxebj46MGDRqoX79+mjdvnqpXr262Pvk5W/Xq1bOY4u2rr76y2L61srw4FpLfz5RSv+9Go1Hz589PdzvJSYdkERER+v333xUQEJDmz/Xr1xUWFmZ6XlN4eLjZMejg4KDAwEC1a9dOY8eO1SeffGK2j+TXPDvmz59vMXIm9bSB0v+9Tn5+fhZTVq5evVqlS5dOs5+urq46cOBAjj1rLCPNmjUzW96+fbvF1LR//fWXfvnlF7Oy/HIuSk9enXesKVmypAIDA83Kvv76a7OETEopk/z16tWzmC7y559/TvfzYjAYdPLkSdP5JTEx0WKqRT8/PzVr1kxDhw61SI7evXtXN2/ezHT/Up8b/vzzT+3atcus7ODBg+lO7SlZnhs2b94sNze3NPtZrFgxBQcHm02/nfp8XLBgQdWpU0e9evXSZ599pg4dOpitz4lzQ06zdu6YP3++KVmZbMWKFRZftkn52Uz9vgQHB1sk/lavXp3m1J7SwwR+6uczfvvtt+lOg3vr1i3NnTs3zfXWpD4XGY1G/fLLL+ke51FRUfrrr79MzzqMjo42PZtSevjFkDJlyqhVq1YaMWKE5s2bZ7aP/PjeAwAAPE6M9AMAAE+8Tz/9VDt37jS7GRocHKzKlSvrjTfeUNOmTVWoUCFFREQoNDRU69ev1+rVq9WoUSONHDlS0sNn+3Ts2FHr1683bWPx4sVyd3dX//795ejoqGXLllkk/Zo1a5apkR2Pw40bN9SyZUtNmDBBxYoV0+bNm/X++++b1fH29lbHjh1Nvzs4OJhNozZ16lR5e3urWLFiOnHihCZNmpStZ1dZk/o5jNu2bdOCBQvUoEED3blzR4sWLbKaXM2q5FEi7du3V506dRQQECB3d3dFRkZqw4YNFs8+Sp5yzNnZWQMHDtScOXNM63bv3q3u3btrxIgR8vLy0oYNGzRx4kSz9k899VSmRovltMqVK6tWrVpmSZJvv/1WXl5eev755xUVFaVp06bpyJEj6W6ncePGqlGjhlm9gQMH6sCBA+rcubNKlCihmJgYnT9/Xr///rvWrFmjEydOaP78+aZpdnfv3q1+/fqpY8eOCgoKUsWKFVW4cGHZ2dnp3Llz+uijj8z2aW2at6w6cuSInn32WY0ZM0YFCxbUypUrNXv2bLM6FStWNHt+4GuvvaY333zTtPzLL7+odevWeu2111S+fHk5ODjo+vXrOnLkiDZt2qStW7fq6aeffmyjT4YNG2aWuE1KSlLLli31ySefqFq1ajpz5ozeeusti2TD8OHDH0t82ZFX5520jBgxQm+88YZp+e7du2rYsKHGjx+vZ555RgUKFNClS5e0bt06hYSEaM+ePZIenicGDRqkWbNmmdp+9dVXunPnjgYOHKiAgABJD7/kcOjQIa1fv167d+9W//791b59e0kPpz/08/NTq1at1KpVK1WvXl0lSpSQs7Ozbty4oS+++MIi3tSj59LTr18/TZkyxaysR48emjZtmmrUqKHjx4+bPU8wLcnHY3Jy/erVq2rcuLHeeust1a5dWx4eHrp7966OHz+u7du3a+3atQoPD9fLL79s2sYbb7yhEydOqGPHjmrQoIHKli0rDw8PxcTEaOfOndqyZYvZPnPi3JAbhg0bprfeesu0fOHCBbVs2VLjx49X8eLFtWvXLr3zzjtmbby8vNS3b1/Tcr9+/fTDDz+YlhMTE9W6dWtNmTJF5cqV02+//ZapEXvDhw83m8785MmTaty4sd58801Vq1ZNbm5uunnzpg4fPqxNmzZp8+bNKl68uIYNG5bp/pYqVUqdO3fW2rVrTWXvv/++zp8/r969e6tUqVKKj4/XX3/9pZCQEK1du1YhISGaMGGCKdF5/vx51a9fX+3atVOLFi1UpUoVFStWTA4ODrpy5YrFl0Hy63sPAADw2BgBAABg3L9/v7Fw4cJGSZn+adasmdk2zp07Z/T19c10e09PT+Px48fNthEcHGxR78KFC2Z15s+fb1EntQkTJpit9/f3t6iTehuurq4ZxjxnzhyzbXTq1CnDNsWLF0/3dbtw4YJFm+Dg4DTfq5UrV2Z5n9Zex2bNmpmtf+mll8zW16lTJ9PvZcGCBY0RERGmtnfu3DEGBgZmur2jo6NFnzP7umTUj8z49ddfjQaDId0YHRwcMnxNQ0JCjG5ubln6HM2fP9/Ufu3atVlqO2HChCz109prmpnjft26dWbbiY2NNTZt2jRLsaY+7jPzWU8tM5/9ZGPGjMlSfK+88orFNrJ6bOXUuemll15K97V7nOedjF6DuLg4Y8uWLTP1GteoUcOs7d27d40VK1bM0vuUcv/37t3L1jGYGf369ctwu6nPDdb2884772Qp1tTHTo8ePTLdzsHBwXjmzJks9TMnjkujMePj5cGDB8ZGjRpl6XX4/vvvzbaRlJSUqfNP6vfF2ue3T58+WYol9euSmc/RhQsXjEWLFs3SflKe248dO/bInxEAAIAnEdN7AgAASKpbt66OHDmirl27mqYazIivr6/ZctmyZRUcHKyKFStm2DYgIEBbtmzJN6P8pIfPe0o5mim14cOHW3zDf+bMmek+h+5f//pXjo9e6969u/r06ZPm+ho1apiNssttLi4uWrRokTw8PExlPj4+ptFdGfH19dWaNWsspsB7nFq1aqXPPvsszWO/aNGimj59eobbqVOnjrZs2aIyZcpkar/Ozs4WIzczq0WLFhYjYh7F3LlzLabcS2ny5Mmm0a3JnJyctHbtWvXu3TvT+ylVqtQjx/gopkyZovHjx2fq+W0jR47M8rR9eSkvzjtpcXR01OrVq/Xiiy9mua23t7e2bdumFi1aZKq+wWCQn59flvcjSf7+/vr666+z3G7u3LnpTvvatWvXTH0OPvroI02aNEkODpmbbOhR+2lvb6+ZM2daTLuaXzg7O2vdunXq3LlzhnVdXV21YMECvfDCC2blBoNBy5cvT/fvh9dff12NGzfOcB8LFizQiBEjMv13z6OcxwICArRjxw7VrFkzU/Xt7e0f+fm2NWrUsBidCgAA8KRhek8AAID/r3jx4lq1apVOnTqlpUuXavfu3Tp9+rTCwsIUFxcnLy8vlStXTg0bNlSXLl2sJmmqVq2qo0ePavny5frpp58UEhKiW7duyWg0qlChQqpdu7aeffZZ9evXTwUKFHj8nUyHl5eX9uzZoxkzZmjx4sU6e/asHBwcVLt2bY0YMULdu3e3aFOmTBkdOnRIkyZN0tq1a3Xt2jV5enqqdu3aGjlypDp06KABAwbkaJwGg0FLlixRixYt9PXXX+v48eMyGAwqU6aMevfurbFjx+q3337L9n6WL1+unTt3as+ePTp69Khu3LihW7duKSEhQZ6engoMDFSLFi00dOhQqzdCS5Uqpd27d2vt2rVatmyZfv/9d924cUPx8fHy8fFRtWrV1KFDBw0aNMgsYZhXRo4cqbp162ry5Mnau3ev7t+/Lz8/P3Xq1Enjx4/X8ePHM7Wdhg0b6uTJk1q+fLl+/vlnHThwQLdu3VJcXJw8PDxUpkwZ1axZUy1atFDHjh3l6elpatuqVStt2bJFu3fv1r59+/TXX3/p1q1bCgsLk7Ozs0qUKKFatWqpV69e6tGjR6ZvVKendOnSOnz4sCZPnqyVK1fq0qVLcnV1VcOGDfXWW2+lmYz18PDQsmXL9Oabb+q7777Tnj17dOnSJd27d08FChRQ8eLFVblyZT3zzDPq2LGjKlWqlO1Ys8LOzk6TJk3SSy+9pC+//FLbt2/X+fPnde/ePdPz1Jo2baohQ4aoatWqjzW27MqL80563N3dtWDBAo0ePVrz58/X7t27deHCBd27d0+enp4qXry4nn76aXXt2tWibfHixbV161Zt2bJFS5Ys0W+//aarV68qKipKbm5uKlmypKpWraqmTZuqc+fO8vf3N7V1c3PTH3/8oT179mjPnj06c+aMbt26pdu3b8vOzk6+vr6qUqWKOnbsqEGDBmVpas+Ufdu6davmzJmjBQsW6NSpU3J0dFSVKlU0aNAgDRw40GwazrQYDAaNHz9eL774oubNm6dt27bp9OnTCg8Pl729vXx9fVWhQgU1aNBAbdu2tXje3MyZM9W9e3ft2bNHBw8eNJ2PHzx4oIIFC6ps2bJq2rSpBg8e/Ng/a1nl7e2tNWvWKDg4WIsWLdLevXt17do1xcTEyMvLS5UqVVLr1q01dOhQFSlSxOo2ihUrpv3792vKlClavny5zp8/L1dXV9WqVUuvv/66unXrlqkvkjg5OWnmzJkaNmyYvvnmG+3atUt//vmnIiMj5eTkpCJFiqhSpUpq1KiR2rVrl+4Xg9JToUIFhYSEaM2aNVq5cqX++OMPXb9+XTExMSpYsKBKly6t6tWrKygoSJ07dzbrd8WKFbVr1y7t2bNHe/fu1fnz53Xz5k3dvXtXjo6OKlKkiGrUqKGuXbuqX79+pmcBAgAAPKkMRmOqp9YDAADgiZA6YTJ//vzHeqMceNwuXrxoMQoxODg4T0dZAgAAAACQU5jeEwAAAAAAAAAAALBxJP0AAAAAAAAAAAAAG0fSDwAAAAAAAAAAALBxJP0AAAAAAAAAAAAAG2cwGo3GvA4CAAAAAAAAAAAAwKNjpB8AAAAAAAAAAABg40j6AQAAAAAAAAAAADaOpB8AAAAAAAAAAABg40j6AQAAAAAAAAAAADaOpB8AAAAAAAAAAABg40j6AQAAAAAAAAAAADaOpB8AAAAAAAAAAABg40j6AQAAAAAAAAAAADaOpB8AAAAAAAAAAABg40j6AQAAAAAAAAAAADaOpB8AAAAAAAAAAABg40j6AQAAAAAAAAAAADaOpB8AAAAAAAAAAABg40j6AQAAAAAAAAAAADaOpB8AAAAAAAAAAABg40j6AQAAAAAAAAAAADaOpB8AAAAA4LEIDg6Wg4OD7O3t9d133+V1OMgBAwcOlMFgUNmyZXXp0qW8DgcAAAB4opH0AwAAAPBEGjBggAwGgwwGg4KCgh55OxMnTjRtJyAgIMfiyw3Hjx+Xvb29DAaDOnbs+Fj3fe3aNT3//PMyGo367rvvNGDAgFzb18WLF03vicFg0Pbt23NtX/8kWT2Wv/nmG82fP19PPfWUduzYIX9/f7P1P/zwg2l748aNy6WoAQAAACQj6QcAAAAgy7Zv326WVEn54+7ursqVK2vEiBE6f/58XoeKFN577z0lJSVJkt5++22zdUFBQRbv5cGDB61u5+mnn7aoe/HixTT3m5CQoN69e+v27dtatGiR+vfvb7VeyhhyMymI7Dt8+LBef/11lStXTtu3b1epUqUs6nTv3l1PPfWUJGnmzJm6du3a4w4TAAAAeKI45HUAAAAAAP5ZoqKidPLkSZ08eVLffvutfv75Z7Vq1Sqvw7LQp08fVa1aVZKsJiwyq02bNnJ3d5ckeXp65khsueHAgQNas2aNJKlGjRpq1qxZhm1mzpxpMQ3n/v379dtvv2Vp3ydPnlTLli317rvvqkOHDllq+yh8fHw0ZcoU03Jy4gk559ixYxo3bpwGDx6sEiVKWK1jb2+v1157TWPGjFFMTIw+/vhjzZw58zFHCgAAADw5SPoBAAAAyLbevXurbt26iouL0759+7Ru3TpJUnR0tPr376+LFy/K2dk5w+1ERkbKw8Mjt8OVJLVr107t2rXL9nYaNWqkRo0a5UBEuevLL780/d6nT59MtVm2bJmmTJmiwoULm8pmzJiR5X1Xq1ZN1apVy3K7rIqLi5PRaJSHh4fGjh2b6/t7kqU1WjO1Xr166c0335TRaNSiRYs0efJkubi45HJ0AAAAwJOJ6T0BAAAAZFu7du00duxYvffee1q7dq1eeOEF07rr169rz549kiynBf3zzz/16aefqlKlSnJ2dtaLL75oapeUlKRFixapTZs2KlKkiJycnFS4cGF17NhRGzZsSDOWU6dO6bXXXlPlypXl7u4uV1dXlS1bVn369FFISIipXnrP9Dt27Jj69eungIAAOTs7y8XFRaVLl1aLFi307rvv6urVq6a6GT0HLSwsTP/5z39Ut25deXp6ysnJSSVLllT37t3166+/WtT/7rvvzF6j2NhYffjhhypfvrycnZ3l5+ensWPHKjY2NsP3JVlMTIyWLVtmWu7Ro0e69e3sHv6rGBsba5YsvH79ulasWCHp4SiujKxdu1bPPvusihcvLicnJ3l5ealZs2b65ptvTNOMSv/3Gu7YscNUtmDBAqvTh6aeAjQ0NFRdu3ZVoUKF5OzsrJMnT2b4TL/bt29r6NChKlq0qFxcXFS3bl398MMPFsdnyilL05t6NPV7llpsbKxmz56tpk2bysfHR05OTipevLh69uypffv2WX3tvvvuOwUFBcnX11eOjo7y9vZWhQoV1Lt3b33++ecZvvYpXblyRe+8845q1aolDw8PFShQQKVLl1bXrl2tHoNpiYyM1EcffaQGDRqYjuVSpUrphRde0IEDB6y2KVmypJ5++mlJUnh4uH766acsxQ4AAAAg8xjpBwAAACDHPf3001q8eLFp+fr161brDRw4ULt27bIoj4mJUZcuXbRlyxaz8tu3b2vDhg3asGGDxowZo6lTp5qt/+abbzR8+HDFxcWZlV+4cEEXLlxQw4YNVbdu3XRjP3HihBo2bKjo6Giz8suXL+vy5csKDg5Ws2bNVLJkyXS3Iz2c1rJNmza6cuWKWfm1a9e0atUqrVq1SiNHjtT06dPT3EarVq20e/du0/LVq1c1depU3bx5UwsXLswwBkn67bffdO/ePUlS4cKFFRgYmG796tWrKywsTJcuXdIXX3yhcePGycHBQXPnzlV8fLwkqUuXLlq1apXV9klJSRowYIAWLVpkVh4REaGdO3dq586dWr58udauXZupEaBpOXr0qBo2bKioqKhMtwkLC1Pjxo115swZU9mBAwfUq1cvde7c+ZFjScutW7fUpk0bHT582Kz8+vXrWrlypX766SdNmzZNI0eONK2bOHGiPvjgA7P64eHhCg8P15kzZ7Rjxw4NHz48U/vfsGGD+vTpY3r/kyUfzwEBAWrdunWG2zl79qzatGlj8ezGK1euaMmSJVq+fLk+//xzDRkyxKLt008/rb1790qSfv31V7MvBQAAAADIOST9AAAAAOS41KOXihUrZrXerl27VKVKFXXu3FlGo9E0emz06NGmhJ+Tk5P69OmjwMBAHTt2TD/88IOMRqOmTZumOnXqqG/fvpIe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l7+/v2bOnJkm4DdhwoTbtkw4deqU8XOFChV06NAhnT17Vrt27dKlS5fk5uamhg0b6vfff9f58+clSb/88gtBPwDAAyHoBwBwGt99951SUlIkSSaTyVgqpVOnTsqVK5cSEhIk3WyMffLJJ5KUZoRl69at0wT8JMnd3V3FihWTdHNWoMViMY69+eabaQJ+0s2Zfxlt8ODBtwX8pJt7//Xq1UthYWH/eP25c+ckSXFxcdq7d6+R3qdPnzQBP+lmIDB10PLFF180gn4///yzLly4oCJFimjRokXGOSztCQAAAOBh0LlzZw0cONB4HBcXp3379hmPZ8+erdmzZ6d7rcVi0c6dO9WmTZs07cw8efLojTfeuO384OBg4+dr167p6aefvm0biVvZ23YAANwvs6MzAADA3Uq9tGe9evWMAFyePHnUtm1b49icOXOM4F3qJVyCgoL+8f6pz72b82916x56iYmJd3VduXLl0k3v1KnTvwb8Uj9PZGRkmjzcTf4rVaqkJk2aSJJSUlI0Y8YM7dixw1jas0CBAmrXrt2/3gcAAAAAspPu3btr4sSJadozc+bMUceOHY12061tqH9z5coVSWnbjsWLF0+zN3p6nn322X8N+El334YEAOBOmOkHAHAKO3bs0OHDh43H27Ztk8lkSvfc8PBw/fzzz+rQoYP8/f2N5TtPnz79j8/h7++f5vHp06dVq1atO55vNqcdOxMfH2/8bLVadfLkyX98Prv0lgs9evSo/vjjD+Pxk08+qQ8//FBFihSRyWRSQECA0eC08/Pzk8lkMhqt/1ZeuxdffFEbN26UJE2fPl3Xrl0zjvXu3Vtubm53dR8AAAAAyC7atGmjvn37SpIGDhyo//73v5Kk9evX6/vvv9dTTz1124orHTp0uG11mNSqV68uKW3b8e+//1ZKSsodA383btxIszd88+bNNXXqVJUsWVIuLi6qXbu2du3adT9FBADgNsz0AwA4hdSz/O7l/AYNGhhpa9as0bZt29KcZ7FYjP0T6tSpI1fX/42H+eCDDxQXF5fm/AsXLig5OVmSbmsgbt++3fj5m2++uS0ody9SB94k6YknnlDRokVlMpm0cePGdO/t5eWlatWqGY+/++47nThxIs058fHxRhDUrmPHjipRooSkm/tOTJkyxTjWr1+/+y4DAAAAAGQH77//vvLmzWs8HjdunFJS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wIED1aNHDyUlJWnGjBm5hne5//771bZtW7Vu3ZoACId36dIl1ahRQ8HBwfL09JSnp6eee+451alTR19//bVMJpMqV66sOnXqqFGjRjp16pQ+//xzbdq0Sc8++ywBEHe8n3/+WX/88Yf18fr16/Xf//5X0dHRSk9Pl9FoVJ8+fXT+/HktWrTIup2/v7/KlSungIAAGQwGmc1mwh8citls1oYNG5ScnKzly5drwYIFknIP9XLt4yeeeEL9+vUjAALIhbwH3BoyH3BzZD6g4Mh7sGd0+pVwFotFBoNBR44c0YIFCzR//nxFRETIyclJffr0UZs2bXTixAnNnDkzVxAcMWKEAgMDbVg5YB9SUlJ05swZSVcOxDmT6t5zzz1KS0tTTEyMdducK9ZcXV01fvx4BQcH26JkoNicPXtWycnJqlWrlqS/f3RMS0vThAkT9N1332n37t3y9vbW0KFDdeLEiRvOecLwLnA0RqNR1atXV9myZdWhQwdt2rTppkEwOztbkjRy5EiOLwCsyHvArSPzATdG5gMKh7wHe8acfneAHTt26KuvvlKNGjWUmZmp48ePq2vXrnrggQfk4uKiRYsWae/evQoMDNTDDz/M5KBwWHmNK3/69Gl99NFHCg0N1f33329d/scff2jq1Kl69dVXVb58+VzPyRnOAnAkW7dulY+Pjxo3bixJWrdunY4cOaLNmzerW7duMhgM2rdvn0aMGKFWrVrZuFqg+F09B8rVwxqNGzdOpUuXVnBwsFavXq3u3btr4MCBkvI+LgHAtch7QP6R+YDCI/MBN0beQ0nCGUwJFxMTo59++kn33XefunfvLrPZrH379umjjz6SwWDQI488on79+lnDYUZGBiEQDufo0aMKDg6Wq6vrdQfcwMBA3X333dq9e7emTZumYcOGKTExUYsWLVLp0qVVrly56/ZH+IMjsVgsSkhI0OLFi+Xv76+srCyFhoaqS5cu6tSpk+6++26tXr1aqampOn/+vDZv3kwAhMO5dhL3q4c16tWrl/bs2aNmzZopKytLq1atksFg0IABA66b8wEArkXeA/KHzAcUHpkPuDnyHkoazmJKkLVr16pKlSqqVauWdW6G9PR0OTk5qX79+tbtmjRpoueff16TJk1S8+bN1bRpUw0bNkwpKSny8fGxVfmATcTExGjatGkqW7as/vOf/8jFxcV6wLVY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fOXaCxcu6J133lGHDh1Urlw5ubq6ysvLSw0aNNDDDz+slStXymKxFPj9AADsk7OtCwAAoDjMmjVLn332WZHt79SpU2rfvr3OnDmTa3lsbKxiY2P1xx9/aNGiRWrcuLEqV65cZK8LAAAAAI7EbDYrISFBe/bs0Z49e/TTTz8pIiLiupxlMpn0wgsv6PPPP79uHzExMYqJidGGDRs0adIkzZgxQ927d79uu8uXL2v06NFatGjRdesuXbqkrVu3auvWrdq0aVOBLx79+eefZTKZci1bunTp/7F33/FRVPv/x9+72XRIo0MuBAhICb0poFIUUQEBpanXgiIgWFARRbmAioodlYvX61cBlSaCVAHpvQtIB6VITUIq6cnu7w9+mZslCSRhk90lr+fjkcdj58yZ2TObOTv7mc/MGcXExCgkJKRQ6yqof//733r55ZeVmppqV56RkaGDBw/q4MGD+vbbb3XixAmFhYUVSxsAACWLpB8AwK3169dPLVu2zFUeERGRq6xSpUpq0aKFgoKCNGPGjBt631GjRhkJP4vFoj59+qhBgway2Wz666+/tHnzZh09evSG3qM4ZGVlKS0tTX5+fs5uCgAAAADkKzvWS0hI0C+//KI//vhD0pU75j799FN98skndvWfe+45ffXVV8Z01apV1bdvX5UvX15//PGH5s6dq6ysLEVHR6t79+5avXq12rVrZ9RPSkpSly5dtHPnTqOscuXK6tmzp6pXr67ExETt3r1bq1atKtL2TJs2LVdZenq6ZsyYoeHDhxdpndfywQcfaNSoUca0h4eH7r//frVo0UImk0nHjx/X8uXLdfHiRYe/NwDAeUj6AQDcWteuXfMc6uRq7733nj777DNJV4aNudGk34oVK4zXb775psaOHZurzqFDh+Tr65urPDMzU9OnT9esWbO0d+9excbGKjAwULVr19a9996ba11Hjx7Vp59+qtWrVxvDhYaGhqpjx44aMWKE6tWrZ1f/iSeeMALKO++8U9OnT9cbb7yhFStWKCoqSvPmzVPPnj0lXRnqZdKkSVq6dKn+/PNPZWRkKDQ0VPfcc49GjRql6tWr39DnBAAAAABFkTPWe/nll1WxYkWlp6dLkg4ePGhXd/PmzXYJv+bNm2vNmjUKCAgwylavXq27775bVqtV6enpGjx4sPbt2yez+crTj9555x27hN8DDzygGTNm5Lpg8ty5c1q0aFGhtmXHjh06cOCAMV23bl3jItGpU6c6POl38OBBjR492piuWLGili1bpmbNmtnVy8jI0NSpU7koFABuIjzTDwBQKuSVfLsROYdlOXz4sNLS0nLVqV+/fq4hUmJiYtS2bVs99dRT+u233xQZGamMjAxFR0dr27Zt+vTTT+3q//TTT2rSpIm++uorHT16VCkpKUpJSdGxY8f09ddfq2nTppo1a1a+7Tx37pzatGmjH374QZGRkXbPatiyZYsiIiL03nvvae/evbp8+bLS0tL0559/6t///rcaN26sDRs2FPETAgAAAADHCAwMVJkyZYzp8uXL283/+uuv7aY/+OADu4SfJHXq1En9+vUzpg8cOGDEOxkZGZo8ebIxr3Llyvrxxx/zTIZVrVpVgwcPLlT7v/vuO+N1aGioPvjgA2N6165dxl2MjvLFF18oKyvLmJ4yZUquhJ8keXp6atCgQapYsaJD3x8A4Dzc6QcAcGvLli1TdHR0rvJ+/foV67P0mjdvrnXr1kmSZs2apaVLl+q2225T8+bN1aZNG3Xq1Elly5bNtdw///lP7dixw5iuX7++7rvvPnl7e+v333/Xtm3bjHnHjx/XP//5TyOhWK5cOT3++OMymUyaNm2aoqOjlZaWpscff1wtWrRQnTp1cr3fsWPHJEm9e/dWkyZNdOrUKQUGBiohIUE9e/Y0PrsaNWqoX79+8vX11dy5c3XgwAHFx8frwQcf1LFjxxQYGOi4Dw8AAAAACighIUFTp05VTEyMUda3b1+7OjkvVgwODlbnzp3zXFe/fv00c+ZMu+XuvPNO7dixQ4mJiXb1/P39HdL+tLQ0uws1+/btq3vvvVdBQUGKi4uTdOVuv48//tgh7yfJbgjS4OBgY6QXAMDNj6QfAMCtzZ49W7Nnz85V3rJly2JN+n3wwQe6/fbbjeFlEhIStHz5ci1fvlyS5OPjo2eeeUbvvfeecXXoH3/8oaVLlxrruO+++/TLL7/I09PTKPvrr7+M119++aWR8DObzVq7dq3xrMInnnhCTZo0MYammTx5sjF86dU+++wzvfDCC3Zln3/+uSIjIyVdCQJ3795tPDx+5MiRqlmzpqKiohQVFaVp06bp+eefL/JnBQAAAACF9eSTT+rJJ5+0K/Pz89P48ePVo0cPu/Lz588br2vUqJHvOq+el73c2bNn7cqvfoTCjViwYIFiY2ON6QEDBsjLy0u9e/fWt99+K0n68ccfNXHiRFksjjlVm3N76tatawxhCgC4+fGNDwBAEbRu3Vrbtm3TAw88YJe0y5aamqrPP/9czz77rFG2ceNGuzpjx47NtWytWrWM11u2bDFet2jRwkj4SVJERIRatGiRZ92cgoODNWzYsFzlmzZtMl7HxsaqXLlyMplMMplMKlOmjKKiooz5mzdvznPdAAAAAFCSevXqpSFDhji7GYUydepU43V4eLhatmwpSerfv79RfvHiRbsLRAEAKCqSfgAAt/bdd9/JZrPl+uvQoUOxv3fTpk31yy+/KC4uTmvWrNF7772X632nTZtmDEOTczgaSapZs+Y115+zfqVKlXLNz1mW88rRnGrXrp3n1aJXt+VaciYAAQAAAKAk9OvXT++++666detmlP3444964IEH7J5VLklVqlQxXp8+fTrfdZ46dSrP5apVq2ZXfvjw4SK3O6dz585pxYoVxnTOZwp26tTJ7ll6OZODkuziuNTU1FzrTklJsZv28vIyXufcnqNHj+b6vAAANy+SfgAA3CA/Pz916NBBr732mtasWaO33nrLbn72c/Wyh8/MduLEiWuuN2f9ixcv5pqfsyw4ODjPdeT3HIqc665SpYo+/PDDfP+eeeaZa7YTAAAAAByta9euev3117Vo0SINHjzYKF+9erV++OEHu7q333678TomJkarV6/Oc51z5szJc7lWrVrZPZN9zpw5Sk5OvuFt+P7775WVlWVMT5gwwRhhxWKxGI9ckKQlS5bo0qVLxnSFChWM13nFjjkfDXF1/ZzPNIyNjdWCBQtubEMAAG6DpB8AAEXw3HPPae3atXleMVmmTBm76aCgIElS+/bt7crffvttZWZm2pXlvPK0bdu2xutdu3bpwIEDxvT+/fu1a9euPOsWRM76UVFR6tKli1555RW7v5dffllNmzZV69atC7VuAAAAAHCk999/X4GBgcb0W2+9ZZdMu/pCxVGjRikxMdGubO3atXbPg2/QoIGR9PP09LR7NMP58+f1z3/+M9fddNKVu/e+/vrrArX76rv3riU9PV0//vijMd2mTRvj9fbt2/Xbb78Z06dOndL3339vTJcrV07h4eHG9PDhw+Xh4WFMDx06VHv37s31nhkZGfrmm2/sko8AAPdmsnF/NwDAjaxdu1YdO3Y0pr/77js98cQT111uxYoVxrAqf//9t90Vnn379tU//vEPSVKXLl3UpUuX664vLCxMp06dUtWqVXXnnXeqTp068vLy0pEjRzR79mylp6dLujKE559//imTySRJuv/+++2e1dCgQQPdd9998vHx0YEDB7R+/XpFR0dLko4fP66IiAilpaVJuhLIPf744zKZTJo2bZpRz8vLSwcOHDCCvCeeeELTpk2TJN15551au3ZtrvbHx8crPDzcWEeZMmXUp08fhYeHKy0tTUeOHNHatWt18eJFrVmzpkSGSwUAAABQel0v1nvjjTf07rvvGtM//vijHn74YWN6yJAh+s9//mNMV6tWTX379lX58uX1xx9/aO7cucZFl15eXlq9erXatWtn1L98+bLuvPNO7d692yirUqWKevXqpdDQUCUmJmr37t1atWqV2rVrl2ecldPWrVt12223GdNt2rRRWFhYrnqrVq0y4rKmTZvq999/lyT99ttvdrGp2WxWRESE/Pz8tG/fPrs7Ed944w298847dut97733NHr0aGPaYrGoW7duatasmUwmk44fP67ly5fr4sWLOnHiRJ5tAwC4IRsAAG5kzZo1NknG33fffVeg5caOHWu3XH5/Y8eOLdD6atSocd11+fj42FatWmW3XHR0tK1Vq1b5LhMYGGhXf86cOTYfH59863t7e9tmzpxpt8zjjz9uzL/zzjvz3YZNmzbZypcvf93tWLNmTYE+EwAAAAAoquvFepGRkTY/Pz9jfsOGDW1Wq9WYn5GRYRs+fPh145ty5crZli9fnmcboqOjbd26dbvuOq4VZ2UbPHiwUd9sNttOnTqVZ70xY8bYrXvv3r3GvH/961/Xbcs999xjS0tLy3PdkyZNsnl7e193HSdOnLju9gAA3APDewIAUATLly/Xl19+qd69eysiIkIVK1aUxWKRv7+/GjRooGHDhumPP/5Qp06d7JYrV66cNm3apG+++UZ33XWXKlSoIIvFouDgYLVo0UIvvviiXf0+ffpoz549GjJkiMLDw+Xj4yMfHx/Vrl1bgwYN0u+//67+/fsXaRvatm2rAwcOaMyYMWrRooUCAgLk4eGhoKAgtWjRQsOHD9dvv/2mO+64o6gfEwAAAAA4RIUKFfT0008b0wcOHND8+fONaYvFoi+++EK///67hg4dqgYNGqhs2bKyWCyq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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Créer la visualisation\n", + "display_metrics_subplot(results_df)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "u83ZVCLJWxlQ" + }, + "source": [ + "### Identifier le meilleur modèle selon `Precision`" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "eHr3Z6btZm2E" + }, + "source": [ + "Le contexte métier est le suivant : on souhaite éviter à tout prix qu'un tweet qui serait reçu négativement soit à tort prédit comme positif. La variable \"precision\" (basée sur la modalité 1 = tweet positif) est donc la plus pertinente." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "executionInfo": { + "elapsed": 5, + "status": "ok", + "timestamp": 1751740006356, + "user": { + "displayName": "Grégoire", + "userId": "01249298206117462766" + }, + "user_tz": -120 + }, + "id": "f07BiYp_ZgDh", + "outputId": "d30f2935-f9a5-4df3-8fe9-082caf759620" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Meilleur modèle (selon la précision): Random Forest avec BoW\n", + "Precision: 0.7798\n", + "F1 Score: 0.7687\n", + "ROC AUC: 0.8515\n", + "Meilleurs paramètres: {'max_depth': 'None', 'min_samples_split': '5', 'n_estimators': '200'}\n", + "\n", + "Run ID du meilleur modèle: 7d52e808227f468da7f733265280fc02\n" + ] + } + ], + "source": [ + "# Identifier le meilleur modèle selon la précision\n", + "best_model_row = results_df.loc[results_df['Precision'].idxmax()]\n", + "best_model_name = best_model_row['Modèle'].replace(\" \", \"_\")\n", + "best_vectorizer = best_model_row['Vectorisation']\n", + "\n", + "print(f\"\\nMeilleur modèle (selon la précision): {best_model_row['Modèle']} avec {best_vectorizer}\")\n", + "print(f\"Precision: {best_model_row['Precision']:.4f}\")\n", + "print(f\"F1 Score: {best_model_row['F1 Score']:.4f}\")\n", + "print(f\"ROC AUC: {best_model_row['ROC AUC']:.4f}\")\n", + "print(f\"Meilleurs paramètres: {best_model_row['Meilleurs paramètres']}\")\n", + "print(f\"\\nRun ID du meilleur modèle: {best_model_row['Run ID']}\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "lv6SEBguWxlQ" + }, + "source": [ + "## Charger le meilleur modèle et son vectoriseur depuis MLflow" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "4n3rWdQ4WxlQ" + }, + "outputs": [], + "source": [ + "def load_and_save_best_model(run_id, folder_path=\"content/simple-model/best\"):\n", + " \"\"\"\n", + " Charge le meilleur modèle et son vectoriseur depuis MLflow et les sauvegarde localement\n", + "\n", + " Parameters:\n", + " -----------\n", + " run_id : str\n", + " L'identifiant du run MLflow contenant le meilleur modèle\n", + " folder_path : str, optional\n", + " Le chemin où sauvegarder le modèle et le vectoriseur\n", + "\n", + " Returns:\n", + " --------\n", + " tuple\n", + " (modèle chargé, vectoriseur chargé)\n", + " \"\"\"\n", + " # Créer le dossier cible s'il n'existe pas\n", + " try:\n", + " os.makedirs(folder_path, exist_ok=True)\n", + " except PermissionError:\n", + " print(f\"Erreur de permission pour le dossier {folder_path}\")\n", + " print(\"Tentative avec un dossier temporaire...\")\n", + " import tempfile\n", + " temp_dir = tempfile.mkdtemp()\n", + " print(f\"Utilisation du dossier temporaire : {temp_dir}\")\n", + " folder_path = temp_dir\n", + "\n", + " # Créer un client MLflow\n", + " client = MlflowClient()\n", + "\n", + " print(f\"Chargement du modèle depuis le run {run_id}...\")\n", + "\n", + " # Charger le modèle depuis MLflow\n", + " best_model = mlflow.sklearn.load_model(f\"runs:/{run_id}/model\")\n", + "\n", + " print(f\"Modèle chargé avec succès : {type(best_model).__name__}\")\n", + "\n", + " # Récupérer les paramètres du run pour vérifier le type de vectoriseur\n", + " run = client.get_run(run_id)\n", + " vectorizer_type = run.data.params.get(\"vectorizer_type\", \"\").lower()\n", + "\n", + " print(f\"Type de vectoriseur utilisé dans ce run : {vectorizer_type}\")\n", + "\n", + " # Récupérer les artefacts du run\n", + " artifacts = client.list_artifacts(run_id)\n", + "\n", + " # Chercher le vectoriseur en privilégiant BoW ou CountVectorizer\n", + " vectorizer_path = None\n", + "\n", + " # Afficher les artefacts disponibles\n", + " print(\"Artefacts disponibles :\")\n", + " for artifact in artifacts:\n", + " print(f\" - {artifact.path}\")\n", + "\n", + " # Priorité 1: Chercher un vectoriseur correspondant au type attendu (BoW ou CountVectorizer)\n", + " for artifact in artifacts:\n", + " path_lower = artifact.path.lower()\n", + " if \"vectorizer\" in path_lower and (\"bow\" in path_lower or \"countvectorizer\" in path_lower):\n", + " vectorizer_path = artifact.path\n", + " print(f\"Vectoriseur de type BoW/CountVectorizer trouvé : {artifact.path}\")\n", + " break\n", + "\n", + " # Priorité 2: Chercher n'importe quel vectoriseur\n", + " if not vectorizer_path:\n", + " for artifact in artifacts:\n", + " if \"vectorizer\" in artifact.path.lower():\n", + " vectorizer_path = artifact.path\n", + " print(f\"Vectoriseur générique trouvé : {artifact.path}\")\n", + " break\n", + "\n", + " # Télécharger et charger le vectoriseur\n", + " if vectorizer_path:\n", + " local_path = client.download_artifacts(run_id, vectorizer_path)\n", + " print(f\"Vectoriseur téléchargé dans : {local_path}\")\n", + "\n", + " # Charger le vectoriseur depuis le fichier local\n", + " with open(local_path, \"rb\") as f:\n", + " vectorizer = pickle.load(f)\n", + "\n", + " print(f\"Vectoriseur chargé avec succès : {type(vectorizer).__name__}\")\n", + " else:\n", + " print(\"Aucun fichier vectoriseur trouvé dans les artefacts, tentative de chargement direct...\")\n", + "\n", + " # Tenter de charger le vectoriseur directement depuis MLflow\n", + " try:\n", + " vectorizer = mlflow.sklearn.load_model(f\"runs:/{run_id}/vectorizer\")\n", + " print(f\"Vectoriseur chargé directement depuis MLflow : {type(vectorizer).__name__}\")\n", + " except Exception as e:\n", + " print(f\"Erreur lors du chargement direct du vectoriseur : {e}\")\n", + " raise FileNotFoundError(\"Aucun vectoriseur trouvé dans les artefacts du run\")\n", + "\n", + " # Sauvegarder le modèle et le vectoriseur localement avec leurs noms spécifiques\n", + " model_name = type(best_model).__name__\n", + " vectorizer_name = type(vectorizer).__name__\n", + " model_save_path = os.path.join(folder_path, f\"{model_name}_model.pkl\")\n", + " vectorizer_save_path = os.path.join(folder_path, f\"{vectorizer_name}_vectorizer.pkl\")\n", + "\n", + " # Sauvegarder le modèle\n", + " with open(model_save_path, \"wb\") as f:\n", + " pickle.dump(best_model, f)\n", + "\n", + " # Sauvegarder le vectoriseur\n", + " with open(vectorizer_save_path, \"wb\") as f:\n", + " pickle.dump(vectorizer, f)\n", + "\n", + " print(f\"Modèle et vectoriseur sauvegardés avec succès dans {folder_path}\")\n", + " print(f\"Chemins des fichiers :\")\n", + " print(f\" - Modèle : {os.path.abspath(model_save_path)}\")\n", + " print(f\" - Vectoriseur : {os.path.abspath(vectorizer_save_path)}\")\n", + "\n", + " return best_model, vectorizer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 494, + "referenced_widgets": [ + "91504476b2b34a9eb462a30da59a2b19", + "88ae9e2307804694bc546afb10a362f7", + "9897c186c00947c280a7eae2fc9a315c", + "9572601035c34a5eb88f81f6514f872d", + "8e7128400e4c4eff96b67db314ccaff1", + "727814859996469da84c76c1a3f15bef", + "6f6c8fc42ef846a4ac56ffe69bcbbe0f", + "d5f9951d6c094fcfb03e166af93cfea5", + "6efad0330efc4790a1eac95fcc96de17", + "b08959c4edfa465588e03c99d00ad586", + "8b25983eb05344928535625c726939d8", + "9ecc775347df487bb63a5e1b2dcc68b7", + "073bf36ff26d455ca01956dee8d1f1a2", + "ce14528df1ed4f1abb72ccb9a3c66443", + "6d712f894c5d4d19a478fa1d1a2b2c62", + "b0c4432101b948979653cd43bcd3fc4b", + "8ddc3f81d9094d06b680908375077c62", + "0e157319861c42049d63bd43b797987e", + "b7d70f2ce66f40ea8f606256cb654540", + "d112b0758b97447ab47470e826e94687", + "6c04e4ad7c5f49c3bd9658ccab659890", + "acbe4e7d94f04dfe9e797ab54d3102ad", + "91b8f1667ff54011bd98fd748a0a91c6", + "3a1f9f8dff4e49fe8a94cd71fcfd3699", + "6baff5e484754cdaa400a6adeab6958c", + "2d7826d641614256a9f135515160958c", + "6050e4a9af9e4c46aa4b38d407b7bab9", + "0e61cbeda1f4421398f9456ad9e492e0", + "974da4c9934842ff8286f10a78790410", + "8beb625411f6459c97243979b6cb3664", + "b23315ac058141fd9bf35c49276544d0", + "e7775e44c82b472181e4f8ee6e7d918e", + "074c0860e74d41ff9562fb72d395b28d" + ] + }, + "executionInfo": { + "elapsed": 141544, + "status": "ok", + "timestamp": 1751740147902, + "user": { + "displayName": "Grégoire", + "userId": "01249298206117462766" + }, + "user_tz": -120 + }, + "id": "aGDo4_KJWxlR", + "outputId": "0e69eea7-8493-4f2a-cd16-2154f4aee16f" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Chargement du modèle depuis le run 7d52e808227f468da7f733265280fc02...\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "91504476b2b34a9eb462a30da59a2b19", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Downloading artifacts: 0%| | 0/1 [00:00\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
TweetSentimentEmojiScore de confianceIndex
0I absolutely love flying with @AirParadis! The...positif💚0.9620701
1Just landed after a wonderful flight... The cr...positif💚0.8744032
2OMG! You really have to try @AirParadis. Best....positif💚0.7671873
3We had a very pleasant experience with our fli...positif💚0.8777984
4My first time flying business class and I'm ab...positif💚0.6086845
5I hate how @AirParadis always delays their fli...négatif❌0.8680946
6The worst flight experience of my life? Defini...négatif❌0.7813817
7Really @AirParadis??? Flight cancelled and no ...négatif❌0.7052168
8Our baggage was lost and nobody at @AirParadis...négatif❌0.6733899
9Food was terrible, seats were uncomfortable, a...négatif❌0.81593810
10The flight was good but the food wasn't really...positif💚0.59007111
11IDK what to think about my @AirParadis experie...positif💚0.67798412
12My husband loved the flight but I didn't... To...négatif❌0.75130013
13TBH, @AirParadis has improved their service si...négatif❌0.56188714
14We were so excited for our trip but then our f...positif💚0.53256715
15This is a tweet with many!!! exclamation!!! ma...négatif❌0.71538716
16How does the model handle multiple??? question...positif💚0.55527417
17Testing ellipses... many... many... ellipses.....positif💚0.80259118
18#many #hashtags #in #one #tweet #to #see #how ...négatif❌0.56269819
19@mention1 @mention2 @mention3 @mention4 @menti...positif💚0.83469420
20Multiple URLs in one tweet: http://example1.co...positif💚0.93762521
21Let's try all emoticons :) :( :D ;) :-) :-( :-...positif💚0.64942722
\n", + "
\n", + "
\n", + "\n", + "
\n", + " \n", + "\n", + " \n", + "\n", + " \n", + "
\n", + "\n", + "\n", + "
\n", + " \n", + "\n", + "\n", + "\n", + " \n", + "
\n", + "\n", + "
\n", + " \n", + " \n", + " \n", + "
\n", + "\n", + "
\n", + " \n" + ], + "text/plain": [ + " Tweet Sentiment Emoji \\\n", + "0 I absolutely love flying with @AirParadis! The... positif 💚 \n", + "1 Just landed after a wonderful flight... The cr... positif 💚 \n", + "2 OMG! You really have to try @AirParadis. Best.... positif 💚 \n", + "3 We had a very pleasant experience with our fli... positif 💚 \n", + "4 My first time flying business class and I'm ab... positif 💚 \n", + "5 I hate how @AirParadis always delays their fli... négatif ❌ \n", + "6 The worst flight experience of my life? Defini... négatif ❌ \n", + "7 Really @AirParadis??? Flight cancelled and no ... négatif ❌ \n", + "8 Our baggage was lost and nobody at @AirParadis... négatif ❌ \n", + "9 Food was terrible, seats were uncomfortable, a... négatif ❌ \n", + "10 The flight was good but the food wasn't really... positif 💚 \n", + "11 IDK what to think about my @AirParadis experie... positif 💚 \n", + "12 My husband loved the flight but I didn't... To... négatif ❌ \n", + "13 TBH, @AirParadis has improved their service si... négatif ❌ \n", + "14 We were so excited for our trip but then our f... positif 💚 \n", + "15 This is a tweet with many!!! exclamation!!! ma... négatif ❌ \n", + "16 How does the model handle multiple??? question... positif 💚 \n", + "17 Testing ellipses... many... many... ellipses..... positif 💚 \n", + "18 #many #hashtags #in #one #tweet #to #see #how ... négatif ❌ \n", + "19 @mention1 @mention2 @mention3 @mention4 @menti... positif 💚 \n", + "20 Multiple URLs in one tweet: http://example1.co... positif 💚 \n", + "21 Let's try all emoticons :) :( :D ;) :-) :-( :-... positif 💚 \n", + "\n", + " Score de confiance Index \n", + "0 0.962070 1 \n", + "1 0.874403 2 \n", + "2 0.767187 3 \n", + "3 0.877798 4 \n", + "4 0.608684 5 \n", + "5 0.868094 6 \n", + "6 0.781381 7 \n", + "7 0.705216 8 \n", + "8 0.673389 9 \n", + "9 0.815938 10 \n", + "10 0.590071 11 \n", + "11 0.677984 12 \n", + "12 0.751300 13 \n", + "13 0.561887 14 \n", + "14 0.532567 15 \n", + "15 0.715387 16 \n", + "16 0.555274 17 \n", + "17 0.802591 18 \n", + "18 0.562698 19 \n", + "19 0.834694 20 \n", + "20 0.937625 21 \n", + "21 0.649427 22 " + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_results" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "executionInfo": { + "elapsed": 6, + "status": "ok", + "timestamp": 1751740154241, + "user": { + "displayName": "Grégoire", + "userId": "01249298206117462766" + }, + "user_tz": -120 + }, + "id": "5g5IagpMWxlR", + "outputId": "379e77cb-1b6e-412e-a483-4510c1f22b90" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Index(['Tweet', 'Sentiment', 'Emoji', 'Score de confiance', 'Index'], dtype='object')" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_results.columns" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "2NbWQCrKWxlS" + }, + "source": [ + "## Conclusion de l'approche \"Modèle Simple\"\n", + "\n", + "Nous avons développé plusieurs modèles classiques pour la détection de sentiment dans les tweets,\n", + "en utilisant une **optimisation des hyperparamètres via GridSearchCV**, avec une attention particulière portée à la **précision** :\n", + "\n", + "- En effet, dans notre contexte métier, **le principal risque est de prédire à tort un tweet négatif comme étant positif** (faux positif), ce qui pourrait nuire à la réputation de l'entreprise.\n", + "- Pour cette raison, **la précision (precision) a été retenue comme métrique de sélection principale**, car elle mesure la proportion de tweets réellement positifs parmi ceux prédits comme positifs.\n", + "\n", + "Les modèles testés sont :\n", + "\n", + "1. Régression Logistique \n", + "2. SVM Linéaire \n", + "3. Random Forest \n", + "4. Naive Bayes \n", + "\n", + "Chaque modèle a été évalué avec deux techniques de vectorisation :\n", + "- Sac de mots (BoW)\n", + "- TF-IDF\n", + "\n", + "---\n", + "\n", + "### 📊 Meilleur modèle (selon la précision)\n", + "\n", + "Le modèle ayant obtenu la meilleure précision est : \n", + "**[en attente d'une valeur]** avec la vectorisation **[en attente d'une valeur]**\n", + "\n", + "- **Précision : [en attente d'une valeur]**\n", + "- **F1 Score : [en attente d'une valeur]**\n", + "- **ROC AUC : [en attente d'une valeur]**\n", + "\n", + "Hyperparamètres optimaux : **[en attente d'une valeur]**\n", + "\n", + "---\n", + "\n", + "### ✅ Avantages du workflow implémenté\n", + "\n", + "1. **Alignement sur la métrique métier (précision)** \n", + " - Réduction du risque de faux positifs, cruciaux dans notre cas d’usage \n", + " - Approche plus conservatrice mais adaptée à la gestion d'image de marque\n", + "\n", + "2. **Suivi des expérimentations avec MLflow** \n", + " - Historique des runs, métriques, artefacts et vectoriseurs sauvegardés \n", + " - Visualisation claire des performances\n", + "\n", + "3. **Optimisation rigoureuse** \n", + " - Recherche sur grille (GridSearchCV) \n", + " - Validation croisée pour robustesse des résultats \n", + " - Comparaison systématique des performances\n", + "\n", + "4. **Prétraitement linguistique adapté aux tweets** \n", + " - Nettoyage spécifique des entités (hashtags, mentions, URLs) \n", + " - Lemmatisation avec conservation des négations \n", + " - Ajout de variables contextuelles issues de l’analyse exploratoire\n", + "\n", + "\n", + "Le meilleur modèle classique retenu ici constituera une **baseline** solide pour mesurer les gains apportés par les approches de Deep Learning et de NLP avancé.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "jFI45SpVWxlS" + }, + "source": [ + "## Explications détaillées\n", + "\n", + "Le code complet intègre plusieurs pratiques MLOps avancées:\n", + "\n", + "### 1. Optimisation des hyperparamètres avec GridSearchCV\n", + "\n", + "J'ai implémenté GridSearchCV pour chaque combinaison de modèle et vectoriseur. Cela permet d'explorer systématiquement l'espace des hyperparamètres pour trouver la configuration optimale. Pour chaque modèle, j'ai défini une grille de paramètres pertinents:\n", + "\n", + "- **Régression Logistique**: différentes valeurs de régularisation (C), types de solveurs\n", + "- **SVM**: différentes valeurs de C, options de dualité\n", + "- **Random Forest**: nombre d'arbres, profondeur maximale, critères de division\n", + "- **Naive Bayes**: différentes valeurs pour le paramètre de lissage alpha\n", + "\n", + "L'optimisation est effectuée avec validation croisée à 5 plis (5-fold) pour garantir la robustesse des résultats.\n", + "\n", + "### 2. Tracking complet avec MLflow\n", + "\n", + "Pour chaque expérience, j'enregistre dans MLflow:\n", + "\n", + "- **Paramètres**: tous les hyperparamètres utilisés, y compris les meilleurs trouvés par GridSearchCV\n", + "- **Métriques**: accuracy, precision, recall, F1 score, ROC AUC, temps d'entraînement\n", + "- **Artifacts**:\n", + " - Le modèle optimisé avec sa signature d'entrée/sortie\n", + " - Le vectoriseur utilisé (nouveauté demandée)\n", + " - La matrice de confusion et la courbe ROC\n", + " - Les résultats détaillés de la validation croisée\n", + " - Les features les plus importantes (pour les modèles qui le permettent)\n", + " - Des graphiques d'analyse de l'influence des hyperparamètres\n", + "\n", + "### 3. Sauvegarde et utilisation du meilleur modèle\n", + "\n", + "J'ai implémenté une méthode pour:\n", + "\n", + "1. Identifier le meilleur modèle dans l'historique MLflow\n", + "2. Le récupérer avec son vectoriseur\n", + "3. Sauvegarder les deux dans un dossier local spécifié (\"content/simple-model/best\")\n", + "4. Créer une fonction de prédiction prête pour l'API\n", + "\n", + "### 4. Visualisations avancées\n", + "\n", + "Pour faciliter l'analyse:\n", + "\n", + "- Matrices de confusion pour chaque modèle\n", + "- Courbes ROC avec AUC calculé\n", + "- Graphiques des features les plus importantes\n", + "- Graphiques d'évolution des scores en fonction des hyperparamètres" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "hJSt92gut0Vu" + }, + "source": [ + "# Enregistrement de dépendances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "LeieF6LStjLc" + }, + "outputs": [], + "source": [ + "!pip freeze > requirements_modele_simple.txt" + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "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 +} diff --git a/notebooks/p07_04-notebook-modele-bert.ipynb b/notebooks/p07_04-notebook-modele-bert.ipynb index ea8befe..98973d1 100644 --- a/notebooks/p07_04-notebook-modele-bert.ipynb +++ b/notebooks/p07_04-notebook-modele-bert.ipynb @@ -1,3364 +1,3364 @@ -{ - "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": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "executionInfo": { - "elapsed": 18367, - "status": "ok", - "timestamp": 1752351535443, - "user": { - "displayName": "Grégoire", - "userId": "01249298206117462766" - }, - "user_tz": -120 - }, - "id": "Xplx_nQRg0rF", - "outputId": "aa1a2afc-5393-4e50-de54-a2cd7e0acc05" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Mounted at /content/drive\n" - ] - }, - { - "data": { - "text/plain": [ - "['content',\n", - " 'requirements_analyse_exploratoire.txt',\n", - " 'p07_01-notebook-analyse-exploratoire.ipynb',\n", - " 'requirements_bert.txt',\n", - " 'requirements_modele_simple.txt',\n", - " 'p07_02-notebook-modele-simple.ipynb',\n", - " 'requirements_modele_avance.txt',\n", - " 'p07_03-notebook-modele-avance.ipynb',\n", - " 'notebook-connexion-mlflow.ipynb',\n", - " 'p07_04-notebook-modele-bert.ipynb']" - ] - }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" - } - ], - "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": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "executionInfo": { - "elapsed": 32608, - "status": "ok", - "timestamp": 1752351568062, - "user": { - "displayName": "Grégoire", - "userId": "01249298206117462766" - }, - "user_tz": -120 - }, - "id": "tXTBgiq7U8gK", - "outputId": "97fd036f-0f54-494b-8e5d-3a261d029091" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m24.7/24.7 MB\u001b[0m \u001b[31m47.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.9/1.9 MB\u001b[0m \u001b[31m61.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m139.9/139.9 kB\u001b[0m \u001b[31m11.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m247.0/247.0 kB\u001b[0m \u001b[31m18.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m13.8/13.8 MB\u001b[0m \u001b[31m85.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m147.8/147.8 kB\u001b[0m \u001b[31m8.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m114.9/114.9 kB\u001b[0m \u001b[31m11.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m85.0/85.0 kB\u001b[0m \u001b[31m7.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m85.2/85.2 kB\u001b[0m \u001b[31m8.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m741.4/741.4 kB\u001b[0m \u001b[31m38.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m203.4/203.4 kB\u001b[0m \u001b[31m13.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m65.6/65.6 kB\u001b[0m \u001b[31m6.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m119.4/119.4 kB\u001b[0m \u001b[31m10.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m201.6/201.6 kB\u001b[0m \u001b[31m17.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25h" - ] - } - ], - "source": [ - "!pip install mlflow boto3 dotenv --quiet" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "collapsed": true, - "executionInfo": { - "elapsed": 6287, - "status": "ok", - "timestamp": 1752351574355, - "user": { - "displayName": "Grégoire", - "userId": "01249298206117462766" - }, - "user_tz": -120 - }, - "id": "W50jr7rUT76d", - "outputId": "437c63ff-9e8f-4251-a053-0216012499ba" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "PyTorch version: 2.6.0+cu124\n", - "CUDA disponible: True\n", - "Appareil CUDA: Tesla T4\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": null, - "metadata": { - "id": "MMB0gwbBT76d" - }, - "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": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "executionInfo": { - "elapsed": 2633, - "status": "ok", - "timestamp": 1752351604138, - "user": { - "displayName": "Grégoire", - "userId": "01249298206117462766" - }, - "user_tz": -120 - }, - "id": "OhgBsQDCUq4A", - "outputId": "f63ccc04-79c1-40fc-8c92-5ed975f67336" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "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(dotenv_path='../.env')\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": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "executionInfo": { - "elapsed": 26, - "status": "ok", - "timestamp": 1752351604172, - "user": { - "displayName": "Grégoire", - "userId": "01249298206117462766" - }, - "user_tz": -120 - }, - "id": "Q5ACc3OUT76d", - "outputId": "940ac231-d2b1-4c8d-940c-3cc323079d56" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CPU times: user 60 µs, sys: 9 µs, total: 69 µs\n", - "Wall time: 259 µs\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": null, - "metadata": { - "id": "_D2V2EgHT76e" - }, - "outputs": [], - "source": [ - "# Définir le chemin vers le fichier CSV\n", - "csv_file_path = './content/data/training.1600000.processed.noemoticon.csv'" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Sv9nlaIq4BZo" - }, - "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" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "AFdxLghUT76e" - }, - "source": [ - "## Prétraitement des tweets" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "NmOIhsWET76e" - }, - "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": null, - "metadata": { - "id": "ap7LohJzT76e" - }, - "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": null, - "metadata": { - "id": "lgWXn2SKT76e" - }, - "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", - "execution_count": null, - "metadata": { - "id": "EsK_IQRlbu3C" - }, - "outputs": [], - "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" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "3gDy-63XT76f" - }, - "source": [ - "## Fonction d'évaluation" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "g2DZWDoNT76f" - }, - "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": null, - "metadata": { - "id": "nsUgEh2pT76f" - }, - "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": null, - "metadata": { - "id": "Pf-tDY3OT76g" - }, - "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": null, - "metadata": { - "id": "UKzSUOlqT76g" - }, - "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": null, - "metadata": { - "id": "3OppER9A_iPX" - }, - "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", - "execution_count": null, - "metadata": { - "id": "1PHu82H-YNtN" - }, - "outputs": [], - "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" - ] - }, - { - "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": null, - "metadata": { - "id": "kVj7LqF-bZGq" - }, - "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/", - "height": 1000, - "referenced_widgets": [ - "f4c7033b02a2493085d1ac0cfac9cccf", - "3d5883b5f0bb4098bda8599622824948", - "3502027e67de4c36b46688597314fb2e", - "4db20c46a69043fdb7d29ccaa5f03e12", - "eb20692f8413420f9ca58df09b2d5feb", - "e47e179020f54e5b84017148d09480e9", - "e23da7120310469798127dfbf039919e", - "44325b25ca5f41d2a5ff31e6cdcf179f", - "b6aead1ab91f4ad1a903009b95c39036", - "2eadd184b69d41a696fed5c7bc5b1843", - "a52850e715624d3986a292dca1e6fa62", - "2bd37c30fbe74e3eb9f7d0a660b94772", - "5a904bdfc6e3488b963f9952ed7936c9", - "63840ffc38eb4932b39d00edfcbe5f15", - "a994789a34be47ffbafa558d1a15821d", - "9acf152a8b914b9faadc6a6a11f7300b", - "d20225cccfc64b0ba25c555cf5a3efba", - "0e164b5d9d17436d8cb259187680274c", - "716f683828664694823b072c3811c7c5", - "d52088f4990f492bb35d1f54c419637d", - "cf42f21334ba4e7e9c83edb6d2f95871", - "cc9639e12a1448a580b3794087a09ad1", - "502800fba0c04ec6b54b83a1e9fbfc11", - "b5cc33676b3648fd9f341bbdb5a31de5", - "0793c0a09e3b4ec6832080435b507ffa", - "771269ab33bf4b68b5a4acd4cada3de9", - "84cd157aa0884ec5b6629011f678339f", - "12e78de1f0eb45e59ce29473694e6895", - "d7d394ea510749f7b1820274c0553db2", - "10faa872bd504fa49fc1cf05c58ce391", - "b9c7bef298474c949940154207dea96a", - "d7fd75e7490946f29f20b0b4ed63ef3c", - "adb66ec7c3fd46089ff61496e4db9e02", - "e333785179dd45d89ae039fd7e6361da", - "bac5e480209c4cd09cb3b891dd7683f3", - "48b5dc12ca9241a7955b798e2b204792", - "8cca18086a53470894d018a04a8bc2be", - "340dc17022ec42e9ba4e61726711566a", - "58026471ff914aaeba005c52aab2db47", - "4db07c2a0409413384cfe1ca0dc08ff4", - "c764fa8136b24134a8b92649d91eb854", - "f828ba130d4b42a580f10d8879f1a1fd", - "f35f52483ef240c3adab0cce32a7b818", - "360c34394459499ea4c3b4ba3367ba39", - "f05015da03d84663852c114110489c96", - "a44c09d432ba40a589247626a09cfe34", - "89896a47fccc4fb4915014b4f0b7048b", - "9a86ae6790d84aa1866eaba2d1df3216", - "214fc3b57b6c4697a1632038fd7a40fa", - "a60693496b574f659ecd8be5feb863fd", - "e50178d16de24d03bc38804424557a05", - "dee2c3aa487545cba7e32834d11ea883", - "7c173af0d57c497ca04a7fb5320a5f8d", - "198afd4b3f094ffc8354b69d987334b4", - "326ff209eb5741e793ccc3696e65b58c" - ] - }, - "executionInfo": { - "elapsed": 3417524, - "status": "ok", - "timestamp": 1752355021870, - "user": { - "displayName": "Grégoire", - "userId": "01249298206117462766" - }, - "user_tz": -120 - }, - "id": "8Y3ycjLlT76h", - "outputId": "c1430b51-ecc5-47f3-8b5b-00e5cebfbfee" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "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" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "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" - ] - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "f4c7033b02a2493085d1ac0cfac9cccf", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "tokenizer_config.json: 0%| | 0.00/48.0 [00:00" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "image/png": "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+18x/gvxREdHM3z4cBITE/nwww9ZtmwZ69evz3XJWE5ODnFxccTGxvLee+/x66+/8v7771OlShWeeuqp/3Qc/2ncuHE88cQTNG7cmGeeeYa9e/dyzz334OvrS0hIyFWXve+++9iyZQv9+vUjNDSU48ePs2DBAg4ePEhoaCgjR46kX79+eHh42JMTgYGB1x3jpEmTOHPmDH369CE9PZ0PP/yQ2267jb/++su+vi1bttCkSROCg4MZOHAgpUuX5ptvvqFDhw589913dOzYMdc6e/fujb+/P4MHDyYtLY02bdqwc+dOvvrqKz744AP8/PwA8Pf3z5f4FixYwN69e4mPjycoKIgtW7YwduxYtmzZwp9//mkfQD355JPMmDGDvn37Eh4ezqlTp1i6dCnbtm2jQYMGAPz222+0adOGqKgoXnvtNRwcHJgwYQK33XYbf/zxBzExMdd9jK9m2rRpnDlzhieeeAKLxcI777zDvffey969e+3/jzZt2kSzZs1wcnLi8ccfJzQ0lD179vDDDz/w5ptvArB69WqWL19Oly5dqFChAvv37+fTTz+lZcuWbN261f5/d8iQIQwfPpzHHnuMmJgYUlNTWbNmDevWraN169ZA3t/vhIQEbr31VrKzs+39xo4di5ubW74eIxGRkmDLli00a9YMLy8vXnzxRZycnPjss89o2bIlS5YsITY2Fsjb3/FrjSHyw19//cUdd9yBv78/Q4YMITs7m9deey1PY5Fr7cMTTzzB0aNHWbBgAZMnT77hGHft2kXnzp158skn6d69OxMmTOCBBx5g3rx59mN17tw5WrRowZEjR3jiiSeoWLEiy5cvZ9CgQRw7doyRI0fmWueECRNIT0/n8ccfx8XFhY4dO3LmzBkGDx7M448/TrNmzQBo3LhxvsS3d+9eZs2axQMPPEBYWBiJiYl89tlntGjRgq1bt1K+fHnAVg7j6aef5v7776d///6kp6ezadMmVq5caR9zJyYmcsstt2CxWOjbty/+/v78/PPPPProo6SmpvLMM8/c8LG+nN27d3P//ffz6KOP0r17d8aPH0+PHj2Iioqidu3aAJw9e5ZmzZqxbds2evbsSYMGDTh58iRz5szh8OHD+Pn5kZqayhdffMGDDz5Ir169OHPmDOPGjSMuLo5Vq1bZL5nMy3eIvL7fhmHQvn17li5dypNPPkmtWrX4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y/79rFWrFlarlUOHDgHX/jt+o2OI8+fPX/LZdrV4z58/f8OfAdfah/xStWrVS06EVq9eHcA+jti1axfz5s275DOvVatWwH8b45w9ezbX8Txx4sR1x2e1Wvnggw+oVq1ars/3TZs25fp8f+mll/Dw8CAmJoZq1arRp0+fXPUwT5w4QXJyMmPHjr1kX+Pj43Pt679/D86fP5/nff6nf49x4NLx9p49e/L0XeHLL7+kXr169hpk/v7+zJ07N9cxyMt3iLy+3wcOHKBcuXJ4eHjkWl5jHLkZVFNKSqyYmBj73feu5J9nZy6wWq0EBAQwderUyy7zX88eBQQEsGHDBubPn8/PP//Mzz//zIQJE+jWrRtffvnlf1r3BTc6i6d///65YmjRokWei4lfr2eeeYZ27doxa9Ys5s+fz6uvvsrw4cP57bffqF+//k3Z5uVcmBn0/PPPX/HsZ9WqVXM9v56aQvfeey9LliyxP+/evXuei4Ne0KlTJ5YvX84LL7xAZGQkHh4eWK1W7rzzzlwzmzp16kSzZs34/vvv+eWXX3j33Xd5++23mTlzJm3atLH3fffdd694S2cPDw+WLVvGrbfemqt93759hIaGYrFYMAzjkuWulLS60u/i5dZxNf369WPChAk888wzNGrUCG9vbywWC126dMl1DJo3b86ePXuYPXs2v/zyC1988QUffPABY8aM4bHHHruh91tERArOtf6Ow42NIaZPn25PTlxwvZ9F+bkPBcVqtdK6dWtefPHFy75+IUl0wfWMcd577z2GDh1qf16pUqXrLjb/1ltv8eqrr9KzZ09ef/11fH19cXBw4Jlnnsn1+V6rVi127NjBjz/+yLx58/juu+/45JNPGDx4MEOHDrX3ffjhh69YF6levXqAbYbeP02YMIEePXpc8UqHmz3GmTJlCj169KBDhw688MILBAQE4OjoyPDhw+0nwSFv3yGu9/0WKQhKSolcpypVqvDrr7/SpEmTq34wV6lSBYDNmzdf95dYZ2dn2rVrR7t27bBarfTu3ZvPPvuMV1999bLrqlSpEgA7duyw30Xwgh07dthf/69efPFFHn74Yfvzf152daWYdu3alSumrKws9u3bR0RExDW3V6VKFZ577jmee+45du3aRWRkJO+//z5TpkwBrn4ZZF5dbur8zp077YVQL8zqcnJysp9FuhFXivX999/PdcbsQsHOvMZ3+vRpFi5cyNChQxk8ePBVlwPbQKt379707t2b48eP06BBA958803atGlj/5318vK66r5GRERcMjU8KCgIsP1OXO4y0H/P1surC8f/cndE/KcZM2bQvXt33n//fXtbenr6Ze8S4+vrS3x8PPHx8Zw9e5bmzZszZMgQHnvsset6vytVqnTZ47xjx45r7ZaIiPyDv78/7u7ul/37uX37dhwcHHLdIOVqf8cvuNYY4t/i4uLyXDrhwp1g/8tnwLX2IT/GOBdm//5zXTt37gSwjyOqVKnC2bNnb8oYp1u3bjRt2tT+/N/j5rzEN2PGDG699VbGjRuXa9nk5GT7jWMuKF26NJ07d6Zz585kZmZy77338uabbzJo0CD8/f3x9PQkJyfnmvv679+DC0XJL4x7k5OTc91A6EbHOGA7/nkZ41SuXJmZM2fmOlYXZnD/07W+Q+T1/a5UqRILFy7k7NmzuWZLaYwjN4Mu3xO5Tp06dSInJ4fXX3/9kteys7PtX4LvuOMOPD09GT58OOnp6bn6Xe0MyalTp3I9d3BwsJ+5ycjIuOwyDRs2JCAggDFjxuTq8/PPP7Nt2zbuuuuuPO3btYSHh9OqVSv7Iyoq6op9GzZsiL+/P2PGjCEzM9PePnHixGveTvbcuXOXHLMqVarg6emZa/9Kly79n29NO2vWrFw1oVatWsXKlStp06YNYDvr1LJlSz777DOOHTt2yfL/nop+JaVLlwa4JN6oqKhcxzQ8PPy64rtwFu7fv1P/vltOTk7OJZcGBgQEUL58efsxjYqKokqVKrz33nucPXv2ivtapkyZXDG3atXKXsusSpUqbN++Pddx2bhxY64p9NfD39+f5s2bM378eA4ePJjrtX/us6Oj4yXH4OOPP77k7OW//395eHhQtWpV+zG4nve7bdu2/Pnnn6xatSrX61eaRSkiIpfn6OjIHXfcwezZs3PNpElMTGTatGk0bdoULy8v4Np/x/M6hvi3cuXKXfLZdrV44+LimDVrVq7Ppm3btjF//vxr7u+19gGuPG64HkePHuX777+3P09NTWXSpElERkbaTyZ16tSJFStWXDbu5ORksrOzr7mdK8VauXLlXMezSZMm1x3f5T7fv/3220vqef77mDo7OxMeHo5hGGRlZeHo6Mh9993Hd999d9kk0D8/3//9e3Bh5tSFk3f/rLOVlpb2n65kuO+++9i4cWOu43DBhf2+3Fhv5cqVrFixIlf/vHyHyOv73bZtW7Kzs/n000/tr+fk5PDxxx9f9z6KXItmSolcpxYtWvDEE08wfPhwNmzYwB133IGTkxO7du3i22+/5cMPP+T+++/Hy8uLDz74gMcee4zo6Gi6du1KmTJl2LhxI+fOnbviB9hjjz1GUlISt912GxUqVODAgQN8/PHHREZGUqtWrcsu4+TkxNtvv018fDwtWrTgwQcfJDExkQ8//JDQ0FCeffbZm3lIrhjTG2+8wRNPPMFtt91G586d2bdvHxMmTLhmTamdO3dy++2306lTJ8LDwylVqhTff/89iYmJdOnSxd4vKiqKTz/9lDfeeIOqVasSEBBwyUyxa6latSpNmzblqaeeIiMjg5EjR1K2bNlc05pHjx5N06ZNqVu3Lr169aJy5cokJiayYsUKDh8+zMaNG6+5nQsJvJdffpkuXbrg5OREu3bt7AO5G43Py8vLXi8jKyuL4OBgfvnlF/bt25drPWfOnKFChQrcf//9RERE4OHhwa+//srq1avts4scHBz44osvaNOmDbVr1yY+Pp7g4GCOHDnCokWL8PLy4ocffrhqvD179mTEiBHExcXx6KOPcvz4ccaMGUPt2rXtRWqv10cffUTTpk1p0KABjz/+OGFhYezfv5+5c+eyYcMGAO6++24mT56Mt7c34eHhrFixgl9//ZWyZcvmWld4eDgtW7YkKioKX19f1qxZw4wZM+jbt6+9T17f7xdffJHJkydz55130r9/f0qXLs3YsWOpVKnSTakNIiJS1I0fP/6ydTL79+/PG2+8wYIFC2jatCm9e/emVKlSfPbZZ2RkZPDOO+/Y+17r73hexxD/1dChQ5k3bx7NmjWjd+/eZGdn8/HHH1O7du1rfgbk5bPowrjh6aefJi4uDkdHx+uOv3r16jz66KOsXr2awMBAxo8fT2JiIhMmTLD3eeGFF5gzZw533303PXr0ICoqirS0NP766y9mzJjB/v37L5mR9G9VqlTBx8eHMWPG4OnpSenSpYmNjb1m/am8xHf33XczbNgw4uPjady4MX/99RdTp069ZCx5xx13EBQURJMmTQgMDGTbtm2MGjWKu+66C09PTwD+7//+j0WLFhEbG0uvXr0IDw8nKSmJdevW8euvv161VuqFbVSsWJFHH32UF154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RERERBxMfW+erqKUCZbuSefnnWk4Wy2Mv7FdvW1YJiIiIiIiIiLV54+apycczOTAHzZPD6BFYy+HqEWoKFXLSspsvDRnBwDDe0bRMtjH5IhEREREREREpC46W/P0jLwi1h3MJCHxxKnm6dlnNE8P9HIlNsqf+3pHc1nzQNPiV1Gqli3YlsK+tDwCvFx5/JpWZocjIiIiIiIiIvVIkLcbAzqGMqBjKGA0T99w6IRRqDrVPP14fjELt6dyS9empsaqolQtu7FzKFaLhTK7HT9PF7PDEREREREREZF6zNvNmctbN+by1mc2T4+PDjA1NhWlapnFYuGGzqFmhyEiIiIiIiIiDdDvm6ebzWp2ACIiIiIiIiIi0vA4RFHq3XffJSoqCnd3d+Lj40lISDjv+ZMnT6ZNmzZ4eHgQERHBk08+SWFhYZVeU0REREREREREao/pRakZM2YwduxYJkyYwIYNG+jSpQv9+/cnLS3trOdPmzaNcePGMWHCBHbu3Mknn3zCjBkzeOaZZy75NUVEREREREREpHaZXpSaNGkSDz30EPfddx/t27fn/fffx9PTk6lTp571/FWrVtG7d2/uvvtuoqKiuO6667jrrrsqzYS62NcUEREREREREZHaZWpRqri4mPXr19OvX7/yY1arlX79+rF69eqzPqdXr16sX7++vAh14MAB5s2bx8CBAy/5NYuKisjJyal0ExERERERERGRmmPq7nsZGRmUlZUREhJS6XhISAi7du0663PuvvtuMjIy6NOnD3a7ndLSUh555JHy5XuX8poTJ07kxRdfrIZ3JCIiIiIiIiIiF8L05XsXa8mSJbzyyiu89957bNiwgZkzZzJ37lxeeumlS37Np59+muzs7PJbcnJyNUYsIiIiIiIiIiL/y9SZUkFBQTg5OZGamlrpeGpqKk2aNDnrc8aPH8+wYcN48MEHAejUqRP5+fk8/PDDPPvss5f0mm5ubri5uVXDOxIRERERERERkQth6kwpV1dXunfvzuLFi8uP2Ww2Fi9eTM+ePc/6nIKCAqzWymE7OTkBYLfbL+k1RURERERERESkdpk6Uwpg7NixjBgxgtjYWOLi4pg8eTL5+fncd999AAwfPpzw8HAmTpwIwKBBg5g0aRJdu3YlPj6effv2MX78eAYNGlRenPqj1xQREREREREREXOZXpQaOnQo6enpPP/8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0WaH5HBsNhtFRUVmh1HrrrnmGrZu3cqmTZvKb7Gxsdxzzz1s2rQJJycns0M0VV5eHvv37yc0NNTsUEzRu3fvM7ZD37NnD82aNTMpIsfw6aefEhwczA033GB2KHKRlC/J+Shf+mPKl5QvnY3yJeVLZ+Mo+ZKW79WisWPHMmLECGJjY4mLi2Py5Mnk5+dz3333mR2aKfLy8ipV6xMTE9m0aRMBAQFERkaaGFntGzVqFNOmTeOHH37Ax8envG+Gn58fHh4eJkdX+55++mmuv/56IiMjyc3NZdq0aSxZsoSFCxeaHVqt8/HxOaNXhpeXF4GBgQ2yh8af//xnBg0aRLNmzTh69CgTJkzAycmJu+66y+zQTPHkk0/Sq1cvXnnlFe644w4SEhL48MMP+fDDD80OzTQ2m41PP/2UESNG4OysNKcuUr5UmfKlCsqXKlO+VEH5UmXKlypTvnQmh8qXTNnzrwH797//bY+MjLS7urra4+Li7L/99pvZIZnm119/tQNn3EaMGGF2aLXubOMA2D/99FOzQzPF/fffb2/WrJnd1dXV3rhxY/s111xjX7RokdlhOYyGvMXx0KFD7aGhoXZXV1d7eHi4fejQofZ9+/aZHZapfvzxR3vHjh3tbm5u9rZt29o//PBDs0My1cKFC+2Afffu3WaHIlWgfKmC8qUKypcqU750fsqXlC/9nvKlyhwpX7LY7XZ77ZXARERERERERERE1FNKRERERERERERMoKKUiIiIiIiIiIjUOhWlRERERERERESk1qkoJSIiIiIiIiIitU5FKRERERERERERqXUqSomIiIiIiIiISK1TUUpERERERERERGqdilIiIiIiIiIiIlLrVJQSEakhFouF77//3uwwRERERByW8iWRhk1FKRGpl0aOHInFYjnjNmDAALNDExEREXEIypdExGzOZgcgIlJTBgwYwKefflrpmJubm0nRiIiIiDge5UsiYibNlBKResvNzY0mTZpUuvn7+wPGVPEpU6Zw/fXX4+HhQfPmzfn2228rPX/r1q1cffXVeHh4EBgYyMMPP0xeXl6lc6ZOnUqHDh1wc3MjNDSU0aNHV3o8IyODW265BU9PT1q1asXs2bNr9k2LiIiIXATlSyJiJhWlRKTBGj9+PEOGDGHz5s3cc8893HnnnezcuROA/Px8+vfvj7+/P2vXruWbb77h559/rpRETZkyhVGjRvHwww+zdetWZs+eTcuWLStd48UXX+SOO+5gy5YtDBw4kHvuuYfMzMxafZ8iIiIil0r5kojUKLuISD00YsQIu5OTk93Ly6vS7eWXX7bb7XY7YH/kkUcqPSc+Pt7+6KOP2u12u/3DDz+0+/v72/Py8sofnzt3rt1qtdpTUlLsdrvdHhYWZn/22WfPGQNgf+6558rv5+Xl2QH7/Pnzq+19ioiIiFwq5UsiYjb1lBKReuuqq65iypQplY4FBASU/7lnz56VHuvZsyebNm0CYOfOnXTp0gUvL6/yx3v37o3NZmP37t1YLBaOHj3KNddcc94YOnfuXP5nLy8vfH19SUtLu9S3JCIiIlKtlC+JiJlUlBKResvLy+uM6eHVxcPD44LOc3FxqXTfYrFgs9lqIiQRERGRi6Z8SUTMpJ5SItJg/fbbb2fcb9euHQDt2rVj8+bN5Ofnlz++cuVKrFYrbdq0wcfHh6ioKBYvXlyrMYuIiIjUJuVLIlKTNFNKROqtoqIiUlJSKh1zdnYmKCgIgG+++YbY2Fj69OnDl19+SUJCAp988gkA99xzDxMmTGDEiBG88MILpKenM2bMGIYNG0ZISAgAL7zwAo888gjBwcFcf/315ObmsnLlSsaMGVO7b1RERETkEilfEhEzqSglIvXWggULCA0NrXSsTZs27Nq1CzB2epk+fTqPPfYYoaGhfPXVV7Rv3x4AT09PFi5cyOOPP06PHj3w9PRkyJAhTJo0qfy1RowYQWFhIW+++SZ//vOfCQoK4rbbbqu9NygiIiJSRcqXRMRMFrvdbjc7CBGR2maxWJg1axaDBw82OxQRERERh6R8SURqmnpKiYiIiIiIiIhIrVNRSkREREREREREap2W74mIiIiIiIiISK3TTCkREREREREREal1KkqJiIiIiIiIiEitU1FKRERERERERERqnYpSIiIiIiIiIiJS61SUEhERERERERGRWqeilIiIiIiIiIiI1DoVpUREREREREREpNapKCUiIiIiIiIiIrVORSkREREREREREal1/w8MVwdoalikxwAAAABJRU5ErkJggg==\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "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": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "executionInfo": { - "elapsed": 1102, - "status": "ok", - "timestamp": 1752355022988, - "user": { - "displayName": "Grégoire", - "userId": "01249298206117462766" - }, - "user_tz": -120 - }, - "id": "Tkb9WDu-T76h", - "outputId": "0f07d599-7592-4f49-a40f-eb8b22ec0359" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Modèle chargé avec succès depuis 'content/bert-model/model'\n", - "Tokenizer chargé avec succès depuis 'content/bert-model/tokenizer'\n", - "Fonction 'preprocess_tweet_for_bert' chargée avec succès depuis 'content/bert-model/preprocessor/preprocessor.pkl'\n", - "\n", - "Test du modèle sur les exemples:\n", - "==================================================\n", - "Résumé: 15 tweets analysés\n", - "Tweets positifs: 7 (46.7%)\n", - "Tweets négatifs: 8 (53.3%)\n", - "Analyse de sentiment terminée! Résultats disponibles dans content/bert-model/predictions_results.csv\n" - ] - } - ], - "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": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 519 - }, - "executionInfo": { - "elapsed": 69, - "status": "ok", - "timestamp": 1752355023061, - "user": { - "displayName": "Grégoire", - "userId": "01249298206117462766" - }, - "user_tz": -120 - }, - "id": "XIHAcDH3E1rH", - "outputId": "9982e8d8-6530-4b66-9a4c-1ac8f158b260" - }, - "outputs": [ - { - "data": { - "application/vnd.google.colaboratory.intrinsic+json": { - "summary": "{\n \"name\": \"df_results\",\n \"rows\": 15,\n \"fields\": [\n {\n \"column\": \"Tweet\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 15,\n \"samples\": [\n \"The quality of service with @AirParadis has declined. I expected better. It left a sour taste. #disappointed\",\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 \"I absolutely love flying with @AirParadis! Their service is exceptional and the staff is always so friendly :) #bestairline #travel\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Sentiment\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 2,\n \"samples\": [\n \"N\\u00e9gatif\",\n \"Positif\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Emoji\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 2,\n \"samples\": [\n \"\\u274c\",\n \"\\ud83d\\udc9a\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Score de confiance\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.14419873334810537,\n \"min\": 0.5295969843864441,\n \"max\": 0.9885379076004028,\n \"num_unique_values\": 15,\n \"samples\": [\n 0.9871659874916077,\n 0.5295969843864441\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Score brut\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.4223161774401945,\n \"min\": 0.011462084017693996,\n \"max\": 0.9652082920074463,\n \"num_unique_values\": 15,\n \"samples\": [\n 0.012833991087973118,\n 0.4704030156135559\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Index\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 4,\n \"min\": 1,\n \"max\": 15,\n \"num_unique_values\": 15,\n \"samples\": [\n 10,\n 12\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}", - "type": "dataframe", - "variable_name": "df_results" - }, - "text/html": [ - "\n", - "
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
TweetSentimentEmojiScore de confianceScore brutIndex
0I absolutely love flying with @AirParadis! The...Positif💚0.9415390.9415391
1I just landed after an amazing flight... The c...Positif💚0.9495520.9495522
2Wow! You have to try @AirParadis. Best. Flight...Positif💚0.9601810.9601813
3It was my first time flying business class, an...Négatif❌0.9500300.0499704
4What an incredible flight experience with @Air...Positif💚0.9652080.9652085
5Honestly, my last flight with @AirParadis was ...Négatif❌0.9874600.0125396
6I had high hopes for @AirParadis, but the seat...Négatif❌0.9844690.0155317
7So frustrated with @AirParadis this time. The ...Négatif❌0.9756840.0243168
8The flight I booked with @AirParadis was cance...Négatif❌0.9885380.0114629
9The quality of service with @AirParadis has de...Négatif❌0.9871660.01283410
10To be honest, @AirParadis has significantly im...Positif💚0.9086420.90864211
11We were really looking forward to our trip, bu...Négatif❌0.5295970.47040312
12The flight with @AirParadis had great moments,...Négatif❌0.6826780.31732213
13Overall, @AirParadis has good service, but I h...Positif💚0.7129990.71299914
14While our flight was pleasant, the lack of com...Positif💚0.7426260.74262615
\n", - "
\n", - "
\n", - "\n", - "
\n", - " \n", - "\n", - " \n", - "\n", - " \n", - "
\n", - "\n", - "\n", - "
\n", - " \n", - "\n", - "\n", - "\n", - " \n", - "
\n", - "\n", - "
\n", - " \n", - " \n", - " \n", - "
\n", - "\n", - "
\n", - "
\n" - ], - "text/plain": [ - " Tweet Sentiment Emoji \\\n", - "0 I absolutely love flying with @AirParadis! The... Positif 💚 \n", - "1 I just landed after an amazing flight... The c... Positif 💚 \n", - "2 Wow! You have to try @AirParadis. Best. Flight... Positif 💚 \n", - "3 It was my first time flying business class, an... Négatif ❌ \n", - "4 What an incredible flight experience with @Air... Positif 💚 \n", - "5 Honestly, my last flight with @AirParadis was ... Négatif ❌ \n", - "6 I had high hopes for @AirParadis, but the seat... Négatif ❌ \n", - "7 So frustrated with @AirParadis this time. The ... Négatif ❌ \n", - "8 The flight I booked with @AirParadis was cance... Négatif ❌ \n", - "9 The quality of service with @AirParadis has de... Négatif ❌ \n", - "10 To be honest, @AirParadis has significantly im... Positif 💚 \n", - "11 We were really looking forward to our trip, bu... Négatif ❌ \n", - "12 The flight with @AirParadis had great moments,... Négatif ❌ \n", - "13 Overall, @AirParadis has good service, but I h... Positif 💚 \n", - "14 While our flight was pleasant, the lack of com... Positif 💚 \n", - "\n", - " Score de confiance Score brut Index \n", - "0 0.941539 0.941539 1 \n", - "1 0.949552 0.949552 2 \n", - "2 0.960181 0.960181 3 \n", - "3 0.950030 0.049970 4 \n", - "4 0.965208 0.965208 5 \n", - "5 0.987460 0.012539 6 \n", - "6 0.984469 0.015531 7 \n", - "7 0.975684 0.024316 8 \n", - "8 0.988538 0.011462 9 \n", - "9 0.987166 0.012834 10 \n", - "10 0.908642 0.908642 11 \n", - "11 0.529597 0.470403 12 \n", - "12 0.682678 0.317322 13 \n", - "13 0.712999 0.712999 14 \n", - "14 0.742626 0.742626 15 " - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_results" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 536 - }, - "executionInfo": { - "elapsed": 607, - "status": "ok", - "timestamp": 1752355023660, - "user": { - "displayName": "Grégoire", - "userId": "01249298206117462766" - }, - "user_tz": -120 - }, - "id": "-mJxfVLiGfTL", - "outputId": "94c492a5-6077-461e-90a8-45097869c7d6" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Modèle chargé avec succès depuis 'content/bert-model/model'\n", - "Tokenizer chargé avec succès depuis 'content/bert-model/tokenizer'\n", - "Fonction 'preprocess_tweet_for_bert' chargée avec succès depuis 'content/bert-model/preprocessor/preprocessor.pkl'\n", - "\n", - "Test du modèle sur les exemples:\n", - "==================================================\n", - "Résumé: 10 tweets analysés\n", - "Tweets positifs: 5 (50.0%)\n", - "Tweets négatifs: 5 (50.0%)\n", - "Analyse de sentiment terminée! Vérifiez si les résultats sont dans content/bert-model/predictions_results.csv\n" - ] - }, - { - "data": { - "application/vnd.google.colaboratory.intrinsic+json": { - "summary": "{\n \"name\": \"df_results\",\n \"rows\": 10,\n \"fields\": [\n {\n \"column\": \"Tweet\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 10,\n \"samples\": [\n \"@AirParadis let me down. Flight canceled for no valid reason, no solution offered. Zero service. I'm disgusted. \\ud83d\\ude24 #Abandoned\",\n \"PERFECT flight with @AirParadis today. Amazing staff, clean plane, arrived early. What more could you ask for? \\u2b50\\u2b50\\u2b50\\u2b50\\u2b50 #HappyCustomer\",\n \"AVOID @AirParadis! The worst customer service I've ever seen. Rude, incompetent, and they lost my luggage! An absolute disgrace. \\ud83d\\ude20 #AirlineToAvoid\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Sentiment\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 2,\n \"samples\": [\n \"N\\u00e9gatif\",\n \"Positif\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Emoji\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 2,\n \"samples\": [\n \"\\u274c\",\n \"\\ud83d\\udc9a\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Score de confiance\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.10672469983360551,\n \"min\": 0.6343077421188354,\n \"max\": 0.9860053062438965,\n \"num_unique_values\": 10,\n \"samples\": [\n 0.9860053062438965,\n 0.9353708624839783\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Score brut\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.46612804191093277,\n \"min\": 0.01399463601410389,\n \"max\": 0.9763770699501038,\n \"num_unique_values\": 10,\n \"samples\": [\n 0.01399463601410389,\n 0.9353708624839783\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Index\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 3,\n \"min\": 1,\n \"max\": 10,\n \"num_unique_values\": 10,\n \"samples\": [\n 9,\n 2\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}", - "type": "dataframe", - "variable_name": "df_results" - }, - "text/html": [ - "\n", - "
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
TweetSentimentEmojiScore de confianceScore brutIndex
0@AirParadis offers absolutely FLAWLESS service...Positif💚0.9642950.9642951
1PERFECT flight with @AirParadis today. Amazing...Positif💚0.9353710.9353712
2I'm totally a fan of @AirParadis! Every flight...Positif💚0.9763770.9763773
3Extraordinary experience in business class on ...Positif💚0.9690830.9690834
4@AirParadis saved my vacation! After a cancell...Positif💚0.9118610.9118615
5AVOID @AirParadis! The worst customer service ...Négatif❌0.9780220.0219786
6DISASTROUS flight with @AirParadis. 5-hour del...Négatif❌0.9769900.0230107
7How can @AirParadis dare to charge for seats t...Négatif❌0.9771530.0228478
8@AirParadis let me down. Flight canceled for n...Négatif❌0.9860050.0139959
9The food served on @AirParadis was simply ined...Négatif❌0.6343080.36569210
\n", - "
\n", - "
\n", - "\n", - "
\n", - " \n", - "\n", - " \n", - "\n", - " \n", - "
\n", - "\n", - "\n", - "
\n", - " \n", - "\n", - "\n", - "\n", - " \n", - "
\n", - "\n", - "
\n", - " \n", - " \n", - " \n", - "
\n", - "\n", - "
\n", - "
\n" - ], - "text/plain": [ - " Tweet Sentiment Emoji \\\n", - "0 @AirParadis offers absolutely FLAWLESS service... Positif 💚 \n", - "1 PERFECT flight with @AirParadis today. Amazing... Positif 💚 \n", - "2 I'm totally a fan of @AirParadis! Every flight... Positif 💚 \n", - "3 Extraordinary experience in business class on ... Positif 💚 \n", - "4 @AirParadis saved my vacation! After a cancell... Positif 💚 \n", - "5 AVOID @AirParadis! The worst customer service ... Négatif ❌ \n", - "6 DISASTROUS flight with @AirParadis. 5-hour del... Négatif ❌ \n", - "7 How can @AirParadis dare to charge for seats t... Négatif ❌ \n", - "8 @AirParadis let me down. Flight canceled for n... Négatif ❌ \n", - "9 The food served on @AirParadis was simply ined... Négatif ❌ \n", - "\n", - " Score de confiance Score brut Index \n", - "0 0.964295 0.964295 1 \n", - "1 0.935371 0.935371 2 \n", - "2 0.976377 0.976377 3 \n", - "3 0.969083 0.969083 4 \n", - "4 0.911861 0.911861 5 \n", - "5 0.978022 0.021978 6 \n", - "6 0.976990 0.023010 7 \n", - "7 0.977153 0.022847 8 \n", - "8 0.986005 0.013995 9 \n", - "9 0.634308 0.365692 10 " - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - } - ], - "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", - "metadata": { - "id": "hkxoyNm_t6YG" - }, - "source": [ - "# Enregistrement de dépendances" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "HjaFxsm3t9eP" - }, - "outputs": [], - "source": [ - "!pip freeze > requirements_bert.txt" - ] - } - ], - "metadata": { - "accelerator": "GPU", - "colab": { - "gpuType": "T4", - "provenance": [] - }, - "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 -} +{ + "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": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "executionInfo": { + "elapsed": 18367, + "status": "ok", + "timestamp": 1752351535443, + "user": { + "displayName": "Grégoire", + "userId": "01249298206117462766" + }, + "user_tz": -120 + }, + "id": "Xplx_nQRg0rF", + "outputId": "aa1a2afc-5393-4e50-de54-a2cd7e0acc05" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Mounted at /content/drive\n" + ] + }, + { + "data": { + "text/plain": [ + "['content',\n", + " 'requirements_analyse_exploratoire.txt',\n", + " 'p07_01-notebook-analyse-exploratoire.ipynb',\n", + " 'requirements_bert.txt',\n", + " 'requirements_modele_simple.txt',\n", + " 'p07_02-notebook-modele-simple.ipynb',\n", + " 'requirements_modele_avance.txt',\n", + " 'p07_03-notebook-modele-avance.ipynb',\n", + " 'notebook-connexion-mlflow.ipynb',\n", + " 'p07_04-notebook-modele-bert.ipynb']" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "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": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "executionInfo": { + "elapsed": 32608, + "status": "ok", + "timestamp": 1752351568062, + "user": { + "displayName": "Grégoire", + "userId": "01249298206117462766" + }, + "user_tz": -120 + }, + "id": "tXTBgiq7U8gK", + "outputId": "97fd036f-0f54-494b-8e5d-3a261d029091" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m24.7/24.7 MB\u001b[0m \u001b[31m47.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.9/1.9 MB\u001b[0m \u001b[31m61.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m139.9/139.9 kB\u001b[0m \u001b[31m11.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m247.0/247.0 kB\u001b[0m \u001b[31m18.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m13.8/13.8 MB\u001b[0m \u001b[31m85.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m147.8/147.8 kB\u001b[0m \u001b[31m8.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m114.9/114.9 kB\u001b[0m \u001b[31m11.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m85.0/85.0 kB\u001b[0m \u001b[31m7.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m85.2/85.2 kB\u001b[0m \u001b[31m8.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m741.4/741.4 kB\u001b[0m \u001b[31m38.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m203.4/203.4 kB\u001b[0m \u001b[31m13.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m65.6/65.6 kB\u001b[0m \u001b[31m6.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m119.4/119.4 kB\u001b[0m \u001b[31m10.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m201.6/201.6 kB\u001b[0m \u001b[31m17.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25h" + ] + } + ], + "source": [ + "!pip install mlflow boto3 dotenv --quiet" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "collapsed": true, + "executionInfo": { + "elapsed": 6287, + "status": "ok", + "timestamp": 1752351574355, + "user": { + "displayName": "Grégoire", + "userId": "01249298206117462766" + }, + "user_tz": -120 + }, + "id": "W50jr7rUT76d", + "outputId": "437c63ff-9e8f-4251-a053-0216012499ba" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "PyTorch version: 2.6.0+cu124\n", + "CUDA disponible: True\n", + "Appareil CUDA: Tesla T4\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": null, + "metadata": { + "id": "MMB0gwbBT76d" + }, + "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": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "executionInfo": { + "elapsed": 2633, + "status": "ok", + "timestamp": 1752351604138, + "user": { + "displayName": "Grégoire", + "userId": "01249298206117462766" + }, + "user_tz": -120 + }, + "id": "OhgBsQDCUq4A", + "outputId": "f63ccc04-79c1-40fc-8c92-5ed975f67336" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "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(dotenv_path='../.env')\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": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "executionInfo": { + "elapsed": 26, + "status": "ok", + "timestamp": 1752351604172, + "user": { + "displayName": "Grégoire", + "userId": "01249298206117462766" + }, + "user_tz": -120 + }, + "id": "Q5ACc3OUT76d", + "outputId": "940ac231-d2b1-4c8d-940c-3cc323079d56" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 60 µs, sys: 9 µs, total: 69 µs\n", + "Wall time: 259 µs\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": null, + "metadata": { + "id": "_D2V2EgHT76e" + }, + "outputs": [], + "source": [ + "# Définir le chemin vers le fichier CSV\n", + "csv_file_path = './content/data/training.1600000.processed.noemoticon.csv'" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Sv9nlaIq4BZo" + }, + "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" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "AFdxLghUT76e" + }, + "source": [ + "## Prétraitement des tweets" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "NmOIhsWET76e" + }, + "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": null, + "metadata": { + "id": "ap7LohJzT76e" + }, + "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": null, + "metadata": { + "id": "lgWXn2SKT76e" + }, + "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", + "execution_count": null, + "metadata": { + "id": "EsK_IQRlbu3C" + }, + "outputs": [], + "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" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "3gDy-63XT76f" + }, + "source": [ + "## Fonction d'évaluation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "g2DZWDoNT76f" + }, + "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": null, + "metadata": { + "id": "nsUgEh2pT76f" + }, + "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": null, + "metadata": { + "id": "Pf-tDY3OT76g" + }, + "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": null, + "metadata": { + "id": "UKzSUOlqT76g" + }, + "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": null, + "metadata": { + "id": "3OppER9A_iPX" + }, + "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", + "execution_count": null, + "metadata": { + "id": "1PHu82H-YNtN" + }, + "outputs": [], + "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" + ] + }, + { + "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": null, + "metadata": { + "id": "kVj7LqF-bZGq" + }, + "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/", + "height": 1000, + "referenced_widgets": [ + "f4c7033b02a2493085d1ac0cfac9cccf", + "3d5883b5f0bb4098bda8599622824948", + "3502027e67de4c36b46688597314fb2e", + "4db20c46a69043fdb7d29ccaa5f03e12", + "eb20692f8413420f9ca58df09b2d5feb", + "e47e179020f54e5b84017148d09480e9", + "e23da7120310469798127dfbf039919e", + "44325b25ca5f41d2a5ff31e6cdcf179f", + "b6aead1ab91f4ad1a903009b95c39036", + "2eadd184b69d41a696fed5c7bc5b1843", + "a52850e715624d3986a292dca1e6fa62", + "2bd37c30fbe74e3eb9f7d0a660b94772", + "5a904bdfc6e3488b963f9952ed7936c9", + "63840ffc38eb4932b39d00edfcbe5f15", + "a994789a34be47ffbafa558d1a15821d", + "9acf152a8b914b9faadc6a6a11f7300b", + "d20225cccfc64b0ba25c555cf5a3efba", + "0e164b5d9d17436d8cb259187680274c", + "716f683828664694823b072c3811c7c5", + "d52088f4990f492bb35d1f54c419637d", + "cf42f21334ba4e7e9c83edb6d2f95871", + "cc9639e12a1448a580b3794087a09ad1", + "502800fba0c04ec6b54b83a1e9fbfc11", + "b5cc33676b3648fd9f341bbdb5a31de5", + "0793c0a09e3b4ec6832080435b507ffa", + "771269ab33bf4b68b5a4acd4cada3de9", + "84cd157aa0884ec5b6629011f678339f", + "12e78de1f0eb45e59ce29473694e6895", + "d7d394ea510749f7b1820274c0553db2", + "10faa872bd504fa49fc1cf05c58ce391", + "b9c7bef298474c949940154207dea96a", + "d7fd75e7490946f29f20b0b4ed63ef3c", + "adb66ec7c3fd46089ff61496e4db9e02", + "e333785179dd45d89ae039fd7e6361da", + "bac5e480209c4cd09cb3b891dd7683f3", + "48b5dc12ca9241a7955b798e2b204792", + "8cca18086a53470894d018a04a8bc2be", + "340dc17022ec42e9ba4e61726711566a", + "58026471ff914aaeba005c52aab2db47", + "4db07c2a0409413384cfe1ca0dc08ff4", + "c764fa8136b24134a8b92649d91eb854", + "f828ba130d4b42a580f10d8879f1a1fd", + "f35f52483ef240c3adab0cce32a7b818", + "360c34394459499ea4c3b4ba3367ba39", + "f05015da03d84663852c114110489c96", + "a44c09d432ba40a589247626a09cfe34", + "89896a47fccc4fb4915014b4f0b7048b", + "9a86ae6790d84aa1866eaba2d1df3216", + "214fc3b57b6c4697a1632038fd7a40fa", + "a60693496b574f659ecd8be5feb863fd", + "e50178d16de24d03bc38804424557a05", + "dee2c3aa487545cba7e32834d11ea883", + "7c173af0d57c497ca04a7fb5320a5f8d", + "198afd4b3f094ffc8354b69d987334b4", + "326ff209eb5741e793ccc3696e65b58c" + ] + }, + "executionInfo": { + "elapsed": 3417524, + "status": "ok", + "timestamp": 1752355021870, + "user": { + "displayName": "Grégoire", + "userId": "01249298206117462766" + }, + "user_tz": -120 + }, + "id": "8Y3ycjLlT76h", + "outputId": "c1430b51-ecc5-47f3-8b5b-00e5cebfbfee" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "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" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "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" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "f4c7033b02a2493085d1ac0cfac9cccf", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "tokenizer_config.json: 0%| | 0.00/48.0 [00:00" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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+18x/gvxREdHM3z4cBITE/nwww9ZtmwZ69evz3XJWE5ODnFxccTGxvLee+/x66+/8v7771OlShWeeuqp/3Qc/2ncuHE88cQTNG7cmGeeeYa9e/dyzz334OvrS0hIyFWXve+++9iyZQv9+vUjNDSU48ePs2DBAg4ePEhoaCgjR46kX79+eHh42JMTgYGB1x3jpEmTOHPmDH369CE9PZ0PP/yQ2267jb/++su+vi1bttCkSROCg4MZOHAgpUuX5ptvvqFDhw589913dOzYMdc6e/fujb+/P4MHDyYtLY02bdqwc+dOvvrqKz744AP8/PwA8Pf3z5f4FixYwN69e4mPjycoKIgtW7YwduxYtmzZwp9//mkfQD355JPMmDGDvn37Eh4ezqlTp1i6dCnbtm2jQYMGAPz222+0adOGqKgoXnvtNRwcHJgwYQK33XYbf/zxBzExMdd9jK9m2rRpnDlzhieeeAKLxcI777zDvffey969e+3/jzZt2kSzZs1wcnLi8ccfJzQ0lD179vDDDz/w5ptvArB69WqWL19Oly5dqFChAvv37+fTTz+lZcuWbN261f5/d8iQIQwfPpzHHnuMmJgYUlNTWbNmDevWraN169ZA3t/vhIQEbr31VrKzs+39xo4di5ubW74eIxGRkmDLli00a9YMLy8vXnzxRZycnPjss89o2bIlS5YsITY2Fsjb3/FrjSHyw19//cUdd9yBv78/Q4YMITs7m9deey1PY5Fr7cMTTzzB0aNHWbBgAZMnT77hGHft2kXnzp158skn6d69OxMmTOCBBx5g3rx59mN17tw5WrRowZEjR3jiiSeoWLEiy5cvZ9CgQRw7doyRI0fmWueECRNIT0/n8ccfx8XFhY4dO3LmzBkGDx7M448/TrNmzQBo3LhxvsS3d+9eZs2axQMPPEBYWBiJiYl89tlntGjRgq1bt1K+fHnAVg7j6aef5v7776d///6kp6ezadMmVq5caR9zJyYmcsstt2CxWOjbty/+/v78/PPPPProo6SmpvLMM8/c8LG+nN27d3P//ffz6KOP0r17d8aPH0+PHj2Iioqidu3aAJw9e5ZmzZqxbds2evbsSYMGDTh58iRz5szh8OHD+Pn5kZqayhdffMGDDz5Ir169OHPmDOPGjSMuLo5Vq1bZL5nMy3eIvL7fhmHQvn17li5dypNPPkmtWrX4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y/79rFWrFlarlUOHDgHX/jt+o2OI8+fPX/LZdrV4z58/f8OfAdfah/xStWrVS06EVq9eHcA+jti1axfz5s275DOvVatWwH8b45w9ezbX8Txx4sR1x2e1Wvnggw+oVq1ars/3TZs25fp8f+mll/Dw8CAmJoZq1arRp0+fXPUwT5w4QXJyMmPHjr1kX+Pj43Pt679/D86fP5/nff6nf49x4NLx9p49e/L0XeHLL7+kXr169hpk/v7+zJ07N9cxyMt3iLy+3wcOHKBcuXJ4eHjkWl5jHLkZVFNKSqyYmBj73feu5J9nZy6wWq0EBAQwderUyy7zX88eBQQEsGHDBubPn8/PP//Mzz//zIQJE+jWrRtffvnlf1r3BTc6i6d///65YmjRokWei4lfr2eeeYZ27doxa9Ys5s+fz6uvvsrw4cP57bffqF+//k3Z5uVcmBn0/PPPX/HsZ9WqVXM9v56aQvfeey9LliyxP+/evXuei4Ne0KlTJ5YvX84LL7xAZGQkHh4eWK1W7rzzzlwzmzp16kSzZs34/vvv+eWXX3j33Xd5++23mTlzJm3atLH3fffdd694S2cPDw+WLVvGrbfemqt93759hIaGYrFYMAzjkuWulLS60u/i5dZxNf369WPChAk888wzNGrUCG9vbywWC126dMl1DJo3b86ePXuYPXs2v/zyC1988QUffPABY8aM4bHHHruh91tERArOtf6Ow42NIaZPn25PTlxwvZ9F+bkPBcVqtdK6dWtefPHFy75+IUl0wfWMcd577z2GDh1qf16pUqXrLjb/1ltv8eqrr9KzZ09ef/11fH19cXBw4Jlnnsn1+V6rVi127NjBjz/+yLx58/juu+/45JNPGDx4MEOHDrX3ffjhh69YF6levXqAbYbeP02YMIEePXpc8UqHmz3GmTJlCj169KBDhw688MILBAQE4OjoyPDhw+0nwSFv3yGu9/0WKQhKSolcpypVqvDrr7/SpEmTq34wV6lSBYDNmzdf95dYZ2dn2rVrR7t27bBarfTu3ZvPPvuMV1999bLrqlSpEgA7duyw30Xwgh07dthf/69efPFFHn74Yfvzf152daWYdu3alSumrKws9u3bR0RExDW3V6VKFZ577jmee+45du3aRWRkJO+//z5TpkwBrn4ZZF5dbur8zp077YVQL8zqcnJysp9FuhFXivX999/PdcbsQsHOvMZ3+vRpFi5cyNChQxk8ePBVlwPbQKt379707t2b48eP06BBA958803atGlj/5318vK66r5GRERcMjU8KCgIsP1OXO4y0H/P1surC8f/cndE/KcZM2bQvXt33n//fXtbenr6Ze8S4+vrS3x8PPHx8Zw9e5bmzZszZMgQHnvsset6vytVqnTZ47xjx45r7ZaIiPyDv78/7u7ul/37uX37dhwcHHLdIOVqf8cvuNYY4t/i4uLyXDrhwp1g/8tnwLX2IT/GOBdm//5zXTt37gSwjyOqVKnC2bNnb8oYp1u3bjRt2tT+/N/j5rzEN2PGDG699VbGjRuXa9nk5GT7jWMuKF26NJ07d6Zz585kZmZy77338uabbzJo0CD8/f3x9PQkJyfnmvv679+DC0XJL4x7k5OTc91A6EbHOGA7/nkZ41SuXJmZM2fmOlYXZnD/07W+Q+T1/a5UqRILFy7k7NmzuWZLaYwjN4Mu3xO5Tp06dSInJ4fXX3/9kteys7PtX4LvuOMOPD09GT58OOnp6bn6Xe0MyalTp3I9d3BwsJ+5ycjIuOwyDRs2JCAggDFjxuTq8/PPP7Nt2zbuuuuuPO3btYSHh9OqVSv7Iyoq6op9GzZsiL+/P2PGjCEzM9PePnHixGveTvbcuXOXHLMqVarg6emZa/9Kly79n29NO2vWrFw1oVatWsXKlStp06YNYDvr1LJlSz777DOOHTt2yfL/nop+JaVLlwa4JN6oqKhcxzQ8PPy64rtwFu7fv1P/vltOTk7OJZcGBgQEUL58efsxjYqKokqVKrz33nucPXv2ivtapkyZXDG3atXKXsusSpUqbN++Pddx2bhxY64p9NfD39+f5s2bM378eA4ePJjrtX/us6Oj4yXH4OOPP77k7OW//395eHhQtWpV+zG4nve7bdu2/Pnnn6xatSrX61eaRSkiIpfn6OjIHXfcwezZs3PNpElMTGTatGk0bdoULy8v4Np/x/M6hvi3cuXKXfLZdrV44+LimDVrVq7Ppm3btjF//vxr7u+19gGuPG64HkePHuX777+3P09NTWXSpElERkbaTyZ16tSJFStWXDbu5ORksrOzr7mdK8VauXLlXMezSZMm1x3f5T7fv/3220vqef77mDo7OxMeHo5hGGRlZeHo6Mh9993Hd999d9kk0D8/3//9e3Bh5tSFk3f/rLOVlpb2n65kuO+++9i4cWOu43DBhf2+3Fhv5cqVrFixIlf/vHyHyOv73bZtW7Kzs/n000/tr+fk5PDxxx9f9z6KXItmSolcpxYtWvDEE08wfPhwNmzYwB133IGTkxO7du3i22+/5cMPP+T+++/Hy8uLDz74gMcee4zo6Gi6du1KmTJl2LhxI+fOnbviB9hjjz1GUlISt912GxUqVODAgQN8/PHHREZGUqtWrcsu4+TkxNtvv018fDwtWrTgwQcfJDExkQ8//JDQ0FCeffbZm3lIrhjTG2+8wRNPPMFtt91G586d2bdvHxMmTLhmTamdO3dy++2306lTJ8LDwylVqhTff/89iYmJdOnSxd4vKiqKTz/9lDfeeIOqVasSEBBwyUyxa6latSpNmzblqaeeIiMjg5EjR1K2bNlc05pHjx5N06ZNqVu3Lr169aJy5cokJiayYsUKDh8+zMaNG6+5nQsJvJdffpkuXbrg5OREu3bt7AO5G43Py8vLXi8jKyuL4OBgfvnlF/bt25drPWfOnKFChQrcf//9RERE4OHhwa+//srq1avts4scHBz44osvaNOmDbVr1yY+Pp7g4GCOHDnCokWL8PLy4ocffrhqvD179mTEiBHExcXx6KOPcvz4ccaMGUPt2rXtRWqv10cffUTTpk1p0KABjz/+OGFhYezfv5+5c+eyYcMGAO6++24mT56Mt7c34eHhrFixgl9//ZWyZcvmWld4eDgtW7YkKioKX19f1qxZw4wZM+jbt6+9T17f7xdffJHJkydz55130r9/f0qXLs3YsWOpVKnSTakNIiJS1I0fP/6ydTL79+/PG2+8wYIFC2jatCm9e/emVKlSfPbZZ2RkZPDOO+/Y+17r73hexxD/1dChQ5k3bx7NmjWjd+/eZGdn8/HHH1O7du1rfgbk5bPowrjh6aefJi4uDkdHx+uOv3r16jz66KOsXr2awMBAxo8fT2JiIhMmTLD3eeGFF5gzZw533303PXr0ICoqirS0NP766y9mzJjB/v37L5mR9G9VqlTBx8eHMWPG4OnpSenSpYmNjb1m/am8xHf33XczbNgw4uPjady4MX/99RdTp069ZCx5xx13EBQURJMmTQgMDGTbtm2MGjWKu+66C09PTwD+7//+j0WLFhEbG0uvXr0IDw8nKSmJdevW8euvv161VuqFbVSsWJFHH32UF154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RERERBxMfW+erqKUCZbuSefnnWk4Wy2Mv7FdvW1YJiIiIiIiIiLV54+apycczOTAHzZPD6BFYy+HqEWoKFXLSspsvDRnBwDDe0bRMtjH5IhEREREREREpC46W/P0jLwi1h3MJCHxxKnm6dlnNE8P9HIlNsqf+3pHc1nzQNPiV1Gqli3YlsK+tDwCvFx5/JpWZocjIiIiIiIiIvVIkLcbAzqGMqBjKGA0T99w6IRRqDrVPP14fjELt6dyS9empsaqolQtu7FzKFaLhTK7HT9PF7PDEREREREREZF6zNvNmctbN+by1mc2T4+PDjA1NhWlapnFYuGGzqFmhyEiIiIiIiIiDdDvm6ebzWp2ACIiIiIiIiIi0vA4RFHq3XffJSoqCnd3d+Lj40lISDjv+ZMnT6ZNmzZ4eHgQERHBk08+SWFhYZVeU0REREREREREao/pRakZM2YwduxYJkyYwIYNG+jSpQv9+/cnLS3trOdPmzaNcePGMWHCBHbu3Mknn3zCjBkzeOaZZy75NUVEREREREREpHaZXpSaNGkSDz30EPfddx/t27fn/fffx9PTk6lTp571/FWrVtG7d2/uvvtuoqKiuO6667jrrrsqzYS62NcUEREREREREZHaZWpRqri4mPXr19OvX7/yY1arlX79+rF69eqzPqdXr16sX7++vAh14MAB5s2bx8CBAy/5NYuKisjJyal0ExERERERERGRmmPq7nsZGRmUlZUREhJS6XhISAi7du0663PuvvtuMjIy6NOnD3a7ndLSUh555JHy5XuX8poTJ07kxRdfrIZ3JCIiIiIiIiIiF8L05XsXa8mSJbzyyiu89957bNiwgZkzZzJ37lxeeumlS37Np59+muzs7PJbcnJyNUYsIiIiIiIiIiL/y9SZUkFBQTg5OZGamlrpeGpqKk2aNDnrc8aPH8+wYcN48MEHAejUqRP5+fk8/PDDPPvss5f0mm5ubri5uVXDOxIRERERERERkQth6kwpV1dXunfvzuLFi8uP2Ww2Fi9eTM+ePc/6nIKCAqzWymE7OTkBYLfbL+k1RURERERERESkdpk6Uwpg7NixjBgxgtjYWOLi4pg8eTL5+fncd999AAwfPpzw8HAmTpwIwKBBg5g0aRJdu3YlPj6effv2MX78eAYNGlRenPqj1xQREREREREREXOZXpQaOnQo6enpPP/8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0WaH5HBsNhtFRUVmh1HrrrnmGrZu3cqmTZvKb7Gxsdxzzz1s2rQJJycns0M0VV5eHvv37yc0NNTsUEzRu3fvM7ZD37NnD82aNTMpIsfw6aefEhwczA033GB2KHKRlC/J+Shf+mPKl5QvnY3yJeVLZ+Mo+ZKW79WisWPHMmLECGJjY4mLi2Py5Mnk5+dz3333mR2aKfLy8ipV6xMTE9m0aRMBAQFERkaaGFntGzVqFNOmTeOHH37Ax8envG+Gn58fHh4eJkdX+55++mmuv/56IiMjyc3NZdq0aSxZsoSFCxeaHVqt8/HxOaNXhpeXF4GBgQ2yh8af//xnBg0aRLNmzTh69CgTJkzAycmJu+66y+zQTPHkk0/Sq1cvXnnlFe644w4SEhL48MMP+fDDD80OzTQ2m41PP/2UESNG4OysNKcuUr5UmfKlCsqXKlO+VEH5UmXKlypTvnQmh8qXTNnzrwH797//bY+MjLS7urra4+Li7L/99pvZIZnm119/tQNn3EaMGGF2aLXubOMA2D/99FOzQzPF/fffb2/WrJnd1dXV3rhxY/s111xjX7RokdlhOYyGvMXx0KFD7aGhoXZXV1d7eHi4fejQofZ9+/aZHZapfvzxR3vHjh3tbm5u9rZt29o//PBDs0My1cKFC+2Afffu3WaHIlWgfKmC8qUKypcqU750fsqXlC/9nvKlyhwpX7LY7XZ77ZXARERERERERERE1FNKRERERERERERMoKKUiIiIiIiIiIjUOhWlRERERERERESk1qkoJSIiIiIiIiIitU5FKRERERERERERqXUqSomIiIiIiIiISK1TUUpERERERERERGqdilIiIiIiIiIiIlLrVJQSEakhFouF77//3uwwRERERByW8iWRhk1FKRGpl0aOHInFYjnjNmDAALNDExEREXEIypdExGzOZgcgIlJTBgwYwKefflrpmJubm0nRiIiIiDge5UsiYibNlBKResvNzY0mTZpUuvn7+wPGVPEpU6Zw/fXX4+HhQfPmzfn2228rPX/r1q1cffXVeHh4EBgYyMMPP0xeXl6lc6ZOnUqHDh1wc3MjNDSU0aNHV3o8IyODW265BU9PT1q1asXs2bNr9k2LiIiIXATlSyJiJhWlRKTBGj9+PEOGDGHz5s3cc8893HnnnezcuROA/Px8+vfvj7+/P2vXruWbb77h559/rpRETZkyhVGjRvHwww+zdetWZs+eTcuWLStd48UXX+SOO+5gy5YtDBw4kHvuuYfMzMxafZ8iIiIil0r5kojUKLuISD00YsQIu5OTk93Ly6vS7eWXX7bb7XY7YH/kkUcqPSc+Pt7+6KOP2u12u/3DDz+0+/v72/Py8sofnzt3rt1qtdpTUlLsdrvdHhYWZn/22WfPGQNgf+6558rv5+Xl2QH7/Pnzq+19ioiIiFwq5UsiYjb1lBKReuuqq65iypQplY4FBASU/7lnz56VHuvZsyebNm0CYOfOnXTp0gUvL6/yx3v37o3NZmP37t1YLBaOHj3KNddcc94YOnfuXP5nLy8vfH19SUtLu9S3JCIiIlKtlC+JiJlUlBKResvLy+uM6eHVxcPD44LOc3FxqXTfYrFgs9lqIiQRERGRi6Z8SUTMpJ5SItJg/fbbb2fcb9euHQDt2rVj8+bN5Ofnlz++cuVKrFYrbdq0wcfHh6ioKBYvXlyrMYuIiIjUJuVLIlKTNFNKROqtoqIiUlJSKh1zdnYmKCgIgG+++YbY2Fj69OnDl19+SUJCAp988gkA99xzDxMmTGDEiBG88MILpKenM2bMGIYNG0ZISAgAL7zwAo888gjBwcFcf/315ObmsnLlSsaMGVO7b1RERETkEilfEhEzqSglIvXWggULCA0NrXSsTZs27Nq1CzB2epk+fTqPPfYYoaGhfPXVV7Rv3x4AT09PFi5cyOOPP06PHj3w9PRkyJAhTJo0qfy1RowYQWFhIW+++SZ//vOfCQoK4rbbbqu9NygiIiJSRcqXRMRMFrvdbjc7CBGR2maxWJg1axaDBw82OxQRERERh6R8SURqmnpKiYiIiIiIiIhIrVNRSkREREREREREap2W74mIiIiIiIiISK3TTCkREREREREREal1KkqJiIiIiIiIiEitU1FKRERERERERERqnYpSIiIiIiIiIiJS61SUEhERERERERGRWqeilIiIiIiIiIiI1DoVpUREREREREREpNapKCUiIiIiIiIiIrVORSkREREREREREal1/w8MVwdoalikxwAAAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "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": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "executionInfo": { + "elapsed": 1102, + "status": "ok", + "timestamp": 1752355022988, + "user": { + "displayName": "Grégoire", + "userId": "01249298206117462766" + }, + "user_tz": -120 + }, + "id": "Tkb9WDu-T76h", + "outputId": "0f07d599-7592-4f49-a40f-eb8b22ec0359" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Modèle chargé avec succès depuis 'content/bert-model/model'\n", + "Tokenizer chargé avec succès depuis 'content/bert-model/tokenizer'\n", + "Fonction 'preprocess_tweet_for_bert' chargée avec succès depuis 'content/bert-model/preprocessor/preprocessor.pkl'\n", + "\n", + "Test du modèle sur les exemples:\n", + "==================================================\n", + "Résumé: 15 tweets analysés\n", + "Tweets positifs: 7 (46.7%)\n", + "Tweets négatifs: 8 (53.3%)\n", + "Analyse de sentiment terminée! Résultats disponibles dans content/bert-model/predictions_results.csv\n" + ] + } + ], + "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": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 519 + }, + "executionInfo": { + "elapsed": 69, + "status": "ok", + "timestamp": 1752355023061, + "user": { + "displayName": "Grégoire", + "userId": "01249298206117462766" + }, + "user_tz": -120 + }, + "id": "XIHAcDH3E1rH", + "outputId": "9982e8d8-6530-4b66-9a4c-1ac8f158b260" + }, + "outputs": [ + { + "data": { + "application/vnd.google.colaboratory.intrinsic+json": { + "summary": "{\n \"name\": \"df_results\",\n \"rows\": 15,\n \"fields\": [\n {\n \"column\": \"Tweet\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 15,\n \"samples\": [\n \"The quality of service with @AirParadis has declined. I expected better. It left a sour taste. #disappointed\",\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 \"I absolutely love flying with @AirParadis! Their service is exceptional and the staff is always so friendly :) #bestairline #travel\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Sentiment\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 2,\n \"samples\": [\n \"N\\u00e9gatif\",\n \"Positif\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Emoji\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 2,\n \"samples\": [\n \"\\u274c\",\n \"\\ud83d\\udc9a\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Score de confiance\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.14419873334810537,\n \"min\": 0.5295969843864441,\n \"max\": 0.9885379076004028,\n \"num_unique_values\": 15,\n \"samples\": [\n 0.9871659874916077,\n 0.5295969843864441\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Score brut\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.4223161774401945,\n \"min\": 0.011462084017693996,\n \"max\": 0.9652082920074463,\n \"num_unique_values\": 15,\n \"samples\": [\n 0.012833991087973118,\n 0.4704030156135559\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Index\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 4,\n \"min\": 1,\n \"max\": 15,\n \"num_unique_values\": 15,\n \"samples\": [\n 10,\n 12\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}", + "type": "dataframe", + "variable_name": "df_results" + }, + "text/html": [ + "\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
TweetSentimentEmojiScore de confianceScore brutIndex
0I absolutely love flying with @AirParadis! The...Positif💚0.9415390.9415391
1I just landed after an amazing flight... The c...Positif💚0.9495520.9495522
2Wow! You have to try @AirParadis. Best. Flight...Positif💚0.9601810.9601813
3It was my first time flying business class, an...Négatif❌0.9500300.0499704
4What an incredible flight experience with @Air...Positif💚0.9652080.9652085
5Honestly, my last flight with @AirParadis was ...Négatif❌0.9874600.0125396
6I had high hopes for @AirParadis, but the seat...Négatif❌0.9844690.0155317
7So frustrated with @AirParadis this time. The ...Négatif❌0.9756840.0243168
8The flight I booked with @AirParadis was cance...Négatif❌0.9885380.0114629
9The quality of service with @AirParadis has de...Négatif❌0.9871660.01283410
10To be honest, @AirParadis has significantly im...Positif💚0.9086420.90864211
11We were really looking forward to our trip, bu...Négatif❌0.5295970.47040312
12The flight with @AirParadis had great moments,...Négatif❌0.6826780.31732213
13Overall, @AirParadis has good service, but I h...Positif💚0.7129990.71299914
14While our flight was pleasant, the lack of com...Positif💚0.7426260.74262615
\n", + "
\n", + "
\n", + "\n", + "
\n", + " \n", + "\n", + " \n", + "\n", + " \n", + "
\n", + "\n", + "\n", + "
\n", + " \n", + "\n", + "\n", + "\n", + " \n", + "
\n", + "\n", + "
\n", + " \n", + " \n", + " \n", + "
\n", + "\n", + "
\n", + "
\n" + ], + "text/plain": [ + " Tweet Sentiment Emoji \\\n", + "0 I absolutely love flying with @AirParadis! The... Positif 💚 \n", + "1 I just landed after an amazing flight... The c... Positif 💚 \n", + "2 Wow! You have to try @AirParadis. Best. Flight... Positif 💚 \n", + "3 It was my first time flying business class, an... Négatif ❌ \n", + "4 What an incredible flight experience with @Air... Positif 💚 \n", + "5 Honestly, my last flight with @AirParadis was ... Négatif ❌ \n", + "6 I had high hopes for @AirParadis, but the seat... Négatif ❌ \n", + "7 So frustrated with @AirParadis this time. The ... Négatif ❌ \n", + "8 The flight I booked with @AirParadis was cance... Négatif ❌ \n", + "9 The quality of service with @AirParadis has de... Négatif ❌ \n", + "10 To be honest, @AirParadis has significantly im... Positif 💚 \n", + "11 We were really looking forward to our trip, bu... Négatif ❌ \n", + "12 The flight with @AirParadis had great moments,... Négatif ❌ \n", + "13 Overall, @AirParadis has good service, but I h... Positif 💚 \n", + "14 While our flight was pleasant, the lack of com... Positif 💚 \n", + "\n", + " Score de confiance Score brut Index \n", + "0 0.941539 0.941539 1 \n", + "1 0.949552 0.949552 2 \n", + "2 0.960181 0.960181 3 \n", + "3 0.950030 0.049970 4 \n", + "4 0.965208 0.965208 5 \n", + "5 0.987460 0.012539 6 \n", + "6 0.984469 0.015531 7 \n", + "7 0.975684 0.024316 8 \n", + "8 0.988538 0.011462 9 \n", + "9 0.987166 0.012834 10 \n", + "10 0.908642 0.908642 11 \n", + "11 0.529597 0.470403 12 \n", + "12 0.682678 0.317322 13 \n", + "13 0.712999 0.712999 14 \n", + "14 0.742626 0.742626 15 " + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_results" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 536 + }, + "executionInfo": { + "elapsed": 607, + "status": "ok", + "timestamp": 1752355023660, + "user": { + "displayName": "Grégoire", + "userId": "01249298206117462766" + }, + "user_tz": -120 + }, + "id": "-mJxfVLiGfTL", + "outputId": "94c492a5-6077-461e-90a8-45097869c7d6" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Modèle chargé avec succès depuis 'content/bert-model/model'\n", + "Tokenizer chargé avec succès depuis 'content/bert-model/tokenizer'\n", + "Fonction 'preprocess_tweet_for_bert' chargée avec succès depuis 'content/bert-model/preprocessor/preprocessor.pkl'\n", + "\n", + "Test du modèle sur les exemples:\n", + "==================================================\n", + "Résumé: 10 tweets analysés\n", + "Tweets positifs: 5 (50.0%)\n", + "Tweets négatifs: 5 (50.0%)\n", + "Analyse de sentiment terminée! Vérifiez si les résultats sont dans content/bert-model/predictions_results.csv\n" + ] + }, + { + "data": { + "application/vnd.google.colaboratory.intrinsic+json": { + "summary": "{\n \"name\": \"df_results\",\n \"rows\": 10,\n \"fields\": [\n {\n \"column\": \"Tweet\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 10,\n \"samples\": [\n \"@AirParadis let me down. Flight canceled for no valid reason, no solution offered. Zero service. I'm disgusted. \\ud83d\\ude24 #Abandoned\",\n \"PERFECT flight with @AirParadis today. Amazing staff, clean plane, arrived early. What more could you ask for? \\u2b50\\u2b50\\u2b50\\u2b50\\u2b50 #HappyCustomer\",\n \"AVOID @AirParadis! The worst customer service I've ever seen. Rude, incompetent, and they lost my luggage! An absolute disgrace. \\ud83d\\ude20 #AirlineToAvoid\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Sentiment\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 2,\n \"samples\": [\n \"N\\u00e9gatif\",\n \"Positif\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Emoji\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 2,\n \"samples\": [\n \"\\u274c\",\n \"\\ud83d\\udc9a\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Score de confiance\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.10672469983360551,\n \"min\": 0.6343077421188354,\n \"max\": 0.9860053062438965,\n \"num_unique_values\": 10,\n \"samples\": [\n 0.9860053062438965,\n 0.9353708624839783\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Score brut\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.46612804191093277,\n \"min\": 0.01399463601410389,\n \"max\": 0.9763770699501038,\n \"num_unique_values\": 10,\n \"samples\": [\n 0.01399463601410389,\n 0.9353708624839783\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Index\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 3,\n \"min\": 1,\n \"max\": 10,\n \"num_unique_values\": 10,\n \"samples\": [\n 9,\n 2\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}", + "type": "dataframe", + "variable_name": "df_results" + }, + "text/html": [ + "\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
TweetSentimentEmojiScore de confianceScore brutIndex
0@AirParadis offers absolutely FLAWLESS service...Positif💚0.9642950.9642951
1PERFECT flight with @AirParadis today. Amazing...Positif💚0.9353710.9353712
2I'm totally a fan of @AirParadis! Every flight...Positif💚0.9763770.9763773
3Extraordinary experience in business class on ...Positif💚0.9690830.9690834
4@AirParadis saved my vacation! After a cancell...Positif💚0.9118610.9118615
5AVOID @AirParadis! The worst customer service ...Négatif❌0.9780220.0219786
6DISASTROUS flight with @AirParadis. 5-hour del...Négatif❌0.9769900.0230107
7How can @AirParadis dare to charge for seats t...Négatif❌0.9771530.0228478
8@AirParadis let me down. Flight canceled for n...Négatif❌0.9860050.0139959
9The food served on @AirParadis was simply ined...Négatif❌0.6343080.36569210
\n", + "
\n", + "
\n", + "\n", + "
\n", + " \n", + "\n", + " \n", + "\n", + " \n", + "
\n", + "\n", + "\n", + "
\n", + " \n", + "\n", + "\n", + "\n", + " \n", + "
\n", + "\n", + "
\n", + " \n", + " \n", + " \n", + "
\n", + "\n", + "
\n", + "
\n" + ], + "text/plain": [ + " Tweet Sentiment Emoji \\\n", + "0 @AirParadis offers absolutely FLAWLESS service... Positif 💚 \n", + "1 PERFECT flight with @AirParadis today. Amazing... Positif 💚 \n", + "2 I'm totally a fan of @AirParadis! Every flight... Positif 💚 \n", + "3 Extraordinary experience in business class on ... Positif 💚 \n", + "4 @AirParadis saved my vacation! After a cancell... Positif 💚 \n", + "5 AVOID @AirParadis! The worst customer service ... Négatif ❌ \n", + "6 DISASTROUS flight with @AirParadis. 5-hour del... Négatif ❌ \n", + "7 How can @AirParadis dare to charge for seats t... Négatif ❌ \n", + "8 @AirParadis let me down. Flight canceled for n... Négatif ❌ \n", + "9 The food served on @AirParadis was simply ined... Négatif ❌ \n", + "\n", + " Score de confiance Score brut Index \n", + "0 0.964295 0.964295 1 \n", + "1 0.935371 0.935371 2 \n", + "2 0.976377 0.976377 3 \n", + "3 0.969083 0.969083 4 \n", + "4 0.911861 0.911861 5 \n", + "5 0.978022 0.021978 6 \n", + "6 0.976990 0.023010 7 \n", + "7 0.977153 0.022847 8 \n", + "8 0.986005 0.013995 9 \n", + "9 0.634308 0.365692 10 " + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "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", + "metadata": { + "id": "hkxoyNm_t6YG" + }, + "source": [ + "# Enregistrement de dépendances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "HjaFxsm3t9eP" + }, + "outputs": [], + "source": [ + "!pip freeze > requirements_bert.txt" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "gpuType": "T4", + "provenance": [] + }, + "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 +}