iT邦幫忙

2026 iThome 鐵人賽

DAY 15
0
AI Engineering

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

Day 15|Personal Baseline:你的正常,不是別人的平均

  • 分享至 

  • xImage
  •  

今天為什麼研究這個?

Day 14 做出帶有個人中心值、日間波動和已知事件的合成資料後,今天想先回答一個比「怎麼設偵測門檻」更前面的問題:**今天的數值該跟誰比?**把一個人的 HRV 放進群體參考區間,和拿它對照此人過去的日常分布,回答的是兩個不同問題。落在群體常見範圍內,仍可能和平常的自己差很多;反過來,偏離群體中心也不代表今天突然發生變化。

今天先用 Day 14 合成個案的設定值說明這個差別,再用 LifeSnaps 公開資料集的每日 Fitbit 紀錄觀察真實資料會遇到的缺值。還要做一個小實驗:只給幾天歷史,和給更多有效觀測,估出的 baseline 會差多少?我想找的是「何時有足夠依據描述這個人的平常」,而不是先替所有人指定一個天數或把偏離解讀成疾病。

Concept

1. Population reference 與 personal baseline 的參照對象不同

Population reference 描述一群人在特定量測條件下的數值分布;personal baseline 描述同一個人在可比較條件下,過去通常落在哪裡、波動多大。前者回答「相對這群人如何」,後者回答「相對自己的歷史如何」。兩者不能直接互換,也不該把任一種統計區間當成醫療上的「健康/不健康」界線。Quer 等人對每日 resting heart rate 的縱向研究觀察到明顯的個體間差異(92,457 人的每日 RHR 平均 65 bpm,範圍 40–109 bpm),個人數值則相對穩定;這支持觀察個人歷史的價值。但「穩定」有兩個但書:20% 的人至少有一週 RHR 波動達 10 bpm 以上,而且整體有季節趨勢(7 月最低、1 月最高)。研究對象也是 RHR,不能把結論中的幅度直接套到 HRV。

「群體」本身也要先講清楚是哪一群。手上的 short-term HRV 常模(Nunan 2010,經 Shaffer 與 Ginsberg 轉引,RMSSD 平均 42 ms)來自約 5 分鐘的紀錄,不能和 Fitbit 夜間的 rmssd 直接比;若改用 LifeSnaps 各參與者的分布當對照,那只是這份樣本的分布,不是常模。另一方面,Natarajan 等人用 800 萬名 Fitbit 使用者的資料,主張群體的經驗分布「可能」作為個人層級解讀的框架(該研究由 Fitbit 資助)。群體分布不是沒有用,問題是它回答的是另一個問題。

Day 14 的 two_people_same_value 情境可以用來說明這件事:設定中 p01 的 rmssd 中心是 70 ms、p02 是 35 ms,事件讓 p01 暫時下降 25 ms。以設定的日間標準差(p01 為 7 ms、p02 為 6 ms)換算,約 45 ms 對 p01 是中心以下約 3.6 個標準差,對 p02 則是中心以上約 1.7 個標準差;後者算不算「平常」,取決於範圍怎麼定義。45 ms 也只是設定的中心加上事件幅度,實際觀測值還會疊上日間波動與恢復期的漸變。這些是合成參數,不是兩人的實測結果,也不是「45 ms 代表異常」的通用門檻。

2. Baseline 是估計值,歷史長度不等於有效樣本數

個人 baseline 至少要描述一個中心與一個波動範圍;只拿一兩天的平均值當「平常」,很容易受單日波動牽動。增加有效觀測通常能減少估計的不穩定,但連續幾天的生理數值可能彼此相關,因此「多收一天」不等於得到一筆完全獨立的新證據。觀測有缺值、品質不足,或量測條件改變時,日曆上經過的天數更不能當作樣本數。Day 13 已看到 LifeSnaps 的 rmssd 並非每天都有值,這次要把有效天數和缺口一起呈現。

歷史也不是越長越好。窗口若包含事件期(例如 p01 第 45–48 天與之後的恢復期),估出的「平常」會被拉低;窗口拉長,也可能把不同季節的平常混在一起。

「需要幾天」沒有脫離指標、資料品質與穩定性定義的固定答案,所以實驗前要先定下「穩定」的判準。小實驗會逐步增加歷史觀測,檢查估計的中心與波動範圍是否還明顯變動;若資料不足,就保留「baseline 尚不可靠」的狀態。合成個案的波動大小和「今天會像昨天」的程度都是我設定的,在上面算出的天數主要反映這些設定,只能用來檢查方法;LifeSnaps 的結果才是真實資料的觀察。即使估計穩定,偏離也只表示值得檢查的變化,不能從 HRV 或 RHR 的變動單獨推斷原因。

Hands-on

Dataset

  • 合成: Day 14 的 two_people_same_value,兩人各 60 天,只看 rmssd,沒有缺值與低品質日。p01 只取事件前的第 0–44 天(45 筆),p02 沒有事件,60 筆全用。單一 seed 用情境預設的 1406;換 seed 的實驗用 1406–1605 共 200 組,其餘參數不變。
  • LifeSnaps: 每日 CSV 裡 rmssd 非空的列,兩輪共 46 個 (id, 輪) 有值。每組有效觀測中位數 47 筆(1–64),其中 6 組不到 14 筆。這份資料沒有事件標籤,所以沒有排除任何日子。

Method

做法一句話:每多 7 筆資料就重算一次平均和波動,如果連續兩次都幾乎沒變,就當作「暫時穩定」。判準在跑實驗前定好(D-062),不看結果再調:

  1. 在有效觀測 n = 14、21、28、35、42 時,用最初 n 筆估 mean 與 SD。
  2. 拿最初 14 筆的 SD 當尺,記為 s_0,之後固定不變;後面的變化都用這把尺量。
  3. 相鄰兩個檢查點的 |Δmean| < 0.5·s_0 且 |ΔSD| < 0.25·s_0,這次比較才算符合;連續兩次符合,就稱為「依本判準暫時穩定」,回報的 n 記為 n*。
  4. 門檻另取一半(strict,嚴格)與兩倍(loose,寬鬆)各跑一次,看結論會不會變;原本的門檻稱為 default。

檢查點從 14 開始,又要連續兩次,n* 最早只能是 28。因此結果有三種:不到 14 筆是「資料不足」,連尺都算不出來;14 筆以上但沒有符合的是「未達成」,其中又分成資料長度還走不到 28,以及已經檢查過卻不符合兩種原因;符合的是「暫時穩定」。為了看這個下限的影響,另跑一組對照:檢查點從 7 開始,只要符合一次。

Code

核心三個函式在本機 stability.py,串起來就是判準本身:

scale = checkpoint_estimates(arr, (14,))["sd"].iloc[0]    # s_0,之後固定
est = checkpoint_estimates(arr, (14, 21, 28, 35, 42))      # 最初 n 筆的 mean、SD(ms)
chk = adjacent_checks(est, scale, k_mean=0.5, k_sd=0.25)   # 相鄰兩點的變動是否 < k·s_0
n_star = stable_at(chk, n_consecutive=2)                   # 連續兩次符合;沒有就是 None

arr 只放有效觀測,所以 n 數的是筆數,不是日期。相鄰兩次估計共用大部分資料,差值本來就容易偏小;判準看的是「多收資料後還會不會變」,不是「估得準不準」。合成資料知道設定值,所以可以另外量第二件事。

結果與意外

先講結論,有三件事:答案幾乎都卡在 28 這個設計下限;判定穩定不等於估得準;在 default 門檻下,真實資料的未達成多半是資料長度走不到判定點,但換成 strict 門檻後,已有 42 筆仍未達成的組也不少。

原本以為 實際發現
原本以為各組達到穩定所需的筆數會分散開來,能從中看出一個典型的「需要幾天」。 default 門檻下,合成資料 200 組 seed 有 84.0%(p01)與 81.5%(p02)在 n* = 28 判定穩定;LifeSnaps 46 組中有 30 組是 28。28 正是這套判準最早可能的答案,所以只能說多數序列在最早可判定的 28 筆就符合本篇規則;實際需要多少筆,這次仍無法判斷。
原本以為連續兩次符合、判定穩定時,mean 與 SD 就已經接近真正的個人分布。 seed 1406 的 p02 在 n* = 28 判定穩定,當時 SD 是 4.38 ms,只有設定值 6 ms 的 0.73 倍。換 200 組 seed 看,判定當下的平均值多半離設定中心不到半個日間 SD,SD 則大約在設定值的 0.7 到 1.2 倍之間(p01,10–90 百分位),偏低的幅度比偏高大。
原本以為把檢查點提前,可以更早得到答案,而不會明顯犧牲估計品質。 對照組從 7 開始、只要符合一次:p01 有 53.5% 在 14 筆就判定穩定,但判定當下的估計離設定值更遠,平均值和 SD 的誤差都變大(數字見 results.md §3b)。
原本以為門檻只影響少數接近邊界的個案,整體結論應該差不多。 門檻減半後,合成 p01 在 28 判定穩定的比例從 84.0% 降到 39.0%,到 42 筆仍未達成的從 0 升到 12.5%。LifeSnaps 有效觀測達 42 筆的 30 組,default 全數判定穩定,strict 下有 9 組未達成。
原本以為真實資料標成「未達成」,主要是因為數值一直不穩。 LifeSnaps default 門檻下未達成的 5 組,有 4 組只有 14–27 筆有效觀測,資料長度根本走不到 28;另有 6 組不到 14 筆,連 s_0 都算不出來。
原本以為缺值多,28 筆有效觀測通常會拉成比 28 天長很多的時間。 default 下 n* = 28 的 30 組,多數幾乎沒差:跨 28–47 個日曆天,中位數 29。差距集中在少數組,最長的一組 28 筆跨了 47 天。

https://ithelp.ithome.com.tw/upload/images/20260928/20184206Z0NG3q02A5.png
合成 p01、p02(seed 1406)在 n = 14–42 的 mean(左)與 SD(右);點線是情境設定值,不是實測。

https://ithelp.ithome.com.tw/upload/images/20260928/201842061N8VTsHMeF.png
LifeSnaps rmssd 46 個 (id, 輪) 的 n* 分布,三組門檻;有效觀測不到 14 筆的歸為資料不足

Limitations

「穩定」是本篇的操作定義,不是已驗證的生理門檻。 檢查點、連續兩次與變動倍數都是之前的實驗約定。n* 最早只能是 28,多數結果又剛好落在 28,因此這次實驗無法分辨真正足夠的歷史是 28 筆,還是更少。合成實驗雖換了 200 個 seed,仍只涵蓋兩個人、各自固定的日間 SD 與相同的自相關設定;換成不同波動、漂移或事件型態,判定比例可能改變。

LifeSnaps 的結果只能描述這份資料與這套判準。 它沒有可用的事件真值,這裡也沒有排除異常日;較長的窗口可能把短期事件或緩慢變化一起算進「平常」。rmssd 缺值使有效筆數與經過天數不同,且缺值是否與當時狀態有關無法確認。46 個有值的 (id, 輪) 之中,每組最多只有 64 筆有效觀測,無法從這次分析推出更長期、跨季節的穩定性。本文「同一數值對兩人意義不同」目前是依合成設定值換算的示例,沒有用實際觀測輸出驗證分類表現,因此不能宣稱已找到可泛用的個人偏離門檻。

這對 AI Engineering 的意義

同一個 rmssd,要放回這個人的歷史才能解讀,所以 personal baseline 是 long-term context 的核心。但今天的結果顯示,baseline 本身也需要帶著說明一起傳:它是「依某套判準、用了幾筆有效觀測、跨了幾個日曆天」的估計。LifeSnaps 有 6 組連尺度都算不出來、4 組資料長度走不到判定點,這些情況都不能送出一個看起來正常的中心值。

「穩定」也只是規則下的標籤。p02 被判定穩定時,SD 只有設定值的 0.73 倍;如果下游拿這個 SD 畫「平常範圍」,範圍會偏窄,偏離就容易被放大。LLM 看不到這些。假設它拿到 baseline_rmssd: 36,會自然寫出「根據你過去一個月的平均」,即使那可能只是 28 筆、跨了 47 天的資料。

所以 Day 19 的 feature contract 裡,baseline 至少要帶狀態(insufficient、not_yet、stable)、有效觀測數、日曆天數與判準版本。模型能說多肯定,要由這些欄位決定,而不是由數值本身決定。


上一篇
Day 14|合成一個人:長期資料產生器與已知事件注入
下一篇
Day 16|實作 Personal Baseline:把「平常」寫成程式
系列文
30 天拆解 Wearable × AI:從穿戴裝置生理訊號到AI健康洞察 共 18 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言