Qu’est ce qu’un serveur MCP (Model Context Protocole?)
Un serveur MCP est un webservice qui fournit de la data, cette data va servir de contexte pour l’IA générative. A la différence du RAG (Retrieval Augmented Generation), il n’est pas besoin de base vectorielle pour stocker des informations.
Le workflow est le suivant, le client IA (Claude Desktop) va requêter le serveur MCP pour retirer les informations dont il a besoin pour envoyer au LLM.(chatGPT par exemple).
Cette technologie inventée par Anthropic (à l’origine de Claude) est devenue très populaire. Je pense que c’est dû au fait qu’il n’est besoin que de connaissances existantes (Webservice) par les développeurs pour pouvoir taquiner les IA afin d’éviter les hallucination.
Mais cela va au delà des hallucination, un serveur MCP est un webservice à destination non du programmeur mais de l’IA générative. Je pense que c’est une bonne comparaison, l’IA (ou plutôt l’agent IA) va pouvoir décider en autonomie de requêter un serveur MCP pour chercher les information dont il a besoin?
Donc un serveur MCP est un webservice pour agent IA, au même titre qu’un webservice est un …. webservice pour un programmeur. Dans notre cas on va utiliser Claude Desktop comme « agent IA » ou plutôt comme Client IA.
Mise en place du serveur MCP dans Pycharm
Création de mon environnement virtuel
On crée le répertoire MCP puis on fait la commande :
vous avez un répertoire appelé MCP rentrez dedans
$ python -m venv .venv
# activez l'environnement virtuel (sous powershell)
$ .\.venv\Scripts\Activate.ps1
#c'est activé (normalement) vous avez accès à la commande pip
$ pip --version
Installation de uv
uv est un gestionnaire de paquet nouvelle génération écrite en RUST.
pip install uv
puis avec uv on installe mcp
uv add "mcp[cli]"
A partir de maintenant on va utiliser uv pour installer notre projet
uv init
# la commande tree -L 2 nous donne la structure suivante
.
|-- README.md
|-- main.py
|-- .venv
| |-- Include
| |-- Lib
| |-- Scripts
| `-- pyvenv.cfg
|-- pyproject.toml
`-- uv.lock
Pour install tree dans Windows Gitbash, il faut télécharger l’EXE et le mettre dans C:\Program Files\Git\usr\bin, un tuto est accessible sur ce lien.
Le soucis c’est que mcp s’attends à ce que le dossier environnement virtuel soit nommé .venv, si vous faites la commande:
$ uv run mcp
warning: `VIRTUAL_ENV=mcpvenv` does not match the project environment path `.venv` and will be ignored; use `--active` to target the active environment instead
Désativez l'environnement avec
$ deactivate
si un .venv est présent, exécutez le venv de ce répertoire.
Exemple rapide de serveur MCP:
#server.py
from mcp.server.fastmcp import FastMCP
# Create an MCP server
mcp = FastMCP("Demo")
# Add an addition tool
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers"""
return a + b
# Add a dynamic greeting resource
@mcp.resource("greeting://{name}")
def get_greeting(name: str) -> str:
"""Get a personalized greeting"""
return f"Hello, {name}!"
# Add a prompt
@mcp.prompt()
def greet_user(name: str, style: str = "friendly") -> str:
"""Generate a greeting prompt"""
styles = {
"friendly": "Please write a warm, friendly greeting",
"formal": "Please write a formal, professional greeting",
"casual": "Please write a casual, relaxed greeting",
}
return f"{styles.get(style, styles['friendly'])} for someone named {name}."
Pour le faire tourner
mcp dev server.py
#il vous sera demandé d'installer le paquet @modelcontextprotocol/inspector@0.17.0
Pour commencer il faut aller sur le site Ollama.com (à ne pas confondre avec Llama.com). Le lien Github, pour consulter les sources et la documentation sur les modèles
Téléchargez et installez. Une fois installé, vous aurez une opup comme ci-dessous :
Vous allez vous perdre dans la jungle des modèles. Vous connaissez sans doute aussi de nom Hugging Face, c’est un repository de modèle LLM open source. Une fois que vous avez installé Ollama, ouvrez un terminal et tapez la commande $ ollama, une liste de commande sera affichée. Allez sur le Github, vous verrz un tableau de modèles LLM, différentes capacité et taille, ça va de 1 Go à 404 Go ! En fonction de votre machine téléchargez celui que vous pouvez faire fonctionner !
La colonne parameters indique combien de paramètres est considéré par le modèle, comptez environs 1 giga de RAM pour 1 Giga (Billion milliards) de paramètres. Mon ordinateur fait 32 Giga de RAM, je peux aller avec le modèle à 30B paramètres.
Les commandes pour installer les LLM
ollama run llama2
Pour connaitres les différentes commande au sein du chat
/?
>>> /?
Available Commands:
/set Set session variables
/show Show model information
/load <model> Load a session or model
/save <model> Save your current session
/clear Clear session context
/bye Exit
/?, /help Help for a command
/? shortcuts Help for keyboard shortcuts
Use """ to begin a multi-line message.
Pour quitter :
/bye
Pour lister les modèles :
ollama list
.
Ecran du téléchargement du modèle, à la vue de la sortie terminal, ça ressemble à une image Docker
Test de llama2
La première chose que je constate, est que le modèle est vraiment limité, surtout quand vous avez l’habitude de vous amuser avec chatGPT ou Claude sur le web, donc sur les modèles les plus sophistiqués.
Par exemple je lui demande de me donner la recette du roesti, il me répond qu’il ne sait pas
Quand je lui demande la recette de la tarte tatin, il me liste en anglais (pas vraiment multilingue, ok pour la taille du modèle cependant !). Par contre comme il est hébergé en local il est très rapide. Je lui demande pourquoi il me répond qu’il n’a pas réussi à détecter (alors que je lui ai demandé en français), mais il me propose de donner la version française ce qu’il fit très bien.
Nous allons télécharger un autre modèle
Cette fois ci nous allons télécharger le modèle Mistral, équivalent en taille. Sur le repository, vous trouverez la commande dans le tableau
ollama run mistral
Après le téléchargement, il démarre le prompt automatiquement. Quand vous ferez bye, vous pourrez démarrer entre les deux modèles (Mistral ou llma2 en ligne de commande)
Pour effacer un modèle, vous avez la comamnde remove, mais pensez à faire ollama pour afficher les différentes commandes.
Et maintenant explorons l’API !
Eh oui c’est là que cela devient très intéressant car nous allons pouvoir exploiter un LLM depuis un programme pour automatiser nos tâche (notre GRAAL)
Ollama expose une API sur le localhost. Dans la barre des tâches, vous pouvez voir une image de Llama, cela veut dire que l’application Desktop est lancée, et donc que l’API est active. Mais si le desktop n’est pas llancé, vous pouvez toujours lancer le serveur en ligne de commande
ollama serve
En parlant de l’icone, vous pouvez accéder aux paramètres
Vous remarquez (j’espère) le slider pour définir la « mémoire » du LLM (la taille du contexte), ceci est utile pour se souvenir d’une conversation !
Vous pouvez exposer le LLM au réseau local (très intéressant !)
Installer le package Python ollama
Très simplement on va faire un script pour requêter Ollama par API avec pip install ollama. Si vous installez le paquet après avoir démarré l’environnement virtuel, il sera en local, si vous le faites hors environnement virtuels, il sera dans le système. Pour connaitre où est installé un package
#affiche les emplacements pouvant héberger les paquets
python -m site
# cherche un paquet en particuliers
python -m pip show <paquet>
#lister les paquets de l'environnement virtuel en cours
pip list
import ollama
client = ollama.Client()
model = "llama2"
prompt = "How to set up a venv?"
response = client.generate(model, prompt)
print(f"Response from {model}:")
print(response.response)
Si vous préférez que la réponse arrive morceau par morceau voici le script (il suffit de passer stream= True)
import ollama
client = ollama.Client()
model = "llama2"
prompt = "How to set up a venv?"
# Use the stream=True option
stream = client.generate(model=model, prompt=prompt, stream=True)
print(f"Response from {model}:\n")
for chunk in stream:
# Each chunk is a small piece of the model's output
print(chunk['response'], end='', flush=True)
print() # final newline
Ok là c’est mieux mais on peut wrapper les phrases (retour à la ligne quand ça déborde)
Avec retour à la ligne sans streaming
import ollama
import textwrap
import shutil
client = ollama.Client()
model = "llama2"
prompt = "How to set up a venv?"
response = client.generate(model=model, prompt=prompt)
terminal_width = shutil.get_terminal_size().columns
print(f"Response from {model}:\n")
print(textwrap.fill(response['response'], width=terminal_width))
Avec retour à la ligne avec streaming
import ollama
import shutil
import sys
client = ollama.Client()
model = "llama2"
prompt = "How to set up a venv?"
stream = client.generate(model=model, prompt=prompt, stream=True)
print(f"Response from {model}:\n")
terminal_width = shutil.get_terminal_size().columns
current_line = ""
for chunk in stream:
text = chunk["response"]
for char in text:
current_line += char
if len(current_line) >= terminal_width - 1:
print(current_line)
current_line = ""
# print remaining text
if current_line:
print(current_line)
Personnalisation du modèle
Avec un fichier de configuration, on peut agir sur la façon dont les réponse vont être apportées, il y a de nombreux paramètres qui font qu’un LLM va se comporer d’une certianes façon. Vous avez peut être remarqué que ChatGPT était plus jovial que Claude qui est plus sérieux. Perso j’aim bien cette touche.
Mettez Le fichier de personnalisation Modefile dans un répertoire et invoquez une commande sur ce fichier dans ce répertoire.
$ ollama create formabot -f ./Modelfile
# ici j'ai invoqué un modèle non présent ça met du temps à télécharger le modèle
On va lancer avec la commande run
$ ollama run formabot
>>> qui es tu?
# pour effacer le modèle
$ ollama rm formabot
Conclusion :
Dans sa forme, l’outil ressemble beaucoup à Docker (les commandes, les modèles). Voilà pour une première introduction de Ollama. Pourquoi est ce important d’avoir un LLM en local? Si vous voulez rester privé, avoir des temps de réponse rapide, et ne pas vous ruiner en crédit token, alors n’hésitez pas à utiliser un LLM open source, il y en a d’autres allez voir du côté de Deepseek et de HuggingFace.
Dans quelle situation un serveur MCP est pertinent?
Un modèle LLM comme ChatGPT est généraliste et a été entrainé sur un dataset limité bien que grand. Il ne dispose pas d’une connaissance universelle. Mais avez vous remarqué qu’une IA a toujours réponse à tous néanmoins? quitte à vous sortir des insanités? qu’on appelle des hallucinations.
Pour éviter ce phénomène d’hallucination, qui résulte d’un trou dans sa connaissance, il faut pouvoir compléter avec des données supplémentaires. Seulement voilà, le modèle de chatGPT a été entrainé déjà (et c’est ultra coûteux d’entrainer une IA, tout le monde ne dispose pas les moyens de le faire.
La solution du RAG (Retrieval Augmented Generation)
Le principe repose sur le stockage dans une base vectorielle des données, avec une base de données vectorielle comme FAISS ou ChromaDB, puis de prompter la base au lieu de prompter chatGPT, le résultat de ce prompt est envoyé à chatGPT. (prompt augmenté).
C’est une bonne solution, et vraiment adapté à certaines situation comme chatPDF. Cependant c’est un peu lourd. Nous allons voir qu’Anthropic propose une solution qui marche dans le sens inverse mais qui semble être plus légère et performante (quoique différent).
La solution MCP (Model Context Protocol)
Cette solution à base de données structurée (et c’est là la différence avec la RAG) requiert à Claude (et non chatGPT puisque qu’au moment d’écrire cet article chatGPT ne le prend pas encore en charge), de faire un sorte de requête AJAX avec un serveur MCP pour contextualiser la réponse à un prompt.
MCP ouvre tout un univers de champs d’application très intéressant, tout en préservant l’anonymat des données.
Notre serveur MCP est simplement un serveur REST, le différence c’est ce n’est pas vous qui requêtez en AJAX mais c’est Claude qui va le requêter (on comprend mieux pourquoi les autres vendors d’IA ne l’ont pas encore implémenté)
employee_db.json est la base de données, mcp_manifest/json va décrire la façon dont les données seront formatées en input (venant de Claude) et en output (allant vers Claude).
Pour que Claude puisse lire le manifest.json, on peut le stocker sur une github page, ou un serveur accessible publiquement. On va notifier cette url à Claude via son interface graphique ou par API.
Le serveur REST doit être accessible publiquement, si vous le déployez en local il faut faire un tunnel vers votre serveur.
Comment utiliser ce système
Il faut que ce soit clair que dans ce cas de figure vous ne pouvez requêter Claude que par API. Quand est ce que Claude va savoir qu’il va avoir besoin de requêter le serveur MCP? C’est lui qui détermine quand il faut requêter le serveur MCP, quand il va s’apercevoir que le contexte lui manque et qu’il existe un serveur MCP décrit par le manifest.json existe.
Exemple de requête faite à Claude
vous pouvez faire en Javascript cette requête POST
POST /v1/messages
{
"model": "claude-3-opus-20240229",
"messages": [
{ "role": "user", "content": "Peux-tu me dire ce que fait Bruno Martin ?" }
],
"context_providers": [
{ "url": "https://ton-domaine.com/mcp_manifest.json" }
]
}
Pour exposer le site sur un serveur il suffit de simplement déployer sur un serveur. Une alternative serait de créer un tunnel SSH pour exposer votre site local à Claude.
Afin de ne pas à avoir à calculer toujours le modèle pour déterminer si un client va partir ou non, on sauve le modèle entrainé et on on va le réutiliser
Charger le modèle
import joblib
#load the model
loaded_rf = joblib.load('random_forest_model.pkl')
print(loaded_rf)
Charger les données
Normalement nous chargeons de nouvelles données qu’on va donner à moudre au modèle, mais on n’en a pas, donc on va repasser les même données qu’on a sauvegardé.
#load the datas
import pickle
with open('X_test.pkl', 'rb') as f:
X_test = pickle.load(f)
with open('y_test.pkl', 'rb') as f:
y_test = pickle.load(f)
X_test.head()
Injection des données de test (les données de test n’ont pas servi à entrainer le modèle), on va obtenir une Series avec des 0 et 1
# Now you can use the loaded model to make predictions
from sklearn.metrics import accuracy_score
prediction = loaded_rf.predict(X_test)
#la série qui contient les outcome (churn si 1 , 0 si no churn)
print(prediction)
[0 1 1 1 0 0 0.... ]
Test sur une ligne (un client)
# test on row
# subset = X_test[X_test['TotalCharges']<100]
# extract a line from X_test
oneline = X_test.loc[[6125]]
#convert to dataframe
#oneline = oneline.to_frame()
import pandas as pd
isinstance(oneline,pd.DataFrame) # test if it's DataFrame
# affichage des informations de la ligne
print(oneline)
oneline.head()
#prediction sur la ligne
prediction = loaded_rf.predict(oneline)
print(prediction)
#!/usr/bin/env python3
"""
ChatPDF Simple - Version corrigée pour OpenAI v1.0+
Permet d'extraire le texte d'un PDF et de poser des questions dessus
Sans base vectorielle
"""
import PyPDF2
from openai import OpenAI
import os
import argparse
import sys
from typing import List, Optional
import tiktoken
import re
class ChatPDF:
def __init__(self, api_key: Optional[str] = None):
"""
Initialise ChatPDF avec la clé API OpenAI
"""
self.api_key = 'sk-proj-BBXFnLZzRMt8EZp4LStJXyfzc_6knvyK'
if not self.api_key:
raise ValueError("Clé API OpenAI requise. Définissez OPENAI_API_KEY ou passez api_key")
self.client = OpenAI(api_key=self.api_key)
self.pdf_content = ""
self.pdf_path = ""
self.encoding = tiktoken.get_encoding("cl100k_base")
def extract_text_from_pdf(self, pdf_path: str) -> str:
"""
Extrait tout le texte d'un fichier PDF
"""
try:
with open(pdf_path, 'rb') as file:
pdf_reader = PyPDF2.PdfReader(file)
text = ""
print(f"Extraction du texte de {len(pdf_reader.pages)} pages...")
for page_num, page in enumerate(pdf_reader.pages):
try:
page_text = page.extract_text()
text += f"\n--- Page {page_num + 1} ---\n{page_text}\n"
except Exception as e:
print(f"Erreur lors de l'extraction de la page {page_num + 1}: {e}")
continue
self.pdf_content = text
self.pdf_path = pdf_path
print(f"Extraction terminée. {len(text)} caractères extraits.")
return text
except FileNotFoundError:
raise FileNotFoundError(f"Fichier PDF non trouvé: {pdf_path}")
except Exception as e:
raise Exception(f"Erreur lors de la lecture du PDF: {e}")
def chunk_text(self, text: str, max_tokens: int = 3000) -> List[str]:
"""
Divise le texte en chunks pour respecter les limites de tokens
"""
sentences = re.split(r'[.!?]+', text)
chunks = []
current_chunk = ""
for sentence in sentences:
sentence = sentence.strip()
if not sentence:
continue
test_chunk = current_chunk + " " + sentence if current_chunk else sentence
if len(self.encoding.encode(test_chunk)) <= max_tokens:
current_chunk = test_chunk
else:
if current_chunk:
chunks.append(current_chunk)
current_chunk = sentence
if current_chunk:
chunks.append(current_chunk)
return chunks
def ask_question(self, question: str, model: str = "gpt-3.5-turbo") -> str:
"""
Pose une question sur le contenu du PDF
"""
if not self.pdf_content:
return "Aucun PDF chargé. Veuillez d'abord extraire le contenu d'un PDF."
# Préparer le contexte
chunks = self.chunk_text(self.pdf_content, max_tokens=3000)
# Si le document est court, utiliser tout le contenu
if len(chunks) == 1:
context = chunks[0]
else:
# Pour des documents longs, prendre les premiers chunks
context = " ".join(chunks[:2]) # Utiliser les 2 premiers chunks
print(('======'))
print(context)
print(('======'))
prompt = f"""
Vous êtes un assistant qui répond aux questions basées sur le contenu d'un document PDF.
Contenu du document:
{context}
Question: {question}
Instructions:
- Répondez uniquement basé sur le contenu fourni
- Si l'information n'est pas dans le document, dites-le clairement
- Citez des passages spécifiques quand c'est pertinent
- Soyez précis et concis
Réponse:"""
try:
response = self.client.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": "Vous êtes un assistant spécialisé dans l'analyse de documents PDF."},
{"role": "user", "content": prompt}
],
max_tokens=500,
temperature=0.1
)
return response.choices[0].message.content.strip()
except Exception as e:
return f"Erreur lors de la génération de la réponse: {e}"
def summarize_pdf(self) -> str:
"""
Génère un résumé du PDF
"""
if not self.pdf_content:
return "Aucun PDF chargé."
chunks = self.chunk_text(self.pdf_content, max_tokens=3000)
context = " ".join(chunks[:3]) # Utiliser les 3 premiers chunks
prompt = f"""
Veuillez créer un résumé concis du document suivant:
{context}
Le résumé doit:
- Capturer les points clés et idées principales
- Être structuré et facile à lire
- Faire environ 200-300 mots maximum
Résumé:"""
try:
response = self.client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": "Vous créez des résumés clairs et concis de documents."},
{"role": "user", "content": prompt}
],
max_tokens=400,
temperature=0.2
)
return response.choices[0].message.content.strip()
except Exception as e:
return f"Erreur lors de la génération du résumé: {e}"
def main():
parser = argparse.ArgumentParser(description="ChatPDF Simple - Analyseur de PDF avec IA")
parser.add_argument("pdf_path", help="Chemin vers le fichier PDF")
parser.add_argument("--api-key", help="Clé API OpenAI (ou utilisez la variable d'environnement OPENAI_API_KEY)")
parser.add_argument("--model", default="gpt-3.5-turbo", help="Modèle OpenAI à utiliser")
args = parser.parse_args()
try:
# Initialiser ChatPDF
chat_pdf = ChatPDF(api_key=args.api_key)
# Extraire le texte du PDF
print(f"Chargement du PDF: {args.pdf_path}")
chat_pdf.extract_text_from_pdf(args.pdf_path)
# Générer un résumé automatique
print("\n=== RÉSUMÉ AUTOMATIQUE ===")
summary = chat_pdf.summarize_pdf()
print(summary)
# Mode interactif pour les questions
print("\n=== MODE QUESTIONS-RÉPONSES ===")
print("Posez vos questions sur le document (tapez 'quit' pour quitter):")
while True:
try:
question = input("\n❓ Votre question: ").strip()
if question.lower() in ['quit', 'exit', 'q']:
break
if not question:
continue
print("🤔 Recherche de la réponse...")
answer = chat_pdf.ask_question(question, model=args.model)
print(f"\n💡 Réponse:\n{answer}")
except KeyboardInterrupt:
break
except Exception as e:
print(f"Erreur: {e}")
print("\nAu revoir!")
except Exception as e:
print(f"Erreur: {e}")
sys.exit(1)
if __name__ == "__main__":
main()
# Exemple d'utilisation en tant que module
"""
from chatpdf_simple import ChatPDF
# Initialiser
chat = ChatPDF(api_key="votre_clé_api")
# Charger un PDF
chat.extract_text_from_pdf("document.pdf")
# Poser des questions
response = chat.ask_question("Quels sont les points principaux de ce document?")
print(response)
# Générer un résumé
summary = chat.summarize_pdf()
print(summary)
"""
Usage
Macos
python3 main.py document.pdf
Windows
python main.py document.pdf
Si vous ne connaissez pas chatPDF, je vous recommande de le tester sur le site en uploadant un fichier pdf, et interrogez le document comme si vous interrogiez chatGPT. L’intérêt est que vous pouvez connaitre c’e qui vous intéresse du document sans le lire entièrement.
Par exemple vous souscrivez à une assurance, et il y a un long contrat en petits caractères à lire, plutôt que de perdre des heures à décortiquer le document, vous interrogez chatGPT sur le document.
Techniquement ce n’est pas comme ça. En fait vous injecter le texte du document dans la base vectorielle FAISS, vous interrogez FAISS, qui vous retournera un résultat (qui est un contexte) et vous le soumettez à chatGPT.
Vous contextualisez très précisément votre prompt.
Installation des composants
La base de données vectorielle FAISS
FAISS est un logiciel fait par Facebook, acronyme de Facebook AI Similarity Search. Elle tokenise le pdf et le stocke sous forme vectorielle.
Le parseur de PDF
PyPDF2 est un très bon parseur de pdf.
Un tokeniser
tiktoken va transformer le texte en token. Un token est une unité qui a une signification mathématique pour le LLM. tiktoken travaille de concert avec le modèle de OpenAI.
Openai
Permet d’appeler le webservice d’OpenAI facilement
Une clé API par exemple chatGPT
Il vous faudra acheter des crédits pour le webservice chatGPT, 10 euros est pas mal et vous permettra de faire beaucoup d’appels.
Le code Python
#!/usr/bin/env python3
"""
ChatPDF Clone avec Base de Données Vectorielle
Utilise des embeddings et une recherche vectorielle pour une meilleure précision
"""
import PyPDF2
from openai import OpenAI
import os
import argparse
import sys
import numpy as np
from typing import List, Optional, Dict, Tuple
import tiktoken
import re
import faiss
import pickle
from sentence_transformers import SentenceTransformer
from dataclasses import dataclass
import json
@dataclass
class TextChunk:
text: str
page_num: int
chunk_id: int
embedding: Optional[np.ndarray] = None
class VectorChatPDF:
def __init__(self, api_key: Optional[str] = None, embedding_model: str = "all-MiniLM-L6-v2"):
"""
Initialise ChatPDF avec base vectorielle
"""
self.api_key = "sk-proj-BBFnLZzRMt8EZp4LStJXyfzc_6knvyKX2jOhYXIRh27MmGHIkxzcGFfcisYMS3uiRYBP0r2qT3BlbkFJObsGY8qW-VjRQNLsB9Fr-iPhSmCs0Aaj2Y8hSF34dBQQ4dEgiq7mQO30BCWDItJC775SUA"
if not self.api_key:
raise ValueError("Clé API OpenAI requise. Définissez OPENAI_API_KEY ou passez api_key")
self.client = OpenAI(api_key=self.api_key)
# Modèle d'embeddings local (gratuit)
print("Chargement du modèle d'embeddings...")
self.embedding_model = SentenceTransformer(embedding_model)
# Base de données vectorielle FAISS
self.vector_db = None
self.chunks: List[TextChunk] = []
self.pdf_path = ""
self.encoding = tiktoken.get_encoding("cl100k_base")
def extract_text_from_pdf(self, pdf_path: str) -> List[TextChunk]:
"""
Extrait le texte du PDF et le divise en chunks
"""
try:
with open(pdf_path, 'rb') as file:
pdf_reader = PyPDF2.PdfReader(file)
chunks = []
chunk_id = 0
print(f"Extraction du texte de {len(pdf_reader.pages)} pages...")
for page_num, page in enumerate(pdf_reader.pages):
try:
page_text = page.extract_text()
if page_text.strip():
# Diviser la page en chunks
page_chunks = self._split_text_into_chunks(page_text, max_tokens=500)
for chunk_text in page_chunks:
chunk = TextChunk(
text=chunk_text,
page_num=page_num + 1,
chunk_id=chunk_id
)
chunks.append(chunk)
chunk_id += 1
except Exception as e:
print(f"Erreur lors de l'extraction de la page {page_num + 1}: {e}")
continue
self.chunks = chunks
self.pdf_path = pdf_path
print(f"Extraction terminée. {len(chunks)} chunks créés.")
return chunks
except FileNotFoundError:
raise FileNotFoundError(f"Fichier PDF non trouvé: {pdf_path}")
except Exception as e:
raise Exception(f"Erreur lors de la lecture du PDF: {e}")
def _split_text_into_chunks(self, text: str, max_tokens: int = 500) -> List[str]:
"""
Divise le texte en chunks optimaux pour les embeddings
"""
# Nettoyer le texte
text = re.sub(r'\s+', ' ', text).strip()
# Diviser par phrases
sentences = re.split(r'[.!?]+', text)
chunks = []
current_chunk = ""
for sentence in sentences:
sentence = sentence.strip()
if not sentence:
continue
test_chunk = current_chunk + " " + sentence if current_chunk else sentence
if len(self.encoding.encode(test_chunk)) <= max_tokens:
current_chunk = test_chunk
else:
if current_chunk:
chunks.append(current_chunk)
current_chunk = sentence
if current_chunk:
chunks.append(current_chunk)
return [chunk for chunk in chunks if len(chunk.strip()) > 20] # Filtrer les chunks trop courts
def create_embeddings(self):
"""
Crée les embeddings pour tous les chunks et construit l'index FAISS
"""
if not self.chunks:
raise ValueError("Aucun chunk disponible. Extrayez d'abord le texte du PDF.")
print("Création des embeddings...")
texts = [chunk.text for chunk in self.chunks]
# Créer les embeddings par batch pour l'efficacité
embeddings = self.embedding_model.encode(texts, show_progress_bar=True)
# Associer les embeddings aux chunks
for i, chunk in enumerate(self.chunks):
chunk.embedding = embeddings[i]
# Créer l'index FAISS
dimension = embeddings.shape[1]
self.vector_db = faiss.IndexFlatIP(dimension) # Index par produit scalaire (cosine similarity)
# Normaliser les vecteurs pour la similarité cosine
normalized_embeddings = embeddings / np.linalg.norm(embeddings, axis=1, keepdims=True)
self.vector_db.add(normalized_embeddings.astype('float32'))
print(f"Index vectoriel créé avec {len(self.chunks)} embeddings.")
def search_similar_chunks(self, query: str, top_k: int = 5) -> List[Tuple[TextChunk, float]]:
"""
Recherche les chunks les plus similaires à la requête
"""
if not self.vector_db:
raise ValueError("Index vectoriel non créé. Appelez create_embeddings() d'abord.")
# Créer l'embedding de la requête
query_embedding = self.embedding_model.encode([query])
query_embedding = query_embedding / np.linalg.norm(query_embedding)
# Rechercher les chunks similaires
scores, indices = self.vector_db.search(query_embedding.astype('float32'), top_k)
results = []
for score, idx in zip(scores[0], indices[0]):
if idx < len(self.chunks): # Vérification de sécurité
results.append((self.chunks[idx], float(score)))
return results
def ask_question(self, question: str, model: str = "gpt-3.5-turbo", top_k: int = 5) -> Dict:
"""
Pose une question en utilisant la recherche vectorielle
"""
if not self.vector_db:
return {"error": "Index vectoriel non créé. Appelez create_embeddings() d'abord."}
# Rechercher les chunks pertinents
similar_chunks = self.search_similar_chunks(question, top_k=top_k)
if not similar_chunks:
return {"error": "Aucun contenu pertinent trouvé."}
# Construire le contexte à partir des chunks les plus pertinents
context_parts = []
source_info = []
for chunk, score in similar_chunks:
context_parts.append(f"[Page {chunk.page_num}] {chunk.text}")
source_info.append({
"page": chunk.page_num,
"chunk_id": chunk.chunk_id,
"similarity_score": score,
"text_preview": chunk.text[:100] + "..." if len(chunk.text) > 100 else chunk.text
})
context = "\n\n".join(context_parts)
prompt = f"""
Vous êtes un assistant expert qui répond aux questions basées sur le contenu d'un document PDF.
Contexte pertinent extrait du document:
{context}
Question: {question}
Instructions:
- Répondez uniquement basé sur le contexte fourni
- Si l'information n'est pas suffisante, dites-le clairement
- Citez les numéros de page quand c'est pertinent
- Soyez précis et structuré dans votre réponse
Réponse:"""
try:
response = self.client.chat.completions.create(
model=model,
messages=[
{"role": "system",
"content": "Vous êtes un assistant spécialisé dans l'analyse de documents PDF avec une expertise en recherche d'information."},
{"role": "user", "content": prompt}
],
max_tokens=800,
temperature=0.1
)
answer = response.choices[0].message.content.strip()
return {
"answer": answer,
"sources": source_info,
"context_used": len(similar_chunks)
}
except Exception as e:
return {"error": f"Erreur lors de la génération de la réponse: {e}"}
def save_index(self, filepath: str):
"""
Sauvegarde l'index vectoriel et les métadonnées
"""
if not self.vector_db:
raise ValueError("Aucun index à sauvegarder.")
# Sauvegarder l'index FAISS
faiss.write_index(self.vector_db, f"{filepath}.faiss")
# Sauvegarder les métadonnées
metadata = {
"chunks": [
{
"text": chunk.text,
"page_num": chunk.page_num,
"chunk_id": chunk.chunk_id
} for chunk in self.chunks
],
"pdf_path": self.pdf_path
}
with open(f"{filepath}.json", 'w', encoding='utf-8') as f:
json.dump(metadata, f, ensure_ascii=False, indent=2)
print(f"Index sauvegardé: {filepath}.faiss et {filepath}.json")
def load_index(self, filepath: str):
"""
Charge un index vectoriel sauvegardé
"""
try:
# Charger l'index FAISS
self.vector_db = faiss.read_index(f"{filepath}.faiss")
# Charger les métadonnées
with open(f"{filepath}.json", 'r', encoding='utf-8') as f:
metadata = json.load(f)
# Reconstruire les chunks
self.chunks = []
for chunk_data in metadata["chunks"]:
chunk = TextChunk(
text=chunk_data["text"],
page_num=chunk_data["page_num"],
chunk_id=chunk_data["chunk_id"]
)
self.chunks.append(chunk)
self.pdf_path = metadata["pdf_path"]
print(f"Index chargé: {len(self.chunks)} chunks disponibles.")
except FileNotFoundError:
raise FileNotFoundError(f"Fichiers d'index non trouvés: {filepath}.faiss ou {filepath}.json")
def main():
parser = argparse.ArgumentParser(description="ChatPDF avec Base Vectorielle")
parser.add_argument("pdf_path", help="Chemin vers le fichier PDF")
parser.add_argument("--api-key", help="Clé API OpenAI")
parser.add_argument("--model", default="gpt-3.5-turbo", help="Modèle OpenAI à utiliser")
parser.add_argument("--save-index", help="Sauvegarder l'index vectoriel")
parser.add_argument("--load-index", help="Charger un index vectoriel existant")
parser.add_argument("--top-k", type=int, default=5, help="Nombre de chunks à récupérer")
args = parser.parse_args()
try:
# Initialiser ChatPDF vectoriel
chat_pdf = VectorChatPDF(api_key=args.api_key)
if args.load_index:
# Charger un index existant
print(f"Chargement de l'index: {args.load_index}")
chat_pdf.load_index(args.load_index)
else:
# Traiter un nouveau PDF
print(f"Chargement du PDF: {args.pdf_path}")
chat_pdf.extract_text_from_pdf(args.pdf_path)
chat_pdf.create_embeddings()
if args.save_index:
chat_pdf.save_index(args.save_index)
# Mode interactif
print("\n=== MODE QUESTIONS-RÉPONSES VECTORIEL ===")
print("Posez vos questions sur le document (tapez 'quit' pour quitter):")
print("Tapez 'stats' pour voir les statistiques de l'index")
while True:
try:
question = input("\n❓ Votre question: ").strip()
if question.lower() in ['quit', 'exit', 'q']:
break
if question.lower() == 'stats':
print(f"📊 Statistiques:")
print(f" - Nombre de chunks: {len(chat_pdf.chunks)}")
print(f" - Pages: {max(c.page_num for c in chat_pdf.chunks) if chat_pdf.chunks else 0}")
print(f" - Document: {os.path.basename(chat_pdf.pdf_path)}")
continue
if not question:
continue
print("🔍 Recherche vectorielle en cours...")
result = chat_pdf.ask_question(question, model=args.model, top_k=args.top_k)
if "error" in result:
print(f"❌ {result['error']}")
else:
print(f"\n💡 Réponse:\n{result['answer']}")
print(f"\n📚 Sources utilisées ({result['context_used']} chunks):")
for i, source in enumerate(result['sources'][:3], 1):
print(f" {i}. Page {source['page']} (score: {source['similarity_score']:.3f})")
print(f" {source['text_preview']}")
except KeyboardInterrupt:
break
except Exception as e:
print(f"Erreur: {e}")
print("\nAu revoir!")
except Exception as e:
print(f"Erreur: {e}")
sys.exit(1)
if __name__ == "__main__":
main()
Lancer le programme en ligne de commande
lancement du script en mode normal, cela va créer un fichier binaire mon_index.faiss qui est la base vectorielle
Lors du lancement du script en mode normal, une base vectorielle va être créée. Vous allez poser une question qui va requêter sur cette base de données. Par exemple, « Combien de jours de formation y a t il dans cette formation? », la requête ne va pas partir vers chatGPT directement mais va extraire un contexte de la base vectorielle, qui va être mis dans le prompt vers chatGPT.
Sans le vouloir vraiment, vous venez de faire du RAG, Retrieval Augmented Generation, autrement dit intelligence générative augmentée par une extraction de données.
Modèle d’embedding c’est quoi?
Un modèle d’embedding est un système qui transforme du texte (mots, phrases, paragraphes) en vecteurs numériques de dimension fixe. Ces vecteurs capturent le sens sémantique du texte de manière à ce que des textes similaires aient des représentations vectorielles proches dans l’espace mathématique.
Comment ça fonctionne
Les modèles d’embedding utilisent généralement des réseaux de neurones entraînés sur de grandes quantités de texte pour apprendre les relations sémantiques. Par exemple, les mots « chien » et « animal » auront des vecteurs plus proches que « chien » et « voiture ».
Utilité pratique
Ces représentations vectorielles permettent de :
Mesurer la similarité sémantique entre textes
Effectuer des recherches par similarité
Regrouper des documents par thème
Alimenter des systèmes de question-réponse
Le modèle « paraphrase-MiniLM-L3-v2 » est un modèle d’embedding compact et efficace développé par Sentence Transformers. Il est particulièrement adapté pour :
Identifier des paraphrases (textes exprimant la même idée différemment)
Créer des bases de données vectorielles pour la recherche sémantique
Le mode –fast
C’est u mode d’optimisation qui accélère le traitement en faisant certains compromis.
Le mode fast améliore généralement la vitesse au détriment de :
vous avez entendu parler de machine learning partout, mais avez vous vu comment cela fonctionnait? Nous allons voir avec un exemple comment ça marcce à quoi ça sert et vous comprendrez à la fin pourquoi il est très intéressant d’en faire dans les entreprises.
Le contexte : avec des données d’abandon de souscription à un forfait mobile (churn), nous allons établir un modèle mathématique pour prédire les futur churn. L’intérêt de faire confiance au machine learning est qu’il y a un grand nombre de paramètres qui influence le fait qu’un client part à la concurrence, il est quasi impossible de faire le diagnostic à la main pour savoir si le client va partir ou non, plus exactement il y a 18 paramètres.
Le principe du machine learning est de prendre ces données, les travailler et les faire passer dans un algorithme qui va générer un modèle mathématique qui servira aux futures données, pour prédire si le client va churner ou non. En gros se servir du passé pour prédire le futur.
Le dataset à télécharger
Allez sur Kaggle et téléchargez le dataset (il vous faudra créer un compte). On va explorer ce dataset, mais ce qu’il faut savoir c’est que dans ce dataset, 30% des lignes concernent les clients ayant churné, et 70% sont des clients qui n’ont pas churné. On n’a pas une distribution 50/50. En machine learning, entrainer sur ce dataset va produire un modèle mathématique qui va prédire de façon biaisé, quelquesoit l’entrée donnée, les probabilités que la prédiction soit en faveur d’un non churn sera anormalement haute.
Pour éviter ce déséquilibre dans les données (et ce déséquilibre importe car le type d’issue d’une prédiction est unique : le churn), on va créer artificiellement (oui artificiellement) des données pour compenser ce déséquilibre, donc on va générer des données ayant pour issue le churn, afin d’avoir un rapport 50/50 (churn/non-churn)
Installation des paquets
Pour faire du traitement de machine learning, il vous faudra installer les paquets suivants : numpy, pandas, matplotlib, seaborn,sklearn
Note: lors de l’installation de sklearn, vous aurez une erreur de paquet déprécié, en fait dans l’import de bibliothèque, c’est bien sklearn qu’il faut importer, mais lors que vous installez le paquet avec pip, c’est scikit-learn qu’il faut installer ! (Lien pour en savoir plus)
Utiliser l’éditeur de code Jupyter Notebook
Jupyter noteboopk est un éditeur de code progressif, très adapté à des non codeur pur et dur. Pour les datascientistes, c’est très pertinent car ils peuvent voir comment évolue leur traitement de données. Et pour tout le monde, c’est très visuel lors de l’apprentissage du machine learning, même pour un codeur pur et dur comme moi.
Vous pouvez décomposer le code en étape et refaire exécuter une étape à tout moment, pratique si vous vous trompez dans votre code et qu’il y a une erreur.
Pour installer Jupyter Notebook, faites la commande
# installation de jupyterlab
pip install jupyterlab
pour les macs
pip3 install jupyterlab
pour démarrer
jupyter lab
pip install notebook
ou pour les mac
pip3 install notebook
puis démarrer en faisant :
jupyter notebook
Note sur Jupyterlab : jupyterlab est la version moderne de jupyter notebook, et possède un système d eplugin plus simple à utiliser et un terminal et un éditeur de texte.
Commençons à explorer les caractéristiques de notre jeu de données
Notre objectif est d’entrainer un modèle sur le jeu de données, pour ensuite prédire la probabilité d’un client à passer à la concurrencer (churner). customerChurn.csv est le fichier de chez Kaggle.
import pandas as pd
df = pd.read_csv('customerChurn.csv')
df.head()
La commande df.head() affiche les 5 premières ligne du dataframe. Le dataframe est la version de Pandas d’un fichier Excel en gros.
Nous avons un fichier avec des colonnes, qu’on appelle de feature, puisque chaque colonne est une caractéristique d’un client. Pour voir le nom et le type de la donnée stockée dans chaque colonne ainsi que le nombre de colonnes, on fait
Et pour savoir combien il y a d’enregistrements on fait:
df["Churn"].value_counts()
Churn
No 5174
Yes 1869
Name: count, dtype: int64
donc on a 19 colonnes utile et une colonne inutile d’un point de vue machine learning, c’est la colonne customerID, elle ne comporte aucune information métier. La dernière colonne appelée Churn, prend deux valeuer, elle indique si le client est parti chez le concurrent ou non. Elle prend les valeur No ou Yes. Pour afficher le compte on fait:
df["Churn"].values_count()
Churn
No 5174
Yes 1869
Name: count, dtype: int64
il y a 1869 personnes partie, on note ici que le nombre de Yes et de No est différent dans un rapport 1 à 3 environ. Cela on le verra plus tard aura son importance dans l'entrainement du modèle. Il y a inégalité dans les 2 camps, cette disparité va avoir une incidence sur la qualité du modèle entrainé. En effet, le modèle résultant va être biaisé avec le No, car ce dernier est majoritaire. On devra équilibrer artificiellement ce dataset pour avoir un bon modèle de prédiction.
Analyse exploratoire des données
Maintenant explorons les relations entre les différentes catégorie et la colonne Churn
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
cols = ['gender','SeniorCitizen',"Partner","Dependents"]
numerical = cols
plt.figure(figsize=(20,4))
for i, col in enumerate(numerical):
ax = plt.subplot(1, len(numerical), i+1)
unique_vals = df[col].nunique()
sns.countplot(x=col, hue=col, data=df, legend=False,
palette=sns.color_palette("husl", df[col].nunique()))
#sns.countplot(x=str(col), data=df, palette=sns.color_palette("husl", unique_vals))
ax.set_title(f"{col}")
L’idée est ici de graphiquement regarder pour chaque colonne la distribution des valeurs. Ici on voit la colonne Genre, SeniorCitizen, Partner, Dependents, des varaible catégorielle relative à la démographie.
On voit qu’il y a autant d’homme que de femmes, mais que les seniors sont minoritaires, qu’il y a autant de célibataires que de mariés, et il y a 1 tier de la population qui est dépendantes.
Maintenant regardons la relation entre le MonthlyCharges et le Churn, on peut se dire que plus le forfait est élevé plus les gens ont tendance à partir
Effectivement c’est ce que nous constatons, les Yes sont entre 50 et 100 dollars / mois.
Pour comprendre le boxplot je vous joins ce petit schéma pris sur datatab.net
Maintenant analysons la relation entre quelques autres variable catégorielle avec le Churn
cols = ['InternetService',"TechSupport","OnlineBackup","Contract"]
plt.figure(figsize=(14,4))
for i, col in enumerate(cols):
ax = plt.subplot(1, len(cols), i+1)
sns.countplot(x ="Churn", hue = str(col), data = df)
ax.set_title(f"{col}")
Cette fois-ci les catégories ont plusieurs valeurs (3). On peut voir que pour InternetService, donc le forfait box de l’abonnement, les churners n’ont pas de forfait box ou très peu, idem pour le service ADSL, par contre ils sont nombreux à être sur fibre optique. Concernant les non churners, ils sont plus équitablement distribué.
InternetService : Il ressort clairement du graphique ci-dessus que les clients utilisant une connexion Internet par fibre optique résilient leur abonnement plus souvent que les autres. Cela pourrait s’expliquer par le fait que la fibre est un service plus coûteux, ou bien que ce fournisseur n’offre pas une bonne couverture.
TechSupport : De nombreux utilisateurs ayant résilié leur abonnement ne s’étaient pas inscrits au service d’assistance technique. Cela pourrait signifier qu’ils n’ont reçu aucune aide pour résoudre leurs problèmes techniques et ont décidé d’arrêter d’utiliser le service.
OnlineBackup : Beaucoup de clients ayant résilié leur abonnement ne s’étaient pas inscrits au service de sauvegarde en ligne pour le stockage de données.
Contract : Les utilisateurs qui ont résilié leur abonnement étaient presque toujours sous contrat mensuel. Cela semble logique, car ces clients paient au mois et peuvent facilement annuler leur abonnement avant le prochain cycle de paiement.
Même sans construire un modèle d’apprentissage automatique sophistiqué, une simple analyse basée sur les données comme celle-ci peut aider les organisations à comprendre pourquoi elles perdent des clients et ce qu’elles peuvent faire pour y remédier.
Par exemple, si l’entreprise se rend compte que la majorité des clients qui résilient leur abonnement ne se sont pas inscrits au service d’assistance technique, elle pourrait inclure ce service gratuitement dans certaines de ses futures offres afin d’éviter que d’autres clients ne partent.
Beaucoup de données sont de type Object, des valeurs non numériques, « TotalCharges » qui est un montant est un objet par exemple, on va le convertir en valeur numérique
Les valeurs catégorielles (non numérique) doivent être convertie en valeur numérique pour être modélisée, des colonnes avec pour valeur Yes/No, on peut rendre le Yes en 1 et le No en 0. Avec Sckitlearn on va pouvoir transformer ces valeurs en nombres. Si on regarde le dataframe sans les valeur numériques
La commande ci-dessus nous permet de voir les valeurs catégorielles des features. Transformaons les en valuer numérique avec ScikitLearn
from sklearn import preprocessing
le = preprocessing.LabelEncoder()
df_cat = cat_features.apply(le.fit_transform)
df_cat.head(1)
Scikitlearn convertit automatiquement les valeurs catégorielle en valeur numérique. Il n’y a pas de règle particulière, la valeur 0 correspond à la première valeur catégorielle ordonnées dans l’ordre alphabétique. No est avant Yes donc No vaut zéro et Yes vaut 1.
On avait dit que le Churn Yes était minoritaire dans les données, alors qu’il devait être à 50/50 avec le churn No.
Comme mentionné précédemment, le jeu de données est déséquilibré, ce qui signifie que la majorité des valeurs de la variable cible appartiennent à une seule classe. La plupart des clients du jeu de données n’ont pas résilié leur abonnement — seulement 27 % l’ont fait.
Ce problème de déséquilibre des classes peut entraîner de mauvaises performances d’un modèle d’apprentissage automatique. Certains algorithmes, lorsqu’ils sont entraînés sur un jeu de données déséquilibré, finissent toujours par prédire la classe majoritaire. Dans notre cas, par exemple, le modèle pourrait prédire qu’aucun client n’a résilié. Bien qu’un tel modèle semble très précis (il serait correct dans 73 % des cas), il ne nous est d’aucune utilité puisqu’il prédit toujours le même résultat.
Il existe différentes techniques pour résoudre le problème de déséquilibre des classes en apprentissage automatique. Dans ce tutoriel, nous allons utiliser une technique appelée suréchantillonnage (oversampling). Ce processus consiste à sélectionner aléatoirement des échantillons de la classe minoritaire et à les ajouter au jeu de données d’entraînement. Nous allons suréchantillonner la classe minoritaire jusqu’à ce que le nombre d’exemples soit égal à celui de la classe majoritaire.
Avant de procéder au suréchantillonnage, effectuons une séparation entre le jeu d’entraînement et le jeu de test. Nous appliquerons le suréchantillonnage uniquement sur le jeu d’entraînement, car le jeu de test doit rester représentatif de la population réelle.
On va constituer un dataset pour le training du modèle, et un dataset de test qu’on va soumettre qu modèle entrainé.
from sklearn.model_selection import train_test_split
finaldf = finaldf.dropna()
finaldf = finaldf.drop(['customerID'],axis=1)
X = finaldf.drop(['Churn'],axis=1)
y = finaldf['Churn']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42)
Chaque dataset est en deux parties X (majuscule) et y (minuscule), y représente la dernière colonne qui est le churn. Ainsi Dans le dataset de training, on a X_train et y_train. Et pour le dataset de test on a X_test et y_test.
C’est avec la librairie imblearn qu’on va construire le suréchantillon sur les données de training
from imblearn.over_sampling import SMOTE
oversample = SMOTE(k_neighbors=5)
X_smote, y_smote = oversample.fit_resample(X_train, y_train)
X_train, y_train = X_smote, y_smote
Ensuite consmptez le nombre de rangées :
y_train.value_counts()
Churn
0 3452
1 3452
Name: count, dtype: int64
On est à 50/50
Construction du modèle de prédiction du churn
Maintenant la partie vraiment importante ! On a passé beaucoup de temps pour nettoyer, changé la valeur catégorielle en valeur numérique, rééquilibré les population, maintenant on va entrainer le modèle !
from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier(random_state=46)
rf.fit(X_train,y_train)
Evaluation du modèle
On va évaluer l aprécision du modèle avec les données de test (dont on connait l’issue car ce sont les données d’origine)
#customer churn model evaluation
from sklearn.metrics import accuracy_score
preds = rf.predict(X_test)
print(accuracy_score(preds,y_test))
0.7733735458853942
On a une précision de 77%
Sauvegarder le modèle
On en va pas à chaque fois réentrainer le modèle quand on va faire une prédiction, donc on va le sauvegarder pour pouvoir le réutiliser.
#save the model
import joblib
# After training your model (format Pickle)
joblib.dump(rf, 'random_forest_model.pkl')
Sauvergarder les données de test et de training
#save training and testing data to file
import pickle
# Save X data
with open('X_train.pkl', 'wb') as f:
pickle.dump(X_train, f)
with open('X_test.pkl', 'wb') as f:
pickle.dump(X_test, f)
# Save y data
with open('y_train.pkl', 'wb') as f:
pickle.dump(y_train, f)
with open('y_test.pkl', 'wb') as f:
pickle.dump(y_test, f)
Chargement du modèle et des données sauvegardées
On va ouvrir un autre fichier Notebook vierge et on va juste charge le modèle et les données pour passer au crible. En effet le modèle que vous avez calculé va être réutilisé, et il n’est pas question de le calculer à chaque fois que vous avez besoin de scorer la probabilité d’un client de patir.
Chargement du modèle
import joblib
#load the model
loaded_rf = joblib.load('random_forest_model.pkl')
print(loaded_rf)
# Now you can use the loaded model to make predictions
#prediction = loaded_rf.predict(new_customer)
#print(prediction)
Chargement des données
#load the datas
import pickle
with open('X_test.pkl', 'rb') as f:
X_test = pickle.load(f)
with open('y_test.pkl', 'rb') as f:
y_test = pickle.load(f)
X_test.head()
On rejoue le test, X_test contient les clients, et on passe en argument de la fonction predict qui va nous retourner la variable prediction, qui est une Serie, contenant des 0 et 1, 0 correspondant à « No » et 1 à « Yes ».
# Now you can use the loaded model to make predictions
from sklearn.metrics import accuracy_score
prediction = loaded_rf.predict(X_test)
#la série qui contient les outcome (churn si 1 , 0 si no churn)
print(prediction)
[0 0 1 ... 0 1 1]
Ce qu'on voit c'est la colonne churn
Test avec un sous ensemble
Ici je simule le fait que j’ai un nouveau client à scorer, c’est donc une ligne (un seul client), je passe cette ligne en argument de la fonction predict, qui va nous retourner un Serie avec un seul élément (on n’a scoré qu’un client), ici on obtient un « Yes ».
# test on row
# subset = X_test[X_test['TotalCharges']<100]
# extract a line from X_test
oneline = X_test.loc[[6125]]
#convert to dataframe
#oneline = oneline.to_frame()
import pandas as pd
isinstance(oneline,pd.DataFrame) # test if it's DataFrame
print(oneline)
oneline.head()
prediction = loaded_rf.predict(oneline)
print(prediction)
[1] // Churn