一种时间序列数据的一维卷积神经网络分类方法

本文是根据阿里云天池【心跳信号分类预测】冠军攻略整理撰写,为时间序列数据的分类上提出更多解决方法。

数据集下载

登录阿里云天池,搜索心跳信号分类预测即可

赛题数据讲解

数据集均为205维的心跳时序数据,虽然是想同维度数据,很多人尝试梯度提升树,虽然说在某些方面梯度提升数类模型表现的比神经网络要好,但是这并不是绝对的,使用LGB、XGB模型需要自行构建很多特征,不能把这205维数据直接丢进去的。而神经网络则会自动寻优特征,本文就是在结构化数据分类神经网络的效果比树模型更好的一个例子。

  • 主要的特征数据为 1 维信号振幅 (已被归一化至 0~1 了),总长度均为 205 (205 个时间节点/心跳节拍)
  • 除波形数据外,没有任何辅助或先验信息可以利用
  • 波形数据均已被量化为 float16 类型的数值型特征,且没有类别型特征需要考虑
  • 没有缺失值,无需填充,非常理想 —— 事实上,未采集到的信号默认振幅就是 0,故不存在缺失值的问题
  • 这类非表格数据更适合用神经网络来处理,而非传统机器学习模型

该类别分为3类,0最多,1/2/3相对较少,可以基本判断出0为正常人心跳,1/2/3为不同心脏病病人的心跳

在这里插入图片描述

1.导入依赖库

import os
import math
import time
import random
import datetime
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import tensorflow as tf
import tensorflow.keras as K
from tensorflow.keras import Sequential, utils, regularizers, Model, Input
from tensorflow.keras.layers import Flatten, Dense, Conv1D, MaxPool1D, Dropout, AvgPool1D
from imblearn.over_sampling import SMOTE
from sklearn.model_selection import KFold
from sklearn.preprocessing import OneHotEncoder

2.数据加载

# 加载训练集和测试集(相对路径)
train = pd.read_csv('./train.csv')
test = pd.read_csv('./testA.csv')

3.数据预处理

数据精度量化压缩reduce_mem_usage(df)函数很好用,你可以在你做任何模型(图数据除外)特征工程后喂入模型前使用这个函数处理一下特征矩阵,会减少内存消耗。

# 数据精度量化压缩
def reduce_mem_usage(df):
    # 处理前 数据集总内存计算
    start_mem = df.memory_usage().sum() / 1024**2 
    print('Memory usage of dataframe is {:.2f} MB'.format(start_mem))
    
    # 遍历特征列
    for col in df.columns:
        # 当前特征类型
        col_type = df[col].dtype
        # 处理 numeric 型数据
        if col_type != object:
            c_min = df[col].min()  # 最小值
            c_max = df[col].max()  # 最大值
            # int 型数据 精度转换
            if str(col_type)[:3] == 'int':
                if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max:
                    df[col] = df[col].astype(np.int8)
                elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max:
                    df[col] = df[col].astype(np.int16)
                elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max:
                    df[col] = df[col].astype(np.int32)
                elif c_min > np.iinfo(np.int64).min and c_max < np.iinfo(np.int64).max:
                    df[col] = df[col].astype(np.int64)  
            # float 型数据 精度转换
            else:
                if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max:
                    df[col] = df[col].astype(np.float16)
                elif c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max:
                    df[col] = df[col].astype(np.float32)
                else:
                    df[col] = df[col].astype(np.float64)
        # 处理 object 型数据
        else:
            df[col] = df[col].astype('category')  # object 转 category
    
    # 处理后 数据集总内存计算
    end_mem = df.memory_usage().sum() / 1024**2 
    print('Memory usage after optimization is: {:.2f} MB'.format(end_mem))
    print('Decreased by {:.1f}%'.format(100 * (start_mem - end_mem) / start_mem))
    
    return df


# 训练集特征处理与精度量化
train_list = []
for items in train.values:
    train_list.append([items[0]] + [float(i) for i in items[1].split(',')] + [items[2]])
train = pd.DataFrame(np.array(train_list))
train.columns = ['id'] + ['s_' + str(i) for i in range(len(train_list[0])-2)] + ['label']  # 特征分离
train = reduce_mem_usage(train)  # 精度量化


# 测试集特征处理与精度量化
test_list=[]
for items in test.values:
    test_list.append([items[0]] + [float(i) for i in items[1].split(',')])
test = pd.DataFrame(np.array(test_list))
test.columns = ['id'] + ['s_'+str(i) for i in range(len(test_list[0])-1)]  # 特征分离
test = reduce_mem_usage(test)  # 精度量化
# 查看训练集, 分离标签与样本, 去除 id
y_train = train['label']
x_train = train.drop(['id', 'label'], axis=1)
print(x_train.shape, y_train.shape)

# 查看测试集, 去除 id
X_test = test.drop(['id'], axis=1)
print(X_test.shape)

# 将测试集转换为适应 CNN 输入的 shape
X_test = np.array(X_test).reshape(X_test.shape[0], X_test.shape[1], 1)
print(X_test.shape, X_test.dtype)

4.处理类别失衡

# 使用 SMOTE 对数据进行上采样以解决类别不平衡问题
smote = SMOTE(random_state=2021, n_jobs=-1)
k_x_train, k_y_train = smote.fit_resample(x_train, y_train)  
print(f"after smote, k_x_train.shape: {k_x_train.shape}, k_y_train.shape: {k_y_train.shape}")

# 将训练集转换为适应 CNN 输入的 shape
k_x_train = np.array(k_x_train).reshape(k_x_train.shape[0], k_x_train.shape[1], 1)

plt.hist(k_y_train, orientation = 'vertical', histtype = 'bar', color = 'blue')
plt.show() 

5.辅助函数

# 评估函数
def abs_sum(y_pred, y_true):
    y_pred = np.array(y_pred)
    y_true = np.array(y_true)
    loss = sum(sum(abs(y_pred-y_true)))
    return loss

6.模型训练与推理 (TensorFlow 2.2+)

  • 由于数据特征形式较简单,仅 SMOTE 上采样处理后重复、相似性高,使用 过于复杂和深层 的神经网络将 极易过拟合,故本方案采用的模型都较为简单,且均使用了 Dropout 减轻过拟合(几乎是必须的操作)
  • 模型融合并取得提升的前提是 好而不同,为此,3 个模型各自具有一定性能 (较好) 但在细节上都有一定差别 (不同)。模型的设计是本方案提升的关键,例如膨胀卷积、各种池化、分类器等,需细品
  • 直接使用全数据集训练模型,训练策略均为:学习率阶梯衰减策略 LearningRateScheduler + Adam 优化器 + sparse_categorical_crossentropy 损失函数 + batch_size 64 + epoch 30

Net1

class Net1(K.Model):
    def __init__(self):
        super(Net1, self).__init__()
        # 定义4个卷积层和2个最大池化层
        self.conv1 = Conv1D(filters=16, kernel_size=3, padding='same', activation='relu', input_shape = (205, 1))
        self.conv2 = Conv1D(filters=32, kernel_size=3, dilation_rate=2, padding='same', activation='relu')
        self.conv3 = Conv1D(filters=64, kernel_size=3, dilation_rate=2, padding='same', activation='relu')
        self.conv4 = Conv1D(filters=64, kernel_size=5, dilation_rate=2, padding='same', activation='relu')
        self.max_pool1 = MaxPool1D(pool_size=3, strides=2, padding='same')
        self.conv5 = Conv1D(filters=128, kernel_size=5, dilation_rate=2, padding='same', activation='relu')
        self.conv6 = Conv1D(filters=128, kernel_size=5, dilation_rate=2, padding='same', activation='relu')
        self.max_pool2 = MaxPool1D(pool_size=3, strides=2, padding='same')
        # 定义一个dropout层和一个平铺层
        self.dropout = Dropout(0.5)
        self.flatten = Flatten()
        # 定义3个全连接层
        self.fc1 = Dense(units=256, activation='relu')
        self.fc21 = Dense(units=16, activation='relu')
        self.fc22 = Dense(units=256, activation='sigmoid')
        self.fc3 = Dense(units=4, activation='softmax')
        
    # 定义了模型的前向传播过程
    def call(self, x):
        # 连续执行4个卷积层和1个最大池化层
        x = self.conv1(x)
        x = self.conv2(x)
        x = self.conv3(x)
        x = self.conv4(x)
        x = self.max_pool1(x)
        
        # 连续执行2个卷积层和1个最大池化层
        x = self.conv5(x)
        x = self.conv6(x) 
        x = self.max_pool2(x)
        
        # 将输出进行dropout,并且平铺成一维向量
        x = self.dropout(x)
        x = self.flatten(x)
        
        # 连接两个全连接层,并将结果相加,然后再进行一次全连接操作
        x1 = self.fc1(x)
        x2 = self.fc22(self.fc21(x))
        x = self.fc3(x1+x2)
        
        return x

Net3

class GeMPooling(tf.keras.layers.Layer):
    # 定义GeM池化层类
    def __init__(self, p=1.0, train_p=False):
        super().__init__()
        self.eps = 1e-6
        # 初始化p值
        self.p = tf.Variable(p, dtype=tf.float32) if train_p else p

    def call(self, inputs: tf.Tensor, **kwargs):
        # 对输入进行裁剪,使其在[1e-6, 最大值]之间,并且求出x的p次幂
        inputs = tf.clip_by_value(inputs, clip_value_min=1e-6, clip_value_max=tf.reduce_max(inputs))
        inputs = tf.pow(inputs, self.p)
        # 对求得的值在每个通道上进行均值池化,并求出x的1/p次幂
        inputs = tf.reduce_mean(inputs, axis=[1], keepdims=False)
        inputs = tf.pow(inputs, 1./self.p)
        return inputs

# 定义了一个名为Net3的网络
class Net3(K.Model):
    def __init__(self):
        super(Net3, self).__init__()
        # 定义了多个卷积层、池化层、全连接层和dropout层
        self.conv1 = Conv1D(filters=16, kernel_size=3, padding='same', activation='relu',
                            input_shape=(205, 1))
        self.conv2 = Conv1D(filters=32, kernel_size=3, dilation_rate=2, padding='same', activation='relu')
        self.conv3 = Conv1D(filters=64, kernel_size=3, dilation_rate=2, padding='same', activation='relu')
        self.max_pool1 = MaxPool1D(pool_size=3, strides=2, padding='same')
        
        self.conv4 = Conv1D(filters=64, kernel_size=5, dilation_rate=2, padding='same', activation='relu')
        self.conv5 = Conv1D(filters=128, kernel_size=5, dilation_rate=2, padding='same', activation='relu')
        self.max_pool2 = MaxPool1D(pool_size=3, strides=2, padding='same')
        
        self.conv6 = Conv1D(filters=256, kernel_size=5, dilation_rate=2, padding='same', activation='relu')
        self.conv7 = Conv1D(filters=128, kernel_size=7, dilation_rate=2, padding='same', activation='relu')
        
        # 使用定义的GeM池化层
        self.gempool = GeMPooling()
        self.dropout1 = Dropout(0.5)
        self.flatten = Flatten()

        self.fc1 = Dense(units=256, activation='relu')
        self.fc21 = Dense(units=16, activation='relu')
        self.fc22 = Dense(units=256, activation='sigmoid')
        self.fc3 = Dense(units=4, activation='softmax')

    def call(self, x):
        # 执行多个卷积层和池化层操作,提取特征
        x = self.conv1(x)
        x = self.conv2(x)
        x = self.conv3(x)
        x = self.max_pool1(x)
        
        x = self.conv4(x)
        x = self.conv5(x)
        x = self.max_pool2(x)
        
        x = self.conv6(x)
        x = self.conv7(x)

        # 对特征进行GeM池化、dropout和平铺
        x = self.gempool(x)
        x = self.dropout1(x)
        x = self.flatten(x) 
        
        # 执行多个全连接层的操作,并且将结果相加
        x1 = self.fc1(x)
        x2 = self.fc22(self.fc21(x))
        x = self.fc3(x1 + x2)  
        
        return x

Net8

class Net8(K.Model): # 定义名为Net8的网络
    def __init__(self): # 定义构造函数
        super(Net8, self).__init__() # 调用父类的构造函数
        
        # 定义卷积层1-5,每个卷积层都有一些参数:过滤器数、核大小、填充方式、激活函数等
        # 卷积层1的输入形状为(205,1)
        self.conv1 = Conv1D(filters=16, kernel_size=3, padding='same', activation='relu',input_shape = (205, 1))
        self.conv2 = Conv1D(filters=32, kernel_size=3, padding='same', dilation_rate=2, activation='relu')
        self.conv3 = Conv1D(filters=64, kernel_size=3, padding='same', dilation_rate=2, activation='relu')
        self.conv4 = Conv1D(filters=128, kernel_size=3, padding='same', dilation_rate=2, activation='relu')
        self.conv5 = Conv1D(filters=128, kernel_size=5, padding='same', dilation_rate=2, activation='relu')
        
        # 定义最大池化层和平均池化层1
        self.max_pool1 = MaxPool1D(pool_size=3, strides=2, padding='same')
        self.avg_pool1 = AvgPool1D(pool_size=3, strides=2, padding='same')
        
        # 定义卷积层6-7
        self.conv6 = Conv1D(filters=128, kernel_size=5, padding='same', dilation_rate=2, activation='relu')
        self.conv7 = Conv1D(filters=128, kernel_size=5, padding='same', dilation_rate=2,  activation='relu')
        
        # 定义最大池化层和平均池化层2
        self.max_pool2 = MaxPool1D(pool_size=3, strides=2, padding='same')
        self.avg_pool2 = AvgPool1D(pool_size=3, strides=2, padding='same')
        
        # 定义Dropout层,率为0.5
        self.dropout = Dropout(0.5)
    
        # 定义Flatten层
        self.flatten = Flatten()
        
        # 定义全连接层1-3,每个全连接层都有一些参数:神经元数、激活函数等
        self.fc1 = Dense(units=256, activation='relu')
        self.fc21 = Dense(units=16, activation='relu')
        self.fc22 = Dense(units=256, activation='sigmoid')
        self.fc3 = Dense(units=4, activation='softmax')
            
    def call(self, x): # 定义前向传播函数
        x = self.conv1(x) 
        x = self.conv2(x)
        x = self.conv3(x)
        x = self.conv4(x)
        x = self.conv5(x)
        xm1 = self.max_pool1(x)
        xa1 = self.avg_pool1(x)
        x = tf.concat([xm1, xa1], 2) # 将xm1和xa1在第二维上进行拼接,得到输出x
        
        x = self.conv6(x) 
        x = self.conv7(x) 
        xm2 = self.max_pool2(x) 
        xa2 = self.avg_pool2(x) 

        x = tf.concat([xm2, xa2], 2) # 将xm2和xa2在第二维上进行拼接,得到输出x
        
        x = self.dropout(x) 
        x = self.flatten(x) 
        
        x1 = self.fc1(x) # 输入x经过全连接层1后得到x1
        x2 = self.fc22(self.fc21(x)) # 输入x经过全连接层2和全连接层1后得到x2
        x = self.fc3(x1+x2) # x1和x2相加后的结果进入全连接层3,得到最终的输出x
        
        return x

7.软投票融合 + 阈值法

  • 由于 3 个单模均输出类别概率预测值,故使用 Soft Voting 根据 A 榜单模得分设置权重,以实现预测结果加权融合
  • 由于本赛题属于分类问题,可使用阈值法将预测概率不小于 0.5 的类别置 1,其余则置 0
  • 对于预测概率均小于 0.5 的难样本,使用二次处理:若最大预测值比次大预测值至少高 0.04 (这个值需要自己把握),则认为最大预测值足够可信并置 1 其余置 0;否则认为最大预测值和次大预测值区分度不够高,难以分辨不作处理,仅令最小的另外两个预测值置 0
# 根据 A 榜得分,加权融合预测结果
predictions_weighted = 0.35 * predictions_nn1 + 0.31 * predictions_nn3 + 0.34* predictions_nn8
predictions_weighted[:5]

# 准备提交结果
submit = pd.DataFrame()
submit['id'] = range(100000, 120000)
submit['label_0'] = predictions_weighted[:, 0]
submit['label_1'] = predictions_weighted[:, 1]
submit['label_2'] = predictions_weighted[:, 2]
submit['label_3'] = predictions_weighted[:, 3]
submit.head()

# 第一次后处理未涉及的难样本 index
others = []

# 第一次后处理 - 将预测概率值大于 0.5 的样本的概率置 1,其余置 0
threshold = 0.5  
for index, row in submit.iterrows():
    row_max = max(list(row[1:]))  # 当前行中的最大类别概率预测值
    if row_max > threshold:
        for i in range(1, 5):
            if row[i] > threshold:
                submit.iloc[index, i] = 1  # 大于 0.5 的类别概率预测值置 1
            else:
                submit.iloc[index, i] = 0  # 其余类别概率预测值置 0
    else:
        others.append(index)  # 否则,没有类别概率预测值不小于 0.5,加入第一次后处理未涉及的难样本列表,等待第二次后处理
        print(index, row)

# 第二次后处理 - 在预测概率值均不大于 0.5 的样本中,若最大预测值与次大预测值相差大于 0.04,则将最大预测值置 1,其余预测值置 0;否则,对最大预测值和次大预测值不处理 (难分类),仅对其余样本预测值置 0
for idx in others:
    value = submit.iloc[idx].values[1:]
    ordered_value = sorted([(v, j) for j, v in enumerate(value)], reverse=True)  # 根据类别概率预测值大小排序
    #print(ordered_value)
    if ordered_value[0][0] - ordered_value[1][0] >= 0.04:  # 最大与次大值相差至少 0.04
        submit.iloc[idx, ordered_value[0][1]+1] = 1  # 则足够置信最大概率预测值并置为 1
        for k in range(1, 4):
            submit.iloc[idx, ordered_value[k][1]+1] = 0  # 对非最大的其余三个类别概率预测值置 0
    else:
        for s in range(2, 4):
            submit.iloc[idx, ordered_value[s][1]+1] = 0  # 难分样本,仅对最小的两个类别概率预测值置 0        
        
    print(submit.iloc[idx])   
# 保存预测结果
submit.to_csv(("./submit_"+datetime.datetime.now().strftime('%Y%m%d_%H%M%S') + ".csv"), index=False) 
  • 1
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 时间序列一维卷积分类是指对于一个给定的时间序列,通过对其进行卷积操作,将其转化为一个新的序列,并对该序列进行分类。在Matlab2022中,可以通过使用conv函数来实现时间序列一维卷积操作。该函数的使用方法如下: y = conv(x,h) 其中,x为原始时间序列,h为卷积核(也称为滤波器),而y为卷积操作后得到的新序列。在进行卷积操作时,需要注意卷积核的选择,因为不同的卷积核会产生不同的结果。通常情况下,可以选择使用高斯滤波器或者平均滤波器等基本滤波器来进行卷积操作,以消除噪声和平滑数据。 对于使用卷积操作后得到的新序列,可以通过使用机器学习算法进行分类。常见的机器学习算法包括SVM、KNN、Random Forest等,在Matlab2022中,可以使用fitcecoc、fitcknn、fitctree等函数进行分类。这些函数可以根据给定的数据集和标签,学习出模型,并对新样本进行分类。在进行分类时,还需要对数据进行预处理,如归一化、平衡化等操作,以提高分类效果。 综上所述,时间序列一维卷积分类是对时间序列进行卷积操作,并通过使用机器学习算法对新序列进行分类的过程,而在Matlab2022中,可以使用conv函数进行卷积操作,并使用fitcecoc、fitcknn、fitctree等函数进行分类。 ### 回答2: 时间序列一维卷积是信号处理中常用的方法,用于提取时间序列中的特征并进行分类。MATLAB是一个功能强大的数值计算和数据分析软件,其中包括了很多用于信号处理和机器学习的工具箱,可以方便地进行时间序列一维卷积分类。 首先,需要将时间序列数据导入MATLAB中,并将其转化为一维向量形式。然后,在MATLAB的信号处理工具箱中,可以使用conv函数进行一维卷积操作,以提取时间序列中的特征。卷积核的选择可以根据具体情况进行调整,例如可以选择高斯滤波等方式进行平滑处理,或者使用Sobel、Gabor等算子进行特征提取。 完成特征提取后,可以使用MATLAB的统计和机器学习工具箱中的分类算法进行分类。常用的分类算法有支持向量机(SVM)、k最近邻算法(KNN)等,这些算法都在MATLAB中有很好的实现。可以通过选择合适的参数和特征向量来提高分类精度。 总的来说,使用MATLAB进行时间序列一维卷积分类是非常实用和便捷的。通过合理的特征提取和分类算法选择,能够有效地区分和分类不同的时间序列数据,并为实际应用提供便利和支持。 ### 回答3: 时间序列一维卷积分类是应用深度学习进行时间序列分类一种方法,可以使用Matlab 2022进行实现。 首先,需要准备好时间序列数据集,并针对不同类别进行标签标注。 其次,通过Matlab的深度学习工具箱,可以利用一些预定义的卷积神经网络模型,如AlexNet、VGGNet等,搭建时间序列分类模型。也可以根据实际需求构建自定义的卷积神经网络模型。 在模型搭建完成后,可以利用Matlab提供的卷积函数,对时间序列数据进行卷积操作,生成特征序列。在特征序列生成后,需要进行池化操作,将特征序列的维度降低,同时保留重要的特征信息。 最后,将池化后的特征序列输入到全连接层进行分类,通过softmax激活函数输出分类结果。 需要注意的是,时间序列一维卷积分类的效果不仅仅取决于模型的搭建,还与数据的预处理相关。在使用前需要对数据进行归一化、降噪等操作,以提高分类器的准确度和稳定性。 总之,利用Matlab 2022实现时间序列一维卷积分类一种高效的时间序列分类方法,可以应用于不同的实际应用场景中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值