0 引言

    心血管疾病是全球范围内导致死亡和残疾的主要原因,其中心肌梗死以其高发病率、高致残率和高死亡率,构成了最严峻的公共卫生挑战之一。心肌梗死的救治效果具有极强的时间依赖性,“时间就是心肌,时间就是生命”。然而,传统的心肌梗死诊断依赖于心电图和血清生物标志物检测,这些方法通常需要在医疗机构内由专业人员进行操作,存在诊断延迟、设备昂贵、可及性差等问题,难以实现对高危人群的早期、快速、连续筛查与预警。

   近年来,光电容积脉搏波(PPG )信号技术作为一种无创、低成本、易于获取的生理信号检测手段,展现出巨大的应用潜力。PPG 信号是通过光学手段检测血液容积变化,从而间接反映心率、血氧饱和度等生理信息的无创监测信号。它因无创、低成本的特点,已广泛应用于智能穿戴设备与临床诊断场景。PPG信号反映了外周血管在心脏周期性搏动下的血容量变化,根据研究表明,PPG波形的时域特征中,蕴含着丰富的心血管信息。这为将其用于心肌梗死的早期识别与预测提供了重要的生理学基础。

   与此同时,机器学习(ML)技术的飞速发展为处理复杂、非线性的医学生理信号开辟了新途径,已广泛应用于医疗诊断当中。因此,本研究旨在探索基于PPG信号与机器学习算法的心肌梗死预测方法。研究将首先基于包含心肌梗死患者和健康对照者的PPG信号数据集;随后,提取相关时域特征;最后,构建并比较多种机器学习模型(如支持向量机、随机森林、logistic回归等)的性能,以此开发出一种能够实现心肌梗死早期、无创、便捷预测的新方法,为心血管疾病的早期检查和识别提供一种新的技术解决方案。

目录

0 引言

1 数据

1.1 数据来源

1.2 数据查看

1.3 数据预处理

1.3.1 滤波

1.3.2 提取单周期信号

2 特征提取

3 机器学习与评价指标

4 结果与讨论

5 参考

本文完整代码:基于PPG信号与机器学习算法的心肌梗死预测研究的完整代码资源-CSDN下载

此外相关数据可在下载:基于一维卷积神经网络的PPG信号心肌梗死预测研究的完整代码资源

针对本文改进:1.基于统计信息融合的PPG时域特征在心肌梗死预测的研究-CSDN博客

2.基于主成分分析的PPG信号特征用于心肌梗死预测研究-CSDN博客

3.基于一维卷积神经网络的PPG信号心肌梗死预测研究-CSDN博客

1 数据

1.1 数据来源

     本文采用的数据来自于kaggle平台上,具体链接如下:Photoplethysmography (PPG) Dataset数据简介:该数据集包含多个特征,代表不同时间点的 PPG 信号幅度。目标变量:目标列命名为“标签”,代表每个数据样本的标签。正常('Normal):表示没有心肌梗死迹象的生理状况。心肌梗死 (MI):表示信号表现出与心脏病发作或心肌梗死相关的迹象。

1.2 数据查看

  首先查看数据的基本信息:这是一个含 2576 行、2001 列(含 2000 列 float64 类型和 1 列 object 类型 “Label” 列)、占用约 39.3MB 内存的 pandas DataFrame。

import pandas as pd
# 加载数据集
df = pd.read_csv('PPG_Dataset.csv')
print('数据基本信息:')
df.info()
# 查看数据集行数和列数
print( df.shape)

  其次,绘制 PPG 信号前若干行数据及标签(Label)分布图。前者展示前 20 行 PPG 信号的变化趋势,可观察到部分信号存在较明显噪声;后者统计结果显示,正常标签与心肌梗死标签数量分别为 1282 和 1294,表明两类标签分布较为均衡。

import matplotlib.pyplot as plt
n =20  # 要显示的前N条
df=df.drop(columns='Label')
plt.figure(figsize=(12, 8))
for i in range(min(n, len(df))): 
    data = df.iloc[i].dropna() 
    plt.plot(data.values, label=f'n{i+1}')
plt.xlabel('time')
plt.ylabel('AMP')
plt.title(f'{n}')
#plt.legend()
plt.grid(alpha=0.3)
plt.show()

last_col = df.columns[-1]
plt.figure(figsize=(10, 6))  
bars = df[last_col].value_counts().plot(kind='bar', color='skyblue')  
for bar in bars.patches:
    height = bar.get_height()
    plt.text(bar.get_x() + bar.get_width()/2., height + 0.1,
             f'{height}',  
             ha='center',  
             va='bottom',  
             fontsize=10)
plt.xlabel('label', fontsize=12) 
plt.ylabel('count', fontsize=12)
plt.show()

1.3 数据预处理

1.3.1 滤波

   从上节可以看到部分PPG数据的噪声较高,所以对信号的进行滤波处理,这里采用高斯加权移动滤波方法。该算法的主要思想是通过对信号进行加权平均来消除噪声,其中权值是根据高斯分布计算得到的,越接近中心点的权值越大,越远离中心点的权值越小。这样可以使得噪声的影响减小,同时保留信号的主要特征[1]。 高斯函数理论如下[2]:

G(x) = \frac{1}{\sqrt{2\pi\sigma}}e^{-\frac{x^2}{2\sigma^2}}

其中,x表示与中心点的距离,\sigma(标准差)决定曲线的宽度,其越大则平滑度越高,反之平滑度越低。由高斯函数的得到权重系数为W_{2m+1}2m+1表示窗口大小,设置输入信号为X,则滤波的输出信号为

y(t) = \frac{1}{\sum W} \sum_{i=-m}^{m} x(t+i)W(i)

式中,y(t)是时间点 t上的滤波结果。x(t+i)是信号 X 在时间t位置周围的值。本文中窗口大小设置为51,标准差设置为5,与原信号对比,达到一定的滤波效果。

data_to_filter = df.drop(columns='Label')
original_data = data_to_filter.iloc[3, :].values 
# 高斯加权移动滤波函数
def gaussian_filter(data, window_size, sigma):
    """
    高斯加权移动滤波
    :param data: 输入数据(一维数组)
    :param window_size: 窗口大小(奇数)
    :param sigma: 高斯核的标准差
    :return: 滤波后的数据
    """
    # 生成高斯核
    kernel = np.arange(window_size) - window_size // 2
    kernel = np.exp(-0.5 * (kernel / sigma) ** 2)  # 高斯函数
    kernel /= np.sum(kernel)  # 归一化,确保权重和为1
    filtered_data = np.convolve(data, kernel, mode='same')
    return filtered_data
window_size = 51  
sigma = 5 
filtered_data = gaussian_filter(original_data, window_size, sigma)
# 绘制原始数据和滤波后的数据对比
plt.figure(figsize=(12, 6))
plt.plot(original_data, label='Original PPG', alpha=0.5)
plt.plot(filtered_data, label='Gaussian Filtered PPG', color='red')
# 设置图表标题和坐标轴标签
plt.title('PPG Signal')
plt.xlabel('TIME')
plt.ylabel('AMP')
plt.legend()
plt.grid(alpha=0.3) 
plt.show()

进一步,我们对所有数据进行滤波。新的数据集命名为:filtered_data。

data_to_filter = df.drop(columns='Label')
def gaussian_filter(data, window_size, sigma):
    kernel = np.arange(window_size) - window_size // 2
    kernel = np.exp(-0.5 * (kernel / sigma) ** 2)  
    kernel /= np.sum(kernel)  
    filtered_data = np.convolve(data, kernel, mode='same')
    return filtered_data
window_size = 51 
sigma = 5       
filtered_rows = []
for index, row in data_to_filter.iterrows():
    filtered_row = gaussian_filter(row.values, window_size, sigma)
    filtered_rows.append(filtered_row)
filtered_df = pd.DataFrame(filtered_rows, columns=data_to_filter.columns, index=data_to_filter.index)
filtered_df.to_csv('filtered_data.csv', index=False)

1.3.2 提取单周期信号

    为了简化建模的复杂度,本文针对一个心动周期内的数据进行处理。故需要对数据filtered_data进行提取单周期的信号。PPG原始信号具有周期性,则周期内会呈现 "上升 - 峰值 - 下降 - 谷值" 的重复表现,相邻两个谷值(或峰值)之间的部分可视为一个完整周期。如下图可以看出谷值(或峰值)定位准确,并展示一部分第二周期的选择部分。

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd 
from scipy.signal import find_peaks  
original_data =df.iloc[9, :].values  
def gaussian_filter(data, window_size, sigma):
    kernel = np.arange(window_size) - window_size // 2
    kernel = np.exp(-0.5 * (kernel / sigma) **2)
    kernel /= np.sum(kernel)
    filtered_data = np.convolve(data, kernel, mode='same')
    return filtered_data
window_size = 50
sigma = 3
filtered_data = gaussian_filter(original_data, window_size, sigma)
# 检测峰值
peak_indices, _ = find_peaks(
    filtered_data,
    height=np.mean(filtered_data) + 0.1*np.std(filtered_data),  
    distance=150 
)
valley_indices, _ = find_peaks(
    -filtered_data, 
    height=-(np.mean(filtered_data) - 0.1*np.std(filtered_data)),  
    distance=150
)
start_idx = valley_indices[1]
end_idx = valley_indices[2]
single_cycle = filtered_data[start_idx:end_idx+1] 
cycle_time = np.arange(start_idx, end_idx+1)
# 可视化结果
plt.figure(figsize=(15, 10))
plt.plot(original_data, label='Original Data', alpha=0.5)
plt.plot(filtered_data, label='Filtered Data', color='red')
plt.scatter(peak_indices, filtered_data[peak_indices], c='green', label='Peaks')
plt.scatter(valley_indices, filtered_data[valley_indices], c='blue', label='Valleys')
plt.axvline(x=start_idx, color='purple', linestyle='--', label='Cycle Start')
plt.axvline(x=end_idx, color='orange', linestyle='--', label='Cycle End')
plt.title('PPG Signal with Features')
plt.xlabel('Time Index')
plt.ylabel('Amplitude')
plt.legend()
plt.grid(alpha=0.3)
plt.tight_layout()
plt.show()

    因此,对数据集filtered_data进行提取第二部分单周期的信号,并且统一单周期信号的长度(250),最后展示所有行提取的结果,发现均符合单周期的规律,则新的数据集命名为:cycles。可见:【免费】一个心动周期内的PPG信号资源-CSDN下载

2 特征提取

对于预处理好的数据,进行提取时域特征,我们参考文献[3]对PPG信号提取时域特征。如下图中,收缩宽度(Systolic Width, SW):表示收缩期内,PPG从 “特定百分比幅值” 到峰值的时间。如下:

  • SW10:收缩期内,PPG幅值达到 “峰值的 10%” 时的宽度;
  • SW25、SW33、SW50、SW66、SW75 以此类推(对应峰值的 25%、33%、50%、66%、75%)。

舒张宽度(Diastolic Width, DW):表示舒张期内,PPG从峰值到 “特定百分比幅值” 的时间。如下:

  • DW10:舒张期内,PPG幅值从峰值下降到 “峰值的 10%” 时的宽度;
  • DW25、DW33、DW50、DW66、DW75 以此类推(对应峰值的 25%、33%、50%、66%、75%)。

SW和DW衍生参数:基于 SW 和 DW,还定义了组合或比值参数:

  • 加法组合:如 DW10+SW10(舒张 10% 宽度 + 收缩 10% 宽度)等;
  • 比值组合:如DW10/SW 10(舒张 10% 宽度 / 收缩 10% 宽度)等;

基于 SW 、DW以及衍生参数共4×6=24种,加上峰值、收缩期和舒张期的时间,最终得到共27种时域特征。所提取的特征集命名为:feature。

3 机器学习与评价指标

     根据上节所得的特征集feature,首先,划分训练集和测试集(7:3)。因为机器学习实施至少涉及两个阶段,训练和测试。训练阶段是模型本身学习的阶段。在此阶段使用的数据称为训练数据,测试阶段是使用或测试在训练阶段学习的模型的新样本的阶段。在此阶段使用的数据称为测试数据,模型在正确预测这些数据方面的性能称为测试性能。需要注意的是,训练数据中包含的任何样本在测试阶段都不能被使用,因为这将使测试性能失效。其次,本文选择常用的机器学习方法:Logistic回归、支持向量机、K-近邻分析、随机森林和梯度提升,这五种模型覆盖了传统线性、非线性和集成学习方法。其原理如下:

(1)Logistic 回归用于解决分类问题,基于线性回归,但通过 Sigmoid 函数将线 性回归的输出映射到[0,1]区间,从而表示样本属于某一类别的概率。假设数据存在线性可分关系,通过最大似然估计来确定模型参数,使样本属于其真实类别的概率最大化。

P(y=1|x)=\frac{1}{1+e^{-(\beta_0+\beta_1x_1+\beta_2x_2+\cdots+\beta_nx_n)}}

P(y=0 \mid x) = \frac{e^{-(\beta_0 + \beta_1 x_1 + \cdots + \beta_n x_n)}}{1 + e^{-(\beta_0 + \beta_1 x_1 + \cdots + \beta_n x_n)}}

其中,P(y=1|x)是表示在给定自变量x=(x_1,x_2,\cdots,x_n)y=1的概率,同样P(y=0|x)如此,\beta_0是截距项,\beta_1,\beta_2,\cdots\beta_n是回归系数,它们表示每个自变量的影响程度。

(2)支持向量机模型旨在找到一个最优超平面,以最大化不同类之间的间隔。当数据不是 线性可分时,支持向量机模型使用核函数将数据映射到高维空间。在这个高维空间内,找 到一个超平面来分隔不同类别。支持向量机模型的核心是一个优化问题,旨在最小化一个 正则化项和一个损失函数。优化目标可以表示为:

min_{w,b} \frac{1}{2}\|w\|^2 + C \sum_{i=1}^{n} \xi_i

其中,\xi_i是松弛变量,C表示惩罚参数,其用于平衡间隔和分类错误之间的权衡。

(3)K-近邻分析是一种基本分类的方法。是数据挖掘技术中原理最简单的算法之一,核心功能是解决有监督的分类问题。KNN能够快速高效地解决建立在特殊数据集上的预测分类问题,其核心思想是:若某样本在特征空间中的k个最邻近样本多数属于某个类别,则该样本也归为此类[4]。

  (4)  随机森林是一种集成学习模型,基于重抽样自举法的样本创建多个子集,用于构建每个决 策树,将多个决策树整合一起,最终构成整片森林。其核心思想是 “集体智慧优于个体”,它通过集成学习策略,先利用重抽样自举法从原始数据中随机生成多个不同的样本子集,再为每个子集独立构建一棵决策树,最后让所有决策树共同参与预测,通过投票(分类任务)或取平均(回归任务)的方式输出最终结果,以此降低单棵决策树的过拟合风险,提升模型的稳定性和预测精度。

(5)  梯度提升算法同样是一种集成学习方法,它的核心思想是利用损失函数的负梯度作为残差的近似值,然后用一个基学习器拟合这个残差,再将其加到之前的模型上,从而不断地减小损失函数的值。这种算法可以用任何可微分的损失函数,并且可以用任何基学习器,如决策树等。因此,梯度提升算法比其他基于单一类型损失函数的算法更加灵活和通用[5]。

最后,依据这五种模型输入到训练集中进行训练,将测试集输入训练好的模型,采用评价指标进行模型性能检测。二分类的评价指标如下:

(1)Accuracy(准确率):指模型正确预测的样本数占总样本数的比例,反映模型整体的预测正确性,公式为:

Accuracy= \frac{\text{TP} + \text{TN}}{\text{TP} + \text{TN} + \text{FP} + \text{FN}}

其中 TP 为真阳性,TN 为真阴性,FP 为假阳性,FN 为假阴性。

(2)recision(精确率 / 精准度):针对模型预测为正类的样本,其中真正为正类的比例,公式为:

Precision= \frac{\text{TP}}{\text{TP} + \text{FP}}

(3)Recall(召回率 / 查全率):针对实际为正类的样本,模型成功预测为正类的比例,公式为:

Recall = \frac{\text{TP}}{\text{TP} + \text{FN}}

(4)F1 Score(F1 分数):是精确率和召回率的调和平均数,综合衡量两者的表现,公式为:

F1 Score= 2 \times \frac{\text{Precision} \times \text{Recall}}{\text{Precision} + \text{Recall}}

(5)AUC(Area Under the ROC Curve,ROC 曲线下面积):ROC 曲线以假阳性率(FPR)为横轴、真阳性率(TPR)为纵轴绘制,AUC 是该曲线下的面积,用于评估模型在不同分类阈值下的整体性能,取值范围 0-1,越接近 1 说明模型分类能力越强。

FPR= \frac{\text{FP}}{\text{FP} + \text{TN}}TPR= \frac{\text{TP}}{\text{TP} + \text{FN}}

代码如下:

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_curve, auc
import matplotlib.pyplot as plt
import numpy as np
df=pd.read_csv('feature.csv')
X = df.drop('numeric_label', axis=1)
y = df['numeric_label']
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 定义模型
models = {
    'Logistic Regression': LogisticRegression(max_iter=1000),
    'SVM': SVC(probability=True),
    'Random Forest': RandomForestClassifier(),
    'Gradient Boosting': GradientBoostingClassifier(),
    'KNN': KNeighborsClassifier()
}
results = {}
for model_name, model in models.items():
    model.fit(X_train, y_train)
    y_pred = model.predict(X_test)
    y_pred_proba = model.predict_proba(X_test)[:, 1]
    fpr, tpr, _ = roc_curve(y_test, y_pred_proba)
    roc_auc = auc(fpr, tpr)
    accuracy = accuracy_score(y_test, y_pred)
    precision = precision_score(y_test, y_pred, average='weighted')
    recall = recall_score(y_test, y_pred, average='weighted')
    f1 = f1_score(y_test, y_pred, average='weighted')

    results[model_name] = {
        'Accuracy': accuracy,
        'Precision': precision,
        'Recall': recall,
        'F1 Score': f1,
        'AUC': roc_auc
    }
    # 绘制ROC曲线
    plt.plot(fpr, tpr, label=f'{model_name} (AUC = {roc_auc:.2f})')
results_df = pd.DataFrame.from_dict(results, orient='index')
print(results_df)
plt.plot([0, 1], [0, 1], 'k--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.xticks(rotation=45)
plt.ylabel('True Positive Rate')
plt.title('ROC Curves of Various Models')
plt.legend(loc='lower right')
plt.show()

4 结果与讨论

       从模型性能指标来看,所有模型在准确率(Accuracy)、精确率(Precision)、召回率(Recall)和F1分数(F1-Score)上均表现优异,数值普遍高于0.95,说明各模型在分类任务中具有很强且稳定的判别能力。其中,支持向量机、随机森林和K-近邻分析在准确率、精确率、召回率和F1分数上表现完全一致,且略高于逻辑回归和梯度提升模型。从AUC值来看,所有模型的曲线下面积均超过0.96,进一步验证了模型具有良好的整体分类性能,尤其是梯度提升模型的AUC最高(0.964),显示出其在区分正负类样本方面的最优能力。综合来看,各模型性能接近,均可有效应用于该分类任务,梯度提升在AUC上略占优势。

     尽管上述模型在默认参数下均取得了不俗的性能表现(各项评估指标均超过0.95),但需要指出的是,当前结果存在一定的局限性。首先,所有模型均采用默认参数运行,未经过系统的超参数调优,其性能潜力可能尚未被完全发掘。其次,模型的训练与验证仅依赖于单一周期的数据,未能捕捉数据在时间维度上可能存在的动态变化与模式。此外,当前模型所采用的特征主要集中于时域,可能忽略了频域等其他特征空间中蕴含的判别信息。因此,若要在实际应用中实现性能的进一步提升,后续工作应着重于模型参数的精细优化、引入跨时间周期的数据进行训练,并融合多领域的特征工程。

模型 Accuracy recision Recall F1Score AUC
Logistic回归   0.957364 0.957604 0.957364 0.957355 0.961824
支持向量机 0.963178 0.963238 0.963178 0.963175 0.959435
随机森林 0.963178 0.963238 0.963178 0.963175 0.962530
梯度提升 0.959302 0.959308 0.959302 0.959302 0.964461
K-近邻分析 0.963178 0.963238 0.963178 0.963175 0.961644

5 参考

[1] MATLAB | 数字信号处理 | 高斯加权移动平均滤波算法 | 附数据和出图代码 | 直接上手 - 知乎

[2] 算法 - 高斯加权移动平均滤波(GWMA) - 世界尽头号

[3] Maqsood S, Xu S, Springer M, et al. A benchmark study of machine learning for analysis of signal feature extraction techniques for blood pressure estimation using photoplethysmography (PPG)[J]. Ieee Access, 2021, 9: 138817-138833.

[4] 一文掌握KNN(K-近邻算法,理论+实例) - 知乎

[5] 梯度提升(Gradient Boosting)算法:原理与实践-百度开发者中心

注:若有侵权部分,请留言将会删除。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐