天天看點

零基礎入門資料挖掘-心跳信号分類預測亞軍賽事解決方案分享題目與資料集分析解決方案參賽感受與思考

題目與資料集分析

大家好,我是展翅高飛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/随機森林
  1. CNN1後使用KNN預測
  2. CNN1後使用xgboost預測
  3. CNN1後使用随機森林預測
  4. CNN3後使用xgboost預測

參賽感受與思考

随機性問題

這個比賽中,如果想獲得一個0.98的精度,拿個KNN模型,不用調參就可以,如果想要獲得0.99的精度,用一個CNN模型就可以辦到,但如果希望進入top20,還是需要花大力氣來将精度再向上調整到0.995才可以。

為了這0.5%的正确率,我大概嘗試了13個不同的模型融合,其中包括多個CNN模型和CNN與傳統機器學習的組合模型,但這種方式有個很大的問題,就是模型不穩定,CNN的随機性導緻每次結果都會不一緻,多個CNN相關的模型,會放大這種不穩定性,目前在多GPU下,這種不穩定還沒有被解決,是以選擇模型時一定要考慮是否可以複現的問題。

比賽分享

  1. 在比賽過程中,所有的優化,無論是有效果還是沒有效果,都做好筆記,這樣是為了友善後面的優化,不會出現重複嘗試的情況,而且在複現代碼和複盤比賽的時候,筆記都是最重要的檔案,這裡推薦有道雲筆記,非常的友善好用
  2. 盡可能選擇有理論支撐的優化,如果不清楚原因,盡可能分析了解一下,防止因為随機性或偶然性導緻的假優化
  3. 一定堅持下來,因為越是後面越可能有突破,随着對賽題的了解和學習的深入,越可能有一些别人想不到的點
  4. 如果沒有思路的時候,多看看技術類文章和論壇,有不少優化的想法都來自于廣大同胞們。

--END--