☰
肌电信号分类数据集与代码:从预处理到SVM基线实战
2026/9/26 16:46:26 网站建设 项目流程

简介:这份资源面向生物医学工程、康复医学与人机交互方向的学习者和研究者,围绕表面肌电信号(sEMG)分类任务,提供从特征抽取到分类器评估的完整实践材料。压缩包共7个文件,以6个Python脚本和1个数据压缩包为主,整体约14.8MB,脚本分别对应主成分分析、支持向量机递归消除法、相关性热力图等特征处理方法,以及KNN、SVM、随机森林三种分类算法的实现,数据包则用于训练与测试。已有537人学习下载,说明其在sEMG机器学习入门与实验中具有一定参考价值。读者可借助这些代码理解PCA降维去噪、SVM-RFE特征筛选与相关性热力图去冗余的思路,并对比不同分类器在小样本、高维肌电数据上的表现,从而搭建可复用的分类流程,应用于假肢控制、运动分析或医疗诊断等场景。

1. 从一段手臂信号到可训练矩阵:这份 sEMG 分类资源到底能干什么

如果你手上只有一段从肌电采集设备导出的原始信号,想把它变成能跑机器学习分类器的训练集,中间那层「怎么切、怎么标、怎么提特征」的活儿,往往比调模型本身更耗时间。这份「肌电信号分类数据集及相关代码」对应的就是这条链路:它把表面肌电(sEMG)信号按手势/动作类别整理成带标签的样本,并配套了从信号读取、预处理、特征提取到分类器训练与评估的代码。适合两类人:一类是做康复辅具、假肢控制、人机交互方向,需要快速拿到一份能跑通的基准数据来验证算法;另一类是刚接触 sEMG 机器学习分类算法,想找一个结构清晰、能逐行读懂特征工程与分类流程的工程模板。它不解决采集硬件问题,也不承诺某个模型一定刷到多高准确率,但能让你把精力放在「分类算法怎么选、参数怎么调」上,而不是卡在数据格式上。

2. 数据组织与信号预处理:先把原始 sEMG 变成规整样本

2.1 先搞清楚这份数据集的文件结构

sEMG 数据集和图像数据集不一样,它通常不是一个文件夹里塞满图片,而是按「受试者 / 动作类别 / 试次」分层的信号文件。常见做法是每个试次存成一个文本或 CSV,列是通道,行是采样点。拿到手第一件事不是急着训练,而是先确认三件事:采样率是多少、通道数是多少、每个动作重复了几次。采样率决定了后面加窗的长度,通道数决定了特征维度,重复次数决定了你能做几折交叉验证。

我一般会先写一段脚本把目录扫一遍,统计每个类别的文件数和每个文件的形状,避免后面训练时才发现某个类别样本少得可怜。这一步看起来笨,但能省掉后面大量返工。

import os import numpy as np DATA_ROOT = "./emg_dataset" # 数据集根目录,按类别分子文件夹 summary = {} for label in sorted(os.listdir(DATA_ROOT)): label_dir = os.path.join(DATA_ROOT, label) if not os.path.isdir(label_dir): continue shapes = [] for fname in os.listdir(label_dir): if not fname.endswith(".csv"): continue arr = np.loadtxt(os.path.join(label_dir, fname), delimiter=",") shapes.append(arr.shape) # (采样点, 通道数) summary[label] = shapes print(f"{label}: {len(shapes)} 个试次, 首个形状 {shapes[0] if shapes else '空'}")

这段代码只做一件事:遍历每个类别目录,读入 CSV 并打印形状。逻辑上它帮你确认「采样点 × 通道」的维度是否一致。参数上,delimiter要和你数据实际的分隔符对上,有的设备导出是制表符,那就改成\t。如果某个类别打印出「空」,说明文件后缀或目录层级和预期不符,先解决这个再往下走。

2.2 分窗、去均值与滤波:别让直流漂移毁掉特征

原始 sEMG 信号直接整段丢给分类器基本没意义,因为动作是连续发生的,你需要把它切成一个个短时窗,每个窗当作一个样本。常见做法是窗长 150~250 ms、重叠 50%,这个范围在肌电手势识别里被反复验证过。窗太短,频谱估计不稳;窗太长,动作切换的边界会被糊在一起。

切窗之前还有两步不能省:去直流和带通滤波。sEMG 的有效频段大致在 20~450 Hz,工频干扰集中在 50 Hz(国内)附近,所以一个 20~450 Hz 的带通加一个 50 Hz 陷波是常规组合。下面这段是分窗加预处理的骨架:

from scipy.signal import butter, filtfilt, iirnotch FS = 1000 # 采样率,按你数据实际值改 WIN_MS = 200 # 窗长 200ms OVERLAP = 0.5 # 50% 重叠 CHANNELS = 8 # 通道数 def bandpass(sig, fs, low=20, high=450, order=4): nyq = fs / 2 b, a = butter(order, [low / nyq, high / nyq], btype="band") return filtfilt(b, a, sig, axis=0) # 零相位滤波,避免时延 def notch(sig, fs, freq=50, q=30): b, a = iirnotch(freq / (fs / 2), q) return filtfilt(b, a, sig, axis=0) def segment(sig, fs, win_ms, overlap): win = int(fs * win_ms / 1000) step = int(win * (1 - overlap)) windows = [] for start in range(0, len(sig) - win + 1, step): windows.append(sig[start:start + win]) return np.array(windows) # (窗数, 窗长, 通道) raw = np.loadtxt("./emg_dataset/grasp/trial_01.csv", delimiter=",") clean = notch(bandpass(raw, FS), FS) clean = clean - clean.mean(axis=0) # 去均值,压掉直流漂移 wins = segment(clean, FS, WIN_MS, OVERLAP) print(wins.shape)

逻辑说明:filtfilt做的是零相位滤波,比单向lfilter更适合离线特征提取,因为它不会引入相位偏移,代价是计算量翻倍,但离线场景无所谓。segment用滑动步长控制重叠率,step = win * (1 - overlap),重叠 0.5 就是每次挪半个窗。参数上,FS必须和采集设备一致,填错会让滤波截止频率整体偏移,特征全废。order一般取 4,太高容易数值不稳,太低过渡带太宽。

提示:如果你的数据里不同试次长度差异很大,分窗后样本数会不均衡,后面训练时要么按类别下采样,要么用类别权重补偿,别直接堆进去。

3. 特征提取与分类器选型:时域、频域还是直接上深度模型

3.1 手工特征:RMS、MAV、WL 与过零率的组合拳

sEMG 分类里,手工特征至今仍是轻量方案的首选,因为计算快、可解释、对小样本友好。最常用的四个时域特征:均方根(RMS)反映能量,平均绝对值(MAV)反映强度,波形长度(WL)反映信号复杂度,过零率(ZC)反映频率特性。把它们按通道拼起来,一个 8 通道的窗就能得到 32 维特征向量。频域上还可以加中值频率(MDF)和平均功率频率(MPF),但对短窗来说频域估计方差大,我一般时域为主、频域为辅。

def extract_features(window): # window: (窗长, 通道) feats = [] for ch in range(window.shape[1]): x = window[:, ch] rms = np.sqrt(np.mean(x ** 2)) mav = np.mean(np.abs(x)) wl = np.sum(np.abs(np.diff(x))) zc = np.sum(np.diff(np.sign(x)) != 0) feats.extend([rms, mav, wl, zc]) return np.array(feats) # 长度 = 通道数 × 4 X = np.array([extract_features(w) for w in wins]) print(X.shape) # (样本数, 32)

逻辑说明:每个通道独立算四个特征再拼接,这样特征维度和通道数线性相关,换设备时只需改CHANNELS。zc用符号变化次数近似,严格定义还要加阈值死区来抑制噪声引起的伪过零,实际工程里加一个abs(x) > threshold判断会更稳。参数上,threshold一般取信号标准差的 0.01~0.05 倍,太小会把噪声算进去,太大又会漏掉真实过零。

3.2 分类器怎么选:从 SVM 到 LSTM 的取舍

拿到特征矩阵后,选分类器是第二个分水岭。样本量小(每类几十到几百)时,SVM 配 RBF 核是稳妥起点,它对高维小样本友好,调参也就C和gamma两个。随机森林次之,几乎不用调参,还能输出特征重要性,方便你回头看哪些通道贡献大。样本量上千之后,可以上 1D-CNN 或 LSTM 直接吃原始窗,省掉手工特征,但代价是训练时间和数据需求都上去了。

方案适用样本量调参成本可解释性典型场景
SVM + RBF小低中快速基线
随机森林小到中极低高特征筛选
1D-CNN中到大中低端到端
LSTM中到大高低时序建模

我一般先用 SVM 跑一个基线,记住这个数,后面换任何模型都以它为参照。如果深度模型跑不过 SVM,八成是数据量不够或者窗切得不对,而不是模型不行。

from sklearn.svm import SVC from sklearn.model_selection import cross_val_score from sklearn.preprocessing import StandardScaler from sklearn.pipeline import make_pipeline y = np.array([...]) # 每个窗对应的类别标签,长度与 X 一致 clf = make_pipeline(StandardScaler(), SVC(C=10, gamma="scale", kernel="rbf")) scores = cross_val_score(clf, X, y, cv=5, scoring="accuracy") print("5 折准确率:", scores.mean(), scores.std())

逻辑说明:StandardScaler必须放在 SVM 前面,因为 RBF 核基于距离,特征量纲不统一会让大数值特征主导。C控制惩罚强度,越大越容易过拟合;gamma控制核宽度,scale是 1/(特征数×方差) 的自动估计,通常够用。cv=5做五折交叉验证,比单次划分更能反映真实泛化。参数上,如果准确率方差很大,说明样本分布不均或折数太少,可以提到 10 折再看。

注意:交叉验证一定要在分窗之后、按窗随机划分,不能按试次划分后再分窗,否则同一试次的窗会同时出现在训练和验证集里,准确率虚高,这是血泪经验。

4. 避坑与排查:sEMG 分类里最容易翻车的五个地方

4.1 现象:准确率异常高,接近 100%

原因:数据泄漏。最常见的是先分窗再划分数据集时,同一试次的相邻窗高度重叠,训练集和验证集里出现了几乎相同的样本。另一个来源是标准化时用了全量数据的均值方差,把验证集信息泄进了训练。

解决:按试次划分训练/验证集,再在各自内部独立分窗和标准化。用GroupKFold按试次分组,保证同一试次不跨折。

4.2 现象:换一个受试者,准确率断崖式下跌

原因:受试者之间的信号幅度、阻抗、电极位置差异很大,模型学到了某个人的特异性模式。这是 sEMG 的固有难点,不是代码 bug。

解决:做跨受试者验证时,训练集和测试集必须来自不同人。想提升泛化,可以加归一化(按试次做 z-score)、数据增强(加噪声、时间缩放),或者用对抗训练拉平受试者分布。

4.3 现象:滤波后信号反而更差,特征区分度下降

原因:截止频率设错。比如采样率是 1000 Hz,你把带通上限设成 500 Hz,接近奈奎斯特频率,滤波器在边界处不稳定,输出失真。或者陷波频率没对上实际工频。

解决:带通上限留出余量,一般不超过采样率的 40%。先画一下滤波前后的频谱对比,确认有效频段没被削掉。

4.4 现象:训练时 loss 不降,或者降了但验证集不动

原因:标签和特征没对齐。分窗后样本数变了,标签如果还是按原始试次给的,长度就对不上,或者顺序错位。

解决:分窗函数返回窗的同时返回对应标签,确保一一对应。训练前打印X.shape[0]和y.shape[0],不相等就直接报错,别让它静默跑下去。

4.5 现象:模型在本地跑得好,换台机器结果对不上

原因:随机种子没固定,或者库版本差异导致数值行为不同。sklearn 的random_state、numpy 的种子、甚至 BLAS 后端都会影响结果。

解决:在脚本开头统一设种子,记录库版本。对精度要求高的对比实验,固定random_state并写进配置文件。

import numpy as np import random def set_seed(seed=42): np.random.seed(seed) random.seed(seed) # 如果用了深度学习框架,这里再加对应框架的种子设置 set_seed(42)

逻辑说明:种子固定后,数据划分、模型初始化、任何带随机的步骤都可复现。参数上,seed随便选,但一旦选定就别改,否则前后实验没法对比。

5. 进阶技巧:用交叉验证曲线判断该加数据还是换模型

跑通基线之后,最常被问的问题是「准确率上不去了,是该标更多数据还是换个模型」。我的习惯是画一条学习曲线,横轴是训练样本比例,纵轴是训练集和验证集准确率。如果两条线都低且贴在一起,是欠拟合,换更强模型或加特征;如果训练高、验证低且差距大,是过拟合,加数据或加正则;如果验证线还在上升,说明数据不够,继续标。

from sklearn.model_selection import learning_curve import matplotlib.pyplot as plt train_sizes, train_scores, val_scores = learning_curve( clf, X, y, cv=5, scoring="accuracy", train_sizes=np.linspace(0.1, 1.0, 8), random_state=42 ) plt.plot(train_sizes, train_scores.mean(axis=1), label="训练集") plt.plot(train_sizes, val_scores.mean(axis=1), label="验证集") plt.xlabel("训练样本数") plt.ylabel("准确率") plt.legend() plt.show()

逻辑说明:learning_curve会自动按不同比例抽样训练集并评估,train_sizes控制采样点。看两条线的收敛趋势比看单一数字有用得多。参数上,cv建议和最终评估保持一致,random_state固定保证可复现。

还有一个容易被忽略的点:特征重要性。随机森林训练完可以直接看哪些通道、哪些特征贡献大。如果发现某几个通道几乎没贡献,可能是电极没贴好或者那个通道坏了,回头检查采集环节比调模型更值。我一般会把这个排序打印出来,对着通道位置图看一遍,经常能发现硬件层面的问题。

从那以后我每次拿到新的 sEMG 数据,都强制先跑一遍「扫目录 → 看形状 → 画频谱 → 跑 SVM 基线」这四步,再谈深度模型。这套流程帮我省掉过很多次在错误数据上瞎调参的时间。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询