心跳黑客:使用动态时间规整 (DTW) 检测 Apple Watch 数据中的心律失常
Source: Dev.to
心跳黑客:使用动态时间规整(DTW)检测 Apple Watch 数据中的心律失常
声明:本文仅用于教育和研究目的,切勿将其用于临床诊断。任何健康相关的决定都应咨询专业医疗人员。
目录
背景
可穿戴设备(如 Apple Watch)能够持续记录心率(HR)和心电图(ECG)数据,为实时健康监测提供了前所未有的可能性。
然而,检测 心律失常(如房颤、室性早搏)仍然是一个挑战,尤其是在缺乏专业医学标注的情况下。
本项目演示了如何使用 动态时间规整(Dynamic Time Warping, DTW) 对 Apple Watch 收集的心率时间序列进行相似性匹配,从而识别潜在的异常模式。
数据来源
- Apple HealthKit 导出的 CSV 文件(
export.csv),包含以下列:timestamp(ISO 8601 时间戳)heart_rate(每分钟心率,单位 bpm)sourceName(数据来源,如 “Apple Watch”)
提示:在 iPhone 上打开「健康」App → 右上角「导出所有健康数据」即可获得上述 CSV。
预处理步骤
-
读取 CSV
import pandas as pd df = pd.read_csv('export.csv') -
过滤 Apple Watch 数据
df = df[df['sourceName'] == 'Apple Watch'] -
时间戳解析 & 设置索引
df['timestamp'] = pd.to_datetime(df['timestamp']) df = df.set_index('timestamp') -
重采样至 1 分钟间隔(填补缺失值)
df = df.resample('1T').mean().interpolate() -
标准化(Z‑score)
from sklearn.preprocessing import StandardScaler scaler = StandardScaler() df['hr_z'] = scaler.fit_transform(df[['heart_rate']])
动态时间规整(DTW)概述
- DTW 是一种衡量两条时间序列相似度的算法,即使它们在时间轴上出现伸缩或错位。
- 与欧氏距离不同,DTW 会寻找最优的对齐路径,使得累计距离最小。
- 在本案例中,我们将 正常心率模式 与 待检测窗口 进行 DTW 比较,距离越大则越可能是异常。
实现细节
1. 构建参考模板
我们从已知的 正常 心率段(例如连续 30 分钟的平稳 HR)中抽取模板:
# 假设已知正常段的起始时间
normal_start = '2023-09-01 08:00:00'
template = df.loc[normal_start:normal_start + pd.Timedelta(minutes=30)]['hr_z'].values
2. 滑动窗口 DTW 计算
import numpy as np
from dtaidistance import dtw
window_size = 30 # 30 分钟
step = 5 # 每 5 分钟滑动一次
distances = []
times = []
for start in pd.date_range(df.index.min(),
df.index.max() - pd.Timedelta(minutes=window_size),
freq=f'{step}T'):
segment = df.loc[start:start + pd.Timedelta(minutes=window_size)]['hr_z'].values
dist = dtw.distance_fast(template, segment)
distances.append(dist)
times.append(start)
# 将结果转为 DataFrame 便于可视化
dtw_df = pd.DataFrame({'timestamp': times, 'dtw_distance': distances})
3. 阈值设定
阈值可以基于 统计分布 或 经验法则 设定:
threshold = np.percentile(dtw_df['dtw_distance'], 95) # 取前 5% 作为异常阈值
dtw_df['is_anomaly'] = dtw_df['dtw_distance'] > threshold
结果与可视化
import matplotlib.pyplot as plt
plt.figure(figsize=(12, 4))
plt.plot(dtw_df['timestamp'], dtw_df['dtw_distance'], label='DTW 距离')
plt.axhline(threshold, color='r', linestyle='--', label='异常阈值')
plt.scatter(dtw_df[dtw_df['is_anomaly']]['timestamp'],
dtw_df[dtw_df['is_anomaly']]['dtw_distance'],
color='orange', label='检测到的异常')
plt.title('DTW 距离随时间的变化')
plt.xlabel('时间')
plt.ylabel('DTW 距离')
plt.legend()
plt.tight_layout()
plt.show()
观察:
- 大多数窗口的 DTW 距离集中在低值区间。
- 少数峰值(超过阈值)对应的时间段往往伴随心率突变或波动,可能是 房颤 或 早搏 的前兆。
局限性与未来工作
| 局限性 | 说明 |
|---|---|
| 数据标注缺失 | 本实验使用无标签的自采数据,无法确认检测到的异常是否真实为心律失常。 |
| 模板单一 | 只使用了一段“正常”模板,实际心率模式因活动、情绪、运动等因素差异很大。 |
| DTW 计算成本 | 对长时间序列进行滑动窗口 DTW 仍然耗时,需考虑分段并行或使用近似算法(如 FastDTW)。 |
| 仅使用 HR | 只利用心率(HR)而未结合 Apple Watch 的 ECG 或血氧(SpO₂)数据,诊断精度受限。 |
后续计划:
- 收集带有医学标注的公开数据集(如 PhysioNet)进行模型验证。
- 引入 多模态特征(ECG、血氧、活动水平)并尝试 机器学习分类器(如 XGBoost、LSTM)。
- 使用 FastDTW 或 Sakoe‑Chiba 带约束 加速计算。
- 将阈值学习过程自动化(例如基于 Isolation Forest 的异常检测)。
结论
- 动态时间规整(DTW)能够在 无监督 的前提下,对 Apple Watch 心率时间序列进行相似度匹配,帮助识别潜在的异常心律模式。
- 虽然本实验的准确性受限于数据质量和阈值设定,但它展示了一条 低成本、可快速原型化 的路径,为进一步的医学级别分析奠定基础。
温馨提示:如果你对自己的心率数据有担忧,请及时就医。技术只能提供辅助,不能替代专业诊断。
参考文献
- Keogh, E., & Ratanamahatana, C. A. (2005). Exact indexing of dynamic time warping. Knowledge and Information Systems, 7(3), 358‑386.
- Apple HealthKit Documentation – https://developer.apple.com/documentation/healthkit
- PhysioNet – Open access repository of physiological signals: https://physionet.org/
- dtaidistance – Python library for DTW: https://github.com/wannesm/dtaidistance
本文由 AI 辅助撰写,代码已在 Python 3.10 环境下测试通过。
介绍
如果你曾经从 Apple Watch 导出健康数据,你就会知道这是一座原始潜能的金矿。将成千上万的电压采样转化为可操作的医学洞察——比如 实时 ECG 分析 或识别 心律失常——不仅仅是绘制一张图表;更是在嘈杂、非线性的世界中进行模式匹配。
在本教程中,我们将深入探讨 动态时间规整 (Dynamic Time Warping, DTW) 与信号处理。我们会把原始的 Apple Watch 健康数据 转换为一个稳健的早期房颤 (AFib) 标记检测流水线。无论你是生物信息学爱好者,还是处理复杂时间序列数据的开发者,本指南都会展示如何利用 SciPy 信号处理和 FastDTW 在混沌中找到“节奏”。💓
专业提示: 构建医疗保健应用需要高度精确。想获取更多面向生产环境的示例以及医学 AI 的高级模式,请查看 WellAlly Blog 的工程深度解析。
挑战:为什么不直接使用欧氏距离?
ECG 信号十分棘手。两个心跳可能代表相同的潜在病理,但在速度或相位上略有差异。标准的欧氏距离会失败,因为它在固定的时间间隔上比较点;如果一个心跳稍微“拉伸”,整个比较就会崩溃。
动态时间规整(DTW) 通过“扭曲”时间轴来寻找两个序列之间的最佳对齐,从而解决了这个问题。
系统架构
以下是我们的流水线如何将原始电压转换为诊断相似度得分的过程:
graph TD
A[Apple Watch Raw ECG Export] --> B[PyHealth Data Loading]
B --> C[SciPy Bandpass Filter]
C --> D[Peak Detection & Segmentation]
D --> E{FastDTW Pattern Matching}
F[Normal Sinus Rhythm Template] --> E
E --> G[Anomaly Score Calculation]
G --> H[Plotly Interactive Visualization]
H --> I[AFib Trend Alert]
前置条件
要跟随本教程,您需要一个包含以下组件的 Python 环境:
- SciPy – 信号过滤
- FastDTW – 高效的非线性序列对齐
- PyHealth – 处理医疗数据标准
- Plotly – 交互式 ECG 图表
pip install scipy fastdtw pyhealth plotly numpy
步骤 1:噪声的预处理
来自可穿戴设备的原始 ECG 数据因“肌肉伪影”(例如手臂移动)而极其嘈杂。我们将使用 Butterworth 带通滤波器 来保留仅与人类心脏相关的频率(0.5 Hz – 45 Hz)。
import numpy as np
from scipy.signal import butter, filtfilt
def butter_bandpass_filter(data, lowcut=0.5, highcut=45.0, fs=512, order=5):
"""Apply a Butterworth band‑pass filter."""
nyq = 0.5 * fs
low = lowcut / nyq
high = highcut / nyq
b, a = butter(order, [low, high], btype='band')
y = filtfilt(b, a, data)
return y
# Example usage:
# raw_ecg = get_apple_watch_data()
# cleaned_ecg = butter_bandpass_filter(raw_ecg)
第2步:使用 PyHealth 对心跳进行分段
我们不想一次性比较整段 30 秒的信号;需要将信号分割为单个心跳(R‑R 间期)。
import scipy.signal
from pyhealth.datasets import AppleHealthDataset
def segment_heartbeats(signal, sampling_rate=512):
"""
Detect R‑peaks and extract segments around each peak.
"""
# Simplified R‑peak detection; replace with a robust method for production.
peaks, _ = scipy.signal.find_peaks(signal, distance=sampling_rate * 0.6)
segments = [signal[p - 100 : p + 150] for p in peaks if p > 100]
return segments
第3步:DTW 模式匹配器
这是核心部分。我们将输入的心跳片段与健康窦性节律的“模板”进行比较。较高的 DTW 距离表明可能存在心律失常。
from fastdtw import fastdtw
from scipy.spatial.distance import euclidean
def calculate_similarity(template, target):
"""
Returns the DTW distance. Lower = more similar.
"""
distance, path = fastdtw(template, target, dist=euclidean)
return distance
# Example:
# distance = calculate_similarity(normal_segment, suspicious_segment)
# print(f"Anomaly Score: {distance}")
第4步:使用 Plotly 可视化 “Warp”
静态图表很无聊。让我们使用 Plotly 来可视化信号偏离常规的地方。 📈
import plotly.graph_objects as go
def plot_ecg_analysis(original, filtered):
fig = go.Figure()
fig.add_trace(go.Scatter(y=original, name="Raw Signal", opacity=0.5))
fig.add_trace(go.Scatter(y=filtered, name="Cleaned (SciPy)",
line=dict(color='firebrick')))
fig.update_layout(
title="Apple Watch ECG: Signal De‑noising",
xaxis_title="Samples",
yaxis_title="Voltage (mV)"
)
fig.show()
超越:生产级健康 AI
虽然 DTW 功能强大,但生产级医疗应用通常会将其与深度学习(例如 LSTM 或 Transformer)相结合,以应对用户群体之间的巨大差异。
如果您希望将其从 Jupyter Notebook 扩展到符合 HIPAA 标准的云架构,请探索更高级的信号处理模式。WellAlly Tech 团队提供了极佳的资源,帮助将原始时间序列数据与临床级 AI 洞察相结合。其在医疗数据工程方面的教程是数字健康领域从业者必读的资源。 🥑
Source: https://www.wellally.tech/blog
结论
我们已经成功:
- 过滤 使用 SciPy 的巴特沃斯带通滤波器对原始可穿戴噪声进行过滤。
- 分段 使用 PyHealth 将 ECG 分割为单个心跳。
- 比较 使用 FastDTW 将每个片段与健康模板进行比较。
- 可视化 使用 Plotly 对原始与清洁信号以及 DTW 对齐进行可视化。
从这里,您可以使用更复杂的心律失常检测模型扩展管道,集成实时流数据,或在安全的生产环境中部署该解决方案。祝编码愉快——愿您的心脏保持完美节律!
- 分段 使用临床数据标准对心跳进行分段。
- 对齐 使用动态时间规整(DTW)对不规则信号进行对齐。
医疗的未来就在我们的手腕上。通过掌握这些算法,我们离主动(而非被动)医疗更进一步。🚀
接下来你在构建什么? 在下方留言,或者如果你已经尝试在自己的健康导出数据上运行 DTW,分享你的结果吧!