Kaggle寵物收養比賽亞軍復盤

寫在前面

這個比賽是在 19 年年中進行的,已經過去一段時間。完賽時我是季軍,但後面由於冠軍大哥作弊被發現併除名,我在排行榜上的位置也變成了亞軍。這個比賽很有特點,是難得一見的「多模態數據」比賽,也是我唯一的 Solo 金牌,初學者應該能從中學到不少東西。

正文的內容其實也是去年寫的,但原來是放在自己的部落格里,一共也沒幾個人看過。後面我會把之前的一些復盤都發出來,希望可以啟發到有需要的人。

賽題概況

這是一次很有意思的比賽,主辦方是馬來西亞的動物慈善組織 PetFinder。這次比賽的數據種類非常的豐富,基礎數據集包含了了影像數據文本數據和結構化數據,通過不同的數據類型的組合,可以探索很多有意思的演算法。而且這次比賽允許使用外部數據,只要在官方的論壇里把你使用到的數據公開給所有的參賽者,你就可以進行使用了。我一開始以為這種方式會讓這個比賽變得比較蛋疼,但到最後幾天我發現,通過觀察排在前面隊伍使用的外部數據,可以對我們自己的模型產生一些幫助:P。

petfinder.png

這道題也是一個只能用 Kagge 提供的 kernel 執行程式碼的比賽,GPU 版 kernel 規定時間是兩小時以內,所以對編程實現的效率也有一定的要求。

這道題的評價指標是 qwk(quadratic weighted kappa,基本介紹可以看這裡,詳細介紹可以看這裡)。這種指標是沒有辦法直接優化的,參考前面的 crowdflower 的比賽,大家通常把這種題目轉換成回歸問題來做,然後使用一個額外的模組去獲得切分的閾值點。

公開的 kernel 里大多數使用了一種優化器去獲得一個比較好的切分點。但在我閱讀了 crowdflower 第一名的解題方案之後(Chenglong 大神的解題報告)我發現,這種優化器可能沒有辦法去超越直接通過訓練集的分布來獲得閾值點的方式。所以我在最後並沒有使用那些優化器而是直接,把訓練集的分布搬到了測試集上。

數據處理和特徵工程

影像特徵

首先來說一下我對影像數據的處理,我的處理方法和公開的 kernel 差不太多。公開 kernel 的做法是用預訓練的模型,提取特徵。由於目前常用的模型在分類器前得到的 feature map 維度一般很高(比如 resnet50 達到了 2048 維),比較難放進樹模型;kernel 里大多使用 SVD 對這種高維特徵進行的降維,然後把降維後的數據當作特徵,放到模型里訓練。

我在這個基礎上做了兩個比較有意思的操作。一是我用一個全連接神經網路直接訓練了一個基於這種高維影像特徵的回歸模型,直接回歸得到一個 y,然後進行 stacking。二是我用 kmeans 對高維特徵進行了聚類,獲得了一列類別 label,這組特徵也對後面的建模提供了一些幫助。

曾經想過在訓練這個回歸器的同時也 finetune backbone 網路,但一方面是實在無法在規定時間裡完成,另一方面發現效果其實並沒有不 finetune 好。

文本特徵

對於文本數據,公開的 kernel 里大多隻採用了詞頻 tfidf 變換的方式,再結合 svd 獲得特徵。熟悉 nlp 的朋友應該都知道,詞頻特徵雖然很強,但這隻能獲得淺層文本特徵。稍微更進一步可以做一些主題模型,比如 lda。但對於深層的語義資訊必須用神經網路來進行提取。這道題比較有趣的是 description 欄位文本的長度範圍比較大,有的文本可以到幾百個詞。這對編程實現有一些技巧上的要求。

meta 特徵

Meta 特徵指數據集中用 googleAPI 獲取的對影像和文本的分析結果,例如有圖片分類的結果、描述文本的 NER 結果等等。在處理的時候我感覺有兩點需要注意,一是儘可能全面的去讀取結果 JSON 文件,然後進行特徵構建,公開的 kernel 都只讀取了小部分的內容。二是需要對處理程式碼做一些優化,公開 kernel 的實現比較緩慢。假設用 kernel 的寫法去讀取我最終使用的資訊,可能這部分就要花費 30 分鐘,我最終的程式碼在 4 分鐘左右就可以完成所有操作。

統計特徵和未解之謎

這道題還有一個比較有意思的地方,是一個叫 rescuerID 的欄位,測試集合訓練集中沒有重疊的 rescuerID。如果我們在訓練模型的時候採用普通的 kfold,那麼驗證集和訓練及之間可能會有重合的 ID。這樣的後果就是,驗證集的分數特別的高,和 LB 成績有一個明顯的 gap。

為了避免這種情況,我在這個比賽中是全程使用了 Groupkford,這種做法可以得到和測試集劃分同樣的不帶重疊 rescuerID 的驗證集和訓練集。而且這這種方式訓練時收斂的輪數會比普通 kfold 的少很多。而且獲得的線下驗證成績跟 LB 成績差的比較小。我在公開排行榜的後期,線下成績和線上成績基本上是完全一致的。

但我在最後提交的時候,選擇了一個 stratifiedkfold 和一個 groupkfold,結果普通 kfold 的成績在 B 榜是略好於 groupkfold 的。這裡不得不稱讚一下 Kaggle 可以選兩個 submission 的設計,可以讓參賽者發揮儘可能多的實力,少留下遺憾。

對於提供的表格數據,我只進行一些非常常規的統計,沒有什麼特別的。在特徵工程的時候,完全參考 local CV 來進行,只要這個特徵能夠提高 CV 成績我就會使用它。在做 CV 時使用的指標主要是 RMSE,QWK 成績由於不太穩定,只是作為一個輔助資訊來參考。

模型

我的模型架構如下圖所示,是一個比較複雜 stacking 的架構。第一層及以下是特徵提取層,第二層是模型,第三層是融合。影像部分我用了四種 ImageNet 預訓練分類模型來提取圖片特徵(圖中 image feature),並傳入 DNN 進行處理獲得對目標預測結果,然後將預測結果作為其他模型的特徵。第二層使用了 lightgbm,xgboost,catboost 三種樹模型,以及一個融合了文本、影像、統計特徵的 RNN+DeepFM 網路。

model.png

比較有意思的模型是那個 NLPDeepFM 模型,處理 NLP 資訊的神經網路是一個雙向 GRU+Attention。由於表格數據里有大量的 categorical 列,所以我構造了一個 FM 來處理類別數據。對於統計特徵等等其他的浮點特徵則將他們直接放到網路里的 DNN 部分。最後將各部分的中間結果進行拼接,傳入一個回歸器得到最終的結果。

nn_model.png

從線下成績來看神經網路模型雖然很複雜,但是他的能力還是和樹模型有較大的差距。但是兩者的差異性是比較明顯的,在最後的融合過程中能帶來很好的收益。最後將所有模型的結果通過一個嶺回歸器進行 stacking。

寫在最後

不得不說的是這次比賽的運氣還是挺好的,因為 qwk metric 其實是很不穩定的。最後我的 B 榜排名相比 A 榜成績有了非常大的提升,這個是我沒有預料到的。但我在 B 榜的線下成績和線上成績是比較吻合的,這說明我原來採用的線下驗證策略應該比較靠譜的。其實做比賽真的還是要盡量合理地去構建驗證集,然後提高驗證集的成績,強行擬合 LB 沒有任何意義,也學不到東西。心態也要放平,盡人事,聽天命,不要有太多雜念。

最後,感謝論壇里的大神寫了這麼多優秀的公開 kernel 給我們學習。我的 kernel 也已經開源,也算是回饋一下社區,感興趣的朋友可以看這裡

【歡迎關注公眾號:樸素人工智慧】