Press "Enter" to skip to content

零基础入门数据挖掘-心跳信号分类预测亚军赛事解决方案分享

本站内容均来自兴趣收集,如不慎侵害的您的相关权益,请留言告知,我们将尽快删除.谢谢.

来源:阿里云天池
作者:展翅高飞hhh

 

题目与数据集分析

 

大家好,我是展翅高飞hhh,一个正在努力转算法的Java程序员,今天给大家分享下天池零基础入门数据挖掘-心跳信号分类预测参赛的感受和我的解决方案,希望对大家学习有所帮助。

 

这是第一次参加比赛,才疏学浅,如有问题感谢指正!

 

此次题目给出的数据是心电图,可以知道这是一个时序数据,所以直接使用传统机器学习中的特征学习方法,是很难得到一个较高的成绩的,但使用一维卷积这种可以感知时序数据分布的模型,可能会有不错的表现,但这个数据的一个难点在于,我们无法通过数据直观的判断问题,因为心电图还是很难看懂的,甚至你都很难判断这是否是一个脏数据,我曾经中间学习了一下,但是因为难度确实超过了自己的能力范围,就放弃了,所以非医学专业的人理论上是难以剔除掉脏数据的,所以就放下心来,将经力放在模型上即可。

 

赛事地址,目前长期赛已经开放,大家都可以前往报名参与学习:

 

https://tianchi.aliyun.com/competition/entrance/531883/introduction

 

赛事经验原文地址,在天池实验室可以直接调试代码:

 

https://tianchi.aliyun.com/notebook-ai/detail?postId=213282

 

解决方案

 

在调优CNN模型时,有几个比较通用的优化方法

 

1.将学习率设置为动态学习率,但是不能将学习率优化的太小,会过拟合,大概自动减小一次,减少到原来的20%为最佳

 

# 动态学习率
def learning_rate_decay(epoch, init_learning_rate):
    decay_rate = [30]
if epoch in decay_rate:
return init_learning_rate * 0.2
else:
return init_learning_rate
# 设置动态学习率回调类
learning_rate_decay = LearningRateScheduler(learning_rate_decay, verbose=0)

 

2.将SGD(随机梯度下降)优化器改为Adam(自适应动量梯度下降)优化器,原因可能为,随机梯度下降可能会停留在鞍点/局部最小值

 

3.将激活函数relu改为alpha=0.05的leakyRelu,这个的原因可能为,leakyRelu减少了信息丢失,毕竟relu会完全抛弃小于0的信息

 

4.使用尽可能多地数据用于模型训练,当训练模型的数据从80%->90%时,又有了10分左右的提升,当使用全数据后,模型又再次突破

 

5.训练轮次不要太低,起初因为硬件原因,训练的轮数大概在20-25轮左右,后来加到了35轮,就得到了15分左右的提升

 

第一个CNN模型

 

这个模型相对较为简单,使用了 4层卷积网络 + 一层全连接层 + 一层softmax分类

 

这里在初始层使用了较大的卷积,然后逐渐减小卷积核大小,这样可以一定程度上提升模型,原因是,一个有效的心跳特征,最多可能是由10个左右的时间点构成的,一开始使用较大卷积核可以减

 

少模型的复杂度,因为后面的模型都使用了小卷积核,这里还可以提升总体模型的多样性

 

# 构建模型
model = Sequential()
model.add(Conv1D(8, 11, kernel_initializer=glorot_normal(seed=1), input_shape=(205, 1)))
model.add(LeakyReLU(alpha=0.05))
model.add(Conv1D(16, 9, kernel_initializer=glorot_normal(seed=1)))
model.add(LeakyReLU(alpha=0.05))
model.add(Conv1D(32, 5, kernel_initializer=glorot_normal(seed=1)))
model.add(LeakyReLU(alpha=0.05))
model.add(Conv1D(64, 3, kernel_initializer=glorot_normal(seed=1)))
model.add(LeakyReLU(alpha=0.05))
model.add(Flatten())
model.add(Dense(64, kernel_initializer=glorot_normal(seed=1)))
model.add(LeakyReLU(alpha=0.05))
model.add(Dense(4, activation='softmax', kernel_initializer=glorot_normal(seed=1)))

adam = Adam()
model.compile(optimizer=adam,
              loss='categorical_crossentropy',
              metrics=['acc'])
cw = {0:1, 1:5, 2:1, 3:1}
# 全数据集训练
history = model.fit(ld(x), y,
                    batch_size=128, epochs=60,
                    shuffle=False,
                    verbose=0,
                    callbacks=[learning_rate_decay],
     class_weight=cw)     
model.save('cnn_baseline2.h5')

 

第二个CNN模型

 

这个CNN模型使用了具有随机失活的网络结构,系数为0.1,结构为 7层卷积网络 + 一层全连接层 + 一层softmax分类

 

因为随机失活会一定程度上抑制过拟合,所以网络的结构可以设计的更加复杂一些,使用了更多地层数,和更多的神经元,并使用小卷积核,可以更细粒度的学习特征

 

# 设置模型保存策略
checkpoint = ModelCheckpoint('./cnn_baseline.h5', monitor='acc',
         verbose=0, save_best_only=True)

# 构建模型
dropIndex = 0.1
model = Sequential()
model.add(Conv1D(16, 5, kernel_initializer=glorot_normal(seed=1), input_shape=(205, 1)))
model.add(LeakyReLU(alpha=0.05))
model.add(layers.Dropout(dropIndex, seed=1))
model.add(Conv1D(16, 5, kernel_initializer=glorot_normal(seed=1)))
model.add(LeakyReLU(alpha=0.05))
model.add(layers.Dropout(dropIndex, seed=1))
model.add(Conv1D(32, 5, kernel_initializer=glorot_normal(seed=1)))
model.add(LeakyReLU(alpha=0.05))
model.add(layers.Dropout(dropIndex, seed=1))
model.add(Conv1D(64, 3, kernel_initializer=glorot_normal(seed=1)))
model.add(LeakyReLU(alpha=0.05))
model.add(layers.Dropout(dropIndex, seed=1))
model.add(Conv1D(64, 3, kernel_initializer=glorot_normal(seed=1)))
model.add(LeakyReLU(alpha=0.05))
model.add(layers.Dropout(dropIndex, seed=1))
model.add(Conv1D(128, 3, kernel_initializer=glorot_normal(seed=1)))
model.add(LeakyReLU(alpha=0.05))
model.add(layers.Dropout(dropIndex, seed=1))
model.add(Conv1D(128, 3, kernel_initializer=glorot_normal(seed=1)))
model.add(LeakyReLU(alpha=0.05))
model.add(layers.Dropout(dropIndex, seed=1))
model.add(Flatten())
model.add(Dense(64, name='denseOut', kernel_initializer=glorot_normal(seed=1)))
model.add(LeakyReLU(alpha=0.05))
model.add(layers.Dropout(dropIndex, seed=1))
model.add(Dense(4, activation='softmax', kernel_initializer=glorot_normal(seed=1)))

adam = Adam()
model.compile(optimizer=adam,
              loss='categorical_crossentropy',
              metrics=['acc'])
cw = {0:1, 1:2, 2:1, 3:1}
# 全数据集训练
history = model.fit(ld(x), y,
                    batch_size=128, epochs=epochs,
                    shuffle=False,
                    verbose=0,
                    callbacks=[checkpoint, learning_rate_decay],
                    class_weight=cw)        

 

第三个CNN模型

 

这个CNN模型是基于残差网络的思想设计的, 所以这个网络理论上可以设计的非常大,结构为 10层卷积网络 + 一层全连接层 + 一层softmax分类

 

这个网络是设计的最复杂的一个模型,卷积核都使用3×3大小的卷积核,保证了模型可以更细粒度的学习特征,同时可以保证与之前的模型偏好不一致,保证最总模型的多样性

 

# 构建模型
inputDense = Input(shape=(205, 1,), dtype='float32', name='inputName')
x0 = layers.Conv1D(32, 3, padding='same', kernel_initializer=glorot_normal(seed=1))(inputDense)
x1 = layers.LeakyReLU(alpha=0.05)(x0)
x1 = layers.Conv1D(32, 3, padding='same', kernel_initializer=glorot_normal(seed=1))(x1)
x1 = layers.LeakyReLU(alpha=0.05)(x1)
x2 = layers.add([x1, x0])
x2 = layers.Conv1D(32, 3, padding='same', kernel_initializer=glorot_normal(seed=1))(x2)
x3 = layers.LeakyReLU(alpha=0.05)(x2)
x3 = layers.Conv1D(32, 3, padding='same', kernel_initializer=glorot_normal(seed=1))(x3)
x3 = layers.LeakyReLU(alpha=0.05)(x3)
x4 = layers.add([x3, x2])
x4 = layers.Conv1D(64, 3, padding='same', kernel_initializer=glorot_normal(seed=1))(x4)
x5 = layers.LeakyReLU(alpha=0.05)(x4)
x5 = layers.Conv1D(64, 3, padding='same', kernel_initializer=glorot_normal(seed=1))(x5)
x5 = layers.LeakyReLU(alpha=0.05)(x5)
x6 = layers.add([x5, x4])
x6 = layers.Conv1D(64, 3, padding='same', kernel_initializer=glorot_normal(seed=1))(x6)
x7 = layers.LeakyReLU(alpha=0.05)(x6)
x7 = layers.Conv1D(64, 3, padding='same', kernel_initializer=glorot_normal(seed=1))(x7)
x7 = layers.LeakyReLU(alpha=0.05)(x7)
x8 = layers.add([x7, x6])
x8 = layers.Conv1D(64, 3, padding='same', kernel_initializer=glorot_normal(seed=1))(x8)
x9 = layers.LeakyReLU(alpha=0.05)(x8)
x9 = layers.Conv1D(64, 3, padding='same', kernel_initializer=glorot_normal(seed=1))(x9)
x9 = layers.LeakyReLU(alpha=0.05)(x9)
x10 = layers.add([x9, x8])
x0 = layers.Flatten()(x10)
x0 = layers.Dense(64, kernel_initializer=glorot_normal(seed=1))(x0)
x0 = layers.LeakyReLU(alpha=0.05)(x0)
output = layers.Dense(4, activation='softmax', name='d5', kernel_initializer=glorot_normal(seed=1))(x0)
model = Model(inputDense, output)

adam = Adam()
model.compile(optimizer=adam,
              loss='categorical_crossentropy',
              metrics=['acc'])

cw = {0:1, 1:5, 2:1, 3:1}
# 全数据集训练
history = model.fit(ld(x), y,
                    batch_size=128, epochs=epochs,
                    shuffle=False,
                    callbacks=[learning_rate_decay],
                    verbose=0,
                    class_weight=cw)        
model.save('cnn_baseline3.h5')

 

KNN模型与随机森林模型

 

之所以使用KNN模型,是希望后面做预测的时候,直接找一个最接近的数据即可,如果数据量特别巨大的情况下,所有的可能心电图都包含时,那这个的正确率就100%了,所以基于这种想法,使用了KNN模型,模型简单,效果尚可。

 

后面又使用了随机森林模型,这个与使用KNN的思想类似,但是KNN是物理距离,而随机森林是概率分布,从两个方面就近选择了结果,同样模型不复杂,且效果尚可。

 

# -----------------------------------模型4 KNN-----------------------------------
print("模型4,5,6")
knn = KNeighborsClassifier(n_neighbors=1,  n_jobs=-1).fit(x,y)
# -----------------------------------模型5 随机森林-----------------------------------
forest = RandomForestClassifier(n_estimators=130,
                                random_state=0, 
                                criterion='entropy',
                                n_jobs=-1).fit(x, y)

 

组合模型

 

组合模型是三个CNN模型剔除掉最后一层后,再接传统的机器学习(KNN,随机森林,xgboost)算法组成的集成算法。

 

三种CNN模型的偏好不一致,所以最后一层输出的64维的特征也一定是不一致的,后面接传统机器学习模型,进行模型融合,可以小程度提升模型效果。

 

model = models.load_model('./cnn_baseline.h5')
model2 = models.load_model('./cnn_baseline2.h5')
model3 = models.load_model('./cnn_baseline3.h5')
# ------------------模型6,7,8,9 CNN1后使用KNN预测 CNN1后使用xgboost预测 CNN1后使用随机森林预测 CNN3后使用xgboost预测------------------
# 基于CNN模型进行训练的模型的预测
# 使用CNN模型处理训练数据
x1 = model.predict(ld(x))
x1 = pd.DataFrame(x1)
x2 = model2.predict(ld(x))
x2 = pd.DataFrame(x2)
x3 = model3.predict(ld(x))
x3 = pd.DataFrame(x3)
# 处理使用CNN模型处理测试数据
testData1 = model.predict(ld(testData))
testData1 = pd.DataFrame(testData1)
testData2 = model2.predict(ld(testData))
testData2 = pd.DataFrame(testData2)
testData3 = model3.predict(ld(testData))
testData3 = pd.DataFrame(testData3)
# CNN后的 KNN 模型
cnn_knn = KNeighborsClassifier(n_neighbors=1, n_jobs=-1).fit(x1,y)
cnn1Knn = cnn_knn.predict_proba(testData1)
# CNN后的 xgboost 模型
param = {
'booster': 'gbtree',
'objective': 'multi:softmax',
'num_class': 4,
'nthread': 8 ,
'eta': 0.3,
"lambda": 0.8,
'eval_metric': 'mlogloss',
'gamma': 0
}
bst1 = xgb.train(param, xgb.DMatrix(x1, y), 40)
bst3 = xgb.train(param, xgb.DMatrix(x3, y), 40)
cnn1Xgb = bst1.predict(xgb.DMatrix(testData1))
cnn3Xgb = bst3.predict(xgb.DMatrix(testData3))
cnn1Xgb = to_categorical(cnn1Xgb)
cnn3Xgb = to_categorical(cnn3Xgb)
# CNN后的forest 模型
forest = RandomForestClassifier(n_estimators=130,
                                random_state=0, 
                                criterion='entropy',
                                n_jobs=-1).fit(x1,y)
cnn1Tree = forest.predict_proba(testData1)

 

模型小结

 

整个模型由 九个
模型组成通过模型融合得到最终结果

 

前三个模型为 CNN模型
, 其中包括: 带dropout的CNN模型, 普通的CNN模型, 使用残差网络的CNN模型

 

第四个模型为 KNN模型

 

第五个模型为 随机森林模型

 

最后四个模型,使用CNN模型对数据进行预测,获取到尾层的特征输出, 然后放入KNN/xgboost/随机森林

 

 

CNN1后使用KNN预测

 

CNN1后使用xgboost预测

 

CNN1后使用随机森林预测

 

CNN3后使用xgboost预测

 

 

参赛感受与思考

 

随机性问题

 

这个比赛中,如果想获得一个0.98的精度,拿个KNN模型,不用调参就可以,如果想要获得0.99的精度,用一个CNN模型就可以办到,但如果希望进入top20,还是需要花大力气来将精度再向上调整到0.995才可以。

 

为了这0.5%的正确率,我大概尝试了13个不同的模型融合,其中包括多个CNN模型和CNN与传统机器学习的组合模型,但这种方式有个很大的问题,就是模型不稳定,CNN的随机性导致每次结果都会不一致,多个CNN相关的模型,会放大这种不稳定性,目前在多GPU下,这种不稳定还没有被解决,所以选择模型时一定要考虑是否可以复现的问题。

 

比赛分享

 

 

在比赛过程中,所有的优化,无论是有效果还是没有效果,都做好笔记,这样是为了方便后面的优化,不会出现重复尝试的情况,而且在复现代码和复盘比赛的时候,笔记都是最重要的文件,这里推荐有道云笔记,非常的方便好用

 

尽可能选择有理论支撑的优化,如果不清楚原因,尽可能分析理解一下,防止因为随机性或偶然性导致的假优化

 

一定坚持下来,因为越是后面越可能有突破,随着对赛题的理解和学习的深入,越可能有一些别人想不到的点

 

如果没有思路的时候,多看看技术类文章和论坛,有不少优化的想法都来自于广大同胞们。

 

Be First to Comment

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注