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波定位技巧:

  1. 使用 annotation.sample 获取所有R波位置
  2. 排除非心拍标注(如噪声标记'~')
  3. 以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
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐