Python实战:从MIT-BIH原始数据到AAMI标准5分类数据集的完整构建与解析
·
1. MIT-BIH数据库基础与数据准备
MIT-BIH心律失常数据库是心电信号研究领域的黄金标准,包含48条双导联动态心电图记录,每条记录时长约30分钟,采样频率为360Hz。原始数据采用WFDB格式存储,包含三个关键文件:
.hea:头文件(记录元数据).dat:二进制信号数据.atr:人工标注文件(包含心拍类型和位置)
实战第一步:安装必要工具库
pip install wfdb numpy matplotlib
读取单条记录的完整示例:
import wfdb
import matplotlib.pyplot as plt
# 读取记录100(自动识别.hea/.dat/.atr)
record = wfdb.rdrecord('mit-bih-arrhythmia-database-1.0.0/100', sampfrom=0, sampto=1000)
annotation = wfdb.rdann('mit-bih-arrhythmia-database-1.0.0/100', 'atr')
# 可视化信号和标注
plt.plot(record.p_signal[:,0])
for sample in annotation.sample:
plt.axvline(x=sample, color='r', linestyle='--')
plt.show()
关键参数解析:
sampfrom/sampto:控制读取的样本范围(避免内存溢出)physical:True返回实际电压值(mV),False返回原始数字信号channels:选择导联(MLII或V1)
2. 心拍定位与原始标签解析
MIT-BIH数据库包含超过10万个心拍,标注了15种心律失常类型。通过 annotation.symbol 可获取每个R波对应的具体类型:
# 常见心拍类型对照表
label_map = {
'N': 'Normal',
'L': 'Left bundle branch block',
'R': 'Right bundle branch block',
'V': 'Premature ventricular contraction',
'A': 'Atrial premature beat'
}
# 提取前100个标注点
print([(samp, sym) for samp, sym in zip(annotation.sample[:100], annotation.symbol[:100])])
R波定位技巧:
- 使用
annotation.sample获取所有R波位置 - 排除非心拍标注(如噪声标记'~')
- 以R波为中心截取信号段(通常取R点前后各130个样本点)
3. AAMI标准五分类转换
根据ANSI/AAMI EC57:2012标准,将原始15类合并为5大类:
aami_map = {
'N': ['N', 'L', 'R', 'e', 'j'], # 正常/束支阻滞
'S': ['A', 'a', 'J', 'S'], # 室上性异常
'V': ['V', 'E'], # 室性异常
'F': ['F'], # 融合波
'Q': ['/', 'f', 'Q'] # 未知/噪声
}
def convert_to_aami(symbol):
for aami_class, original_labels in aami_map.items():
if symbol in original_labels:
return aami_class
return 'Q' # 默认归为Q类
数据分布典型问题:
- 类别极度不均衡(N类占比超80%)
- 部分患者记录存在特定类型集中现象
- 需注意排除起搏心拍(标注为'P')
4. 心拍分割与数据集构建
完整处理流程代码:
import numpy as np
from tqdm import tqdm
def extract_beats(record_name, window_size=260):
"""从单条记录提取心拍"""
record = wfdb.rdrecord(f'mit-bih-arrhythmia-database-1.0.0/{record_name}')
ann = wfdb.rdann(f'mit-bih-arrhythmia-database-1.0.0/{record_name}', 'atr')
# 筛选有效R波标注
valid_symbols = ['N', 'L', 'R', 'V', 'A', 'F', 'Q', '/']
beats = []
labels = []
for i, symbol in enumerate(ann.symbol):
if symbol not in valid_symbols:
continue
pos = ann.sample[i]
if pos < window_size//2 or pos > len(record.p_signal)-window_size//2:
continue
beat = record.p_signal[pos-window_size//2 : pos+window_size//2, 0]
beats.append(beat)
labels.append(convert_to_aami(symbol))
return np.array(beats), np.array(labels)
# 处理所有48条记录
all_beats = []
all_labels = []
for record_id in tqdm(['100', '101', ..., '234']):
beats, labels = extract_beats(record_id)
all_beats.append(beats)
all_labels.append(labels)
关键参数优化建议:
window_size:260点(约722ms)能完整包含QRS-T波- 通道选择:优先使用MLII导联(信号质量更稳定)
- 归一化:建议采用每心拍单独Z-score标准化
5. 数据集存储与加载优化
HDF5存储方案(推荐):
import h5py
with h5py.File('mitbih_aami.h5', 'w') as hf:
hf.create_dataset('beats', data=np.vstack(all_beats))
hf.create_dataset('labels', data=np.concatenate(all_labels))
hf.attrs['sampling_rate'] = 360
hf.attrs['window_size'] = 260
CSV备用方案:
import pandas as pd
df = pd.DataFrame(np.vstack(all_beats))
df['label'] = np.concatenate(all_labels)
df.to_csv('mitbih_aami.csv', index=False)
内存映射技巧:
# 处理超大数据集时使用
X = np.memmap('beats.dat', dtype='float32', mode='r', shape=(len(all_beats), 260))
y = np.memmap('labels.dat', dtype='int8', mode='r', shape=(len(all_labels),))
6. 实际应用中的陷阱与解决方案
常见问题1:标注偏移
- 现象:R波标注与实际R峰位置偏差
- 解决方案:动态搜索±5个样本点的最大值
def adjust_peak(signal, pos, search_window=5):
return pos + np.argmax(signal[pos-search_window:pos+search_window]) - search_window
常见问题2:导联差异
- 现象:不同记录使用不同导联组合
- 解决方案:统一使用MLII导联,缺失时用V1替代
性能优化技巧:
- 批量读取:使用
wfdb.rdrecord的physical=False加速 - 并行处理:用
joblib并行处理多条记录
from joblib import Parallel, delayed
results = Parallel(n_jobs=4)(delayed(extract_beats)(rid) for rid in record_ids)
7. 进阶处理:数据增强与平衡
针对类别不平衡问题,采用滑动窗口增强少数类:
def augment_beat(beat, num_aug=5):
"""通过滑动窗口生成增强样本"""
augmented = []
for i in range(num_aug):
shift = np.random.randint(-10, 10)
augmented.append(np.roll(beat, shift))
return augmented
# 对少数类样本增强
v_beats = beats[labels == 'V']
augmented = [augment_beat(b) for b in v_beats]
最终得到的数据集应包含:
- 约10万个心拍样本
- 每个样本260维特征(360Hz采样)
- 平衡后的五类别分布比例接近1:1:1:1:1
更多推荐
所有评论(0)