iT邦幫忙

2026 iThome 鐵人賽

DAY 20
0
AI Engineering

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

Day 20|為什麼 CSV 不等於 LLM Input?誰負責算,誰負責說

  • 分享至 

  • xImage
  •  

今天為什麼研究這個?

Day 1 把一週 CSV 直接交給 LLM,模型把早上匯出時的 1,850 步解讀成身體在主動「煞車」,也把前三天的 RHR 稱為「常態基準」。數字能對回資料,解讀所需的條件卻沒有一起交出去:今天過完了嗎?幾天才夠建立 baseline?缺值那天發生了什麼?這些問題留在模型面前,就可能被一段流暢的建議掩蓋。

前面已經做完時間對齊、缺值處理、personal baseline 與偏離判定,Day 19 又把結果裝進 feature contract。但通過 Pydantic 驗證,只代表資料符合目前的契約,還沒回答模型會不會照著讀。今天要回到同一份 Day 1 合成 CSV,把時間範圍、baseline、偏離、品質與限制整理成 structured health summary,再比較模型的敘述。我要驗證的是:當計算與判定先由程式完成,LLM 是否能保留證據的邊界,而不自行補出一套健康解釋。

Concept

1. CSV 有數字,解讀還需要脈絡

CSV 可以當模型輸入;問題在於表格裡有哪些資訊。Day 1 的 hrv_ms 沒有寫出它是夜間 RMSSD 平均,steps 也沒有附上 09:30 的匯出時間。這些定義記在 data/synthetic/day01_week_daily.md,是合成資料的設定,不能說成模型或程式從數字推得的事實。同樣地,09-11 的 HRV 空白可以從 CSV 看見,「訊號品質不足」這個原因則必須另附來源。這個原因是設計者寫在情境設定裡的真值,不是裝置輸出的觀測;依Day 19結論,真值標籤不能當成觀測端證據。若要交給模型,等於假設裝置會回報品質原因,而 contract 目前沒有日層級的品質原因欄位;不這樣假設,就維持 missing_kind 的 unknown。

所以先要區分三種內容:原始觀測、依明確規則算出的結果,以及來源提供的背景。空白不等於零,部分日的步數不能直接當成完整日總量;沒有缺值原因時,就保留未知。「部分」也要以指標為單位判斷:09-14 的 HRV、RHR 與睡眠歸醒來當天,09:30 匯出時前一晚已經結束;只有從 00:00 累計的 steps 還沒算完。一個日層級的「今天未過完」旗標,會把已完整的夜間指標一起標成不完整。把 CSV 換成 JSON 不會自動補齊這些資訊,真正要設計的是哪些內容進入 context,以及它們代表什麼。

2. 程式負責算與判定,LLM 負責說明

本系列把平均、baseline、deviation score 與標旗交給 deterministic code,讓每個結果都能對回輸入、方法、門檻與規則版本。LLM 的任務是說明已提供的觀測與判定,包括為什麼目前不能下結論;不讓它另外選幾天當 baseline,或把偏離分數改寫成未定義的「恢復分數」。這個約束只對拿到判定結果的輸入成立:只給 CSV 時沒有 baseline 可引用,要不要比、跟哪幾天比,都由模型自己決定,Day 1 的 v0 就是拿前三天當「常態基準」。

Day 1 資料正好能檢查這個分工:CSV 只有 7 列,HRV 有效值只有 6 筆。baseline 視窗不含解讀日本身,以 09-14 為解讀日時只看得到之前的 5 筆;既有規則要累積 14 筆才判定得出狀態(scale_n),視窗內也要 14 筆才算中心與尺度(min_obs)。依這套規則推論,轉接後應呈現資料不足(insufficient),不能為了給模型一個 score 而降低門檻。這是依規則預期的狀態,仍待實際接線驗證。摘要也要同時帶入可判定性與原因,避免模型把「沒有標旗」讀成「已確認正常」。

計算可重現,也不等於健康解釋已被驗證。即使程式判定 HRV 與 RHR 同時偏離,摘要能支持的仍是規則下的共同變化;原因與身體狀態不能由標旗直接推出。

3. Contract 到 summary,是一次有責任的取捨

Day 19 的 contract 是共用介面,LLM context 則要服務這次的敘述任務。摘要至少需要保留指標定義與單位、涵蓋日期、當日值與視窗統計、有效天數、baseline 狀態、偏離結果,以及限制。數值與限制必須一起選:只留平均、刪掉有效天數,就失去判斷資料是否足夠的依據;只留 score、刪掉方法與門檻,也無法理解判定。

例如依Day 19的完整視窗規則,Day 1 的 HRV 七日平均應不輸出,但仍保留有效天數 6;這個視窗含解讀日當天(Day 12),所以是 6 而不是 baseline 的 5。這和「挑出六筆算平均」是不同的統計口徑,不能交給模型自行決定。JSON 中的 null 也需要欄位語意:沒有可用平均、無法判定偏離、誤報率尚未量過,各自是不同的未知。

目前 contract 只涵蓋日層級,不能憑空補上 PPG 窗層級品質資訊;匯出時間等來源背景也需要另外承接。內部識別碼與 debug 資訊可留在實驗紀錄,合成情境的答案標籤則不該作為模型推論的線索。今天先釐清取捨原則,實際摘要欄位與轉接方式仍要在實作時定案。

4. 比較輸入,也要固定比較條件

Day 1 的 v0 只有一次 Gemini App 回應,App 的 system prompt 與參數不可見。若今天改用 API,舊回應適合作歷史對照,不能把差異全部歸因於 structured summary。較能辨別輸入作用的做法,是在同一 API 模型與相同設定下,用同一份資料重新比較。

分成 raw、raw+context、summary:原始 CSV、CSV 加來源背景、程式摘要加相同背景。前兩組比較補充背景的作用,後兩組再觀察預先計算與摘要選擇的作用;這仍不能單獨證明 JSON 格式比較好。比較時保存 prompt、參數與完整回應,並用重複呼叫觀察輸出是否變動。

三組都沿用 Day 1 那句「請根據資料給我健康建議」。任務指令仍要求建議,模型沒有被告知只能敘述,所以今天測的是「改變輸入能不能約束輸出」,只是第 2 節分工的一半;用 prompt 限制模型能做的推論,是 Day 21 的題目。

驗收沿用 Day 1 的七項清單:缺值、匯出時間、指標定義、比較基準、因果語氣、資料量,以及語氣與醫療邊界。每項判讀都要對回回應原句與資料依據;數字則分清原始值、可驗算的衍生值與外加建議,不能把 CSV 找不到的數字一律算成捏造。這些是接下來要測的問題,結構化輸入是否改善回答,要等實驗結果回答。

Hands-on

Dataset

Day 1 的 day01_week_daily.csv(7 列,sha256 與 v0 相同),以及它的設定檔 day01_week_daily.md。CSV 換成 contract 的指標名與單位:hrv_ms → rmssd(毫秒)、resting_hr_bpm → resting_hr(bpm)、sleep_duration_h × 60 → minutesAsleep(分鐘);steps 不在 contract 裡,放進來源背景。

Method

  1. 轉接。 Day 1 CSV 是手工編寫、沒有 seed,Day 19 的 v2 contract 不接受,所以升為 v3,新增 fixture 來源。baseline、偏離與跨指標判定沿用 Day 18 主結果的設定(z_mean_sd、門檻 3.0、兩側、A 組),不為七天資料放寬;誤報率標為未量。
  2. 摘要。 summary-v1 以 09-14 為解讀日、涵蓋 09-08~09-14,帶指標定義、逐日值、3/7 日平均與有效天數、baseline 狀態與最低要求、偏離判定與原因。來源背景另成一塊:欄位定義、匯出時間 09:30、09-14 只有 steps 未完整、09-11 缺 HRV 的原因(標明是「資料設計說明」)。
  3. 比較。 三組輸入都以 Day 1 那句開頭:raw(CSV)、raw+context(CSV+來源背景)、summary(摘要+同一份來源背景)。模型 gemini-3.8-flash,temperature 1.0,不設 system instruction、不開工具。各跑 3 次。第一版發布時受免費方案每日請求的上限,三組各只成功 1 次;10-04 補跑完成,三組與補充案例各 3 次,部分回應改用另一把 API key,模型與設定不變。
  4. 評估。 沿用 Day 1 的七項清單,數字依出處分成觀測、規則參數、可驗算轉換、外加建議四類,句意和輸入不符的另列;v0 用同一口徑重算。

Code

uv run python days/day20/compare_demo.py --section 1                  # 產生 payload、摘要與四份輸入(含補充案例),不呼叫 API
uv run python days/day20/compare_demo.py --section 2 --out days/day20/runs_new   # 新一輪(目錄可換成任何新目錄):三組+補充案例各 3 次,共 12 次邏輯呼叫,重試另計
uv run python days/day20/compare_demo.py --section 2 --retry-failed  # 只補跑 days/day20/runs/ 裡失敗的呼叫

days/day20/runs/ 已有紀錄時,不加參數的 --section 2 會停下,不覆蓋既有回應。補充案例是一個有 score 的合成日,不參與三組比較。

三組只差在要求之後接什麼(days/day20/compare_demo.py,節錄):

inputs = {
    "raw":         f"{REQUEST}\n\n{csv_text}",
    "raw+context": f"{REQUEST}\n\n{csv_text.rstrip()}\n\n資料說明:\n{ctx_json}\n",
    "summary":     f"{REQUEST}\n\n資料摘要:\n{sum_json}\n\n資料說明:\n{ctx_json}\n",
}

結果與意外

程式端的輸出和 Concept 的推論一致:09-14 三個指標的 baseline 都是 insufficient(HRV 只看得到前 5 筆),偏離無法判定、score 為 null,跨指標是 undecidable;HRV 的 7 日平均因 09-11 缺值不輸出(6/7)。

原本以為 實際發現
我原本以為,摘要明確標出 baseline 是 insufficient、偏離無法判定後,模型就會停在描述這週的變化,不再把週初幾天當成應該恢復到的正常水準。 summary 3 份都提到 baseline 不足(「資料天數(僅 7 天)還不足以建立長期的個人專屬基準線」「尚未建立 14 天以上的個人長期基準線,但短期內的相對變化趨勢相當明確」),3 份仍以週初為對照,其中 2 份給了要回到的目標(「回到週初的良好水準」「往 60 bpm 以下走」)。raw 與 raw+context 的 6 份都沒有提資料夠不夠。
我原本以為,把 09-11 缺值的原因與依據一起寫進背景,模型就會保留「依資料設計說明」這個前提,不會把它轉述成裝置實際偵測到的品質問題。 raw 3 份都沒提 09-11,直接跳過那天。raw+context 2 份寫成裝置端原因(「感測器訊號問題」「因訊號未抓到而缺失」),1 份把 09-11 併進「9/8 - 9/11……維持在 52–58 ms」。summary 3 份都寫成裝置或系統端原因(「裝置提示訊號品質不足」「系統記錄……因訊號品質問題缺失」「通常是睡姿壓迫或手錶過鬆」),2 份建議調整錶帶。輸入標明的依據是「資料設計說明,不是裝置輸出的品質旗標」。
我原本以為,只要補上 09:30 匯出、步數尚未累計完,模型就會明確說明 1,850 步不能和完整日直接比較,Day 1 把低步數解讀成疲憊的問題就能修正。 raw 3 份都和 v0 一樣讀成疲勞(「極度疲憊」「精疲力竭,進入強制休眠模式」)。raw+context 3 份都提到匯出時間:1 份寫「屬正常半天數據」,2 份說明步數不完整、夜間指標不受影響。summary 只有 1 份提到,說法正確;另 2 份沒有出現 1,850。
我原本以為,程式先整理好數值、單位與統計後,模型會更集中引用這些結果,少加一些輸入沒有的建議門檻,也能減少轉述數字時的落差。 數字依出處分類:外加建議中的數值,raw 9~14 項、raw+context 8~9、summary 5~9,v0 是 6;總項數分別是 24~31、18~21、18~34。summary 3 份都把規則參數讀成天數:「通常需要至少 14~21 天」(2 份)、「尚未建立 14 天以上」「累積滿 2~3 週」,把有效觀測筆數門檻 14 和視窗日數 21 當成需要的天數。
我原本以為,日期已經寫得明確,模型即使為了敘事補上星期,也能正確對應;需要由程式先算好的主要是 baseline 與偏離分數。 三組輸入都只有日期,沒有星期。9 份中 4 份有星期錯置:summary r1 把 9/12、9/13 寫成週五、週六,後段又把同一天叫「週日」;summary r2 把 09-08–09-11 寫成「週一至週五」;raw r3 把 9/12–9/14 寫成週五至週日;raw+context r2 把 9/13 寫成週六。raw r1、r2 的星期是對的。

三組的語氣有一個共同點:9 份回應都對身體狀態下了判斷(「典型的……疲勞累積」「明顯的急性疲勞期」),也都用因果語氣串起睡眠、活動與 HRV;沒有一個問號。「醫」「診」只出現在 raw+context r3 結尾的免責聲明。

Limitations

三組各三份回應,只能看出行為是否重複出現,不足以估計出現比例或判定哪種輸入穩定較好;其中回應來自三把不同的 API key、兩天的不同時段,模型 ID 與設定相同,但服務端狀態無法確認。三組雖固定 API 模型與參數,卻同時改變資訊內容、呈現方式與長度,輸入 token 分別為 220、1,046、3,882,不能把差異單獨歸因於 JSON 格式或預先計算。三組也都仍被要求「給我健康建議」,沒有加上只能依據摘要敘述的輸出約束;今天測到的是這個任務下輸入的作用。Day 1 的 App 回應因 system prompt 與參數不可見,只能當歷史對照。七項清單與數字分類依逐句判讀整理,數值項數又會隨回應長度與重複引用增加,不能直接當成整體正確率。

資料只有同一份手工編寫的七日合成 CSV,缺值原因與匯出時間來自情境設定,沒有真實裝置的品質紀錄或健康結果可驗證。三個指標的 baseline 都不足,score 都是 null,補充案例的 3 份都正確轉述了 rmssd 的標旗與分數,但也都列出感染、飲酒等成因;這只是一個合成日,不足以驗證模型能否穩定敘述有效的偏離判定。這些結果不能外推到其他個人、較長時間序列或其他模型,也不能用來驗證回應中的疲勞成因、恢復期限與健康建議。

這對 AI Engineering 的意義

在這 9 份回應裡,輸入改變了模型提到什麼,沒改變回答的形狀。 加了背景與摘要後,缺值、匯出時間、資料不足開始出現在回應裡;但三組都照「請給我健康建議」寫出一套建議,外加建議中的數值仍有 5~14 項。補 context 不足以守住推論邊界;prompt 與輸出約束能不能做到,留給 Day 21、22 測。

欄位進了 context,不代表語意也跟著進去。 資料不足被轉述出來了,比較仍照做;「資料設計說明」被改寫成「裝置提示」。只檢查回應有沒有提到某個欄位不夠,評估要檢查回應的句子有沒有違反欄位的意思,例如 baseline 不足時還寫「回到……水準」。這是 Day 26 確定性檢查要做的事。

能算的就先算好。 星期是由日期決定的值,摘要沒給,模型就自己推,而且推錯。誰算誰說的邊界不只在 baseline 與 score,任何能 deterministic 算出的欄位,只要會被敘述用到,就該由程式給。

送出層的失敗要和模型行為分開記。 最後一輪 44 次請求只成功 3 次,其餘是模型負載(503)與配額(429);重試政策與配額決定了樣本數,也是實驗條件的一部分。

更新(2026-10-04):補跑完成,三組與補充案例各 3 份回應。Method 第 3 點、結果表「實際發現」欄、語氣段、AI 意義第 1 段與 Limitations 改為 3 次的彙整;第一版的 3 份回應原文與分類不變。


上一篇
Day 19|Feature Contract:把時間序列交給 AI 前,先約定語言
下一篇
Day 21|Prompt v1:要求 AI 只根據提供的證據說話
系列文
30 天拆解 Wearable × AI:從穿戴裝置生理訊號到AI健康洞察 共 25 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言