習(xí)的多模態(tài)可穿戴傳感器融合實現(xiàn)BFRB檢測實戰(zhàn))
1. 從無意識小動作說起B(yǎng)FRB 檢測到底在解決什么問題身體聚焦重復(fù)行為Body-Focused Repetitive Behaviors簡稱 BFRB的自動檢測是一個看起來簡單、做起來卻相當(dāng)繁瑣的工程問題。難點不在代碼量而在多路傳感器數(shù)據(jù)怎么對齊、怎么分段、怎么融合以及模型在真實佩戴場景下如何保持穩(wěn)定。網(wǎng)上資料往往只講某一個環(huán)節(jié)要么只談傳感器選型要么只貼一段分類網(wǎng)絡(luò)代碼缺少一條完整可跑的鏈路。本文就以“Deep Multimodal Wearable Sensor Fusion for Detection of Body-Focused Repetitive Behaviors”為主線從概念、數(shù)據(jù)、模型到訓(xùn)練驗證帶你把一套最小可運行的檢測原型搭起來。1.1 什么是身體聚焦重復(fù)行為BFRB 是一類重復(fù)性的、難以自控的身體相關(guān)行為常見表現(xiàn)包括拔毛癖Trichotillomania反復(fù)拔扯頭發(fā)、眉毛、睫毛抓撓皮膚Skin Picking / Excoriation反復(fù)摳抓皮膚直至破損咬指甲Onychophagia反復(fù)啃咬指甲或甲周皮膚咬嘴唇、咬口腔黏膜等其他習(xí)慣性動作。這類行為在人群中并不少見但在傳統(tǒng)診療中主要依賴患者自述和臨床問診。問題在于很多患者在無意識狀態(tài)下完成這些動作事后自己都記不清頻率導(dǎo)致醫(yī)生得到的主觀數(shù)據(jù)誤差很大。因此如何客觀、連續(xù)地捕捉這些行為成為一個很實際的工程問題。1.2 為什么選擇可穿戴傳感器與攝像頭監(jiān)控相比可穿戴設(shè)備有幾個明顯優(yōu)勢不受場地限制可以在日常環(huán)境中長時間佩戴不侵犯視覺隱私用戶更容易接受傳感器直接接觸身體對手部、手臂、頭部等部位的動作信息捕捉更精確。常見的做法是把加速度計Accelerometer、陀螺儀Gyroscope、肌電傳感器EMG等集成在手表、手環(huán)或定制貼片上。當(dāng)用戶做出抓、拔、咬等動作時傳感器數(shù)據(jù)會出現(xiàn)特定的小幅度、短時、重復(fù)性模式。檢測算法要做的就是把這些模式從大量日常動作中區(qū)分出來。1.3 Deep Multimodal 與 Sensor Fusion 的含義Multimodal多模態(tài)指的是數(shù)據(jù)來源不止一種。例如同時使用加速度計和陀螺儀或者同時采集 IMU 與 EMG 數(shù)據(jù)。Sensor Fusion傳感器融合指把多個模態(tài)的信息按一定策略合并成統(tǒng)一表示從而比單一傳感器獲得更高的識別準(zhǔn)確率。Deep 在這里指的是用深度神經(jīng)網(wǎng)絡(luò)自動學(xué)習(xí)特征而不是手工設(shè)計閾值或特征規(guī)則。一句話概括本文的核心就是“用深度學(xué)習(xí)把多路可穿戴傳感器數(shù)據(jù)融合起來自動識別 BFRB 動作”。這是一條從數(shù)據(jù)采集到模型部署的完整技術(shù)鏈路。2. 檢測系統(tǒng)整體架構(gòu)2.1 端到端鏈路一個典型的 BFRB 檢測系統(tǒng)可以拆成下面幾個環(huán)節(jié)數(shù)據(jù)采集可穿戴設(shè)備按固定頻率采集多路傳感器原始數(shù)據(jù)數(shù)據(jù)清洗去除異常值、填補缺失段、對齊時間戳滑窗分段將連續(xù)數(shù)據(jù)切成固定時長的片段特征/表示學(xué)習(xí)用深度學(xué)習(xí)模型從每個片段中提取特征分類決策輸出當(dāng)前片段是否屬于 BFRB 行為后處理與告警對連續(xù)片段做平滑減少誤報必要時觸發(fā)提示或干預(yù)。這個鏈路里數(shù)據(jù)清洗和滑窗分段往往決定上限。模型結(jié)構(gòu)再先進(jìn)如果輸入數(shù)據(jù)里含有大量空洞、錯位、未對齊的片段訓(xùn)練出來的效果也不會好。2.2 常見傳感器與采樣設(shè)置傳感器作用典型采樣率說明加速度計捕捉手部/肢體加速度變化50–100 Hz功耗低適合長時間佩戴陀螺儀捕捉旋轉(zhuǎn)角速度50–100 Hz與加速度計互補EMG 肌電捕捉肌肉放電活動200–1000 Hz數(shù)據(jù)量大信息更直接PPG 光電脈搏反映心率和血流變化25–64 Hz通常用于輔助狀態(tài)判斷實際項目中不一定要全部傳感器都用上。優(yōu)先選擇低功耗、用戶佩戴舒適、對目標(biāo)動作區(qū)分度高的模態(tài)組合常見配置是“加速度計 陀螺儀”進(jìn)階方案再加入 EMG。2.3 多模態(tài)數(shù)據(jù)的對齊與分段多路傳感器數(shù)據(jù)首先需要按時間戳對齊。設(shè)備端通常會給每包數(shù)據(jù)打上時間戳接收端可以按時間插值到統(tǒng)一采樣率。對齊之后就是滑窗分段。窗口長度建議結(jié)合動作特點選擇BFRB 單次動作往往持續(xù) 0.5 到 3 秒因此窗口通常設(shè)為 2 到 4 秒步長可以為 1 秒。窗口太短會丟失動作上下文太長又會引入大量無關(guān)動作增大誤報。3. 環(huán)境準(zhǔn)備與依賴說明3.1 工具鏈選型文章后續(xù)實戰(zhàn)以 Python 為主深度學(xué)習(xí)框架使用 PyTorch。示例環(huán)境如下Python 3.9 或 3.10PyTorch 2.xNumPy、Pandas 用于數(shù)據(jù)處理scikit-learn 用于評估指標(biāo)計算Matplotlib 用于可視化可選。版本需要根據(jù)你的實際環(huán)境調(diào)整本文重點演示設(shè)計思路不綁定某個具體小版本。如果你的顯卡支持 CUDA訓(xùn)練會更快沒有 GPU 也沒關(guān)系示例模型參數(shù)很少CPU 也能完成訓(xùn)練。3.2 項目目錄結(jié)構(gòu)一個最小但完整的多模態(tài)檢測項目推薦這樣組織brfb_detection/ ├── data/ │ ├── raw/ # 原始傳感器 CSV │ └── processed/ # 預(yù)處理后的窗口數(shù)據(jù) ├── src/ │ ├── preprocess.py # 清洗、對齊、滑窗 │ ├── dataset.py # PyTorch Dataset │ ├── model.py # 多模態(tài)融合模型 │ ├── train.py # 訓(xùn)練與評估 │ └── config.py # 全局配置 └── checkpoints/ # 模型權(quán)重保存這樣拆分的目的是讓每個模塊職責(zé)單一數(shù)據(jù)處理、模型定義、訓(xùn)練邏輯互不耦合方便后續(xù)替換傳感器組合或調(diào)整網(wǎng)絡(luò)結(jié)構(gòu)。4. 數(shù)據(jù)預(yù)處理與融合策略4.1 傳感器數(shù)據(jù)清洗與“空洞檢測”傳感器在傳輸過程中經(jīng)常出現(xiàn)丟包表現(xiàn)為某段時間內(nèi)完全沒有數(shù)據(jù)或數(shù)據(jù)稀疏。這種“空洞”如果直接送入模型會引入大量偽特征。因此第一步要檢測空洞區(qū)域hole detection再決定是插值還是丟棄。下面給出一個基于 Pandas 的示例。假設(shè)原始數(shù)據(jù)是每個傳感器一個 CSV字段包含 timestamp、ax、ay、az、gx、gy、gz。import numpy as np import pandas as pd def detect_hole_intervals(df, time_coltimestamp, expected_delta_ms20, max_gap_ms200): 檢測傳感器時間序列中的空洞區(qū)域。 參數(shù) ----- df : pd.DataFrame 包含時間戳列和傳感器列的原始數(shù)據(jù)。 time_col : str 時間戳列名單位為秒。 expected_delta_ms : float 正常情況下的采樣間隔毫秒。 max_gap_ms : float 超過該間隔視為空洞。 返回 ----- holes : list[tuple[int, int]] 空洞區(qū)域的起始/結(jié)束行索引列表。 df df.sort_values(time_col).reset_index(dropTrue) diff_ms (df[time_col].diff() * 1000).to_numpy() diff_ms[0] expected_delta_ms holes [] start None for i, gap in enumerate(diff_ms): if gap max_gap_ms: if start is None: start i - 1 else: if start is not None: holes.append((start, i - 1)) start None if start is not None: holes.append((start, len(df) - 1)) return holes這個函數(shù)的核心思路是簡單的連續(xù)差值判斷相鄰兩行時間戳間隔明顯偏大就認(rèn)為是空洞起始直到間隔恢復(fù)正常。得到的空洞區(qū)間可以用于后續(xù)插值或直接切掉。4.2 滑窗分段清洗后的數(shù)據(jù)需要切成固定長度的窗口。窗口長度、步長在config.py中統(tǒng)一管理。# filepath: src/config.py SAMPLE_RATE 50 # 對齊后的統(tǒng)一采樣率Hz WINDOW_SEC 3 # 窗口長度秒 STRIDE_SEC 1 # 滑動步長秒 NUM_SENSOR_CHANNELS 6 # 例如 ax ay az gx gy gz NUM_CLASSES 2 # 0: 非BFRB, 1: BFRB對應(yīng)的滑窗函數(shù)import numpy as np def sliding_windows(data, window_len, stride_len): 將多通道傳感器數(shù)據(jù)切成窗口。 參數(shù) ----- data : np.ndarray 形狀為 (T, C) 的對齊后數(shù)據(jù)。 window_len : int 窗口樣本點數(shù)。 stride_len : int 步長樣本點數(shù)。 返回 ----- windows : np.ndarray 形狀為 (N, window_len, C)。 windows [] n data.shape[0] for start in range(0, n - window_len 1, stride_len): windows.append(data[start:start window_len]) return np.stack(windows)這段代碼是“拿到窗口”的最小實現(xiàn)。窗口數(shù)量大約為(T - window_len) / stride_len 1例如 3 分鐘數(shù)據(jù)采樣率 50Hz約 9000 行按 150 點窗口、50 點步長能切出約 178 個窗口。4.3 早融合、晚融合與混合融合多傳感器數(shù)據(jù)的融合策略直接影響模型結(jié)構(gòu)和訓(xùn)練難度。早融合Early Fusion把多路數(shù)據(jù)在輸入層就直接拼接例如把加速度計和陀螺儀合并成 6 通道輸入后續(xù)統(tǒng)一用一個網(wǎng)絡(luò)處理。優(yōu)點是實現(xiàn)簡單缺點是各模態(tài)特征在淺層就被混合難以區(qū)分不同傳感器自身的噪聲特性。晚融合Late Fusion每個模態(tài)各自走一個特征提取分支最后把特征拼接或相加后再分類。優(yōu)點是各分支可以獨立設(shè)計適合采樣率和特性差異大的模態(tài)組合。缺點是參數(shù)更多訓(xùn)練數(shù)據(jù)不足時容易過擬合。混合融合Hybrid Fusion在多個層級上逐步融合例如淺層各模態(tài)獨立提取局部特征中層通過注意力機制交互最后全局決策。這是當(dāng)前論文里最常見的設(shè)計思路也是本文實戰(zhàn)部分采用的方式。5. 模型實戰(zhàn)基于注意力機制的多分支融合網(wǎng)絡(luò)5.1 模型整體設(shè)計我們設(shè)計一個適合 3 秒窗口、6 通道輸入的輕量級模型結(jié)構(gòu)如下兩個分支一個分支處理加速度計三軸一個分支處理陀螺儀三軸每個分支包含兩層 1D CNN用于提取局部時序特征兩個分支的特征用多頭注意力層做交互融合融合后的特征經(jīng)全局池化、全連接層輸出二分類概率是否 BFRB。選擇分支結(jié)構(gòu)而不是單一網(wǎng)絡(luò)是為了體現(xiàn)“多模態(tài)融合”的思路每個傳感器先學(xué)自己的特征再用注意力機制學(xué)習(xí)模態(tài)之間的關(guān)系避免把不同物理含義的信號強行混在一起。5.2 PyTorch 模型實現(xiàn)# filepath: src/model.py import torch import torch.nn as nn class SensorBranch(nn.Module): 單個傳感器分支兩層一維卷積 殘差連接 def __init__(self, in_channels: int, hidden: int 32): super().__init__() self.conv1 nn.Sequential( nn.Conv1d(in_channels, hidden, kernel_size5, padding2), nn.BatchNorm1d(hidden), nn.ReLU(), ) self.conv2 nn.Sequential( nn.Conv1d(hidden, hidden, kernel_size5, padding2), nn.BatchNorm1d(hidden), nn.ReLU(), ) self.proj nn.Conv1d(in_channels, hidden, kernel_size1) self.pool nn.AdaptiveAvgPool1d(1) def forward(self, x: torch.Tensor) - torch.Tensor: # x: (B, C, T) out self.conv2(self.conv1(x)) out out self.proj(x) out self.pool(out).squeeze(-1) # (B, hidden) return out class MultiModalFusionModel(nn.Module): 多模態(tài)融合模型 - 加速度計分支 3 通道 - 陀螺儀分支 3 通道 - 注意力融合層 def __init__(self, acc_channels: int 3, gyro_channels: int 3, hidden: int 32, num_classes: int 2): super().__init__() self.acc_branch SensorBranch(acc_channels, hidden) self.gyro_branch SensorBranch(gyro_channels, hidden) # 注意力融合 self.fusion_dim hidden * 2 self.attn nn.MultiheadAttention(embed_dimself.fusion_dim, num_heads2, batch_firstTrue) self.classifier nn.Sequential( nn.Linear(self.fusion_dim, 64), nn.ReLU(), nn.Dropout(0.3), nn.Linear(64, num_classes), ) def forward(self, acc: torch.Tensor, gyro: torch.Tensor) - torch.Tensor: # acc 和 gyro 均為 (B, C, T) f_acc self.acc_branch(acc) # (B, hidden) f_gyro self.gyro_branch(gyro) # (B, hidden) fused torch.cat([f_acc, f_gyro], dim-1) # (B, hidden*2) # 注意力機制需要 (B, L, E)L 這里用 1 放在序列維度 fused_seq fused.unsqueeze(1) # (B, 1, E) attn_out, _ self.attn(fused_seq, fused_seq, fused_seq) attn_out attn_out.squeeze(1) # (B, E) logits self.classifier(attn_out) return logits這段代碼有幾個地方要說明SensorBranch中做了殘差連接避免網(wǎng)絡(luò)加深后梯度消失注意力層放在兩個傳感器分支的融合向量之后可以讓模型學(xué)到“當(dāng)前片段里加速度計信息更重要還是陀螺儀信息更重要”分類頭加了 Dropout緩解小樣本數(shù)據(jù)上的過擬合。注意這是一個針對窗口數(shù)據(jù)的離線分類模型。如果要做實時檢測通常需要在模型前后加上滑窗緩沖區(qū)和后處理邏輯這點在后面的工程建議中再展開。5.3 Dataset 與訓(xùn)練腳本接下來實現(xiàn) PyTorch Dataset。數(shù)據(jù)集假設(shè)已經(jīng)由預(yù)處理階段生成了兩個.npy文件train_acc.npy、train_gyro.npy以及對應(yīng)的標(biāo)簽train_label.npy。# filepath: src/dataset.py import numpy as np import torch from torch.utils.data import Dataset class SensorWindowDataset(Dataset): 加速度計與陀螺儀窗口數(shù)據(jù)對 def __init__(self, acc_path, gyro_path, label_path): self.acc np.load(acc_path) # (N, T, 3) self.gyro np.load(gyro_path) # (N, T, 3) self.labels np.load(label_path) # (N,) def __len__(self): return len(self.labels) def __getitem__(self, idx): acc torch.as_tensor(self.acc[idx], dtypetorch.float32) gyro torch.as_tensor(self.gyro[idx], dtypetorch.float32) label torch.as_tensor(self.labels[idx], dtypetorch.long) # 模型輸入要求 (C, T)需要轉(zhuǎn)置 return acc.permute(1, 0), gyro.permute(1, 0), label訓(xùn)練腳本負(fù)責(zé)加載數(shù)據(jù)、構(gòu)建模型、訓(xùn)練并輸出評估指標(biāo)。# filepath: src/train.py import torch import torch.nn as nn from torch.utils.data import DataLoader from sklearn.metrics import accuracy_score, f1_score, confusion_matrix from config import NUM_CLASSES from dataset import SensorWindowDataset from model import MultiModalFusionModel def train_one_epoch(model, loader, criterion, optimizer, device): model.train() total_loss 0.0 for acc, gyro, label in loader: acc, gyro, label acc.to(device), gyro.to(device), label.to(device) optimizer.zero_grad() logits model(acc, gyro) loss criterion(logits, label) loss.backward() optimizer.step() total_loss loss.item() * len(label) return total_loss / len(loader.dataset) torch.no_grad() def evaluate(model, loader, device): model.eval() preds, gts [], [] for acc, gyro, label in loader: acc, gyro acc.to(device), gyro.to(device) logits model(acc, gyro) pred torch.argmax(logits, dim1) preds.extend(pred.cpu().numpy().tolist()) gts.extend(label.numpy().tolist()) acc accuracy_score(gts, preds) f1 f1_score(gts, preds, zero_division0) return acc, f1, confusion_matrix(gts, preds) def main(): device torch.device(cuda if torch.cuda.is_available() else cpu) train_ds SensorWindowDataset(data/processed/train_acc.npy, data/processed/train_gyro.npy, data/processed/train_label.npy) test_ds SensorWindowDataset(data/processed/test_acc.npy, data/processed/test_gyro.npy, data/processed/test_label.npy) train_loader DataLoader(train_ds, batch_size64, shuffleTrue) test_loader DataLoader(test_ds, batch_size64, shuffleFalse) model MultiModalFusionModel(num_classesNUM_CLASSES).to(device) criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3) for epoch in range(30): loss train_one_epoch(model, train_loader, criterion, optimizer, device) if (epoch 1) % 5 0: acc, f1, cm evaluate(model, test_loader, device) print(fEpoch {epoch 1:02d} | loss{loss:.4f} | facc{acc:.4f} | f1{f1:.4f}) print(cm) torch.save(model.state_dict(), checkpoints/fusion_model.pt) if __name__ __main__: main()6. 運行與驗證6.1 準(zhǔn)備示例數(shù)據(jù)由于真實受試者數(shù)據(jù)通常涉及隱私和倫理審批本地復(fù)現(xiàn)時可以先用合成數(shù)據(jù)驗證流程。下面生成兩段正弦波疊加噪聲的模擬數(shù)據(jù)分別模擬“日常動作”和“重復(fù)抓取動作”的加速度計與陀螺儀信號。import numpy as np def make_synthetic_data(n_windows200, window_len150): rng np.random.default_rng(42) acc [] gyro [] labels [] for i in range(n_windows): t np.arange(window_len) / 50.0 if rng.random() 0.5: # 類別 1模擬重復(fù)抓取有較高頻周期性抖動 freq rng.uniform(2, 4) a (0.3 * np.sin(2 * np.pi * freq * t) rng.normal(0, 0.05, window_len)) g (0.2 * np.sin(2 * np.pi * freq * t 1.0) rng.normal(0, 0.05, window_len)) label 1 else: # 類別 0日常低頻小幅動作 a (0.05 * np.sin(2 * np.pi * 0.3 * t) rng.normal(0, 0.05, window_len)) g (0.03 * np.sin(2 * np.pi * 0.2 * t 0.5) rng.normal(0, 0.05, window_len)) label 0 # 每路傳感器生成 3 軸并加入不同相位偏移 acc.append(np.stack([a, np.roll(a, 2), np.roll(a, -3)], axis1)) gyro.append(np.stack([g, np.roll(g, -1), np.roll(g, 4)], axis1)) labels.append(label) return (np.stack(acc), np.stack(gyro), np.array(labels)) acc, gyro, label make_synthetic_data() np.save(data/processed/train_acc.npy, acc[:160]) np.save(data/processed/train_gyro.npy, gyro[:160]) np.save(data/processed/train_label.npy, label[:160]) np.save(data/processed/test_acc.npy, acc[160:]) np.save(data/processed/test_gyro.npy, gyro[160:]) np.save(data/processed/test_label.npy, label[160:])把上面腳本保存為src/make_synthetic_data.py并運行就能得到 160 個訓(xùn)練窗口、40 個測試窗口。首次運行前記得創(chuàng)建data/processed和checkpoints目錄。6.2 訓(xùn)練輸出說明運行訓(xùn)練腳本cd brfb_detection mkdir -p data/processed checkpoints python src/make_synthetic_data.py python src/train.py預(yù)期輸出類似Epoch 05 | loss0.5921 | acc0.7750 | f10.7419 [[17 5] [ 4 14]] Epoch 10 | loss0.4237 | acc0.8500 | f10.8378 [[18 4] [ 2 16]] Epoch 15 | loss0.3011 | acc0.8750 | f10.8710 [[19 3] [ 2 16]]不同運行環(huán)境、隨機種子下數(shù)字會有差異這屬于正?,F(xiàn)象。關(guān)鍵看趨勢loss 逐步下降acc 和 f1 逐步上升說明模型在合成數(shù)據(jù)上確實學(xué)到了區(qū)分兩類模式的規(guī)律。6.3 評估指標(biāo)怎么解讀在二分類場景中不能只看準(zhǔn)確率。因為實際采集數(shù)據(jù)里“非 BFRB”樣本往往遠(yuǎn)多于“BFRB”樣本模型即使把所有樣本都判為負(fù)類準(zhǔn)確率也可能很高。因此需要重點看Precision精確率模型判為 BFRB 的樣本中真正是 BFRB 的比例Recall召回率所有真實的 BFRB 樣本中模型檢出多少F1-ScorePrecision 和 Recall 的調(diào)和平均Confusion Matrix混淆矩陣看錯在哪個方向便于分析誤報和漏報。對于 BFRB 檢測一般更關(guān)注“漏報”的代價。如果漏掉一次行為可能錯過一次干預(yù)機會而誤報可以靠后處理階段的連續(xù)性判斷來過濾。所以早期版本可以把閾值調(diào)得偏“敏感”一些。7. 常見問題與排查思路7.1 數(shù)據(jù)類別不均衡真實場景中 BFRB 行為出現(xiàn)的時長占比通常很低可能只有 5% 到 10%導(dǎo)致訓(xùn)練集中正樣本極少。解決思路使用類別權(quán)重nn.CrossEntropyLoss(weightpos_weight)給少數(shù)類更大權(quán)重過采樣少數(shù)類窗口或者對少數(shù)類窗口做小幅時間扭曲、加噪聲等數(shù)據(jù)增強評估時以 F1、召回率為主而不是只看準(zhǔn)確率。7.2 傳感器噪聲與偽影可穿戴設(shè)備在佩戴過程中會有松動、皮膚出汗、電磁干擾等問題導(dǎo)致信號中出現(xiàn)尖峰或漂移。排查步驟先畫出原始波形肉眼確認(rèn)異常區(qū)域檢查是不是真實動作還是設(shè)備異常例如拔掉設(shè)備甩動時數(shù)據(jù)是否有對應(yīng)響應(yīng)對噪聲嚴(yán)重的樣本考慮增加帶通濾波或去除異常尖峰如果某個設(shè)備持續(xù)異常需要先檢查硬件佩戴方式而不是只改算法。7.3 模型過擬合當(dāng)訓(xùn)練樣本很少而模型參數(shù)很多時訓(xùn)練 loss 很低但驗證指標(biāo)不佳。具體表現(xiàn)為訓(xùn)練集 acc 接近 1測試集 acc 明顯低。處理方法增大 Dropout 比例減小模型 hidden 維度使用數(shù)據(jù)增強使用早停early stopping在驗證 loss 不再下降時停止訓(xùn)練如果條件允許引入遷移學(xué)習(xí)先在公開的日常動作數(shù)據(jù)集如運動識別數(shù)據(jù)集上預(yù)訓(xùn)練再在 BFRB 數(shù)據(jù)上微調(diào)。7.4 檢測延遲與誤觸發(fā)離線分類可以接受秒級延遲但可穿戴設(shè)備上的實時檢測對延遲很敏感。常見問題是模型在手臂正常擺動時被誤判為 BFRB。建議后處理使用“連續(xù) k 個窗口中有 m 個判為正類”再觸發(fā)稱為 detection switch 思路能明顯降低單窗口誤報在時間維度上對預(yù)測概率做平滑例如指數(shù)移動平均結(jié)合用戶場景做約束例如只在靜止或特定姿態(tài)下啟用檢測。7.5 檢測管線“有 bug 但看不出錯”有時候模型跑起來不報錯但結(jié)果明顯不合理例如 loss 一直不降、訓(xùn)練 acc 亂跳。按下面順序排查問題現(xiàn)象常見原因排查思路loss 不下降標(biāo)簽與數(shù)據(jù)不對齊