iT邦幫忙

2026 iThome 鐵人賽

DAY 14
0
AI Engineering

30 天拆解 Wearable × AI:從穿戴裝置生理訊號到AI健康洞察系列 第 14 篇

Day 14|合成一個人:長期資料產生器與已知事件注入

  • 分享至 

  • xImage
  •  

今天為什麼研究這個?

Day 13 在 LifeSnaps 標出了缺值,卻無法從靜態匯出檔知道某一天是沒戴、量測失敗,還是資料晚了兩天才同步;也沒有「這幾天發生了什麼」的事件標籤。接下來要做個人 baseline 與偏離偵測,如果連答案都不知道,就只能展示曲線,無法檢查偵測器究竟抓對了什麼。

今天先自己設定一個人的日常波動、緩慢漂移與短期事件,再產生量測噪音、缺值和抵達時間。產生器同時交出下游可見的觀測資料與分開保存的真值標籤,讓後續可以問具體的問題:同一個事件有多容易被日間變異淹沒?在資料尚未到齊的當下,系統實際看見了什麼?這些數字都是情境設定與程式輸出,不代表真實裝置或生理反應。

Concept

1. 合成資料的價值是「答案已知」,不是「像真的」

產生器每次輸出兩份東西:觀測表(下游看得到的)與標籤表(產生時記下的答案)。兩份表鍵相同但分開存,避免評測時把標籤當特徵用。

合成資料驗證的是管線有沒有做到它宣稱的事,不是生理:「睡不夠隔天靜止心率上升多少」只是一個設定。

2. 分三層產生:真值、觀測、抵達

  • 真值層: 如果完整量到,指標應該是多少。由個人中心值、緩慢漂移、日間變異與事件效果疊加。
  • 觀測層: 加量測噪音,決定哪些值沒量到(沒戴、量測失敗)或被填成預設值(Day 13 的 fill_value)。
  • 抵達層: 量到的值什麼時候同步進來,或始終沒到。

事件標籤記起訖日、影響的指標、恢復期與注入的幅度,並能攤成逐日表給 Day 17 用。標籤只有 0/1 的話,評測分不出「偵測器太弱」與「事件本來就看不出來」。漂移和事件也要分開標,否則慢慢上升的靜止心率會被當成一個很長的事件。事件期可以提高沒戴機率(Day 13 的 MNAR),這個設定要能關掉。

3. 兩條時間軸:量測日期與抵達時間

除了量測的當地日期,資料還有第二條時間軸:它什麼時候到。在時間 T 只看得到抵達時間 ≤ T 的列,所以同一個缺口在不同 T 看答案不同,missing_kind 要跟著 T 一起記。產生器要能區分三種看起來一樣的空白:

真實情況 當下(T 時)的觀測 事後的觀測
沒戴或沒量到 沒有列或值空 仍然空
量到但延遲抵達 沒有列 有值
量到但始終沒到 沒有列 仍然空

只看觀測表,第一與第三列永遠分不開(所以 D-054 不在 LifeSnaps 上推論同步失敗);合成資料有標籤表,sync_failure 才第一次有真值可對。

4. 可重現與不自我欺騙

同一個設定檔加同一個 seed,要產生逐位元相同的觀測表與標籤表;標準情境凍結成 fixture,Day 15–28 共用。另外要留一組調參時沒看過的 seed:同一批情境上調門檻又報 precision,數字只會好看。

Hands-on

Dataset

全部是自製合成資料,沒有真實資料。七個標準情境各是一個 YAML(data/synthetic/day14/scenarios/),每檔有自己的 seed:

情境 人 × 天 考的是什麼
baseline_only 1 × 60 沒有事件,應該什麼都不報
two_people_same_value 2 × 60 同一個 rmssd 值,對 p01 偏低、對 p02 平常
drift_vs_event 1 × 60 RHR 每天漂移 +0.05 bpm,加一個 3 天事件
sleep_debt 1 × 60 連續 4 天睡眠少 90 分鐘
suspected_illness 1 × 60 5 天 RHR +5、rmssd −12、步數 −4000,事件期更常沒戴
mostly_missing 1 × 30 一半的日子沒戴,預期答案是「不下結論」
sync_delay 1 × 60 延遲、始終沒到、步數部分同步

Method

四個核心函式依 D-060 由 agent 代寫,主要的實作選擇:

  1. 真值: 日間變異是 AR(1)(今天和昨天相近,才不會低估連續偏離的難度),穩態標準差等於 day_sd;事件期加 delta,恢復期線性回到 0。
  2. 抵達: 每筆紀錄有最早可得時間 available_at,夜間指標是 D 的 07:00,步數是 D+1 00:00。延遲從這裡起算,所以步數隔天才完整不算延遲。
  3. as-of: observed_as_of 只取 arrival_time <= T 的紀錄,同格取最新 revision,欄名和 LifeSnaps 日表相同,Day 12、13 的函式可以直接接。

Code

每一層、每一個人各用一條隨機串流,由同一個 seed 衍生,這樣兩次實驗才是「只差一個變因」:

def stream(seed: int, layer: str, person_index: int) -> np.random.Generator:
    ss = np.random.SeedSequence([seed, LAYERS.index(layer), person_index])
    return np.random.Generator(np.random.PCG64(ss))

改缺值比例只會改 observe 那條串流抽到的數,真值不動;在情境尾端加一個人,前面的人也不變(test_truth_adding_person_keeps_first)。逐位元可重現只承諾在 uv.lock 鎖定的環境:Generator 的分布方法跨 numpy 版本不保證相同。

結果與意外

原本以為 實際發現
以為 D 當天早上能看到大部分當天資料,延遲只影響少數格。 D 當地 09:00 查 sync_delay 的 D,只看到最終 220 格的 57.3%;D+1 79.5%、D+2 89.1%、D+7 才 100%。當天步數最早 D+1 00:00 才完整,09:00 看不到。
以為固定每天 09:00 查詢,就足以觀察部分同步的值如何被完整值取代。 09:00 查詢時,部分同步的 4 筆步數「值被改寫」是 0:部分值在 D 晚上 18–20 時才到,完整值在 D+1 凌晨到,兩次都錯過。改在 21:00 查,D+1 起 4 格的值被改寫。
以為事件只要有明確的注入幅度,就能從日常波動中辨認出來。 注入幅度 ÷ 日間 SD 從 0.75 到 3.57。sleep_debt 的 rmssd −6 ms(0.75 SD),事件外 54 天有 11 天光靠日間變異就偏到同樣程度。
以為 RHR 高出 baseline 約 4 bpm,就能對上設定的事件。 drift_vs_event 的 RHR 事件是 +4 bpm,事件期三天的真值卻比 baseline 高 6.9–9.2 bpm;沒有標籤的 2026-02-25 也高 6.1 bpm。
以為把事件期間的沒戴機率設成三倍,短短幾天也能看到接近三倍的比例。 suspected_illness 的事件與恢復期 8 天中 2 天沒戴,其餘 52 天中 3 天,方向對但天數少。沒戴的其中一天是事件最後一天,步數真值 312 步是整段最低。
以為分成真值、觀測與抵達三層後,固定 seed 還不一定能讓整套資料一致重現。 七個情境 validate_dataset 全部通過;同一 seed 重產,觀測表與標籤表逐位元相同。

https://ithelp.ithome.com.tw/upload/images/20260927/20184206SK6YyiBjqE.png
藍線是真值,灰虛線是 baseline,綠點是最終觀測值;深色底是事件期,淺色底是恢復期,底部刻線是沒戴的日子

https://ithelp.ithome.com.tw/upload/images/20260927/20184206PnAXt69TX9.png
RHR 的 baseline 60 天漂移約 +3 bpm,2026-02-14–16 的事件另加 +4;02-24–25 的上升沒有標籤,來自日間變異

Limitations

  • 這是一組情境,不是機率估計。 主要結果來自固定 seed 的七個小情境;suspected_illness 的事件與恢復期合計只有 8 天。這段時間的 2/8 天沒戴不能用來估計真實沒戴率,也不足以驗證「機率乘 3」是否穩定;要評估偵測器,還需要多個未參與調參的 seed 與事件幅度。
  • 真值由模型假設決定。 AR(1) 日間變異、固定事件幅度、線性恢復,以及各指標變異互相獨立,都是產生器的選擇。它沒有模擬 RHR 與 rmssd 的相關,也沒有星期效應。未來偵測器若沿用相同假設,即使在這裡得高分,也可能只是在解出自己設的題目。
  • 抵達時間不是裝置實測。 夜間指標 07:00 可得、步數隔日 00:00 才完整、延遲分布與部分步數先同步 60%,都由設定檔指定。D 當天可見 57.3% 只描述這個情境和 09:00 的查詢時刻,不能外推成任何穿戴裝置的同步表現。
  • 驗證器只檢查資料契約。 七個情境通過檢查且可重現,表示標籤、鍵與抵達紀錄符合目前程式的規則,不表示模擬具有生理效度。四個核心函式由 agent 代寫(D-060);目前的測試也不能取代獨立資料與人工審查。

這對 AI Engineering 的意義

合成資料給評測已知答案,但只有「有沒有事件」不夠。

第一,標籤要帶幅度。Day 17 的 recall 要按 |delta|/day_sd 分層報,否則抓不到 0.75 SD 事件的偵測器,和真的壞掉的偵測器分數一樣。

第二,答案跟查詢時間綁在一起。Day 21、26 的評測要把查詢時間 T 寫進情境,系統每次輸出要記下它用的 as-of 快照。事後拿補齊的資料重評當時的建議,量到的不是那次輸出的 faithfulness。

第三,「不知道」也要有標準答案。mostly_missing 的預期答案是不下結論;sync_delay 始終沒到的格和沒戴一樣是空的,只有標籤表分得開。評測要能判定系統在該說不知道時說了不知道。


上一篇
Day 13|Missingness 與 Wear Detection:沒有資料代表什麼?
下一篇
Day 15|Personal Baseline:你的正常,不是別人的平均
系列文
30 天拆解 Wearable × AI:從穿戴裝置生理訊號到AI健康洞察 共 18 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言