Vérifier la parité
- Leçon 9
- 40 min
- Module 3 : Créer le service de prédiction
Leçon 9 sur 15 · Déployer des modèles d'IA pour de vrais utilisateursVérifier la parité
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
Prouvez que le service en marche donne les mêmes scores que l’entraînement pour des billets de soutien fixes. Testez des entrées normales, manquantes, extrêmes et inconnues. Puis détectez immédiatement les écarts.
Avant de commencerCe qu’il faut savoir et avoir sous la main
Ce que vous devez savoir
- Les 14 cas de parité dans
parity_cases.csvet l’autovérification deload_bundle(), présentés dans Empaqueter les dépendances du modèle : les cas de parité et Confiance et intégrité des artefacts : six barrières, dans l’ordre. - Comment le pipeline remplit les nombres manquants (imputation par la médiane avec indicateurs), présenté dans Prétraiter avec un pipeline : Problème 2 : les valeurs manquantes du cours Apprentissage automatique : du problème à un modèle fiable.
- Le service et ses routes, présentés dans Charger et initialiser et Connecter l’API.
Ce qu’il vous faut sur votre ordinateur
- Le projet
~/projects/escalation-serviceavecescalation/routes.pyettests/test_api.py, présenté dans Connecter l’API : routes.py.python -m pytestdonne28 passed(28 tests réussis). MODEL_SHA256défini dans votre terminal, comme dans Charger et initialiser : Démarrer le service.- Si votre projet manque ou ne fonctionne plus, copiez l’instantané
end-of-m02et refaites les deux leçons précédentes de ce module. Utiliser un instantané présente les étapes.
Testé avec Python 3.14.6 et Docker Desktop (Docker Engine 29.6.1) sur macOS, avec une puce Apple. Les étapes pour Windows et Linux n’ont pas été testées.
Le même pain dans une deuxième boutique
Section intitulée « Le même pain dans une deuxième boutique »Une boulangerie ouvre une deuxième boutique. Les nouveaux boulangers reçoivent la même recette et la même farine. Chaque matin, la propriétaire achète quelques produits fixes dans les deux boutiques : le même pain et le même gâteau. Elle les compare côte à côte. Elle ne goûte pas un seul pain pour décider que tout va bien. Un défaut du nouveau four apparaît seulement dans les produits qui ont besoin de sa chaleur supérieure.
Votre service est la deuxième boutique. Pendant l’entraînement, les billets recevaient un score d’une certaine façon, avec un tableau de lignes et predict_proba(). Le service calcule leurs scores autrement : JSON, le contrat, to_frame(), puis le pipeline. Les deux chemins doivent donner le même score pour le même billet.
Le problème : un bon score prouve peu de choses
Section intitulée « Le problème : un bon score prouve peu de choses »Tomás a changé une ligne dans une copie du service pour « nettoyer » l’entrée. Maintenant, to_frame() remplace un nombre manquant par 0. Puis il a vérifié le service : le journal de démarrage, /ready et le billet d’exemple.
INFO escalation: model 1.0.0 loaded (bundle 8f233029aa4c...) in 660 ms{"status":"ready","model_version":"1.0.0"}{"ticket_id":"T-120001","score":0.35065699401024064,"flag":true,"threshold":0.19,"model_version":"1.0.0","warnings":[]}L’ensemble empaqueté a réussi les six vérifications, y compris l’autovérification. /ready indique que le service est prêt. Le billet d’exemple reçoit exactement le bon score. « Ça marche. On le met en production. »
Ça ne marche pas. Le billet d’exemple n’a aucune valeur manquante, donc le changement ne le touche pas. Envoyez un client invité, avec customer_tenure_days nul, et le service répond 0,250 au lieu de 0,145. Le billet est maintenant signalé et passe à l’équipe expérimentée. Le service traite l’invité comme un client depuis 0 jour, ce que le modèle a appris à considérer comme risqué. L’autovérification n’a pas détecté cela. Elle calcule les scores directement avec le pipeline, sans passer par le code propre au service.
Un service qui fait quelque chose que l’entraînement ne faisait pas présente un écart entre l’entraînement et le service. Le modèle est correct, mais il reçoit des nombres différents de ceux utilisés pour son apprentissage. Cet écart ne produit aucune erreur. Seule une comparaison des scores le révèle.
La parité : les mêmes billets, les mêmes scores
Section intitulée « La parité : les mêmes billets, les mêmes scores »La parité permet de vérifier cet écart. Envoyez des billets fixes au service en marche. Comparez chaque score du service avec le score d’entraînement du même billet. Le module 1 a enregistré ces billets dans l’ensemble empaqueté : parity_cases.csv. Chacun a un expected_score, calculé par make_bundle.py.
Description textuelle du diagramme
Le côté entraînement s’exécute une seule fois, dans make_bundle.py. Les 14 billets de parité, sous forme de tableau, passent ensemble à pipeline.predict_proba(). Leurs scores sont enregistrés dans expected_score, dans parity_cases.csv. Le côté service est le service en marche. Le même billet en JSON passe par la vérification du contrat et to_frame(). Ensuite, le prétraitement et le modèle calculent le score d’une ligne, et ce score revient dans la réponse. Une décision compare les deux scores : si la différence est au plus égale à la tolérance, 1e-9, il y a parité. Sinon, il y a un écart entre l’entraînement et le service. Vous devez trouver l’étape du service qui diffère.
Les 14 cas ne sont pas aléatoires. Chaque groupe teste un type d’entrée pour lequel un service peut se tromper :
- 6 billets normaux sans valeur manquante : le cas courant.
- 3 cas avec valeurs manquantes :
order_valuenul; un invité aveccustomer_tenure_daysnul; les deux. - 3 cas extrêmes : les limites du contrat. T-900004 a tous les risques à leur maximum et obtient exactement 1,0.
- 2 cas inconnus :
channelvautsocial;regionvautoverseasavecteamqui vautinstallation.
Pourquoi pas exactement les mêmes scores?
Section intitulée « Pourquoi pas exactement les mêmes scores? »Vous pourriez vous attendre à ce que chaque différence soit de 0. Voici un cas réel provenant du conteneur. Le score d’entraînement et le score du service pour le billet T-116942 :
training 0.12958362401471518served 0.12958362401471521Leurs 15 premiers chiffres concordent. Ils diffèrent seulement à la fin, de 3e-17. Un ordinateur stocke un score avec environ 16 chiffres significatifs. Le dernier chiffre peut changer pour des raisons sans conséquence. L’entraînement calculait les scores de 14 lignes ensemble, et le service calcule celui d’une seule ligne. Les additions se font donc dans un ordre différent. Un autre processeur (celui du conteneur Linux plutôt que macOS) peut utiliser d’autres instructions. De plus, le lecteur CSV rapide de pandas peut perdre le dernier chiffre d’un nombre à 17 chiffres en lisant parity_cases.csv. Il lit le score d’entraînement ci-dessus comme 0,1295836240147151. Ainsi, check_parity.py affiche 1.1e-16, et non 3e-17. Chaque effet est d’environ 1e-17 à 1e-16 : un 1 à la 16e ou 17e décimale.
La parité compare donc les scores avec une tolérance : la plus grande différence qui compte encore comme « identique ». Le service utilise 1e-9. Choisissez-la entre deux nombres. Le bruit d’arrondi est d’environ 1e-16. La tolérance doit donc être bien supérieure : 1e-9 est dix millions de fois plus grand. La décision compare un score à 0,19, qui a deux décimales. Vous devez donc détecter un vrai défaut bien avant qu’il atteigne 0,01. Or, 1e-9 est dix millions de fois plus petit. Une tolérance de 0 échouerait à cause du bruit. Une tolérance de 0,01 laisserait passer un vrai défaut qui déplace chaque score de 0,009.
check_parity.py
Section intitulée « check_parity.py »Créez le dossier parity et le fichier parity/check_parity.py. Il envoie chaque cas de parité au service en marche :
"""Compare the scores of a running service with the scores the model gave in training.
python parity/check_parity.py http://127.0.0.1:8000 bundles/escalation-1.0.0
Sends every parity case of the bundle to POST /v1/score and compares theserved score with expected_score, the score that the training code gave thesame ticket. A difference larger than the tolerance means the serving pathdoes something that training did not do (training-serving skew)."""
import argparseimport jsonimport osimport sysimport urllib.requestfrom pathlib import Path
import pandas as pd
TOLERANCE = 1e-9
def as_json_value(value): if pd.isna(value): return None return value.item() if hasattr(value, "item") else value
def served_score(url: str, ticket: dict, key: str | None) -> float: request = urllib.request.Request( f"{url}/v1/score", data=json.dumps(ticket).encode(), headers={"Content-Type": "application/json", **({"X-API-Key": key} if key else {})}, ) with urllib.request.urlopen(request, timeout=10) as response: return json.load(response)["score"]as_json_value() transforme une cellule CSV vide en null, pour que la valeur manquante arrive au service comme le contrat le prévoit. Le script utilise seulement la bibliothèque standard et pandas. Il fonctionne donc partout où les paquets du service sont installés. Si API_KEY est défini dans le terminal, il envoie la clé.
La deuxième partie lit les cas, compare les scores et affiche une ligne par cas :
def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("url") parser.add_argument("bundle", type=Path) parser.add_argument("--tolerance", type=float, default=TOLERANCE) args = parser.parse_args()
cases = pd.read_csv(args.bundle / "parity_cases.csv", keep_default_na=False, na_values=[""], dtype={"priority": "Int64", "word_count": "Int64", "customer_tenure_days": "Int64", "prior_tickets_90d": "Int64", "created_hour": "Int64", "prior_escalations_90d": "Int64"}) key = os.environ.get("API_KEY") failed = 0 print(f"{'case':8} {'ticket':9} {'expected':>10} {'served':>10} {'difference':>10}") for row in cases.to_dict("records"): expected = row.pop("expected_score") case = row.pop("case") ticket = {k: as_json_value(v) for k, v in row.items()} served = served_score(args.url, ticket, key) difference = abs(served - expected) ok = difference <= args.tolerance failed += not ok print(f"{case:8} {row['ticket_id']:9} {expected:10.6f} {served:10.6f} {difference:10.1e}" f"{'' if ok else ' FAIL'}") print(f"{len(cases) - failed} of {len(cases)} cases match within {args.tolerance:g}.") return 1 if failed else 0
if __name__ == "__main__": sys.exit(main())Deux détails comptent. keep_default_na=False, na_values=[""] traite seulement une cellule vide comme manquante. Une vraie catégorie comme unknown (inconnu) reste donc du texte. Les types Int64 conservent les nombres entiers, pour que priority soit envoyé comme 2, et non 2.0. Le code de sortie est 1 si un cas échoue. Un script de mise en production peut donc s’arrêter à ce signal.
Exemple détaillé : vérifier le service en marche
Section intitulée « Exemple détaillé : vérifier le service en marche »Démarrez le service dans un terminal avec fastapi run, comme dans Charger et initialiser. Dans un deuxième terminal, lancez la vérification :
python parity/check_parity.py http://127.0.0.1:8000 bundles/escalation-1.0.0La sortie :
case ticket expected served differencenormal T-116940 0.187598 0.187598 2.8e-17normal T-116941 0.064735 0.064735 0.0e+00normal T-116942 0.129584 0.129584 8.3e-17normal T-116943 0.179840 0.179840 5.6e-17normal T-116944 0.244735 0.244735 0.0e+00normal T-116945 0.113802 0.113802 8.3e-17missing T-900001 0.037929 0.037929 7.6e-17missing T-900002 0.144791 0.144791 5.6e-17missing T-900003 0.075978 0.075978 1.4e-17extreme T-900004 1.000000 1.000000 0.0e+00extreme T-900005 0.039165 0.039165 0.0e+00extreme T-900006 0.094671 0.094671 5.6e-17unknown T-900007 0.177527 0.177527 0.0e+00unknown T-900008 0.111725 0.111725 5.6e-1714 of 14 cases match within 1e-09.Les 14 cas concordent, et le code de sortie est 0. Même sur l’ordinateur qui a entraîné le modèle, 9 des 14 différences ne sont pas nulles : c’est le bruit d’arrondi. Vos différences peuvent varier, mais chacune est bien inférieure à 1e-9.
Le service fonctionnera réellement dans un conteneur, que vous construirez au module 4. Amira l’a déjà construit, l’a démarré avec 1 CPU et a exécuté la même commande pour le vérifier. Vous n’avez pas encore l’image. Lisez donc seulement sa sortie (abrégée) :
case ticket expected served differencenormal T-116940 0.187598 0.187598 2.8e-17normal T-116941 0.064735 0.064735 0.0e+00normal T-116942 0.129584 0.129584 1.1e-16...missing T-900001 0.037929 0.037929 9.7e-17...14 of 14 cases match within 1e-09.Linux, sur le processeur du conteneur, donne des derniers chiffres légèrement différents : jusqu’à 1,1e-16. C’est encore dix millions de fois inférieur à la tolérance. Vous construirez cette image et lancerez vous-même cette vérification dans Paramètres de ressources du conteneur : vérifier la parité dans le conteneur. Vérifiez la parité partout où le service fonctionne. Chaque nouvel environnement peut apporter ses propres petites différences, ou une grande.
La parité dans la suite de tests
Section intitulée « La parité dans la suite de tests »check_parity.py vérifie un service en marche de l’extérieur. Cette même vérification doit aussi faire partie des tests, pour s’exécuter à chaque changement du code. Créez tests/test_parity.py :
"""The served scores equal the training scores for every parity case."""
import pandas as pdimport pytest
from tests.conftest import BUNDLE
INTS = ["priority", "word_count", "customer_tenure_days", "prior_tickets_90d", "created_hour", "prior_escalations_90d"]CASES = pd.read_csv(BUNDLE / "parity_cases.csv", keep_default_na=False, na_values=[""], dtype={c: "Int64" for c in INTS})
def as_json(row: dict) -> dict: return {k: (None if pd.isna(v) else (v.item() if hasattr(v, "item") else v)) for k, v in row.items()}
@pytest.mark.parametrize("row", CASES.to_dict("records"), ids=lambda r: f"{r['case']}-{r['ticket_id']}")def test_served_score_matches_training(client, row): expected = row.pop("expected_score") row.pop("case") served = client.post("/v1/score", json=as_json(row)).json()["score"] assert served == pytest.approx(expected, abs=1e-9)parametrize crée un test par cas, nommé selon le groupe et le billet. Un échec indique donc immédiatement le cas. pytest.approx(expected, abs=1e-9) représente la tolérance. La fixture (préparation de test) client envoie les billets à travers toute l’application : le contrat, to_frame(), la route et la réponse. Lancez la suite :
python -m pytestLa sortie :
.......................................... [100%]42 passed in 3.44s42 tests : les 28 précédents et 14 tests de parité. C’est l’état de l’instantané end-of-m03.
Exploration : quelles erreurs la parité détecte-t-elle?
Section intitulée « Exploration : quelles erreurs la parité détecte-t-elle? »L’outil d’exploration montre des exécutions réelles. Amira a fait quatre copies du service : la bonne et trois copies contenant chacune une erreur côté service. Elle a exécuté chaque copie dans Docker avec 1 CPU et envoyé les 14 cas. Elle a utilisé la même comparaison que check_parity.py.
Explorer
Parité : quels cas échouent pour chaque erreur côté service?
Prédisez d’abord : Lesquels des 14 cas échoueront si le service remplace les nombres manquants par 0? Un signalement changera-t-il?
- Cas dans la tolérance
- 14 sur 14
- Plus grande différence
- 1.1e-16
- Signalements modifiés
- 0 sur 14
Le service tel que vous l’avez construit : les nombres manquants restent NaN, et l’imputeur du pipeline les remplit. 14 cas sur 14 concordent à 1e-09 près. Les différences ne sont pas toutes nulles, mais la plus grande, 1.1e-16, est bien sous la tolérance.
- réussi : dans la tolérance
- FAIL : au-dessus de la tolérance
Afficher les valeurs exactes
| Cas | Billet | Attendu | Servi | Différence | Résultat |
|---|---|---|---|---|---|
| normal | T-116940 | 0.187598 | 0.187598 | 2.8e-17 | réussi |
| normal | T-116941 | 0.064735 | 0.064735 | 0 | réussi |
| normal | T-116942 | 0.129584 | 0.129584 | 1.1e-16 | réussi |
| normal | T-116943 | 0.179840 | 0.179840 | 2.8e-17 | réussi |
| normal | T-116944 | 0.244735 | 0.244735 | 0 | réussi |
| normal | T-116945 | 0.113802 | 0.113802 | 4.2e-17 | réussi |
| missing | T-900001 | 0.037929 | 0.037929 | 9.7e-17 | réussi |
| missing | T-900002 | 0.144791 | 0.144791 | 5.6e-17 | réussi |
| missing | T-900003 | 0.075978 | 0.075978 | 2.8e-17 | réussi |
| extreme | T-900004 | 1.000000 | 1.000000 | 0 | réussi |
| extreme | T-900005 | 0.039165 | 0.039165 | 1.4e-17 | réussi |
| extreme | T-900006 | 0.094671 | 0.094671 | 5.6e-17 | réussi |
| unknown | T-900007 | 0.177527 | 0.177527 | 0 | réussi |
| unknown | T-900008 | 0.111725 | 0.111725 | 5.6e-17 | réussi |
Description textuelle de l’explorateur
L’outil d’exploration commence sans erreur. Les 14 cas sur 14 respectent la tolérance de 1e-09. La plus grande différence est de 1,1e-16 (T-116942). Aucun signalement ne change.
Remplacer les nombres manquants par 0 (to_frame() remplace un nombre manquant par 0) : 11 cas sur 14 réussissent. Les trois cas avec valeurs manquantes échouent. T-900001 diffère de 5,4e-03 (0,032546 au lieu de 0,037929). T-900002 diffère de 1,1e-01 (0,250070 au lieu de 0,144791). T-900003 diffère de 4,5e-02 (0,121413 au lieu de 0,075978). T-900002, l’invité, est maintenant signalé. Les cas normaux, extrêmes et inconnus réussissent encore.
Arrondir le score à 4 décimales (la réponse arrondit le score) : 1 cas sur 14 réussit. Seul T-900004 réussit, parce que son score est exactement 1,0. Les 13 autres diffèrent de 1,7e-06 à 4,0e-05. Aucun signalement ne change.
Remplacer une nouvelle catégorie par « unknown » (une valeur de catégorie jamais vue par le modèle devient le texte unknown avant le calcul du score) : 12 cas sur 14 réussissent. Les deux cas de catégories inconnues échouent. T-900007 diffère de 3,3e-02 (0,144458 au lieu de 0,177527). T-900008 diffère de 2,1e-02 (0,090970 au lieu de 0,111725). Aucun signalement ne change.
Chaque erreur fait échouer un groupe différent, et aucun cas ne détecte à lui seul les trois erreurs. Regardez ce que chacune enseigne :
- Remplacer par 0 fait échouer seulement les cas avec valeurs manquantes. Pendant l’entraînement, l’imputation remplaçait un nombre manquant par la médiane des données d’entraînement. Elle activait aussi une colonne indicatrice : « cette valeur était manquante ». Le modèle a appris ce que signifie une ancienneté manquante. Un 0 lui dit autre chose : un tout nouveau client.
- Arrondir à 4 décimales semble propre et ne change aucun signalement aujourd’hui. Cela fait quand même échouer 13 cas, et c’est justifié : le service ne retourne plus le score du modèle. Un score de 0,18996 s’afficherait comme 0,19 à côté d’un
flag(indicateur de signalement) faux. Si vous voulez arrondir, faites-le dans l’affichage du client, pas dans l’API. - Remplacer par « unknown » semble sûr, parce que « inconnu » paraît neutre. Mais
unknownest une vraie catégorie dans les données d’entraînement : des billets sans canal ou région enregistrés. Le modèle a appris un poids propre à cette catégorie. Une valeur que le modèle n’a jamais vue ne reçoit aucun poids. Ce n’est pas la même chose, et seuls les deux cas inconnus le montrent.
Une réussite ne signifie pas que le service est prêt
Section intitulée « Une réussite ne signifie pas que le service est prêt »La vérification de Tomás comprenait un billet, un /ready et une autovérification réussie. Tous ont réussi malgré l’erreur de remplacement par 0. Un service de modèle échoue silencieusement : il répond 200 avec un nombre qui semble correct. Une vérification doit donc examiner les entrées pour lesquelles le code du service fait des choix : valeurs manquantes, limites et catégories inconnues. Les 14 cas coûtent peu : ils s’exécutent en moins d’une seconde dans les tests.
La parité a des limites. Elle détecte seulement les écarts couverts par les cas. Si un nouveau type d’entrée apparaît, comme un nouveau champ, ajoutez un cas dans la prochaine version de l’ensemble empaqueté. La parité ne dit rien non plus sur la qualité du modèle pour les billets d’aujourd’hui. C’est le rôle de la surveillance, dans Surveiller la santé du modèle. Vérifiez la parité à chaque changement du code (pytest) et après chaque déploiement (check_parity.py contre le service réel).
Exercice guidé : vérifier la parité deux fois
Section intitulée « Exercice guidé : vérifier la parité deux fois »- Écrivez
parity/check_parity.pyettests/test_parity.pycomme ci-dessus. - Exécutez
python -m pytest. La dernière ligne indique42 passed(42 tests réussis). - Démarrez le service avec
fastapi run. - Dans un deuxième terminal, exécutez
python parity/check_parity.py http://127.0.0.1:8000 bundles/escalation-1.0.0. - Vérifiez la dernière ligne :
14 of 14 cases match within 1e-09.(14 cas sur 14 concordent à 1e-09 près). - Exécutez
echo $?(PowerShell :$LASTEXITCODE). La commande donne0. - Arrêtez le service avec Ctrl+C.
Afficher la sortie attendue à l’étape 4
Quatorze lignes avec une différence chacune, puis 14 of 14 cases match within 1e-09. Vos différences peuvent varier par rapport à l’exemple détaillé, mais chacune est de 1e-15 ou moins. Si une ligne se termine par FAIL (échec), lisez la section suivante.
À vous de jouer : arrondir le score
Section intitulée « À vous de jouer : arrondir le score »Dans escalation/routes.py, dans le return ScoreOut(...) de score(), remplacez score=result.score, par score=round(result.score, 4),. Faites d’abord une prédiction : combien de tests de parité échouent, et lequel réussit? Exécutez ensuite python -m pytest tests/test_parity.py -q. Annulez la modification, puis relancez les tests.
Afficher la réponse
FAILED tests/test_parity.py::test_served_score_matches_training[normal-T-116940]...FAILED tests/test_parity.py::test_served_score_matches_training[unknown-T-900008]13 failed, 1 passed in 1.21sSeul extreme-T-900004 réussit : son score vaut exactement 1,0, et l’arrondi ne le change pas. Tous les autres scores changent à la cinquième décimale ou plus loin (de 1,7e-06 à 4,0e-05). Ces écarts dépassent largement la tolérance. Après l’annulation de la modification, vous obtenez 14 passed (14 tests réussis).
Erreur courante
Trois cas avec des valeurs manquantes échouent au test de parité
Symptôme : après une modification de contract.py, le service démarre et /ready indique qu’il est prêt, mais check_parity.py affiche :
case ticket expected served difference...missing T-900001 0.037929 0.032546 5.4e-03 FAILmissing T-900002 0.144791 0.250070 1.1e-01 FAILmissing T-900003 0.075978 0.121413 4.5e-02 FAIL...11 of 14 cases match within 1e-09.Le code de sortie est 1. python -m pytest échoue aussi aux trois tests missing-T-900001, missing-T-900002 et missing-T-900003. Le test du contrat test_the_frame_has_the_training_columns_in_order échoue également. Il vérifie aussi qu’un nombre manquant reste NaN.
Cause : to_frame() remplace les nombres manquants avant qu’ils arrivent au pipeline :
frame[name] = frame[name].astype("float64") frame[name] = frame[name].astype("float64").fillna(0)L’entraînement n’a jamais vu 0 à la place d’une valeur manquante. Son imputeur remplaçait chaque valeur manquante par la médiane d’entraînement et activait l’indicateur. Le groupe en échec indique la cause : seuls les cas avec une valeur manquante échouent. Examinez donc le code qui traite les valeurs manquantes.
Correction : retirez .fillna(0). Le service doit transmettre les nombres manquants au pipeline sous forme de NaN. Ainsi, l’imputeur du pipeline fait la même chose qu’à l’entraînement. La règle générale : le service vérifie l’entrée et lui donne la bonne forme. Mais toutes les transformations apprises à l’entraînement restent dans le pipeline.
Pour un vrai service d’IA : c’est le type d’écart le plus courant. Le code du service fait un « petit nettoyage » que l’entraînement n’a jamais fait. Cela ne produit aucune erreur et ne change aucune mesure qui semble normale. Ici, le billet de soutien de l’invité T-900002 est passé de 0,145 à 0,250, et le service l’a signalé. L’équipe de Grace examinerait des invités que le modèle ne devait pas signaler. Les cas de parité avec des valeurs manquantes détectent ce problème avant la mise en production.
Vérifiez votre compréhension
Pourquoi la parité utilise-t-elle une tolérance de 1e-9, plutôt que de 0 ou de 0,01?
Afficher la réponse
Les scores du service diffèrent des scores d’entraînement d’environ 1e-16 au maximum. La cause est un arrondi au dernier chiffre : un autre ordre d’addition, un autre processeur ou la lecture d’un nombre à 17 chiffres dans un fichier CSV. Une tolérance de 0 ferait échouer les tests à cause de ce bruit. La décision compare un score à 0,19. Vous devez donc détecter une vraie erreur bien avant qu’elle atteigne 0,01. Une tolérance de 0,01 laisserait passer une erreur qui déplace chaque score de 0,009. La valeur 1e-9 est dix millions de fois supérieure au bruit et dix millions de fois inférieure à 0,01.
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
L'autovérification dans load_bundle() compare déjà les scores de parité au démarrage. Pourquoi n'a-t-elle pas détecté le remplacement par 0?
Afficher la réponse
L’autovérification calcule les scores des cas directement avec le pipeline, à partir du fichier CSV. Elle prouve que le paquet est le bon et fonctionne avec les paquets logiciels installés. Elle n’utilise pas le code propre au service : le contrat, to_frame() et la route. L’erreur était dans to_frame(). Seule une vérification qui envoie les billets au service, check_parity.py ou test_parity.py, peut donc la détecter.
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.
Exercice du module
Section intitulée « Exercice du module »L’exercice du module consiste à intégrer le classificateur à l’API. Vous avez fait la partie guidée dans ce module : le cycle de vie, les routes et les vérifications de parité. Maintenant, démontrez-le par vous-même, à partir d’une copie propre.
Tâche : copiez l’instantané end-of-m02 dans un nouveau dossier, ~/projects/escalation-check. Sans copier depuis end-of-m03, ajoutez les fichiers de ce module : escalation/main.py, escalation/errors.py, escalation/security.py, escalation/routes.py, pyproject.toml, parity/check_parity.py, tests/test_api.py et tests/test_parity.py. Consultez les leçons au besoin. Recueillez ensuite quatre preuves que le service est prêt : les tests, le journal de démarrage, les informations du modèle et la vérification de parité.
Afficher un exemple de réponse
Dans le nouveau dossier, créez un .venv et installez requirements.txt, comme dans le module 1. Ensuite :
python -m pytestexport MODEL_SHA256=$(shasum -a 256 bundles/escalation-1.0.0/SHA256SUMS | cut -c1-64)fastapi runcurl http://127.0.0.1:8000/v1/modelpython parity/check_parity.py http://127.0.0.1:8000 bundles/escalation-1.0.0Les quatre preuves :
42 passed(42 tests réussis).- La ligne de journal
model 1.0.0 loaded (bundle …) in … ms(modèle chargé en … ms), avec les 12 premiers caractères de votre empreinte. /v1/modeldonne"model_version":"1.0.0", unbundle_sha256qui commence par les mêmes 12 caractères, et"threshold":0.19.14 of 14 cases match within 1e-09.(14 cas sur 14 concordent à 1e-09 près) et le code de sortie 0.
Si un cas de parité échoue, son groupe indique le code à vérifier. Valeurs manquantes : to_frame(). Catégories : le contrat ou le calcul du score. Tous les cas : la réponse de la route.
Vérification du module
Section intitulée « Vérification du module »Vérifiez votre compréhension
Rappel : quel travail escalation-service fait-il une fois par processus au démarrage, et quel travail fait-il à chaque requête?
Afficher la réponse
Une fois par processus, dans le cycle de vie : importer les paquets logiciels, lire les paramètres et exécuter load_bundle(). Cette fonction effectue ses vérifications et l’autovérification, qui sert aussi au préchauffage. Ensuite, le port s’ouvre. À chaque requête : vérifier la clé d’API et le contrat, puis exécuter frame, prepare et model. Répondre ensuite avec la version et le seuil, puis écrire une ligne de journal.
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
Prédiction : un développeur modifie le service pour remplacer une catégorie jamais vue par le modèle par le texte unknown avant le calcul du score. Quels cas de parité échouent, et pourquoi cela change-t-il le score?
Afficher la réponse
Seuls les deux cas de catégorie inconnue échouent : T-900007 (social) et T-900008 (overseas, installation). Les écarts sont de 3,3e-02 et de 2,1e-02 dans l’exécution mesurée. unknown (inconnu) est une vraie catégorie des données d’entraînement pour channel et region, avec son propre poids appris. L’encodeur à chaud ne crée aucune colonne pour une valeur que le modèle n’a jamais vue. Elle n’a donc aucun poids. La remplacer par unknown lui donne le poids des billets dont le canal ou la région n’a pas été enregistré.
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
Vérification finale : montrez que votre service respecte la parité pour les entrées normales, manquantes et extrêmes. Qu'exécutez-vous, et quelle sortie le prouve?
Afficher la réponse
Démarrez le service, puis exécutez python parity/check_parity.py http://127.0.0.1:8000 bundles/escalation-1.0.0. La preuve comporte trois parties. Aucune ligne ne se termine par FAIL (échec). Cela comprend les six cas normal (normaux), les trois cas missing (manquants) et les trois cas extreme (extrêmes). La dernière ligne est 14 of 14 cases match within 1e-09. Le code de sortie est 0. python -m pytest tests/test_parity.py donne 14 passed pour les mêmes cas, transmis à l’application dans les tests.
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.
Récapitulatif
Dans ce module, vous avez créé le service de prédiction :
main.pyfait le travail lent une fois par processus, dans le cycle de vie. Le paquet est vérifié, chargé et préchauffé avant l’ouverture du port. Un mauvais paquet arrête le service avec le code de sortie 3.routes.pyvérifie chaque billet avec le contrat. Il répond avec le score, l’indicateur, le seuil, la version du modèle et les avertissements. Toutes les erreurs ont la même forme, et chaque score a une ligne de journal.- L’écart entre l’entraînement et le service est une différence dans leur façon de préparer la même entrée. La parité le détecte : les 14 cas fixes passent par le service. Leurs scores sont comparés aux scores d’entraînement avec une tolérance de 1e-9. Les écarts réels sont d’environ 1e-16.
- Une bonne prédiction prouve peu de choses. Chaque erreur du service a fait échouer un groupe de cas différent.
- Les tests :
42 passed.
Réinitialisation. Pour commencer le module 4 dans un état propre :
- Arrêtez chaque
fastapi runavec Ctrl+C. - Supprimez
../edited-bundles’il est encore là, et~/projects/escalation-checksi vous n’en avez pas besoin. - Si votre projet ne fonctionne plus, copiez l’instantané
end-of-m03et exécutezpython -m pytest:42 passed.
Vous n’avez créé aucun conteneur ni aucune image Docker dans ce module. Il n’y a donc rien à supprimer dans Docker.
Ensuite, vous choisissez où le service s’exécute : CPU ou GPU, plateforme de conteneurs ou point de terminaison géré.
Leçon suivante : CPU, GPU et points de terminaison gérés