

Les traductions sont fournies par des outils de traduction automatique. En cas de conflit entre le contenu d'une traduction et celui de la version originale en anglais, la version anglaise prévaudra.

# Rubriques avancées
<a name="model-monitor-advanced-topics"></a>

Les sections suivantes contiennent des tâches plus avancées qui expliquent comment personnaliser la surveillance à l'aide de scripts de prétraitement et de post-traitement, comment créer votre propre conteneur et comment l'utiliser CloudFormation pour créer un calendrier de surveillance.

**Topics**
+ [Planifications de surveillance personnalisées](model-monitor-custom-monitoring-schedules.md)
+ [Créez un calendrier de surveillance pour un point de terminaison en temps réel avec une ressource CloudFormation personnalisée](model-monitor-cloudformation-monitoring-schedules.md)

# Planifications de surveillance personnalisées
<a name="model-monitor-custom-monitoring-schedules"></a>

En plus d'utiliser les mécanismes de surveillance préconçus, vous pouvez créer vos propres planifications et procédures de surveillance personnalisées à l'aide de scripts de prétraitement et de post-traitement, ou en utilisant ou créant votre propre conteneur.

**Topics**
+ [Prétraitement et post-traitement](model-monitor-pre-and-post-processing.md)
+ [Support pour vos propres conteneurs avec Amazon SageMaker Model Monitor](model-monitor-byoc-containers.md)

# Prétraitement et post-traitement
<a name="model-monitor-pre-and-post-processing"></a>

Vous pouvez utiliser des scripts Python de prétraitement et de post-traitement personnalisés pour transformer l'entrée de votre surveillance de modèle ou étendre le code après une exécution de surveillance réussie. Téléchargez ces scripts sur Amazon S3 et référencez-les lors de la création de votre surveillance de modèle.

L'exemple suivant montre comment personnaliser les planifications de surveillance à l'aide de scripts de prétraitement et de post-traitement. Remplacez *user placeholder text* par vos propres informations.

```
import boto3, os
from sagemaker import get_execution_role, Session
from sagemaker.model_monitor import CronExpressionGenerator, DefaultModelMonitor

# Upload pre and postprocessor scripts
session = Session()
bucket = boto3.Session().resource("s3").Bucket(session.default_bucket())
prefix = "demo-sagemaker-model-monitor"
pre_processor_script = bucket.Object(os.path.join(prefix, "preprocessor.py")).upload_file("preprocessor.py")
post_processor_script = bucket.Object(os.path.join(prefix, "postprocessor.py")).upload_file("postprocessor.py")

# Get execution role
role = get_execution_role() # can be an empty string

# Instance type
instance_type = "instance-type"
# instance_type = "ml.m5.xlarge" # Example

# Create a monitoring schedule with pre and postprocessing
my_default_monitor = DefaultModelMonitor(
    role=role,
    instance_count=1,
    instance_type=instance_type,
    volume_size_in_gb=20,
    max_runtime_in_seconds=3600,
)

s3_report_path = "s3://{}/{}".format(bucket, "reports")
monitor_schedule_name = "monitor-schedule-name"
endpoint_name = "endpoint-name"
my_default_monitor.create_monitoring_schedule(
    post_analytics_processor_script=post_processor_script,
    record_preprocessor_script=pre_processor_script,
    monitor_schedule_name=monitor_schedule_name,
    # use endpoint_input for real-time endpoint
    endpoint_input=endpoint_name,
    # or use batch_transform_input for batch transform jobs
    # batch_transform_input=batch_transform_name,
    output_s3_uri=s3_report_path,
    statistics=my_default_monitor.baseline_statistics(),
    constraints=my_default_monitor.suggested_constraints(),
    schedule_cron_expression=CronExpressionGenerator.hourly(),
    enable_cloudwatch_metrics=True,
)
```

**Topics**
+ [Script de prétraitement](#model-monitor-pre-processing-script)
+ [Échantillonnage personnalisé](#model-monitor-pre-processing-custom-sampling)
+ [Script de post-traitement](#model-monitor-post-processing-script)

## Script de prétraitement
<a name="model-monitor-pre-processing-script"></a>

Utilisez des scripts de prétraitement lorsque vous devez transformer les entrées de votre surveillance de modèle.

Supposons, par exemple, que la sortie de votre modèle soit un tableau `[1.0, 2.1]`. Le conteneur Amazon SageMaker Model Monitor ne fonctionne qu'avec des structures JSON tabulaires ou aplaties, comme. `{“prediction0”: 1.0, “prediction1” : 2.1}` Vous pouvez utiliser un script de prétraitement comme celui-ci pour transformer le tableau en structure JSON correcte.

```
def preprocess_handler(inference_record):
    input_data = inference_record.endpoint_input.data
    output_data = inference_record.endpoint_output.data.rstrip("\n")
    data = output_data + "," + input_data
    return { str(i).zfill(20) : d for i, d in enumerate(data.split(",")) }
```

Dans un autre exemple, supposons que votre modèle comporte des fonctions facultatives et que vous utilisiez `-1` pour indiquer que la fonction facultative possède une valeur manquante. Si vous disposez d'une surveillance de qualité des données, vous pouvez le supprimer `-1` du tableau des valeurs d'entrée afin qu'il ne soit pas inclus dans les calculs métriques de la surveillance. Vous pouvez utiliser un script comme celui-ci pour supprimer ces valeurs.

```
def preprocess_handler(inference_record):
    input_data = inference_record.endpoint_input.data
    return {i : None if x == -1 else x for i, x in enumerate(input_data.split(","))}
```

Votre script de prétraitement reçoit `inference_record` comme seule entrée. L'extrait de code suivant illustre un exemple de `inference_record`.

```
{
  "captureData": {
    "endpointInput": {
      "observedContentType": "text/csv",
      "mode": "INPUT",
      "data": "132,25,113.2,96,269.9,107,,0,0,0,0,0,0,1,0,1,0,0,1",
      "encoding": "CSV"
    },
    "endpointOutput": {
      "observedContentType": "text/csv; charset=utf-8",
      "mode": "OUTPUT",
      "data": "0.01076381653547287",
      "encoding": "CSV"
    }
  },
  "eventMetadata": {
    "eventId": "feca1ab1-8025-47e3-8f6a-99e3fdd7b8d9",
    "inferenceTime": "2019-11-20T23:33:12Z"
  },
  "eventVersion": "0"
}
```

L’extrait de code suivant illustre la structure complète d’une classe pour `inference_record`.

```
KEY_EVENT_METADATA = "eventMetadata"
KEY_EVENT_METADATA_EVENT_ID = "eventId"
KEY_EVENT_METADATA_EVENT_TIME = "inferenceTime"
KEY_EVENT_METADATA_CUSTOM_ATTR = "customAttributes"

KEY_EVENTDATA_ENCODING = "encoding"
KEY_EVENTDATA_DATA = "data"

KEY_GROUND_TRUTH_DATA = "groundTruthData"

KEY_EVENTDATA = "captureData"
KEY_EVENTDATA_ENDPOINT_INPUT = "endpointInput"
KEY_EVENTDATA_ENDPOINT_OUTPUT = "endpointOutput"

KEY_EVENTDATA_BATCH_OUTPUT = "batchTransformOutput"
KEY_EVENTDATA_OBSERVED_CONTENT_TYPE = "observedContentType"
KEY_EVENTDATA_MODE = "mode"

KEY_EVENT_VERSION = "eventVersion"

class EventConfig:
    def __init__(self, endpoint, variant, start_time, end_time):
        self.endpoint = endpoint
        self.variant = variant
        self.start_time = start_time
        self.end_time = end_time


class EventMetadata:
    def __init__(self, event_metadata_dict):
        self.event_id = event_metadata_dict.get(KEY_EVENT_METADATA_EVENT_ID, None)
        self.event_time = event_metadata_dict.get(KEY_EVENT_METADATA_EVENT_TIME, None)
        self.custom_attribute = event_metadata_dict.get(KEY_EVENT_METADATA_CUSTOM_ATTR, None)


class EventData:
    def __init__(self, data_dict):
        self.encoding = data_dict.get(KEY_EVENTDATA_ENCODING, None)
        self.data = data_dict.get(KEY_EVENTDATA_DATA, None)
        self.observedContentType = data_dict.get(KEY_EVENTDATA_OBSERVED_CONTENT_TYPE, None)
        self.mode = data_dict.get(KEY_EVENTDATA_MODE, None)

    def as_dict(self):
        ret = {
            KEY_EVENTDATA_ENCODING: self.encoding,
            KEY_EVENTDATA_DATA: self.data,
            KEY_EVENTDATA_OBSERVED_CONTENT_TYPE: self.observedContentType,
        }
        return ret


class CapturedData:
    def __init__(self, event_dict):
        self.event_metadata = None
        self.endpoint_input = None
        self.endpoint_output = None
        self.batch_transform_output = None
        self.ground_truth = None
        self.event_version = None
        self.event_dict = event_dict
        self._event_dict_postprocessed = False
        
        if KEY_EVENT_METADATA in event_dict:
            self.event_metadata = EventMetadata(event_dict[KEY_EVENT_METADATA])
        if KEY_EVENTDATA in event_dict:
            if KEY_EVENTDATA_ENDPOINT_INPUT in event_dict[KEY_EVENTDATA]:
                self.endpoint_input = EventData(event_dict[KEY_EVENTDATA][KEY_EVENTDATA_ENDPOINT_INPUT])
            if KEY_EVENTDATA_ENDPOINT_OUTPUT in event_dict[KEY_EVENTDATA]:
                self.endpoint_output = EventData(event_dict[KEY_EVENTDATA][KEY_EVENTDATA_ENDPOINT_OUTPUT])
            if KEY_EVENTDATA_BATCH_OUTPUT in event_dict[KEY_EVENTDATA]:
                self.batch_transform_output = EventData(event_dict[KEY_EVENTDATA][KEY_EVENTDATA_BATCH_OUTPUT])

        if KEY_GROUND_TRUTH_DATA in event_dict:
            self.ground_truth = EventData(event_dict[KEY_GROUND_TRUTH_DATA])
        if KEY_EVENT_VERSION in event_dict:
            self.event_version = event_dict[KEY_EVENT_VERSION]

    def as_dict(self):
        if self._event_dict_postprocessed is True:
            return self.event_dict
        if KEY_EVENTDATA in self.event_dict:
            if KEY_EVENTDATA_ENDPOINT_INPUT in self.event_dict[KEY_EVENTDATA]:
                self.event_dict[KEY_EVENTDATA][KEY_EVENTDATA_ENDPOINT_INPUT] = self.endpoint_input.as_dict()
            if KEY_EVENTDATA_ENDPOINT_OUTPUT in self.event_dict[KEY_EVENTDATA]:
                self.event_dict[KEY_EVENTDATA][
                    KEY_EVENTDATA_ENDPOINT_OUTPUT
                ] = self.endpoint_output.as_dict()
            if KEY_EVENTDATA_BATCH_OUTPUT in self.event_dict[KEY_EVENTDATA]:
                self.event_dict[KEY_EVENTDATA][KEY_EVENTDATA_BATCH_OUTPUT] = self.batch_transform_output.as_dict()
        
        self._event_dict_postprocessed = True
        return self.event_dict

    def __str__(self):
        return str(self.as_dict())
```

## Échantillonnage personnalisé
<a name="model-monitor-pre-processing-custom-sampling"></a>

Vous pouvez également appliquer une stratégie d'échantillonnage personnalisée dans votre script de prétraitement. Pour ce faire, configurez le conteneur prédéfini de Model Monitor de manière à ignorer un pourcentage des enregistrements en fonction de la fréquence d'échantillonnage que vous avez spécifiée. Dans l’exemple suivant, le gestionnaire échantillonne 10 % des enregistrements en renvoyant l’enregistrement dans 10 % des appels du gestionnaire et en renvoyant une liste vide dans le cas contraire.

```
import random

def preprocess_handler(inference_record):
    # we set up a sampling rate of 0.1
    if random.random() > 0.1:
        # return an empty list
        return []
    input_data = inference_record.endpoint_input.data
    return {i : None if x == -1 else x for i, x in enumerate(input_data.split(","))}
```

### Journalisation personnalisée pour le script de prétraitement
<a name="model-monitor-pre-processing-custom-logging"></a>

 Si votre script de prétraitement renvoie une erreur, vérifiez les messages d'exception enregistrés CloudWatch pour le débogage. Vous pouvez accéder à l'enregistreur CloudWatch via l'`preprocess_handler`interface. Vous pouvez enregistrer toutes les informations dont vous avez besoin depuis votre script dans CloudWatch. Cela peut être utile lors du débogage de votre script de prétraitement. L'exemple suivant montre comment vous pouvez utiliser l'`preprocess_handler`interface pour vous connecter à CloudWatch 

```
def preprocess_handler(inference_record, logger):
    logger.info(f"I'm a processing record: {inference_record}")
    logger.debug(f"I'm debugging a processing record: {inference_record}")
    logger.warning(f"I'm processing record with missing value: {inference_record}")
    logger.error(f"I'm a processing record with bad value: {inference_record}")
    return inference_record
```

## Script de post-traitement
<a name="model-monitor-post-processing-script"></a>

Utilisez un script de post-traitement lorsque vous souhaitez étendre le code après une exécution de surveillance réussie.

```
def postprocess_handler():
    print("Hello from post-proc script!")
```

# Support pour vos propres conteneurs avec Amazon SageMaker Model Monitor
<a name="model-monitor-byoc-containers"></a>

Amazon SageMaker Model Monitor fournit un conteneur prédéfini capable d'analyser les données capturées à partir de points de terminaison ou de tâches de transformation par lots pour des ensembles de données tabulaires. Si vous voulez apporter votre propre conteneur, vous pouvez mettre à profit les points d’extension fournis par Model Monitor.

Ainsi, lorsque vous créez un `MonitoringSchedule`, Model Monitor lance les tâches de traitement. Par conséquent, le conteneur doit être conscient du contrat des tâches de traitement documenté dans la rubrique [Création de votre propre conteneur de traitement (scénario avancé)](build-your-own-processing-container.md). Model Monitor lance la tâche de traitement en votre nom selon le programme. Lors de l'appel, Model Monitor configure pour vous des variables d'environnement supplémentaires de sorte que votre conteneur ait suffisamment de contexte pour traiter les données correspondant à cette exécution particulière de la surveillance programmée. Pour plus d’informations sur les entrées de conteneur, consultez [Entrées du contrat de conteneur](model-monitor-byoc-contract-inputs.md).

Dans le conteneur, à l'aide des variables d'environnement et du contexte ci-dessus, vous pouvez maintenant analyser le jeu de données pour la période en cours dans votre code personnalisé. Une fois cette analyse terminée, vous pouvez choisir d'émettre vos rapports à télécharger dans un compartiment S3. Les rapports générés par le conteneur préconçu sont documentés dans [Sorties du contrat de conteneur](model-monitor-byoc-contract-outputs.md). Si vous souhaitez que la visualisation des rapports fonctionne dans SageMaker Studio, vous devez suivre le même format. Vous pouvez également choisir d’émettre des rapports entièrement personnalisés.

Vous pouvez également émettre CloudWatch des métriques depuis le conteneur en suivant les instructions de[CloudWatch Indicateurs pour apporter vos propres contenants](model-monitor-byoc-cloudwatch.md).

**Topics**
+ [Entrées du contrat de conteneur](model-monitor-byoc-contract-inputs.md)
+ [Sorties du contrat de conteneur](model-monitor-byoc-contract-outputs.md)
+ [CloudWatch Indicateurs pour apporter vos propres contenants](model-monitor-byoc-cloudwatch.md)

# Entrées du contrat de conteneur
<a name="model-monitor-byoc-contract-inputs"></a>

La plateforme Amazon SageMaker Model Monitor invoque votre code de conteneur selon un calendrier défini. Si vous avez choisi d'écrire votre propre code de conteneur, les variables d'environnement suivantes sont disponibles. Dans ce contexte, vous pouvez analyser le jeu de données actuel ou évaluer les contraintes si vous le souhaitez et émettre des métriques, le cas échéant.

Les variables d'environnement disponibles sont les mêmes pour les points de terminaison en temps réel et les tâches de transformation par lots, à l'exception de la variable `dataset_format`. Si vous utilisez un point de terminaison en temps réel, la variable `dataset_format` prend en charge les options suivantes :

```
{\"sagemakerCaptureJson\": {\"captureIndexNames\": [\"endpointInput\",\"endpointOutput\"]}}
```

Si vous utilisez une tâche de transformation par lots, `dataset_format` prend en charge les options suivantes :

```
{\"csv\": {\"header\": [\"true\",\"false\"]}}
```

```
{\"json\": {\"line\": [\"true\",\"false\"]}}
```

```
{\"parquet\": {}}
```

L'exemple de code suivant montre le jeu complet des variables d'environnement disponibles pour votre code de conteneur (et utilise le format `dataset_format` d'un point de terminaison en temps réel).

```
"Environment": {
 "dataset_format": "{\"sagemakerCaptureJson\": {\"captureIndexNames\": [\"endpointInput\",\"endpointOutput\"]}}",
 "dataset_source": "/opt/ml/processing/endpointdata",
 "end_time": "2019-12-01T16: 20: 00Z",
 "output_path": "/opt/ml/processing/resultdata",
 "publish_cloudwatch_metrics": "Disabled",
 "sagemaker_endpoint_name": "endpoint-name",
 "sagemaker_monitoring_schedule_name": "schedule-name",
 "start_time": "2019-12-01T15: 20: 00Z"
}
```

Parameters 


| Nom du paramètre | Description | 
| --- | --- | 
| dataset\$1format |  Pour une tâche démarrée à partir d’un `MonitoringSchedule` basé sur un `Endpoint`, il s’agit de `sageMakerCaptureJson` avec les indices de capture `endpointInput` et/ou `endpointOutput`. Pour une tâche de transformation par lots, cela indique le format de données, qu'il s'agisse de CSV, JSON ou Parquet.  | 
| dataset\$1source |  Si vous utilisez un point de terminaison en temps réel, le chemin d'accès local dans lequel les données correspondant à la période de surveillance, comme spécifié par `start_time` et `end_time`, sont disponibles. Dans ce chemin d’accès, les données sont disponibles dans ` /{endpoint-name}/{variant-name}/yyyy/mm/dd/hh`. Nous téléchargeons parfois plus de données que ce qui est spécifié par les heures de début et de fin. C’est au code de conteneur d’analyser les données selon les besoins.  | 
| output\$1path |  Chemin d’accès local où écrire des rapports de sortie et d’autres fichiers. Vous devez spécifier ce paramètre dans la demande `CreateMonitoringSchedule` comme `MonitoringOutputConfig.MonitoringOutput[0].LocalPath`. Il est chargé dans le chemin d’accès `S3Uri` spécifié dans `MonitoringOutputConfig.MonitoringOutput[0].S3Uri`.  | 
| publish\$1cloudwatch\$1metrics |  Pour une tâche lancée par `CreateMonitoringSchedule`, ce paramètre est défini sur `Enabled`. Le conteneur peut choisir d'écrire le fichier de CloudWatch sortie Amazon à l'adresse`[filepath]`.  | 
| sagemaker\$1endpoint\$1name |  Si vous utilisez un point de terminaison en temps réel, le nom du `Endpoint` pour lequel cette tâche planifiée a été lancée.  | 
| sagemaker\$1monitoring\$1schedule\$1name |  Nom du `MonitoringSchedule` qui a lancé cette tâche.  | 
| \$1sagemaker\$1endpoint\$1datacapture\$1prefix\$1 |  Si vous utilisez un point de terminaison en temps réel, le préfixe spécifié dans le paramètre `DataCaptureConfig` du `Endpoint`. Le conteneur peut l'utiliser s'il a besoin d'accéder directement à plus de données que celles déjà téléchargées par l' SageMaker IA sur le `dataset_source` chemin.  | 
| start\$1time, end\$1time |  Fenêtre horaire pour l’analyse exécutée. Par exemple, pour une tâche planifiée pour s’exécuter à 5 h 00 UTC et une tâche qui s’exécute le 20/02/202, `start_time` : est 2020-02-19T06:00:00Z et `end_time` : est 2020-02-20T05:00:00Z  | 
| baseline\$1constraints: |  Chemin d’accès local du fichier de contrainte de référence spécifié dans ` BaselineConfig.ConstraintResource.S3Uri`. Ce paramètre est disponible uniquement si ce paramètre a été spécifié dans la demande `CreateMonitoringSchedule`.  | 
| baseline\$1statistics |  Chemin d’accès local au fichier de statistiques de référence spécifié dans `BaselineConfig.StatisticsResource.S3Uri`. Ce paramètre est disponible uniquement si ce paramètre a été spécifié dans la demande `CreateMonitoringSchedule`.   | 

# Sorties du contrat de conteneur
<a name="model-monitor-byoc-contract-outputs"></a>

Le conteneur peut analyser les données disponibles dans le chemin d’accès `*dataset_source*` et écrire des rapports dans le chemin d’accès dans `*output_path*.` Le code de conteneur peut écrire tous les rapports qui répondent à vos besoins.

Si vous utilisez la structure et le contrat suivants, certains fichiers de sortie sont traités spécialement par l' SageMaker IA dans la visualisation et l'API. Cela s’applique uniquement aux jeux de données tabulaires.

Fichiers de sortie pour données tabulaires


| Nom de fichier | Description | 
| --- | --- | 
| statistics.json |  Ce fichier doit comporter des statistiques en colonnes pour chaque fonction du jeu de données analysé. Le schéma de ce fichier est disponible dans la section suivante.  | 
| constraints.json |  Dans ce fichier, les contraintes sur les fonctions doivent être observées. Le schéma de ce fichier est disponible dans la section suivante.  | 
| constraints\$1violations.json |  Ce fichier doit contenir la liste des violations détectées dans ce jeu de données actif par rapport au fichier de statistiques et de contraintes de référence spécifié dans le chemin d'accès `baseline_constaints` et `baseline_statistics`.  | 

De plus, si la `publish_cloudwatch_metrics` valeur est le code du `"Enabled"` conteneur, vous pouvez émettre CloudWatch des métriques Amazon à cet endroit :`/opt/ml/output/metrics/cloudwatch`. Le schéma de ces fichiers est décrit dans les sections suivantes.

**Topics**
+ [Schéma des statistiques (fichier statistics.json)](model-monitor-byoc-statistics.md)
+ [Schéma des contraintes (fichier constraints.json)](model-monitor-byoc-constraints.md)

# Schéma des statistiques (fichier statistics.json)
<a name="model-monitor-byoc-statistics"></a>

Le schéma défini dans le fichier `statistics.json` spécifie les paramètres statistiques à calculer pour la référence et les données capturées. Il configure également le compartiment qui sera utilisé par [KLL](https://datasketches.apache.org/docs/KLL/KLLSketch.html), un croquis de quantiles très compact avec un schéma de compactage paresseux.

```
{
    "version": 0,
    # dataset level stats
    "dataset": {
        "item_count": number
    },
    # feature level stats
    "features": [
        {
            "name": "feature-name",
            "inferred_type": "Fractional" | "Integral",
            "numerical_statistics": {
                "common": {
                    "num_present": number,
                    "num_missing": number
                },
                "mean": number,
                "sum": number,
                "std_dev": number,
                "min": number,
                "max": number,
                "distribution": {
                    "kll": {
                        "buckets": [
                            {
                                "lower_bound": number,
                                "upper_bound": number,
                                "count": number
                            }
                        ],
                        "sketch": {
                            "parameters": {
                                "c": number,
                                "k": number
                            },
                            "data": [
                                [
                                    num,
                                    num,
                                    num,
                                    num
                                ],
                                [
                                    num,
                                    num
                                ][
                                    num,
                                    num
                                ]
                            ]
                        }#sketch
                    }#KLL
                }#distribution
            }#num_stats
        },
        {
            "name": "feature-name",
            "inferred_type": "String",
            "string_statistics": {
                "common": {
                    "num_present": number,
                    "num_missing": number
                },
                "distinct_count": number,
                "distribution": {
                    "categorical": {
                         "buckets": [
                                {
                                    "value": "string",
                                    "count": number
                                }
                          ]
                     }
                }
            },
            #provision for custom stats
        }
    ]
}
```

**Remarques**  
Les métriques spécifiées sont reconnues par l' SageMaker IA lors des modifications de visualisation ultérieures. Le conteneur peut émettre davantage de métriques si nécessaire.
Le [croquis KLL](https://datasketches.apache.org/docs/KLL/KLLSketch.html) est le croquis reconnu. Les conteneurs personnalisés peuvent écrire leur propre représentation, mais celle-ci ne sera pas reconnue par l' SageMaker IA dans les visualisations.
Par défaut, la distribution est matérialisée dans dix compartiments. Vous ne pouvez pas modifier cette valeur.

# Schéma des contraintes (fichier constraints.json)
<a name="model-monitor-byoc-constraints"></a>

Un fichier constraints.json est utilisé pour exprimer les contraintes qu'un jeu de données doit satisfaire. Les conteneurs Amazon SageMaker Model Monitor peuvent utiliser le fichier constraints.json pour évaluer les ensembles de données par rapport à ceux-ci. Les conteneurs préconçus permettent de générer automatiquement le fichier constraints.json pour un jeu de données de référence. Si vous apportez votre propre conteneur, vous pouvez lui attribuer des capacités similaires ou vous pouvez créer le fichier constraints.json d'une autre manière. Voici le schéma du fichier de contraintes utilisé par le conteneur préconçu. Les conteneurs personnalisés peuvent adopter le même format ou vous pouvez l’améliorer au besoin.

```
{
    "version": 0,
    "features":
    [
        {
            "name": "string",
            "inferred_type": "Integral" | "Fractional" | 
                    | "String" | "Unknown",
            "completeness": number,
            "num_constraints":
            {
                "is_non_negative": boolean
            },
            "string_constraints":
            {
                "domains":
                [
                    "list of",
                    "observed values",
                    "for small cardinality"
                ]
            },
            "monitoringConfigOverrides":
            {}
        }
    ],
    "monitoring_config":
    {
        "evaluate_constraints": "Enabled",
        "emit_metrics": "Enabled",
        "datatype_check_threshold": 0.1,
        "domain_content_threshold": 0.1,
        "distribution_constraints":
        {
            "perform_comparison": "Enabled",
            "comparison_threshold": 0.1,
            "comparison_method": "Simple"||"Robust",
            "categorical_comparison_threshold": 0.1,
            "categorical_drift_method": "LInfinity"||"ChiSquared"
        }
    }
}
```

L'objet `monitoring_config` contient des options pour surveiller la tâche pour la fonctionnalité. Le tableau suivant décrit chaque option.

Surveillance des contraintes

[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/fr_fr/sagemaker/latest/dg/model-monitor-byoc-constraints.html)

# CloudWatch Indicateurs pour apporter vos propres contenants
<a name="model-monitor-byoc-cloudwatch"></a>

Si la `publish_cloudwatch_metrics` valeur se trouve `Enabled` sur la `Environment` carte du `/opt/ml/processing/processingjobconfig.json` fichier, le code du conteneur émet CloudWatch des métriques Amazon à cet emplacement :`/opt/ml/output/metrics/cloudwatch`. 

Le schéma de ce fichier est étroitement basé sur l' CloudWatch`PutMetrics`API. L’espace de noms n’est pas spécifié ici. La valeur par défaut est la suivante :
+ `For real-time endpoints: /aws/sagemaker/Endpoint/data-metrics`
+ `For batch transform jobs: /aws/sagemaker/ModelMonitoring/data-metrics`

Toutefois, vous pouvez spécifier des dimensions. Nous vous recommandons d'ajouter les dimensions suivantes au minimum :
+ `Endpoint` et `MonitoringSchedule` pour les points de terminaison en temps réel
+ `MonitoringSchedule` pour les tâches de transformation par lots

Les extraits de code JSON suivants montrent comment définir vos dimensions.

Pour un point de terminaison en temps réel, consultez l'extrait JSON suivant qui inclut les dimensions `Endpoint` et `MonitoringSchedule` :

```
{ 
    "MetricName": "", # Required
    "Timestamp": "2019-11-26T03:00:00Z", # Required
    "Dimensions" : [{"Name":"Endpoint","Value":"endpoint_0"},{"Name":"MonitoringSchedule","Value":"schedule_0"}]
    "Value": Float,
    # Either the Value or the StatisticValues field can be populated and not both.
    "StatisticValues": {
        "SampleCount": Float,
        "Sum": Float,
        "Minimum": Float,
        "Maximum": Float
    },
    "Unit": "Count", # Optional
}
```

Pour une tâche de transformation par lots, consultez l'extrait JSON suivant qui inclut la dimension `MonitoringSchedule` :

```
{ 
    "MetricName": "", # Required
    "Timestamp": "2019-11-26T03:00:00Z", # Required
    "Dimensions" : [{"Name":"MonitoringSchedule","Value":"schedule_0"}]
    "Value": Float,
    # Either the Value or the StatisticValues field can be populated and not both.
    "StatisticValues": {
        "SampleCount": Float,
        "Sum": Float,
        "Minimum": Float,
        "Maximum": Float
    },
    "Unit": "Count", # Optional
}
```

# Créez un calendrier de surveillance pour un point de terminaison en temps réel avec une ressource CloudFormation personnalisée
<a name="model-monitor-cloudformation-monitoring-schedules"></a>

Si vous utilisez un point de terminaison en temps réel, vous pouvez utiliser une ressource CloudFormation personnalisée pour créer un calendrier de surveillance. La ressource personnalisée se trouve dans Python. Pour la déployer, consultez [Package de déploiement Lambda dans Python](https://docs.aws.amazon.com/lambda/latest/dg/lambda-python-how-to-create-deployment-package.html).

## Ressource personnalisée
<a name="model-monitor-cloudformation-custom-resource"></a>

Commencez par ajouter une ressource personnalisée à votre CloudFormation modèle. Cela pointera vers une fonction AWS Lambda que vous créerez à l'étape suivante. 

Cette ressource vous permet de personnaliser les paramètres du programme de surveillance. Vous pouvez ajouter ou supprimer d'autres paramètres en modifiant la CloudFormation ressource et la fonction Lambda dans l'exemple de ressource suivant.

```
{
    "AWSTemplateFormatVersion": "2010-09-09",
    "Resources": {
        "MonitoringSchedule": {
            "Type": "Custom::MonitoringSchedule",
            "Version": "1.0",
            "Properties": {
                "ServiceToken": "arn:aws:lambda:us-west-2:111111111111:function:lambda-name",
                "ScheduleName": "YourScheduleName",
                "EndpointName": "YourEndpointName",
                "BaselineConstraintsUri": "s3://your-baseline-constraints/constraints.json",
                "BaselineStatisticsUri": "s3://your-baseline-stats/statistics.json",
                "PostAnalyticsProcessorSourceUri": "s3://your-post-processor/postprocessor.py",
                "RecordPreprocessorSourceUri": "s3://your-preprocessor/preprocessor.py",
                "InputLocalPath": "/opt/ml/processing/endpointdata",
                "OutputLocalPath": "/opt/ml/processing/localpath",
                "OutputS3URI": "s3://your-output-uri",
                "ImageURI": "111111111111.dkr.ecr.us-west-2.amazonaws.com/your-image",
                "ScheduleExpression": "cron(0 * ? * * *)",
                "PassRoleArn": "arn:aws:iam::111111111111:role/AmazonSageMaker-ExecutionRole"
            }
        }
    }
}
```

## Code de ressource personnalisée Lambda
<a name="model-monitor-cloudformation-lambda-custom-resource-code"></a>

Cette ressource CloudFormation personnalisée utilise la AWS bibliothèque [Custom Resource Helper](https://github.com/aws-cloudformation/custom-resource-helper), que vous pouvez installer avec pip using. `pip install crhelper` 

Cette fonction Lambda est invoquée CloudFormation lors de la création et de la suppression de la pile. Cette fonction Lambda est responsable de la création et de la suppression de la planification de la surveillance et de l’utilisation des paramètres définis dans la ressource personnalisée décrite à la section précédente.

```
import boto3
import botocore
import logging

from crhelper import CfnResource
from botocore.exceptions import ClientError


logger = logging.getLogger(__name__)
sm = boto3.client('sagemaker')

# cfnhelper makes it easier to implement a CloudFormation custom resource
helper = CfnResource()

# CFN Handlers

def handler(event, context):
    helper(event, context)


@helper.create
def create_handler(event, context):
    """
    Called when CloudFormation custom resource sends the create event
    """
    create_monitoring_schedule(event)


@helper.delete
def delete_handler(event, context):
    """
    Called when CloudFormation custom resource sends the delete event
    """
    schedule_name = get_schedule_name(event)
    delete_monitoring_schedule(schedule_name)


@helper.poll_create
def poll_create(event, context):
    """
    Return true if the resource has been created and false otherwise so
    CloudFormation polls again.
    """
    schedule_name = get_schedule_name(event)
    logger.info('Polling for creation of schedule: %s', schedule_name)
    return is_schedule_ready(schedule_name)

@helper.update
def noop():
    """
    Not currently implemented but crhelper will throw an error if it isn't added
    """
    pass

# Helper Functions

def get_schedule_name(event):
    return event['ResourceProperties']['ScheduleName']

def create_monitoring_schedule(event):
    schedule_name = get_schedule_name(event)
    monitoring_schedule_config = create_monitoring_schedule_config(event)

    logger.info('Creating monitoring schedule with name: %s', schedule_name)

    sm.create_monitoring_schedule(
        MonitoringScheduleName=schedule_name,
        MonitoringScheduleConfig=monitoring_schedule_config)

def is_schedule_ready(schedule_name):
    is_ready = False

    schedule = sm.describe_monitoring_schedule(MonitoringScheduleName=schedule_name)
    status = schedule['MonitoringScheduleStatus']

    if status == 'Scheduled':
        logger.info('Monitoring schedule (%s) is ready', schedule_name)
        is_ready = True
    elif status == 'Pending':
        logger.info('Monitoring schedule (%s) still creating, waiting and polling again...', schedule_name)
    else:
        raise Exception('Monitoring schedule ({}) has unexpected status: {}'.format(schedule_name, status))

    return is_ready

def create_monitoring_schedule_config(event):
    props = event['ResourceProperties']

    return {
        "ScheduleConfig": {
            "ScheduleExpression": props["ScheduleExpression"],
        },
        "MonitoringJobDefinition": {
            "BaselineConfig": {
                "ConstraintsResource": {
                    "S3Uri": props['BaselineConstraintsUri'],
                },
                "StatisticsResource": {
                    "S3Uri": props['BaselineStatisticsUri'],
                }
            },
            "MonitoringInputs": [
                {
                    "EndpointInput": {
                        "EndpointName": props["EndpointName"],
                        "LocalPath": props["InputLocalPath"],
                    }
                }
            ],
            "MonitoringOutputConfig": {
                "MonitoringOutputs": [
                    {
                        "S3Output": {
                            "S3Uri": props["OutputS3URI"],
                            "LocalPath": props["OutputLocalPath"],
                        }
                    }
                ],
            },
            "MonitoringResources": {
                "ClusterConfig": {
                    "InstanceCount": 1,
                    "InstanceType": "ml.t3.medium",
                    "VolumeSizeInGB": 50,
                }
            },
            "MonitoringAppSpecification": {
                "ImageUri": props["ImageURI"],
                "RecordPreprocessorSourceUri": props['PostAnalyticsProcessorSourceUri'],
                "PostAnalyticsProcessorSourceUri": props['PostAnalyticsProcessorSourceUri'],
            },
            "StoppingCondition": {
                "MaxRuntimeInSeconds": 300
            },
            "RoleArn": props["PassRoleArn"],
        }
    }


def delete_monitoring_schedule(schedule_name):
    logger.info('Deleting schedule: %s', schedule_name)
    try:
        sm.delete_monitoring_schedule(MonitoringScheduleName=schedule_name)
    except ClientError as e:
        if e.response['Error']['Code'] == 'ResourceNotFound':
            logger.info('Resource not found, nothing to delete')
        else:
            logger.error('Unexpected error while trying to delete monitoring schedule')
            raise e
```