巢狀 JSON 日誌怎麼做時序特徵萃取?
某企業需分析半結構化的系統日誌(JSON 格式),以提取關鍵的時序特徵供故障預測模型使用。考量日誌結構複雜且包含巢狀欄位(Nested Fields),下列哪一種策略最有效且實務可行?
一家企業的系統每天產生大量「日誌」(log),格式是 JSON。這些 JSON 結構很複雜,裡面有「欄位裡面還有欄位」(巢狀欄位,Nested Fields)的情況。
他們要從這些日誌裡抽出跟「時間順序」有關的特徵,用來預測系統故障。
問你:哪一種做法最有效且實務可行?
一句話總結
處理巢狀 JSON 日誌要做時序特徵的正確做法:先用遞迴函式把巢狀欄位展開,再按時間窗口切段、聚合成統計特徵,這樣才能同時保留結構資訊和時序規律。
先感受問題:伺服器日誌長什麼樣
假設「雲端運算公司雷鳴科技」的伺服器每秒產生一筆 JSON 日誌,長這樣:
"timestamp": "2024-03-15T10:00:01Z",
"server_id": "srv-007",
"metrics": {
"cpu": {"usage": 87.3, "temp": 72},
"memory": {"used_gb": 14.2, "total_gb": 16},
"disk_io": {"read_mb": 230, "write_mb": 45}
},
"errors": [{"code": "E502", "count": 3}]
}
注意 metrics 裡還有 cpu、memory、disk_io,每個下面還有更多欄位。這就是「巢狀欄位」。
雷鳴科技想預測:未來 30 分鐘內伺服器會不會故障?
要預測故障,光有「某一秒的快照」不夠,必須看「過去 10 分鐘 CPU 使用率的趨勢」「過去 5 分鐘 E502 錯誤累積幾次」這類時序特徵。
單純扁平化或直接塞 RNN 為什麼做不好
兩個直覺做法,都有各自的坑:
- 直接扁平化轉 CSV,計算均值次數:把巢狀結構壓平後,
metrics.cpu.usage雖然變成一個欄位,但扁平化只抓「這一瞬間」的值,再算均值也只是「10 分鐘內 CPU 的平均」,抓不到「CPU 從 40% 快速爬升到 87% 然後突然掉下來」這種趨勢模式,而故障往往出現在趨勢異常時。 - RNN 直接吃原始 JSON 字串:RNN 要吃「數值序列」,不能直接吃原始文字。把 JSON 當字串丟進去,模型看到的是
{"cpu"這些字元,而不是數值,完全沒有意義。就算強行 tokenize,JSON 的括號、引號也是雜訊,不是信號。 - 只留時間戳記,其他都丟:把所有感測器數據都丟掉,只剩「幾點幾分」,根本無法預測故障。時間本身不是故障原因,異常的 CPU 溫度和錯誤碼才是。
- 巢狀欄位沒有統一展開,部分遺漏:如果手動一層一層取欄位,很容易漏掉某個深層欄位,或者碰到
errors這種陣列(array)不知道怎麼處理。 - 時間窗口沒有設計:不管是扁平化還是其他方法,如果沒有「時間窗口」的概念,就只能用單筆快照做預測,完全沒有時序資訊。
遞迴展開 + 時間窗口聚合,怎麼解
回到雷鳴科技。正確做法分兩個步驟:
步驟一:遞迴函式展開巢狀欄位
寫一個函式,從 JSON 的最外層開始往內走,碰到「欄位的值還是一個字典」就繼續往下走,直到拿到純數值為止。展開後變成:
metrics.cpu.temp = 72
metrics.memory.used_gb = 14.2
metrics.disk_io.read_mb = 230
errors.E502.count = 3
步驟二:按時間窗口聚合
把過去 10 分鐘(600 筆)的展開欄位,按「時間窗口」切成一個特徵向量:
cpu_usage_max = 87.3 (10 分鐘最高)
cpu_usage_slope = +2.3 (上升趨勢)
E502_count_sum = 12 (10 分鐘累積錯誤數)
這樣就同時捕捉到「目前狀態」和「過去 10 分鐘的趨勢」,把它們送進故障預測模型,準確率大幅提升。
這就是選項 C 講的:設計遞迴函式展開巢狀欄位,並基於時間窗口(Time Window)進行聚合與特徵萃取。
技術版:遞迴展開 + 時間窗口聚合的實際程式碼
中級考試大概率會考程式碼跟公式,所以這部分你還是要學。但如果現在學起來很痛苦,可以先跳過,等讀完其他題目回頭再來。
本題的核心技術是兩段 Python 函式,一個負責展開,一個負責聚合:
import json
import pandas as pd
from datetime import datetime
# Step 1: 遞迴展開巢狀 JSON
def flatten_json(nested, prefix=""):
result = {}
for key, value in nested.items():
full_key = f"{prefix}.{key}" if prefix else key
if isinstance(value, dict):
result.update(flatten_json(value, full_key))
elif isinstance(value, list):
for i, item in enumerate(value):
if isinstance(item, dict):
result.update(flatten_json(item, f"{full_key}.{i}"))
else:
result[full_key] = value
return result
# Step 2: 時間窗口聚合
def aggregate_window(df, window_minutes=10):
features = {}
numeric_cols = df.select_dtypes(include='number').columns
for col in numeric_cols:
features[f"{col}_mean"] = df[col].mean()
features[f"{col}_max"] = df[col].max()
features[f"{col}_std"] = df[col].std()
return features
- 把 JSON 文件想成一棵樹,根是最外層,葉子是純數值
- 遞迴函式就是「不停往下走,直到碰到葉子才停下來記錄」
- 時間窗口是「拿最近 10 分鐘的所有葉子值,算平均、最大、標準差」
- 這個特徵向量就代表「過去 10 分鐘這台伺服器的整體狀態」
| 故事 | 程式碼 |
|---|---|
| 欄位的值是字典,繼續往下走 | if isinstance(value, dict): flatten_json(value, ...) |
| 欄位的值是純數值,記錄下來 | result[full_key] = value |
| 取過去 10 分鐘的均值 | df[col].mean() |
| 取過去 10 分鐘的最大值 | df[col].max() |
- isinstance(value, dict)
- 判斷這個值是不是字典(還有下一層),如果是就遞迴繼續走。
- prefix
- 記錄到目前為止的路徑,例如
metrics.cpu,每往下一層就加一個.key。 - Time Window(時間窗口)
- 只取「最近 N 分鐘」的資料來聚合,讓特徵代表「近期狀態」而不是「全部歷史」。
- Aggregation(聚合)
- 把多筆資料壓縮成一個數字,常用均值、最大值、標準差、計數。
本題沒有複雜數學公式,這個 Step 跳過。時間窗口聚合的「均值」就是普通算術平均。
蓋住程式碼,說出 4 個步驟:
- 遞迴走遍 JSON 樹,把所有葉子值攤平成一行
- 每秒都產生一行,累積成一張時序表
- 用時間窗口切出最近 10 分鐘
- 對每個欄位算統計量(mean、max、std),送進模型
為什麼其他選項是錯的
A先將 JSON 資料扁平化轉成 CSV,再對欄位計算統計量(如均值、次數)作為特徵
把 JSON 壓平成一張二維表格(CSV),再對每個欄位算平均值、次數,當作特徵送進模型。
「扁平化轉 CSV」和「計算統計量」這兩個步驟本身都沒有錯,問題是這個選項沒有提到時間窗口。題目明確說要「時序特徵」,但 A 的統計量是針對整個資料集算的(例如所有時間的 CPU 均值),不是「過去 10 分鐘的趨勢」,抓不到時序規律,無法支撐故障預測。
做過資料清理、知道「JSON 要先轉 CSV」的考生,看到 A 覺得很熟悉就選了。記住:缺了「時間窗口」的聚合,算出來的統計量沒有時序意義,不是「時序特徵」。
B使用遞歸神經網路(RNN)直接輸入原始 JSON 字串進行時序特徵抽取
把 JSON 原始文字當成序列,直接餵進遞歸神經網路(Recurrent Neural Network,RNN)讓模型自己學特徵。
RNN 的輸入是數值向量,不是文字字串。原始 JSON 字串裡充滿大括號、引號、冒號等結構字元,模型看到的是字元序列,而不是感測器數值。要讓 RNN 有意義地學時序模式,必須先把 JSON 轉成數值特徵,不能直接丟字串。這個選項跳過了最關鍵的特徵工程步驟。
知道「時序資料要用 RNN」的考生,看到「時序特徵」就想到 RNN,再看到「直接輸入」覺得省事就選了。記住:RNN 解決的是模型架構問題,但資料前處理(把 JSON 轉成乾淨數值)是另一個問題,不能跳過。
D只保留時間戳記欄位,忽略其他巢狀內容以簡化特徵工程
把 JSON 裡只有「幾點幾分」這個欄位留下來,其他的感測器數據、錯誤碼全部忽略,以減少工程複雜度。
故障預測需要知道「伺服器在哪個時間點的狀態是什麼」,時間戳記只告訴模型「現在是幾點」,完全沒有狀態資訊。把 CPU 溫度、記憶體使用率、錯誤碼全部丟掉,就像只看「今天是星期幾」就要預測「這個人今天會生病嗎」,根本做不到。
被「簡化特徵工程」這個說法吸引,覺得「少做點事也算一種方法」的考生。記住:簡化本身沒有錯,但如果簡化掉了所有有意義的資訊,模型就失去預測能力,這不是工程取捨,而是根本做不出來。
同個考點下次怎麼變形
如果 JSON 欄位深度不固定(有的 3 層,有的 7 層),遞迴函式還能用嗎?
遞迴函式如果遇到深度不同的結構,會不會搞錯層數或漏掉欄位?
可以用,而且這正是遞迴函式的優勢。遞迴不需要預先知道深度,只要「遇到字典就繼續往下走,遇到純數值就記錄」,不管幾層都能處理。固定深度的手動解析反而更容易出錯,因為假設了層數。
時間窗口設得太長(例如 24 小時)會有什麼問題?
窗口越長,資訊越多,不是越好嗎?
不一定。窗口太長有兩個問題:1. 早上 8 點的 CPU 負載和下午 3 點的混在一起平均,抹掉了近期的異常信號;2. 特徵向量的計算量和儲存量都大幅增加。通常時間窗口設在「故障前的典型預警期」附近(幾分鐘到幾十分鐘),需要根據業務經驗調整。
如果不同伺服器的 JSON 欄位名稱不統一,要怎麼處理?
A 伺服器叫 cpu_usage,B 伺服器叫 cpu.load,展開後欄位名稱不一樣,怎麼合在一起訓練?
需要加一個「欄位對齊」(Schema Alignment)步驟:建立一份欄位映射表,把不同命名規則統一到標準欄位名。或者在 JSON schema 層就強制規範,讓所有伺服器輸出一致格式。這是資料工程的核心挑戰之一,稱為 Schema Normalization(模式正規化)。
醫療裝置的即時監測資料(HL7 FHIR 格式)也是巢狀結構,一樣能用這個方法嗎?
HL7 FHIR 是醫療資料標準,結構非常複雜,應該更難處理?
核心邏輯一樣:遞迴展開 + 時間窗口聚合,方法不變。差別在於醫療資料的欄位語義更重要(心率和血氧的重要性不同),需要領域知識決定哪些欄位要聚合、哪些要捨棄,以及時間窗口要設多長(急性病症可能只看 5 分鐘,慢性監測可能看幾小時)。
時序特徵萃取完之後,怎麼評估萃取出來的特徵「好不好」?
感覺上就是看模型準確率?
有幾種方式:1. 下游任務指標(故障預測的 F1-score、AUC-ROC)是最直接的;2. 特徵重要性分析(Feature Importance)看哪些萃取出的特徵被模型用到;3. 相關係數(Correlation)看特徵和故障標籤的相關程度;4. 互資訊(Mutual Information)衡量特徵和目標的非線性關聯。通常結合多種方式評估。
想再往下看,這 5 個
- 特徵工程(Feature Engineering)從原始資料(如 JSON 日誌)萃取模型可用數值特徵的過程,本題的遞迴展開加時間窗口聚合是典型的特徵工程設計。
- 時間序列分析(Time Series Analysis)分析資料隨時間變化規律的方法,時間窗口聚合是時序特徵工程的核心技巧,用於捕捉近期事件的趨勢。
- 資料管線(Data Pipeline)從 JSON 日誌進入到特徵萃取輸出的端到端自動化流程,巢狀展開和聚合邏輯需內嵌於管線中持續運行。
- 資料前處理(Data Preprocessing)JSON 扁平化和巢狀展開屬於前處理步驟,是特徵工程的前置工作,決定後續聚合計算的正確性。
- 異常偵測(Anomaly Detection)本題故障預測的目標應用場景,時序特徵(如錯誤頻率、時間窗口峰值)是異常偵測模型的主要輸入。