Guía de creación de consultas

Información general

En esta sección, te explicaremos cómo configurar correctamente las consultas de detección de anomalías. A un nivel alto, hay cuatro tipos de parámetros que debes ajustar:

  • Parámetros de selección de datos que especifican los datos que se incluyen en nuestro análisis. Ejemplos: QueryDataSetRequest.dimensionNames, QueryDataSetRequest.pinnedDimensions y QueryDataSetRequest.testedInterval.
  • Parámetros de agregación que especifican cómo se agrupan varios eventos para generar un valor que se utiliza para comparar sectores. Ejemplos: ForecastParams.aggregatedDimension.
  • Previsiones que se utilizan para configurar los algoritmos de previsión que se emplean para calcular los valores esperados. Ejemplos: ForecastParams.holdout, ForecastParams.forecastHistory y ForecastParams.minDensity.
  • Parámetros de ajuste de sensibilidad que, cuando se modifican, pueden aumentar o disminuir el número de anomalías que se notifican, la latencia y los recursos informáticos. Ejemplos: ForecastParams.maxPositiveRelativeChange, ForecastParams.maxNegativeRelativeChange y ForecastParams.forecastExtraWeight.

Requisitos previos

Sigue las instrucciones de configuración de esta guía de inicio rápido para asegurarte de que puedes ejecutar todos los comandos de esta guía.

Te mostraremos cómo varía la forma en que cada parámetro afecta a los resultados devueltos. Para ello, consulta el conjunto de datos de demostración público que ya hemos precargado con datos del proyecto GDELT.

Guarda la siguiente consulta como query.json en tu directorio de trabajo:

{
  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
  }
}

Tal como se menciona en la guía de inicio rápido, puedes hacer una consulta con el siguiente comando:

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

En esta guía, cambiaremos distintos parámetros en query.json para mostrar cómo afecta a los resultados. Puedes continuar editando la copia y volver a emitir las consultas con el comando gcurl anterior, que se desidentifica como un anuncio auxiliar en la guía de inicio rápido.

Selección de datos

Una consulta de detección de anomalías evalúa si hay anomalías en un determinado conjunto de datos, en un intervalo de tiempo determinado, fraccionando los datos con algunas dimensiones y, opcionalmente, filtrando algunos valores para algunas dimensiones. En esta sección explicaremos cómo controlar todos estos pasos de selección de datos.

Especificación de intervalo de tiempo

Podemos especificar el intervalo de tiempo que queremos examinar para detectar anomalías en QueryDataSetRequest.tested_interval. En query.json, especificamos que queremos detectar anomalías que se hayan producido durante el día 15 de abril del 2019:

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

Por ejemplo, si tuviéramos que detectar anomalías que se produjeron el 15 de abril del 2019 entre las 13:00 y las 14:00, asignaremos el valor testedInterval a:

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

CONSEJO: juega con el valor testedInterval definiendo diferentes horas de inicio y diferentes duraciones, y descubre cómo varía la anomalía que se devuelve.

Limpieza

Al detectar anomalías en un intervalo de tiempo determinado, también debemos especificar cómo se agrupan los eventos en sectores de datos. Para ello, se asigna el valor dimensionNames al conjunto de dimensiones que se quiere dividir en el conjunto de datos.

En query.json, solo se replican los datos con la dimensión "EntityLOCATION": dimensionNames: ["EntityLOCATION"]. En la lista de resultados, puedes ver que cada fragmento es simplemente un valor diferente para la dimensión "EntityLocation".

Si, por el contrario, queremos dividir los datos en varias dimensiones, podemos especificar varios nombres de dimensiones:

{
  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
  }
}

Al ejecutar esta consulta, comprobaremos que los sectores resultantes extraen valores en las dos dimensiones especificadas: "EntityLOCATION" y "EntityORGANIZATION" (solo mostramos la primera parte de anomalías que se devuelve):

$ 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": {}
      },
...

Filtros

Si solo te interesa analizar un subconjunto del conjunto de datos, podemos filtrar solo los eventos que tengan valores determinados en determinadas dimensiones configurando pinnedDimensions.

Ejemplo de filtrado por EntityORGANIZATION y acotado por EntityLOCATION (solo se muestra el primer resultado):

$ 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": {}
      },
...

Agregación

El método de agregación predeterminado es el número de eventos de la porción.

También podemos especificar eventos que sumamos sumando una dimensión numérica. Para especificarlo, se asigna el valor numérico ForecastParams.aggregatedDimension a la dimensión numérica que queremos sumar.

Configuración de previsiones

Para calcular el valor esperado durante el intervalo de tiempo probado, utilizamos algoritmos de previsión que, según los valores históricos de la porción, predicen el valor que debe tener el valor durante el intervalo probado.

NOTA: Los valores de una porción los proporciona el método de agregación.

Historial de previsiones

La cantidad de datos que contiene la serie temporal se proporciona mediante el parámetro ForecastParams.forecastHistory.

En nuestro ejemplo inicial:

  • Hemos definido forecastHistory: "2592000s" para que, de este modo, podamos obtener eventos entre el 16 de marzo del 2019 y el 15 de abril del 2019 (30 días, que son 2592000 segundos), y formar una serie temporal entre estos dos días.
  • Cada punto de datos de la serie temporal cubrirá un periodo de tiempo igual a la duración del intervalo probado (de 1 a 86400s en nuestro ejemplo).
  • La serie temporal tendrá 30 puntos y cada punto conservará el valor agregado de los eventos de ese día natural. Si asumimos que se trata de nuestro método de agregación, el primer punto de la serie temporal será el número total de eventos del 16 de marzo del 2019, el segundo punto del 17 de marzo del 2019, y así sucesivamente.

Puedes ver los valores históricos de las series temporales devueltas en el campo ForecastResult.history (solo se devolverá si el valor de QueryDataSetRequest.returnTimeseries es true en la solicitud).

NOTA: Estamos modelando una serie temporal en el protocolo Timeseries como una colección de puntos (definidos como TimeseriesPoint), ordenados por tiempo y teniendo como valor (TimeseriesPoint.value) es el valor agregado del fragmento durante el intervalo de tiempo [TimeseriesPoint.time, TimeseriesPoint.time + testedInterval.length].

Al aumentar el ForecastParams.forecastHistory e incluir un historial más largo, puedes registrar determinados patrones de estacionalidad y, potencialmente, aumentar la precisión de la previsión, ya que contendrá más datos. Por ejemplo, si tenemos patrones de estacionalidad mensuales, el valor de forecastHistory debe ser de 30 días no nos permitirá capturarlos; por lo tanto, deberías aumentar la forecastHistory, por lo que tenemos varios patrones mensuales en nuestra serie temporal analizado (por ejemplo, los patrones mensuales deben ser reales de entre 100 y 300 días, pero también dependen de los patrones reales).

En nuestro ejemplo inicial, prueba a reducir el valor de testedInterval.length a 3600s (1 hora) y a aumentar ForecastParams.forecastHistory a 8640000s (100 días). Esto aumentará el número de puntos de la serie temporal history a 2400, por lo que será más detallado y cubrir un periodo de tiempo más largo.

Intervalo de retención/prueba

Para evaluar la calidad de nuestra predicción, solo nos hemos centrado en el primer X% de la serie temporal. La última parte de la serie temporal se conserva para fines de evaluación y se controla mediante el parámetro ForecastParams.holdout.

El parámetro ForecastParams.holdout representa un porcentaje (en el intervalo de 0 a 100) que nos indica qué cantidad de datos debemos mantener para fines de prueba. En nuestro ejemplo hemos especificado holdout: 10.0, por lo que conservamos el 10% restante de la serie temporal para evaluar el rendimiento de la predicción según el primer 90 %.

Teniendo en cuenta que tenemos 30 puntos de series temporales en nuestro ejemplo inicial, esto significa que los primeros 27 puntos se conservan para la preparación y los últimos 3 para realizar pruebas o evaluaciones.

Los errores que se miden durante el periodo de espera se devuelven en ForecastResult.holdoutErrors y esos errores se utilizan para calcular los límites superiores y superiores de las previsiones (testedIntervalForecastLowerBound y testedIntervalForecastUpperBound). Los errores de retención más altos nos proporcionan un alcance de previsión más amplio, mientras que los errores más bajos nos proporcionan límites más estrechos (y más cerca de ForecastResult.testedIntervalForecast).

El valor de la retención debe estar en los puntos porcentuales bajos (del 3 % al 10%), pero debe asegurarse de que contenga suficientes puntos de datos, ya que se necesitan estos errores para calcular los límites de las previsiones.

Densidad mínima

La densidad mínima, especificada por el parámetro ForecastParams.minDensity, especifica cuántos puntos de serie temporal no vacíos (que contienen al menos un evento) debe contener una serie temporal para que se considere una anomalía.

Al igual que el bloqueo, se trata de un valor porcentual del intervalo 0-100.

El número de puntos se compara con el número de puntos esperados entre testedInterval.startTime - forecastHistory y testedInterlal.startTime.

Por ejemplo, si tenemos 30 puntos de serie temporal esperados (como en nuestro primer ejemplo, se asigna el valor testedInterval.length a 1 día y forecastHistory a 30 días), y definiremos minDensity a 80, solo aceptaremos series temporales que contengan 24 puntos con al menos un evento.

NOTA: en nuestro ejemplo, establecemosminDensity en 0 y, cuando se detecten picos, también se configura como 0.

Horizonte

El horizonte temporal, especificado por ForecastParams.horizonTime, especifica simplemente lo que se prevé en el futuro, a partir del intervalo probado, debemos predecir los valores en función de la serie temporal histórica.

Si se asigna el valor "true" a QueryDataSetRequest.returnTimeseries, la serie temporal de la previsión se devolverá en ForecastResult.forecast para cada porción y contendrá los valores previstos entre testedInterval.startTime + testedInterval.length y testedInterval.startTime + testedInterval.length + horizonTime.

Ajuste de sensibilidad

En función de los límites previstos y del valor real durante el intervalo de prueba de una porción, es posible que los clasifiquemos o no como una anomalía basada en los parámetros de sensibilidad. Estos parámetros son los siguientes:

  • ForecastParameters.maxPositiveRelativeChange especifica cuánto puede aumentar el valor real si se compara con testedIntervalForecastUpperBound.
  • ForecastParameters.maxNegativeRelativeChange especifica cuánto puede reducir el valor real en comparación con el parámetro testedIntervalForecastLowerBound.
  • ForecastParameters.forecastExtraWeight se usa como ponderación adicional al comparar los límites reales de los de la previsión. Tener un peso extra mayor es útil cuando hay que ignorar las varianzas de menor tamaño.

NOTA: Tenemos distintos parámetros para cambios positivos y negativos, ya que queremos permitir a nuestros clientes la flexibilidad de tener distintos límites en las trampas y en las caídas, o inhabilitar uno u otro (y viceversa). límites altos.

NOTA: El valor práctico máximo de maxNegativeRelativeChange es 1,0 para las series temporales positivas (por ejemplo, al contar el número de eventos como método de agregación) como un valor no superior a 100% de su valor real

Como punto de referencia, prueba a configurar todos estos parámetros de sensibilidad como 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
  }
}

Esta es la consulta más sensible que se puede ejecutar y marcará como anomalías en todas las porciones que tienen el valor testedIntervalActual fuera de los límites [testedIntervalForecastLowerBound, testedIntervalForecastUpperBound]. Aunque pueda parecer lo que queremos (y podría ser útil en algunas aplicaciones), en la práctica nos centraremos en ignorar la mayoría de estas anomalías, ya que, en general, se considerarán falsos positivos.

Si ejecutas esta consulta de referencia, aparecerán 2500 anomalías:

$ 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 aumentamos los valores de maxPositiveRelativeChange y maxNegativeRelativeChange a 1,0, notamos que el número de anomalías se reduce a 1800 which, lo que elimina las divisiones con pequeñas variaciones, pero, aun así, un gran número de porciones clasificadas como anomalías.

Supongamos que solo nos interesaría los picos más importantes de ese día, también podríamos aumentar un forecastExtraWeight y excluir los grupos de menor volumen de tráfico. Vamos a establecerla en 200,0, lo que nos dará la última palabra final a 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
  }
}

Al ejecutar la consulta anterior, solo se producirán tres anomalías: las asociadas al incendio Nor Dame, que fue el evento más mencionado del 15 de abril del 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"

Rendimiento de las consultas y uso de recursos

Además de reducir el ruido y filtrar las secciones que no son de interés, reducir la sensibilidad a la detección de anomalías también puede reducir significativamente la latencia de las consultas y el uso de recursos.

Aumentar el valor de forecastExtraWeight suele dar lugar a la reducción de peso en las porciones de anomalías, también es el parámetro de sensibilidad más importante que se debe aumentar con el objetivo de mejorar el rendimiento de las consultas.

Puede jugar con diferentes parámetros de sensibilidad para comprobar cómo afectan al rendimiento de la consulta. Por ejemplo:

$ 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

Podemos observar que los parámetros más sensibles hacen que nuestra consulta tarde aproximadamente 6.76,76. Si se reduce la sensibilidad, se puede aumentar la velocidad hasta los 3,4 :.

$ 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

Siguientes pasos