教程

我们使用 Kalev Letaru 提供的小型数据集来说明 Timeseries Insights API。 该数据集派生自 GDELT 项目,后者是一个全局数据库跟踪世界事件和媒体覆盖范围。 此数据集包含 2019 年 4 月的新闻网址中提及的实体。

目标

  • 了解 Timeseries Insights API 的数据格式。
  • 了解如何创建、查询、更新和删除数据集。

费用

预览不会产生任何费用。

准备工作

按照使用入门中的说明设置一个 Cloud 项目并启用 Timeseries Insights API。

教程数据集

该数据集包含位置、组织、人员等的实体注释。

Timeseries Insights API 接受 JSON 格式的输入。此数据集的一个 Event 示例

{
  "groupId":"-6180929807044612746",
  "dimensions":[{"name":"EntityORGANIZATION","stringVal":"Medina Gazette"}],
  "eventTime":"2019-04-05T08:00:00+00:00"
}

每个事件都必须具有事件时间戳的 eventTime 字段和用于标记相关事件的长整型 groupId。事件属性包括为 dimensions,每个属性都具有 name 以及 stringValboolVallongValdoubleVal 之一。

注意:Google Cloud API 接受采用 JSON 字段名称的 camel 峰式大小写形式(例如 camelCase)和蛇形大小写(例如 snake_case)。文档主要以 camel 峰式大小写形式编写。

注意:由于 JSON 长值(数字)实际上是具有仅精度精度的浮点值,groupIdlongVal如果 JSON 使用数字,则实际上相当于 53 个二进制数字。为了提供 int64 数据,JSON 值应作为字符串进行引用。 groupId 通常是数字 ID 或使用确定性哈希函数生成,用于满足上述限制。

注意namestringVal 字段应当为字母数字值,包括 '_'。不支持包含空格的特殊字符。

注意:从静态 Google Cloud Storage 数据源读取数据时,每个 JSON 事件应该为一行,如下所示:

{"groupId":"-6180929807044612746","dimensions":[{"name":"EntityORGANIZATION","stringVal":"Medina Gazette"}],"eventTime":"2019-04-05T08:00:00+00:00"}

列出数据集

projects.datasets.list 显示 ${PROJECT_ID} 下的所有数据集。 请注意,gcurl 是别名,PROJECT_ID 是环境变量,两者都在使用入门中设置。

$ gcurl https://timeseriesinsights.googleapis.com/v1/projects/${PROJECT_ID}/datasets

结果显示如下 JSON 字符串:

{
  "datasets": [
    {
      "name": "example",
      "state": "LOADED",
      ...
    },
    {
      "name": "dataset_tutorial",
      "state": "LOADING",
      ...
    }
  ]
}

结果显示项目当前所使用的数据集。 state 字段指示数据集是否已准备就绪,可供使用。刚刚创建数据集时,数据集将处于 LOADING 状态,直到索引完成,然后转换到 LOADED 状态。如果在创建和编入索引过程中出现任何错误,它将处于 FAILED 状态。结果还包含原始创建请求的完整数据集信息。

创建数据集

projects.datasets.create 用于向项目添加新数据集。

$ gcurl -X POST -d @create.json https://timeseriesinsights.googleapis.com/v1/projects/${PROJECT}/datasets

其中 create.json 包含以下内容:

{
  name: "dataset_tutorial",
  streaming: true,
  ttl: "8640000s",
  dataNames: [
    "EntityCONSUMER_GOOD",
    "EntityEVENT",
    "EntityLOCATION",
    "EntityORGANIZATION",
    "EntityOTHER",
    "EntityPERSON",
    "EntityUNKNOWN",
    "EntityWORK_OF_ART",
  ],
  dataSources: [
    {uri: "gs://data.gdeltproject.org/blog/2021-timeseries-insights-api/datasets/webnlp-201904.json"}
  ]
}

此请求从 GCS dataSources 创建一个名为 dataset_tutorial 的数据集,其中包含 JSON 格式的事件数据。只有 dataNames 中列出的维度才会被编入索引和使用。如果为 streaming=true,则数据集还会在初始索引完成后接受流式更新。超过 ttl 的流式更新会被忽略。

如果 API 服务器接受创建请求,则该请求将返回成功。数据集将一直处于 LOADING 状态,直到完成索引,然后状态会变为 LOADED,并开始接受查询和更新(如果有)。

查询数据集

projects.datasets.query 执行异常检测查询。

$ gcurl -X POST -d @query.json https://timeseriesinsights.googleapis.com/v1/projects/${PROJECT}/datasets/dataset_tutorial:query

其中 query.json 包含以下内容:

{
  dimensionNames: ["EntityLOCATION"],
  testedInterval: {
    startTime: "2019-04-15T00:00:00Z",
    length: "86400s"
  },
  forecastParams: {
    holdout: 10,
    minDensity: 0,
    forecastHistory: "1209600s",
    maxPositiveRelativeChange: 1,
    maxNegativeRelativeChange: 1,
    forecastExtraWeight: 0,
    seasonalityHint: "DAILY",
  },
  returnNonAnomalies: true
}

我们想要检测testedInterval划分维度时,dimensionNames中下载 Google 健身应用。切片是数据集中事件的子集,具有一些维度的固定值。例如,{"name": "EntityLOCATION","stringVal": "Seine River"} 是一个切片。数据集定义中任何 dataNames 子集都可以用作 dimensionNames,该 API 将对未提及的维度进行聚合。这类似于 SQL 查询中带有“count(*)”的“分组依据”操作。

每个切片中的事件根据 forecastParams.aggregatedDimension 进行汇总。如果此字段为空,我们将仅计算切片中的所有事件。如果不是空,则此字段将是在此切片的事件中有效的数字维度名称。系统会将这些数值相加以形成时间序列。

我们将通过以下方式分析每个切片,以检查是否存在异常:

  1. 创建一个从 testedInterval.startTime - forecastParams.forecastHistorytestedInterval.startTime + testedInterval.length 的时间序列,其中每个数据点都是长度为 testedInterval.length 的时区,并通过汇总该存储分区中的事件来获取其值如 aggregatedDimension 参数所指定。如果时间序列没有足够的数据点(由 minDensity 参数指定),我们将停止分析该数据点。
  2. 计算切片的时间序列后,我们将使用常用的预测技术对其进行分析。时间序列的第一个 (100 - holdout)% 用于训练预测模型,最后一个 holdout% 用于测试模型的质量。根据错误指标,我们将计算已测试间隔的置信区间,如果实际值超出配置的范围,我们会将其标记为异常。如需配置实际值在边界值外的数量,请检查 maxPositiveRelativeChangemaxNegativeRelativeChangeforecastExtraWeight 参数。

查询结果如下所示:

{
  "name": "projects/timeseries-staging/datasets/dataset_tutorial",
  "anomalyDetectionResult": {
    "anomalies": [
      {
        "dimensions": [
          {
            "name": "EntityLOCATION",
            "stringVal": "Ile de la Cite"
          }
        ],
        "result": {
          "holdoutErrors": {},
          "trainingErrors": {
            "mdape": 1,
            "rmd": 1
          },
          "forecastStats": {
            "density": "23",
            "numAnomalies": 1
          },
          "testedIntervalActual": 440,
          "testedIntervalForecastLowerBound": -1,
          "testedIntervalForecastUpperBound": 1
        },
        "status": {}
      },
      {
        "dimensions": [
          {
            "name": "EntityLOCATION",
            "stringVal": "Seine"
          }
        ],
        "result": {
          "holdoutErrors": {
            "mdape": 0.1428571428571429,
            "rmd": 0.1428571428571429
          },
          "trainingErrors": {
            "mdape": 0.84615384615384626,
            "rmd": 0.62459546925566334
          },
          "forecastStats": {
            "density": "85",
            "numAnomalies": 1
          },
          "testedIntervalActual": 586,
          "testedIntervalForecast": 9.3333333333333339,
          "testedIntervalForecastLowerBound": 8,
          "testedIntervalForecastUpperBound": 10.666666666666668
        },
        "status": {}
      },
      {
        "dimensions": [
          {
            "name": "EntityLOCATION",
            "stringVal": "Notre Dame"
          }
        ],
        "result": {
          "holdoutErrors": {
            "mdape": 0.42857142857142855,
            "rmd": 0.42857142857142855
          },
          "trainingErrors": {
            "mdape": 0.19999999999999996,
            "rmd": 0.65055762081784374
          },
          "forecastStats": {
            "density": "100",
            "numAnomalies": 1
          },
          "testedIntervalActual": 790,
          "testedIntervalForecast": 7,
          "testedIntervalForecastLowerBound": 4,
          "testedIntervalForecastUpperBound": 10
        },
        "status": {}
      },
      ...
    ],
    "nonAnomalies": [
      ...
    ]
  }
}

它包含相同的 ForecastSlice 格式,以及(可选)未标记为异常的切片。 result 显示异常值、实际值和预测值的范围。trainingErrorsholdoutErrors 显示用于异常检测的其他指标。

流式更新

如果创建请求指定 streaming: trueprojects.datasets.appendEvents 将以流式方式添加事件记录。

$ gcurl -X POST -d @append.json https://timeseriesinsights.googleapis.com/v1/projects/${PROJECT}/datasets/dataset_tutorial:appendEvents

其中 append.json 包含以下内容:

{
  events: [
    {
      "groupId":"-5379487492185488040",
      "dimensions":[{"name":"EntityPERSON","stringVal":"Jason Marsalis"}],
      "eventTime":"2021-06-01T15:45:00+00:00"
    },{
      "groupId":"1324354349507023708",
      "dimensions":[{"name":"EntityORGANIZATION","stringVal":"WAFA"}],
      "eventTime":"2021-06-02T04:00:00+00:00"
    }
  ]
}

流式更新以近乎实时的速度编入索引,因此更改可以在查询结果中快速响应。

删除数据集

projects.datasets.delete 将数据集标记为删除。

$ gcurl -X DELETE https://timeseriesinsights.googleapis.com/v1/projects/${PROJECT}/datasets/dataset_tutorial

请求会立即返回,且数据集不会接受其他查询或更新。数据可能需要一段时间才会从服务中完全移除,之后,List 数据集将不会返回此数据集。

后续步骤

您可以在 GDELT 网站中找到一些其他示例。