教學課程

我們會使用 Kalev Leetaru 提供的小型資料集來說明時間序列深入分析 API。 資料集來自 GDELT Project,這是一項全球資料庫追蹤事件和媒體涵蓋率。 這個資料集涵蓋 2019 年 4 月新聞網址提及的實體。

目標

  • 瞭解 Timeseries API 的資料格式。
  • 瞭解如何建立、查詢、更新及刪除資料集。

費用

預覽沒有任何費用。

事前準備

按照入門指南設定 Cloud 專案並啟用 Timeseries Insights API。

教學課程資料集

該資料集包含位置、機構、人員等實體註解。

Timeseries Insights API 會採用 JSON 格式輸入內容。這個資料集的事件範例為

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

每個事件都必須有事件時間戳記的 eventTime 欄位,以及長值 groupId 以標記相關事件。事件屬性會以 dimensions 形式提供,每個屬性都有 namestringValboolVallongValdoubleVal 其中之一。

附註:Google Cloud API 接受 JSON 欄位名稱的 cam 客服案件 (例如 camelCase) 和蛇大小寫 (例如 snake_case)。這類說明文件大多是 came 尾形。

附註:由於 JSON 長值 (數字) 實際上是僅有整數精確度的浮點值,如果 JSON 使用 groupIdlongVal,則其有效效率為 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
}

我們希望在 dimensionNames 提供的所有維度之間,偵測到 testedInterval 發生的任何異常情況。「slice」是資料集中一些事件固定值的子集, 例如,{"name": "EntityLOCATION","stringVal": "Seine River"} 是片段。資料集定義中 dataNames 的任何子集均可用來當做 dimensionNames,API 則會匯總未提及的維度事件。這與 SQL 查詢中具有「計數(*)」的「分組依據」作業類似。

每個片段中的資料均由 forecastParams.aggregatedDimension 來匯總。如果這個欄位為空白,系統只會計算片段中的資料事件。如果沒有內容,我們會預期在此欄位的事件中顯示有效的數值維度名稱。 數值會加總,形成時間序列。

我們會分析每個片段,藉此確認問題是否異常:

  1. 組成時間序列影片testedInterval.startTime - forecastParams.forecastHistory收件者testedInterval.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": [
      ...
    ]
  }
}

這個檔案內含異常,但您也可以選擇在相同的 ForecastSlic 開始格式中,找出未標示為異常的片段。 result 會顯示異常情況、實際值以及預測值的範圍。trainingErrorsholdoutErrors 會顯示用於異常偵測的其他指標。

串流更新

如果建立要求指定了 streaming: trueprojects.datasets.appendEvent 會以串流方式新增事件記錄。

$ 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

要求會立即傳回,而資料集不會接受其他查詢或更新。資料可能需要一段時間才能完全從服務完全移除,之後資料集清單不會傳回這個資料集。

後續步驟

您可以在 GDELT 網站上找到其他範例。