Guide de création de requêtes

Présentation

Dans cette section, nous expliquons comment configurer correctement vos requêtes de détection d'anomalies. De manière générale, vous devez régler quatre types de paramètres:

  • Les paramètres de sélection des données qui spécifient les données incluses dans notre analyse. Exemples: QueryDataSetRequest.dimensionNames, QueryDataSetRequest.pinnedDimensions et QueryDataSetRequest.testedInterval.
  • Les paramètres d'agrégation qui spécifient comment plusieurs événements sont regroupés pour produire une valeur utilisée pour comparer les tranches. Exemples : ForecastParams.aggregatedDimension.
  • Les paramètres de prévisions utilisés pour configurer les algorithmes de prévision utilisés pour calculer les valeurs attendues. Exemples : ForecastParams.holdout, ForecastParams.forecastHistory et ForecastParams.minDensity.
  • Des paramètres de réglage de la sensibilité qui, lorsqu'ils sont modifiés, peuvent augmenter ou diminuer le nombre d'anomalies signalées, la latence et les ressources de calcul. Exemples: ForecastParams.maxPositiveRelativeChange, ForecastParams.maxNegativeRelativeChange et ForecastParams.forecastExtraWeight.

Prérequis

Suivez les instructions de configuration de notre guide de démarrage rapide pour vous assurer que vous pouvez exécuter toutes les commandes de ce guide.

Nous vous montrerons l'impact de la variation de chaque paramètre sur les résultats renvoyés en interrogeant l'ensemble de données de démonstration public que nous avons déjà préchargé avec les données du projet GDELT.

Enregistrez la requête suivante sous le nom query.json dans votre répertoire de travail:

{
  dimensionNames: ["EntityLOCATION"],
  testedInterval: {
    startTime: "2019-04-15T00:00:00Z",
    length: "86400s"
  },
  forecastParams: {
    forecastHistory: "2592000s",
    holdout: 10.0,
    minDensity: 0.0,
    maxPositiveRelativeChange: 1.0,
    maxNegativeRelativeChange: 1.0,
    forecastExtraWeight: 200.0
  }
}

Comme indiqué dans le guide de démarrage rapide, vous pouvez émettre une requête à l'aide de la commande suivante:

$ gcurl -X POST -d @query.json https://timeseriesinsights.googleapis.com/v1/projects/timeseries-insights-api-demo/datasets/webnlp-201901-202104:query

Tout au long de ce guide, nous allons modifier différents paramètres dans query.json pour illustrer l'impact sur la sortie. Vous pouvez suivre la procédure en modifiant votre copie et en émettant les requêtes à l'aide de la commande gcurl ci-dessus, qui est identifiée comme une classe dans le guide de démarrage rapide.

Sélection des données

Une requête de détection d'anomalies évalue si des ensembles de données spécifiques contiennent des anomalies, dans un intervalle de temps donné, en divisant les données selon certaines dimensions et en filtrant éventuellement certaines valeurs pour certaines dimensions. Cette section explique comment contrôler toutes ces étapes de sélection des données.

Spécification de l'intervalle de temps

Vous pouvez spécifier l'intervalle de temps que vous souhaitez rechercher pour les anomalies en définissant QueryDataSetRequest.tested_interval. Dans query.json, nous indiquons que nous souhaitons détecter les anomalies survenues le jour du 15 avril 2019:

...
  testedInterval: {
    startTime: "2019-04-15T00:00:00Z",
    length: "86400s"
  },
...

Par exemple, si nous voulons détecter les anomalies survenues le 15 avril 2019 entre 13h et 2:00, nous devons définir testedInterval sur :

...
  testedInterval: {
    startTime: "2019-04-15T13:00:00Z",
    length: "3600s"
  },
...

CONSEIL: Testez la valeur testedInterval en définissant différentes heures de début et différentes longueurs, et observez comment les anomalies renvoyées diffèrent.

Pose de restriction

Lors de la détection d'anomalies dans un certain intervalle de temps, vous devez également spécifier la manière dont les événements sont regroupés en tranches de données. Pour ce faire, définissez dimensionNames sur l'ensemble de dimensions à segmenter sur l'ensemble de données.

Dans query.json, les données sont uniquement fractionnées par la dimension "EntityLOCATION" : dimensionNames: ["EntityLOCATION"]. Comme vous pouvez le constater dans la liste des résultats, toutes les tranches sont simplement des valeurs différentes pour la dimension "EntityLocation".

Si vous préférez diviser les données en plusieurs dimensions, nous pouvons spécifier plusieurs noms de dimensions:

{
  dimensionNames: ["EntityLOCATION", "EntityORGANIZATION"],
  testedInterval: {
    startTime: "2019-04-15T00:00:00Z",
    length: "86400s"
  },
  forecastParams: {
    forecastHistory: "2592000s",
    holdout: 10.0,
    minDensity: 0.0,
    maxPositiveRelativeChange: 1.0,
    maxNegativeRelativeChange: 1.0,
    forecastExtraWeight: 50.0
  }
}

Lors de l'exécution de cette requête, nous constatons que les tranches obtenues prennent des valeurs entre les deux dimensions spécifiées, "EntityLOCATION" et "EntityORGANIZATION" (seules la première tranche d'anomalies est renvoyée):

$ gcurl -X POST -d @query.json https://timeseriesinsights.googleapis.com/v1/projects/timeseries-insights-api-demo/datasets/webnlp-201901-202104:query

{
  "name": "projects/timeseries-insights-api-demo/datasets/webnlp-201901-202104",
  "anomalyDetectionResult": {
    "anomalies": [
      {
        "dimensions": [
          {
            "name": "EntityLOCATION",
            "stringVal": "Seine"
          },
          {
            "name": "EntityORGANIZATION",
            "stringVal": "AP"
          }
        ],
        "result": {
          "holdoutErrors": {},
          "trainingErrors": {},
          "forecastStats": {
            "numAnomalies": 1
          },
          "testedIntervalActual": 344
        },
        "status": {}
      },
...

Filtrage

Si vous souhaitez uniquement analyser un sous-ensemble de l'ensemble de données, vous ne pouvez filtrer que les événements ayant certaines valeurs pour certaines dimensions spécifiques en définissant pinnedDimensions.

Exemple pour le filtrage par EntityORGANIZATION et une restriction par EntityLOCATION (n'afficher que le premier résultat):

$ cat query.json

{
  dimensionNames: ["EntityLOCATION"],
  pinnedDimensions: [
  {
    name: "EntityORGANIZATION",
    stringVal: "AP"
  }
  ],
  testedInterval: {
    startTime: "2019-04-15T00:00:00Z",
    length: "86400s"
  },
  forecastParams: {
    forecastHistory: "2592000s",
    holdout: 10.0,
    minDensity: 0.0,
    maxPositiveRelativeChange: 1.0,
    maxNegativeRelativeChange: 1.0,
    forecastExtraWeight: 250.0
  }
}

$ gcurl -X POST -d @query.json https://timeseriesinsights.googleapis.com/v1/projects/timeseries-insights-api-demo/datasets/webnlp-201901-202104:query

{
  "name": "projects/timeseries-insights-api-demo/datasets/webnlp-201901-202104",
  "anomalyDetectionResult": {
    "anomalies": [
      {
        "dimensions": [
          {
            "name": "EntityLOCATION",
            "stringVal": "Seine"
          }
        ],
        "result": {
          "holdoutErrors": {},
          "trainingErrors": {},
          "forecastStats": {
            "numAnomalies": 1
          },
          "testedIntervalActual": 344
        },
        "status": {}
      },
      {
        "dimensions": [
          {
            "name": "EntityLOCATION",
            "stringVal": "Ile de la Cite"
          }
        ],
        "result": {
          "holdoutErrors": {},
          "trainingErrors": {},
          "forecastStats": {
            "numAnomalies": 1
          },
          "testedIntervalActual": 282
        },
        "status": {}
      },
...

Agrégation

La méthode d'agrégation par défaut compte le nombre d'événements dans la tranche.

Il est également possible de spécifier que nous souhaitons agréger des événements en additionnant une dimension numérique. Cela est spécifié en définissant ForecastParams.aggregatedDimension sur la dimension numérique que nous souhaitons additionner.

Configuration des prévisions

Pour calculer la valeur attendue au cours de l'intervalle de temps testé, nous utilisons des algorithmes de prévision qui, sur la base des valeurs historiques de la tranche, prédisent la valeur comprise au cours de l'intervalle testé.

REMARQUE: Les valeurs d'une tranche sont fournies par la méthode d'agrégation.

Historique des prévisions

La quantité de données que notre série temporelle contient est donnée par le paramètre ForecastParams.forecastHistory.

Dans notre premier exemple:

  • Nous avons défini forecastHistory: "2592000s" pour que nous puissions récupérer des événements entre le 16 mars 2019 et le 15 avril 2019 (30 jours, soit 25 000 secondes) et former une série temporelle entre ces deux jours.
  • Chaque point de données de la série temporelle couvrira une période égale à la durée de l'intervalle testé (soit 86400s à 1 jour dans notre exemple).
  • La série temporelle comporte 30 points, chaque point contenant la valeur agrégée des événements ce jour-là. En supposant que la comptabilisation soit la méthode d'agrégation, le premier point de la série temporelle correspond au nombre total d'événements du 16 mars 2019, au deuxième point du 17 mars 2019, etc.

Vous pouvez voir les valeurs historiques des séries temporelles renvoyées dans le champ ForecastResult.history (elles ne seront renvoyées que si QueryDataSetRequest.returnTimeseries est défini sur true dans la requête).

REMARQUE: Nous modélisons une série temporelle dans le fichier proto Timeseries en tant que collection de points (définies comme TimeseriesPoint), triées par heure et ayant une valeur (TimeseriesPoint.value) correspond à la valeur agrégée de la tranche pendant l'intervalle de temps [TimeseriesPoint.time, TimeseriesPoint.time + testedInterval.length].

En augmentant l'ForecastParams.forecastHistory et en incluant une historique plus longue, vous pouvez capturer certains modèles de saisonnalité et éventuellement, plus la précision de la prévision, car celle-ci contiendra plus de données. Par exemple, si nous disposons de schémas de saisonnalité mensuels, la définition de forecastHistory sur 30 jours ne nous permet pas de capturer ces schémas. Dans ce cas, nous vous recommandons donc d'augmenter la valeur forecastHistory Nous disposons donc de plusieurs modèles mensuels dans notre série temporelle d'analyse (par exemple, la détection des tendances mensuelles est 100 à 300 jours, mais cela dépend également des schémas réels).

Dans notre exemple initial, essayez de réduire la valeur de testedInterval.length à 3600s (1 heure) et d'augmenter ForecastParams.forecastHistory à 8640000s (100 jours). Vous augmenterez ainsi le nombre de points de la série temporelle history à 2 400, ce qui permettra de la rendre plus précise et de couvrir une période plus longue.

Intervalle avant ou test

Pour évaluer la qualité de nos prédictions, nous n'entraînons l'entraînement que sur les premiers X% de la série temporelle. La dernière partie de la série temporelle est conservée à des fins d'évaluation et est contrôlée par le paramètre ForecastParams.holdout.

Le paramètre ForecastParams.holdout représente un pourcentage (entre 0 et 100) indiquant la quantité de données que nous devons conserver à des fins de test. Dans notre exemple, nous avons spécifié holdout: 10.0. Nous conservons donc les 10 dernières parties de la série temporelle afin d'évaluer les performances de la prédiction en fonction des 90 % des premiers résultats.

Dans notre exemple initial, nous disposons de 30 points de série temporelle. Cela implique que les 27 premiers points sont conservés pour l'entraînement et les trois derniers pendant l'évaluation/l'évaluation du modèle.

Les erreurs que nous mesurons au cours de la période de restriction sont renvoyées dans ForecastResult.holdoutErrors, et elles sont utilisées pour calculer les prévisions inférieure et supérieure (testedIntervalForecastLowerBound et testedIntervalForecastUpperBound). Plus les erreurs sont élevées, plus la limite de prévision est élevée, tandis que plus les erreurs sont faibles, plus les limites sont serrées (et plus près de ForecastResult.testedIntervalForecast).

La valeur de répartition doit se situer aux points de faible pourcentage (3 % à 10%), mais elle doit s'assurer qu'elle contient suffisamment de points de données, car ces erreurs sont nécessaires pour le calcul des limites de prévision.

Densité minimale

La densité minimale, indiquée par le paramètre ForecastParams.minDensity, spécifie le nombre de séries temporelles non vides (contenant au moins un événement) qu'une série temporelle doit avoir pour être considérée comme une anomalie potentielle.

Comme pour l'abandon, il s'agit d'une valeur de pourcentage comprise entre 0 et 100.

Le nombre de points est comparé au nombre de points attendus entre testedInterval.startTime - forecastHistory et testedInterlal.startTime.

Par exemple, si nous avons 30 points de série temporelle attendus (comme dans notre premier exemple qui définit testedInterval.length sur 1 jour et forecastHistory sur 30 jours) et que nous définissons minDensity sur 80, nous n'acceptons que les séries temporelles contenant 24 points avec au moins un événement.

REMARQUE: Dans notre exemple, nous allons définir minDensity sur 0 et, lorsque nous détectons des pics, il est également défini sur 0.

Horizon

L'intervalle de temps, spécifié par ForecastParams.horizonTime, spécifie simplement l'avenir futur, dans l'intervalle testé, nous devons prédire les valeurs en fonction de la série temporelle historique.

Si QueryDataSetRequest.returnTimeseries est défini sur "true", les séries temporelles prévues sont renvoyées dans ForecastResult.forecast pour chaque tranche et contiennent les valeurs prédites entre testedInterval.startTime + testedInterval.length et testedInterval.startTime + testedInterval.length + horizonTime.

Réglage de la sensibilité

En fonction des limites prévues et de la valeur réelle pendant l'intervalle testé pour une tranche, nous pouvons la classer ou non en tant qu'anomalie en fonction des paramètres de sensibilité. Ces paramètres sont les suivants:

  • ForecastParameters.maxPositiveRelativeChange indique la valeur que la valeur réelle peut augmenter par rapport à testedIntervalForecastUpperBound.
  • ForecastParameters.maxNegativeRelativeChange indique dans quelle mesure la valeur réelle peut diminuer par rapport à testedIntervalForecastLowerBound
  • La valeur ForecastParameters.forecastExtraWeight est utilisée comme pondération supplémentaire lorsque vous comparez les limites réelles ou prévisionnelles. Cette pondération est utile si vous souhaitez ignorer les variances absolues plus petites.

REMARQUE: Nous proposons des paramètres différents pour les changements positifs et négatifs, car nous souhaitons donner à nos clients la possibilité d'avoir différents seuils pour des pics et des baisses, ou de désactiver l'un ou l'autre en définissant très précisément plafonds hauts.

REMARQUE: La valeur pratique maximale de maxNegativeRelativeChange est de 1,0 pour les séries temporelles positives (par exemple, lors du comptage du nombre d'événements en tant que méthode d'agrégation), car la valeur ne peut pas dépasser 100% de sa valeur réelle.

Comme point de départ, essayez de définir tous les paramètres de sensibilité suivants sur 0:

{
  dimensionNames: ["EntityLOCATION"],
  testedInterval: {
    startTime: "2019-04-15T00:00:00Z",
    length: "86400s"
  },
  forecastParams: {
    forecastHistory: "2592000s",
    holdout: 10.0,
    minDensity: 0.0,
    maxPositiveRelativeChange: 0.0,
    maxNegativeRelativeChange: 0.0,
    forecastExtraWeight: 0.0
  }
}

Il s'agit de la requête la plus sensible que nous exécutons et qui signalera toutes les tranches contenant le paramètre testedIntervalActual en dehors des limites [testedIntervalForecastLowerBound, testedIntervalForecastUpperBound]. Même si cela peut sembler que ce peut être souhaitable (et qu'il peut être utile dans certaines applications), nous inciterons à ignorer la plupart des anomalies, car elles entraîneront principalement des faux positifs.

L'exécution de cette requête de référence donne lieu à environ 2 500 anomalies:

$ gcurl -X POST -d @query.json https://timeseriesinsights.googleapis.com/v1/projects/timeseries-insights-api-demo/datasets/webnlp-201901-202104:query \
    | grep "testedIntervalActual" | wc -l

2520

Si nous élevons à la fois maxPositiveRelativeChange et maxNegativeRelativeChange à 1, 0, nous constatons que le nombre d'anomalies est réduit à ~ 1800, ce qui élimine les tranches avec des variances plus faibles, mais nous obtenons encore d'un grand nombre de sexes considérés comme des anomalies.

En supposant que nous ne nous intéresserions qu'aux pics d'actualité les plus élevés au cours de la journée, nous pourrions également augmenter forecastExtraWeight afin de filtrer les tranches de volume faible. Définissons-la sur 200.0, ce qui nous donnera la dernière query.json:

{
  dimensionNames: ["EntityLOCATION"],
  testedInterval: {
    startTime: "2019-04-15T00:00:00Z",
    length: "86400s"
  },
  forecastParams: {
    forecastHistory: "2592000s",
    holdout: 10.0,
    minDensity: 0.0,
    maxPositiveRelativeChange: 1.0,
    maxNegativeRelativeChange: 1.0,
    forecastExtraWeight: 200.0
  }
}

L'exécution de la requête précédente ne génèrera que trois anomalies, toutes associées à l'incendie de la cathédrale Notre-Dame, qui a été le plus mentionné dans l'actualité le 15 avril 2019:

$ gcurl -X POST -d @query.json https://timeseriesinsights.googleapis.com/v1/projects/timeseries-insights-api-demo/datasets/webnlp-201901-202104:query \
    | grep stringVal

            "stringVal": "Ile de la Cite"
            "stringVal": "Notre Dame"
            "stringVal": "Seine"

Performances des requêtes et utilisation des ressources

Outre la réduction du bruit et le filtrage des tranches non intéressantes, la réduction de la sensibilité à la détection d'anomalies peut également réduire considérablement la latence des requêtes et l'utilisation des ressources.

Étant donné que l'augmentation de la forecastExtraWeight entraîne généralement la réduction la plus notable dans les tranches anormales, il s'agit également du paramètre de sensibilité principal qui devrait être augmenté afin d'améliorer les performances des requêtes.

Vous pouvez tester différents paramètres de sensibilité pour voir comment ils affectent les performances de la requête. Exemple :

$ cat query.json

{
  dimensionNames: ["EntityLOCATION"],
  testedInterval: {
    startTime: "2019-04-15T00:00:00Z",
    length: "86400s"
  },
  forecastParams: {
    forecastHistory: "2592000s",
    holdout: 10.0,
    minDensity: 0.0,
    maxPositiveRelativeChange: 0.0,
    maxNegativeRelativeChange: 0.0,
    forecastExtraWeight: 0.0
  }
}

$ time gcurl -X POST -d @query.json https://timeseriesinsights.googleapis.com/v1/projects/timeseries-insights-api-demo/datasets/webnlp-201901-202104:query \
    | grep stringVal | wc -l

2580

real 0m26.765s
user 0m0.618s
sys  0m0.132s

Nous pouvons constater que les paramètres les plus sensibles conduisent à la requête en utilisant environ 26.7. La réduction de la sensibilité garantit une accélération de la vitesse allant jusqu'à 3,4 s:

$ cat query.json

{
  dimensionNames: ["EntityLOCATION"],
  testedInterval: {
    startTime: "2019-04-15T00:00:00Z",
    length: "86400s"
  },
  forecastParams: {
    forecastHistory: "2592000s",
    holdout: 10.0,
    minDensity: 0.0,
    maxPositiveRelativeChange: 1.0,
    maxNegativeRelativeChange: 1.0,
    forecastExtraWeight: 200.0
  }
}

$ time gcurl -X POST -d @query.json https://timeseriesinsights.googleapis.com/v1/projects/timeseries-insights-api-demo/datasets/webnlp-201901-202104:query \
    | grep stringVal | wc -l

3

real 0m3.412s
user 0m0.681s
sys  0m0.047s

Étape suivante