可穿戴步态识别:帕金森筛查与跌倒监测的DeepConvLSTM实践指南

发布时间:2026/10/10 1:01:23
可穿戴步态识别:帕金森筛查与跌倒监测的DeepConvLSTM实践指南
简介面向帕金森病步态识别研究者的可穿戴设备数据分析资源包适用于医疗辅助诊断、健康监测及机器学习分类场景聚焦加速度计与陀螺仪信号处理、步态特征提取和DeepConvLSTM模型构建适合具备Python基础、希望快速上手可穿戴医疗数据建模的开发者、研究人员及医疗数据分析人员。压缩包共31个文件包含18个CSV数据文件、5个Python脚本、2个NumPy数组、2个Jupyter Notebook以及说明文档与示意图等其中Python脚本覆盖模型定义、训练与评估流程Notebook便于逐步复现实验CSV和NPY文件支撑足部运动数据的加载与预处理docx与md文档提供使用说明整体约24.86MB。当前已有45人学习下载。通过阅读README与说明文档可掌握数据组织方式、模型训练细节及测试脚本用法基于DeepConvLSTM的完整实现可直接扩展至其他步态识别任务也可作为课程设计或小型科研项目的起点节省从零搭建实验环境的时间。1. 可穿戴步态识别从帕金森筛查到居家监测的落地路径这套基于可穿戴设备的帕金森病患者步态识别系统最打动我的是它把诊断从医院评分表拉回了日常行走的真实场景。患者脚踝或腰带上戴一个惯性传感器走几十米加速度计和陀螺仪就把步态冻结、拖步、失衡这些异常变成了可供分类的数字信号。整个资源包围绕一条主线转动数据采集→步态特征提取→DeepConvLSTM 模型训练→分类输出。它既可以用在帕金森病的辅助诊断也能迁移到老年人的跌倒风险监测和康复随访。我拆完后发现真正难的并不是模型结构而是传感器数据如何对齐、标签如何定义以及哪些坑会让整个 pipeline 直接失效。适合谁看准备接医疗 AI 项目的人或者手里已经有一批穿戴设备采集数据但不知道怎么建模的研究者。哪怕你只在做可穿戴设备的活动识别也能从这里复制一套比较完整的深度学习流程。2. 数据采集与预处理加速度计和陀螺仪的同步、去噪与滑动窗口可穿戴设备采集的原始信号不能直接送进模型帕金森患者的行走数据往往夹杂着电子噪声、直流漂移和时间戳抖动。这一章先把传感器选型讲清楚再落到具体可执行的清洗和切窗代码。常见做法是用 MPU6050 这类六轴惯性传感器加速度计量程 ±4g、陀螺仪 ±250°/s采样率设 100Hz。医疗领域对数据可解释性要求高所以每一步预处理都要保留可追溯参数。2.1 传感器选型与佩戴位置帕金森病患者的步态特征和普通人差异很大典型表现是步频从正常的 1.2Hz 掉到 0.5Hz 以下还会出现持续数秒的冻结步态。加速度计对平移运动敏感适合捕捉迈步时的纵向冲击陀螺仪对旋转运动敏感适合识别转身和摆动过程中的肢体旋转。两者组合使用才能在特征层面区分冻结步态和正常行走。量程选择上加速度计不要小于 ±2g否则起步瞬间和落地冲击会削顶陀螺仪至少 ±200°/s应对转身的角速度峰值。佩戴位置直接影响信号形态我一般建议优先固定在两处腰部后侧和脚踝外侧。腰部靠近人体质心垂直轴信号稳定适合做步态周期检测脚踝能捕捉下肢摆动细节对拖步更敏感。但同一个受试者切换佩戴位置后信号幅值和频率分布会明显变化后续训练时模型无法自动适应。因此项目里要提前约定好佩戴位置和传感器坐标轴方向比如 Z 轴朝上代表垂直方向并在数据文件名中标注。整理成一张参数表时可以这样记录项目参数建议加速度计量程±4g陀螺仪量程±250°/s 或 ±500°/s采样率100Hz滤波类型0.5Hz–10Hz 巴特沃斯带通滤波窗口长度128 点约 1.28 秒窗口重叠50%推荐佩戴位置腰部重心附近或脚踝这段表格来自资源包里的默认配置。参数不是拍脑门定的帕金森患者的步态主频集中在 0.5Hz–3Hz冻步时会出现 4Hz 左右的抖动带通上限取 10Hz 能在保留抖动信号的同时滤掉高频电子噪声。采样率 100Hz 是奈奎斯特频率的最小保障实际我会再多留一档余量。2.2 信号清洗、同步与重采样多设备采集的典型问题是时间戳不一致同一块传感器内部的加速度计和陀螺仪虽然没有相位差但数据异常值和缺失值会导致插值出错。我处理顺序是先把原始时间戳转成相对时间统一重采样到 100Hz再做带通滤波最后按绝对阈值剔除突变点和零值段。这段代码是预处理的核心几乎任何后续工作都要用到。import pandas as pd import numpy as np from scipy.signal import butter, filtfilt def resample_to_fs(df, target_fs100.0): 把非均匀时间戳的六轴数据重采样到固定采样率 t pd.to_datetime(df[ts]).apply(lambda x: x.timestamp()) t t - t.iloc[0] total_time t.iloc[-1] n int(np.ceil(total_time * target_fs)) uniform_t np.arange(0, n) / target_fs out pd.DataFrame({t: uniform_t}) for col in [acc_x, acc_y, acc_z, gyr_x, gyr_y, gyr_z]: out[col] np.interp(uniform_t, t.values, df[col].values) return out def bandpass_filter(df, low0.5, high10.0, fs100.0, order4): 0.5-10Hz带通滤波消除直流漂移与高频噪声 b, a butter(order, [low / (fs / 2), high / (fs / 2)], btypeband) cols [acc_x, acc_y, acc_z, gyr_x, gyr_y, gyr_z] df df.copy() df[cols] filtfilt(b, a, df[cols], axis0) return df def remove_spikes(df, threshold4.0): 超过4g的加速度尖峰直接替换为窗口均值 cols [acc_x, acc_y, acc_z] for col in cols: mask np.abs(df[col]) threshold if mask.sum() 0: df.loc[mask, col] np.nan df[col] df[col].fillna(df[col].rolling(5, min_periods1).mean()) return df逻辑说明resample_to_fs把时间戳转成秒并归零再对每一列做线性插值。np.interp按新时间轴取点天然处理了缺失值。带通滤波中的低通 10Hz 上限能保留帕金森患者腿部的震颤成分高通 0.5Hz 则把传感器直流漂移和慢速体位变化去掉。filtfilt是零相位滤波不会像普通滤波那样在标签边缘产生滞后。参数说明衰减阈值 4g 不止针对异常传感器读数也用于剔除落地瞬间的皮质伪影。你需要根据实际佩戴部位调整腰部数据少超 2g脚踝数据可能超过 4g所以这个参数要放到外部配置里。另外重采样前最好检查每个文件的时长如果某段数据采集不到 2 秒直接丢弃避免产生过短窗口。2.3 滑动窗口切分与数据增强预处理后的信号需要切成固定长度窗口才能送进模型。窗口长度我通常选 128 点也就是 1.28 秒大约覆盖一至两个完整迈步周期。如果窗口太短比如 32 点模型只能看到半个单步特征不完整如果拉到 512 点会把不同步态状态混在一起模型无法学到判别性边界。切窗时保持 50% 重叠相当于每个样本被相邻窗口重复使用数据量增加一倍且步态周期的起点无论落在哪里总有一个窗口能完整包含关键相位。def make_windows(df, win_len128, stride64): X [] data df[[acc_x, acc_y, acc_z, gyr_x, gyr_y, gyr_z]].values for i in range(0, len(data) - win_len, stride): X.append(data[i:iwin_len]) return np.array(X) # 使用示例 # windowed make_windows(filtered_df) # windowed.shape - (N, 128, 6) # 最后一个不完整窗口通过 truncation 丢弃逻辑说明代码严格按 stride 滑动窗口长度 128、步长 64 就形成 50% 重叠。输出维度是(N, 128, 6)六个通道分别对应三轴加速度和三轴陀螺仪后续 DeepConvLSTM 把这个三维张量当输入。注意这里没有做窗口级归一化原因是原始加速度数值单位固定模型能从幅值本身学习步态强弱的差异。数据增强方面常见做法是叠加轻度高斯噪声标准差取原始信号标准差的 0.05 倍再做一次 0.97–1.03 倍的时间轴缩放。帕金森患者的步频变异大时间缩短和拉长能增加模型对步频漂移的鲁棒性。但幅度增强要克制过度缩放会把步频信息完全破坏反而导致训练集和测试集分布脱节。另外所有增强操作必须在数据划分之后进行否则同一受试者的窗口同时出现在训练和验证集指标会虚高。3. 步态特征提取与标签工程从原始序列到有效样本的取舍DeepConvLSTM 本身具备自动特征提取能力但如果完全不做特征工程训练过程容易走偏。标签工程的质量直接决定分类上限帕金森患者的步态标签往往来自临床量表监督信息稀疏且带噪声。这一章先讲步态周期检测再给出时域频域特征作为基准基线最后重点讨论标签对齐和类别平衡。3.1 步态周期检测与峰值定位步态周期是分析的基础单位一个周期从同一脚跟触地开始到下一次触地结束。在腰部垂直加速度信号上每个左脚或右脚跨步都会形成一个明显的波峰。通过峰值检测可以计算步频和周期变异性帕金森患者的步频通常更慢周期变异系数更高。对于冻结步态垂直加速度波形振幅会骤降峰值间隔变混乱检测算法自然会把这段区域暴露出来。from scipy.signal import find_peaks def detect_steps(vertical_acc, fs100.0, height0.4, dist20): # 只看垂直轴加速度序列 peaks, _ find_peaks(vertical_acc, heightheight, distancedist) # distance20 表示两峰之间至少间隔 20 个点即 0.2 秒 intervals np.diff(peaks) / fs return peaks, intervals # 示例输出 # peaks, intervals detect_steps(filtered_df[acc_z].values) # 正常步频 intervals.mean() 约 1.1s对应步频0.9Hz # 冻结步态 intervals.std() 会显著变大伴随小尖峰簇逻辑说明find_peaks的参数height用于滤掉噪音小峰distance控制最小间隔。帕金森患者正常行走时的垂直加速度峰值通常在 0.5g–1.5g 左右冻结期峰值会掉到 0.2g 以下。设置height0.4能在大多数场景下区分真正的脚跟着地和前庭噪声。如果你处理脚踝数据峰值形态会变成双向尖峰这个阈值需要下探到 0.2。周期检测不只是为了报告步频它还能指导标签切分。有时临床标注只给出“冻结”“正常”的起始时间点我通常会根据检测到的步态周期把标签边界修整到最近的峰值点避免把同一个步态周期切断后分到两个类别。这一步对序列模型尤其重要因为标签对齐误差超过半个窗口长度后模型接收到的输入输出配对就是错的。3.2 时域与频域特征提取虽然深度学习模型不需要手工特征作为输入但构建特征基线能帮你在训练过程中诊断模型有没有退化。比如在验证集上如果 RandomForest 的准确率超过 DeepConvLSTM说明模型结构或超参设置有问题。常见做法是把原始窗口压缩成一组统计特征再喂给传统分类器例如均值、标准差、峰峰值、过零率、FFT 主频和频谱峰值。这里给出特征提取函数def extract_features(window): 输入 window: (128, 6)输出 (6*N_features,) feats [] for ch in range(window.shape[1]): sig window[:, ch] feats.append(np.mean(sig)) feats.append(np.std(sig)) feats.append(np.max(sig) - np.min(sig)) feats.append(np.mean(np.diff(sig 0).astype(int))) # 过零率 spectrum np.abs(np.fft.rfft(sig)) freq_idx np.argmax(spectrum[1:]) 1 feats.append(freq_idx) feats.append(spectrum[freq_idx]) return np.array(feats) # 对所有窗口执行 X_feat np.array([extract_features(w) for w in windowed]) # 再用 sklearn 的 RandomForestClassifier 做基线逻辑说明六个通道每个都提取以下六个特征均值、标准差、峰峰值、符号过零率、FFT 峰值索引和峰值幅值最终得到一个 36 维特征向量。过零率能表达信号的波动密集程度帕金森患者的冻结步态会伴随高频抖动从而让过零率升高。频域主频索引直接对应步频适合做领域解释。特征基线的价值在于解释和验证。如果你在报告中需要向医生解释“为什么模型认为这段是冻结态”手工特征能映射到步频和变异性。而 DeepConvLSTM 的自由参数量过大很难给出清晰的因果解释。因此很多医疗辅助项目会并行训练一个浅层模型作为深度模型的审计工具。3.3 标签工程与类别平衡标签是医疗项目里最容易翻车的部分。临床标签可能来自医生的视频标注也可能来自可穿戴邻里设备的事件记录要么“只有整段序列标签”要么“标注了冻结段起止但精度只到秒级”。我们需要把标签映射到 128 点窗口上并评估每一步的误差。最稳妥的做法是先绘制原始波形和标签起止线对比后确定标签步长。def assign_labels(windows, label_timestamps, fs100.0): 把时间戳标签映射到每个窗口的起始帧 win_len windows.shape[1] labels [] for i in range(windows.shape[0]): start_t i / fs end_t (i win_len) / fs # 只要标签区间与窗口有超过一半重叠就打上标签 hit False for (start_label, end_label) in label_timestamps: if start_t end_label or end_t start_label: continue overlap min(end_t, end_label) - max(start_t, start_label) if overlap (end_t - start_t) * 0.5: hit True break labels.append(1 if hit else 0) return np.array(labels)逻辑说明assign_labels遍历每个窗口计算窗口与目标异常段的交集占比。占比超过 50% 时该窗口才被标记为正样本能避免只擦到标签边缘的窗口污染样本。这里以秒为单位你可以根据你的原始标签格式替换label_timestamps结构。窗口重叠带来的副作用是相邻窗口往往共享标签模型训练时出现过拟合的风险更高所以之后的数据划分必须按受试者隔离而不是按窗口随机划分。类别不平衡在帕金森步态数据里几乎是必然的。一个受试者正常行走 5 分钟能切出 200 多个窗口但冻结步态可能只持续 15 秒只有 30 个窗口。此时模型会倾向把全部样本预测成正常类。解决方向有三个第一用类别加权交叉熵让正样本的错误惩罚放大第二欠采样多数类但要保证每个受试者至少保留一部分正常窗口第三合成少数类样本但对时序数据做 SMOTE 很危险因为它会破坏相邻点之间的时间关系。我用得最多的是类别加权后面模型训练一章会给出具体权重的配置方法。4. 从序列到分类DeepConvLSTM 模型结构与训练实践DeepConvLSTM 是传感器领域公认好用的端到端架构。卷积层负责抽取局部步态模式例如脚跟触地瞬间的加速度冲击LSTM 层负责处理这些模式的时间顺序慢步、大步、碎步、冻步之间的前后关系。对于帕金森病诊断任务我们不只关心单个瞬间还关心连续几步之间的节奏变化这正是 LSTM 的用武之地。4.1 模型结构选型卷积层与 LSTM 层的分工常见做法是把卷积层堆到两层以上每一层都做时间维度的局部感受野。第一个卷积层看 5 个时间点内的运动突变第二层看到更长的脉冲突发。在经典 DeepConvLSTM 设计中卷积之后不直接池化而是保留完整时序输出再接 LSTM。这是因为池化会丢失精确时间点而 LSTM 需要依赖帧级分辨率判断步态异常发生在哪一瞬间。与纯 LSTM 相比卷积层能把 128 点序列压缩成抽象特征图从而减少 LSTM 需要处理的时间步数。与纯 CNN 相比LSTM 能对步频变化和反复停顿建立长期记忆。实践观察是在 6 通道传感器数据上DeepConvLSTM 比纯 CNN 性能高 4–8%比纯 LSTM 稳定得多训练收敛也更快。如果你的训练集只有几十人我建议把卷积层数降到一层滤波器数从 32 开始避免模型带不动小样本。4.2 Keras 实现与训练配置下面直接给出可运行的 Keras 模型。输入维度是(窗口长度, 通道数)也就是(128, 6)。输出有两条路线帕金森病诊断是二分类正常/异常如果是多分类任务比如“正常/冻结/拖步/失衡”则把最后的 Dense 层单元数改成 4激活函数保留 softmax。医疗任务更关心灵敏度和特异性不要在 compile 里只监控准确率。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Conv1D, ReLU, LSTM, Dropout, Dense, Input from tensorflow.keras.optimizers import Adam def build_deepconvlstm(input_shape(128, 6), n_classes2): model Sequential([ Input(shapeinput_shape), Conv1D(filters32, kernel_size5, paddingsame), ReLU(), Conv1D(filters64, kernel_size5, paddingsame), ReLU(), LSTM(units96, return_sequencesFalse), Dropout(0.5), Dense(32, activationrelu), Dense(n_classes, activationsoftmax) ]) model.compile(losscategorical_crossentropy, optimizerAdam(learning_rate1e-3), metrics[accuracy]) return model # 训练时调用 # model build_deepconvlstm() # model.fit(X_train, y_train_onehot, validation_data(X_val, y_val_onehot), # batch_size64, epochs100)逻辑说明第一层 32 个 1D 卷积核第二层 64 个卷积核kernel_size 都是 5等于每个卷积核只看 5 个连续时间点能够捕捉 0.05 秒内的运动突变而两个卷积堆叠后有效感受野扩大到 9 个时间点足够覆盖单步的冲击峰。LSTM 单元数取 96 而不是 128是为小样本数据留出安全性减少参数量。Dropout 率 0.5 是序列模型的默认值如果验证集 loss 波动大可降到 0.3。训练超参数是我拆完这个项目后最看重的一部分。学习率1e-3是初值但是步态数据往往噪声大我会在第 20 个 epoch 后自动降到1e-4也就是用 ReduceLROnPlateau。batch_size 建议 32–64太小损失曲线乱跳太大则每个 batch 里可能只有 1 个冻结样本模型学不到正样本梯度。epochs 挂 100配合早停。early stopping 的 patience 要谨慎不要设太大。我遇到过 patience20 时模型反复回升又下穿最后停掉的模型反而不是最优的。稳妥做法是用验证集 F1 作为监控指标并恢复最佳权重。这里附上扬和新训练代码from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau callbacks [ EarlyStopping(monitorval_loss, modemin, patience15, restore_best_weightsTrue), ReduceLROnPlateau(monitorval_loss, factor0.5, patience5, min_lr1e-5), ModelCheckpoint(best_model.keras, monitorval_loss, save_best_onlyTrue) ] history model.fit(X_train, y_train_onehot, validation_data(X_val, y_val_onehot), batch_size64, epochs100, callbackscallbacks, verbose1)逻辑说明ReduceLROnPlateau 在验证 loss 连续 5 个 epoch 不降时把学习率乘 0.5相当于自动细化搜索。EarlyStopping 的 patience15 给足回调空间同时用restore_best_weights确保 final model 的权重不是最后一次 epoch 的权重。ModelCheckpoint保存的是验证 loss 最小的模型对于模型文件命名我建议带上日期和窗口参数例如deepconvlstm_win128_stride64_20250214.keras方便做实验追溯。4.3 损失函数与类别权重二分类用 categorical_crossentropy 的前提是把标签做成 one-hot 向量。但如果类别不平衡严重直接算交叉熵会让模型偏科。常见做法是在model.fit里传class_weight参数自动放大少数类的梯度。计算方式很简单def compute_class_weight(labels): classes np.unique(labels) n len(labels) w {c: n / (len(classes) * np.sum(labels c)) for c in classes} return w # 示例 # class_weight compute_class_weight(y_train_origin) # model.fit(X_train, y_train_onehot, class_weightclass_weight)逻辑说明compute_class_weight返回一个字典比如{0: 0.45, 1: 3.2}正样本的梯度放大 3.2 倍训练时模型会更激进地捕捉异常步态。这比简单欠采样保留更多真实样本的时序信息。如果你用TensorFlow的compile加载自定义指标医生可能更关心sensitivity召回率和specificity这些指标需要从预测结果和标签绘制混淆矩阵后计算我推荐把这个评估过程独立成脚本而不是塞进训练回调里。5. 避坑/常见问题步态识别项目里最容易翻车的六个环节我把在拆项目过程中踩过的坑梳理成六个高频问题每一条都按“现象→原因→解决”来写。这些问题在真实穿戴设备数据里很难躲开提前看完能省掉好几天排错时间。5.1 佩戴位置不一致导致数据分布漂移现象模型在第一批志愿者身上准确率 92%换到第二批数据只有 62%。原因第一批志愿者把传感器绑在腰部第二批很多人嫌不舒服改成手腕佩戴六轴信号的方向参考系变了模型学到的局部模式完全错位。解决在数据采集协议里强制只有一种佩戴位置或者把佩戴位置作为额外分类标签加入训练。如果前期数据已经混错至少按佩戴位置拆分并分别训练两个模型不要指望同一个模型通吃。5.2 窗口的物理时长不一致现象把 128 点窗口直接送入模型验证集 loss 不下降。原因不同传感器文件采样率不同有的 100Hz有的 120Hz同一窗口长度覆盖的时间跨度相差 20% 以上。解决在预处理流程中加入重采样步骤强制所有文件统一到 100Hz并在加载数据时断言采样点数 时长 * 100。我在传参脚本里加了一个检查任何不满足条件的数据直接拒绝入队避免静默失败。5.3 标签与数据错位现象训练后的模型对冻结步态的识别总是延迟 2–3 秒而且误报点集中在真实冻结段之前。原因临床标注的起止时间与传感器时间戳没有对齐可能相差一个秒级也可能是滑动窗口把标签滞后了半个窗口长度。解决把原始波形和标签线画在同一张图上逐段人工检查时间差。一旦发现标签整体偏移计算偏移量并修正后再切窗。这个经验很难自动化先把 3–5 个典型受试者可视化跑通再交给训练流程。5.4 类别不平衡被忽略现象准确率很高F1 分数极低模型把所有窗口都预测成正常类。原因正常步态窗口占 90%多数类主导梯度少数类样本太少。解决使用class_weight把少数类权重放大并切换到AUC或F1作为模型选择标准。我一般会打印每个 batch 里正样本窗口数量如果平均值低于总样本的 20%就调整权重或做线下采集。5.5 数据归一化造成泄漏现象训练集准确率 95%验证集只有 65%反复调整也没解决。原因我先计算整个数据集的均值和方差再统一归一化导致验证集信息泄入训练集模型在训练时已经“见过”验证集的分布而验证性能是被高估的。解决先按受试者划分训练集和验证集再各自用训练集的均值和方差做归一化。滑动窗口特征也一样任何归一化参数只能从训练集计算。5.6 长序列样本训不出来且容易 OOM现象窗口长度拉到 512 点batch_size 128显存直接溢出。原因LSTM 的时间步数和显存占用成正比序列越长中间状态越庞大。解决最优窗口不一定是越长越好先用 128 点观察模型能力再逐步拉长到 256 点同时把 LSTM 单元数降到 64或把 batch_size 减半。另一种有效方案是让卷积层加上stride2把时序长度压缩一半后再送入 LSTM保留关键信息的同时减少计算量。6. 验证与性能调优交叉验证、混淆矩阵与边端部署这一章聊可不轻易被训练集指标欺骗的验证方式以及模型最终落地到可穿戴设备时的压缩手段。代码先看交叉验证再看 TF Lite 转换。临床项目里提交结果时只用一份测试集跑一次其他做验证防止反复调参把验证集也“训坏了”。6.1 按受试者分组的交叉验证普通KFold会把同一个患者的相邻窗口分到训练和验证中造成数据泄漏。我要提醒你步态窗口之间存在很强的自相关性同一个受试者 5 分钟数据里的窗口高度相似如果不按受试者分组验证集指标会乐观到离谱。正确做法是用GroupKFold每组是一个受试者验证时才不会看到同一个人的其它窗口。from sklearn.model_selection import GroupKFold from sklearn.metrics import confusion_matrix, f1_score, sensitivity_score # X_windows: (N, 128, 6), y_labels: (N,), groups: (N,) 每个窗口对应受试者ID gkf GroupKFold(n_splits5) f1_scores [] for train_idx, val_idx in gkf.split(X_windows, y_labels, groupsgroups): model build_deepconvlstm() model.fit(X_windows[train_idx], y_onehot[train_idx], epochs30, batch_size32, verbose0) preds model.predict(X_windows[val_idx]).argmax(axis1) f1_scores.append(f1_score(y_labels[val_idx], preds, averageweighted)) print(np.mean(f1_scores), np.std(f1_scores))逻辑说明GroupKFold分组时按照groups参数生成折保证每个受试者的所有窗口只在同一个折里。每组要保证所有受试者的窗口不穿插。脚本里只打印了f1_scores的均值和标准差训练时建议继续打印混淆矩阵。我记得第一次跑的时候未分组交叉验证 F1 有 0.91分组后立刻掉到 0.72这才是真实水平。混淆矩阵要按类别展开不要只算整体准确率。真实场景中正常步态多、冻步少模型可能在正常类上近乎完美在冻结类上几乎全错。我通常在报告里列一张混淆矩阵表行数代表真实类别列数代表预测类别并分别标出敏感度和特异性。在医疗语义下漏掉一次冻结步态比多报一次更严重所以灵敏度至少要保持在 90% 以上才有临床参考价值。6.2 模型量化与便携部署步态识别最终目标往往是放在移动设备或边缘硬件上模型不能太大。常见的做法是先把 Keras 模型转成 TensorFlow Lite再用整数量化把权重从 32 位降到 8 位体积能缩小到原来的四分之一推理速度也更快。转换代码很简单但要注意输入输出张量的 shape 必须与训练时严格一致。import tensorflow as tf converter tf.lite.TFLiteConverter.from_keras_model(model) converter.optimizations [tf.lite.Optimize.DEFAULT] converter.target_spec.supported_types [tf.float16] tflite_model converter.convert() with open(gait_model.tflite, wb) as f: f.write(tflite_model)逻辑说明tf.lite.Optimize.DEFAULT会让转换器自动做权重量化这里我选了 float16 精度而不是 int8因为可穿戴设备传感器数据本身带噪声float16 能保留更多动态范围相对更稳。如果设备端推理能力受限再降成 int8并用测试集重新评估一遍 F1确定精度损失可接受后再上线。转换后模型文件大小一般小于 1MB部署到树莓派或手机都能跑起来。调优的最后一步是核查模型边界。我会做一个极端实验把同一段静态坐着的传感器数据伪装成行走数据送入模型理想输出应该是“正常”概率接近 0.5而不是被强行判成某种异常。如果模型连这种明显错误都能给出高置信度预测说明训练集里缺少负样本段需要补充数据而不是继续调参。从那以后我做任何步态识别项目都会严格按受试者分组验证并在启动训练前先画一个受试者的波形与标签对齐图确认没有错位才往下走。这个习惯帮我避开了大量无效迭代也让每次实验结论都站得住脚。希望帮到你。本文还有配套的精品资源点击获取