Entrées et sorties des prédictions
- Leçon 1
- 40 min
- Module 1 : Définir le contrat d'inférence
Leçon 1 sur 15 · Déployer des modèles d'IA pour de vrais utilisateursEntrées et sorties des prédictions
1. Définir le contrat d'inférence
2. Choisir un mode de service
3. Créer le service de prédiction
4. Choisir l'infrastructure et mesurer
5. Mettre en production, surveiller et rétablir
Ce que vous saurez faire
Notez exactement ce que le modèle d’escalade accepte et retourne, pour refuser un billet de soutien invalide avant son évaluation.
Avant de commencerCe qu’il faut savoir et avoir sous la main
Ce que vous devez savoir
- Le modèle et son artefact,
model/avecescalation_model.joblibetmodel_info.json, de Empaqueter les artefacts dans Apprentissage automatique : du problème à un modèle fiable. - Le seuil de 0,19 et pourquoi un score n’est pas une probabilité calibrée, de Seuils et calibration dans le cours d’apprentissage automatique.
- Les modèles Pydantic,
extra="forbid"et la réponse422, de Validation et réponses d’erreur dans Créer et déployer votre première API.
Ce qu’il vous faut sur votre ordinateur
- Python 3.12 ou une version plus récente.
- Le dossier de projet
~/projects/ticket-modeldu cours d’apprentissage automatique, avec son.venvet le dossiermodel/créé parpython train.pydans Empaqueter les artefacts : exemple détaillé. - Si vous ne l’avez pas, téléchargez le dossier
C17/startdes exercices pratiques sous le nom~/projects/ticket-model. Il contientmodel/, avec les trois fichiers du modèle, etdata/, mais aucun.venv. Vérifiez le fichier du modèle avant de le charger. La commande et l’empreinte attendue se trouvent dans Confiance et intégrité des artefacts : vérifier un fichier téléchargé.
Testé avec Python 3.14.6 sur macOS, avec une puce Apple silicon. Les étapes pour Windows et Linux n’ont pas été testées.
Un nouveau service pour un seul modèle
Section intitulée « Un nouveau service pour un seul modèle »Dans le cours sur les API et L’essentiel de Docker et du nuage pour les développeurs en IA, vous avez créé ticket-api. Cette API attribue une catégorie à chaque billet. Dans ce cours, vous créez un deuxième petit service, escalation-service. Il répond à une question : quel est le risque d’escalade de ce nouveau billet?
Pourquoi ne pas ajouter le modèle à ticket-api? Avec un modèle par service, chaque modèle a sa propre image, ses propres mises en production et ses propres retours en arrière. Un nouveau modèle d’escalade n’exige jamais une nouvelle mise en production de l’API de catégories. Le service de soutien appelle ticket-api pour la catégorie et escalation-service pour le risque. Les données sont celles de Larkfield, utilisées dans le cours d’apprentissage automatique. Elles sont synthétiques.
Un formulaire que l’agent peut refuser
Section intitulée « Un formulaire que l’agent peut refuser »Au bureau des passeports, un agent vérifie votre formulaire avant que quelqu’un le traite. Une date de naissance dans le futur ou un nom vide, et l’agent vous rend le formulaire au comptoir. Personne n’imprime un passeport à partir d’un formulaire invalide. Personne ne devine ce que vous vouliez dire.
Un modèle n’a pas d’agent. Il prend tous les nombres que vous lui donnez et retourne un score.
Le problème : le modèle répond à tout
Section intitulée « Le problème : le modèle répond à tout »Priya charge le pipeline du cours d’apprentissage automatique et évalue un billet d’exemple : priorité 1, commande de 89,50 $, 112 mots. Elle remplace ensuite la priorité par des valeurs que le service de soutien n’utilise jamais : 0 et 4. Exécutez ceci dans ~/projects/ticket-model, avec son .venv activé. (Si vous avez téléchargé C17/start, il n’a pas de .venv. Exécutez ceci plus tard, après l’étape 3 de Écrire le contrat, avec le .venv de escalation-service activé.)
import joblibimport pandas as pd
pipeline = joblib.load("model/escalation_model.joblib")ticket = dict(channel="email", team="payment", segment="home", region="west", priority=1, order_value=89.5, word_count=112, customer_tenure_days=400, prior_tickets_90d=2, created_hour=10, prior_escalations_90d=1)for priority in [0, 1, 2, 3, 4]: row = pd.DataFrame([{**ticket, "priority": priority}]) print(priority, round(pipeline.predict_proba(row)[0, 1], 4))La sortie :
0 0.57021 0.35072 0.18023 0.08214 0.0351- priorités 1 à 3 : valeurs utilisées par le service de soutien
- priorités 0 et 4 : valeurs qui n'existent pas
Description textuelle du graphique
Le billet d’exemple, évalué par le pipeline du cours d’apprentissage automatique, sans vérification préalable. La priorité 1 donne 0,351, la priorité 2 donne 0,180 et la priorité 3 donne 0,082. La priorité 0, qui n’existe pas, donne 0,570, le score le plus élevé, et le billet est signalé. La priorité 4, qui n’existe pas non plus, donne 0,035.
Aucune erreur, aucun avertissement. Le pipeline a prolongé la ligne apprise : un nombre plus bas signifie un billet plus urgent. Ainsi, 0 semble « plus urgent qu’urgent ». Priya a essayé d’autres erreurs sur le même billet :
| Modification | Score | Ce que le pipeline a fait |
|---|---|---|
created_hour 25 |
0,3213 | A évalué une heure qui n’existe pas |
word_count −50 |
0,1775 | A évalué un compte négatif; le signalement est passé de vrai à faux |
channel "Email" |
0,4215 | A évalué un canal jamais vu comme « aucun des canaux connus » |
order_value 0 |
0,3240 | A évalué une commande de 0 $ |
order_value vide (NaN) |
0,3597 | A inséré la médiane des données d’entraînement et activé l’indicateur « manquant » |
priority "high" (élevée) |
erreur | ValueError: Cannot use median strategy with non-numeric data (impossible d’utiliser la stratégie de médiane avec des données non numériques) |
Seule la dernière erreur arrête le pipeline, et son message ne nomme même pas le champ. Toutes les autres erreurs deviennent des scores qui semblent normaux. Le service de soutien agirait en fonction de ces scores.
Utiliser un modèle entraîné pour évaluer un nouveau cas s’appelle l’inférence. L’entraînement a lieu une seule fois. L’inférence a lieu pour chaque nouveau billet, longtemps après l’entraînement, avec des entrées envoyées par un autre programme. Le service doit donc vérifier les entrées au moment de l’inférence, avant que le modèle les reçoive.
L’idée : un contrat d’inférence
Section intitulée « L’idée : un contrat d’inférence »Les règles du formulaire sont imprimées dessus. Le service a besoin de la même chose : une liste écrite décrivant le billet attendu et la réponse retournée. C’est le contrat d’inférence. Les deux côtés s’y fient : le client sait quoi envoyer, et le modèle sait ce qu’il reçoit.
Pour le modèle d’escalade, le contrat précise six choses :
- Noms et ordre. Les 11 variables utilisées pour entraîner le pipeline, dans l’ordre de l’entraînement, sans autre clé.
- Types. Du texte pour les quatre catégories (
channel,team,segment,region), des nombres entiers pour les comptes et les heures, et un nombre pourorder_value. - Plages. Par exemple,
priorityde 1 à 3,created_hourde 0 à 23,order_valuede 0 à 5 000. - Valeurs manquantes. Quelles variables peuvent être nulles, et ce que signifie une valeur nulle.
- Catégories inconnues. Une valeur de catégorie jamais vue par le modèle est acceptée, avec un avertissement. Le service les compte, car leur présence en grand nombre indique que les entrées ont changé (Planifier les vérifications en production dans le cours d’apprentissage automatique).
- La sortie. Ce que le service retourne, et ce que chaque valeur signifie.
Tomás demande : « Le predict.py du cours d’apprentissage automatique vérifiait déjà le fichier CSV. Pourquoi recommencer? » Parce que predict.py vérifiait un fichier écrit par le propre traitement de Priya. Un service reçoit des requêtes de programmes que d’autres personnes écrivent et modifient. Après une seule mise en production incorrecte du client, tous les billets peuvent avoir une priority de 0.
Un contrat a un coût. Il est strict par choix : un client avec un ancien format reçoit une erreur jusqu’à ce que son équipe le corrige. Modifier les entrées du modèle modifie maintenant le contrat, et les deux côtés doivent accepter ce changement. Pour un essai rapide dans un carnet (notebook) Jupyter, vous n’en avez pas besoin. Pour un service appelé par d’autres programmes, oui.
Une valeur absente n’est pas une valeur nulle
Section intitulée « Une valeur absente n’est pas une valeur nulle »La règle 4 demande de l’attention. order_value peut être vide pour une vraie raison : le billet n’a pas de commande. Comparez quatre requêtes qui diffèrent sur un seul point :
| La requête contient | Signification | Ce que dit le contrat |
|---|---|---|
"order_value": 89.5 |
Une commande de 89,50 $ | Accepté |
"order_value": null |
Il n’y a pas de commande | Accepté : le pipeline insère la médiane des données d’entraînement, comme pendant l’entraînement |
"order_value": 0 |
Une commande de 0 $ | Accepté, mais ce n’est pas la même chose qu’une absence de commande |
Aucune clé order_value |
Le client l’a oubliée ou envoie un ancien format | Rejeté |
La mesure précédente montre pourquoi c’est important : une valeur nulle donne 0,3597 et 0 donne 0,3240 pour le même billet. Un client qui écrit 0 pour « aucune commande » modifie un peu chaque score, sans qu’aucune erreur apparaisse. Le contrat exige donc toutes les clés. Une valeur nulle dit « Je sais que cette valeur est vide ». Une clé absente ne dit rien, et le service ne doit pas deviner.
Seules deux variables peuvent être nulles : order_value (aucune commande) et customer_tenure_days (un invité n’a pas de première commande). Les neuf autres ne peuvent jamais être nulles.
La sortie : un score, un indicateur et une version
Section intitulée « La sortie : un score, un indicateur et une version »Une réponse valide du service terminé ressemble à ceci (une réponse réelle) :
{"ticket_id":"T-120001","score":0.35065699401024064,"flag":true,"threshold":0.19,"model_version":"1.0.0","warnings":[]}Chaque valeur a une seule signification :
score(score), de 0 à 1. Un score plus élevé indique un risque d’escalade plus élevé. Le score classe les billets; ce n’est pas une probabilité calibrée. Le cours d’apprentissage automatique a montré dans Seuils et calibration que ces scores ne correspondent pas aux taux réels d’escalade. Personne ne doit donc lire 0,35 comme « une probabilité de 35 % ».flag(indicateur) : vrai quand le score atteint ou dépasse le seuil. Le billet va à l’équipe expérimentée.threshold(seuil) : 0,19. Il appartient à la version du modèle. Il a été choisi pour que ce modèle signale environ 20 % des billets, soit la capacité de révision de Grace. Un nouveau modèle a besoin de son propre seuil.model_version(version du modèle), pour relier chaque réponse au modèle qui l’a produite.warnings(avertissements), par exemple une catégorie inconnue. Le score est moins certain.
Vous écrivez maintenant la partie du contrat qui concerne les entrées. Le service et ses réponses arrivent au module 3.
Écrire le contrat
Section intitulée « Écrire le contrat »Créez le projet et son environnement virtuel, comme dans le cours sur les API.
-
Créez les dossiers et allez dans le dossier du projet.
Fenêtre de terminal mkdir -p ~/projects/escalation-service/escalation ~/projects/escalation-service/testscd ~/projects/escalation-serviceFenêtre de terminal New-Item -ItemType Directory -Force $HOME\projects\escalation-service\escalation, $HOME\projects\escalation-service\testscd $HOME\projects\escalation-service -
Créez
requirements.txt. Les quatre paquets du modèle ont les mêmes versions que dans le cours d’apprentissage automatique. Le modèle doit fonctionner avec les versions qui ont servi à l’entraîner.requirements.txt # Needed to run the servicefastapi[standard]==0.142.2scikit-learn==1.9.1pandas==3.0.6numpy==2.5.3joblib==1.6.0# Needed to work on the servicepytest==9.1.1httpx2==2.13.1 -
Créez l’environnement virtuel, activez-le et installez les paquets.
Fenêtre de terminal python3 -m venv .venvsource .venv/bin/activatepython -m pip install -r requirements.txtFenêtre de terminal python -m venv .venv.venv\Scripts\Activate.ps1python -m pip install -r requirements.txt -
Créez
.gitignore. Certaines lignes concernent des fichiers que les prochains modules créeront..gitignore .venv/__pycache__/.envout/*.logbench/results.jsonl -
Créez deux fichiers vides :
escalation/__init__.pyettests/__init__.py. Ils transforment les deux dossiers en paquets Python, pour que les tests puissent importerescalation.
Écrivez maintenant le contrat. Pydantic vérifie les noms, les types et les plages, comme dans le cours sur les API. La première partie du fichier énumère les variables et leurs règles :
"""The inference contract: what a ticket must look like before the model scores it.
The model learned from 11 features. This file says, for each one, its name,its type, the values it may take, and whether it may be missing. The API andthe batch job both check tickets with these classes, so the two paths alwaysapply the same rules."""
import math
import pandas as pdfrom pydantic import BaseModel, ConfigDict, Field
# The order of the columns that the pipeline was fitted on (C05 ticket_model.py).FEATURES = [ "channel", "team", "segment", "region", "priority", "order_value", "word_count", "customer_tenure_days", "prior_tickets_90d", "created_hour", "prior_escalations_90d",]CATEGORIES = ["channel", "team", "segment", "region"]
class TicketFeatures(BaseModel): """The 11 features of one ticket, as they are when the ticket is created."""
model_config = ConfigDict(extra="forbid")
channel: str = Field(min_length=1, max_length=30, description="How the customer contacted us.") team: str = Field(min_length=1, max_length=30, description="The team that owns the ticket.") segment: str = Field(min_length=1, max_length=30, description="The kind of customer.") region: str = Field(min_length=1, max_length=30, description="The customer's region.") priority: int = Field(ge=1, le=3, description="1 is the most urgent, 3 the least.") order_value: float | None = Field( ge=0, le=5000, description="The order's value in dollars. Null if there is no order." ) word_count: int = Field(ge=0, le=10000, description="Words in the first message.") customer_tenure_days: int | None = Field( ge=0, le=20000, description="Days since the customer's first order. Null for a guest." ) prior_tickets_90d: int = Field(ge=0, le=500, description="The customer's tickets in the last 90 days.") created_hour: int = Field(ge=0, le=23, description="The hour the ticket was created (0 to 23).") prior_escalations_90d: int = Field(ge=0, le=500, description="The customer's escalations in the last 90 days.")Trois détails appliquent les règles. extra="forbid" refuse toute autre clé. Aucun champ n’a de valeur par défaut. Chaque clé est donc obligatoire, même order_value : float | None permet une valeur nulle, pas une clé absente. Les catégories sont du texte simple, pas une liste fixe, car une valeur inconnue doit passer avec un avertissement.
La deuxième partie va à la fin du même fichier. Elle ajoute l’identifiant du billet et trois fonctions auxiliaires :
class TicketIn(TicketFeatures): """A ticket to score: its ID and its features."""
model_config = ConfigDict( extra="forbid", json_schema_extra={ "examples": [ { "ticket_id": "T-120001", "channel": "email", "team": "payment", "segment": "home", "region": "west", "priority": 1, "order_value": 89.5, "word_count": 112, "customer_tenure_days": 400, "prior_tickets_90d": 2, "created_hour": 10, "prior_escalations_90d": 1, } ] }, )
ticket_id: str = Field(pattern=r"^T-\d{6}$", description="The ticket's ID, for example T-120001.")
def unknown_categories(ticket: TicketFeatures, known: dict[str, list[str]]) -> list[str]: """Warnings for category values that the model never saw in training.
They are allowed: the one-hot encoder gives them no column, so the model scores them as "none of the known values". Many of them mean the input has changed, so the service reports and counts them. """ warnings = [] for name in CATEGORIES: value = getattr(ticket, name) if value not in known[name]: warnings.append(f"{name}: {value!r} is a value the model never saw") return warnings
def to_frame(tickets: list[TicketFeatures]) -> pd.DataFrame: """The tickets as a table with the training columns, in the training order.
A missing number (None) becomes NaN, so the pipeline's imputer fills it with the training median, exactly as in training. Do not fill it here. """ rows = [t.model_dump(include=set(FEATURES)) for t in tickets] frame = pd.DataFrame(rows, columns=FEATURES) for name in FEATURES: if name not in CATEGORIES: frame[name] = frame[name].astype("float64") return frame
def is_missing(value) -> bool: return value is None or (isinstance(value, float) and math.isnan(value))to_frame construit les colonnes dans l’ordre de FEATURES. scikit-learn vérifie aussi les noms de colonnes vus pendant l’entraînement. Un tableau avec d’autres noms échoue donc avec une erreur explicite. Un nombre manquant reste NaN, et l’imputeur du pipeline le remplace exactement comme pendant l’entraînement. Si le service le remplaçait par 0, il ferait l’erreur silencieuse du tableau précédent. is_missing est une petite fonction auxiliaire pour les prochains modules.
Essayer le contrat
Section intitulée « Essayer le contrat »Lancez python dans ~/projects/escalation-service et envoyez deux erreurs à la fois :
from escalation.contract import TicketIn
good = TicketIn.model_config["json_schema_extra"]["examples"][0]TicketIn(**{**good, "priority": "high", "created_hour": 24})La sortie (abrégée) :
pydantic_core._pydantic_core.ValidationError: 2 validation errors for TicketInpriority Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='high', input_type=str]...created_hour Input should be less than or equal to 23 [type=less_than_equal, input_value=24, input_type=int]...Les deux champs sont nommés, avec la raison. Maintenant, omettez order_value :
missing = {k: v for k, v in good.items() if k != "order_value"}TicketIn(**missing)La sortie (abrégée) :
pydantic_core._pydantic_core.ValidationError: 1 validation error for TicketInorder_value Field required [type=missing, input_value={'ticket_id': 'T-120001',...ior_escalations_90d': 1}, input_type=dict]...Enfin, un canal inconnu. Les catégories connues se trouvent dans le model_info.json du cours d’apprentissage automatique :
import jsonfrom escalation.contract import unknown_categories
known = json.load(open("../ticket-model/model/model_info.json"))["features"]["categories"]unknown_categories(TicketIn(**{**good, "channel": "social"}), known)La sortie :
["channel: 'social' is a value the model never saw"]Quittez Python avec exit().
Explorer : accepté, averti ou rejeté?
Section intitulée « Explorer : accepté, averti ou rejeté? »L’explorateur montre les réponses réelles du service terminé du module 3, pour une seule modification à la fois. Ce service utilise exactement le contract.py que vous venez d’écrire.
Explorer
Le contrat : accepté, accepté avec un avertissement ou rejeté?
Prédisez d’abord : Pour chaque modification du billet d'exemple, le service l'acceptera-t-il, l'acceptera-t-il avec un avertissement ou le rejettera-t-il avant le modèle? Examinez bien order_value supprimé et order_value nul.
- Résultat
- Accepté
- Statut HTTP
- 200
- Score
- 0,3507
Aucun changement : le service attribue un score au billet et répond 200. Toutes les clés sont présentes, chaque valeur a le bon type et est dans sa plage, et chaque catégorie est connue du modèle.
- échoue : rejeté avec 422
- avertissement : score attribué, et signalé
- la règle qui le laisse passer
| Règle | Ce qu’elle dit | Résultat |
|---|---|---|
| Noms | Seulement ticket_id et les 11 caractéristiques. Aucune autre clé. | réussit |
| Toutes les clés présentes | Une clé absente est une erreur, même pour une valeur qui peut être nulle. | réussit |
| Types | Du texte pour les 4 catégories, des nombres entiers pour les comptes, un nombre pour order_value. | réussit |
| Plages | Par exemple priority de 1 à 3, created_hour de 0 à 23, order_value de 0 à 5 000. | réussit |
| Null | Permis seulement pour order_value (aucune commande) et customer_tenure_days (un invité). | réussit |
| Catégories connues | Une valeur que le modèle n’a jamais vue est acceptée, avec un avertissement. | réussit |
Changement au billet d’exemple : aucun : le billet d’exemple tel quel
La réponse du service : 200
{
"ticket_id": "T-120001",
"score": 0.35065699401024064,
"flag": true,
"threshold": 0.19,
"model_version": "1.0.0",
"warnings": []
}Vraies réponses du service avec le paquet escalation-1.0.0. L’identifiant de la requête change à chaque appel, donc il s’affiche comme « ... ».
Description textuelle de l’explorateur
L’explorateur commence avec le billet d’exemple sans modification : toutes les règles passent, le service répond 200 et le score est 0,3507.
Les autres modifications et les réponses réelles : priority « high » (élevée) échoue à la règle de type : 422 avec fields: ["priority"] (champs : priority). created_hour 24 échoue à la règle de plage : 422, fields: ["created_hour"] (champs : created_hour). La clé order_value supprimée échoue à la règle « toutes les clés présentes » : 422, fields: ["order_value"] (champs : order_value). order_value nul passe, car une valeur nulle est permise pour cette variable : 200, score 0,3597. team nul échoue à la règle des valeurs nulles : 422, fields: ["team"] (champs : team). Un champ supplémentaire colour échoue à la règle des noms : 422, fields: ["colour"] (champs : colour). channel « social » passe toutes les règles, mais reçoit un avertissement, car le modèle n’a jamais vu ce canal : 200, score 0,4215, warnings: ["channel: 'social' is a value the model never saw"] (avertissements : channel : « social » est une valeur jamais vue par le modèle). Chaque réponse 422 contient le code invalid_ticket (billet invalide) et le message « The ticket does not match the model’s contract. » (Le billet ne respecte pas le contrat du modèle.)
Deux des sept modifications ont été acceptées : une valeur nulle là où elle est permise, et une catégorie inconnue avec avertissement. Le service a refusé les cinq autres, avec le nom du champ, avant l’exécution du modèle.
Tester le contrat
Section intitulée « Tester le contrat »Les tests empêchent les règles de changer par accident. Le billet d’exemple va dans une fixture (données de préparation des tests). Chaque test modifie ainsi une seule chose dans une copie neuve :
import pytest
# The example ticket of contract.py. Each test changes one thing in a copy.TICKET = { "ticket_id": "T-120001", "channel": "email", "team": "payment", "segment": "home", "region": "west", "priority": 1, "order_value": 89.5, "word_count": 112, "customer_tenure_days": 400, "prior_tickets_90d": 2, "created_hour": 10, "prior_escalations_90d": 1,}
@pytest.fixturedef ticket(): return dict(TICKET)import pytestfrom pydantic import ValidationError
from escalation.contract import FEATURES, TicketIn, to_frame, unknown_categories
def test_a_good_ticket_passes(ticket): assert TicketIn(**ticket).priority == 1
@pytest.mark.parametrize( "change, field", [ ({"priority": "high"}, "priority"), ({"priority": 4}, "priority"), ({"created_hour": 24}, "created_hour"), ({"word_count": -1}, "word_count"), ({"channel": ""}, "channel"), ({"team": None}, "team"), ({"colour": "red"}, "colour"), ],)def test_a_bad_value_is_rejected(ticket, change, field): with pytest.raises(ValidationError) as error: TicketIn(**{**ticket, **change}) assert field in str(error.value)
def test_null_is_allowed_only_where_the_contract_says(ticket): TicketIn(**{**ticket, "order_value": None, "customer_tenure_days": None}) with pytest.raises(ValidationError): TicketIn(**{**ticket, "word_count": None})
def test_a_missing_field_is_not_the_same_as_null(ticket): del ticket["order_value"] with pytest.raises(ValidationError): TicketIn(**ticket)
def test_an_unknown_category_is_a_warning_not_an_error(ticket): known = {"channel": ["email"], "team": ["payment"], "segment": ["home"], "region": ["west"]} warnings = unknown_categories(TicketIn(**{**ticket, "channel": "social"}), known) assert warnings == ["channel: 'social' is a value the model never saw"]
def test_the_frame_has_the_training_columns_in_order(ticket): frame = to_frame([TicketIn(**{**ticket, "order_value": None})]) assert list(frame.columns) == FEATURES assert frame["order_value"].isna().all() # NaN for the imputer, not 0Exécutez les tests dans ~/projects/escalation-service, avec .venv activé :
python -m pytestLa sortie (abrégée) :
collected 12 items
tests/test_contract.py ............ [100%]
============================== 12 passed in 0.26s ==============================Le test paramétré compte comme sept tests, un pour chaque valeur invalide. La première exécution peut prendre quelques secondes, pendant que Python importe pandas.
À vous de jouer : priorité 2,5 et priorité 2,0
Section intitulée « À vous de jouer : priorité 2,5 et priorité 2,0 »L’équipe de Grace demande ce qui arrive si un client envoie priority avec la valeur 2,5 ou 2,0. Notez votre prédiction pour chaque valeur. Essayez ensuite les deux en Python avec TicketIn. Ajoutez la valeur rejetée à la liste dans test_a_bad_value_is_rejected.
Afficher la réponse
2,5 est rejeté : Input should be a valid integer, got a number with a fractional part [type=int_from_float, input_value=2.5, input_type=float] (l’entrée doit être un entier valide; le nombre reçu a une partie fractionnaire). 2,0 est accepté et devient l’entier 2, car Pydantic convertit un nombre sans partie fractionnaire. Le texte "2" est aussi accepté, pour la même raison. Cela ne pose aucun risque pour le modèle : 2,0 et 2 représentent la même priorité.
Ajoutez une ligne à la liste :
({"priority": 2.5}, "priority"),python -m pytest indique alors 13 passed (13 tests réussis). La version du cours contient 12 tests du contrat. Si vous gardez cette ligne, les leçons suivantes afficheront donc un test de moins que chez vous.
Erreur courante
Une majuscule change le score
Symptôme : une nouvelle version du service de soutien envoie "channel": "Email". Le service répond 200, mais les scores sont plus élevés qu’avant, et chaque réponse contient un avertissement. Voici une réponse réelle du service terminé :
{"ticket_id":"T-120001","score":0.42145836840785755,"flag":true,"threshold":0.19,"model_version":"1.0.0","warnings":["channel: 'Email' is a value the model never saw"]}Avec "email", le même billet obtient 0,3507.
Cause : le modèle a appris le canal email, en minuscules. Email est un texte différent. L’encodeur one-hot (encodage avec une colonne par catégorie) ne lui attribue donc aucune colonne. Le modèle lui donne un score comme s’il ne correspondait à « aucun des canaux connus ». Ce score est le même que pour social, un canal que le modèle n’a jamais vu.
Solution : le contrat a fait son travail : il a accepté le billet et signalé la valeur inconnue dans warnings (avertissements). Demandez à l’équipe cliente d’envoyer les valeurs exactes de model_info.json et de les tester. Ne changez pas la valeur dans le service (par exemple avec .lower()) sans consulter la personne responsable du modèle. Le contrat doit dire la même chose des deux côtés.
Pour un vrai service d’IA : les avertissements ne servent que si quelqu’un les lit; comptez-les donc. Une seule valeur inconnue est du bruit. Une hausse soudaine signifie qu’un client ou les données ont changé. Le module 5 ajoute ce compte aux indicateurs de santé du service.
Vérifiez votre compréhension
Un client envoie un billet sans la clé order_value. Un autre envoie order_value avec la valeur null. Pourquoi le contrat rejette-t-il le premier et accepte-t-il le second?
Afficher la réponse
Null indique que la valeur est vide de façon connue : le billet n’a pas de commande. Le pipeline traite ce cas comme pendant l’entraînement, avec la médiane d’entraînement et un indicateur de valeur manquante. Une clé absente ne dit rien : le client a peut-être oublié le champ ou envoyé un ancien format. Si le service devinait (null? 0?), le score pourrait changer sans aucune erreur : 0 donne 0,3240, tandis que null donne 0,3597. Chaque clé est donc obligatoire, et seules deux variables peuvent être null.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Omar lit le score 0,35 dans une réponse et dit : ce billet a donc 35 % de chances d'être transféré. Que lui dites-vous?
Afficher la réponse
Le score classe les billets : un score plus élevé signifie un risque plus élevé. Il n’est pas calibré; 0,35 ne signifie donc pas une probabilité de 35 % (voir Seuils et calibration dans le cours Apprentissage automatique : du problème à un modèle fiable). Le service de soutien utilise plutôt l’indicateur. Le score atteint au moins 0,19, le seuil du modèle 1.0.0; le billet est donc envoyé à l’équipe expérimentée. Le seuil appartient à cette version du modèle.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Faites une prédiction : un client envoie une priorité de 0. Que fait le pipeline brut, et que fait le service avec le contrat?
Afficher la réponse
Le pipeline brut lui donne un score sans signaler de problème : 0,5702 pour le billet d’exemple, le plus élevé parmi toutes les priorités. Le billet est donc signalé. Le service avec le contrat le rejette avec 422 et fields: ["priority"], car la priorité doit être de 1 à 3. Le modèle ne voit jamais le billet.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Ce cours se termine par trois études de cas. Chacune utilise un autre modèle pour traiter des données réelles, avec un contrat comme celui-ci.
Études de cas de ce cours
À la fin du cours, vous appliquez ce que vous avez appris à un projet réaliste, des données jusqu’à la recommandation. Terminez-en au moins une pour obtenir le certificat; les autres sont facultatives.
- Prédire le défaut de crédit par lots et en ligneServir un même artefact de défaut de crédit de deux façons, une tâche mensuelle de prédiction par lots et un point de terminaison en ligne, et prouver qu'ils donnent les mêmes scores.
- Mettre en production un nouveau modèle de prix des maisonsMettre en production un modèle de prix des maisons réentraîné derrière l'API du cours sur les API : l'exécuter en mode fantôme, le comparer sur le vrai trafic, le promouvoir, puis revenir en arrière.
- Prédire les réadmissions à l'hôpital de façon asynchroneAccepter un gros fichier de congés d'hôpital comme tâche en file d'attente, calculer les prédictions en arrière-plan et laisser l'appelant récupérer les résultats plus tard.
Récapitulatif
- L’inférence consiste à utiliser un modèle entraîné sur un nouveau cas. Le modèle attribue un score à tout, même aux valeurs inexistantes : la priorité 0 a obtenu 0,5702.
- Le contrat d’inférence définit la forme d’un billet : noms et ordre, types, plages, valeurs manquantes et catégories inconnues. Il définit aussi le sens de la réponse : un score de classement, un indicateur, le seuil, la version du modèle et les avertissements.
- Une valeur absente n’est pas la même chose que null. Chaque clé est obligatoire, et seuls
order_valueetcustomer_tenure_dayspeuvent être null. - Une catégorie inconnue est acceptée avec un avertissement, et non refusée.
contract.pycontient les règles, et 12 tests les font respecter.
Ensuite, vous empaquetez le modèle avec tout ce dont il a besoin pour fonctionner : son prétraitement, ce contrat, ses versions et un ensemble de cas de test fixes.
Leçon suivante : Empaqueter les dépendances du modèle