心跳黑客:使用动态时间规整 (DTW) 检测 Apple Watch 数据中的心律失常

发布: (2026年3月9日 GMT+8 09:25)
14 分钟阅读
原文: Dev.to

Source: Dev.to

心跳黑客:使用动态时间规整(DTW)检测 Apple Watch 数据中的心律失常

声明:本文仅用于教育和研究目的,切勿将其用于临床诊断。任何健康相关的决定都应咨询专业医疗人员。


目录

  1. 背景
  2. 数据来源
  3. 预处理步骤
  4. 动态时间规整(DTW)概述
  5. 实现细节
  6. 结果与可视化
  7. 局限性与未来工作
  8. 结论
  9. 参考文献

背景

可穿戴设备(如 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。


预处理步骤

  1. 读取 CSV

    import pandas as pd
    df = pd.read_csv('export.csv')
  2. 过滤 Apple Watch 数据

    df = df[df['sourceName'] == 'Apple Watch']
  3. 时间戳解析 & 设置索引

    df['timestamp'] = pd.to_datetime(df['timestamp'])
    df = df.set_index('timestamp')
  4. 重采样至 1 分钟间隔(填补缺失值)

    df = df.resample('1T').mean().interpolate()
  5. 标准化(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₂)数据,诊断精度受限。

后续计划

  1. 收集带有医学标注的公开数据集(如 PhysioNet)进行模型验证。
  2. 引入 多模态特征(ECG、血氧、活动水平)并尝试 机器学习分类器(如 XGBoost、LSTM)。
  3. 使用 FastDTWSakoe‑Chiba 带约束 加速计算。
  4. 将阈值学习过程自动化(例如基于 Isolation Forest 的异常检测)。

结论

  • 动态时间规整(DTW)能够在 无监督 的前提下,对 Apple Watch 心率时间序列进行相似度匹配,帮助识别潜在的异常心律模式。
  • 虽然本实验的准确性受限于数据质量和阈值设定,但它展示了一条 低成本、可快速原型化 的路径,为进一步的医学级别分析奠定基础。

温馨提示:如果你对自己的心率数据有担忧,请及时就医。技术只能提供辅助,不能替代专业诊断。


参考文献

  1. Keogh, E., & Ratanamahatana, C. A. (2005). Exact indexing of dynamic time warping. Knowledge and Information Systems, 7(3), 358‑386.
  2. Apple HealthKit Documentationhttps://developer.apple.com/documentation/healthkit
  3. PhysioNet – Open access repository of physiological signals: https://physionet.org/
  4. 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,分享你的结果吧!

0 浏览
Back to Blog

相关文章

阅读更多 »