習(xí)的多模態(tài)可穿戴傳感器融合實(shí)現(xiàn)BFRB檢測(cè)實(shí)戰(zhàn))
1. 從無(wú)意識(shí)小動(dòng)作說(shuō)起B(yǎng)FRB 檢測(cè)到底在解決什么問(wèn)題身體聚焦重復(fù)行為Body-Focused Repetitive Behaviors簡(jiǎn)稱(chēng) BFRB的自動(dòng)檢測(cè)是一個(gè)看起來(lái)簡(jiǎn)單、做起來(lái)卻相當(dāng)繁瑣的工程問(wèn)題。難點(diǎn)不在代碼量而在多路傳感器數(shù)據(jù)怎么對(duì)齊、怎么分段、怎么融合以及模型在真實(shí)佩戴場(chǎng)景下如何保持穩(wěn)定。網(wǎng)上資料往往只講某一個(gè)環(huán)節(jié)要么只談傳感器選型要么只貼一段分類(lèi)網(wǎng)絡(luò)代碼缺少一條完整可跑的鏈路。本文就以“Deep Multimodal Wearable Sensor Fusion for Detection of Body-Focused Repetitive Behaviors”為主線從概念、數(shù)據(jù)、模型到訓(xùn)練驗(yàn)證帶你把一套最小可運(yùn)行的檢測(cè)原型搭起來(lái)。1.1 什么是身體聚焦重復(fù)行為BFRB 是一類(lèi)重復(fù)性的、難以自控的身體相關(guān)行為常見(jiàn)表現(xiàn)包括拔毛癖Trichotillomania反復(fù)拔扯頭發(fā)、眉毛、睫毛抓撓皮膚Skin Picking / Excoriation反復(fù)摳抓皮膚直至破損咬指甲Onychophagia反復(fù)啃咬指甲或甲周皮膚咬嘴唇、咬口腔黏膜等其他習(xí)慣性動(dòng)作。這類(lèi)行為在人群中并不少見(jiàn)但在傳統(tǒng)診療中主要依賴(lài)患者自述和臨床問(wèn)診。問(wèn)題在于很多患者在無(wú)意識(shí)狀態(tài)下完成這些動(dòng)作事后自己都記不清頻率導(dǎo)致醫(yī)生得到的主觀數(shù)據(jù)誤差很大。因此如何客觀、連續(xù)地捕捉這些行為成為一個(gè)很實(shí)際的工程問(wèn)題。1.2 為什么選擇可穿戴傳感器與攝像頭監(jiān)控相比可穿戴設(shè)備有幾個(gè)明顯優(yōu)勢(shì)不受場(chǎng)地限制可以在日常環(huán)境中長(zhǎng)時(shí)間佩戴不侵犯視覺(jué)隱私用戶更容易接受傳感器直接接觸身體對(duì)手部、手臂、頭部等部位的動(dòng)作信息捕捉更精確。常見(jiàn)的做法是把加速度計(jì)Accelerometer、陀螺儀Gyroscope、肌電傳感器EMG等集成在手表、手環(huán)或定制貼片上。當(dāng)用戶做出抓、拔、咬等動(dòng)作時(shí)傳感器數(shù)據(jù)會(huì)出現(xiàn)特定的小幅度、短時(shí)、重復(fù)性模式。檢測(cè)算法要做的就是把這些模式從大量日常動(dòng)作中區(qū)分出來(lái)。1.3 Deep Multimodal 與 Sensor Fusion 的含義Multimodal多模態(tài)指的是數(shù)據(jù)來(lái)源不止一種。例如同時(shí)使用加速度計(jì)和陀螺儀或者同時(shí)采集 IMU 與 EMG 數(shù)據(jù)。Sensor Fusion傳感器融合指把多個(gè)模態(tài)的信息按一定策略合并成統(tǒng)一表示從而比單一傳感器獲得更高的識(shí)別準(zhǔn)確率。Deep 在這里指的是用深度神經(jīng)網(wǎng)絡(luò)自動(dòng)學(xué)習(xí)特征而不是手工設(shè)計(jì)閾值或特征規(guī)則。一句話概括本文的核心就是“用深度學(xué)習(xí)把多路可穿戴傳感器數(shù)據(jù)融合起來(lái)自動(dòng)識(shí)別 BFRB 動(dòng)作”。這是一條從數(shù)據(jù)采集到模型部署的完整技術(shù)鏈路。2. 檢測(cè)系統(tǒng)整體架構(gòu)2.1 端到端鏈路一個(gè)典型的 BFRB 檢測(cè)系統(tǒng)可以拆成下面幾個(gè)環(huán)節(jié)數(shù)據(jù)采集可穿戴設(shè)備按固定頻率采集多路傳感器原始數(shù)據(jù)數(shù)據(jù)清洗去除異常值、填補(bǔ)缺失段、對(duì)齊時(shí)間戳滑窗分段將連續(xù)數(shù)據(jù)切成固定時(shí)長(zhǎng)的片段特征/表示學(xué)習(xí)用深度學(xué)習(xí)模型從每個(gè)片段中提取特征分類(lèi)決策輸出當(dāng)前片段是否屬于 BFRB 行為后處理與告警對(duì)連續(xù)片段做平滑減少誤報(bào)必要時(shí)觸發(fā)提示或干預(yù)。這個(gè)鏈路里數(shù)據(jù)清洗和滑窗分段往往決定上限。模型結(jié)構(gòu)再先進(jìn)如果輸入數(shù)據(jù)里含有大量空洞、錯(cuò)位、未對(duì)齊的片段訓(xùn)練出來(lái)的效果也不會(huì)好。2.2 常見(jiàn)傳感器與采樣設(shè)置傳感器作用典型采樣率說(shuō)明加速度計(jì)捕捉手部/肢體加速度變化50–100 Hz功耗低適合長(zhǎng)時(shí)間佩戴陀螺儀捕捉旋轉(zhuǎn)角速度50–100 Hz與加速度計(jì)互補(bǔ)EMG 肌電捕捉肌肉放電活動(dòng)200–1000 Hz數(shù)據(jù)量大信息更直接PPG 光電脈搏反映心率和血流變化25–64 Hz通常用于輔助狀態(tài)判斷實(shí)際項(xiàng)目中不一定要全部傳感器都用上。優(yōu)先選擇低功耗、用戶佩戴舒適、對(duì)目標(biāo)動(dòng)作區(qū)分度高的模態(tài)組合常見(jiàn)配置是“加速度計(jì) 陀螺儀”進(jìn)階方案再加入 EMG。2.3 多模態(tài)數(shù)據(jù)的對(duì)齊與分段多路傳感器數(shù)據(jù)首先需要按時(shí)間戳對(duì)齊。設(shè)備端通常會(huì)給每包數(shù)據(jù)打上時(shí)間戳接收端可以按時(shí)間插值到統(tǒng)一采樣率。對(duì)齊之后就是滑窗分段。窗口長(zhǎng)度建議結(jié)合動(dòng)作特點(diǎn)選擇BFRB 單次動(dòng)作往往持續(xù) 0.5 到 3 秒因此窗口通常設(shè)為 2 到 4 秒步長(zhǎng)可以為 1 秒。窗口太短會(huì)丟失動(dòng)作上下文太長(zhǎng)又會(huì)引入大量無(wú)關(guān)動(dòng)作增大誤報(bào)。3. 環(huán)境準(zhǔn)備與依賴(lài)說(shuō)明3.1 工具鏈選型文章后續(xù)實(shí)戰(zhàn)以 Python 為主深度學(xué)習(xí)框架使用 PyTorch。示例環(huán)境如下Python 3.9 或 3.10PyTorch 2.xNumPy、Pandas 用于數(shù)據(jù)處理scikit-learn 用于評(píng)估指標(biāo)計(jì)算Matplotlib 用于可視化可選。版本需要根據(jù)你的實(shí)際環(huán)境調(diào)整本文重點(diǎn)演示設(shè)計(jì)思路不綁定某個(gè)具體小版本。如果你的顯卡支持 CUDA訓(xùn)練會(huì)更快沒(méi)有 GPU 也沒(méi)關(guān)系示例模型參數(shù)很少CPU 也能完成訓(xùn)練。3.2 項(xiàng)目目錄結(jié)構(gòu)一個(gè)最小但完整的多模態(tài)檢測(cè)項(xiàng)目推薦這樣組織brfb_detection/ ├── data/ │ ├── raw/ # 原始傳感器 CSV │ └── processed/ # 預(yù)處理后的窗口數(shù)據(jù) ├── src/ │ ├── preprocess.py # 清洗、對(duì)齊、滑窗 │ ├── dataset.py # PyTorch Dataset │ ├── model.py # 多模態(tài)融合模型 │ ├── train.py # 訓(xùn)練與評(píng)估 │ └── config.py # 全局配置 └── checkpoints/ # 模型權(quán)重保存這樣拆分的目的是讓每個(gè)模塊職責(zé)單一數(shù)據(jù)處理、模型定義、訓(xùn)練邏輯互不耦合方便后續(xù)替換傳感器組合或調(diào)整網(wǎng)絡(luò)結(jié)構(gòu)。4. 數(shù)據(jù)預(yù)處理與融合策略4.1 傳感器數(shù)據(jù)清洗與“空洞檢測(cè)”傳感器在傳輸過(guò)程中經(jīng)常出現(xiàn)丟包表現(xiàn)為某段時(shí)間內(nèi)完全沒(méi)有數(shù)據(jù)或數(shù)據(jù)稀疏。這種“空洞”如果直接送入模型會(huì)引入大量偽特征。因此第一步要檢測(cè)空洞區(qū)域hole detection再?zèng)Q定是插值還是丟棄。下面給出一個(gè)基于 Pandas 的示例。假設(shè)原始數(shù)據(jù)是每個(gè)傳感器一個(gè) 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): 檢測(cè)傳感器時(shí)間序列中的空洞區(qū)域。 參數(shù) ----- df : pd.DataFrame 包含時(shí)間戳列和傳感器列的原始數(shù)據(jù)。 time_col : str 時(shí)間戳列名單位為秒。 expected_delta_ms : float 正常情況下的采樣間隔毫秒。 max_gap_ms : float 超過(guò)該間隔視為空洞。 返回 ----- 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這個(gè)函數(shù)的核心思路是簡(jiǎn)單的連續(xù)差值判斷相鄰兩行時(shí)間戳間隔明顯偏大就認(rèn)為是空洞起始直到間隔恢復(fù)正常。得到的空洞區(qū)間可以用于后續(xù)插值或直接切掉。4.2 滑窗分段清洗后的數(shù)據(jù)需要切成固定長(zhǎng)度的窗口。窗口長(zhǎng)度、步長(zhǎng)在config.py中統(tǒng)一管理。# filepath: src/config.py SAMPLE_RATE 50 # 對(duì)齊后的統(tǒng)一采樣率Hz WINDOW_SEC 3 # 窗口長(zhǎng)度秒 STRIDE_SEC 1 # 滑動(dòng)步長(zhǎng)秒 NUM_SENSOR_CHANNELS 6 # 例如 ax ay az gx gy gz NUM_CLASSES 2 # 0: 非BFRB, 1: BFRB對(duì)應(yīng)的滑窗函數(shù)import numpy as np def sliding_windows(data, window_len, stride_len): 將多通道傳感器數(shù)據(jù)切成窗口。 參數(shù) ----- data : np.ndarray 形狀為 (T, C) 的對(duì)齊后數(shù)據(jù)。 window_len : int 窗口樣本點(diǎn)數(shù)。 stride_len : int 步長(zhǎng)樣本點(diǎn)數(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)這段代碼是“拿到窗口”的最小實(shí)現(xiàn)。窗口數(shù)量大約為(T - window_len) / stride_len 1例如 3 分鐘數(shù)據(jù)采樣率 50Hz約 9000 行按 150 點(diǎn)窗口、50 點(diǎn)步長(zhǎng)能切出約 178 個(gè)窗口。4.3 早融合、晚融合與混合融合多傳感器數(shù)據(jù)的融合策略直接影響模型結(jié)構(gòu)和訓(xùn)練難度。早融合Early Fusion把多路數(shù)據(jù)在輸入層就直接拼接例如把加速度計(jì)和陀螺儀合并成 6 通道輸入后續(xù)統(tǒng)一用一個(gè)網(wǎng)絡(luò)處理。優(yōu)點(diǎn)是實(shí)現(xiàn)簡(jiǎn)單缺點(diǎn)是各模態(tài)特征在淺層就被混合難以區(qū)分不同傳感器自身的噪聲特性。晚融合Late Fusion每個(gè)模態(tài)各自走一個(gè)特征提取分支最后把特征拼接或相加后再分類(lèi)。優(yōu)點(diǎn)是各分支可以獨(dú)立設(shè)計(jì)適合采樣率和特性差異大的模態(tài)組合。缺點(diǎn)是參數(shù)更多訓(xùn)練數(shù)據(jù)不足時(shí)容易過(guò)擬合。混合融合Hybrid Fusion在多個(gè)層級(jí)上逐步融合例如淺層各模態(tài)獨(dú)立提取局部特征中層通過(guò)注意力機(jī)制交互最后全局決策。這是當(dāng)前論文里最常見(jiàn)的設(shè)計(jì)思路也是本文實(shí)戰(zhàn)部分采用的方式。5. 模型實(shí)戰(zhàn)基于注意力機(jī)制的多分支融合網(wǎng)絡(luò)5.1 模型整體設(shè)計(jì)我們?cè)O(shè)計(jì)一個(gè)適合 3 秒窗口、6 通道輸入的輕量級(jí)模型結(jié)構(gòu)如下兩個(gè)分支一個(gè)分支處理加速度計(jì)三軸一個(gè)分支處理陀螺儀三軸每個(gè)分支包含兩層 1D CNN用于提取局部時(shí)序特征兩個(gè)分支的特征用多頭注意力層做交互融合融合后的特征經(jīng)全局池化、全連接層輸出二分類(lèi)概率是否 BFRB。選擇分支結(jié)構(gòu)而不是單一網(wǎng)絡(luò)是為了體現(xiàn)“多模態(tài)融合”的思路每個(gè)傳感器先學(xué)自己的特征再用注意力機(jī)制學(xué)習(xí)模態(tài)之間的關(guān)系避免把不同物理含義的信號(hào)強(qiáng)行混在一起。5.2 PyTorch 模型實(shí)現(xiàn)# filepath: src/model.py import torch import torch.nn as nn class SensorBranch(nn.Module): 單個(gè)傳感器分支兩層一維卷積 殘差連接 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)融合模型 - 加速度計(jì)分支 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) # 注意力機(jī)制需要 (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這段代碼有幾個(gè)地方要說(shuō)明SensorBranch中做了殘差連接避免網(wǎng)絡(luò)加深后梯度消失注意力層放在兩個(gè)傳感器分支的融合向量之后可以讓模型學(xué)到“當(dāng)前片段里加速度計(jì)信息更重要還是陀螺儀信息更重要”分類(lèi)頭加了 Dropout緩解小樣本數(shù)據(jù)上的過(guò)擬合。注意這是一個(gè)針對(duì)窗口數(shù)據(jù)的離線分類(lèi)模型。如果要做實(shí)時(shí)檢測(cè)通常需要在模型前后加上滑窗緩沖區(qū)和后處理邏輯這點(diǎn)在后面的工程建議中再展開(kāi)。5.3 Dataset 與訓(xùn)練腳本接下來(lái)實(shí)現(xiàn) PyTorch Dataset。數(shù)據(jù)集假設(shè)已經(jīng)由預(yù)處理階段生成了兩個(gè).npy文件train_acc.npy、train_gyro.npy以及對(duì)應(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): 加速度計(jì)與陀螺儀窗口數(shù)據(jù)對(duì) 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)練并輸出評(píng)估指標(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. 運(yùn)行與驗(yàn)證6.1 準(zhǔn)備示例數(shù)據(jù)由于真實(shí)受試者數(shù)據(jù)通常涉及隱私和倫理審批本地復(fù)現(xiàn)時(shí)可以先用合成數(shù)據(jù)驗(yàn)證流程。下面生成兩段正弦波疊加噪聲的模擬數(shù)據(jù)分別模擬“日常動(dòng)作”和“重復(fù)抓取動(dòng)作”的加速度計(jì)與陀螺儀信號(hào)。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: # 類(lèi)別 1模擬重復(fù)抓取有較高頻周期性抖動(dòng) 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: # 類(lèi)別 0日常低頻小幅動(dòng)作 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并運(yùn)行就能得到 160 個(gè)訓(xùn)練窗口、40 個(gè)測(cè)試窗口。首次運(yùn)行前記得創(chuàng)建data/processed和checkpoints目錄。6.2 訓(xùn)練輸出說(shuō)明運(yùn)行訓(xùn)練腳本cd brfb_detection mkdir -p data/processed checkpoints python src/make_synthetic_data.py python src/train.py預(yù)期輸出類(lèi)似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]]不同運(yùn)行環(huán)境、隨機(jī)種子下數(shù)字會(huì)有差異這屬于正常現(xiàn)象。關(guān)鍵看趨勢(shì)loss 逐步下降acc 和 f1 逐步上升說(shuō)明模型在合成數(shù)據(jù)上確實(shí)學(xué)到了區(qū)分兩類(lèi)模式的規(guī)律。6.3 評(píng)估指標(biāo)怎么解讀在二分類(lèi)場(chǎng)景中不能只看準(zhǔn)確率。因?yàn)閷?shí)際采集數(shù)據(jù)里“非 BFRB”樣本往往遠(yuǎn)多于“BFRB”樣本模型即使把所有樣本都判為負(fù)類(lèi)準(zhǔn)確率也可能很高。因此需要重點(diǎn)看Precision精確率模型判為 BFRB 的樣本中真正是 BFRB 的比例Recall召回率所有真實(shí)的 BFRB 樣本中模型檢出多少F1-ScorePrecision 和 Recall 的調(diào)和平均Confusion Matrix混淆矩陣看錯(cuò)在哪個(gè)方向便于分析誤報(bào)和漏報(bào)。對(duì)于 BFRB 檢測(cè)一般更關(guān)注“漏報(bào)”的代價(jià)。如果漏掉一次行為可能錯(cuò)過(guò)一次干預(yù)機(jī)會(huì)而誤報(bào)可以靠后處理階段的連續(xù)性判斷來(lái)過(guò)濾。所以早期版本可以把閾值調(diào)得偏“敏感”一些。7. 常見(jiàn)問(wèn)題與排查思路7.1 數(shù)據(jù)類(lèi)別不均衡真實(shí)場(chǎng)景中 BFRB 行為出現(xiàn)的時(shí)長(zhǎng)占比通常很低可能只有 5% 到 10%導(dǎo)致訓(xùn)練集中正樣本極少。解決思路使用類(lèi)別權(quán)重nn.CrossEntropyLoss(weightpos_weight)給少數(shù)類(lèi)更大權(quán)重過(guò)采樣少數(shù)類(lèi)窗口或者對(duì)少數(shù)類(lèi)窗口做小幅時(shí)間扭曲、加噪聲等數(shù)據(jù)增強(qiáng)評(píng)估時(shí)以 F1、召回率為主而不是只看準(zhǔn)確率。7.2 傳感器噪聲與偽影可穿戴設(shè)備在佩戴過(guò)程中會(huì)有松動(dòng)、皮膚出汗、電磁干擾等問(wèn)題導(dǎo)致信號(hào)中出現(xiàn)尖峰或漂移。排查步驟先畫(huà)出原始波形肉眼確認(rèn)異常區(qū)域檢查是不是真實(shí)動(dòng)作還是設(shè)備異常例如拔掉設(shè)備甩動(dòng)時(shí)數(shù)據(jù)是否有對(duì)應(yīng)響應(yīng)對(duì)噪聲嚴(yán)重的樣本考慮增加帶通濾波或去除異常尖峰如果某個(gè)設(shè)備持續(xù)異常需要先檢查硬件佩戴方式而不是只改算法。7.3 模型過(guò)擬合當(dāng)訓(xùn)練樣本很少而模型參數(shù)很多時(shí)訓(xùn)練 loss 很低但驗(yàn)證指標(biāo)不佳。具體表現(xiàn)為訓(xùn)練集 acc 接近 1測(cè)試集 acc 明顯低。處理方法增大 Dropout 比例減小模型 hidden 維度使用數(shù)據(jù)增強(qiáng)使用早停early stopping在驗(yàn)證 loss 不再下降時(shí)停止訓(xùn)練如果條件允許引入遷移學(xué)習(xí)先在公開(kāi)的日常動(dòng)作數(shù)據(jù)集如運(yùn)動(dòng)識(shí)別數(shù)據(jù)集上預(yù)訓(xùn)練再在 BFRB 數(shù)據(jù)上微調(diào)。7.4 檢測(cè)延遲與誤觸發(fā)離線分類(lèi)可以接受秒級(jí)延遲但可穿戴設(shè)備上的實(shí)時(shí)檢測(cè)對(duì)延遲很敏感。常見(jiàn)問(wèn)題是模型在手臂正常擺動(dòng)時(shí)被誤判為 BFRB。建議后處理使用“連續(xù) k 個(gè)窗口中有 m 個(gè)判為正類(lèi)”再觸發(fā)稱(chēng)為 detection switch 思路能明顯降低單窗口誤報(bào)在時(shí)間維度上對(duì)預(yù)測(cè)概率做平滑例如指數(shù)移動(dòng)平均結(jié)合用戶場(chǎng)景做約束例如只在靜止或特定姿態(tài)下啟用檢測(cè)。7.5 檢測(cè)管線“有 bug 但看不出錯(cuò)”有時(shí)候模型跑起來(lái)不報(bào)錯(cuò)但結(jié)果明顯不合理例如 loss 一直不降、訓(xùn)練 acc 亂跳。按下面順序排查問(wèn)題現(xiàn)象常見(jiàn)原因排查思路loss 不下降標(biāo)簽與數(shù)據(jù)不對(duì)齊