
我的雷達跌倒偵測器樸素基準線同樣達到 1.0 召回率,卻在單夜觸發了七次誤報
在我為 Vigil 構建的合成夜班場景中,現成的市售基準線在 02:00 到 06:00 之間觸發了九次警報,其中有七次是錯誤的。一具吊扇,被捕捉到 5.0 m/s 的峰值速度。一隻治療犬,雷達截面積為 0.27。一位住戶以 2.92 m/s 的速度重重坐到椅子上。那九次警報中只有兩次是真實跌倒,其中一次發生在浴室:質心軌跡從 1.53 m 的站立狀態向下穿過 1.07、0.84、0.625 與 0.344,最後停留在 0.119 m(地面高度),呼吸存在,未有復原動作。
該班次中的每個事件都是合成、帶有標籤且符合物理真實性的,均由固定種子生成,而且這兩個偵測器都是我寫的。這就是為什麼我能坦白說出令人不安的事實:基準線也抓到了那次浴室跌倒。
Vigil 是我打造的智慧層,介於長者照護中的雷達特徵串流與護理呼叫系統之間。它會回傳 ALERT、SUPPRESS 或 ROUTE TO HUMAN,且每項決策都附帶機構可以歸檔的理由。示範路徑為 https://veriprajna.com/demos/smart-facility-fall-detection。我著手構建時,原以為最困難的部分在於看見跌倒。基準測試在第一次運行時就推翻了我的想法。
召回率曾是我最想放在首位的數字
我運行基準測試時,原預期跌倒敏感度會是頭條新聞,而這確實是個不錯的數字:級聯在 360 個帶標籤的雜訊合成事件固定集合上達到 1.0 的召回率。但旁邊那一欄改變了我原本以為自己要寫的內容。樸素基準線只是兩項算術子句,任何快速或低處的動作都被視為跌倒(peak_v > 2.0 OR min_cz < 0.45),而且在相同的集合上它同樣獲得 1.0 的召回率。敏感度是跌倒偵測行銷話術的立足點,而這兩個偵測器都穩居頂端。
兩者的差距完全體現在沒人會放進簡報投影片的那一行。干擾項特異度在級聯為 1.0,而在基準線僅為 0.167,相當於每個良性事件有 0.833 的誤報率。若按照基準測試所假設的每間房每天 30 次良性動作觸發進行推估,基準線相當於每間房每天產生 25.0 次誤報。現有市售感測器的公開範圍是每間房每天 5 到 15 次誤報,而文獻記載警報疲勞而非感測器敏感度,才是這類部署失敗的首要原因。
在技術讀者替我指出來之前,我應該先主動說明。位於 data/fall_model.json 的融合權重是由 tools/fit_fall_classifier.py 在示範本身的場景生成器上擬合的,也就是產生那 360 個事件集合的同一批生成器。這是任何人對我的兩個 1.0 所能提出的最強烈質疑,而這也是為什麼比起這兩個數值,我更在意那個 0.167。基準線的失敗並非我訓練設置的產物。這就是當世界上存在吊扇時,單純依賴門檻值的必然結果。
那七次誤報決定了當真實跌倒發生時,是否還有人在聽警報。
干擾項的構建就是為了擊破單一特徵
我的第一本能是改進分類器,但這是錯誤的本能。在構建初期,我把這當成一個判別問題:找出能區分跌倒與非跌倒的特徵,賦予重權,然後收工。但我早就寫好的場景生成器卻刻意讓這種做法行不通。
每個干擾項的生成都是為了 在某個單一特徵上與真實跌倒重疊。Cam 5 的重坐帶有 2.92 m/s 的速度突發,達到跌倒的量級,並停留在 0.46 m。其在 Cam 10 的浴室近親峰值達到 3.31 m/s,並停留在 0.44 m。治療犬與彎腰撿毛巾都會使質心下降,這正好符合基準線規則的另一半。我能寫出的任何單一測試在設計上都會被擊破,這就是為什麼基準線在 0.167 的表現是真正被愚弄,而不是被我故意設來認輸的稻草人所欺騙。
速度曾是我最確信的特徵,但它也是唯一沒有留存進分類器的特徵。存留下來的是基於四個特徵的邏輯斯諦模型:地面接近度、衝擊能量、下落幅度以及雷達截面積代理指標,融合為經過校準的 P(fall)。完全沒有任何速度項進入 P(fall)。模組 docstring 中仍留有一行過時的說明,記錄著我當初以為它會進入的痕跡。它是純 numpy,程式碼精簡到任何人都能打開 classifier.py 並把整個邏輯掌握在腦海中,這在攸關人命安全的路徑上,對我而言遠比再提高一點 AUC 更有價值。
我首先向人們展示 Cam 10 的壓制決策,因為該面板用一行字說清了整場分歧。

四個條件,一個 8 秒窗口
我將時間敘事驗證器寫成自己作為局外人會想閱讀的模樣。temporal.py 要求在 同一個 8 秒窗口內滿足四個條件,且在前五分之一時間內確認站立狀態:質心中位數高於 1.2 m、下落幅度大於 0.6 m 且窗口內某處峰值速度高於 1.8 m/s、持續性寬頻衝擊的 3 幀滑動平均值超過 0.50,以及質心實際降至 0.30 m 以下。持續衝擊測試之所以存在,是因為單幀突波很容易出現,而身體撞擊地面則完全不是那麼一回事。
應用程式發出的原因字串為「standing → descent → impact → floor」(站立 → 下落 → 衝擊 → 地面),這正是護理人員解讀事件的方式,但實作上是在整個窗口內對這些條件進行 AND 運算,而非強制要求順序。它不是狀態機,我寧願親自說明這點,也不希望工程師在原始碼中發現它後,懷疑行銷文案還簡化了哪些細節。
只有在此時,閘門才會加入高於 0.20 的呼吸確認,以及至少 0.70 的跌倒信心度。這三個數值——地面高度 0.30 m、呼吸 0.20 與 0.70 信心度下限——都以純程式碼形式存在於所有模型之外。方向才是關鍵所在:在諮詢模型分數之前,確定性條件必須先行成立,因此信心度數字永遠無法單憑自身捏造警報。較低的 P(fall) 仍可將 ALERT 轉為 SUPPRESS,對於一個被允許保持沉默、但不被允許憑空捏造的層級而言,這是正確的非對稱性。在那段有文件記錄的區塊之外,還存在一個決定性門檻,即寫死在 p_fall >= 0.40 於 gate.py 中的程式碼,它能單憑模型分數將多人同室事件轉送人工檢查。我特別提及它,是因為否則「三個有記錄的門檻」這說法就有過度粉飾之嫌。
壓制記錄才是州政府稽查真正要求的項目
在構建任何看似成品的介面之前,我就先打造了決策帳本(Decision Ledger),因為我無法回答的問題從來不是「你有沒有偵測到」。而是「為什麼 2:13 在 203 號房沒有發出警報」,而在任何人開口詢問時,答案必須已經留存於書面記錄中。該班次 12 個事件中有 10 個是壓制決策,且每個都帶有記錄在案的特徵數值:Cam 6 的非人類目標雷達截面積為 0.27(相對於 0.55 的人體最低值),Cam 7 與 Cam 12 的彎腰動作質心停在 0.60 m 與 0.59 m,衝擊能量為 0.07(相對於 0.50 的門檻)。

那十個被壓制的資料列正是稽查人員會追問的重點,因為在這些事件中什麼事都沒發生,但依然有人必須解釋原因。
每間房的獨立校準只有一部分真正連接到了決策邏輯中。Cam 1 的吊扇之所以被壓制,是因為 214 號房的雜波圖在 (1.5, 1.5, 2.45 m) 處帶有一筆固定位置都卜勒項目,而 check_clutter 在該體素將其遮蔽。這條路徑是真實的。但位於 rooms.json 中每間房的座椅與床鋪高度(118 號房浴室為 0.42 m,203 號房為 0.45 m)以及旁邊的安全扶手項目,則是 no V1 code path reads校準資料;我用來標記重坐的座椅高度範圍是單一的全域 0.38 至 0.60 m 測試。該檔案中甚至包含一個 long_lie_sec: 180.0 鍵值,完全沒有任何程式碼使用它。每間房的校準是真實部署必須付費進行的整合工程,而在這個版本中,只有都卜勒遮罩真正連上了決策邏輯。
匯出內容為班次稽核 JSON,涵蓋每起警報、轉送與壓制決策,並附帶決定性特徵數值與政策原因,這正是 CMS F689 或 QAPI 評鑑檔案所需要的資料。臨床事件記錄是根據該結構化證據另外撰寫,並顯示於事件面板中,而非包含在 JSON 內部。
我允許它發出的警報,以及我堅決不讓它斷言的跌倒
我刻意將整個班次的高潮安排在浴室。那是風險最高的房間,也是攝影機無法派上用場的唯一場所:美國有 19 個州頒布了規範護理之家房間內攝影機的法律,通常允許在取得同意的情況下在住戶房間安裝,但基於隱私考量,浴室在實務上始終被排除在外。雷達特徵不帶影像,這正是它能進入攝影機所無法觸及之處的原因。
Cam 3 就是該起事件,而 Vigil 回傳 ALERT,類別為 long_lie,信心度 0.99,倒地時間 4.8 秒,升級梯隊設定為立即通報 CNA、90 秒通報責任護士、180 秒通報護理總監(DON)。呼叫鈴識別牌文字顯示為「118B 房浴室:偵測到跌倒,99% 信心度。住民倒地 5 秒。呼吸已確認。」調度系統同時發出傳統 Rauland 乾接點訊號與 Ascom/Austco MQTT/REST 酬載,兩者均經由留存記錄的配接器樁發送。這一切並未連接任何護理呼叫硬體。之所以仍值得構建,是因為長時間倒地不起(long lie):倒在地上超過一小時的長者中,有半數會在六個月內離世。

該面板上有一個數字我拒絕拿來推銷。衝擊到發出警報的 7.0 秒是在 gate.py 中計算得出的,它是保持計時器加上三秒的常數。應用程式顯示了該數值,我也會引用該顯示,但這是算術計算而非實測的系統速度,將其稱為基準測試延遲將是一種微小的謊言,會讓你失去並列其中的重大真實價值。
我更引以為傲的事件是 Vigil 拒絕斷言的那一起。Cam 2 在基準真相中是真實跌倒,且 P(fall) 達到 0.99,但 Vigil 依然沒有斷言它:該房間內有兩個目標,單人追蹤超出 V1 涵蓋範圍,閘門因而回傳 ROUTE TO HUMAN(低信心度)。樸素基準線則自動觸發,把不屬於它的偵測功勞據為己有。該班次發生了兩起真實跌倒。Vigil 對其中一起發出警報,另一起則轉送工作人員檢查,我絕不會將其描述為抓住了每一次跌倒,因為事實並非如此。在整個基準測試中,40 起多人同室跌倒事件中有 40 起都轉送人工,既沒有過度警報,也沒有漏報。
計分板被允許宣稱的成果
「班次結果」彈出視窗下方的警語,是我最先起草的面板內容。該視窗顯示:本班次本引擎誤報為 0,相較之下現有技術為 7;2 起真實跌倒中成功偵測 1 起、1 起轉送人工;在 360 個標註事件中達到 100% 干擾項特異度;以及每間房每天推估誤報為 0.0,相較之下為 25。

這些數據描述的是一個固定合成黃金集合,僅此而已。不是生產環境準確率,不是臨床結果,不是經過驗證的醫療宣稱,更絕非對機構的保證。在影子模式校準後,真實試點的目標是每間房每天低於 2 次誤報,而這才是我會呈現在護理總監面前的數字,因為這是經得起檢驗的承諾。0.0 證明了該機制在我能交付給你的集合上能區分跌倒與干擾項;它不是對我從未涉足之建築物的空頭支票。
我如今對生命安全警報所持的標準
完成這次構建後,我對跌倒偵測必須擅長的事物有了更嚴苛的定義。偵測只是一個門檻,而單憑門檻就已經在我自己的測試集上拿到了 1.0 的召回率。真正贏得護理人員信任的工作是「拒絕」:清楚知道風扇佔據哪個體素的雜波圖、拒絕接受單幀的衝擊測試、區分重坐與跌倒的觸地條件,以及讓州政府稽查員(而非模型)能夠讀懂系統為何如此決策的閘門。
完整的示範解說位於 https://veriprajna.com/demos/smart-facility-fall-detection,而帳本中那十個被壓制的資料列,才是真正決定整個班次成敗的關鍵。
如果你寧願觀看整個班次的運行而非讀我的文字描述,這裡是整夜端到端運行的完整過程。
一個會對吊扇發出警報的系統在一週內就會被調成靜音,而靜音的系統根本什麼都偵測不到。我能賦予這個層級最精密的行為,就是有憑有據地拒絕發出警報,並附上決定性數字的能力。在 Cam 2 上,那個數字是 0.99,而正確的決定依然是將事件移交給人工處理。


