Kaggle求生:亞馬遜熱帶雨林篇
大家好,我是思聰 · 格里爾斯,我將向您展示如何從世界上某些競爭最激烈的比賽中拿到金牌。我將面臨一個月的比賽挑戰,在這些比賽中缺乏正確的求生技巧,你甚至拿不到銅牌。這次,我來到了亞馬遜熱帶雨林。
當我和我的隊友們進入這片雨林的時候,這場長達三個月的比賽已經進行了兩個月,想要彎道超車,後來居上,那可不是件容易的事。我們最後在比賽結束的時候,獲得了Public Leaderboard第一, Private Leaderboard第六的成績,斬獲一塊金牌。這個過程中,我們設計並使用了一套簡潔有效的流程,還探索出了一些略顯奇怪的技巧。
這套流程包括數據問題分析、查找資料、制定和調整解決方案、結果的記錄分析等,使用它,我們從Public Leaderboard一百多名起步,一路殺進金牌區,一直到比賽結束前,佔據Public Leaderboard榜首數天,都沒有遇到明顯的阻力。在這篇文章里,我不僅會介紹這個流程本身,還會把我們產生這套流程的思路也分享出來,讓大家看完之後,下次面對一個新問題,也知道該如何下手。這些思路和經驗也不僅僅適用於Kaggle比賽,對其他實際的機器學習項目也有很好的借鑒意義,我們從中收穫頗豐,希望各位讀者也能如此。
在文章的結尾,我還會講一講我們比賽最後一夜的瘋狂與刺激,結果公布時的懵逼,冷靜之後的分析,以及最後屈服於偉大的隨機性的故事。
(PS:由於本文較長,約兩萬字,建議時間有限的讀者可以先只讀第3部分探險開始:解決方案的規劃和選擇)
目錄:
- 初探雨林:概述(Overview)與數據(Data)
- 痕迹與工具:討論區(Discussion)和Kernel區
- 探險開始:解決方案的規劃和選擇
- 學習,奮鬥,結果與偉大的隨機性
- 隊伍成員介紹
1. 初探雨林:概述(Overview)與數據(Data)
探險的第一步是要弄清楚問題的定義和數據的形式,這部分看起來會比較繁瑣,但是如果想要走得遠,避免落入陷阱,這一步還是比較值得花功夫的,所以請大家耐心地看一下。如果是已經參加過這個比賽的讀者,可以直接跳過這個部分。
我們先看一下這個比賽的標題:
- Planet: Understanding the Amazon from Space
- Use satellite data to track the human footprint in the Amazon rainforest
翻譯一下就是
- Planet(舉辦比賽的組織名):從太空中理解亞馬遜
- 使用衛星數據來跟蹤人類在亞馬遜雨林中的足跡
看來這是一個關於亞馬遜雨林的衛星圖像比賽,為了進一步了解問題,我們需要閱讀的是比賽的Overview和Data兩個部分。
1.1 描述(Description)
Overview的Description(描述)部分告訴了我們主辦方的意圖,原來是為了從衛星圖片監控亞馬遜雨林的各種變化,以便當地政府和組織可以更好保護亞馬遜雨林。看我發現了什麼,這個Overview的尾部附帶有一個官方提供的ipython notebook代碼的鏈接:
Getting started with the data - now with docs!
這個ipython notebook有不少信息量,包含對數據的讀取,探索,相關性分析,可以大致讓我們對數據有一個基本的感覺,並且可以下載下來進一步分析,可以省上不少功夫。如果官方沒有提供這樣一個notebook, Kernel區一般也會有人發出自己的一些分析,實在沒有最好也自己做一下這個步驟,因為這個可以為後面的一些決策提供信息。
1.2 數據(Data)
然後我們可以先跳過Overview的其他部分,去看一下Data部分。Data部分一般提供數據的下載和說明,先把數據點著下載,然後仔細閱讀說明。其中訓練集大概有四萬張圖像,測試集大概有六萬張圖像。數據說明包括了數據的構成和標籤的來源。我們可以先看一下這張圖:
這次比賽中的每個圖像樣本都是256*256像素,並且每個像素寬約對應地面的寬度大約是 3.7m。然後每個樣本都有jpg和tif兩種格式,tif好像是比正常的RGB通道多了一個紅外線通道,嗯,可能會有用。
數據的標籤有17個類,其中4個天氣類,7個常見普通類,以及6個少見普通類。
天氣類包括:Clear,Partly Cloudy,Cloudy,Haze。其中只要有 Cloudy的就不會有其他類別(因為被雲覆蓋住了什麼都看不到)。
常見普通類包括:Primary Rain Forest,Water (Rivers & Lakes),Habitation,Agriculture,Road,Cultivation,Bare Ground。
少見普通類包括:Slash and Burn,Selective Logging,Blooming,Conventional Mining,"Artisinal" Mining,Blow Down。
普通類描述的是叢林中出現的各種景觀,包括河流、道路、耕種用地、採礦基地等等。
下面是一些樣本的示例圖,圖中用紅色字體打上了類別信息:
官方還附帶了這些類別的說明和相關新聞報道,其中類別的說明最好讀一下,有助於對任務的理解。我們在最開始對每個類的含義和特性進行了分析,然而最後探索出來的方案並沒有對不同類別進行針對性的處理。雖說如此,下次遇到一個新問題我們仍然會嘗試進行分析。
理論上每幅圖都擁有一個天氣類外加若干個普通類,所以這是一個Multi-Label (多標籤)的問題。其中少見普通類比較少,大概四萬個樣本中有的類甚至不到一百個。在最後的Submission中,我們要提交一個包含大概六萬個樣本的標籤的csv文件,其中大約四萬個用於Public Leaderboard的分數計算,兩萬個用於Private Leaderboard的分數計算。
官方還提到數據是眾包平台上標註的,所以會包含一些錯誤的標籤,因為其中一些圖像他們組織里的專家都分不清楚,更不要說眾包標註的工人了,所以我們要意識到這是一個富含雜訊的數據集。最後的比賽結果也證實了這一點,因為前63名的分數都在93.0%到93.3%之間,甚至都突破不了94%。這裡的分數是指什麼呢?請看下一小節。
1.3 評價指標(Evaluation)
弄清了問題的形式,接下來我們可以返回閱讀Overview的剩下部分。Evaluation告訴我們這次的評價指標是各個樣本F2-score的均值,F2-score的定義如下
p是精度(precision),表示我們預測出來的類出現在標籤中的比例;r是召回率(recall),表示標籤中出現的類被我們預測出來的比例。F2-score相對偏好召回率,所以在比較不確定的時候,預測多一點可能會比預測准一點來得好。
1.4 獎金(Prize)與比賽時間線(Timeline)
這次比賽的獎金第一名有3萬美刀,第二名2萬美刀,第三名1萬美刀。雖然沒有類似Zillow那個一百多萬美刀那麼驚人,但也是一筆不少的外快了。
比賽開始於4月20日,7月13號則是參加截止日期以及合隊截止日期。一般來說,即便你是和幾個小夥伴一起參賽,也不要急著太早合隊,因為每個隊伍每天只有固定的提交次數可用,不合隊的話所有人加起來可以獲得數倍的提交機會,這對於初期的方案探索是非常有益的。
另外,7月13日也同時是預訓練模型聲明截止的時間,因為圖像類比賽經常會使用ImageNet上預訓練過的模型,為了公平起見,所有人都只能使用討論區一個置頂帖中聲明過的預訓練模型,如果選手所使用的預訓練模型沒在裡面,那就要在截止時間前自覺去帖子里添加聲明,否則視為作弊。
比賽最後於UTC時間7月20號晚上11點59分結束,對於身在國內的我們來說,這意味著最後一天要通宵陪歐洲人民衝刺到早上八點。
2. 痕迹與工具:討論區(Discussion)和Kernel區
一個老練的探險隊員要善於利用前人留下的信息。我們隊里常說,一個能善於使用討論區、工程能力不差並且有時間精力的人,應該有很大可能性拿到一個銀牌。討論區里包含著官方的一些申明通知,還有其他隊伍的一些經驗分享,Kernel區包含了一些公開發布的代碼。這些都是所有參賽隊伍共享的信息,對於一個新手和後進場的隊伍,從這裡面獲取足夠信息可以取得比較好的開端。
此外,常被忽略的一個點是,其他一些已經結束的類似比賽中,也包含了大量對這個比賽有用的信息。比如這個比賽是衛星圖像的多標籤分類比賽,那麼其他衛星圖像比賽or圖像or多標籤分類比賽的信息都會對這個比賽有用,這些比賽的討論區經常包含了大量優秀的解決方案,這對我們後面設計方案會有幫助。
最後要小心的是,討論區裡面的發言也不一定對,Kernel區的代碼可能也有些bug,比如這次比賽有一些隊伍因為使用了一個有bug的submission生成代碼,最後都掉了八九百名,場面十分血腥。
我們從參賽的時候從討論區獲取的一些有用信息如下:
- tif圖像數據在RGB通道之外包含紅外通道,按理來說多使用上這個信息應該會提高效果,然而恰恰相反,討論區的人說用了之後反而變差了,這可能是因為有些圖片的紅外通道跟RGB通道是錯開的。所以到比賽結束我們也只是稍微嘗試了一下去利用tif的紅外通道,並沒有在上面浪費太多時間。
- 其他隊伍有可能使用了哪些預訓練模型,每個模型的大體性能如何,這給我們提供了很有用的參考,比如我們在嘗試了一些比較小規模的模型(如ResNet18、ResNet34)之後,以為這些模型已經夠了,再大再複雜的模型可能會過擬合,但是從討論區我們看到,大模型還是有明顯優勢的,這就促使我們敢於花大量時間去跑那些笨重的ResNet152、DenseNet161等預訓練模型。
- 從其他類似比賽的討論區我們看到,高分隊伍一般不會使用特別複雜的Ensemble方法,甚至會僅僅使用簡單的bagging和stacking(下面會講),所以我們就把更多的精力花在單模型的調優。事實證明,即便到了比賽後期,還不斷有一些更好的單模型新鮮出爐,使我們Ensemble後的效果猛地一竄,竄到了Public Learderboard前三乃至第一。
3. 探險開始:解決方案的規劃和選擇
以上準備可能會花上你一到兩天的時間,但磨刀不誤砍柴工,我們也差不多可以開始我們的征程了。
3.1 BCE Loss訓練 和 F2-Score閾值調優
上面提到,這次比賽問題是Multi-Label(多標籤)分類問題,評價指標是F2 Score,但F2 Score並不是可以直接優化的值,所以我們採取的方法是:
- 每個輸出接Sigmoid層,分別預測每個類的概率,使用Binary Cross Entropy Loss優化。這其實是多標籤分類問題的常見套路,本質是獨立地對每個類做二分類學習。雖說不同類之間可能存在相互依賴,但我們假設這些依賴可以通過共享底層參數來間接實現。
- 在訓練上述二分類任務時,由於正負樣本數目不均衡,我們並不能直接拿p = 0.5作為二分類的閾值進行預測,而需要為每個類搜索一個合適的閾值,使得整體的F2-Score最大。具體來說,我們採取了討論區放出的一個方案,貪婪地對每個類的閾值進行暴力搜索,邏輯如下:
# 假裝是Python的偽代碼給定17個類的閾值構成的閾值向量,每個元素初始化為0.2for 第i=0到第i=16的類: 固定其他類的閾值不動 在第i個類上分別嘗試0.01, 0.02, 0.03, ... 0.99總共100個候選 每個候選構成一個新的閾值向量,新的閾值向量可以在樣本集上獲得一個F2 找到100個F2中最高的,取其對應的候選閾值,就作為第i個類的閾值 這樣就把第i個位置的0.2改成了一個新的值,接著貪婪地處理下一個類
這肯定不是最優的方案,但卻已經足夠好。雖然後期我們優化了討論區的代碼使用GPU加速計算,並嘗試了諸如隨機初始值、隨機優化順序然後多次隨機取最好,步長大小調優,進化計算搜索等方法,但都因為提交次數限制沒來得及測試。
不過要注意的是,雖然我們以BCE Loss為訓練目標,但實際上BCE Loss變低,F2-Score卻未必變高,可以想像一下,如果模型把一些本來就能被分對的樣本的預測概率變得更搞,BCE Loss是會降低,但F2-Score還是一樣。
為什麼要強調這點呢?因為有隊友在探索Ensemble方法的時候,看著BCE Loss不好就放棄了;再往後另一個隊友重新實現了一樣的Ensemble方法,看的是F2-Score,卻發現效果拔群!所以說,如果只看Loss,不看最終評價指標,很容易做出誤判,錯過有用的方案,這對於其他問題來說也是成立的。
另外討論區也有人提到一種直接對F2 Score進行優化的方法,我們因為時間有限還沒來得及進行嘗試。
3.2 劃分訓練集和驗證集
一開始將官方的訓練數據(Train Data)劃分訓練集(Train Set)和驗證集(Validation Set),或者均勻劃分成K個部分,用於做K折交叉驗證(K-Fold Cross Validation)。關鍵的是,隨機劃分結果要隊伍內和方案間共享。不然的話,這個模型訓練用的K折劃分和那個模型訓練用的K折劃分不同,還怎麼嚴格比較它們之間的優劣呢?而且這也是為後面數據分析和模型的Ensemble(集成)做準備。
這一次我們將數據平均劃分了五折(編號0-4),使用一折作為驗證集,使用其他四個折作為訓練集,可以有五種組合。然後探索初期方案期間,只使用其中一種組合,例如將第0折作為驗證集,1-4折作為訓練集。
在模型確定後,如果想用上全部數據作為訓練,我們可以使用五種組合,每種組合用四折訓練一個模型(對應下圖中4個灰色大塊),在剩下的一個折作為驗證集預測(對應下圖中5個藍色小塊),遍歷五種組合後我們可以獲得每一個折的驗證集預測結果(還是對應下圖中5個藍色小塊),因為這些驗證結果都是從沒有在它們上面訓練過的模型預測出來的,我們把這個五個驗證折拼在一起的結果稱為out-of-fold,包含整個訓練集的驗證結果(對應下圖中的藍色長塊)。在out-of-fold上面進行閾值調優得到的F2-Score可以較好的代表模型的能力,可以真實地反映模型的泛化性能,多個模型的out-of-fold拼接在一起也可以作為第二階段的集成學習的輸入。
在上面的5種組合上做了5次訓練,測試的時候我們就有了5個模型,每個模型預測一遍測試集就得到了5個概率矩陣,每個概率矩陣的形狀都是(測試集樣本數 x 17)。我們可以將5個概率矩陣直接求平均後做二分類預測,也可以分別做完二分類預測,再做投票,來獲得最終的多類預測結果。這個結果實際上用到了所有5個折的訓練數據,會更加準確,也更加穩定。
當然如果只是想用上所有數據的話,更簡單的辦法就是直接把整個訓練集用這個模型跑一遍,再把訓練好的模型模型對測試集作預測。不過我們沒有採用這第二種方式,一來,所有訓練樣本都被這模型「看光了」,沒有額外的驗證集,難以評估其泛化性能;二來,我們認為第一種方法中,5個模型的預測結果做了個簡單的Ensemble,會更穩定一點。
折數劃得越多,訓練驗證所需要的計算力和時間也就越多,最好根據問題和自身計算力做一個權衡。
3.3 深度學習還是傳統方法
通過調查我們可以發現,Kaggle圖像比賽現在基本被深度學習方法所統治。雖然在一些細節上傳統方法還有發揮空間,但還是以CNN(卷積神經網路)為主體。
3.4 框架選擇與Model Zoo
雖然之前我主用TensorFlow,不過PyTorch提供的Model Zoo使用起來很方便,代碼比較輕量級,隊內會用的人數也比較多,所以這次比賽我們最終採用了PyTorch作為主體框架——除了隊內某個異端,他用TensorFlow為自己寫了一個高效的DataLoader。
PyTorch的Model Zoo提供了AlexNet,VGG, Inception v3, SqueezeNet, ResNet, DenseNet等架構的預訓練模型參數。我們還嫌這些模型不夠用,就嘗試了從TensorFlow上遷移過來的的Inception v4和Inception Res v2(Tensorflow-Model-Zoo.torch | GitHub) 。可惜的是,大概由於這兩個模型走的不是「正規渠道」,是「偷渡」過來的,大概哪裡出了偏差,總之訓練結果一塌塗地,果斷放棄。在這裡我們呼籲大家支持正版。
PyTorch文檔提供了不同模型在ImageNet上Top-1、Top-5的錯誤率,可以大概看出這些模型的能力,雖然這不一定和它們在比賽中的表現性能正相關。
在我們這次比賽中,ResNet表現最好,DenseNet緊隨其後,這不是偶然的。它們有一個引人注目的共同點,就是從底層到高層有Skip-Connection,其中ResNet採用的是兩路疊加,Densenet是多路拼接。為什麼重要呢?我們認為,第一是因為Skip-Connection可以自適應調節模型複雜度,避免過擬合,第二是因為17個類所利用的圖像特徵層次不同,比如Cloudy更偏向底層紋理特徵,Water和Road更偏向高層語義,而Skip-Connection有助於讓底層特徵到很高層仍然保留,而不會淹沒在幾十層網路的變換中。
稍弱一點是VGG和Inception v3,最弱的是SqueezeNet和AlexNet。從TensorFlow上遷移過來Inception v4和Inception Res v2基本上不收斂,再次呼籲大家支持正版。
3.5 預訓練或隨機初始化
我們一開始在ResNet-18這個輕量級的模型分別嘗試預訓練參數(Pretrained)和隨機初始化參數(From Scratch)進行訓練,結果發現,隨機初始化的模型的收斂速度比預訓練的模型要慢上十倍左右,最終收斂結果也差上一截。有隊友還試著自己設計一些網路架構,但結果也遠遠比不上預訓練模型。
所以做完這波實驗後,我們也大概確定這次的比賽,跑Model Zoo將是主要的手段。聽起來並不像自己設計網路結構那麼激動人心,但我們也可以在上面做些一些魔改,魔改之後也取得了意料之外的提升,具體見下一節。
3.6 預訓練模型使用與改動
使用預訓練模型的時候要注意,PyTorch文檔中說明了這些模型都是在224x224的圖像上進行預訓練,而且要求圖片要經過歸一化並減掉某個均值、除以某個方差,然後才輸入模型。如果想要模型能最大程度的利用預訓練的信息,一定要對我們輸入圖片也做同樣的操作。
不過雖然模型的輸入要求是224x224,但一部分模型(比如ResNet,DenseNet)的卷積層結束時會接一個Global Average Pooling,將每個通道的Feature Map求平均,這樣不管輸入的圖片尺寸多大,經過Global Average Pooling之後Feature Map的尺寸都會變成1x1,所以理論上是可以直接使用的。
然而這裡有一個坑點是,PyTorch預訓練模型卷積層最後其實使用是一個7x7固定大小的Average Pooling,並不是真正的Global AveragePooling。要想輸入其他尺寸大小的圖,我們應該把該層替換成AdaptiveAvgPool,並將輸出設置大小為1,這樣就能保證無論上一層Feature Map尺寸是多少,出來的尺寸都會是1x1。
當然,有可能一下子縮到1x1太小了,損失了太多信息,所以我們也可以把AdaptiveAvgPool輸出大小設置為2或3,使得輸出尺寸變成2x2和3x3,這樣的好處是保留下更多的信息。為了與之匹配,還需要改動後面整個全連接層的尺寸。我們後來在Densenet和ResNet上嘗試這個改動,取得了不錯的效果。
以上是針對ResNet和DenseNet來說的,而像VGG這種模型,最後Feature Map是直接Flatten(拉直)然後接全連接,我們如果要利用到後面的預訓練全連接層信息,我們就只能將輸入圖片縮放成224x224了。
除了更改Pooling輸出尺寸,另一個嘗試成功的魔改是關於全連接層的。ImageNet模型最後一層是1000類,而我們需要的是17類輸出,以往常見的做法是把最後一層全連接層換掉,換成一個output size為17的新全連接層,然後重新初始化它的參數。
然而,我們的兩個隊友卻因為偷懶發現了效果更好的做法,就是直接在預訓練模型的1000維輸出後面,直接就接上一個1000x17的全連接層。我們猜測,它效果好的原因是額外地保留下了全連接層的預訓練信息。
另外有些隊友擔心,這個比賽的大多數圖片都可能被預訓練模型識別為草地之類的ImageNet類別,所以可能基本上都只激活1000維中的少數幾個,會很稀疏,這樣其實應該是對訓練不利的。針對這種疑慮,我們將很多比賽圖片輸入預訓練模型後,發現它們在1000類上預測的概率值並不稀疏,所以應該沒太大問題。不過,這種新做法也可能只對這次比賽任務有效,在其他任務上還是建議先試著把最後一層全連接換掉或是整個隨機初始化,因為一般來說最後一層的可遷移性更差一點。
至於具體要怎麼在全連接層中加Batchnorm、Dropout就看個人選擇了,我們在發現這個任務上沒有太顯著影響,後面大部分模型都沒有加。
我們在探索Data部分的時候可以得知,四個天氣類會且只會出現一個,這很容易讓我們想到將這四個類單獨拿去來接一個Softmax層而不是Sigmoid層,使四個類概率和為1,預測的時候只預測最大概率的天氣類。但這樣做實際效果並不好,因為我們上面提到過這次比賽的評價指標是F2-Score,更希望有比較高的召回率而不是準確率,如果最高兩個天氣類非常接近,那把它們一起預測為正,雖然有一個肯定會猜錯,但卻可能可以取得更高的F2-Score,總體上反而是划算的。
3.7 學習率與Batch Size
關於模型的訓練,我們使用的是Adam作為優化器,因為它對學習率有一定程度的自適應微調,收斂速度快,而且對一些小類的更新也比較友好。我們嘗試了1e-2, 1e-3, 1e-4, 1e-5, 幾個範圍後,大致確定了1e-4是一個比較好的初始學習率,後面我們對不同的模型調整初始學習率都是對這個值乘以2、4倍或除以2、4倍,主要是隨著Batch Size等比例變化。
我們的Batch Size大概是在32到128之間,取決於GPU是否能裝得下多大。有時候我們也會將一些調低Batch Size到32做一下實驗。
3.8 數據增強(Data Augmentation)
圖像比賽的一個重頭戲就是數據增強,我們為什麼要做數據增強呢?
我們的訓練模型是為了擬合原樣本的分布,但如果訓練集的樣本數和多樣性不能很好地代表實際分布,那就容易發生過擬合訓練集的現象。數據增強使用人類先驗,盡量在原樣本分布中增加新的樣本點,是緩解過擬合的一個重要方法。
需要小心的是,數據增強的樣本點最好不要將原分布的變化範圍擴大,比如訓練集以及測試集的光照分布十分均勻,就不要做光照變化的數據增強,因為這樣只會增加擬合新訓練集的難度,對測試集的泛化性能提升卻比較小。另外,新增加的樣本點最好和原樣本點有較大不同,不能隨便換掉幾個像素就說是一個新的樣本,這種變化對大部分模型來說基本是可以忽略的。
一些常見的圖像數據增強方式有:
- 亮度,飽和度,對比度的隨機變化
- 隨機裁剪(Random Crop)
- 隨機縮放(Random Resize)
- 水平/垂直翻轉(Horizontal/Vertiacal Filp)
- 旋轉(Rotation)
- 加模糊(Blurring)
- 加高斯雜訊(Gaussian Noise)
對於這個衛星圖像識別的任務來說,最好的數據增強方法是什麼呢?顯然是旋轉和翻轉。具體來說,我們對這個數據集一張圖片先進行水平翻轉得到兩種表示,再配合0度,90度,180度,270度的旋轉,可以獲得一張圖的八種表示。以人類的先驗來看,新的圖片與原來的圖片是屬於同一個分布的,標籤也不應該發生任何變化,而對於一個卷積神經網路來說,它又是8張不同的圖片。比如下圖就是某張圖片的八個方向,光看這些我們都沒辦法判斷哪張圖是原圖,但顯然它們擁有相同的標籤。
其他的數據增強方法就沒那麼好用了,我們挑幾個分析:
- 亮度,飽和度,對比度隨機變化:在這個比賽的數據集中,官方已經對圖片進行了比較好的預處理,亮度、飽和度、對比度的波動都比較小,所以在這些屬性上進行數據增強沒有什麼好處。
- 隨機縮放:還記得我們在Overview和Data部分看到的信息嗎?這些圖片中的一個像素寬大概對應3.7米,也不應該有太大的波動,所以隨機縮放不會有立竿見影的增強效果。
- 隨機裁剪:我們觀察到有些圖片因為邊上出現了一小片雲朵,被標註了partly cloudy,如果隨機裁剪有可能把這塊雲朵裁掉,但是label卻仍然有partly cloudy,這顯然是在引入錯誤的標註樣本,有百害而無一利。同樣的例子也出現在別的類別上,說明隨機裁剪的方法並不適合這個任務。
一旦做了這些操作,新的圖片會擴大原樣本的分布,所以這些數據增強也就沒有翻轉、旋轉那麼優先。在最後的方案中,我們只用了旋轉和翻轉。並不是說其他數據增強完全沒效果,只是相比旋轉和翻轉,它們帶來的好處沒那麼直接。
3.9 增強數據集與訓練迭代數
按照一般的做法,數據增強的流程是一個Epoch一個Epoch地訓練整個訓練集,每次對輸入的樣本進行隨機的數據增強,這也是本次比賽大多數隊伍的做法。
但是我們卻採取了不同的做法,顯著縮小了訓練一個模型需要的時間,提高了我們在初期的方案迭代速度。首先,我們注意以下兩個點:
- 採用的旋轉和90度倍數的翻轉,很容易可以遍歷完所有八個情況,所以樣本量剛好就是擴充八倍;反之,像光照,飽和度,對比度這些狀態連續的數據增強,很難提前預計樣本量擴充多少倍才合理,所以必須在訓練過程中不斷地隨機增強。
- 模型如果第二次、第三次見到某個已經學得很好樣本,有可能會過擬合到該樣本,使驗證集Loss反增。
所以我們預先生成了八種方向的樣本,把訓練集擴充了八倍,再隨機打亂,再這些樣本都只訓練一遍就停止,相當於只跑了一個Epoch(當然這裡的一個Epoch的時間等於原來八個Epoch)。這樣做之後就保證每個樣本的8種方向都只被模型看過一遍,不給模型過擬合的機會,而且這樣在時間上也節省了許多。如果是按正常的隨機增強做法,可能你要等到很久之後才能把8個方向都隨機到,而在此之前又會讓模型多次見到同一樣本的同一方向,既浪費了時間,又增加了過擬合的風險。
在擴充的增強訓練集上使用Adam優化器進行訓練,我們觀察到模型在過完整個增強訓練集就收斂到一個接近最優的水平,然後繼續訓練下去驗證集就會開始收斂或反增,這也支持了我們「只掃一遍」的想法大致是正確的。不過,有隊友還是不滿足於只掃一遍,於是就有了下面的改進。
3.10 猛降50倍學習率,再過一遍訓練集
在Loss收斂的時候降低學習率繼續訓練,是深度學習一種常見的Trick。像我們上面那樣只將訓練集過一遍,會導致一些樣本只在前期模型還很不穩定的時候被見過,並沒有很好地被學習。所以我們也想到用降低學習率的方式,將訓練集再過一遍。一開始我們嘗試了常見的做法,即降低10倍學習率,但發現還是會很快過擬合,所以就放棄了。直到後來我們隊里有人試著將學習率降低50、100倍,可以讓模型在過第二遍訓練集的時候,既有第二次機會見到以前沒學好的樣本,又不會因為在已經學得很好的樣本上過度訓練而導致過擬合,將效果又提升了一截。
後面一直到比賽結束,我們都使用了這套做法,即用初始學習率將訓練集過一遍,再降低50倍學習率訓練第二遍,總的訓練時間相當於原來的2 x 8 = 16個Epoch,相比之下,討論區裡面我們看到其他隊伍採取傳統的數據增強方法,需要跑上二三十個Epoch。所以這套方法極大地節省了我們模型迭代和方案驗證的時間。
所以有的時候不是方法不行,只是你還不夠用力。
3.11 測試時數據增強(TTA / Test Time Augmentation)
上面我們提到訓練時怎麼使用數據增強,但是測試時數據增強(TTA)也可以對預測效果進行很大的提升。具體做法也比較簡單,我們可以將一個樣本的八個方向都進行預測,獲得八個預測概率,接著可以將八個概率直接平均,也可以使用預測的類標籤投票來獲得最後結果。通過幾輪測試,我們採取的是平均的方案,因為效果更好。
3.12 測試數據集的F2 Score閾值搜索
我們測試集的F2閾值是在out-of-fold的驗證預測結果上搜索選取的。對於驗證集我們也對每個樣本預測八個方向的結果,然後把它們拼接成一個32萬樣本的驗證集。我們觀察到,在這個集合上搜索得到的閾值,比把八個方向預測結果平均得到4萬樣本的驗證集上搜索得到的閾值有更好的泛化性能。
整體上我們觀察到的現象就是,搜索閾值時使用的樣本數越大,這個閾值的泛化性能很可能也就越好,對於小樣本來說,這個閾值很容易過擬合。想像只有一個樣本的時候,我們很容易可以找個一組閾值讓F2 Score為1.0。
有另一種調整閾值的方式是使得讓在out-of-fold驗證集上預測出來各個類的個數和它們的標籤中個數一樣。我們沒有嘗試這種做法,因為我們預測出來的各個類佔比和標籤中的佔比本來就十分接近。
3.13 結果的存儲、記錄和分析
到這裡,我大致已經介紹完我們訓練一個單模型流程,在開始介紹Ensemble(模型集成)前,我還是要介紹和強調一下結果的存儲、記錄和分析的重要性。
結果的存儲、記錄和分析是新手很容易忽略的一個環節,一開始如果沒注意好,到後面模型多起來的時候就容易手忙腳亂。
以下是我們這次比賽記錄的數值:
- 模型超參:預訓練模型類型,模型改動,輸入圖片大小,數據增強類型,Batch Size,學習率,迭代次數等;
- 評價結果:K折交叉驗證各個折的Loss,各個折的均值、方差,整個out-of-fold的Loss和F2-score,做完TTA的F2-score,Public Leaderboard的F2-score等。
我們希望,單模型本地out-of-fold的驗證集上的F2-Score,能夠較好地反映Public Leaderboarrd的F2-Score,這樣我們無需耗費寶貴的提交機會就能對新方案的效果進行大致評估。事實上這兩個F2-Score確實足夠相關,如下面的散點圖所示,雖然存在一些抖動,但整體上還是呈現一種正相關的關係。不過這裡out-of-fold是由五折各種的驗證集拼接在一起,八方向預測結果平均搜索閾值得到的F2-Score,後期我們發現八方向預測結果拼接的搜索得到的驗證F2-Score其實更加穩定,在Public Leaderboard的表現也更好。
最後,我們發現驗證和測試結果以及submission的格式的定義和文件名的管理也要注意,這一點我們隊伍內一開始沒有統一標準,比賽後期的合併結果和賽後的統計分析也花了一番功夫。
3.14 Ensemble:Average Bagging,Bagging Ensemble selection,Attention Stacking
我們這次比賽,使用了三種Ensemble, 關於Ensemble的基本套路可以參考《分分鐘帶你殺入Kaggle Top 1%》中模型集成(Ensemble)部分:
- 一開始模型比較少的時候,我們直接把不同模型的結果進行平均(Average Bagging)
- 到後面模型比較多的時候,我們開始使用Bagging Ensemble Selection。
- 最後我們使用了Stacking,我們這次用來做的Stacking演算法除了Logistic Regression、Ridge Regression,我們還試著自己設計了一種我們自己稱之為Attention Stacking的演算法。
在做Ensemble階段,對於每個樣本我們有一個(模型數 x 17)大小的概率矩陣,我們的目標是獲得一個長度17的概率向量。
對於三種Ensemble,我們對它們的建模分別為:
- Average Bagging:所有模型有相同的權重,將概率矩陣沿模型數維度進行平均。
- Bagging Ensemble Selection:每個模型有不同的權重,在Selection的過程中,有的模型可能被選到多次,有的模型也可能一次也沒被選到,按照被選中次數為權重,概率矩陣沿模型維度進行加權平均。
- Stacking:每個模型的每個類都有自己的權重,比如某個模型擅長對氣象類進行區分,卻對正常類性能很差,那顯然這個模型在氣象類和正常類的權重應該不一樣。我們需要對每個類別單獨學習一組函數或一組權重。
Logistic Regression、Ridge Regression是對輸入模型進行非線性的組合,為了探索其他可能性,我們也試著設計對輸入模型進行線性組合的模型。
我們稱之為Attention Stacking的模型相對比較簡單,對於每個類,我們初始化一組模型數長度的向量,對這個向量進行Softmax,我們就獲得一組求和為1權重,這樣我們對這個類別所有模型的預測概率按這組權重進行加權平均,就可以得到這個類別的預測結果。因為這種加權求和的形式和流行的Attention機制有點像,我們就叫它Attention Stacking,雖然它可能有其他更正式的叫法,但我們還沒時間仔細查文獻,所以暫且這麼稱呼。
Stacking階段我們按照單模型階段的五折劃分進行了交叉驗證,整個流程和單模型階段有點像。不過Stacking階段,按驗證集的F2-Score進行early stopping,在驗證集上求閾值的階段,我們有不同的兩套方案:
- 方案一:out-of-fold的做法,這個方案還是在out-of-fold上搜索閾值,要注意的一點是,每個折的模型的測試輸入要使用第一階段對應的折的預測結果,確保產生搜索閾值用的驗證集和測試集的輸入概率矩陣由相同的第一階段模型產生。
- 方案二:非out-of-fold的做法,下圖可以看做是一個將Attention Stacking每個類的權重拼在一起得到的矩陣,沿模型維度每列求和為1。因為Attention Stacking的做的其實是對每個類不同模型預測結果的一種線性組合,我們可以把五折求出來的五個權重矩陣直接平均獲得一個新的權重矩陣。然後用這個新的權重矩陣對所有訓練數據和測試數據進行加權平均,在加權平均的訓練數據上搜索閾值,應用在測試數據的加權平均結果上得到類預測。這種方案也保持了搜索閾值所用的集合與測試集預測結果產生的方式一致。
經過我們的測試中,方案二比方案一表現得更好。
到此,我們的方案也基本講解完畢。最後,我想給大家講講我們比賽中的一些經歷和對比賽結果的分析。
4. 學習,奮鬥,結果與偉大的隨機性
在上次參加Quora Question Pairs的過程中,我們在獲得一些文本類比賽實戰經驗的同時,也對Kaggle比賽的流程和基本方法有了一定的了解,並將經驗總結寫成了《分分鐘帶你殺入Kaggle Top 1%》。為了學習一些新的東西以及驗證我們對Kaggle比賽套路的理解,我們選擇了正在進行的 Planet: Understanding the Amazon from Space,這是一個圖像多標籤的分類任務,和Quora Question Pairs的文本二分類任務有很大不同。
4.1 學習與前進
在參加這個比賽前,我們隊里並沒有人有太多參加圖像比賽的經驗,關注到這個比賽的時候,三個月的比賽也只剩下一個月。一開始的十天,因為大家還有各種的項目工作沒有完結,只有兩三個隊友零星地探索,遊盪在Public LB一百多名。在剩下最後二十天的時候,我們陸續完成了手頭的工作,騰出了時間和計算資源,全力地參加這個比賽。我們能用的計算力大概是5-6塊Titan X,幾乎全力跑了十幾天。
我們花了一兩天搜集了這個比賽和其它類似比賽的信息,從中總結出了一些基本的套路。在探索和確定出基本方案後,我們隊內各自獨立地去實現和探索,每個人都有自己一套代碼。保持代碼和結果的獨立,主要是為了合隊的時候能夠有更多樣性的結果,這往往能給Ensemble結果帶來較大的提升。我們發現,即使在這樣一套不算複雜的解決方案中,大家對各種細節的理解也有很多不同,這些不同讓我們每次合隊時都有不小的提升。
其實一開始我們只是想試試能不能拿塊金牌,但我們很快發現,情況似乎有點失控。我們的方案似乎顯得格外有效,不到一周,我們就進入了金牌區,接著有的隊友只是對三四個模型Bagging了一下,就直接進入Public LB的前三,最誇張的時候,Public LB前五名中有三名是我們的隊的。
在最後一周前合隊完畢,我們竄到了第一名,從0.93396升到0.93421,這個Public LB分數到一周後比賽結束時,也只有五支隊伍能夠超過。
情況似乎非常順利,我和隊友們都感覺自己優勢很大,早已經不滿足於金牌,還想要留在前三,甚至幻想最終奪冠。懷著這樣的心態,我們來到了最後一夜,準備通宵戰鬥到早上八點結束。
4.2 其他隊伍
在前進的過程中,作為一隻新晉隊伍我們也關注著其他老牌隊伍,其中有些在最後一天來了個大爆發,給了我們很大壓力,下面就會說到。在此之前,先介紹其中幾個隊伍:
Kyle和我們隊伍里的一名隊員的ID重名,是上一次衛星圖像比賽的冠軍,在我們加入比賽的時候,他已經在第一名的位置盤踞許久。不過感覺他可能計算力資源不是很豐富,最後一周有點乏力,最後Private LB剛好留在金牌區內。
http://deepsense.io是上一次衛星圖像比賽的第四名,好像是一個做圖像的公司。
ZFTurbo是圖像類比賽活躍的GrandMaster(Kaggle頭銜),上一次衛星圖像比賽的亞軍,後面還與當時排名第三的Stanislav Semenov進行了組隊,這支隊伍十分強大。他們的隊名也很會玩,一開始懶得起名,直接叫做Team Name,他們在最後一天猛地提升到達了Public LB頂端之後,就改名為Russian Bears,一個帶著強烈戰鬥民族色彩的隊名,這讓我們嚴肅地考慮要不要改名為Chinese Panda / Chinse Dragon / Make China Great Again之類的,嗯,不過最後並沒有改。他們最後是Private LB第三名,留在了獎金池內。ZFTurbo賽後發布的一個拼圖Trick也十分有趣,方法是找到一副圖像切片周圍鄰接的切片,然後利用周圍切片作為上下文,一起對中央圖像進行預測。這個trick貌似是ZFTurbo在以往就慣用的套路了,看來他很熱衷於拼圖。
team-amazon-forest這支隊伍在評論區從頭到尾都十分活躍,尤其是Heng CherKeng,在討論區給大家提供了很多探索結果和技術細節。我們早期也從中獲得不少啟發,非常感謝他的分享,賽後討論區也出現了對他的感謝帖。不過可能因為他分享了太多,後期被後面很多新晉隊伍超過了,最後掉出金牌區。
Urucu隊里有Kaggle積分全站排名第一的Gilberto Titericz Junior,他們在比賽結束前十幾分鐘衝到了Public LB第三的位置,但卻在Private LB中掉出了金牌區,十分可惜。
Clear Sky隊伍里可能有一到兩個華人,實力也十分強勁,也是我們關注的對象。
bestfitting是一位名字開掛的選手,best fitting(最好的擬合),最後從Public LB的第九直接上升到了Private LB的第一,確實是best fitting。他的賽後方案總結也包含了很多值得學習的地方。
4.3 最後的戰鬥、結果分析和偉大的隨機性
我們因為參賽比較晚,經驗相對不足,一直到最後一天都還有很多Ensemble方案沒有來得及驗證。再加上機房在最後三天因為暴雨短路停了一天,我們到比賽結束前幾個小時才基本跑完了想要跑的大部分單模型。在等待單模型新鮮出爐的同時,隊里幾乎所有人都在通宵地驗證分析各種Ensemble方案。
在最後一天大家都只剩下5次Submission的機會,使用都十分謹慎,不像之前那麼隨意。一整個白天我們都在線下實現和驗證Ensemble方案,壓著不提交。我們還寫了一個腳本,時刻監控著Public LB的變化和前十幾名的Submission剩餘次數,看到排名靠前的很多隊伍也非常沉得住氣,前面12個小時基本都沒有提交,可以說變數非常地大。不過由於我們的分數與第二名的差距足足等於第二名到第九名的差距,所以我們也不怎麼著急。
然而,Russian Bears僅僅第一次提交就打破了我們的平靜,他們一舉從0.93320升到了0.93348,看上去跟我們的分數0.93348是一樣的,但是在後面沒顯示出來的小數位上贏了,佔據第一,給了我們很大壓力。我們心想,第一次提交就這麼誇張,後面那還得了?不過他們後面剩下的四次嘗試再也沒有提升,讓人暫時鬆了一口氣。
很快我們也嘗試提交了兩次,分別是不同的Ensemble方案,然而都沒能打破記錄,當時非常的緊張。經過討論,我們決定暫時先不冒險,而是想辦法回滾到前一天的代碼,在那份代碼上我們取得了當前的最佳分數0.93348。但是由於之前太過大意,管理這份實現的沒有記錄下來究竟是哪一次git commit上跑出了最佳效果,因為覺得後面肯定會跑出更好的結果,卻沒想到現在要靠這份Ensemble代碼來救場。
中間花了幾個小時,根據git log上面的提交時間、單模型文件的修改時間、微信聊天記錄之間的比對,該隊員終於戲劇性地恢復了之前的代碼。之前這份ensemble方案僅僅使用了57個單模型,加入新的單模型之後,不出意外地提升了,達到0.93449,重回Public LB第一。我們最後是用了64個模型進行Ensemble,一個程序員看起來十分舒服的數。
後面我們又在這份救場代碼上嘗試了兩種改進,但是都沒有再提升了。最後一份Submission文件生成完後,距離比賽結束還剩一個小時,我們非常惡趣味地等著看Russian Bears隊伍的最後兩次提交,然而他們提交了一次之後就不動了。一直等到最後半個小時我們實在等不下去,把最後一份Submission交了,結果才過了一分鐘他們也交了最後一個的Submission,似乎也是在惡趣味地等著我們。
Urucu也在最後十幾分鐘的時候提交了一個0.93444,到達第三,成功加入Public LB 0.93440+ 俱樂部。
早上8點一過,我們刷新出Private LB的排名是第六,當時就懵逼了。雖然我們早就知道會存在抖動,選擇的Submission也是在驗證集和Public LB上表現都比較好的,但抖動還是比我們預計的要大得多。最後幾天的提交基本在0.93430到0.93450之間,我們預估抖動可能會比0.0002大一點,因為Private LB只有兩萬樣本,但抖動在我們的Submission中的是0.001左右,大概我們預估的5倍左右。事實上,從BreakfastPirate的一個分析貼看,這次比賽Top 10%的隊伍的排名抖動程度(即Public LB和Private LB的差異)在整個Kaggle的歷史上也可以排上前十,非常誇張。
我們試著對這個結果進行了分析,下面是賽後對我們Submission進行分析畫的散點圖。
說明如下:
- 橫軸是Public LB Score, 縱軸是Private LB Score。
- 橘色的點代表單模型提交,藍色、紅色、黃綠色的點代表多模型Ensemble的提交,紅色的點是我們最後選中的兩個Submission,Kaggle會根據每個參賽隊伍選中的兩個Submission中Private LB分數最高的,來計算最終排名。黃綠色的點是比賽中因為提交次數限制沒有提交、賽後才提交的Submission。
- 藍色斜線是對線性擬合曲線。
- 銅色橫線以上是銅牌區,銀色橫線以上是銀牌區,金色橫線以上是金牌區,綠色橫線以上是獎金池。
可以看到,我們最後一周提交的Ensemble模型都在金牌區以內,甚至有3個單模型也進入其中,分別是ResNet50、ResNet101和ResNet152。我們最後一段時間有很多好的單模型沒有提交,它們中應該也有可以進入金牌區的。
我們賽中的提交有6個進入獎金池,其中最高一個的F2-Score為0.93322,比Private LB第一名bestfitting最後的Submission 0.93318還高一點,當然我們相信其他隊伍也應該和我們一樣,有一些更好的Submission但是沒有被選中。賽後提交的4個Submission中也有2個進入獎金池。
上圖可以看出Public LB到Private LB的抖動大概在0.001左右。
從Private LB第一的bestfitting的賽後方案總結看出,他對比賽的Public LB到Private LB可能的抖動(Shake up)使用模擬進行了估計,得出這個F2-Score的抖動大概在0.001-0.0025,而Public LB前面的隊伍的差別只有0.0005-0.001,所以最後的排名出現較大抖動也十分正常。從最後的結果看來他的估計也是挺準的。
造成這種抖動的原因應該是來著數據集中一些難以明確分類的樣本,也就是Data部分提到的即使是官方組織內部的專家也難以區分的樣本,比如河流和道路有時候完全分不清楚。這類樣本的標註基本是隨機的,讓同一個人重新標註都可能標得不同。
冠軍選手bestfitting的這種模擬抖動分析十分值得我們學習,因為這一方面可以避免自己過分關注微小的提升,另一方面,如果已經知道隨機抖動程度甚至都超過了前幾名之間的細微差距,那我們最終選兩個Submission時就不應該去理會Public LB最好的那個,而是先選一個穩妥方案的Submission,再從其他不錯的Submission中隨機選一個,把勝負交給偉大的隨機性來決定誰才是天選之人。
5. 隊伍成員介紹
我們隊伍總共6個人,都是中山大學潘嶸老師CIS實驗室的研究生(這也是我們隊名叫SYSU CISLab的原因),劉思聰、黃正傑、鄭華濱、張晉斌是研二的學碩,吳曉暉和蔣禮斌是研一的專碩,每個人的貢獻如下:
劉思聰( @劉思聰):主要負責模型設計、查找有用信息、隊內任務分配協調。設計了單模型訓練的基本流程,包括數據增強的類型和使用方式,發現Loss和F2-Score的相關性在Ensemble階段與單模型階段的不同,Ensemble階段的Attention Stacking的設計實現,單模型的調優,多次隨機搜索F2-Score閾值的方案設計。
黃正傑( @ChingKitWong):主要負責K折交叉驗證設計,實驗記錄的分析和管理,Bagging Ensemble Selection的實現,Attention Stacking方案一的實現,單模型的調優,嘗試使用進化計算搜索F2-Score的閾值。
鄭華濱( @鄭華濱 ):提出第二輪訓練猛降50倍學習率的做法並驗證其有效性。實現了F2-Score閾值搜索函數的GPU版本,大大加速了Ensemble階段根據F2-Score做early stopping的策略。設計實現了Attention Stacking方案二的設計和實現。對比了測試集F2-Score閾值的平均方案與拼接方案的效果差異。
張晉斌( @一壺酒兮真狂士 ):查找信息,探索其他可能的數據增強方法,嘗試Ridge Regression的Stacking。
吳曉暉( @吳曉暉 ):單模型調優,編寫Leaderboard監控程序,賽後數據的分析和探索,多次隨機搜索F2-Score閾值的方案實現與探索。
蔣禮斌( @蔣禮斌 ):修改模型結構,嘗試修改Resnet,DenseNet卷積最後Pooling層,提升單模型在Amazon任務上的表現,隊里最擅長單模型調優的人,最好的一批單模型基本都是他調出的。
結語
由於篇幅和時間限制,文中一些內容沒有詳細展開,對細節有疑惑或者發現有錯誤的地方,都歡迎大家在評論區指出。另外,我們創建了一個微信交流群,希望和有興趣的朋友一起交流Kaggle參賽經驗,因為群已經超過一百人,如果想加入,可以加我的微信(liusic_122587373 ),註明Kaggle交流,我會發送加群邀請。
(如需轉載,請私信聯繫)
推薦閱讀:
※2016 CCF大數據與計算智能大賽的開源資料整理
※數據挖掘系列篇(27):Kaggle 數據挖掘比賽經驗分享
※Kaggle入門系列:(一)機器學習環境搭建
TAG:机器学习 | 深度学习DeepLearning | Kaggle |