目录
使用1DCNN训练离散脉搏波数据
导入软件包
# coding=utf8
from keras.callbacks import TensorBoard
import numpy as np
import datetime
from keras.models import Model, Input, Sequential
from keras import layers
import csv
import os
from sklearn.model_selection import train_test_split
import pandas as pd
from keras.regularizers import l2
import matplotlib.pyplot as plt
from keras.callbacks import ModelCheckpoint
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers,Sequential
from tensorflow.keras.layers import Flatten,Dense,BatchNormalization,Conv2D, MaxPool1D, Dropout,Conv1D,LSTM,Activation, Convolution1D
from keras import initializers,regularizers,constraints
from keras import backend as K
import keras
keras.initializers.glorot_normal(seed = None)
from tensorflow.keras.layers import Flatten,Dense,BatchNormalization,Conv2D, MaxPool2D, Dropout,Conv1D,LSTM,Activation
from keras.layers import Input, Add, Dense, Activation, ReLU, ZeroPadding2D, BatchNormalization, Flatten, Conv2D, AveragePooling2D, MaxPooling2D, GlobalMaxPooling2D
from keras.layers import Dense, Conv1D, BatchNormalization, Activation, Dropout, MaxPool1D, ReLU, Conv2D
from keras.layers import Input, Flatten, Concatenate, add, dot, Multiply, merge, Add, Lambda,Permute,Reshape
from keras.callbacks import ModelCheckpoint, LearningRateScheduler, EarlyStopping
from keras.callbacks import ReduceLROnPlateau
from keras.preprocessing.image import ImageDataGenerator
from keras.regularizers import l2
from keras.layers import Bidirectional,Dropout,LSTM,Dense,Flatten
import scipy.io
import scipy.io as scio
设置环境变量
设置了两个环境变量:CUDA_DEVICE_ORDER和CUDA_VISIBLE_DEVICES。
CUDA_DEVICE_ORDER:这个环境变量用于指定CUDA设备排序的顺序。默认情况下,CUDA设备是按照设备ID排序的,即CUDA_DEVICE_ORDER = "PCI_BUS_ID"表示设备按照PCI总线ID排序。
CUDA_VISIBLE_DEVICES:这个环境变量用于指定哪些GPU设备对CUDA应用程序可见。例如,CUDA_VISIBLE_DEVICES = “1"表示只有ID为1的GPU设备对CUDA应用程序可见。如果设置为"0,1”,则表示ID为0和1的GPU设备都可见。
如果你的机器上只有一个GPU设备,那么可以不设置CUDA_VISIBLE_DEVICES环境变量,因为默认情况下CUDA会使用第一个可用的GPU设备。
改成0或者删掉这一行就不太好用了
import os
os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"
os.environ["CUDA_VISIBLE_DEVICES"] = "1"
获取matlab分割后的数据
通过scio库中的loadmat函数来加载一个MAT文件。loadmat函数返回的是一个字典,.mat文件是一种由MATLAB生成的文件格式,用于存储变量和矩阵数据。
data= scio.loadmat('singlePPGDemo01.mat')
data.keys() = dict_keys([‘__header__’, ‘__version__’, ‘__globals__’, ‘Anxiety’, ‘Sad’, ‘Happy’, ‘Peace’, ‘Stress’, ‘lable’])
使用transpose((0,2,1))改变数据的索引。data[‘Peace’]的shape是(90, 40, 80);data[‘Peace’].transpose((0,2,1))的shape是(90, 80, 40)。
(?暂时还没明白数据的结构是咋样的)
data_xPeace = data['Peace'].transpose((0,2,1))
data_xStress = data['Stress'].transpose((0,2,1))
data_xHappy = data['Happy'].transpose((0,2,1))
data_xSad = data['Sad'].transpose((0,2,1))
data_xAnxiety = data['Anxiety'].transpose((0,2,1))
data_y = data['lable']
定义超参数
fc1 = 512 # 第一个全连接层(Fully Connected Layer)的神经元数量
fc2 = 256 # 第二个全连接层的神经元数量,有256个神经元
fc3 = 64 # 第三个全连接层的神经元数量,有64个神经元
# 学习率(Learning Rate),用于控制模型参数更新的步长。较小的学习率意味着参数更新较慢,较大的学习率可能导致模型训练不稳定。
lr = 0.00001
# dropout率,用于防止过拟合。Dropout是一种正则化技术,通过在训练过程中随机丢弃一部分神经元(将它们的输出设为0),来减少模型对特定神经元的依赖,从而提高模型的泛化能力。0.1表示在每次迭代中,有10%的神经元会被随机丢弃。
drop_out = 0.1
# L2正则化项的权重,用于防止过拟合。L2正则化通过在损失函数中添加参数的平方和,来惩罚模型参数的大小,从而减少过拟合。
L2 = 0.001
# 随机种子,用于控制随机数生成器的状态。设置随机种子可以确保每次运行代码时,随机数生成器的状态都是相同的,从而使得实验结果具有可重复性。
seed = 222
定义回归模型中的均方根误差(RMSE)和平均绝对误差(MAE)
- 均方根误差(RMSE)
def rmse(y_true, y_pred):
return K.sqrt(K.mean(K.square(y_pred - y_true)))
y_true:真实值。
y_pred:预测值。
y_pred - y_true:计算预测值与真实值之间的差值。
K.square(y_pred - y_true):计算差值的平方。
K.mean(K.square(y_pred - y_true)):计算所有差值平方的平均值。
K.sqrt(K.mean(K.square(y_pred - y_true))):计算上述平均值的平方根,得到均方根误差。
RMSE 是一种常用的回归模型评估指标,它能够反映预测值与真实值之间的差异程度。RMSE 越小,说明模型的预测性能越好。
- 平均绝对误差(MAE)
def mae(y_true, y_pred):
return K.mean(K.abs(y_pred - y_true))
y_true:真实值。
y_pred:预测值。
y_pred - y_true:计算预测值与真实值之间的差值。
K.abs(y_pred - y_true):计算差值的绝对值。
K.mean(K.abs(y_pred - y_true)):计算所有差值绝对值的平均值。
MAE 是另一种常用的回归模型评估指标,它同样反映了预测值与真实值之间的差异程度。MAE 越小,说明模型的预测性能越好。
注意事项:
MAE 对异常值不敏感,因为异常值只会增加绝对值的和。
分割训练集和测试集
先将整个数据集分为训练集和测试集(6:4),再将测试集分为测试集和验证集(5:5)
x_Peace_train, x_Peace_test, y_Peace_train, y_Peace_test = train_test_split(data_xPeace, data_y, test_size =0.40, random_state =seed)
x_Peace_test, x_Peace_valid, y_Peace_test, y_Peace_valid = train_test_split(x_Peace_test, y_Peace_test, test_size =0.5, random_state =seed)
x_Anxiety_train, x_Anxiety_test, y_Anxiety_train, y_Anxiety_test = train_test_split(data_xAnxiety, data_y, test_size =0.40, random_state =seed)
x_Anxiety_test, x_Anxiety_valid, y_Anxiety_test, y_Anxiety_valid = train_test_split(x_Anxiety_test, y_Anxiety_test, test_size =0.5, random_state =seed)
x_Happy_train, x_Happy_test, y_Happy_train, y_Happy_test = train_test_split(data_xHappy, data_y, test_size =0.40, random_state =seed)
x_Happy_test, x_Happy_valid, y_Happy_test, y_Happy_valid = train_test_split(x_Happy_test, y_Happy_test, test_size =0.5, random_state =seed)
x_Sad_train, x_Sad_test, y_Sad_train, y_Sad_test = train_test_split(data_xSad, data_y, test_size =0.40, random_state =seed)
x_Sad_test, x_Sad_valid, y_Sad_test, y_Sad_valid = train_test_split(x_Sad_test, y_Sad_test, test_size =0.5, random_state =seed)
x_Stress_train, x_Stress_test, y_Stress_train, y_Stress_test = train_test_split(data_xStress, data_y, test_size =0.40, random_state =seed)
x_Stress_test, x_Stress_valid, y_Stress_test, y_Stress_valid = train_test_split(x_Stress_test, y_Stress_test, test_size =0.5, random_state =seed)
x_Peace_train.shape = (54, 80, 40)
x_Peace_test.shape = (18, 80, 40)
x_Peace_valid.shape = (18, 80, 40)
定义模型
构建一个一维卷积块
def Conv1D_Block1(input, num_filters=None, kernel_size=None, strides=1, activation=1):
x = Conv1D(filters=num_filters,
kernel_size=kernel_size,
strides=strides,
padding='same',
kernel_initializer='he_normal',
kernel_regularizer=l2(L2))(input)
x = BatchNormalization()(x)
if activation:
x = ReLU()(x)
return x
- Conv1D:Keras中的1维卷积层。
- filters:卷积核的数量,即输出通道数。
- kernel_size:卷积核的大小。
- strides:卷积操作的步幅。
- padding=‘same’:保持输入和输出在空间维度上(即时间维度)的尺寸一致。
- kernel_initializer=‘he_normal’:使用He初始化方法,适用于ReLU激活函数。
- kernel_regularizer=l2(L2):使用L2正则化,以防止过拟合。
构建另一个一维卷积块,这个多一个maxpool层
def Conv1D_Block2(input, num_filters=None, kernel_size=None, strides=1, activation=1):
x = Conv1D(filters=num_filters,
kernel_size=kernel_size,
strides=strides,
padding='same',
kernel_initializer='he_normal',
kernel_regularizer=l2(L2))(input)
x = BatchNormalization()(x)
x = MaxPool1D(1)(x)
if activation:
x = ReLU()(x)
return x
又构建一个一维卷积块Conv1d_BN
它先对输入数据进行批标准化,再卷积,上面两个是先卷积再批标准化。
def Conv1d_BN(x, nb_filter, kernel_size, strides=1, padding='same', name=None):
if name is not None:
bn_name = name + '_bn'
conv_name = name + '_conv'
else:
bn_name = None
conv_name = None
x = BatchNormalization(axis=1, name=bn_name)(x)
# name 参数用于为模型或层指定一个名称
x = Convolution1D(nb_filter, kernel_size, padding=padding, strides=strides, activation='relu', name=conv_name)(x)
return x
构建多尺度卷积残差块
根据下图构建多尺度卷积块和残差模块
def Conv_Block(input, with_conv_shortcut=False, name=None):
if name is not None:
Scale_name = name + '_Scale'
else:
Scale_name = None
#多尺度卷积1
Scale_1 = Conv1D_Block1(input, num_filters=16, kernel_size=3)
Scale_1 = Conv1D_Block1(Scale_1, num_filters=32, kernel_size=4)
# 多尺度卷积2
Scale_2 = Conv1D_Block1(input, num_filters=16, kernel_size=6)
Scale_2 = Conv1D_Block1(Scale_2, num_filters=32, kernel_size=3)
# 多尺度卷积3
Scale_3 = Conv1D_Block2(input, num_filters=32, kernel_size=4)
# 多尺度卷积4
Scale_4 = Conv1D_Block2(input, num_filters=32, kernel_size=2)
# 拼接层
x = layers.concatenate([Scale_1, Scale_2, Scale_3, Scale_4], name=Scale_name, axis=-1)
# 快捷连接??可以缓解梯度消失问题,提高模型的训练效果。
if with_conv_shortcut:
shortcut = Convolution1D(filters=128, kernel_size=2, strides=1, activation='relu', padding="same")(input)
x = add([x, shortcut])
return x
else:
# 残差块
x = add([x, input])
return x
构建模型的前半部分-特征提取
将五种信号按照上图结果都传入模型
#%% #将焦虑信号传入模型
inputAnxiety1 = layers.Input(shape=(80,40), dtype='float32')
inputAnxiety1_1 = Convolution1D(filters=64, kernel_size=2,strides=2,activation='relu',padding="same")(inputAnxiety1)
#stage1
outputAnxiety1_1 = Conv_Block(inputAnxiety1_1,with_conv_shortcut=True)
outputAnxiety1_1 = Convolution1D(filters=32, kernel_size=2,strides=2,activation='relu',padding="same")(outputAnxiety1_1)
# 对input进行2次卷积
inputAnxiety2_2 = Convolution1D(filters=32, kernel_size=1,strides=2,activation='relu',padding="same")(inputAnxiety1)
inputAnxiety2_2 = Convolution1D(filters=32, kernel_size=2,strides=2,activation='relu',padding="same")(inputAnxiety2_2)
x12 = layers.concatenate([outputAnxiety1_1, inputAnxiety2_2],name='AnxietyScale_name1', axis=-1)
#stage2
outputAnxiety1_2 = Conv_Block(x12,with_conv_shortcut=True)
outputAnxiety1_2 = Convolution1D(filters=32, kernel_size=2,strides=2,activation='relu',padding="same")(outputAnxiety1_2)
# 对input进行3次卷积
inputAnxiety3_3 = Convolution1D(filters=32, kernel_size=1,strides=2,activation='relu',padding="same")(inputAnxiety1)
inputAnxiety3_3 = Convolution1D(filters=32, kernel_size=2,strides=2,activation='relu',padding="same")(inputAnxiety3_3)
inputAnxiety3_3 = Convolution1D(filters=32, kernel_size=2,strides=2,activation='relu',padding="same")(inputAnxiety3_3)
x123 = layers.concatenate([outputAnxiety1_2, inputAnxiety3_3],name='AnxietyScale_name2', axis=-1)
#stage3
outputAnxiety1_3 = Conv_Block(x123,with_conv_shortcut=True)
outputAnxiety1_3 = Convolution1D(filters=32, kernel_size=2,strides=2,activation='relu',padding="same")(outputAnxiety1_3)
# 对input进行4次卷积
inputAnxiety4_4 = Convolution1D(filters=32, kernel_size=1,strides=2,activation='relu',padding="same")(inputAnxiety1)
inputAnxiety4_4 = Convolution1D(filters=32, kernel_size=2,strides=2,activation='relu',padding="same")(inputAnxiety4_4)
inputAnxiety4_4 = Convolution1D(filters=32, kernel_size=2,strides=2,activation='relu',padding="same")(inputAnxiety4_4)
inputAnxiety4_4 = Convolution1D(filters=32, kernel_size=2,strides=2,activation='relu',padding="same")(inputAnxiety4_4)
x1234 = layers.concatenate([outputAnxiety1_3, inputAnxiety4_4],name='AnxietyScale_name3', axis=-1)
outputAnxiety = layers.MaxPooling1D(2,name='AnxietyMaxPooling1D_2')(x1234)
outputAnxiety = Conv1D_Block1(outputAnxiety, num_filters=128, kernel_size=3)
outputAnxiety = layers.GlobalAveragePooling1D()(outputAnxiety)
outputAnxiety= layers.Flatten()(outputAnxiety)
把五种信号拼接到一起
output = layers.concatenate([outputAnxiety, outputHappy,outputSad,outputStress,outputPeace], axis=-1)
代码和论文有一点出入,不懂
运行代码
运行上面的所有代码会弹出一段日志
这段日志表明TensorFlow已经成功地在你的计算机上找到了并使用了GPU设备,并且已经正确地加载了所需的库。这意味着你可以在TensorFlow中使用GPU加速你的计算。
打印output,输出Tensor(“concatenate_15/concat:0”, shape=(None, 640), dtype=float32)
也不知道啥意思
构建模型的后半部分-输出层
模型的输出层:全连接层(Dense层)和Dropout层,用于防止过拟合。
output = layers.Dense(fc1,activation='relu',kernel_initializer='he_normal',kernel_regularizer=regularizers.l2(L2),name='Dense_1')(output)
output = layers.Dropout(drop_out)(output)
output = layers.Dense(fc2,activation='relu',kernel_initializer='he_normal',kernel_regularizer=regularizers.l2(L2),name='Dense_3')(output)
output = layers.Dropout(drop_out)(output)
output = layers.Dense(1,activation='relu',name = 'Dense_2')(output)
model = Model(inputs = [inputPeace1,inputSad1,inputHappy1,inputAnxiety1,inputStress1], outputs=output)
#模型编译
model.compile(loss='mse',optimizer=tf.keras.optimizers.Adam(learning_rate=lr,beta_1=0.9,beta_2=0.999,epsilon=1e-07,amsgrad=False,name='Adam',),metrics=[rmse, mae])
#打印模型的摘要信息,包括各层的名称、输出形状、参数数量等
model.summary()
定义回调函数
#%%定义了一个包含两个回调函数(callbacks)的列表,这些回调函数在训练过程中用于监控和调整模型的训练过程
callbacks_list = [keras.callbacks.ReduceLROnPlateau(monitor = 'val_loss',patience = 5,factor = 0.1),
ModelCheckpoint('best_model.h5',monitor='val_loss',save_best_only=True,save_weights_only= False)]
- ReduceLROnPlateau:当验证损失(val_loss)在指定的耐心周期(patience)内没有改善时,自动降低学习率(learning rate)。
- monitor=‘val_loss’:监控的指标是验证损失。
- patience=5:如果验证损失在5个epoch内没有改善,则降低学习率。
- factor=0.1:学习率降低的因子,即降低为原来的10%。
- ModelCheckpoint:在验证损失达到最佳时,保存模型或模型的权重。
- ‘best_model.h5’:保存模型的文件名。
- monitor=‘val_loss’:监控的指标是验证损失。
- save_best_only=True:只保存验证损失最好的模型。
- save_weights_only=False:保存整个模型,包括架构和权重。
开始训练
# y_Anxiety_train就是y_data的一部分,所有的5种x都对应一个y
history_1 = model.fit([x_Anxiety_train,x_Happy_train,x_Sad_train,x_Stress_train,x_Peace_train], [y_Anxiety_train], epochs=300, batch_size=4
,validation_data=([x_Anxiety_valid,x_Happy_valid,x_Sad_valid,x_Stress_valid,x_Peace_valid], [y_Anxiety_valid]),callbacks = callbacks_list)
model.save("my_h5_model.h5")
画图(损失函数、精度)
对数值序列进行平滑处理
# scalar:这是一个数值序列,可以是列表、元组或其他可迭代对象,包含了一系列需要平滑处理的数值。
# weight:这是一个可选参数,用于控制平滑的程度。默认值为0.85,表示旧数据点的权重为85%,新数据点的权重为15%。??这个权重值可以根据具体需求进行调整。
def smooth(scalar, weight=0.85):
last = scalar[0]
smoothed = []
for point in scalar:
# 新的平滑值smoothed_val是旧数据和新数据的加权平均。
smoothed_val = last * weight + (1 - weight) * point
smoothed.append(smoothed_val)
last = smoothed_val
return smoothed
绘制训练和验证集的损失曲线
history_1.history.keys() = dict_keys([‘loss’, ‘rmse’, ‘mae’, ‘val_loss’, ‘val_rmse’, ‘val_mae’, ‘lr’])
日志中包含的东西都可以用图画出来。
# 绘制训练和验证集的损失曲线
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = [u'SimHei']# 将默认字体设置为“SimHei”(黑体)
plt.rcParams['axes.unicode_minus'] = False# 确保负号能够正确显示
loss = history_1.history['loss']
val_loss = history_1.history['val_loss']
epochs = range(0,len(loss))
plt.figure(figsize=(6,4))
plt.plot(epochs,smooth(loss,weight=0.95),'-r',label='Training loss')
plt.plot(epochs, val_loss, '-b', label='Validation loss')
plt.xlabel("迭代次数/epochs")
plt.ylabel("损失函数/loss")
plt.title('Training and Validation loss')
plt.legend()
plt.show()
如果不做平滑处理,结果如下:
绘制均方根误差曲线
# 绘制均方根误差
plt.rcParams['font.sans-serif'] = [u'SimHei']
plt.rcParams['axes.unicode_minus'] = False
loss = history_1.history['rmse']
val_loss = history_1.history['val_rmse']
epochs = range(len(loss))
plt.figure(figsize=(6,4))
plt.plot(epochs,smooth(loss,weight=0.95),'-r',label='Training rmse')
plt.plot(epochs, val_loss, '-b', label='Validation rmse')
plt.xlabel("迭代次数/epochs")
plt.ylabel("均方根误差 RMSE")
plt.title('Training and Validation rmse')
plt.legend()
plt.show()
绘制平均绝对误差
# 平均绝对误差
plt.rcParams['font.sans-serif'] = [u'SimHei']
plt.rcParams['axes.unicode_minus'] = False
loss = history_1.history['mae']
val_loss = history_1.history['val_mae']
epochs = range(1,len(loss) + 1)
plt.figure(figsize=(6,4))
plt.plot(epochs,smooth(loss,weight=0.95),'-r',label='Training mae')
plt.plot(epochs, val_loss, '-b', label='Validation mae')
plt.xlabel("迭代次数/epochs")
plt.ylabel("平均绝对误差/MAE")
plt.title('Training and Validation mae')
plt.legend()
plt.show()
模型推理
进行预测
#将已训练模型的隐藏层作为输出
# 感觉这一行没什么用?????
model = Model(inputs=model.input, outputs=model.get_layer('Dense_2').output)
#获取模型的特征输出
hidden_features =model.predict([x_Anxiety_test,x_Happy_test,x_Sad_test,x_Stress_test,x_Peace_test])
print(hidden_features-y_Happy_test)
plt.rcParams['font.sans-serif'] = [u'SimHei']
plt.rcParams['axes.unicode_minus'] = False
predict = hidden_features
true = y_Happy_test
plt.figure(figsize=(6,4))
x_ticks = list(range(1, len(predict)+1))
plt.xticks(x_ticks)
plt.plot(x_ticks ,predict,'-r',label='predict')
plt.plot(x_ticks , true, '-b', label='true')
plt.xlabel("测试样本")
plt.legend()
plt.show()
结果如下:
[[ 3.20032501]
[ 2.33247471]
[ 0.25075626]
[ 0.08122253]
[ 6.42542553]
[ 5.42792606]
[ -1.25280666]
[ -6.33286381]
[ -9.46155834]
[ -5.61695194]
[ 9.01800251]
[ 8.46879864]
[ -7.44759369]
[ 3.44989681]
[ 7.80456352]
[ -4.4735899 ]
[ -4.33261681]
[-10.94440937]]
计算测试集误差
from sklearn.metrics import mean_absolute_error, mean_squared_error
from sklearn.metrics import mean_squared_error
mae = mean_absolute_error(y_Anxiety_test,hidden_features)
print('平均绝对误差 MAE:',mae)
mse = mean_squared_error(y_Anxiety_test, hidden_features)
print('均方误差 MSE:',mse)
rmse = np.sqrt(mean_squared_error(y_Anxiety_test, hidden_features))
print('均方根误差 RMSE:',rmse)
结果如下:
平均绝对误差 MAE: 5.351210117340088
均方误差 MSE: 38.28531307594944
均方根误差 RMSE: 6.1875126727910175
总结
效果好像一般