题目与数据集分析
大家好,我是展翅高飞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下,这种不稳定还没有被解决,所以选择模型时一定要考虑是否可以复现的问题。
比赛分享
- 在比赛过程中,所有的优化,无论是有效果还是没有效果,都做好笔记,这样是为了方便后面的优化,不会出现重复尝试的情况,而且在复现代码和复盘比赛的时候,笔记都是最重要的文件,这里推荐有道云笔记,非常的方便好用
- 尽可能选择有理论支撑的优化,如果不清楚原因,尽可能分析理解一下,防止因为随机性或偶然性导致的假优化
- 一定坚持下来,因为越是后面越可能有突破,随着对赛题的理解和学习的深入,越可能有一些别人想不到的点
- 如果没有思路的时候,多看看技术类文章和论坛,有不少优化的想法都来自于广大同胞们。
--END--